151 lines
3.1 KiB
Markdown
151 lines
3.1 KiB
Markdown
# Debugging & Flaky Tests
|
|
|
|
## Debugging Tools
|
|
|
|
```typescript
|
|
// Pause execution and open inspector
|
|
await page.pause();
|
|
|
|
// Enable step-by-step mode
|
|
PWDEBUG=1 npx playwright test
|
|
|
|
// Slow motion
|
|
test.use({ launchOptions: { slowMo: 500 } });
|
|
|
|
// Headed mode
|
|
npx playwright test --headed
|
|
```
|
|
|
|
## Trace Viewer
|
|
|
|
```bash
|
|
# View trace from failed test
|
|
npx playwright show-trace trace.zip
|
|
|
|
# Generate trace always
|
|
test.use({ trace: 'on' });
|
|
|
|
# View in UI mode
|
|
npx playwright test --ui
|
|
```
|
|
|
|
## Common Flaky Test Causes
|
|
|
|
### 1. Race Conditions
|
|
|
|
```typescript
|
|
// ❌ Bad: Element may not exist yet
|
|
await page.click('.submit-btn');
|
|
|
|
// ✅ Good: Auto-waiting built in
|
|
await page.getByRole('button', { name: 'Submit' }).click();
|
|
```
|
|
|
|
### 2. Animation/Transitions
|
|
|
|
```typescript
|
|
// ❌ Bad: Click during animation
|
|
await page.click('.menu-item');
|
|
|
|
// ✅ Good: Wait for stable state
|
|
await page.getByRole('menuitem').click();
|
|
await expect(page.getByRole('menu')).toBeVisible();
|
|
```
|
|
|
|
### 3. Network Timing
|
|
|
|
```typescript
|
|
// ❌ Bad: Assumes data loaded
|
|
await page.goto('/dashboard');
|
|
expect(await page.textContent('.user-name')).toBe('John');
|
|
|
|
// ✅ Good: Wait for network
|
|
await page.goto('/dashboard');
|
|
await page.waitForResponse('**/api/user');
|
|
await expect(page.getByTestId('user-name')).toHaveText('John');
|
|
```
|
|
|
|
### 4. Test Isolation
|
|
|
|
```typescript
|
|
// ❌ Bad: Tests share state
|
|
test('test 1', async () => { /* creates user */ });
|
|
test('test 2', async () => { /* assumes user exists */ });
|
|
|
|
// ✅ Good: Each test is independent
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.request.post('/api/test/reset');
|
|
});
|
|
```
|
|
|
|
## Proper Waiting
|
|
|
|
```typescript
|
|
// Wait for element state
|
|
await expect(page.getByText('Success')).toBeVisible();
|
|
await expect(page.getByRole('button')).toBeEnabled();
|
|
await expect(page.getByRole('dialog')).toBeHidden();
|
|
|
|
// Wait for navigation
|
|
await page.waitForURL(/dashboard/);
|
|
|
|
// Wait for response
|
|
await page.waitForResponse(r => r.url().includes('/api/data'));
|
|
|
|
// Wait for load state
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// AVOID arbitrary waits
|
|
await page.waitForTimeout(3000); // ❌ BAD
|
|
```
|
|
|
|
## Retry Strategies
|
|
|
|
```typescript
|
|
// playwright.config.ts
|
|
export default defineConfig({
|
|
retries: process.env.CI ? 2 : 0,
|
|
|
|
// Retry only specific tests
|
|
expect: {
|
|
timeout: 10000, // Increase assertion timeout
|
|
},
|
|
});
|
|
|
|
// Per-test retry
|
|
test('flaky test', async ({ page }) => {
|
|
test.info().annotations.push({ type: 'issue', description: 'Known flaky' });
|
|
// ...
|
|
});
|
|
```
|
|
|
|
## Debugging Output
|
|
|
|
```typescript
|
|
// Console output
|
|
test('debug test', async ({ page }) => {
|
|
page.on('console', msg => console.log(msg.text()));
|
|
page.on('pageerror', err => console.log(err.message));
|
|
});
|
|
|
|
// Screenshot on step
|
|
await page.screenshot({ path: 'debug.png' });
|
|
```
|
|
|
|
## Quick Reference
|
|
|
|
| Command | Purpose |
|
|
|---------|---------|
|
|
| `PWDEBUG=1` | Enable inspector |
|
|
| `--headed` | Show browser |
|
|
| `--ui` | UI mode |
|
|
| `page.pause()` | Pause execution |
|
|
| `show-trace` | View trace file |
|
|
|
|
| Fix | Flaky Cause |
|
|
|-----|-------------|
|
|
| Auto-wait locators | Race conditions |
|
|
| `waitForResponse` | Network timing |
|
|
| Test isolation | Shared state |
|
|
| Increase timeout | Slow operations |
|