我面过不少简历上写着“精通Playwright”的候选人。问“怎么定位一个按钮”,都能答上来。问“为什么click()有时候会报Element is not attached to the DOM”,一半人卡住。
“会”和“懂”之间的分界线,往往不在你写过多少脚本,而在你被问到“它为什么这样设计”时,能不能说出个所以然。
下面这8道题,每一道都有一个“会用的人会答的版本”,和一个“懂的人会补上的那一层”。你可以对着过一遍,看看自己停在哪一层。
第1题:自动等待到底在等什么?
“会”的回答: 等元素可见、可点击。
“懂”的那一层:click()在动作执行前会做一组可操作性检查——元素解析到恰好一个节点、可见、稳定、接收事件、启用、不在inert状态。“稳定”不是等一个固定的时间窗口,而是比较元素在连续动画帧之间的位置和尺寸是否停止变化。“接收事件”会在元素中心点做命中检测,被遮罩挡住就不算通过。
真正的分界线: 自动等待只覆盖“动作执行前的元素状态”。组件在检查和点击之间重新渲染,元素被替换掉,click依然会报not attached——这是自动等待和重新渲染的竞态,不是自动等待“失效”。
第2题:page.route()和page.waitForResponse()有什么区别?
“会”的回答: route用来Mock,waitForResponse用来等响应。
“懂”的那一层: 这是控制和观察两种完全不同的意图。route是拦截层,请求不会到达真实服务器,你能改请求、改响应、直接abort;waitForResponse是观察层,真实请求照常发生,你只是在旁路捕获。
更关键的是执行顺序。route必须在触发动作之前注册,否则请求已经发出去了。waitForResponse的Promise也必须在点击之前创建,用Promise.all并发起来——先点再等,响应可能已经回来了。
真正的分界线: 知道“什么时候注册”比知道“两个API分别干什么”重要得多。
第3题:怎么验证“点击提交之后,接口真的被调用了”?
“会”的回答: 点完之后等页面出现“提交成功”,断言文案可见。
“懂”的那一层: 页面说成功,和接口真的发了并返回了正确结果,是两件事。前端可以乐观更新,先弹Toast再发请求;接口可以返回500,Toast照样弹出来。
正确的做法分两层:点击之前创建响应等待,用Promise.all和点击并发;拿到响应之后,断言不能停在状态码,要看业务code、关键字段,涉及资金和状态的场景再通过接口或数据库确认最终状态。
真正的分界线: UI断言证明用户看到了,网络断言证明系统做了,业务断言证明结果对了。少一层,就是在赌。
第4题:React的class是随机生成的,你怎么定位?
“会”的回答: 用XPath或者CSS选择器。
“懂”的那一层: React的class是构建时哈希,拿它做定位等于把脚本绑在一次构建上。定位优先级应该是getByRole → getByLabel → getByText → getByTestId,最后才是CSS。getByRole走可访问性树,class怎么变都不影响;文本是产品语义,前端能改id和class,但按钮叫什么名字通常是产品决定的。
真正的分界线: 知道getByTestId最稳,但需要前端配合。懂的人会主动在PR里推动加data-testid,而不是等脚本挂了再回来求人。
第5题:storageState能复用登录态,那为什么并行测试还会串号?
“会”的回答: 每个测试加载storageState,跳过登录。
“懂”的那一层:storageState保存的是cookies和localStorage,加载它只是恢复了浏览器会话。但两个worker访问的是同一个业务系统,服务端的数据是共享的。管理员刚审批完订单,普通用户的测试页面可能读到的是管理员改过的状态——浏览器会话隔离了客户端状态,没有隔离服务端数据。
真正的分界线: 知道storageState文件是敏感凭据,要按worker分配独立文件或独立账号,不能多个worker共享同一个auth.json。更懂的人会补一句:多角色场景必须用独立context,各自加载各自的状态,不能把不同角色的状态塞进同一个context。
第6题:networkidle为什么在SPA里不可靠?
“会”的回答: 等页面网络空闲,500毫秒没有请求。
“懂”的那一层: SPA有埋点轮询、WebSocket心跳、service worker,可能永远没有500毫秒的真正静默。就算偶尔等到了,那也可能是两次轮询之间的空隙,页面还没准备好。networkidle等的是一个代理指标,不是“页面准备好了”这个事实。
正确的做法是等具体信号:等某个API响应、等某个UI状态、等应用自己的就绪标志。等“对的信号”,不等“看起来安静了”。
第7题:strict mode violation是自动等待失效了吗?
“会”的回答: 定位器写错了,改成.first()就行。
“懂”的那一层: strict mode是Playwright的设计选择,不是bug。locator匹配到多个元素时,它拒绝替你猜,直接抛异常。.first()确实能让报错消失,但它只是把“静默点错元素”变成了“静默点第一个”——错误被掩盖了,不是被修复了。
真正的分界线: 正确的做法是收窄locator的作用域,比如getByRole("dialog").getByRole("button", {name: "Save"}),确保它唯一指向你真正想操作的元素。用.first()之前,先问一句:为什么会有两个?
第8题:force: true解决超时,它到底跳过了什么?
“会”的回答: 元素被挡住的时候,加force强制点击。
“懂”的那一层:force: true跳过的是非必要的可操作性检查——包括“元素是否真的接收点击事件”这一项。也就是说,它让Playwright停止验证“你点的东西是不是真的能点到”。
元素被遮罩挡住、动画还没停、元素不可见,force都能让它“点成功”。它消除的是超时报错,不是超时的原因。 如果被遮罩挡住是因为一个真实的UI Bug,force会让这个Bug从测试里消失。
真正的分界线:force只该用在“确认元素状态没问题、只是Playwright的检查过于保守”的场景。用它之前,先搞清楚元素为什么不可操作。
这8道题,考的是同一件事
8道题看起来在考不同知识点,但它们指向同一个问题:你是在“调用API”,还是在“理解一个会实时变化的系统”?
自动等待、route、waitForResponse、strict mode、storageState、force——这些机制的存在,是因为浏览器和测试执行之间永远存在时序和状态的缝隙。“会”的人知道这些API能干什么。“懂”的人知道它们在哪一层兜底、在哪一层兜不住。
面试官问这些,不是在考你背没背文档。他是在确认:当你的测试红了、绿了、又红了的时候,你知道该怀疑哪一层。
给你的自检清单
对着这8道题过一遍,标记三种状态:
能答出“会”的版本——你用过Playwright,能写脚本,但遇到非典型失败时,排查会靠猜。
能答出“懂”的那一层——你知道机制在替你做什么,也知道它的边界在哪。这是能进生产环境的水平。
能补上“真正的分界线”——你不仅知道机制,还知道什么时候不该用它(.first()、force、共享storageState)。这是能搭框架、能做技术决策的水平。
大部分人停在第一层和第二层之间。 不是因为他们不努力,是因为“会”的反馈来得快,“懂”的反馈只在翻车的时候才来。
下一次你的Playwright测试红了,别急着加force或者.first()。先问一句:它在哪一层兜不住了?
想清楚这一句,那8道题才算真正答完了。
本文部分内容参考了霍格沃兹测试开发学社整理的相关技术资料,主要涉及软件测试、自动化测试、测试开发及 AI 测试等内容,侧重测试实践、工具应用与工程经验整理。