Playwright调试Skill playwright-debugging

这个技能提供系统化的方法,用于诊断和修复 Playwright 浏览器自动化中的问题,如脚本失败、测试不稳定、选择器失效或超时。关键词:Playwright, 调试, 浏览器自动化, 测试, 问题诊断, 前端测试, 自动化测试, 调试工具。

测试 0 次安装 0 次浏览 更新于 3/8/2026

name: playwright-debugging description: 当 Playwright 脚本失败、测试不稳定、选择器停止工作或超时时使用 - 提供浏览器自动化问题的系统化调试方法 user-invocable: false

Playwright 调试

概述

浏览器自动化故障可归为可预测的类别。本技能提供系统化的方法,快速诊断和修复问题。

何时使用

  • 之前工作的脚本现在失败
  • 间歇性测试失败(不稳定性)
  • “元素未找到”错误
  • 超时错误
  • 自动化中的意外行为
  • 元素不可交互

何时不使用:

  • 编写新自动化(使用 playwright-patterns 技能)
  • API 或后端调试

快速参考

问题 首选操作
定位器超时 使用 --ui 模式运行,使用 .count().isVisible() 检查元素状态
不稳定测试(有时通过) 用基于条件的等待替换 waitForTimeout()
“元素不可见” 检查计算样式,等待覆盖层消失
本地工作,CI 失败 使用 waitForLoadState('networkidle'),增加超时
元素不可点击 检查是否被覆盖层覆盖,等待动画完成
陈旧元素 导航后重新查询,而不是存储定位器

诊断框架

1. 复现和隔离

第一步:你能复现吗?

// 运行单个测试以隔离问题
npx playwright test path/to/test.spec.js

// 以有头模式运行以观察
npx playwright test --headed

// 以慢动作运行
npx playwright test --headed --slow-mo=1000

需回答的问题:

  • 失败是否一致或间歇性?
  • 在所有浏览器中失败还是仅一种?
  • 在有头和无头模式中都失败吗?
  • 最近有变化吗(网站更新、代码更改)?

2. 增加可见性

使用 UI 模式进行交互式调试:

# 最适合本地开发 - 提供时间旅行调试
npx playwright test --ui

UI 模式提供:

  • 所有操作的可视时间线
  • 文件更改时的观察模式
  • 网络和控制台标签
  • 测试执行的时间旅行

使用检查器逐步执行测试:

# 通过实时浏览器逐步执行测试
npx playwright test --debug

检查器允许:

  • 逐步执行操作
  • 直接从浏览器拾取定位器
  • 实时编辑选择器并查看结果
  • 查看可操作性日志

在失败点截图:

// 在失败操作前
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;
}

启用详细日志:

# API 级别调试
DEBUG=pw:api npx playwright test

# 带有 playwright 对象的浏览器 DevTools
PWDEBUG=console npx playwright test

使用 PWDEBUG=console,你可以在 DevTools 中访问:

// 在浏览器控制台中
playwright.$('.selector')      // 使用 Playwright 引擎查询
playwright.$$('selector')      // 获取所有匹配项
playwright.inspect('selector') // 在元素面板中高亮
playwright.locator('selector') // 创建定位器

使用跟踪查看器:

// 记录跟踪
await context.tracing.start({ screenshots: true, snapshots: true });
// ... 你的测试代码
await context.tracing.stop({ path: 'trace.zip' });

// 查看跟踪
npx playwright show-trace trace.zip

使用测试步骤组织跟踪:

// 在跟踪查看器中分组操作
await test.step('登录', async () => {
  await page.fill('input[name="username"]', '用户');
  await page.click('button[type="submit"]');
});

await test.step('导航到仪表板', async () => {
  await page.click('a[href="/dashboard"]');
});

为定位器添加描述以提高清晰度:

// 描述出现在跟踪查看器和报告中
const submitButton = page.locator('#submit').describe('提交按钮');
await submitButton.click();

VS Code 调试:

安装 Playwright VS Code 扩展以:

  • 在 VS Code 中使用断点进行实时调试
  • 编辑时在浏览器中高亮定位器
  • “显示浏览器”选项以获得实时反馈
  • 右键单击任何测试进行“调试测试”

这将调试直接集成到你的编辑器工作流程中。

3. 检查元素状态

检查元素是否存在:

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);

4. 验证选择器

在浏览器控制台中测试选择器:

// 使用 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);

检查多个匹配项:

// 有多个元素吗?
const all = await page.locator('.button').all();
console.log(`找到 ${all.length} 个匹配元素`);

// 获取所有匹配项的文本
const texts = await page.locator('.button').allTextContents();
console.log('所有匹配文本:', texts);

