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

Playwright Python捕获异常并优雅退出的问题咨询

解决Playwright脚本在CICD中无法干净退出的问题

问题场景

在CICD流程中通过subprocess()调用Playwright Python脚本时,触发KeyboardInterrupt(如按<ctrl+c>)或出现异常时,脚本无法干净退出,会出现两类报错:

关闭浏览器时报错

Traceback (most recent call last):
  File "C:\dist\testing\exit-test.py", line 59, in <module>
    browser.close()
  File "C:\dist\venvs\testing\Lib\site-packages\playwright\sync_api\_generated.py", line 13927, in close
    return mapping.from_maybe_impl(self._sync(self._impl_obj.close(reason=reason)))
                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\dist\venvs\testing\Lib\site-packages\playwright\_impl\_sync_base.py", line 115, in _sync
    return task.result()
           ^^^^^^^^^^^^^
  File "C:\dist\venvs\testing\Lib\site-packages\playwright\_impl\_browser.py", line 192, in close
    raise e
  File "C:\dist\venvs\testing\Lib\site-packages\playwright\_impl\_browser.py", line 189, in close
    await self._channel.send("close", {"reason": reason})
  File "C:\dist\venvs\testing\Lib\site-packages\playwright\_impl\_connection.py", line 59, in send
    return await self._connection.wrap_api_call(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\dist\venvs\testing\Lib\site-packages\playwright\_impl\_connection.py", line 514, in wrap_api_call
    raise rewrite_error(error, f"{parsed_st['apiName']}: {error}") from None
Exception: Browser.close: Connection closed while reading from the driver

直接退出时的异步残留报错

Exception ignored in: <function BaseSubprocessTransport.__del__ at 0x00000242299C2A20>
Traceback (most recent call last):
  File "C:\dist\python312\Lib\asyncio\base_subprocess.py", line 126, in __del__
  File "C:\dist\python312\Lib\asyncio\base_subprocess.py", line 104, in close
  File "C:\dist\python312\Lib\asyncio\proactor_events.py", line 109, in close
  File "C:\dist\python312\Lib\asyncio\base_events.py", line 795, in call_soon
  File "C:\dist\python312\Lib\asyncio\base_events.py", line 541, in _check_closed
RuntimeError: Event loop is closed
Exception ignored in: <function _ProactorBasePipeTransport.__del__ at 0x00000242299E8220>
Traceback (most recent call last):
  File "C:\dist\python312\Lib\asyncio\proactor_events.py", line 116, in __del__
  File "C:\dist\python312\Lib\asyncio\proactor_events.py", line 80, in __repr__
  File "C:\dist\python312\Lib\asyncio\windows_utils.py", line 102, in fileno
ValueError: I/O operation on closed pipe
Task was destroyed but it is pending!
task: <Task pending name='Task-12' coro=<Locator.click() running at C:\dist\venvs\testing\Lib\site-packages\playwright\_impl\_locator.py:156> wait_for=<Future pending cb=[Task.task_wakeup()]> cb=[SyncBase._sync.<locals>.<lambda>() at C:\dist\venvs\testing\Lib\site-packages\playwright\_impl\_sync_base.py:111, ProtocolCallback.__init__.<locals>.cb() at C:\dist\venvs\testing\Lib\site-packages\playwright\_impl\_connection.py:191]>

解决方法

核心是确保Playwright资源的生命周期被正确管理,同时处理异步任务的残留问题:

  • 使用Playwright的上下文管理器(with语句)自动管理playwright实例、浏览器、页面,避免手动关闭时的连接问题
  • 全局捕获KeyboardInterrupt和所有异常,确保资源清理完成后再退出
  • 避免在循环内直接调用sys.exit(),改为标记退出状态,统一完成清理后退出

修改后的完整代码

#!/usr/bin/env python3
"""
pip install playwright
playwright install
playwright install-deps
"""
import argparse, json, platform, sys, time
from playwright.sync_api import sync_playwright

url = "https://www.google.no"
parser = argparse.ArgumentParser(description='playwright example')
parser.add_argument('--browser', help='browser type', choices=['chrome','firefox','msedge','webkit'], default='chrome')
parser.add_argument('--headed', help='set to set headed with browser', action='store_true')
args, unknown_args = parser.parse_known_args()

if unknown_args:
    print(f"unknown parameters: {unknown_args}")
    sys.exit(-1)

args.headless = not args.headed

if platform.system() != 'Darwin' and args.browser == 'webkit':
    print("webkit only supported on apple")
    sys.exit(-1)

exit_code = 0
try:
    # 使用上下文管理器自动管理Playwright实例
    with sync_playwright() as p:
        t0 = time.time()
        if args.browser == 'firefox':
            browser = p.firefox.launch(headless=args.headless)
        elif args.browser == 'chrome':
            browser = p.chromium.launch(channel='chrome', headless=args.headless)
        elif args.browser == 'msedge':
            browser = p.chromium.launch(channel='msedge', headless=args.headless)
        elif args.browser == 'webkit':
            browser = p.webkit.launch(headless=args.headless)
        
        # 使用上下文管理器自动管理浏览器和页面
        with browser, browser.new_page() as page:
            searches = ["python", "playwright", "selenium", "requests", "beautifulsoup"]
            for search in searches:
                t1 = time.time()
                page.goto(url)
                try:
                    page.get_by_role("button", name="Godta alle").click(timeout=1000)
                except Exception:
                    pass
                
                page.get_by_label("Søk", exact=True).type(search)
                page.get_by_label("Søk", exact=True).press("Enter")
                page.locator("#tsf").get_by_role("button", name="Søk", exact=True)
                time.sleep(1)
                page.screenshot(path=f"pics/google-{search}.png")

except KeyboardInterrupt:
    print("Received interrupt, exiting gracefully...")
    exit_code = 1
except Exception as e:
    print(f"Error occurred: {str(e)}")
    exit_code = -1

sys.exit(exit_code)

关键修改点说明

  1. 上下文管理器替代手动启动/关闭:

    • with sync_playwright() as p:自动启动和停止Playwright服务,避免手动调用p.start()和p.stop()的遗漏
    • with browser, browser.new_page() as page:自动关闭浏览器和页面,无需手动调用browser.close(),确保资源被正确释放
  2. 全局异常捕获:

    • 顶层try-except块捕获KeyboardInterrupt和所有异常,确保无论触发何种中断,都能完成资源清理后再退出
    • 移除循环内的sys.exit(),改用exit_code标记退出状态,最后统一退出
  3. 简化逻辑:

    • 将args.headless = True改为args.headless = not args.headed,简化判断逻辑
    • 移除循环内的异常捕获,统一到顶层处理,避免嵌套异常导致的清理失败

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 23:52:12