前言
刚开始写 Playwright 自动化脚本,你一定见过这种写法:
with sync_playwright() as p:
browser = p.chromium.launch()
...
很多教程一笔带过,让新手很懵:这个 with 是干嘛的?p 又是啥?
这篇就用人话,用最简单的比喻,帮你彻底搞明白。
一、先记住一个比喻:去网吧上网
你去网吧,前台给你一张临时上机卡 (就是那个 p)。
在网吧里,你可以用这张卡开机、打开浏览器、玩游戏......
不管你是正常下机,还是突然被老妈拎回家,前台最后都一定会把卡收回,电脑自动关闭。
对应到代码:
with sync_playwright() as p:
# 你拿了卡 p,在网吧里随便玩
browser = p.chromium.launch()
# 离开 with,卡被回收,浏览器全部关掉
就这么简单。
二、为什么要这样写?
核心好处就是两个字:省心。
如果你不用 with,就得自己开机和关机:
p = sync_playwright().start() # 手动开机
# ... 操作浏览器 ...
p.stop() # 必须手动关机
问题是:万一中间代码报错了,p.stop() 没执行,浏览器进程就会赖在后台不走,资源越占越多。
而 with 不管你中间怎么崩,最后保证一定会关机。这就是 Python 上下文管理器的保护机制。
三、p 到底是什么?
p 就是你进入 Playwright 世界的入口 。
有了它,你才能启动浏览器:p.chromium.launch()
有了它,你才能用火狐:p.firefox.launch()
可以把 p 理解成一张"万能遥控器",没有它,你什么都点不了。
四、异步版本一样道理
异步写法就是把 with 换成 async with,其他逻辑一模一样:
async with async_playwright() as p:
browser = await p.chromium.launch()
同样是进去给卡,出来收卡,只不过这个网吧支持"同时招呼好多人"。
五、结合pytest,示例:
用 pytest 写也是完全一样的思路:把 with sync_playwright() 当成自动帮你"开机 + 关机"的管家。
只不过 pytest 里大家一般不会在每个测试函数里都写一遍 with,那样每跑一个用例都会重新启动和关闭整个 Playwright 服务,很慢。
所以实际测试中,更常见的做法是用 fixture 共享浏览器,让 Playwright 在整个测试会话里只启动一次:
import pytest
from playwright.sync_api import sync_playwright
@pytest.fixture(scope="session")
def browser():
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
yield browser # 测试在这期间使用浏览器
# with 结束自动清理
def test_baidu(browser):
page = browser.new_page()
page.goto("https://www.baidu.com")
assert page.title() != ""
page.close()
但核心逻辑没变:with 照样在进场时帮你启动,出场时帮你关闭,只是我们把它提到 fixture 里,让所有测试共用一个浏览器,省时间。
简单讲就是:思路一样,只是挪到了 fixture 里复用,避免重复开机。
六、使用 pytest-playwright 插件
使用 pytest-playwright 插件时,你通常不需要再写 with sync_playwright() as p: 了。插件的 Fixture 体系已经接管了整个生命周期管理,能帮你自动启动和关闭浏览器,代码也更简洁。
✅ 优先推荐:使用内置 Fixture,告别手动 with
这是插件官方推荐的方式,也是我之前提到的建议。它的核心优势在于,无需手动管理 with 语句,测试代码会非常干净、专注。
from playwright.sync_api import Page
def test_baidu(page: Page): # 👈 直接注入 page fixture
page.goto("http://127.0.0.1:8000/practice")
page.get_by_role("textbox", name="姓名:").fill("小罗")
# ... 其他操作
page.pause()
这是最理想、被官方推荐的方式,因为它的代码最简洁,并且能无缝支持 --headed、--tracing 等所有命令行参数。
🔧 如果非要手动管理:可以用 with,但不与内置 Fixture 混用
如果你必须完全手动控制,可以彻底不用插件,纯手工编写测试:
python
from playwright.sync_api import sync_playwright
def test_baidu():
with sync_playwright() as p: # 👈 with 管理 playwright 对象
browser = p.chromium.launch()
page = browser.new_page()
page.goto("http://127.0.0.1:8000/practice")
page.get_by_role("textbox", name="姓名:").fill("小罗")
# ...
page.pause()
browser.close()
这种方式能保证资源的正确释放,但无法使用插件的 Fixture ,意味着 --tracing 等所有便捷特性都将失效。
🚦 with 与 Fixture:两种模式的对比与选择
下面这个表格可以帮助你快速理清两种方式的适用场景:
| 官方内置 Fixture ✅ 推荐 | 手动管理 with ⚠️ 特殊场景 |
|
|---|---|---|
| 玩法 | 直接开玩 ,只需安装 pytest-playwright,测试里用 page 这个现成的 Fixture 就行。 |
DIY 模式 ,需要自己在代码里写 with sync_playwright() as p。 |
| 代码量 | 极简,不关心浏览器怎么造、怎么关。 | 较啰嗦,with 和关闭步骤都得自己写。 |
| 清理 | 插件自动清理,不用担心资源泄露。 | 依赖 with 代码块,离开后自动关闭。 |
支持--tracing命令行 |
✅ 完全支持 | ❌ 不支持 |
| 适用场景 | 绝大多数自动化测试场景。 | 对启动细节有极致控制需求的少数场景 |
with sync_playwright() as p 是 Playwright 原始 API 的生命周期管理方式。但在 pytest-playwright 这个优秀的插件生态下,它已经为你做好了自动化的生命周期管理。你应该把精力集中在编写测试逻辑上,而不是重复处理浏览器的启停。
另外,page.pause() 在 pytest-playwright 插件中也能正常工作。你只需要用 --headed 命令开启有头模式,它就会启动 Playwright Inspector,帮助你逐步调试。
所以,结论是:pytest-playwright 完全可以使用上下文管理,只是你不需要再自己写了。
七、一句话总结
with sync_playwright() as p: 就是一个自动开机、自动关机的"网吧模式",帮你免去一切手动清理的烦恼。
记住这个比喻,以后写 Playwright 再也不用纠结这一行是干啥的了。