常见问题和修复

问题:元素未找到

原因:

  • 选择器错误
  • 元素尚未加载
  • 元素在 iframe 中
  • 元素是动态创建的

调试步骤:

// 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);

问题:元素不可见/不可点击

原因:

  • 元素被隐藏(CSS: display:none, visibility:hidden)
  • 元素被另一个元素覆盖
  • 元素在视口外
  • 元素动画未完成

调试步骤:

// 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 });

问题:时序/竞态条件

原因:

  • 网络请求未完成
  • 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);

问题:陈旧元素引用

原因:

  • 页面刷新或导航
  • 元素从 DOM 中移除并重新添加
  • 动态内容替换了元素

修复:

// 不要在导航过程中存储元素句柄
const button = page.locator('.button'); // 错误:可能变陈旧
await page.goto('/other-page');
await button.click(); // 错误:陈旧

// 在导航后重新查询
await page.goto('/other-page');
await page.locator('.button').click(); // 正确:新鲜查询

问题:表单提交不工作

原因:

  • 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());

常见错误

错误 为什么错误 正确方法
添加 waitForTimeout(5000) 掩盖时序问题,使测试变慢,不可靠 使用基于条件的等待:expect().toBeVisible()
在未理解原因的情况下强制点击 绕过 Playwright 的可操作性检查 诊断元素不可点击的原因,修复根本原因
不使用现代调试工具 诊断缓慢,猜测问题 --ui--debug 开始进行可视化调试
仅在有头模式下测试 隐藏 CI 中出现的时序问题 始终在无头模式下测试
使用脆弱的选择器 HTML 结构更改时中断 使用基于角色或 data-testid 的选择器
跳过跟踪查看器 错过发生的详细时间线 为失败测试启用跟踪

调试检查清单

自动化失败时,按顺序检查:

  1. ☐ 我能一致复现失败吗?
  2. ☐ 在有头模式和慢动作下失败吗?
  3. ☐ 我是否在失败前后截图了?
  4. ☐ 选择器是否实际匹配元素?
  5. ☐ 元素是否可见和启用?
  6. ☐ 元素是否在 iframe 中?
  7. ☐ 我是否等待了页面加载完成?
  8. ☐ 是否有需要时间加载的动态内容?
  9. ☐ 是否有网络请求仍在进行?
  10. ☐ 我是否检查了浏览器控制台的 JavaScript 错误?

调试工具参考

工具 命令 使用时机
UI 模式 --ui 时间旅行调试,带可视时间线(最适合本地开发)
检查器 --debug 逐步执行测试,实时拾取定位器
有头模式 --headed 需要查看浏览器
慢动作 --slow-mo=1000 动作太快无法观察
调试模式 PWDEBUG=1 打开检查器(旧方法,优先使用 --debug)
控制台调试 PWDEBUG=console 使用 playwright 对象访问浏览器 DevTools
跟踪查看器 show-trace trace.zip 需要完整时间线分析
截图 page.screenshot() 需要视觉证据
控制台日志 DEBUG=pw:api 需要 API 调用详情
暂停 await page.pause() 需要手动检查

不稳定模式

不稳定:80% 的时间工作

可能原因: 竞态条件

修复:

// 替换任意等待
await page.waitForTimeout(2000); // 错误

// 使用基于条件的等待
await expect(page.locator('.result')).toBeVisible(); // 正确

不稳定:CI 失败,本地工作

可能原因: 时序差异

修复:

// 为 CI 增加默认超时
test.setTimeout(60000);
page.setDefaultTimeout(30000);

// 等待网络空闲
await page.waitForLoadState('networkidle');

不稳定:失败并显示“元素不可点击”

可能原因: 重叠元素或动画

修复:

// 等待元素可操作
await expect(page.locator('.button')).toBeVisible();
await expect(page.locator('.button')).toBeEnabled();

// 或等待覆盖层消失
await expect(page.locator('.loading-overlay')).not.toBeVisible();

记住

调试优先级:

  1. 可靠复现问题
  2. 增加可见性(截图、日志、跟踪)
  3. 验证元素状态和选择器
  4. 检查时序和等待
  5. 在不同模式(有头、浏览器)中测试

自动等待优势: Playwright 自动等待元素:

  • 附加到 DOM
  • 可见
  • 启用且稳定
  • 不被覆盖层覆盖

大多数操作(点击、填充等)包括自动等待。显式等待仅需用于复杂条件。

大多数 Playwright 问题是时序相关的。用基于条件的等待替换任意超时。如有疑问,减速并在有头模式下使用 --ui--debug 观察。