Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:28:25 +08:00
commit bc292955bb
23 changed files with 5385 additions and 0 deletions

View File

@@ -0,0 +1,498 @@
# Test Debugger Skill
## Purpose
Debug failing Playwright E2E tests by analyzing error messages, stack traces, screenshots, and Playwright traces. Provides actionable solutions for common test failures including timeouts, selector issues, race conditions, and unexpected behaviors. Optionally uses Playwright MCP for live debugging.
## When to Use This Skill
Use this skill when you need to:
- Fix failing or flaky Playwright tests
- Understand why a test is timing out
- Debug element selector issues
- Analyze test traces and screenshots
- Resolve race conditions
- Investigate unexpected test behavior
- Use Playwright MCP to inspect live browser state
Do NOT use this skill when:
- Creating new tests (use test-generator skill)
- Building Page Objects (use page-object-builder skill)
- Refactoring test code (use test-maintainer skill)
## Prerequisites
Before using this skill:
1. Failing test file or error message
2. Test execution output or logs
3. Optional: Screenshots from test failure
4. Optional: Playwright trace file
5. Optional: Playwright MCP access for live debugging
## Instructions
### Step 1: Gather Failure Information
Ask the user for:
- **Error message** and stack trace
- **Test file** location and test name
- **Expected vs actual** behavior
- **Screenshots** (if available)
- **Trace file** path (if available)
- **Frequency**: Does it fail always or intermittently (flaky)?
- **Environment**: Local, CI, specific browser?
### Step 2: Analyze the Error
Identify the error type:
**Timeout Errors:**
- `Timeout 30000ms exceeded`
- `waiting for selector`
- `waiting for navigation`
**Selector Errors:**
- `Element not found`
- `strict mode violation`
- `No node found`
**Assertion Errors:**
- `Expected ... but received ...`
- `toBe`, `toContain`, `toBeVisible` failures
**Navigation Errors:**
- `Target closed`
- `Navigation failed`
- `ERR_CONNECTION_REFUSED`
**Race Conditions:**
- Intermittent failures
- Works locally but fails in CI
- Different results on different runs
### Step 3: Use Playwright MCP (Optional)
If Playwright MCP is available and needed:
- Use MCP tools to inspect browser state
- Navigate to the problematic page
- Check element visibility and attributes
- Verify data-testid values exist
- Test locator strategies
- Capture screenshots
### Step 4: Identify Root Cause
Common root causes:
**1. Missing or Wrong data-testid:**
- Element has different testid than expected
- testid doesn't exist in HTML
- Multiple elements with same testid
**2. Timing Issues:**
- Element not yet loaded when accessed
- No explicit wait before interaction
- Network requests still pending
- Animations or transitions in progress
**3. Element State:**
- Element exists but not visible
- Element disabled or not clickable
- Element covered by another element
- Element in different frame/iframe
**4. Test Isolation:**
- Tests depend on each other
- Shared state between tests
- Cleanup not performed
- Browser context pollution
**5. Environment Differences:**
- Different viewport sizes
- Different network speeds
- CI vs local differences
- Browser-specific issues
### Step 5: Provide Solution
For each root cause, provide:
1. **Explanation**: What's wrong
2. **Fix**: Code changes needed
3. **Prevention**: How to avoid in future
4. **Verification**: How to confirm it's fixed
### Step 6: Apply the Fix
**For Selector Issues:**
```typescript
// ❌ Before
await page.locator('[data-testid="wrong-id"]').click();
// ✅ After
await page.locator('[data-testid="correct-id"]').click();
```
**For Timeout Issues:**
```typescript
// ❌ Before
await page.locator('[data-testid="submit"]').click();
// ✅ After
await page.locator('[data-testid="submit"]').waitFor({ state: 'visible' });
await page.locator('[data-testid="submit"]').click();
```
**For Race Conditions:**
```typescript
// ❌ Before
await page.locator('[data-testid="submit"]').click();
await expect(page.locator('[data-testid="result"]')).toBeVisible();
// ✅ After
await page.locator('[data-testid="submit"]').click();
await page.waitForLoadState('networkidle');
await expect(page.locator('[data-testid="result"]')).toBeVisible();
```
### Step 7: Verify the Fix
Guide the user to:
1. Run the test locally multiple times (3-5 times)
2. Check if error is resolved
3. Verify test passes consistently
4. Run related tests to ensure no regression
5. Consider running in CI if flakiness was CI-specific
## Examples
### Example 1: Timeout Error
**Input:**
```
Test failed with error:
TimeoutError: Timeout 30000ms exceeded.
=========================== logs ===========================
waiting for locator('[data-testid="submit-button"]')
```
**Analysis:**
- Timeout error waiting for element
- Element may not be loading
- Selector may be incorrect
**Solution:**
```typescript
// Check if the data-testid is correct in the HTML
// Add explicit wait with better error message
await page.locator('[data-testid="submit-button"]').waitFor({
state: 'visible',
timeout: 30000
});
// If still failing, verify element exists in DOM
const exists = await page.locator('[data-testid="submit-button"]').count();
console.log(`Submit button count: ${exists}`); // Should be 1
// Check if page has loaded
await page.waitForLoadState('domcontentloaded');
// Final solution
await page.waitForLoadState('domcontentloaded');
await page.locator('[data-testid="submit-button"]').waitFor({ state: 'visible' });
await page.locator('[data-testid="submit-button"]').click();
```
**Prevention:**
- Always add explicit waits before interactions
- Verify data-testid values in HTML
- Use `waitForLoadState` after navigation
### Example 2: Element Not Found
**Input:**
```
Error: Element not found
locator.click: Target closed
locator('[data-testid="user-menu"]')
```
**Analysis:**
- Element may not exist in current page state
- Possible strict mode violation (multiple elements)
- Element may be in a different frame
**Solution:**
```typescript
// Step 1: Verify element exists
const count = await page.locator('[data-testid="user-menu"]').count();
console.log(`Found ${count} elements`);
// If count = 0: Element doesn't exist, check data-testid in HTML
// If count > 1: Multiple elements, need to be more specific
// Step 2: If multiple elements, use .first() or filter
await page.locator('[data-testid="user-menu"]').first().click();
// Step 3: If in iframe, switch to frame first
const frame = page.frameLocator('[data-testid="app-frame"]');
await frame.locator('[data-testid="user-menu"]').click();
// Step 4: Add proper wait
await page.locator('[data-testid="user-menu"]').waitFor({ state: 'attached' });
await page.locator('[data-testid="user-menu"]').click();
```
**Prevention:**
- Ensure data-testid values are unique on the page
- Check for elements in frames/iframes
- Add waits before interaction
### Example 3: Flaky Test (Passes Sometimes)
**Input:**
```
Test fails intermittently:
- Passes 70% of the time locally
- Fails 90% of the time in CI
Error: expect(received).toContainText(expected)
Expected substring: "Success"
Received string: ""
```
**Analysis:**
- Classic race condition
- Element loads but content not yet populated
- Likely caused by async data fetching
- CI is slower so more likely to fail
**Solution:**
```typescript
// ❌ Before: No wait for content
await page.locator('[data-testid="submit"]').click();
await expect(page.locator('[data-testid="message"]')).toContainText('Success');
// ✅ After: Wait for specific condition
await page.locator('[data-testid="submit"]').click();
// Option 1: Wait for network to settle
await page.waitForLoadState('networkidle');
await expect(page.locator('[data-testid="message"]')).toContainText('Success');
// Option 2: Wait for specific API call
await Promise.all([
page.waitForResponse('**/api/submit'),
page.locator('[data-testid="submit"]').click()
]);
await expect(page.locator('[data-testid="message"]')).toContainText('Success');
// Option 3: Use Playwright's auto-waiting in assertion
await page.locator('[data-testid="submit"]').click();
await expect(page.locator('[data-testid="message"]')).toContainText('Success', {
timeout: 10000 // Explicit timeout for slow operations
});
```
**Prevention:**
- Wait for network requests to complete
- Use explicit timeouts for slow operations
- Run tests multiple times to catch flakiness
- Enable retries in playwright.config.ts
### Example 4: Assertion Failure
**Input:**
```
Error: expect(received).toBeVisible()
locator('[data-testid="success-message"]')
Expected: visible
Received: hidden
```
**Analysis:**
- Element exists but is hidden
- May need to wait for element to appear
- Check if element has conditional visibility
- Verify test logic is correct
**Solution:**
```typescript
// Step 1: Verify element exists
const exists = await page.locator('[data-testid="success-message"]').count();
console.log(`Element count: ${exists}`);
// Step 2: Check element state
const isVisible = await page.locator('[data-testid="success-message"]').isVisible();
console.log(`Is visible: ${isVisible}`);
// Step 3: Wait for visibility with timeout
await page.locator('[data-testid="success-message"]').waitFor({
state: 'visible',
timeout: 10000
});
// Step 4: If still not visible, check CSS
const display = await page.locator('[data-testid="success-message"]').evaluate(
el => window.getComputedStyle(el).display
);
console.log(`Display property: ${display}`);
// Step 5: Final solution
await page.locator('[data-testid="submit"]').click();
await page.waitForLoadState('networkidle');
await expect(page.locator('[data-testid="success-message"]')).toBeVisible({
timeout: 10000
});
```
**Prevention:**
- Add explicit waits for elements that appear after actions
- Verify success conditions in the application logic
- Use appropriate timeout values
### Example 5: Using Playwright MCP for Debugging
**Input:**
"My test is failing but I can't figure out why. The error says element not found but I see it in the screenshot."
**Solution (Using MCP):**
```
1. Use Playwright MCP to navigate to the page:
- Navigate to the page where test fails
- Take screenshot to verify page state
2. Use MCP to check if element exists:
- Use MCP to find elements by data-testid
- Check how many elements match
- Inspect element attributes
3. Use MCP to test the locator:
- Try different locator strategies
- Check element visibility
- Verify element is in correct frame
4. Based on MCP findings, update the test:
- If element has different testid: Update locator
- If element in iframe: Add frame handling
- If multiple matches: Make locator more specific
```
## Best Practices
### Debugging Process
1. **Read error carefully**: Error messages are usually accurate
2. **Check test in isolation**: Run the single failing test
3. **Use debugging tools**: Screenshots, traces, MCP
4. **Add console logs**: Temporary logs to understand state
5. **Verify assumptions**: Check that data-testid values are correct
6. **Test incrementally**: Fix one issue at a time
### Common Debugging Techniques
1. **Add explicit waits**: Most failures are timing-related
2. **Check element count**: Verify unique selectors
3. **Use page.pause()**: Interactive debugging mode
4. **Enable headed mode**: See what's happening visually
5. **Slow motion**: Add `slowMo` in config to slow down actions
6. **Check traces**: Use Playwright trace viewer
### Preventing Future Issues
1. **Always use data-testid**: Stable locators
2. **Add explicit waits**: Don't rely on auto-waiting alone
3. **Test isolation**: Each test should be independent
4. **Proper cleanup**: Reset state between tests
5. **Handle async**: Wait for network/animations
6. **Run multiple times**: Catch flaky tests early
## Common Issues and Solutions
### Issue 1: Test Passes Locally but Fails in CI
**Problem:** Test works on developer machine but fails in CI environment
**Solutions:**
- **Viewport difference**: CI may use different screen size
```typescript
await page.setViewportSize({ width: 1920, height: 1080 });
```
- **Slower CI**: Increase timeouts for CI
```typescript
timeout: process.env.CI ? 60000 : 30000
```
- **Headless issues**: Test in headless mode locally
```bash
npx playwright test --headed=false
```
- **Network speed**: Add retries in config for CI
### Issue 2: Cannot Find Element with Correct data-testid
**Problem:** Selector looks correct but element not found
**Solutions:**
- Check element is in main page, not iframe
- Verify element is not dynamically loaded later
- Check for typos in data-testid value
- Use Playwright MCP to inspect actual HTML
- Add wait for element to be added to DOM
```typescript
await page.waitForSelector('[data-testid="element"]');
```
### Issue 3: Test Works First Time but Fails on Reruns
**Problem:** First run passes, subsequent runs fail
**Solutions:**
- **State leaking**: Tests aren't isolated
- Use `test.beforeEach` to reset state
- Use fixtures for clean context
- **Storage persistence**: Clear local storage/cookies
```typescript
await page.context().clearCookies();
await page.evaluate(() => localStorage.clear());
```
- **Database state**: Reset test database between runs
### Issue 4: Element Found but Click Doesn't Work
**Problem:** `element.click()` doesn't do anything or throws error
**Solutions:**
- **Element covered**: Another element is covering it
```typescript
await page.locator('[data-testid="modal-close"]').click({ force: true });
```
- **Element disabled**: Check if element is enabled
```typescript
await expect(page.locator('[data-testid="submit"]')).toBeEnabled();
```
- **Wrong element**: Multiple elements match, clicking wrong one
```typescript
await page.locator('[data-testid="item"]').first().click();
```
- **Animation in progress**: Wait for animations to complete
```typescript
await page.waitForTimeout(500); // Avoid this
// Better: Wait for element to be stable
await page.locator('[data-testid="element"]').waitFor({ state: 'visible' });
await page.waitForLoadState('networkidle');
```
### Issue 5: Assertion Timing Out
**Problem:** `expect()` assertion times out after 5 seconds
**Solutions:**
- **Increase timeout**: For slow operations
```typescript
await expect(locator).toBeVisible({ timeout: 15000 });
```
- **Wait for condition**: Add wait before assertion
```typescript
await page.waitForLoadState('networkidle');
await expect(locator).toBeVisible();
```
- **Wrong expectation**: Verify what you're asserting is correct
- Check expected text/value in application
- Verify element actually appears on success
## Resources
The `resources/` directory contains helpful references:
- `debugging-checklist.md` - Step-by-step debugging guide
- `common-errors.md` - List of common errors and quick fixes
- `playwright-commands.md` - Useful Playwright debugging commands

