playwright-debugging
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePlaywright Debugging
Playwright调试
Overview
概述
Browser automation failures fall into predictable categories. This skill provides a systematic approach to diagnose and fix issues quickly.
浏览器自动化失败可归为几种可预见的类型。本方案提供了一套系统化的方法,可快速诊断并修复问题。
When to Use
适用场景
- Scripts that worked before now fail
- Intermittent test failures (flakiness)
- "Element not found" errors
- Timeout errors
- Unexpected behavior in automation
- Elements not interactable
When NOT to use:
- Writing new automation (use playwright-patterns skill)
- API or backend debugging
- 之前运行正常的脚本现在执行失败
- 间歇性测试失败(不稳定)
- “元素未找到”错误
- 超时错误
- 自动化执行出现意外行为
- 元素无法交互
不适用场景:
- 编写新的自动化脚本(请使用playwright-patterns方案)
- API或后端调试
Quick Reference
速查指南
| Problem | First Action |
|---|---|
| Timeout on locator | Run with |
| Flaky test (passes sometimes) | Replace |
| "Element not visible" | Check computed styles, wait for overlays to disappear |
| Works locally, fails CI | Use |
| Element not clickable | Check if covered by overlay, wait for animations to complete |
| Stale element | Re-query after navigation instead of storing locator |
| 问题 | 首要操作 |
|---|---|
| 定位器超时 | 使用 |
| 测试不稳定(时而通过时而失败) | 用基于条件的等待替换 |
| “元素不可见” | 检查计算样式,等待遮罩层消失 |
| 本地运行正常,CI环境执行失败 | 使用 |
| 元素无法点击 | 检查是否被遮罩层覆盖,等待动画完成 |
| 元素状态过期 | 导航后重新查询元素,而非存储定位器 |
Diagnostic Framework
诊断框架
1. Reproduce and Isolate
1. 复现并隔离问题
First step: Can you reproduce it?
javascript
// Run single test to isolate issue
npx playwright test path/to/test.spec.js
// Run with headed mode to observe
npx playwright test --headed
// Run with slow motion
npx playwright test --headed --slow-mo=1000Questions to answer:
- Does it fail consistently or intermittently?
- Does it fail in all browsers or just one?
- Does it fail in headed and headless mode?
- Did something change recently (site update, code change)?
第一步:能否稳定复现问题?
javascript
// 运行单个测试以隔离问题
npx playwright test path/to/test.spec.js
// 使用有头模式运行以观察执行过程
npx playwright test --headed
// 使用慢动作模式运行
npx playwright test --headed --slow-mo=1000需要确认的问题:
- 是持续失败还是间歇性失败?
- 是在所有浏览器中都失败还是仅在某一个浏览器中失败?
- 在有头和无头模式下都会失败吗?
- 最近是否有变更(网站更新、代码修改)?
2. Add Visibility
2. 增加可视化调试手段
Use UI Mode for interactive debugging:
bash
undefined使用UI模式进行交互式调试:
bash
// 最适合本地开发——提供时间旅行调试功能
npx playwright test --uiUI模式提供以下功能:
- 所有操作的可视化时间线
- 文件变更时自动重新运行测试的监听模式
- 网络和控制台标签页
- 测试执行过程的时间旅行回放
使用Inspector单步调试测试:
bash
// 在真实浏览器中单步执行测试
npx playwright test --debugInspector支持:
- 逐单步执行操作
- 直接在浏览器中选取定位器
- 实时编辑选择器并查看结果
- 查看可交互性日志
在失败节点截图:
javascript
// 在可能失败的操作前截图
await page.screenshot({ path: 'before-action.png', fullPage: true });
// 尝试执行操作
try {
await page.click('.button');
} catch (error) {
await page.screenshot({ path: 'after-error.png', fullPage: true });
throw error;
}启用详细日志:
bash
// API级别的调试日志
DEBUG=pw:api npx playwright test
// 带Playwright对象的浏览器开发者工具
PWDEBUG=console npx playwright test启用后,可在开发者工具中使用以下功能:
PWDEBUG=consolejavascript
// 在浏览器控制台中
playwright.$('.selector') // 使用Playwright引擎查询元素
playwright.$$('selector') // 获取所有匹配元素
playwright.inspect('selector') // 在元素面板中高亮显示元素
playwright.locator('selector') // 创建定位器使用追踪查看器:
javascript
// 开始录制追踪
await context.tracing.start({ screenshots: true, snapshots: true });
// ... 你的测试代码
await context.tracing.stop({ path: 'trace.zip' });
// 查看追踪记录
npx playwright show-trace trace.zip用测试步骤组织追踪记录:
javascript
// 在追踪查看器中分组显示操作
await test.step('登录', async () => {
await page.fill('input[name="username"]', 'user');
await page.click('button[type="submit"]');
});
await test.step('导航至仪表盘', async () => {
await page.click('a[href="/dashboard"]');
});为定位器添加描述以提升可读性:
javascript
// 描述会显示在追踪查看器和测试报告中
const submitButton = page.locator('#submit').describe('提交按钮');
await submitButton.click();VS Code调试:
安装Playwright VS Code扩展以获得以下功能:
- 在VS Code中使用断点进行实时调试
- 编辑代码时在浏览器中高亮显示定位器
- “显示浏览器”选项以获得实时反馈
- 右键点击任意测试选择“调试测试”
此扩展可将调试直接集成到你的编辑器工作流中。
Best for local development - provides time-travel debugging
3. 检查元素状态
npx playwright test --ui
UI Mode gives you:
- Visual timeline of all actions
- Watch mode for re-running on file changes
- Network and console tabs
- Time-travel through test execution
**Use Inspector to step through tests:**
```bash检查元素是否存在:
javascript
const element = page.locator('.button');
// 是否存在于DOM中?
const count = await element.count();
console.log(`找到 ${count} 个元素`);
// 是否可见?
const isVisible = await element.isVisible();
console.log(`可见: ${isVisible}`);
// 是否启用?
const isEnabled = await element.isEnabled();
console.log(`已启用: ${isEnabled}`);
// 获取所有属性
const attrs = await element.evaluate(el => ({
classes: el.className,
id: el.id,
display: window.getComputedStyle(el).display,
visibility: window.getComputedStyle(el).visibility,
opacity: window.getComputedStyle(el).opacity
}));
console.log(attrs);Step through test execution with live browser
4. 验证选择器
npx playwright test --debug
Inspector allows:
- Stepping through actions one at a time
- Picking locators directly from the browser
- Editing selectors live and seeing results
- Viewing actionability logs
**Take screenshots at failure point:**
```javascript
// Before failing action
await page.screenshot({ path: 'before-action.png', fullPage: true });
// Try action
try {
await page.click('.button');
} catch (error) {
await page.screenshot({ path: 'after-error.png', fullPage: true });
throw error;
}Enable verbose logging:
bash
undefined在浏览器控制台中测试选择器:
javascript
// 使用page.evaluate测试选择器
const found = await page.evaluate(() => {
const el = document.querySelector('.button');
return el ? {
text: el.textContent,
visible: el.offsetParent !== null,
enabled: !el.disabled
} : null;
});
console.log('选择器测试结果:', found);检查是否存在多个匹配元素:
javascript
// 是否有多个匹配元素?
const all = await page.locator('.button').all();
console.log(`找到 ${all.length} 个匹配元素`);
// 获取所有匹配元素的文本
const texts = await page.locator('.button').allTextContents();
console.log('所有匹配元素的文本:', texts);API-level debugging
常见问题及修复方案
—
问题:元素未找到
DEBUG=pw:api npx playwright test
原因:
- 选择器错误
- 元素尚未加载完成
- 元素位于iframe中
- 元素是动态创建的
调试步骤:
javascript
// 1. 检查选择器是否能匹配到元素
const exists = await page.locator('.button').count() > 0;
console.log('元素存在:', exists);
// 2. 显式等待元素加载(现代方式)
await page.locator('.button').waitFor({ timeout: 10000 });
// 或让自动等待机制处理:
await page.locator('.button').click();
// 3. 检查元素是否在iframe中
const frame = page.frameLocator('iframe');
await frame.locator('.button').click();
// 4. 导出所有匹配元素
const all = await page.evaluate(() => {
return Array.from(document.querySelectorAll('button')).map(el => ({
text: el.textContent,
classes: el.className,
id: el.id
}));
});
console.log('页面上所有按钮:', all);Browser DevTools with playwright object
问题:元素不可见/无法点击
PWDEBUG=console npx playwright test
With `PWDEBUG=console`, you get DevTools access to:
```javascript
// In browser console
playwright.$('.selector') // Query with Playwright engine
playwright.$$('selector') // Get all matches
playwright.inspect('selector') // Highlight in Elements panel
playwright.locator('selector') // Create locatorUse trace viewer:
javascript
// Record trace
await context.tracing.start({ screenshots: true, snapshots: true });
// ... your test code
await context.tracing.stop({ path: 'trace.zip' });
// View trace
npx playwright show-trace trace.zipOrganize traces with test steps:
javascript
// Group actions in trace viewer
await test.step('Login', async () => {
await page.fill('input[name="username"]', 'user');
await page.click('button[type="submit"]');
});
await test.step('Navigate to dashboard', async () => {
await page.click('a[href="/dashboard"]');
});Add descriptions to locators for clarity:
javascript
// Descriptions appear in trace viewer and reports
const submitButton = page.locator('#submit').describe('Submit button');
await submitButton.click();VS Code debugging:
Install the Playwright VS Code extension for:
- Live debugging with breakpoints in VS Code
- Locator highlighting in browser while editing
- "Show Browser" option for real-time feedback
- Right-click "Debug Test" on any test
This integrates debugging directly into your editor workflow.
原因:
- 元素被隐藏(CSS: display:none, visibility:hidden)
- 元素被其他元素覆盖
- 元素在视口外
- 元素动画尚未完成
调试步骤:
javascript
// 1. 检查计算样式
const styles = await page.locator('.button').evaluate(el => ({
display: window.getComputedStyle(el).display,
visibility: window.getComputedStyle(el).visibility,
opacity: window.getComputedStyle(el).opacity,
zIndex: window.getComputedStyle(el).zIndex
}));
console.log('元素样式:', styles);
// 2. 滚动到元素可见区域
await page.locator('.button').scrollIntoViewIfNeeded();
// 3. 等待元素稳定(动画结束)
await expect(page.locator('.button')).toBeVisible();
await page.waitForTimeout(100); // 短暂等待动画完成
// 4. 必要时强制点击(最后手段)
await page.locator('.button').click({ force: true });3. Inspect Element State
问题:时序/竞态条件
Check if element exists:
javascript
const element = page.locator('.button');
// Does it exist in DOM?
const count = await element.count();
console.log(`Found ${count} elements`);
// Is it visible?
const isVisible = await element.isVisible();
console.log(`Visible: ${isVisible}`);
// Is it enabled?
const isEnabled = await element.isEnabled();
console.log(`Enabled: ${isEnabled}`);
// Get all attributes
const attrs = await element.evaluate(el => ({
classes: el.className,
id: el.id,
display: window.getComputedStyle(el).display,
visibility: window.getComputedStyle(el).visibility,
opacity: window.getComputedStyle(el).opacity
}));
console.log(attrs);原因:
- 网络请求未完成
- JavaScript仍在执行
- 动画正在进行
- 动态内容正在加载
调试步骤:
javascript
// 1. 等待网络空闲
await page.goto('https://example.com');
await page.waitForLoadState('networkidle');
// 2. 等待特定网络请求完成
await page.waitForResponse(resp =>
resp.url().includes('/api/data') && resp.status() === 200
);
// 3. 等待JavaScript条件满足
await page.waitForFunction(() =>
window.dataLoaded === true
);
// 4. 等待元素数量稳定
await expect(page.locator('.item')).toHaveCount(10);4. Verify Selector
问题:元素状态过期
Test selector in browser console:
javascript
// Use page.evaluate to test selector
const found = await page.evaluate(() => {
const el = document.querySelector('.button');
return el ? {
text: el.textContent,
visible: el.offsetParent !== null,
enabled: !el.disabled
} : null;
});
console.log('Selector test:', found);Check for multiple matches:
javascript
// Are there multiple elements?
const all = await page.locator('.button').all();
console.log(`Found ${all.length} matching elements`);
// Get text of all matches
const texts = await page.locator('.button').allTextContents();
console.log('All matching texts:', texts);原因:
- 页面刷新或导航
- 元素被移除后重新添加到DOM
- 动态内容替换了元素
修复方案:
javascript
// 不要在导航后复用存储的元素句柄
const button = page.locator('.button'); // 错误:可能会过期
await page.goto('/other-page');
await button.click(); // 错误:元素状态已过期
// 导航后重新查询元素
await page.goto('/other-page');
await page.locator('.button').click(); // 正确:重新查询的新鲜元素Common Issues and Fixes
问题:表单提交失败
Issue: Element Not Found
—
Causes:
- Selector is wrong
- Element hasn't loaded yet
- Element is in iframe
- Element is dynamically created
Debug steps:
javascript
// 1. Check if selector exists at all
const exists = await page.locator('.button').count() > 0;
console.log('Element exists:', exists);
// 2. Wait for element explicitly (modern approach)
await page.locator('.button').waitFor({ timeout: 10000 });
// Or let auto-waiting handle it:
await page.locator('.button').click();
// 3. Check if in iframe
const frame = page.frameLocator('iframe');
await frame.locator('.button').click();
// 4. Dump all matching elements
const all = await page.evaluate(() => {
return Array.from(document.querySelectorAll('button')).map(el => ({
text: el.textContent,
classes: el.className,
id: el.id
}));
});
console.log('All buttons on page:', all);原因:
- JavaScript验证阻止提交
- 事件监听器尚未绑定
- 表单action属性设置错误
调试步骤:
javascript
// 1. 提交前验证表单状态
const formState = await page.evaluate(() => {
const form = document.querySelector('form');
return {
action: form?.action,
method: form?.method,
valid: form?.checkValidity()
};
});
console.log('表单状态:', formState);
// 2. 手动触发表单事件
await page.fill('input[name="email"]', 'test@example.com');
await page.dispatchEvent('input[name="email"]', 'blur');
// 3. 使用form.submit()替代点击按钮
await page.evaluate(() => document.querySelector('form').submit());Issue: Element Not Visible/Clickable
常见错误
Causes:
- Element is hidden (CSS: display:none, visibility:hidden)
- Element is covered by another element
- Element is outside viewport
- Element hasn't finished animating
Debug steps:
javascript
// 1. Check computed styles
const styles = await page.locator('.button').evaluate(el => ({
display: window.getComputedStyle(el).display,
visibility: window.getComputedStyle(el).visibility,
opacity: window.getComputedStyle(el).opacity,
zIndex: window.getComputedStyle(el).zIndex
}));
console.log('Element styles:', styles);
// 2. Scroll into view
await page.locator('.button').scrollIntoViewIfNeeded();
// 3. Wait for element to be stable (not animating)
await expect(page.locator('.button')).toBeVisible();
await page.waitForTimeout(100); // Brief wait for animation
// 4. Force click if needed (last resort)
await page.locator('.button').click({ force: true });| 错误做法 | 错误原因 | 正确做法 |
|---|---|---|
添加 | 掩盖时序问题,降低测试速度,不可靠 | 使用基于条件的等待: |
| 未排查原因就强制点击 | 绕过Playwright的可交互性检查 | 排查元素无法点击的原因,修复根本问题 |
| 不使用现代调试工具 | 诊断速度慢,靠猜测解决问题 | 从 |
| 仅在有头模式下测试 | 隐藏CI环境中会出现的时序问题 | 同时在无头模式下测试 |
| 使用脆弱的选择器 | HTML结构变化时会失效 | 使用基于角色或data-testid的选择器 |
| 跳过追踪查看器 | 错过详细的执行时间线 | 为失败的测试启用追踪功能 |
Issue: Timing/Race Conditions
调试检查清单
Causes:
- Network requests not complete
- JavaScript still executing
- Animations in progress
- Dynamic content loading
Debug steps:
javascript
// 1. Wait for network to be idle
await page.goto('https://example.com');
await page.waitForLoadState('networkidle');
// 2. Wait for specific network request
await page.waitForResponse(resp =>
resp.url().includes('/api/data') && resp.status() === 200
);
// 3. Wait for JavaScript condition
await page.waitForFunction(() =>
window.dataLoaded === true
);
// 4. Wait for element count to stabilize
await expect(page.locator('.item')).toHaveCount(10);自动化失败时,按以下顺序排查:
- ☐ 能否稳定复现问题?
- ☐ 在有头慢动作模式下是否会失败?
- ☐ 是否在失败前后进行了截图?
- ☐ 选择器是否真的能匹配到元素?
- ☐ 元素是否可见且已启用?
- ☐ 元素是否在iframe中?
- ☐ 是否等待页面加载完成?
- ☐ 是否有需要等待加载的动态内容?
- ☐ 是否有未完成的网络请求?
- ☐ 是否检查了浏览器控制台的JavaScript错误?
Issue: Stale Element Reference
调试工具参考
Causes:
- Page refreshed or navigated
- Element was removed and re-added to DOM
- Dynamic content replaced element
Fix:
javascript
// DON'T store element handles across navigation
const button = page.locator('.button'); // BAD: might become stale
await page.goto('/other-page');
await button.click(); // ERROR: stale
// DO re-query after navigation
await page.goto('/other-page');
await page.locator('.button').click(); // GOOD: fresh query| 工具 | 命令 | 适用场景 |
|---|---|---|
| UI模式 | | 带可视化时间线的时间旅行调试(最适合本地开发) |
| Inspector | | 单步执行测试,实时选取定位器 |
| 有头模式 | | 需要观察浏览器执行过程 |
| 慢动作模式 | | 操作过快无法观察 |
| 调试模式 | | 打开Inspector(旧方式,推荐使用--debug) |
| 控制台调试 | | 带Playwright对象的浏览器开发者工具 |
| 追踪查看器 | | 需要完整的执行时间线分析 |
| 截图 | | 需要可视化证据 |
| 控制台日志 | | 需要API调用详情 |
| 暂停 | | 需要手动检查 |
Issue: Form Submission Not Working
不稳定测试模式
—
不稳定:80%的概率通过
Causes:
- JavaScript validation preventing submit
- Event listeners not attached yet
- Form action not set correctly
Debug steps:
javascript
// 1. Verify form state before submit
const formState = await page.evaluate(() => {
const form = document.querySelector('form');
return {
action: form?.action,
method: form?.method,
valid: form?.checkValidity()
};
});
console.log('Form state:', formState);
// 2. Trigger form events manually
await page.fill('input[name="email"]', 'test@example.com');
await page.dispatchEvent('input[name="email"]', 'blur');
// 3. Use form.submit() instead of clicking button
await page.evaluate(() => document.querySelector('form').submit());可能原因: 竞态条件
修复方案:
javascript
// 替换固定时长等待
await page.waitForTimeout(2000); // 错误
// 使用基于条件的等待
await expect(page.locator('.result')).toBeVisible(); // 正确Common Mistakes
不稳定:本地正常,CI环境失败
| Mistake | Why It's Wrong | Right Approach |
|---|---|---|
Adding | Masks timing issues, makes tests slower, unreliable | Use condition-based waits: |
| Force-clicking without understanding why | Bypasses Playwright's actionability checks | Diagnose WHY element isn't clickable, fix root cause |
| Not using modern debugging tools | Slower diagnosis, guessing at issues | Start with |
| Testing only in headed mode | Hides timing issues that appear in CI | Always test in headless mode too |
| Using brittle selectors | Breaks when HTML structure changes | Use role-based or data-testid selectors |
| Skipping trace viewer | Miss detailed timeline of what happened | Enable tracing for failing tests |
可能原因: 时序差异
修复方案:
javascript
// 为CI环境延长默认超时时间
test.setTimeout(60000);
page.setDefaultTimeout(30000);
// 等待网络空闲
await page.waitForLoadState('networkidle');Debugging Checklist
不稳定:因“元素无法点击”失败
When automation fails, check in this order:
- ☐ Can I reproduce the failure consistently?
- ☐ Does it fail in headed mode with slow motion?
- ☐ Have I taken screenshots before/after the failure?
- ☐ Does the selector actually match an element?
- ☐ Is the element visible and enabled?
- ☐ Is the element in an iframe?
- ☐ Have I waited for page load to complete?
- ☐ Is there dynamic content that needs time to load?
- ☐ Are there network requests still in flight?
- ☐ Have I checked browser console for JavaScript errors?
可能原因: 元素重叠或动画
修复方案:
javascript
// 等待元素可交互
await expect(page.locator('.button')).toBeVisible();
await expect(page.locator('.button')).toBeEnabled();
// 或等待遮罩层消失
await expect(page.locator('.loading-overlay')).not.toBeVisible();Debugging Tools Reference
注意事项
| Tool | Command | Use When |
|---|---|---|
| UI Mode | | Time-travel debugging with visual timeline (best for local dev) |
| Inspector | | Step through test execution, pick locators live |
| Headed mode | | Need to see browser |
| Slow motion | | Actions too fast to observe |
| Debug mode | | Open Inspector (older approach, prefer --debug) |
| Console debug | | Access browser DevTools with playwright object |
| Trace viewer | | Need full timeline analysis |
| Screenshot | | Need visual evidence |
| Console logs | | Need API call details |
| Pause | | Need to inspect manually |
调试优先级:
- 稳定复现问题
- 增加可视化手段(截图、日志、追踪)
- 验证元素状态和选择器
- 检查时序和等待逻辑
- 在不同模式下测试(有头、多浏览器)
自动等待的优势:
Playwright会自动等待元素满足以下条件:
- 已附加到DOM
- 可见
- 已启用且稳定
- 未被遮罩层覆盖
大多数操作(点击、填充等)都包含自动等待。仅在复杂条件下才需要显式等待。
大多数Playwright问题都是时序相关的。用基于条件的等待替换固定时长的超时。如有疑问,请放慢速度,在有头模式下使用或观察执行过程。
--ui--debugFlakiness Patterns
—
Flaky: Works 80% of the time
—
Likely cause: Race condition
Fix:
javascript
// Replace arbitrary waits
await page.waitForTimeout(2000); // BAD
// With condition-based waits
await expect(page.locator('.result')).toBeVisible(); // GOOD—
Flaky: Fails on CI but works locally
—
Likely cause: Timing differences
Fix:
javascript
// Increase default timeout for CI
test.setTimeout(60000);
page.setDefaultTimeout(30000);
// Wait for network idle
await page.waitForLoadState('networkidle');—
Flaky: Fails with "element not clickable"
—
Likely cause: Overlapping elements or animations
Fix:
javascript
// Wait for element to be actionable
await expect(page.locator('.button')).toBeVisible();
await expect(page.locator('.button')).toBeEnabled();
// Or wait for overlay to disappear
await expect(page.locator('.loading-overlay')).not.toBeVisible();—
Remember
—
Debugging priorities:
- Reproduce the issue reliably
- Add visibility (screenshots, logs, traces)
- Verify element state and selector
- Check timing and waits
- Test in different modes (headed, browsers)
Auto-waiting advantages:
Playwright automatically waits for elements to be:
- Attached to DOM
- Visible
- Enabled and stable
- Not covered by overlays
Most actions (click, fill, etc.) include auto-waiting. Explicit waits are only needed for complex conditions.
Most Playwright issues are timing-related. Replace arbitrary timeouts with condition-based waits. When in doubt, slow down and observe in headed mode with or .
--ui--debug—