Skip to content

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 TypeTypical CauseSolution
TimeoutErrorElement not found in timeAdd explicit waits, check selector
Error: Element not foundSelector doesn’t matchUpdate selector, verify element exists
Error: Element not visibleElement hidden or coveredScroll into view, wait for visibility
Error: Element not enabledElement disabledWait for enabled state
Error: Target closedPage closed unexpectedlyHandle 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:

Terminal window
npx stepwright run ./script.ts --headed

Slow 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

  1. Verify selector syntax

    Use Playwright’s codegen to find selectors:

    Terminal window
    npx playwright codegen https://example.com
  2. Check element exists

    const count = await ctx.page.locator('#my-element').count();
    ctx.log('Element count:', count);
  3. Wait for element

    await ctx.page.waitForSelector('#my-element', {
    state: 'visible',
    timeout: 10000,
    });
  4. Try alternative selectors

    // By text
    await ctx.page.locator('text=Submit').click();
    // By role
    await ctx.page.locator('role=button[name="Submit"]').click();
    // By test ID
    await 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');
})
.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 case
const failureCase = generateFailureCase(result, {
scriptInfo: { name: 'Debug Script', path: './script.ts' },
});
// Get AI analysis
const 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