Playwright自动化测试实战:从环境搭建到网络拦截与调试
2026/9/23 2:45:50 网站建设 项目流程

简介:Playwright自动化测试实战文档,围绕微软出品的Playwright测试框架展开,面向测试工程师、Python开发者以及自动化测试入门者,系统讲解基于Python的Playwright应用。内容先从环境搭建入手,覆盖Windows、macOS、Linux三大系统下PyCharm安装、pip升级、pytest-playwright安装,以及Chromium、Firefox、WebKit内置浏览器下载,可帮助读者快速扫清跨浏览器自动化测试的环境障碍。随后通过百度首页实战案例,逐步演示输入内容、点击“百度一下”按钮、Web断言、命令行参数配置、录制用例与base_url使用,引导完成第一条自动化脚本。进阶部分还整理了click、hover、下拉框、导航等常用方法实践,并给出调试技巧、性能优化与最佳实践建议,同时点出与传统测试框架的差异。资源以单个docx格式文件打包,大小约17.16MB,目录结构清晰、章节衔接连贯,既能通读学习,也可作为日常查询手册。目前已有375人学习下载,适合希望系统掌握Playwright并落地到实际项目的读者。

1. 为什么说 Playwright 已经不只是“另一个 Selenium”

很多团队还在用 Selenium 维护一套端到端测试,每天下午你就开始盯着 rerun 的用例,点开截图看是不是又因为“元素不可交互”挂掉了。这种状态下,Playwright 的出现不是换一个 API 那么简单。它把等待机制内置进每个操作,把网络请求拦截、多标签页、iframe 这些过去要额外写一堆 helper 的能力全部做成了原生 API,代码量能砍掉三分之一到一半。下面这套方案,就是围绕实际落地讲的:怎么搭最小工程、怎么控制启动参数、怎么处理动态页面和接口依赖,最后怎么保证失败之后能快速定位。适合做自动化测试的工程师、爬虫方向的人,以及准备面试时想用 Playwright 讲清楚原理的测试开发。

2. Playwright 自动化测试的最小环境与启动参数

Playwright 支持 Python、Node.js、Java、.NET 等多种语言,但自动化测试生态里,Python 和 pytest 绑定最紧,所以下面的示例都用 Python。环境安装很简单,但很多东西藏在参数里,只有理解了浏览器实例和上下文之间的关系,才能写出隔离性好、不互相影响的用例。

2.1 安装 Playwright 与浏览器内核

安装分两步,两条命令缺一不可:

pip install playwright playwright install chromium

第一条安装控制库,第二条把浏览器内核下载到本机缓存。pip install playwright不会附带浏览器二进制,所以如果你只装了这个就启动浏览器,会遇到“Executable doesn’t exist”的错误。需要同时下载内核,而且官方推荐用chromium作为默认内核,日常回归只需要跑它一个就够了,不必把 firefox、webkit 都装一遍。

安装完成后验证一下版本:

playwright --version

如果输出了版本号,说明控制库可用。CI 环境里下载慢的话,可以把浏览器缓存目录提前打到镜像里,或者设置PLAYWRIGHT_BROWSERS_PATH指向固定目录,避免每次构建都重新下载。

2.2 用 sync_playwright 启动浏览器的最小代码

一个能跑通的最小脚本长这样:

from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch( headless=False, slow_mo=300, ) page = browser.new_page() page.goto("https://example.com") print(page.title()) browser.close()

sync_playwright是一个上下文管理器,负责启动驱动进程和清理资源。chromium.launch负责拉起浏览器实例,headless=False表示有头模式,方便观察;slow_mo=300让每个操作之间延迟 300ms,模拟真人操作的速度,适合调试阶段。browser.new_page()会创建新的标签页,goto会等页面触发load事件,但注意它不保证所有接口都返回完毕,所以后面还需要显式等待。

2.3 启动参数与上下文配置

launch管的是浏览器进程,而context才是隔离的会话环境。常见做法是在new_context里传入浏览器配置,同一个浏览器实例可以开出多个互不干扰的上下文,它们的 cookie、localStorage 是完全独立的。

参数类型默认值作用
headlessboolTrue是否无头运行,CI 必须为True
slow_moint每次操作后的延迟毫秒数,调试用
viewportdict1280x720窗口尺寸,影响响应式布局
user_agentstr自定义 UA,部分页面会做 UA 校验
localestren-US浏览器语言,影响 i18n 文案
timezone_idstr指定时区,比如Asia/Shanghai
permissionslist授权定位、摄像头等权限
ignore_https_errorsboolFalse忽略证书错误,测试环境常用

