
CI 截图里“保存”按钮已经出现locator.click()却在 30 秒后超时。继续加waitForTimeout(5000)有时能绿但负载一高又失败。这个现象通常不是单纯的“页面慢”而是元素在可见、稳定、无遮挡、可接收事件等检查中仍有一项不成立。下面把问题压缩成一个能本地复现的页面并用 trace 与业务断言定位失败层。示例未在你的环境执行文中只给出预期观察方式。一个会移动并被遮罩的按钮保存为/tmp/actionability.html!doctypehtmlmetacharsetutf-8style#save{position:absolute;top:80px;left:10px;transition:left .8s;}#mask{position:fixed;inset:0;background:#fff8;z-index:2;}/stylebuttonidsave保存/buttondividmask/divpidstatus/pscriptsave.onclick()status.textContentsaved;requestAnimationFrame(()save.style.left240px);setTimeout(()mask.remove(),1200);/script测试用 Locator 表达目标用结果文本表达业务成功import{test,expect}fromplaywright/test;test(保存动作完成,async({page}){awaitpage.goto(file:///tmp/actionability.html);constsavepage.getByRole(button,{name:保存});awaitsave.click();awaitexpect(page.locator(#status)).toHaveText(saved);});Playwright 在每次动作前重新解析 Locator不会把创建 Locator 时的 DOM 节点永久缓存。按钮移动时稳定性检查未通过遮罩存在时事件接收检查未通过条件恢复后动作才会继续。click 成功只证明动作发出不证明保存完成自动等待解决的是动作可执行性不是业务正确性。按钮可点击后后端仍可能返回 500前端也可能吞掉异常。把“能点”和“保存成功”写成两层证据constresponsePromisepage.waitForResponse(responseresponse.url().endsWith(/api/profile)response.request().method()PUT);awaitpage.getByRole(button,{name:保存}).click();constresponseawaitresponsePromise;expect(response.status()).toBe(200);awaitexpect(page.getByRole(status)).toContainText(保存成功);不要无条件使用networkidle代替具体响应。埋点、长轮询或 WebSocket 会让“网络空闲”与业务结束无关。验证失败时要区分动作超时、响应状态错误、响应正确但 UI 没更新这三类问题属于不同边界。用 trace 回放最后一次不满足的条件配置只在失败时保留证据import{defineConfig}fromplaywright/test;exportdefaultdefineConfig({timeout:30_000,use:{trace:retain-on-failure,screenshot:only-on-failure}});执行与打开报告npx playwrighttestsave.spec.ts--reporterlinetest_status$?findtest-results-nametrace.zip-printexit$test_status实际运行时测试退出码为 0 且业务断言成立才是成功非零退出码是失败。若生成trace.zip可用npx playwright show-trace 路径检查动作前后的 DOM 快照、调用日志与网络事件。Trace 是失败证据不应因为“重试后通过”而被删除。针对原因修复别统一增加休眠如果日志显示 strict mode violation应缩小 Locator 直到只匹配一个元素节点频繁重建时避免保存elementHandle继续使用语义 Locator遮罩拦截事件时应等待遮罩消失或修复页面状态业务请求慢则等待具体响应并为该步骤设置合理超时。强制点击click({ force: true })会跳过部分检查适合极少数明确需要绕过用户交互约束的场景却可能把真实遮挡变成假通过。稳定的 E2E 用例应该留下四段清楚的因果链目标唯一、动作条件成立、请求得到预期响应、用户可见结果出现。