You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在检测到特定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)

关键修改点

  1. 自定义异常:创建InfiniteAlertLoop异常,在检测到循环时抛出,直接中断同步执行流程,避免后续代码冲突。
  2. 关闭浏览器上下文:调用d.page.context.close()直接关闭上下文,彻底终止该上下文下的所有页面操作,从根源上停止弹窗循环。
  3. 简化全局状态:去掉全局的failed和page变量,通过弹窗对象直接获取上下文,避免全局状态带来的逻辑混乱。
  4. 精准异常捕获:在主流程中仅捕获自定义异常,确保程序正常退出,不会出现"异常处理期间执行"的拦截问题。

内容的提问来源于stack exchange,提问作者Mark Green

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 06:34:56