playwright-debugging

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Playwright 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

速查指南

ProblemFirst Action
Timeout on locatorRun with
--ui
mode, check element state with
.count()
,
.isVisible()
Flaky test (passes sometimes)Replace
waitForTimeout()
with condition-based waits
"Element not visible"Check computed styles, wait for overlays to disappear
Works locally, fails CIUse
waitForLoadState('networkidle')
, increase timeout
Element not clickableCheck if covered by overlay, wait for animations to complete
Stale elementRe-query after navigation instead of storing locator
问题首要操作
定位器超时使用
--ui
模式运行,通过
.count()
.isVisible()
检查元素状态
测试不稳定(时而通过时而失败)用基于条件的等待替换
waitForTimeout()
“元素不可见”检查计算样式,等待遮罩层消失
本地运行正常,CI环境执行失败使用
waitForLoadState('networkidle')
,延长超时时间
元素无法点击检查是否被遮罩层覆盖,等待动画完成
元素状态过期导航后重新查询元素,而非存储定位器

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=1000
Questions 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 --ui
UI模式提供以下功能:
  • 所有操作的可视化时间线
  • 文件变更时自动重新运行测试的监听模式
  • 网络和控制台标签页
  • 测试执行过程的时间旅行回放
使用Inspector单步调试测试:
bash
// 在真实浏览器中单步执行测试
npx playwright test --debug
Inspector支持:
  • 逐单步执行操作
  • 直接在浏览器中选取定位器
  • 实时编辑选择器并查看结果
  • 查看可交互性日志
在失败节点截图:
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=console
后,可在开发者工具中使用以下功能:
javascript
// 在浏览器控制台中
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 locator
Use 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.zip
Organize 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 });
错误做法错误原因正确做法
添加
waitForTimeout(5000)
掩盖时序问题,降低测试速度,不可靠使用基于条件的等待:
expect().toBeVisible()
未排查原因就强制点击绕过Playwright的可交互性检查排查元素无法点击的原因,修复根本问题
不使用现代调试工具诊断速度慢,靠猜测解决问题
--ui
--debug
模式开始进行可视化调试
仅在有头模式下测试隐藏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);
自动化失败时,按以下顺序排查:
  1. ☐ 能否稳定复现问题?
  2. ☐ 在有头慢动作模式下是否会失败?
  3. ☐ 是否在失败前后进行了截图?
  4. ☐ 选择器是否真的能匹配到元素?
  5. ☐ 元素是否可见且已启用?
  6. ☐ 元素是否在iframe中?
  7. ☐ 是否等待页面加载完成?
  8. ☐ 是否有需要等待加载的动态内容?
  9. ☐ 是否有未完成的网络请求?
  10. ☐ 是否检查了浏览器控制台的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模式
--ui
带可视化时间线的时间旅行调试(最适合本地开发)
Inspector
--debug
单步执行测试,实时选取定位器
有头模式
--headed
需要观察浏览器执行过程
慢动作模式
--slow-mo=1000
操作过快无法观察
调试模式
PWDEBUG=1
打开Inspector(旧方式,推荐使用--debug)
控制台调试
PWDEBUG=console
带Playwright对象的浏览器开发者工具
追踪查看器
show-trace trace.zip
需要完整的执行时间线分析
截图
page.screenshot()
需要可视化证据
控制台日志
DEBUG=pw:api
需要API调用详情
暂停
await page.pause()
需要手动检查

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环境失败

MistakeWhy It's WrongRight Approach
Adding
waitForTimeout(5000)
Masks timing issues, makes tests slower, unreliableUse condition-based waits:
expect().toBeVisible()
Force-clicking without understanding whyBypasses Playwright's actionability checksDiagnose WHY element isn't clickable, fix root cause
Not using modern debugging toolsSlower diagnosis, guessing at issuesStart with
--ui
or
--debug
for visual debugging
Testing only in headed modeHides timing issues that appear in CIAlways test in headless mode too
Using brittle selectorsBreaks when HTML structure changesUse role-based or data-testid selectors
Skipping trace viewerMiss detailed timeline of what happenedEnable 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:
  1. ☐ Can I reproduce the failure consistently?
  2. ☐ Does it fail in headed mode with slow motion?
  3. ☐ Have I taken screenshots before/after the failure?
  4. ☐ Does the selector actually match an element?
  5. ☐ Is the element visible and enabled?
  6. ☐ Is the element in an iframe?
  7. ☐ Have I waited for page load to complete?
  8. ☐ Is there dynamic content that needs time to load?
  9. ☐ Are there network requests still in flight?
  10. ☐ 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

注意事项

ToolCommandUse When
UI Mode
--ui
Time-travel debugging with visual timeline (best for local dev)
Inspector
--debug
Step through test execution, pick locators live
Headed mode
--headed
Need to see browser
Slow motion
--slow-mo=1000
Actions too fast to observe
Debug mode
PWDEBUG=1
Open Inspector (older approach, prefer --debug)
Console debug
PWDEBUG=console
Access browser DevTools with playwright object
Trace viewer
show-trace trace.zip
Need full timeline analysis
Screenshot
page.screenshot()
Need visual evidence
Console logs
DEBUG=pw:api
Need API call details
Pause
await page.pause()
Need to inspect manually
调试优先级:
  1. 稳定复现问题
  2. 增加可视化手段(截图、日志、追踪)
  3. 验证元素状态和选择器
  4. 检查时序和等待逻辑
  5. 在不同模式下测试(有头、多浏览器)
自动等待的优势: Playwright会自动等待元素满足以下条件:
  • 已附加到DOM
  • 可见
  • 已启用且稳定
  • 未被遮罩层覆盖
大多数操作(点击、填充等)都包含自动等待。仅在复杂条件下才需要显式等待。
大多数Playwright问题都是时序相关的。用基于条件的等待替换固定时长的超时。如有疑问,请放慢速度,在有头模式下使用
--ui
--debug
观察执行过程。

Flakiness 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:
  1. Reproduce the issue reliably
  2. Add visibility (screenshots, logs, traces)
  3. Verify element state and selector
  4. Check timing and waits
  5. 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
--ui
or
--debug
.