关于 playwright-debugging
playwright-debugging 是系统诊断 Playwright 浏览器自动化故障的技能,面向编写测试的人员,涵盖不稳定测试、定位器与时序问题,以及 CI 中出现的失败。
主要功能
- 01复现并隔离故障
- 02用 UI Mode 和 Inspector 检查测试
- 03检查定位器与元素状态
- 04分析截图、日志和追踪记录
- 05用条件等待替代固定延时
适用场景
- 01排查间歇性测试失败
- 02诊断元素未找到或无法点击
- 03分析仅在 CI 中发生的故障
- 04追查时序和网络问题
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ ed3dai/ ed3d-plugins --skill playwright-debugging安装后在 Claude Code 中输入/playwright-debugging确认已加载。
详细介绍
Playwright 调试
概述
浏览器自动化失败通常可归入一些可预测的类别。本技能提供了一套系统化方法,帮助你快速诊断和解决问题。
适用场景
- 之前可正常运行的脚本现在失败
- 测试偶发失败(不稳定)
- “找不到元素”错误
- 超时错误
- 自动化行为异常
- 元素无法交互
不适用场景:
- 编写新的自动化(请使用 playwright-patterns 技能)
- API 或后端调试
快速参考
| 问题 | 首要操作 |
|---|---|
| 定位器超时 | 使用 --ui 模式运行,并通过 .count()、.isVisible() 检查元素状态 |
| 测试不稳定(有时通过) | 将 waitForTimeout() 替换为基于条件的等待 |
| “元素不可见” | 检查计算样式,并等待覆盖层消失 |
| 本地运行正常,CI 失败 | 使用 waitForLoadState('networkidle'),并增加超时时间 |
| 元素不可点击 | 检查是否被覆盖层遮挡,并等待动画完成 |
| 元素已失效 | 导航后重新查询,而不是保存定位器 |
诊断框架
1. 重现并隔离
第一步:你能重现问题吗?
// 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
需要回答的问题:
- 失败是持续发生还是偶尔发生?
- 所有浏览器都会失败,还是只有一个浏览器?
- 在有头模式和无头模式下都会失败吗?
- 最近是否发生了变化(网站更新、代码变更)?
2. 增加可见性
使用 UI Mode 进行交互式调试:
# Best for local development - provides time-travel debugging
npx playwright test --ui
UI Mode 提供:
- 所有操作的可视化时间线
- 文件变更后重新运行的监视模式
- 网络和控制台标签页
- 在测试执行过程中进行时间回溯
使用 Inspector 逐步执行测试:
# Step through test execution with live browser
npx playwright test --debug
Inspector 支持:
- 一次逐步执行一个操作
- 直接从浏览器中选取定位器
- 实时编辑选择器并查看结果
- 查看可操作性日志
在失败时截取屏幕截图:
// 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;
}
启用详细日志记录:
# API-level debugging
DEBUG=pw:api npx playwright test
# Browser DevTools with playwright object
PWDEBUG=console npx playwright test
使用 PWDEBUG=console,你可以通过 DevTools 访问:
// 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
使用跟踪查看器:
// 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
使用测试步骤整理跟踪记录:
// 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"]');
});
为定位器添加描述以便理解:
// Descriptions appear in trace viewer and reports
const submitButton = page.locator('#submit').describe('Submit button');
await submitButton.click();
VS Code 调试:
安装 Playwright VS Code 扩展,即可:
- 在 VS Code 中使用断点进行实时调试
- 编辑时在浏览器中高亮显示定位器
- 使用“Show Browser”选项获取实时反馈
- 右键单击任意测试并选择“Debug Test”
这会将调试功能直接集成到编辑器工作流中。
3. 检查元素状态
检查元素是否存在:
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);
4. 验证选择器
在浏览器控制台中测试选择器:
// 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);
检查是否匹配多个元素:
// 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);
常见问题及解决方法
问题:找不到元素
原因:
- 选择器错误
- 元素尚未加载
- 元素位于 iframe 中
- 元素是动态创建的
调试步骤:
// 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);
问题:元素不可见/不可点击
原因:
- 元素被隐藏(CSS:display:none、visibility:hidden)
- 元素被其他元素遮挡
- 元素位于视口之外
- 元素动画尚未完成
调试步骤:
// 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 });
问题:时序/竞态条件
原因:
- 网络请求尚未完成
- 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);
问题:元素引用已失效
原因:
- 页面已刷新或导航
- 元素已从 DOM 中移除并重新添加
- 动态内容替换了该元素
解决方法:
// 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
问题:表单提交无效
原因:
- JavaScript 验证阻止了提交
- 事件监听器尚未添加
- 表单 action 设置不正确
调试步骤:
// 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"]', '[email protected]');
await page.dispatchEvent('input[name="email"]', 'blur');
// 3. Use form.submit() instead of clicking button
await page.evaluate(() => document.querySelector('form').submit());
常见错误
| 错误 | 错误原因 | 正确做法 |
|---|---|---|
添加 waitForTimeout(5000) | 掩盖时序问题、拖慢测试,且不可靠 | 使用基于条件的等待:expect().toBeVisible() |
| 不明原因就强制点击 | 绕过 Playwright 的可操作性检查 | 诊断元素无法点击的原因,修复根本问题 |
| 不使用现代调试工具 | 诊断速度较慢,只能猜测问题 | 从 --ui 或 --debug 开始进行可视化调试 |
| 只在有头模式下测试 | 隐藏在 CI 中出现的时序问题 | 也要始终在无头模式下测试 |
| 使用脆弱的选择器 | HTML 结构变化时就会失效 | 使用基于角色或 data-testid 的选择器 |
| 跳过追踪查看器 | 错过问题发生过程的详细时间线 | 为失败的测试启用追踪 |
调试检查清单
自动化失败时,按以下顺序检查:
- ☐ 我能否稳定复现故障?
- ☐ 在启用慢动作的有头模式下是否会失败?
- ☐ 我是否在故障发生前后截取了屏幕截图?
- ☐ 选择器是否确实匹配某个元素?
- ☐ 元素是否可见且已启用?
- ☐ 元素是否位于 iframe 中?
- ☐ 我是否等待页面加载完成?
- ☐ 是否有需要等待加载的动态内容?
- ☐ 是否仍有网络请求正在进行?
- ☐ 我是否检查了浏览器控制台中的 JavaScript 错误?
调试工具参考
| 工具 | 命令 | 使用时机 |
|---|---|---|
| UI 模式 | --ui | 通过可视化时间线进行时间旅行调试(最适合本地开发) |
| 检查器 | --debug | 单步执行测试,实时选择定位器 |
| 有头模式 | --headed | 需要查看浏览器时 |
| 慢动作 | --slow-mo=1000 | 操作太快,难以观察时 |
| 调试模式 | PWDEBUG=1 | 打开检查器(较旧的方式,优先使用 --debug) |
| 控制台调试 | PWDEBUG=console | 使用 playwright 对象访问浏览器开发者工具 |
| 追踪查看器 | show-trace trace.zip | 需要分析完整时间线时 |
| 屏幕截图 | page.screenshot() | 需要视觉证据时 |
| 控制台日志 | DEBUG=pw:api | 需要查看 API 调用详情时 |
| 暂停 | await page.pause() | 需要手动检查时 |
不稳定模式
不稳定:80% 的情况下能正常运行
可能原因: 竞态条件
修复方法:
// Replace arbitrary waits
await page.waitForTimeout(2000); // BAD
// With condition-based waits
await expect(page.locator('.result')).toBeVisible(); // GOOD
不稳定:在 CI 中失败,但在本地正常运行
可能原因: 时序差异
修复方法:
// Increase default timeout for CI
test.setTimeout(60000);
page.setDefaultTimeout(30000);
// Wait for network idle
await page.waitForLoadState('networkidle');
不稳定:因“元素不可点击”而失败
可能原因: 元素重叠或动画
修复方法:
// 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();
请记住
调试优先事项:
- 稳定复现问题
- 增加可见性(屏幕截图、日志、追踪)
- 验证元素状态和选择器
- 检查时序和等待
- 在不同模式下测试(有头模式、不同浏览器)
自动等待的优势: Playwright 会自动等待元素满足以下条件:
- 已附加到 DOM
- 可见
- 已启用且稳定
- 未被覆盖层遮挡
大多数操作(点击、填充等)都包含自动等待。只有在处理复杂条件时才需要显式等待。
大多数 Playwright 问题都与时序有关。用基于条件的等待替代任意超时。如不确定,请在有头模式下使用 --ui 或 --debug 放慢速度并观察。
评论
支持 Markdown