ignore_https_errors在测试环境几乎必设,因为自签名证书会让页面直接加载失败。permissions可以用来授予地理位置权限,不用真的去点浏览器弹窗。localetimezone_id适合做国际化断言,比如切换到中文环境后判断“登录成功”这个文案是否显示。这些参数放在new_context里比放在launch里更合理,因为一个浏览器进程可以开出多个不同配置的上下文,并行用例之间不会被 cookie 串号。

3. 页面操作与元素定位:从 codegen 到选择器优先级

页面操作的核心是“定位元素 → 执行动作 → 断言结果”。Playwright 的定位 API 比 Selenium 更贴近用户行为,而且内置自动等待。很多人一上来就点录屏,确实能快速跑通,但真正到了写用例和排查时,还是得理解选择器的优先级和等待机制。

3.1 用 codegen 快速生成脚本

录制工具是第一步。命令行:

playwright codegen --channel=chrome https://example.com

--channel=chrome会调用本机安装的 Chrome,而不是 Playwright 自带的 Chromium。这种做法有两个好处:一是本机 Chrome 更接近真实用户,登录态可以复用;二是有些内网页面只对 Chrome 版本有依赖。如果不指定,默认用 Playwright 的 Chromium,效果也一致。弹出的浏览器窗口里,右侧会自动生成操作代码,语言可以切成 Python 或 JavaScript。

codegen 生成的脚本只能当草稿,不能直接作为测试用例。原因有几点:生成的选择器往往是#app > div > div > .btn这种长 CSS 路径,页面结构一变就挂;录制的坐标点击没有语义;没有断言,无法判断页面是否真的进入了预期状态。我的做法是拿录制结果当元素线索,然后手动改成稳定的 role 或 text 选择器。

3.2 选择器优先级:role、text、CSS、XPath

Playwright 支持多种选择器,建议按这个顺序选:

选择器示例使用场景
rolepage.get_by_role("button", name="提交")按可访问性语义定位
textpage.get_by_text("登录成功")按文本内容定位
CSSpage.locator("[data-testid='user-name']")有稳定测试属性时使用
XPathpage.locator("//input[@id='xxx']")保底方案,不优先

get_by_role是首选,因为按钮、链接、输入框这些语义是用户和屏幕阅读器都依赖的,样式改变不会影响测试。get_by_text适合定位提示文案或列表项,但要注意文本可能被拆成多个子节点,这时可以用has_text做局部匹配。CSS 里优先选择>page.get_by_role("textbox", name="用户名").fill("admin") page.get_by_role("button", name="登录").click() page.get_by_text("欢迎回来").wait_for()

fill会先找到可见文本框,清空原来的值再输入。click会自动等待元素稳定、可点击后才真正点击,不需要手动加time.sleepwait_for是显式等待,默认超时 5 秒,可以传timeout=10000覆盖。

3.3 用 pytest 组织用例与断言

如果在测试项目里用 pytest,官方推荐安装pytest-playwright插件。装完之后可以直接用pagefixture:

def test_login(page): page.goto("https://example.com/login") page.get_by_label("用户名").fill("admin") page.get_by_label("密码").fill("password") page.get_by_role("button", name="登录").click() expect(page.get_by_text("欢迎回来")).to_be_visible()

这里的expect来自playwright.sync_api,断言会不断重试,直到超时。to_be_visible()默认在 5 秒内每 100ms 检查一次元素可见性,所以页面加载慢也不会秒失败。断言失败时,输出里会带上元素的快照,方便定位问题。注意get_by_labelget_by_rolename参数匹配的是可访问名称,不是value属性,所以表单里的<label>标签一定要写的规范,否则这两个 API 会找不到元素。

4. 监听页面请求与动态 iframe:高级拦截与执行上下文

很多测试用例想验证的不只是界面上的文字,还有某个操作后前端有没有发出正确的接口请求,或者后端返回特定数据时页面怎么渲染。这类场景用 Selenium 做起来很绕,因为要额外引入代理工具。Playwright 直接内置了网络事件监听和路由拦截,几行代码就能 mock 接口。

4.1 监听页面请求与响应

监听请求和响应用page.on事件:

def test_track_api(page): requests = [] responses = [] page.on("request", lambda req: requests.append(req.url)) page.on("response", lambda res: responses.append((res.url, res.status))) page.goto("https://example.com/login") page.get_by_role("button", name="登录").click() assert any("api/login" in url for url in requests)

page.on("request")会在每次请求发出前触发,page.on("response")在响应到达后触发。这里用同步 API 的 lambda 做记录足够,但要注意 handler 里不要写耗时操作,因为事件回调是串行执行的,太慢会拖慢整个测试。

