Playwright Python 版入门:和 JS 版不一样的那几处

2026-08-18

搜 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-playwrightuv add pytest-playwright。装完包还要再装浏览器:

playwright install

第二条是纯 library 线,写在 docs/src/library-python.md 里,用于把 Playwright 当通用浏览器自动化工具(爬取、截图、批处理脚本),不跑测试。它装的是另一个包:

pip install --upgrade pip
pip install playwright
playwright install

注意两条线装的包名不同:pytest-playwrightplaywright。文档给的升级命令也是分开的,插件线升级要把两个包一起带上: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.tsdocs/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 名文档里的说明
functioncontext本次测试的新 browser context
functionpage本次测试的新 browser page
functionnew_context在一个测试里创建多个 context,用于多用户场景
sessionplaywrightPlaywright 实例
sessionbrowser_type当前浏览器的 BrowserType 实例
sessionbrowserPlaywright 启动的 Browser 实例
sessionbrowser_name浏览器名,字符串
sessionbrowser_channel浏览器 channel,字符串
sessionis_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 syncpython 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 的取值是 onoffretain-on-failure 三个,--screenshot 的取值是 onoffonly-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 一节开头就写明:这些参数只作用于默认的 browsercontextpage 三个 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 签名、配置项与默认值随版本变动,请以仓库最新内容为准。 本文不涉及浏览器版本矩阵与版本清单,相关信息请以官方发布说明为准。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。