View File

@@ -0,0 +1,378 @@
# Common Playwright Errors and Quick Fixes
## Timeout Errors
### Error: "Timeout 30000ms exceeded waiting for selector"
**Cause:** Element not found within timeout period
**Quick Fixes:**
```typescript
// 1. Add explicit wait
await page.waitForSelector('[data-testid="element"]', { state: 'visible' });
// 2. Verify data-testid is correct
const count = await page.locator('[data-testid="element"]').count();
console.log('Found', count, 'elements');
// 3. Wait for page to load first
await page.waitForLoadState('domcontentloaded');
// 4. Increase timeout if legitimately slow
await page.locator('[data-testid="element"]').waitFor({
state: 'visible',
timeout: 60000
});
```
### Error: "Timeout 30000ms exceeded waiting for navigation"
**Cause:** Page navigation taking too long or not happening
**Quick Fixes:**
```typescript
// 1. Wait for specific URL
await page.waitForURL('/expected-path', { timeout: 45000 });
// 2. Wait for load state
await page.waitForLoadState('networkidle');
// 3. Check if navigation actually occurs
console.log('Current URL:', page.url());
```
## Selector Errors
### Error: "strict mode violation: locator resolved to X elements"
**Cause:** Multiple elements match the selector
**Quick Fixes:**
```typescript
// 1. Use .first() or .last()
await page.locator('[data-testid="item"]').first().click();
// 2. Use .nth() for specific element
await page.locator('[data-testid="item"]').nth(2).click();
// 3. Make selector more specific (avoid if possible)
await page.locator('[data-testid="item"][aria-selected="true"]').click();
// 4. Filter locators
await page.locator('[data-testid="item"]').filter({ hasText: 'Active' }).click();
```
### Error: "Element not found"
**Cause:** Element doesn't exist or wrong selector
**Quick Fixes:**
```typescript
// 1. Verify element exists
const exists = await page.locator('[data-testid="element"]').count() > 0;
console.log('Element exists:', exists);
// 2. Check for typos in data-testid
// Verify in HTML: <button data-testid="correct-id">
// 3. Wait for element to be added to DOM
await page.locator('[data-testid="element"]').waitFor({ state: 'attached' });
// 4. Check if in iframe
const frame = page.frameLocator('[data-testid="app-frame"]');
await frame.locator('[data-testid="element"]').click();
```
## Assertion Errors
### Error: "expect(received).toBeVisible() - Expected visible but received hidden"
**Cause:** Element exists but is not visible
**Quick Fixes:**
```typescript
// 1. Wait for element to become visible
await page.locator('[data-testid="element"]').waitFor({ state: 'visible' });
await expect(page.locator('[data-testid="element"]')).toBeVisible();
// 2. Check visibility with timeout
await expect(page.locator('[data-testid="element"]')).toBeVisible({
timeout: 10000
});
// 3. Debug visibility
const isVisible = await page.locator('[data-testid="element"]').isVisible();
console.log('Is visible:', isVisible);
```
### Error: "expect(received).toContainText() - Expected substring not found"
**Cause:** Element doesn't contain expected text or not loaded yet
**Quick Fixes:**
```typescript
// 1. Wait for text to appear
await expect(page.locator('[data-testid="element"]')).toContainText('Expected', {
timeout: 10000
});
// 2. Wait for network before checking
await page.waitForLoadState('networkidle');
await expect(page.locator('[data-testid="element"]')).toContainText('Expected');
// 3. Check actual text content
const text = await page.locator('[data-testid="element"]').textContent();
console.log('Actual text:', text);
```
## Click Errors
### Error: "locator.click: Target closed"
**Cause:** Element or page disappeared before click completed
**Quick Fixes:**
```typescript
// 1. Wait for element to be stable
await page.locator('[data-testid="element"]').waitFor({ state: 'visible' });
await page.waitForTimeout(100); // Allow animations to settle
await page.locator('[data-testid="element"]').click();
// 2. Use force click if element is covered
await page.locator('[data-testid="element"]').click({ force: true });
// 3. Check if element is being removed
const count = await page.locator('[data-testid="element"]').count();
console.log('Element count before click:', count);
```
### Error: "Element is not clickable"
**Cause:** Element is disabled, covered, or not in viewport
**Quick Fixes:**
```typescript
// 1. Check if element is enabled
await expect(page.locator('[data-testid="button"]')).toBeEnabled();
await page.locator('[data-testid="button"]').click();
// 2. Scroll into view
await page.locator('[data-testid="element"]').scrollIntoViewIfNeeded();
await page.locator('[data-testid="element"]').click();
// 3. Wait for element to be clickable
await page.locator('[data-testid="element"]').waitFor({ state: 'visible' });
await expect(page.locator('[data-testid="element"]')).toBeEnabled();
await page.locator('[data-testid="element"]').click();
```
## Network Errors
### Error: "net::ERR_CONNECTION_REFUSED"
**Cause:** Server is not running or wrong URL
**Quick Fixes:**
```typescript
// 1. Verify server is running
// Check if dev server is started: npm run dev
// 2. Check baseURL in playwright.config.ts
use: {
baseURL: 'http://localhost:3000', // Correct port?
}
// 3. Use full URL if baseURL not set
await page.goto('http://localhost:3000/path');
```
### Error: "Navigation failed because page was closed"
**Cause:** Page closed prematurely during navigation
**Quick Fixes:**
```typescript
// 1. Don't close page too early
// Remove premature page.close() calls
// 2. Wait for navigation to complete
await page.goto('/path');
await page.waitForLoadState('domcontentloaded');
// 3. Check for popup blockers or redirects
```
## Fill/Type Errors
### Error: "Cannot type into a non-editable element"
**Cause:** Trying to fill a non-input element
**Quick Fixes:**
```typescript
// 1. Verify element is an input/textarea
const tagName = await page.locator('[data-testid="element"]').evaluate(
el => el.tagName
);
console.log('Tag name:', tagName); // Should be INPUT or TEXTAREA
// 2. Check if element is contenteditable
const isEditable = await page.locator('[data-testid="element"]').evaluate(
el => el.contentEditable
);
// 3. Use correct element selector
// Ensure data-testid is on the input, not its container
```
### Error: "Element is disabled"
**Cause:** Trying to interact with disabled element
**Quick Fixes:**
```typescript
// 1. Wait for element to be enabled
await expect(page.locator('[data-testid="input"]')).toBeEnabled();
await page.locator('[data-testid="input"]').fill('value');
// 2. Check why element is disabled
const isDisabled = await page.locator('[data-testid="input"]').isDisabled();
console.log('Is disabled:', isDisabled);
// 3. Ensure prerequisites are met (e.g., terms accepted)
await page.locator('[data-testid="terms-checkbox"]').check();
await page.locator('[data-testid="submit"]').click();
```
## Frame/Iframe Errors
### Error: "Frame was detached"
**Cause:** Interacting with iframe that was removed
**Quick Fixes:**
```typescript
// 1. Re-acquire frame reference
const frame = page.frameLocator('[data-testid="app-frame"]');
await frame.locator('[data-testid="element"]').click();
// 2. Wait for frame to be ready
await page.frameLocator('[data-testid="app-frame"]').locator('body').waitFor();
// 3. Check if frame exists
const frameCount = await page.frames().length;
console.log('Frame count:', frameCount);
```
## Race Condition Errors
### Error: Test passes sometimes, fails other times
**Cause:** Race condition / flaky test
**Quick Fixes:**
```typescript
// 1. Wait for network to settle
await page.waitForLoadState('networkidle');
// 2. Wait for specific API calls
await page.waitForResponse('**/api/data');
// 3. Add explicit waits
await page.locator('[data-testid="element"]').waitFor({ state: 'visible' });
// 4. Increase timeouts
await expect(page.locator('[data-testid="result"]')).toBeVisible({
timeout: 15000
});
// 5. Enable retries in playwright.config.ts
retries: 2,
```
## Context/State Errors
### Error: "localStorage is not defined"
**Cause:** Accessing localStorage before page is loaded
**Quick Fixes:**
```typescript
// 1. Navigate to page first
await page.goto('/');
await page.evaluate(() => localStorage.setItem('key', 'value'));
// 2. Use context.addInitScript for early setup
await context.addInitScript(() => {
localStorage.setItem('key', 'value');
});
```
### Error: "Test passes in isolation but fails in suite"
**Cause:** Test pollution / shared state
**Quick Fixes:**
```typescript
// 1. Clear state in beforeEach
test.beforeEach(async ({ page }) => {
await page.context().clearCookies();
await page.evaluate(() => {
localStorage.clear();
sessionStorage.clear();
});
});
// 2. Use test.describe.serial for dependent tests
test.describe.serial('Login flow', () => {
// Tests run in order
});
// 3. Create fresh context for each test
test.use({ storageState: undefined });
```
## Configuration Errors
### Error: "Cannot find module '@playwright/test'"
**Cause:** Playwright not installed
**Quick Fixes:**
```bash
# Install Playwright
npm install -D @playwright/test
# Install browsers
npx playwright install
```
### Error: "No tests found"
**Cause:** Test files not matching pattern
**Quick Fixes:**
```typescript
// In playwright.config.ts
testDir: './tests',
testMatch: '**/*.spec.ts', // Or *.test.ts
```
## Debugging Commands
```bash
# Run with UI mode
npx playwright test --ui
# Run in headed mode
npx playwright test --headed
# Debug specific test
npx playwright test --debug test-file.spec.ts
# Show trace
npx playwright show-trace trace.zip
# Generate code
npx playwright codegen localhost:3000
```