更精确的写法是page.expect_response

with page.expect_response("**/api/login") as resp_info: page.get_by_role("button", name="登录").click() resp = resp_info.value assert resp.status == 200

这个块会把“点击”和“等待响应”绑定起来,不会漏掉点击之后的网络请求,比 sleep 后再去查列表靠谱得多。

4.2 拦截并修改接口返回

如果后端接口在测试环境不可用,可以直接 mock 掉。比如登录接口返回的 token 是动态的,你想固定用一个测试 token:

def handle_route(route): route.fulfill(json={"token": "test-token", "username": "admin"}, status=200) page.route("**/api/login", handle_route) page.goto("https://example.com/login") page.get_by_role("button", name="登录").click()

page.route的 URL 匹配规则支持 glob 模式,**匹配任意路径。route.fulfill会直接伪造一个响应,不再走到真实网络。这个能力在做异常场景测试时也很好用,比如模拟接口 500:

page.route("**/api/stock", lambda route: route.abort("failed"))

route.abort会终止请求,前端拿到的就是网络失败错误,适合测试错误提示分支。

需要注意的是,后注册的 route 会覆盖前面匹配到的规则。如果你希望先处理一部分请求、剩下的放行,需要在 handler 里调用route.continue_()

4.3 动态 iframe 里的元素定位

iframe 一直是 Selenium 的痛点,切换 frame 要写switch_to.frame,嵌套一多很容易乱。Playwright 用frame_locator处理,不需要全局切换:

frame = page.frame_locator("#dynamic-frame") frame.get_by_role("button", name="提交").click()

frame_locator返回一个独立的作用域,所有定位都在目的 iframe 内查找。对于动态加载的 iframe,需要先保证 iframe 出现在 DOM 里。常见做法是:

locator = page.locator("#dynamic-frame") locator.wait_for()

frame_locator本身不会自动等 iframe 出现,但如果你在它下面定位某个元素,它会等到元素被找到为止。所以更直接的方式是:

page.frame_locator("#dynamic-frame").get_by_text("加载完成").wait_for()

这段代码会在 iframe 里等 “加载完成” 这个文本出现。如果 iframe 内容来自另一个域名,也不用考虑跨域问题,Playwright 在系统层面驱动浏览器,不受页面 JS 的同源策略限制。这在爬虫场景搭配 scrapy-playwright 时也一样,iframe 的加载时序最终会反映到元素可见性上,等待策略要写在交互附近,不要写在开场后很久的位置。

5. 回归排错提升:trace viewer 与滚动、下载的固定套路

自动化测试用例一多,失败之后的定位就显得格外重要。Playwright 的 trace viewer 能记录每一步操作的截图、DOM 快照和网络请求,失败时直接回放,比看日志猜原因高效得多。

browser = p.chromium.launch() context = browser.new_context() context.tracing.start(screenshots=True, snapshots=True) page = context.new_page() page.goto("https://example.com") # 测试到这里如果失败 context.tracing.stop(path="trace.zip") browser.close()

跑完测试后在命令行执行:

playwright show-trace trace.zip

这里注意,screenshots=True会为每个步骤保存截图,snapshots=True会保存 DOM 快照。trace 文件随着步骤增多会变大,建议只在失败用例里动态开启 tracing,而不是所有用例都开,否则 CI 上的产物会膨胀。

另一个经常遇到的问题就是滚动和文件下载。Playwright 的click默认会把元素滚动到可视区域,所以大多数情况下不用手动滚动。但有些页面是懒加载的,比如无限列表,需要滚到底部才能触发下一页加载,这时用:

page.mouse.wheel(0, 1000)

或者更直接地用:

page.evaluate("window.scrollBy(0, 1000)")

wheel会模拟鼠标滚轮事件,更接近用户操作,适合触发滚动监听器;evaluate直接调用 JS,适合快速跳转。下载文件则用expect_download

with page.expect_download() as dl_info: page.get_by_role("link", name="下载报表").click() download = dl_info.value download.save_as("/tmp/report.xlsx")

expect_download会捕获浏览器的下载事件,文件会先保存到临时目录,你再把它移动到指定路径。注意不要用goto触发下载,这会阻塞页面导航。

在 CI 里跑的时候,我习惯把PLAYWRIGHT_BROWSERS_PATH=/tmp/playwright加在流水线环境变量里,这样并行任务之间不会抢同一个浏览器缓存目录,能省掉不少偶发的 “Executable doesn’t exist” 报错。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询