
本文详解如何使用 selenium 正确等待、定位并关闭由 javascript 动态生成的弹窗(如 angular 的 mat-dialog),避免“元素不可点击”等常见异常,提升自动化脚本稳定性。
本文详解如何使用 selenium 正确等待、定位并关闭由 javascript 动态生成的弹窗(如 angular 的 mat-dialog),避免“元素不可点击”等常见异常,提升自动化脚本稳定性。
在 Web 自动化测试或数据抓取中,频繁遇到点击按钮后弹出模态框(Popup/Dialog)的场景——例如 Angular Material 的 <mat-dialog> 组件。若未正确处理其生命周期,极易触发 ElementClickInterceptedException(如报错 “not clickable at point (684, 847). Other element would receive the click”),根本原因在于:弹窗 DOM 已存在但尚未渲染完成、遮罩层未就绪、或关闭按钮被其他元素覆盖。
✅ 正确关闭弹窗的三大核心原则
等待可见性,而非仅存在性
❌ 错误:presence_of_element_located 仅确认元素存在于 DOM,但可能仍不可见或未交互就绪。
✅ 正确:始终使用 visibility_of_element_located 或 element_to_be_clickable,确保元素已渲染、可见且可操作。-
优先使用显式等待(WebDriverWait),杜绝 time.sleep()
time.sleep(2) 是反模式:时间过短易失败,过长拖慢执行。应交由 Selenium 主动轮询状态:wait = WebDriverWait(driver, 10) # 复用同一实例,避免重复初始化 wait.until(EC.element_to_be_clickable((By.XPATH, "//span[text()='Close']"))).click()
-
关闭后验证弹窗是否真正消失
点击关闭按钮后,需确认弹窗 DOM 已被移除(stale)或不可见,防止后续操作误触残留元素:close_btn = driver.find_element(By.XPATH, "//span[text()='Close']") close_btn.click() wait.until(EC.staleness_of(close_btn)) # 等待该元素从 DOM 中消失 # 或 wait.until(EC.invisibility_of_element_located((By.ID, "mat-dialog-3")))
? 优化后的完整实践代码(含关键注释)
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import csv
# 启动浏览器(Selenium 4.6+ 内置驱动管理,无需 WebDriverManager)
driver = webdriver.Chrome()
driver.maximize_window()
wait = WebDriverWait(driver, 10) # 全局复用等待实例
try:
driver.get("https://mmiconnect.in/app/exhibition/catalogue/ep2023")
# 【步骤1】关闭初始浮层(如有)
try:
wait.until(EC.element_to_be_clickable((By.CLASS_NAME, "close-button"))).click()
except:
pass # 无初始弹窗则跳过
# 【步骤2】定位所有“View details”按钮(动态获取数量,避免硬编码)
detail_buttons = wait.until(
EC.visibility_of_all_elements_located((By.XPATH, "//button//span[text()='View details']"))
)
all_data = []
# 【步骤3】逐个点击并处理弹窗
for i, btn in enumerate(detail_buttons):
btn.click() # 触发弹窗
# 等待弹窗内表格完全加载(关键:等待表格可见 + 内容非空)
table = wait.until(EC.visibility_of_element_located((By.ID, "customers")))
# 可选:等待表格首行内容渲染完成(防 Angular 延迟绑定)
wait.until(lambda d: d.find_element(By.CSS_SELECTOR, "#customers td.field-content").text.strip())
# 【步骤4】提取数据
rows = table.find_elements(By.TAG_NAME, "tr")
page_data = []
for row in rows:
cells = row.find_elements(By.TAG_NAME, "td")
row_data = [cell.text.strip() for cell in cells if cell.text.strip()]
if row_data: # 过滤空行
page_data.append(row_data)
all_data.extend(page_data)
# 【步骤5】精准关闭弹窗
close_btn = wait.until(EC.element_to_be_clickable((By.XPATH, "//span[text()='Close']")))
close_btn.click()
wait.until(EC.staleness_of(close_btn)) # 确保弹窗 DOM 已销毁
# 【步骤6】一次性写入 CSV(大幅提升性能)
with open("mmei.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerows(all_data)
finally:
driver.quit()⚠️ 关键注意事项
- 定位策略优先级:By.XPATH 和 By.CSS_SELECTOR 比 By.CLASS_NAME 更稳定(类名易变,文本或属性更可靠)。示例中 //span[text()='Close'] 比依赖 aria-label 更健壮。
- 异常处理粒度:避免 except Exception: 吞没真实错误。应捕获特定异常,如 TimeoutException(等待超时)、NoSuchElementException(元素未找到),便于快速定位问题。
- 资源清理:务必在 finally 块中调用 driver.quit(),确保浏览器进程彻底释放。
- Angular 应用特别提示:MatDialog 默认使用 *ngIf 控制显示,因此 staleness_of 比 invisibility_of_element_located 更准确——它验证元素是否已被 Angular 从 DOM 移除。
遵循以上方法,即可稳定、高效地关闭各类动态弹窗,让 Selenium 脚本从“偶发失败”走向“生产就绪”。


















