避开 Playwright 常见陷阱,让你的 UI 测试更快更稳

简介: 本文总结Playwright UI自动化测试中8大高频陷阱:硬编码等待、脆弱定位器、重渲染竞态、重复登录、状态污染、滥用networkidle、全程录屏及盲目并发。直击CI不稳定、用例偶发失败等痛点,提供基于Web-First理念的工程化解决方案,助你写出更稳定、高效、可维护的测试代码。

做UI自动化测试的朋友应该都有过这种体验——本地跑得好好的,一上CI就挂;周一全绿,周二莫名其妙红一片;加了sleep能过,不加就报元素找不到。

如果你也遇到过这些情况,别急着怀疑是自己的代码写得不够好。很多时候问题出在“习惯”上——把Selenium时代的老经验照搬到Playwright里,或者没搞清楚Playwright的底层机制就开始写用例。

下面这8个坑,是我和团队在过去一年里一个一个踩过来的。整理出来,希望能帮你少走点弯路。

坑1:还在用waitForTimeout /sleep 硬等
这是新手最容易犯的错误,没有之一。

很多人习惯了Selenium里那种Thread.sleep(3000) 的写法,到了Playwright还是改不掉。动不动就page.wait_for_timeout(5000),觉得“等5秒总该加载完了吧”。

问题在哪? 固定等待时间就像刻舟求剑——网络快的时候浪费5秒,网络慢的时候5秒还不够。测试要么跑得慢,要么时好时坏。

正确的做法:Playwright自带自动等待机制,执行click()、fill() 这些操作之前,它会自动等待元素可见、可操作、稳定。你什么都不用写。

❌ 错误示范

page.wait_for_timeout(3000)
page.locator("#submit").click()

✅ 正确示范——Playwright自己会等

page.locator("#submit").click() # 自动等待按钮可点击
如果确实需要等待某个特定条件(比如API返回数据),用wait_for_response 或wait_for_selector,绑定到明确信号上。

什么时候可以用 wait_for_timeout? 说实话,90%的场景用不上。只有在调试阶段或者等待第三方非交互内容加载时,才把它当作最后手段。

坑2:定位器写得“太聪明”
复杂CSS选择器、依赖文本内容的选择器、XPath——这些东西写的时候觉得很爽,一改版全废。

比如你写了个page.locator('#main-content > div:nth-child(3) > button'),开发同事把页面结构调整了一下,你的测试就挂了。

正确的做法:优先用data-testid。让开发在核心UI元素上加上测试专用的属性,这是代码和测试之间的“契约”。

❌ 脆弱的选择器

page.locator('#app > div.container > button.btn-primary')

✅ 稳定的选择器

page.locator('[data-testid="submit-button"]')
如果项目里暂时没有data-testid,退而求其次可以用get_by_role 或get_by_text,但尽量选择不容易变动的属性。

坑3:自动等待“撞上”组件重渲染
这个坑比较隐蔽,很多人都没意识到。

Playwright的自动等待机制是这样的:它检查元素是否可见、稳定、可操作,检查通过之后立即执行操作。但如果就在“检查通过”和“执行操作”之间的那个瞬间,你的组件正好重新渲染了——按钮被替换成了一个新按钮——点击就会失败,报错Element is not attached to the DOM。

典型场景:点击保存按钮,按钮变成“保存中...”的禁用状态,保存完成后再变回可点击状态。第二次点击就可能撞上重渲染。

解决方案:

方案一:用 data-testid 定位到不会重渲染的外层元素

save_button = page.locator('[data-testid="save-container"] button')
save_button.click()

方案二:点击前先显式等待一下

save_button.wait_for(state="visible")
save_button.click()
或者更干脆——在测试环境里把动画效果关掉,减少重渲染的触发。

坑4:每个测试都从头登录
一套测试用例几十上百个,每个用例都打开登录页、输用户名密码、点登录——慢不说,万一登录接口挂了,所有测试全崩。

解决方案:用storageState 保存登录态。

先跑一次登录,保存登录状态到文件

在 playwright.config.py 里配置

use = {
"storage_state": "auth.json"
}
这样每个测试启动时就已经是登录状态了,又快又稳。

坑5:测试之间“互相传染”
这个坑特别恶心——单个用例跑全过,一起跑就随机挂。问题出在测试之间共享了可变状态。

比如你在模块级别定义了一个page 对象,用例A把它导航到了页面A,用例B以为它还在首页,结果就挂了。

解决方案:每个测试用独立的page 或context。Playwright的fixture机制天然支持这一点:

✅ 每个测试都有自己的 page,互不干扰

def test_something(page):
page.goto("/page-a")

# ...

def test_something_else(page):
page.goto("/page-b") # 干净的页面,不受上一个用例影响

# ...

如果确实需要共享某些只读数据(比如配置),用beforeAll 没问题,但不要共享可变对象。

坑6:滥用networkidle 等待
page.wait_for_load_state("networkidle") 看起来很美——“等所有网络请求都结束了再继续”。但问题在于:单页应用(SPA)里网络请求可能永远停不下来——轮询、WebSocket、长连接,这些东西会让networkidle 一直等下去。

解决方案:不要等“所有请求结束”,等“你关心的那个请求结束”。

