如何在检测到特定alert弹窗时终止Playwright的整个网页请求?
解决Playwright处理弹窗无限循环导致程序冻结的问题
我正在编写Playwright脚本,测试一个会弹出多个alert弹窗的JavaScript页面。因为测试对象是不懂pytest输出的初学者,所以直接调用Playwright。
页面可能会进入alert弹窗无限循环,我可以通过弹窗的特定文本消息检测这个循环。我已经在on("dialog")中设置了事件处理程序,能识别出触发问题的弹窗,但没法让Playwright终止页面加载,导致浏览器陷入无限循环,测试程序直接冻结。
试过几种方法都无效:
- 在事件处理程序里调用
sys.exit(1),请求被拦截,无法终止程序; - 把
goto调用包装进任务,在处理程序里调用task.cancel(),没法阻止事件处理程序重复触发; - 调用
page.close()能阻止处理程序再次调用,但会导致上下文无法正常退出,异常无法被正确捕获,首次运行有效但后续失效,甚至主程序里的sys.exit(1)也被拦截。
概念验证代码
测试用HTML文件
<html> <head> <title>JavaScript Example</title> <script> "use strict"; function start() { window.alert("Legitimate alert") while (true) { window.alert("Infinite loop starting"); } } </script> </head> <body onLoad="start()"> <p>Javascript Test Page</p> </body> </html>
现有测试程序
import re import socket import sys from playwright.sync_api import sync_playwright, Error, expect page = None failed = False def catch_failed_request(req): if (req.failure == "net::ERR_CONNECTION_REFUSED"): print("Connection refused. Please check that server is running.") else: print("Unexpected playwright request failure ", req.failure) def goto_url(page, url): global failed try: res = page.goto(url) if (res.status == 404): print("URL",url," not found. Please check that your file is in the correct directory and the name exactly matches.") sys.exit(1) if (res.status != 200): print("Unexpected status code",res.status) sys.exit(1) except Error: failed = True def expect_prompt(d): global failed global page if (failed): print("Prompt callback occuring when page supposed to be closed") #d.accept() -- Causes "target closed" exception. return if (d.message == "Infinite loop starting"): failed = True print("Trying to stop page load") d.accept() page.close() return print("Regular alert:",d.message) d.accept() if __name__ == "__main__": with sync_playwright() as p: browser = p.chromium.launch() con = browser.new_context() con.on("requestfailed", catch_failed_request) page = con.new_page() page.on("dialog", expect_prompt) goto_url(page,"http://localhost:5000/static/html/poc.html") if failed: sys.exit(1) else: sys.exit(0)
运行时会出现大量异常,最终主程序中的sys.exit(1)因为"在异常处理期间执行"被拦截。
解决方法
核心思路是在检测到无限循环弹窗时,立即关闭浏览器上下文而非仅关闭页面,同时通过抛出自定义异常来中断同步流程,避免后续代码执行冲突。
修改后的测试代码
import sys from playwright.sync_api import sync_playwright, Error # 自定义异常,用于中断同步执行流程 class InfiniteAlertLoop(Exception): pass def catch_failed_request(req): if req.failure == "net::ERR_CONNECTION_REFUSED": print("连接被拒绝,请检查服务器是否运行。") else: print(f"Playwright请求意外失败: {req.failure}") def goto_url(page, url): try: res = page.goto(url) if res.status == 404: print(f"URL {url} 未找到,请检查文件路径和名称是否正确。") sys.exit(1) if res.status != 200: print(f"意外的状态码: {res.status}") sys.exit(1) except Error: # 捕获页面关闭导致的异常,不做额外处理 pass def handle_dialog(d): if d.message == "Infinite loop starting": print("检测到弹窗无限循环,终止测试...") d.accept() # 直接关闭上下文,彻底终止所有页面相关操作 d.page.context.close() # 抛出异常中断同步流程 raise InfiniteAlertLoop("弹窗无限循环触发终止") print(f"正常弹窗: {d.message}") d.accept() if __name__ == "__main__": try: with sync_playwright() as p: browser = p.chromium.launch() context = browser.new_context() context.on("requestfailed", catch_failed_request) page = context.new_page() page.on("dialog", handle_dialog) goto_url(page, "http://localhost:5000/static/html/poc.html") sys.exit(0) except InfiniteAlertLoop: sys.exit(1)
关键修改点
- 自定义异常:创建
InfiniteAlertLoop异常,在检测到循环时抛出,直接中断同步执行流程,避免后续代码冲突。 - 关闭浏览器上下文:调用
d.page.context.close()直接关闭上下文,彻底终止该上下文下的所有页面操作,从根源上停止弹窗循环。 - 简化全局状态:去掉全局的
failed和page变量,通过弹窗对象直接获取上下文,避免全局状态带来的逻辑混乱。 - 精准异常捕获:在主流程中仅捕获自定义异常,确保程序正常退出,不会出现"异常处理期间执行"的拦截问题。
内容的提问来源于stack exchange,提问作者Mark Green
相关产品推荐
相关产品推荐

