Debugging Failures
Debugging Failures
When scripts fail, effective debugging is essential. This guide covers techniques for identifying and fixing issues.
Understanding Failure Output
Result Structure
When a script fails, examine the result:
const result = await script.run();
if (!result.success) { console.log('Failed step:', result.failedStep?.name); console.log('Step index:', result.failedStep?.index); console.log('Checkpoint:', result.failedStep?.checkpoint); console.log('Error message:', result.failedStep?.error.message); console.log('Error stack:', result.failedStep?.error.stack);
// Artifacts console.log('Screenshot:', result.failedStep?.artifacts.screenshot); console.log('DOM:', result.failedStep?.artifacts.dom); console.log('Console:', result.failedStep?.artifacts.console); console.log('URL:', result.failedStep?.artifacts.url);}Common Error Types
| Error Type | Typical Cause | Solution |
|---|---|---|
TimeoutError | Element not found in time | Add explicit waits, check selector |
Error: Element not found | Selector doesn’t match | Update selector, verify element exists |
Error: Element not visible | Element hidden or covered | Scroll into view, wait for visibility |
Error: Element not enabled | Element disabled | Wait for enabled state |
Error: Target closed | Page closed unexpectedly | Handle navigation, check for redirects |
Using Artifacts
Screenshots
Enable screenshot capture:
.config({ screenshotOnFailure: true, artifactDir: './debug-artifacts',})Review the screenshot to understand page state at failure.
DOM Snapshots
Enable DOM capture:
.config({ domOnFailure: true,})Examine the HTML to verify element existence and structure.
Console Logs
Browser console messages are captured automatically. Check for:
- JavaScript errors
- Network failures
- Application logs
Debug Mode
Headed Mode
Run with visible browser:
.config({ headless: false,})Or via CLI:
npx stepwright run ./script.ts --headedSlow Motion
Slow down actions to observe:
.config({ launchOptions: { slowMo: 500, // 500ms between actions },})DevTools
Open DevTools automatically:
.config({ launchOptions: { devtools: true, },})Pause on Failure
Add breakpoints in your script:
.step('Debug this step', async (ctx) => { // Add pause for debugging await ctx.page.pause(); // Opens Playwright inspector
await ctx.page.click('#button');})Adding Debug Steps
Strategic Screenshots
.step('Before login', async (ctx) => { await ctx.screenshot('01-before-login'); await ctx.page.goto('/login'); await ctx.screenshot('02-login-page-loaded');})
.step('Fill credentials', async (ctx) => { await ctx.page.fill('#email', ctx.data.email); await ctx.screenshot('03-email-filled');
await ctx.page.fill('#password', ctx.data.password); await ctx.screenshot('04-password-filled');})
.step('Submit', async (ctx) => { await ctx.screenshot('05-before-submit'); await ctx.page.click('#submit');
await ctx.page.waitForURL('**/dashboard'); await ctx.screenshot('06-after-submit');})Verbose Logging
.config({ verbose: true,})
.step('Debug step', async (ctx) => { ctx.log('Current URL:', ctx.page.url());
const elementCount = await ctx.page.locator('.item').count(); ctx.log('Found items:', elementCount);
const isVisible = await ctx.page.locator('#target').isVisible(); ctx.log('Target visible:', isVisible);
const attributes = await ctx.page.locator('#target').getAttribute('class'); ctx.log('Target classes:', attributes);})DOM Inspection
.step('Inspect DOM', async (ctx) => { // Get full page HTML const fullDom = await ctx.getDOM(); console.log('Full page length:', fullDom.length);
// Get specific element const containerDom = await ctx.getDOM('#main-container'); console.log('Container:', containerDom.slice(0, 500));
// Check if element exists const exists = await ctx.page.locator('#target').count() > 0; ctx.log('Element exists:', exists);
// Get element bounding box const box = await ctx.page.locator('#target').boundingBox(); ctx.log('Element position:', box);})Debugging Common Issues
Selector Not Found
-
Verify selector syntax
Use Playwright’s codegen to find selectors:
Terminal window npx playwright codegen https://example.com -
Check element exists
const count = await ctx.page.locator('#my-element').count();ctx.log('Element count:', count); -
Wait for element
await ctx.page.waitForSelector('#my-element', {state: 'visible',timeout: 10000,}); -
Try alternative selectors
// By textawait ctx.page.locator('text=Submit').click();// By roleawait ctx.page.locator('role=button[name="Submit"]').click();// By test IDawait ctx.page.locator('[data-testid="submit-btn"]').click();
Timing Issues
.step('Handle timing', async (ctx) => { // Wait for network idle await ctx.page.waitForLoadState('networkidle');
// Wait for specific condition await ctx.page.waitForFunction(() => { const spinner = document.querySelector('.loading'); return !spinner || spinner.style.display === 'none'; });
// Then interact await ctx.page.click('#button');})Page Navigation
.step('Handle navigation', async (ctx) => { // Wait for navigation after click await Promise.all([ ctx.page.waitForNavigation(), ctx.page.click('#nav-link'), ]);
// Verify URL const url = ctx.page.url(); ctx.log('Navigated to:', url);
if (!url.includes('/expected-page')) { throw new Error(`Unexpected URL: ${url}`); }})Multiple Matching Elements
.step('Handle multiple elements', async (ctx) => { // Count matches const count = await ctx.page.locator('.item').count(); ctx.log('Found items:', count);
// Click first await ctx.page.locator('.item').first().click();
// Click specific by index await ctx.page.locator('.item').nth(2).click();
// Click by filter await ctx.page.locator('.item', { hasText: 'Specific Text' }).click();})Using Reporters for Debug
Custom Debug Reporter
import type { Reporter, StepResult } from '@korvol/stepwright';
class DebugReporter implements Reporter { onStepStart(step: StepResult) { console.log(`\n>>> Starting: ${step.name}`); console.time(step.name); }
onStepEnd(step: StepResult) { console.timeEnd(step.name); if (step.status === 'failed') { console.log('FAILED:', step.error?.message); console.log('Stack:', step.error?.stack); } }
onEnd(result: StepwrightResult) { console.log('\n=== Summary ==='); for (const step of result.steps) { const icon = step.status === 'passed' ? '✓' : step.status === 'failed' ? '✗' : '○'; console.log(`${icon} ${step.name} (${step.duration}ms)`); } }}
// Usage.reporter(new DebugReporter())Video Recording
Record video for debugging:
.config({ video: { enabled: true, dir: './debug-videos', size: { width: 1280, height: 720 }, },})Watch the video to understand the sequence of events leading to failure.
Fixwright Analysis
When debugging is difficult, use Fixwright to analyze:
import { generateFailureCase } from '@korvol/stepwright';import { FixWright } from '@korvol/fixwright';
// Generate failure caseconst failureCase = generateFailureCase(result, { scriptInfo: { name: 'Debug Script', path: './script.ts' },});
// Get AI analysisconst fixwright = new FixWright({ ai: { apiKey: process.env.ANTHROPIC_API_KEY! },});
fixwright.on('attempt:analyzing', () => { console.log('Claude is analyzing the failure...');});
fixwright.on('attempt:success', (attempt) => { console.log('Analysis:', attempt.analysis); console.log('Suggested fix:', attempt.proposedFix);});Next Steps
- Best Practices - Prevent failures
- Retries and Recovery - Handle flaky tests
- Fixwright Overview - Automated fixing