❌ 可能永远等不完

page.wait_for_load_state("networkidle")

✅ 只等数据接口返回

with page.expect_response("**/api/orders") as response_info:
page.locator("#load-orders").click()
response = response_info.value
这样既精准又高效。

坑7:Trace/视频全程开着
Trace和视频确实好用,出问题的时候能帮你快速定位。但全程开着会严重拖慢测试速度,CI上跑一次多花好几分钟。

解决方案:只在失败时开启。

playwright.config.py

use = {
"trace": "on-first-retry", # 只在重试时录trace
"video": "on-first-retry",
}
这样大部分通过的测试跑得快,失败的测试也有足够的现场信息供排查。

坑8:盲目加并发
“测试跑得慢?加worker!”——这个思路听起来没毛病,但实际上并发不是越高越好。

并发太高会导致CPU、内存、数据库连接池资源竞争,测试反而更慢,甚至出现莫名其妙的超时失败。

解决方案:先profile再调参。先跑一遍看看瓶颈在哪——是CPU满了?数据库连接不够?还是网络带宽受限?找到真正的瓶颈再决定要不要加worker,加几个。

一张表总结

核心问题
解决方案
硬编码等待
用sleep/固定超时
依赖Playwright自动等待 + web-first断言
脆弱定位器
依赖CSS层级/文本
优先data-testid 或get_by_role
自动等待撞重渲染
组件在操作瞬间被替换
用稳定定位器 + 显式wait_for
重复登录
每个用例都走登录流程
用storageState 复用登录态
测试间状态污染
共享可变对象
每个用例独立page/context
滥用networkidle
SPA里请求停不下来
等特定API响应,不等全部
Trace全程开
拖慢测试速度
只在失败/重试时开启
盲目加并发
资源竞争反而更慢
先找瓶颈再调worker数
最后说两句
上面这些坑,大多数不是Playwright本身的问题,而是使用方式的问题。它提供的工具都是好工具,关键看你怎么用。

如果你刚接触Playwright不久,建议从“坑3”(自动等待撞重渲染)和“坑6”(networkidle)这两个入手重点看一下——这两个是最容易让人困惑、也最不容易自己琢磨明白的。

如果你的测试套件已经有一定规模了,建议优先排查“坑4”(重复登录)和“坑5”(测试间污染),这两个对稳定性和速度的影响最大。

你有什么踩过的坑上面没提到的?欢迎评论区补充,大家一起避雷。

相关文章
|
9天前
|
人工智能 JSON 自然语言处理
让教学更智慧:用阿里云百炼工作流,自动生成中小学教材内容#小有可为#有温度的AI
通过可视化工作流编排,将大模型推理能力转化为标准化的教学内容生成引擎。教师只需输入教材标题和适用学段,即可自动获得结构完整、符合课程标准的章节内容,大幅降低备课门槛,助力教育资源均衡化。
483 125
|
18天前
|
Linux 程序员 数据格式
【2026最新】Notepad++下载、安装和使用一篇搞定(附中文版安装包)
Notepad++ 是一款免费开源、轻量高效的 Windows 文本编辑器,支持 C/Python/HTML 等 80+ 语言语法高亮、代码折叠、正则替换、编码转换及插件扩展,专为程序员与文本处理用户打造,完美替代系统记事本。(239字)
|
3天前
|
人工智能 缓存 安全
Claude Code 封号真实原因曝光,这次彻底不装了,直接针对国内开发者的账号下手?
Claude Code 封号潮背后:逆向扒出客户端隐写区域标记,Anthropic 政策收紧叠加 DeepSeek 7 月涨价,国产替代更紧迫。
|
5天前
|
人工智能 安全 Cloud Native
Higress 新发布:AI Gateway 能力增强,Gateway API 及其推理扩展持续打磨
增强 AI 网关能力,持续打磨 Gateway API 及其推理扩展。
322 124
|
13天前
|
机器学习/深度学习 人工智能 调度
🐴 HappyHorse 1.1 现已上线阿里云百炼!快来查收模型使用指南,现在调用享 6 折~
HappyHorse 1.1 是新一代视频生成大模型,全面升级动态表现力、角色一致性、指令遵循、视觉质感与音画协同能力。支持I2V/T2V/R2V三类生成,适配短剧、电商广告、品牌营销等场景,提供高质、流畅、可控的AI视频生产力。
810 5
🐴 HappyHorse 1.1 现已上线阿里云百炼!快来查收模型使用指南,现在调用享 6 折~
|
4天前
|
人工智能 安全 程序员
终于,Claude Code 封号的原因被曝光了!竟然针对中国用户,植入隐形代码?!
通俗易懂地揭秘 Claude Code 封号的手段,分享一些自己对 AI 编程困境的思考,Codex、Cursor、DeepSeek、智谱 GLM、甚至是豆包,都有所行动了
321 1
|
11天前
|
人工智能 定位技术 SEO
我学 GEO 第 15 天:终于知道AI GEO该如何做?
我是暴走的莉莉酱,边旅行边研究AI GEO的数字游民。专注普通人如何提升“AI可见度”——让AI在回答用户问题时准确识别、理解并推荐你。不讲玄学,只做可测、可调、可持续的GEO实践。
458 127

热门文章

最新文章