Initial commit
This commit is contained in:
498
skills/test-debugger/SKILL.md
Normal file
498
skills/test-debugger/SKILL.md
Normal 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
|
||||
378
skills/test-debugger/resources/common-errors.md
Normal file
378
skills/test-debugger/resources/common-errors.md
Normal 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
|
||||
```
|
||||
250
skills/test-debugger/resources/debugging-checklist.md
Normal file
250
skills/test-debugger/resources/debugging-checklist.md
Normal 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)
|
||||
480
skills/test-debugger/resources/playwright-commands.md
Normal file
480
skills/test-debugger/resources/playwright-commands.md
Normal 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
|
||||
```
|
||||
Reference in New Issue
Block a user