View File

@@ -0,0 +1,250 @@
# Playwright Test Debugging Checklist
## Initial Assessment
- [ ] Read the error message carefully
- [ ] Note the failing test name and location
- [ ] Check if test fails consistently or intermittently
- [ ] Identify which browser(s) are failing
- [ ] Check if failure is local only or also in CI
## Error Type Identification
### Timeout Errors
- [ ] Check if element exists with correct data-testid
- [ ] Verify page has loaded completely
- [ ] Add explicit wait before interaction
- [ ] Check if network requests are pending
- [ ] Increase timeout if operation is legitimately slow
### Selector Errors
- [ ] Verify data-testid in HTML matches test code
- [ ] Check if multiple elements have the same testid
- [ ] Ensure element is not in an iframe
- [ ] Confirm element is added to DOM (not removed)
- [ ] Use Playwright Inspector to test selector
### Assertion Errors
- [ ] Verify expected value is correct
- [ ] Check if element exists and is visible
- [ ] Add wait before assertion
- [ ] Check if content is populated asynchronously
- [ ] Increase assertion timeout if needed
### Navigation Errors
- [ ] Check if URL is correct
- [ ] Verify server is running
- [ ] Check for network issues
- [ ] Ensure proper wait after navigation
- [ ] Look for redirects or authentication requirements
## Element Investigation
- [ ] Count elements matching selector (should be 1)
```typescript
const count = await page.locator('[data-testid="element"]').count();
console.log('Element count:', count);
```
- [ ] Check element visibility
```typescript
const isVisible = await page.locator('[data-testid="element"]').isVisible();
console.log('Is visible:', isVisible);
```
- [ ] Check element state
```typescript
const isEnabled = await page.locator('[data-testid="element"]').isEnabled();
console.log('Is enabled:', isEnabled);
```
- [ ] Get element attributes
```typescript
const testId = await page.locator('[data-testid="element"]').getAttribute('data-testid');
console.log('data-testid:', testId);
```
## Timing Investigation
- [ ] Add waits before interactions
```typescript
await page.locator('[data-testid="element"]').waitFor({ state: 'visible' });
```
- [ ] Wait for page load
```typescript
await page.waitForLoadState('domcontentloaded');
await page.waitForLoadState('networkidle');
```
- [ ] Wait for specific API calls
```typescript
await page.waitForResponse('**/api/endpoint');
```
- [ ] Check if animations/transitions are in progress
```typescript
await page.waitForTimeout(500); // Only for testing, remove later
```
## Interactive Debugging
- [ ] Run test in headed mode
```bash
npx playwright test --headed
```
- [ ] Use debug mode
```bash
npx playwright test --debug
```
- [ ] Add `page.pause()` in test
```typescript
await page.pause(); // Pauses execution for manual inspection
```
- [ ] Enable slow motion
```typescript
// In playwright.config.ts
use: {
launchOptions: {
slowMo: 1000, // Slow down by 1 second
},
}
```
- [ ] Take screenshots for inspection
```typescript
await page.screenshot({ path: 'debug-screenshot.png', fullPage: true });
```
## Trace Analysis
- [ ] Enable trace recording
```typescript
// In playwright.config.ts
use: {
trace: 'on-first-retry',
}
```
- [ ] Open trace viewer
```bash
npx playwright show-trace trace.zip
```
- [ ] Check trace for:
- Network requests
- Console logs
- Screenshots at each step
- DOM snapshots
- Action timeline
## Test Isolation Check
- [ ] Run test in isolation (comment out other tests)
- [ ] Check if test passes when run alone
- [ ] Verify no shared state between tests
- [ ] Ensure proper cleanup in `afterEach`
- [ ] Check for global state mutations
## Environment Differences
### Local vs CI
- [ ] Run test in headless mode locally
```bash
npx playwright test --headed=false
```
- [ ] Check viewport size matches CI
```typescript
await page.setViewportSize({ width: 1920, height: 1080 });
```
- [ ] Compare timeouts (CI may need longer)
- [ ] Check if CI has different environment variables
### Browser Differences
- [ ] Test in all configured browsers
```bash
npx playwright test --project=chromium
npx playwright test --project=firefox
npx playwright test --project=webkit
```
- [ ] Check for browser-specific issues
- [ ] Verify CSS compatibility
- [ ] Test JavaScript feature support
## Common Quick Fixes
### Fix #1: Add Explicit Wait
```typescript
// Before
await page.locator('[data-testid="element"]').click();
// After
await page.locator('[data-testid="element"]').waitFor({ state: 'visible' });
await page.locator('[data-testid="element"]').click();
```
### Fix #2: Wait for Network
```typescript
// Before
await page.locator('[data-testid="submit"]').click();
await expect(page.locator('[data-testid="result"]')).toBeVisible();
// After
await page.locator('[data-testid="submit"]').click();
await page.waitForLoadState('networkidle');
await expect(page.locator('[data-testid="result"]')).toBeVisible();
```
### Fix #3: Handle Multiple Elements
```typescript
// Before
await page.locator('[data-testid="item"]').click(); // Error if multiple
// After
await page.locator('[data-testid="item"]').first().click();
```
### Fix #4: Increase Timeout
```typescript
// Before
await expect(page.locator('[data-testid="slow-element"]')).toBeVisible();
// After
await expect(page.locator('[data-testid="slow-element"]')).toBeVisible({
timeout: 15000
});
```
### Fix #5: Check Element State
```typescript
// Before
await page.locator('[data-testid="button"]').click();
// After
await expect(page.locator('[data-testid="button"]')).toBeEnabled();
await page.locator('[data-testid="button"]').click();
```
## Verification
- [ ] Run test 5+ times to verify consistency
- [ ] Test in all browsers
- [ ] Run full test suite to check for regressions
- [ ] Test in CI environment
- [ ] Document the fix for future reference
## Prevention
- [ ] Add explicit waits where needed
- [ ] Use only data-testid locators
- [ ] Ensure test isolation
- [ ] Add retry logic in config
- [ ] Review and refactor flaky tests regularly
- [ ] Keep tests simple and focused
- [ ] Maintain good test hygiene (cleanup, fixtures)

