3.5 KiB
3.5 KiB
Debugging Guide
First Moves
- Reproduce in headed mode.
- Capture a trace before rewriting selectors or waits.
- Check whether the failure is selector drift, actionability, environment drift, shared state, or a real product regression.
Inspector and Headed Runs
npx playwright test --debug
npx playwright test my-test.spec.ts --debug
npx playwright test --headed
await page.pause();
Trace Viewer
npx playwright test --trace on
npx playwright show-trace trace.zip
use: {
trace: 'retain-on-failure',
}
Start with traces for CI and flaky failures. Use screenshots and videos as supporting evidence, not as the primary debugging tool.
Common Errors
Element Not Found
Use explicit waits and confirm the right frame or shadow boundary before rewriting selectors. If the locator is ambiguous, improve the locator instead of clicking the first match.
await page.waitForSelector('.element');
const frame = page.frameLocator('iframe');
await frame.locator('.element').click();
await page.click('.element', { timeout: 60000 });
Flaky Click
Check visibility, scrolling, overlays, and disabled state before forcing the click.
await page.locator('.btn').waitFor({ state: 'visible' });
await page.locator('.btn').scrollIntoViewIfNeeded();
await page.locator('.btn').click();
Use force: true only after confirming that the overlay or disabled state is not the real bug.
If the click target keeps changing, inspect actionability conditions first: visible, stable, enabled, and actually receiving pointer events.
Timeout in CI
Slow environments usually need better waits, traces, or fewer workers before they need bigger timeouts.
export default defineConfig({
timeout: 60000,
expect: { timeout: 10000 },
});
await expect.poll(async () => {
return await page.locator('.items').count();
}, { timeout: 30000 }).toBeGreaterThan(5);
Network Issues
page.on('request', request => {
console.log('>>', request.method(), request.url());
});
page.on('response', response => {
console.log('<<', response.status(), response.url());
});
const responsePromise = page.waitForResponse('**/api/data');
await page.click('.load-data');
const response = await responsePromise;
Failure Artifacts
test.afterEach(async ({ page }, testInfo) => {
if (testInfo.status !== 'passed') {
await page.screenshot({
path: `screenshots/${testInfo.title}.png`,
fullPage: true,
});
}
});
Console and Runtime Errors
page.on('console', msg => {
console.log('PAGE LOG:', msg.text());
});
page.on('pageerror', error => {
console.log('PAGE ERROR:', error.message);
});
Compare Local vs CI
| Check | Command |
|---|---|
| Viewport | await page.viewportSize() |
| User agent | await page.evaluate(() => navigator.userAgent) |
| Timezone | await page.evaluate(() => Intl.DateTimeFormat().resolvedOptions().timeZone) |
| Network | page.on('request', ...) |
| Shared auth/data | verify whether tests mutate the same account or fixtures |
Debugging Checklist
- Run with
--debugor--headed. - Add
await page.pause()before the failure point. - Capture trace, screenshot, and console output before changing selectors.
- Check for iframes, shadow DOM, overlays, loading states, and shared auth or data collisions.
- Compare viewport, network behavior, workers, and environment flags between local and CI.
- Only then rewrite selectors, waits, fixtures, or test structure.