Playwright Python 版入门:和 JS 版不一样的那几处
搜 Playwright 教程,翻十篇有九篇是 JS 版的。你照着敲 npm init playwright@latest,发现自己项目里根本没有 package.json;好不容易找到 Python 的安装命令装上了,又发现教程里让你改的 playwright.config.ts 在你的目录里压根不存在。
这几处不一样不是文档没写,而是写在了另一个文件里。Playwright 仓库的 docs/src/ 目录下,文件名带 -js / -python / -java / -csharp 后缀的是该语言专属页,不带后缀的是共用页。Python 的入门写在 docs/src/intro-python.md,跑测试写在 docs/src/running-tests-python.md,插件参考写在 docs/src/test-runners-python.md,纯库用法写在 docs/src/library-python.md。JS 版的 docs/src/intro-js.md 与它们是并列关系,内容并不互相覆盖。
docs/src/languages.md 把这层关系说得很直白:各语言共享同一套底层实现,浏览器自动化的核心能力都支持,不一样的是测试生态的集成方式。所以你从 JS 教程里学到的 locator、断言、自动等待这些概念可以直接迁移,会绊倒你的是外面那一圈脚手架。
前置条件:先分清你要装哪一条线
Python 侧有两条互不相同的安装路径,选错了后面全乱。
第一条是 Pytest 插件线,也是 docs/src/intro-python.md 明确推荐的写端到端测试的方式。文档写明这个插件自带 context 隔离和多浏览器配置。它的安装命令是:
pip install pytest-playwright
用 Poetry 或 uv 的话,文档里给的分别是 poetry add pytest-playwright 和 uv add pytest-playwright。装完包还要再装浏览器:
playwright install
第二条是纯 library 线,写在 docs/src/library-python.md 里,用于把 Playwright 当通用浏览器自动化工具(爬取、截图、批处理脚本),不跑测试。它装的是另一个包:
pip install --upgrade pip
pip install playwright
playwright install
注意两条线装的包名不同:pytest-playwright 和 playwright。文档给的升级命令也是分开的,插件线升级要把两个包一起带上:pip install pytest-playwright playwright -U。
至于最低 Python 版本和受支持的操作系统,docs/src/intro-python.md 有一节 System requirements 专门列,Windows、macOS、Linux 发行版都写了。这类数值会随版本调整,装之前请直接照仓库那一节核一遍,别信任何二手清单——包括这篇。
步骤一:没有 config 文件,配置分散在两处
JS 侧 npm init playwright@latest 会生成 playwright.config.ts,docs/src/intro-js.md 写明它集中管理目标浏览器、超时、retries、projects、reporters。Python 侧没有这个东西。想固化配置,仓库给的是两处:
一处是 pytest 自己的 ini 文件,docs/src/test-runners-python.md 给的原文示例是:
# content of pytest.ini
[pytest]
# Run firefox with UI
addopts = --headed --browser firefox
另一处是 conftest.py 里覆盖 fixture。比如忽略 HTTPS 证书错误,文档给的写法是覆盖 browser_context_args:
import pytest
@pytest.fixture(scope="session")
def browser_context_args(browser_context_args):
return {
**browser_context_args,
"ignore_https_errors": True
}
也就是说,JS 里改一个字段的事,Python 里要么写进 addopts,要么写成一个覆盖 fixture。这是我认为最容易让人卡住的一处——你在文档里搜「config」搜不到东西,因为它根本不叫这个名字。
步骤二:fixture 名字是硬约定
docs/src/test-runners-python.md 的 Fixtures 一节按作用域列了插件提供的 fixture。用法是把 fixture 名当参数写进测试函数签名,名字写错就是普通的 pytest 参数错误,不会有 Playwright 相关的提示。
| 作用域 | fixture 名 | 文档里的说明 |
|---|---|---|
| function | context | 本次测试的新 browser context |
| function | page | 本次测试的新 browser page |
| function | new_context | 在一个测试里创建多个 context,用于多用户场景 |
| session | playwright | Playwright 实例 |
| session | browser_type | 当前浏览器的 BrowserType 实例 |
| session | browser | Playwright 启动的 Browser 实例 |
| session | browser_name | 浏览器名,字符串 |
| session | browser_channel | 浏览器 channel,字符串 |
| session | is_chromium / is_webkit / is_firefox | 对应浏览器类型的布尔值 |
这张表要横着读:function 作用域的三个在测试函数请求时创建、测试结束时销毁;session 作用域的那几个在第一次被请求时创建、所有测试结束时销毁。所以 page 每个用例都是新的,而 browser 是整个会话共用的。
另外三个是用来改启动参数的:browser_type_launch_args 覆盖 launch 的参数,browser_context_args 覆盖新建 context 的参数,connect_options 用于通过 WebSocket 端点连接已有浏览器。文档写明前两个都要返回 Dict。
单个用例想临时改 context 选项,不必写 fixture,文档给了 marker 写法:@pytest.mark.browser_context_args(timezone_id="Europe/Berlin", locale="en-GB")。同一节里还有 @pytest.mark.skip_browser("firefox") 和 @pytest.mark.only_browser("chromium")。括号里那些值是仓库示例里的取值,换成你自己的即可。
步骤三:同步和异步是两套 import
这是 Python 绑定独有的一处分叉。docs/src/library-python.md 写明 Playwright 支持同步和异步两种 API 变体,如果你的项目用 asyncio,就该用异步那套。两套的入口 import 不同:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://playwright.dev")
print(page.title())
browser.close()
import asyncio
from playwright.async_api import async_playwright
async def main():
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page()
await page.goto("https://playwright.dev")
print(await page.title())
await browser.close()
asyncio.run(main())
断言也分两套。docs/src/api/class-locatorassertions.md 里同一个例子给了 python sync 和 python async 两个代码块:同步版 from playwright.sync_api import Page, expect 然后 expect(...).to_have_text("Submitted"),异步版 from playwright.async_api import Page, expect 然后 await expect(...).to_have_text("Submitted")。方法名一样,前面多不多 await 取决于你 import 的是哪个模块。
写 pytest 用例时,插件默认给的 page 是同步的。要写异步用例,docs/src/test-runners-python.md 的 Async Fixtures 一节写明要另外装 pytest-playwright-asyncio,并且要求 pytest-asyncio>=0.26.0、在配置里设 asyncio_default_test_loop_scope = session,用例上标 @pytest.mark.asyncio(loop_scope="session")。这几项是仓库当前文档里的要求,随版本可能变动。
以上代码取自仓库文档的示例,为按仓库代码中的接口语义组合的示例,未经实测,以仓库最新代码为准。
步骤四:CLI 参数替代了 config 里的一部分
docs/src/running-tests-python.md 里跑测试就是 pytest。改浏览器和显示模式靠命令行参数:
pytest --browser webkit --headed
--browser 可以出现多次,docs/src/test-runners-python.md 的 CLI arguments 一节还列了 --browser-channel、--slowmo、--device、--output、--tracing、--video、--screenshot、--full-page-screenshot。其中 --tracing 与 --video 的取值是 on、off、retain-on-failure 三个,--screenshot 的取值是 on、off、only-on-failure 三个;这些取值和它们各自的默认值都写在那一节里,默认值随版本可能变动,以仓库最新内容为准。
并行也和 JS 侧不同。JS 的 test runner 自带并行,Python 侧文档写明要装 pytest-xdist,然后用 --numprocesses 参数:
pytest --numprocesses auto
文档同时提醒:这个值可以从 2 设到机器的 CPU 数,设得太高可能出现预期之外的行为;docs/src/running-tests-python.md 里的建议是取逻辑核数的一半。
以上为按仓库文档中的参数语义组合的示例,未经实测,以仓库最新内容与 --help 的实际输出为准。
边界:文档明写的几处限制
CLI 参数不是全局生效的。 docs/src/test-runners-python.md 在 CLI arguments 一节开头就写明:这些参数只作用于默认的 browser、context、page 三个 fixture;如果你用 API 自己 new 了一个 context 或 page,CLI 参数不会应用上去。这一条很反直觉,调试时容易白找半天。
Windows 上的 asyncio event loop 有硬限制。 docs/src/library-python.md 的 Known issues 一节写明:Playwright 把 driver 跑在子进程里,所以在 Windows 上需要 asyncio 的 ProactorEventLoop,因为 SelectorEventLoop 不支持异步子进程。文档同时记录了在 Windows Python 3.7 上 Playwright 会把默认 event loop 设成 ProactorEventLoop,理由是 Python 3.8+ 上它本来就是默认。如果你的框架自己换了 event loop 策略,这一处要先排查。
API 不是线程安全的。 同一节写明,多线程环境下应该每个线程创建一个 playwright 实例。
别用 time.sleep()。 文档写明它会导致状态过期,如果确实要等,用 page.wait_for_timeout(5000)——括号里是仓库示例中的值,不是推荐值;文档本身的措辞是更希望你别等。
配 unittest.TestCase 有限制。 docs/src/test-runners-python.md 给了搭配写法,同时写明这种用法只能指定单个浏览器,指定多个也不会生成多浏览器矩阵。
本文引用的这几页文档里,我们没有看到标为 experimental 或 deprecated 的条目。浏览器由 playwright install 一并安装、版本与 Playwright 版本绑定,具体版本请以官方发布说明为准,本文不涉及。
怎么验证装对了
按 docs/src/intro-python.md 的约定建一个文件,文件名和函数名都要以 test_ 开头——比如 test_example.py:
import re
from playwright.sync_api import Page, expect
def test_has_title(page: Page):
page.goto("https://playwright.dev/")
# Expect a title "to contain" a substring.
expect(page).to_have_title(re.compile("Playwright"))
然后依次跑:pytest 跑通说明插件和浏览器都装上了;pytest --headed 能弹出浏览器窗口说明 headed 模式可用;pytest --browser webkit --browser firefox(这个多次传 --browser 的写法出自 docs/src/running-tests-python.md)能跑起来,说明这两个引擎的二进制也在本地了;默认的 chromium 前一步已经验证过。
想确认调试链路,docs/src/running-tests-python.md 给了三种 shell 的写法。Linux/macOS:
PWDEBUG=1 pytest -s
Windows 的 cmd:
set PWDEBUG=1
pytest -s
Windows 的 PowerShell:
$env:PWDEBUG=1
pytest -s
文档写明这会同时打开浏览器窗口和 Playwright Inspector,可以单步走 API 调用,Inspector 里的 Pick Locator 能选中元素看 Playwright 会用什么 locator。另外插件参考页里还提到,直接在用例里写 breakpoint() 就能进 pdb。
如果浏览器要装到指定目录(比如 CI 缓存或 C 盘不够),docs/src/browsers.md 给的 Python 侧写法在三种 shell 下分别是 PLAYWRIGHT_BROWSERS_PATH=$HOME/pw-browsers python -m playwright install、cmd 下 set PLAYWRIGHT_BROWSERS_PATH=%USERPROFILE%\pw-browsers 后再 playwright install、PowerShell 下 $Env:PLAYWRIGHT_BROWSERS_PATH="$Env:USERPROFILE\pw-browsers" 后再 playwright install。装完去那个目录看有没有生成浏览器目录,就知道环境变量吃上了没有。
最后提醒一句:Playwright 会启动真实浏览器并执行页面脚本、读写存储状态,codegen 的 --save-storage 之类能力还会把会话数据落盘。跑第三方站点或复用已有浏览器 profile 时,请自行评估这层边界。安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。
本文依据 github.com/microsoft/playwright 仓库于 2026-08-18 的公开内容整理,
事实来自仓库内的文档与源码。我们没有对文中涉及的功能做过实测,
因此不涉及运行速度、稳定性与实际表现的任何描述。
该项目迭代频繁,文中涉及的 API 签名、配置项与默认值随版本变动,请以仓库最新内容为准。
本文不涉及浏览器版本矩阵与版本清单,相关信息请以官方发布说明为准。