View File

@@ -0,0 +1,480 @@
# Useful Playwright Debugging Commands
## Running Tests
```bash
# Run all tests
npx playwright test
# Run specific test file
npx playwright test login.spec.ts
# Run tests matching pattern
npx playwright test --grep "login"
# Run tests in specific browser
npx playwright test --project=chromium
npx playwright test --project=firefox
npx playwright test --project=webkit
# Run tests in headed mode (see browser)
npx playwright test --headed
# Run specific test by line number
npx playwright test login.spec.ts:42
# Run tests in parallel
npx playwright test --workers=4
# Run tests sequentially
npx playwright test --workers=1
```
## Debugging
```bash
# Debug mode (opens inspector)
npx playwright test --debug
# Debug specific test
npx playwright test login.spec.ts --debug
# UI Mode (interactive)
npx playwright test --ui
# Step through tests
npx playwright test --debug --headed
# Playwright Inspector
PWDEBUG=1 npx playwright test
```
## Code Generation
```bash
# Generate test code by recording actions
npx playwright codegen
# Generate code for specific URL
npx playwright codegen https://example.com
# Generate with specific device
npx playwright codegen --device="iPhone 12"
# Generate with authentication
npx playwright codegen --save-storage=auth.json
```
## Trace Viewing
```bash
# Show trace file
npx playwright show-trace trace.zip
# Open trace from test results
npx playwright show-trace test-results/login-test/trace.zip
# View trace with network data
npx playwright show-trace trace.zip --network
```
## Report Viewing
```bash
# Open HTML report
npx playwright show-report
# Open specific report
npx playwright show-report ./playwright-report
```
## Installation & Setup
```bash
# Install Playwright
npm init playwright@latest
# Install Playwright Test
npm install -D @playwright/test
# Install browsers
npx playwright install
# Install specific browser
npx playwright install chromium
npx playwright install firefox
npx playwright install webkit
# Install with dependencies (Linux)
npx playwright install --with-deps
# Update Playwright
npm install -D @playwright/test@latest
npx playwright install
```
## Configuration
```bash
# Run with custom config
npx playwright test --config=custom.config.ts
# List all projects
npx playwright test --list
# Show configuration
npx playwright show-config
```
## Useful Test Options
```bash
# Run tests with retries
npx playwright test --retries=3
# Set timeout
npx playwright test --timeout=60000
# Run tests with maximum failures
npx playwright test --max-failures=5
# Run only failed tests
npx playwright test --last-failed
# Update snapshots
npx playwright test --update-snapshots
# Ignore snapshots
npx playwright test --ignore-snapshots
```
## Environment Variables
```bash
# Enable debug logs
DEBUG=pw:api npx playwright test
# Enable verbose logging
DEBUG=* npx playwright test
# Set browser
BROWSER=firefox npx playwright test
# Set headed mode
HEADED=1 npx playwright test
# Disable parallel execution
PWTEST_PARALLEL=0 npx playwright test
```
## In-Test Debugging Commands
### Console Logging
```typescript
// Log to console
console.log('Debug message:', value);
// Log page URL
console.log('Current URL:', page.url());
// Log element count
const count = await page.locator('[data-testid="item"]').count();
console.log('Element count:', count);
// Log element text
const text = await page.locator('[data-testid="element"]').textContent();
console.log('Element text:', text);
// Log all text contents
const allText = await page.locator('[data-testid="item"]').allTextContents();
console.log('All texts:', allText);
```
### Page Pause
```typescript
// Pause test execution (opens inspector)
await page.pause();
// Pause on specific condition
if (someCondition) {
await page.pause();
}
```
### Screenshots
```typescript
// Take screenshot
await page.screenshot({ path: 'screenshot.png' });
// Full page screenshot
await page.screenshot({ path: 'screenshot.png', fullPage: true });
// Element screenshot
await page.locator('[data-testid="element"]').screenshot({ path: 'element.png' });
// Screenshot with timestamp
const timestamp = Date.now();
await page.screenshot({ path: `debug-${timestamp}.png` });
```
### Video Recording
```typescript
// In playwright.config.ts
use: {
video: 'on', // or 'retain-on-failure'
}
// In test
const path = await page.video()?.path();
console.log('Video saved at:', path);
```
### Trace Recording
```typescript
// Start tracing
await context.tracing.start({ screenshots: true, snapshots: true });
// Stop tracing and save
await context.tracing.stop({ path: 'trace.zip' });
// Or in playwright.config.ts
use: {
trace: 'on-first-retry', // or 'on' for all tests
}
```
### Evaluate JavaScript
```typescript
// Execute JavaScript in page context
const result = await page.evaluate(() => {
return document.title;
});
console.log('Page title:', result);
// With parameters
const result = await page.evaluate((text) => {
return document.body.textContent?.includes(text);
}, 'search term');
// Get element properties
const value = await page.locator('[data-testid="input"]').evaluate(
(el: HTMLInputElement) => el.value
);
```
### Wait Commands
```typescript
// Wait for timeout (avoid in production)
await page.waitForTimeout(1000);
// Wait for selector
await page.waitForSelector('[data-testid="element"]');
// Wait for URL
await page.waitForURL('/dashboard');
await page.waitForURL(/\/user\/\d+/);
// Wait for load state
await page.waitForLoadState('load');
await page.waitForLoadState('domcontentloaded');
await page.waitForLoadState('networkidle');
// Wait for response
await page.waitForResponse('**/api/data');
await page.waitForResponse(response =>
response.url().includes('/api/') && response.status() === 200
);
// Wait for request
await page.waitForRequest('**/api/users');
// Wait for function
await page.waitForFunction(() => document.querySelectorAll('.item').length > 5);
// Wait for event
await page.waitForEvent('response');
await page.waitForEvent('request');
```
### Network Debugging
```typescript
// Listen to all requests
page.on('request', request =>
console.log('>>', request.method(), request.url())
);
// Listen to all responses
page.on('response', response =>
console.log('<<', response.status(), response.url())
);
// Listen to console messages
page.on('console', msg => console.log('PAGE LOG:', msg.text()));
// Listen to page errors
page.on('pageerror', error => console.log('PAGE ERROR:', error));
// Intercept and log requests
await page.route('**/api/**', route => {
console.log('API Request:', route.request().url());
route.continue();
});
```
### State Inspection
```typescript
// Get all cookies
const cookies = await context.cookies();
console.log('Cookies:', cookies);
// Get localStorage
const localStorage = await page.evaluate(() => {
return JSON.stringify(window.localStorage);
});
console.log('LocalStorage:', localStorage);
// Get sessionStorage
const sessionStorage = await page.evaluate(() => {
return JSON.stringify(window.sessionStorage);
});
console.log('SessionStorage:', sessionStorage);
// Get viewport size
const viewport = page.viewportSize();
console.log('Viewport:', viewport);
```
### Element State Checks
```typescript
// Check visibility
const isVisible = await page.locator('[data-testid="element"]').isVisible();
console.log('Is visible:', isVisible);
// Check if enabled
const isEnabled = await page.locator('[data-testid="button"]').isEnabled();
console.log('Is enabled:', isEnabled);
// Check if checked
const isChecked = await page.locator('[data-testid="checkbox"]').isChecked();
console.log('Is checked:', isChecked);
// Get bounding box
const box = await page.locator('[data-testid="element"]').boundingBox();
console.log('Bounding box:', box);
// Get attribute
const value = await page.locator('[data-testid="input"]').getAttribute('value');
console.log('Value:', value);
// Get inner text
const text = await page.locator('[data-testid="element"]').innerText();
console.log('Inner text:', text);
// Get input value
const inputValue = await page.locator('[data-testid="input"]').inputValue();
console.log('Input value:', inputValue);
```
## Performance Testing
```bash
# Run with tracing
npx playwright test --trace=on
# Run with video
npx playwright test --video=on
# Slow down execution
npx playwright test --slow-mo=1000
```
## CI/CD Commands
```bash
# Run in CI mode
CI=1 npx playwright test
# Run with JUnit reporter
npx playwright test --reporter=junit
# Run with multiple reporters
npx playwright test --reporter=html,json,junit
# Run with sharding
npx playwright test --shard=1/3
npx playwright test --shard=2/3
npx playwright test --shard=3/3
```
## Test Filtering
```bash
# Run tests with specific tag
npx playwright test --grep @smoke
# Skip tests with tag
npx playwright test --grep-invert @skip
# Run tests in specific describe block
npx playwright test --grep "Login tests"
# Run only tests marked with test.only
# (Automatically done if test.only exists)
```
## Maintenance Commands
```bash
# Clean test output
rm -rf test-results/
rm -rf playwright-report/
# List installed browsers
npx playwright --version
# Check for browser updates
npx playwright install --dry-run
# Uninstall browsers
npx playwright uninstall
# Clear browser cache
npx playwright install --force
```
## Tips
1. **Use `--headed` for visual debugging**
```bash
npx playwright test --headed --project=chromium
```
2. **Use `--debug` to step through test**
```bash
npx playwright test login.spec.ts --debug
```
3. **Use `--ui` for interactive mode**
```bash
npx playwright test --ui
```
4. **Use `show-trace` to analyze failures**
```bash
npx playwright show-trace trace.zip
```
5. **Use `codegen` to learn selectors**
```bash
npx playwright codegen http://localhost:3000
```