Loading...
Loading...
Compare original and translation side by side
| 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环境执行失败 | 使用 |
| 元素无法点击 | 检查是否被遮罩层覆盖,等待动画完成 |
| 元素状态过期 | 导航后重新查询元素,而非存储定位器 |
// 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// 运行单个测试以隔离问题
npx playwright test path/to/test.spec.js
// 使用有头模式运行以观察执行过程
npx playwright test --headed
// 使用慢动作模式运行
npx playwright test --headed --slow-mo=1000undefined// 最适合本地开发——提供时间旅行调试功能
npx playwright test --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对象的浏览器开发者工具
PWDEBUG=console npx playwright testPWDEBUG=console// 在浏览器控制台中
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"]', 'user');
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();
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:**
```bashconst 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);
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;
}undefined// 使用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);// 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);
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// 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();// 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 });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);// 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);// 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);// 不要在导航后复用存储的元素句柄
const button = page.locator('.button'); // 错误:可能会过期
await page.goto('/other-page');
await button.click(); // 错误:元素状态已过期
// 导航后重新查询元素
await page.goto('/other-page');
await page.locator('.button').click(); // 正确:重新查询的新鲜元素// 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);// 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());// 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的选择器 |
| 跳过追踪查看器 | 错过详细的执行时间线 | 为失败的测试启用追踪功能 |
// 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);// 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调用详情 |
| 暂停 | | 需要手动检查 |
// 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());// 替换固定时长等待
await page.waitForTimeout(2000); // 错误
// 使用基于条件的等待
await expect(page.locator('.result')).toBeVisible(); // 正确| 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 |
// 为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();| 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 |
--ui--debug// Replace arbitrary waits
await page.waitForTimeout(2000); // BAD
// With condition-based waits
await expect(page.locator('.result')).toBeVisible(); // GOOD// 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();--ui--debug