简介:这份PDF面向Python自动化测试初学者与从Selenium转型的测试工程师,系统讲解Playwright的环境搭建与快速上手路径。内容从Selenium 1.0到4.0的发展脉络切入,梳理Cypress、Puppeteer、TestCafe等后Selenium时代框架的定位差异,进而说明Playwright支持Chromium、Firefox、WebKit并统一API的优势。随后覆盖pip安装playwright、浏览器驱动配置、同步API启动浏览器访问页面的完整流程,并展开headless模式、智能等待机制、录制生成脚本、page.pause()断点调试与Playwright Inspector定位等实用技能。资源包为1个PDF文件,约916KB,篇幅精炼、目录清晰,适合按章节顺序通读或作为环境配置速查手册。目前已有2092人学习下载,可帮助读者快速完成环境准备、写出首个自动化脚本,并建立对主流测试框架选型的整体认知。
1. 环境准备与快速开始:Playwright+Python 自动化测试的第一道门槛
很多人第一次接触 Playwright,是因为被 Selenium 的环境配置折磨过——驱动版本对不上、浏览器启动慢、等待写到吐。Playwright 出现后,官方直接把浏览器内核打包进安装流程,一条命令就能拉起 Chromium、Firefox 和 WebKit。但“能装”和“能用”之间,还隔着 Python 版本、虚拟环境、异步模式选择这几道坎。这篇笔记面向的是准备用 Playwright+Python 搭自动化测试框架的工程师,不管你是刚转自动化,还是从 Selenium 迁过来,第一章要解决的就是:把环境装干净,把第一个脚本跑通,并且知道每一步为什么这么做。上海悠悠那份资料里提到的“环境准备与快速开始”,核心痛点其实就三个——装得对、跑得起来、看得懂报错。
2. Playwright 与 Python 的版本匹配:别让环境成为黑匣子
2.1 为什么 Python 版本和 Playwright 版本必须一起看
Playwright 的 Python 绑定对解释器版本有硬性要求。截至我写这篇笔记时,Playwright 的 Python 包要求 Python 3.8 及以上,但如果你用的是 3.8,部分新版本 Playwright 已经不再支持。常见做法是直接上 Python 3.10 或 3.11,这两个版本在 Windows、macOS 和主流 Linux 发行版上都有预编译轮子,装起来不折腾。
另一个容易被忽略的点是 Playwright 自身的版本迭代速度。它不像某些库半年不更新,Playwright 基本每个月都有小版本,浏览器内核版本也跟着走。如果你在团队里做自动化,一定要把playwright的版本号写进requirements.txt或pyproject.toml,不要用pip install playwright不带版本号就完事。我一般会锁定到具体小版本,比如playwright==1.44.0,这样别人克隆你的仓库时不会因为自动升级到新版本导致浏览器行为差异。
提示:如果你在 CI 里跑 Playwright,Python 版本和 Playwright 版本要在本地和 CI 环境保持一致,否则会出现“本地能跑、CI 报错”的玄学问题。
2.2 用虚拟环境隔离依赖:venv 还是 conda
不管你是做测试脚本还是搭测试平台,虚拟环境是必须的。Python 自带的venv足够用,没必要为了 Playwright 单独上 conda。下面是我常用的初始化流程:
# 创建项目目录并进入 mkdir playwright-demo && cd playwright-demo # 创建虚拟环境,指定 Python 3.11 python3.11 -m venv .venv # 激活虚拟环境(Windows 用 .venv\Scripts\activate) source .venv/bin/activate # 升级 pip 并安装指定版本的 Playwright pip install --upgrade pip pip install playwright==1.44.0 # 安装浏览器内核 playwright install这段命令里有两个关键点。第一,python3.11 -m venv .venv明确指定了解释器版本,避免系统里多个 Python 版本时搞混。第二,playwright install会下载 Chromium、Firefox 和 WebKit 三个浏览器,默认下载到用户目录下的缓存文件夹。如果你只想装 Chromium,可以写成playwright install chromium,能省不少下载时间。
参数说明:playwright install后面可以跟浏览器名称,也可以加--with-deps在 Linux 上自动安装系统依赖。如果你在 Docker 里跑,--with-deps基本是必加的,否则会缺一堆共享库。
2.3 验证安装是否成功:三行代码跑通第一个页面
装完之后别急着写复杂脚本,先用三行代码确认环境没问题:
from playwright.sync_api import sync_playwright with sync_playwright() as p: # 启动 Chromium,headless=False 可以看到浏览器界面 browser = p.chromium.launch(headless=False) page = browser.new_page() page.goto("https://example.com") # 打印页面标题,确认能正常获取内容 print(page.title()) browser.close()逻辑说明:sync_playwright()是同步模式的入口,适合刚上手和写简单脚本。p.chromium.launch()启动浏览器实例,headless=False让你能看到浏览器窗口,方便调试。page.goto()打开目标地址,page.title()返回页面标题。如果这一步能打印出Example Domain,说明环境完全就绪。
参数说明:launch()方法支持很多参数,常用的有headless(是否无头模式)、slow_mo(每步操作放慢毫秒数,调试用)、args(传给浏览器内核的启动参数)。新手建议先用headless=False和slow_mo=500,能直观看到每一步在做什么。
3. 同步还是异步:Playwright 两种 API 的选型与落地
3.1 同步 API 和异步 API 的本质区别
Playwright 的 Python 绑定同时提供同步和异步两套 API。同步 API 底层其实也是异步实现的,只是用 greenlet 做了封装,让你写起来像同步代码。异步 API 则直接暴露async/await,适合已经在用 asyncio 的项目。
选型建议很简单:如果你只是写测试脚本、做页面抓取、跑自动化流程,同步 API 完全够用,代码可读性也更好。如果你要把 Playwright 集成到 FastAPI、aiohttp 这类异步框架里,或者需要同时控制多个浏览器上下文做并发,那就用异步 API。
我见过不少团队一开始用同步 API,后来想加并发发现改造成本很高。所以如果你预判未来会有并发需求,一开始就上异步 API 也不是坏事。但如果你只是做单线程的 UI 自动化测试,同步 API 能让你少踩很多坑。
3.2 同步模式的最小可运行模板
下面是一个同步模式的完整模板,包含了页面导航、元素定位、断言和截图:
from playwright.sync_api import sync_playwright, expect def run_test(): with sync_playwright() as p: # 启动浏览器,设置视口大小 browser = p.chromium.launch(headless=True) context = browser.new_context(viewport={"width": 1280, "height": 720}) page = context.new_page() # 导航到目标页面 page.goto("https://example.com", wait_until="networkidle") # 定位元素并断言文本 heading = page.locator("h1") expect(heading).to_have_text("Example Domain") # 截图保存,方便排查 page.screenshot(path="example.png", full_page=True) # 关闭上下文和浏览器 context.close() browser.close() if __name__ == "__main__": run_test()逻辑说明:browser.new_context()创建独立的浏览器上下文,相当于一个干净的会话,cookie 和缓存互不干扰。page.goto()的wait_until参数控制等待策略,networkidle表示网络空闲后再继续,适合页面有异步加载的场景。expect()是 Playwright 自带的断言库,比手动写assert更稳定,它会自动重试直到超时。
参数说明:viewport设置视口大小,影响响应式布局的页面。full_page=True在截图时截取整个页面,而不只是可视区域。wait_until可选load、domcontentloaded、networkidle、commit,默认是load。
3.3 异步模式的最小可运行模板
异步模式的代码结构略有不同,但核心逻辑一致:
import asyncio from playwright.async_api import async_playwright, expect async def run_test(): async with async_playwright() as p: browser = await p.chromium.launch(headless=True) context = await browser.new_context(viewport={"width": 1280, "height": 720}) page = await context.new_page() await page.goto("https://example.com", wait_until="networkidle") heading = page.locator("h1") await expect(heading).to_have_text("Example Domain") await page.screenshot(path="example_async.png", full_page=True) await context.close() await browser.close() if __name__ == "__main__": asyncio.run(run_test())逻辑说明:所有 Playwright 的调用前都要加await,async with替代with。asyncio.run()是入口。如果你在 Jupyter Notebook 里跑,直接用await run_test()就行,不用asyncio.run()。
参数说明:异步 API 的参数和同步 API 完全一致,区别只在调用方式。如果你要并发多个页面,可以用asyncio.gather()同时跑多个run_test()协程。
4. 浏览器内核与上下文管理:把资源管住,别让内存泄漏
4.1 Chromium、Firefox、WebKit 该选哪个
Playwright 支持三大浏览器内核,但实际项目中不需要三个都跑。选型依据是你的目标用户用什么浏览器。如果只做内部系统测试,Chromium 足够,它也是 Playwright 默认下载的。如果要做跨浏览器兼容性测试,再考虑加 Firefox 和 WebKit。
我一般会在本地开发时用 Chromium,CI 里跑 Chromium + Firefox 两个。WebKit 在 Linux 上依赖比较多,如果 CI 环境是精简的 Docker 镜像,装 WebKit 容易翻车。常见做法是本地用 Chromium 快速迭代,CI 里用 Chromium 跑主流程,Firefox 跑冒烟测试。
注意:
playwright install默认下载三个浏览器,如果你确定不用 Firefox 和 WebKit,用playwright install chromium可以节省大量下载时间和磁盘空间。
4.2 browser、context、page 三层结构的关系
Playwright 的资源模型是三层:browser是浏览器进程,context是浏览器上下文(相当于一个隐身窗口),page是具体标签页。一个 browser 可以有多个 context,一个 context 可以有多个 page。
这种设计的好处是隔离性。每个 context 有独立的 cookie、localStorage 和缓存,互不影响。做多用户测试时,给每个用户开一个 context 就行。做并发测试时,可以在一个 browser 里开多个 context,比开多个 browser 省资源。
from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch(headless=True) # 创建两个独立上下文,模拟两个用户 context1 = browser.new_context() context2 = browser.new_context() page1 = context1.new_page() page2 = context2.new_page() page1.goto("https://example.com") page2.goto("https://example.com") # 两个页面的 cookie 互不干扰 print(page1.context.cookies()) print(page2.context.cookies()) context1.close() context2.close() browser.close()逻辑说明:browser.new_context()创建独立上下文,context.new_page()创建标签页。两个 context 的 cookie 完全隔离,适合模拟多用户场景。
参数说明:new_context()支持user_agent、viewport、locale、timezone_id等参数,可以模拟不同设备和地区。做国际化测试时,locale和timezone_id很有用。
4.3 用 with 语句管理资源,避免忘记关闭
手动调close()容易漏,尤其是脚本报错时。用with语句可以确保资源释放:
from playwright.sync_api import sync_playwright with sync_playwright() as p: with p.chromium.launch(headless=True) as browser: with browser.new_context() as context: with context.new_page() as page: page.goto("https://example.com") print(page.title())逻辑说明:with语句在代码块结束时自动调用close(),即使中间抛异常也能保证资源释放。嵌套with看起来有点啰嗦,但比手动管理可靠得多。
参数说明:sync_playwright()本身也支持with,它会自动清理 Playwright 的底层资源。如果你在 pytest 里用 fixture,可以把这些with封装到 fixture 的yield前后。
5. 避坑与排查:新手最容易翻车的五个场景
5.1 浏览器下载失败或超时
现象:执行playwright install时卡住,或者报错Download failed。
原因:浏览器内核文件比较大,Chromium 大概 150MB,Firefox 和 WebKit 加起来超过 300MB。网络不稳定时容易下载失败。
解决:可以设置环境变量PLAYWRIGHT_DOWNLOAD_HOST指向国内镜像源,或者分多次安装,先装 Chromium,再装其他。如果公司网络有代理,确保HTTP_PROXY和HTTPS_PROXY环境变量正确设置。
5.2 页面加载完了但元素找不到
现象:page.goto()返回了,但page.locator()定位不到元素,报TimeoutError。
原因:goto()默认等待load事件,但很多现代页面是异步渲染的,load触发时元素还没挂载。
解决:把wait_until改成networkidle,或者用page.wait_for_selector()显式等待元素出现。更推荐的做法是用expect(locator).to_be_visible(),它会自动重试。
5.3 headless 模式下截图是白屏
现象:headless=True时截图全白,headless=False正常。
原因:某些页面在无头模式下渲染时机不同,或者字体加载有问题。
解决:加--disable-gpu和--no-sandbox启动参数,或者设置device_scale_factor。如果还不行,试试page.wait_for_timeout(1000)强制等一秒再截图。
5.4 在 Docker 里跑报缺少共享库
现象:CI 的 Docker 容器里跑 Playwright 报libnss3.so找不到。
原因:Playwright 的浏览器依赖一堆系统库,精简镜像里没有。
解决:用playwright install --with-deps自动装依赖,或者基于官方镜像mcr.microsoft.com/playwright/python构建。如果自己写 Dockerfile,记得装libnss3、libatk1.0-0、libatk-bridge2.0-0这些包。
5.5 同步 API 在 Jupyter 里报事件循环错误
现象:在 Jupyter Notebook 里用同步 API,报It looks like you are using Playwright Sync API inside the asyncio loop。
原因:Jupyter 本身运行在 asyncio 事件循环里,同步 API 和它冲突。
解决:在 Jupyter 里改用异步 API,或者用nest_asyncio打补丁。我一般直接在 Notebook 里用异步 API,代码结构更干净。
6. 从快速开始到可持续维护:一个可复用的初始化脚本
6.1 把环境检查写进脚本,减少“在我机器上能跑”
每次换机器或新人入职,环境问题最耗时间。我习惯写一个check_env.py,把 Python 版本、Playwright 版本、浏览器可用性都检查一遍:
import sys import subprocess from playwright.sync_api import sync_playwright def check_python_version(): # 要求 Python 3.10 及以上 if sys.version_info < (3, 10): raise RuntimeError(f"需要 Python 3.10+,当前是 {sys.version}") print(f"Python 版本: {sys.version}") def check_playwright_version(): import playwright print(f"Playwright 版本: {playwright.__version__}") def check_browser_launch(): with sync_playwright() as p: for name in ["chromium", "firefox", "webkit"]: try: browser = getattr(p, name).launch(headless=True) browser.close() print(f"{name}: 可用") except Exception as e: print(f"{name}: 不可用 - {e}") if __name__ == "__main__": check_python_version() check_playwright_version() check_browser_launch()逻辑说明:check_python_version()用sys.version_info做版本比较,低于 3.10 直接抛异常。check_browser_launch()遍历三个浏览器,逐个尝试启动,启动失败就打印错误信息。这个脚本可以放进 CI 的初始化步骤,提前发现问题。
参数说明:getattr(p, name)动态获取浏览器类型,避免写三遍重复代码。launch(headless=True)用无头模式,启动更快。
6.2 用 pytest 组织第一个测试用例
Playwright 官方推荐用 pytest 做测试运行器。装pytest和pytest-playwright后,可以这样写:
import pytest from playwright.sync_api import Page, expect @pytest.fixture(scope="function") def test_page(page: Page): # page fixture 由 pytest-playwright 提供 page.goto("https://example.com") return page def test_example_title(test_page: Page): expect(test_page.locator("h1")).to_have_text("Example Domain") def test_example_screenshot(test_page: Page): test_page.screenshot(path="pytest_example.png")逻辑说明:pytest-playwright插件自动提供pagefixture,每个测试函数拿到独立的 page。test_pagefixture 在page基础上做导航,测试函数直接断言。
参数说明:scope="function"表示每个测试函数都新建 page,隔离性最好。如果测试之间需要共享登录状态,可以把 scope 改成session,但要注意状态污染。
6.3 一个我踩过的坑:别在 fixture 里做太多事
我早期写 fixture 时,喜欢把登录、导航、数据准备全塞进去,结果一个 fixture 几百行,报错时根本不知道哪一步挂了。后来改成每个 fixture 只做一件事,登录一个 fixture,导航一个 fixture,数据准备一个 fixture,测试函数按需组合。这样排查问题时,看 fixture 名字就知道哪一步出错。
另一个习惯是:所有 fixture 里加日志。Playwright 的page.on("console")和page.on("requestfailed")可以捕获浏览器控制台输出和失败请求,排查问题时非常有用。我一般会在 conftest.py 里加一个全局 fixture,自动挂上这些监听器。
希望帮到你。
本文还有配套的精品资源,点击获取