Event and Error Handling

Back

Loading concept...

🎭 Playwright Event & Error Handling

Your Safety Net for Automated Testing


The Story: Meet Your Watchful Guardian

Imagine you have a smart security guard watching over your house. This guard doesn’t just stand there—they:

  • Listen for sounds (events happening)
  • Read notes left at the door (console messages)
  • Spot broken windows (page errors)
  • Know what to do when things go wrong (error handling)
  • Stay calm when alarms randomly go off (flaky tests)

That’s exactly what Playwright’s event and error handling does for your automated tests!


🎧 Event Handling: The Listener

What is Event Handling?

Think of events like doorbells ringing. Every time something happens on a webpage—a file downloads, a popup appears, a new page opens—it’s like a doorbell ringing. Playwright can listen for these doorbells and react!

How Does It Work?

// Listen for file downloads
page.on('download', async (download) => {
  console.log('File downloaded!');
  await download.saveAs('my-file.pdf');
});

This is like telling your guard: “When someone delivers a package, save it in the storage room.”

Common Events You Can Listen For

graph LR A["🎭 Page Events"] --> B["📥 download"] A --> C["🪟 popup"] A --> D["📄 request"] A --> E["📨 response"] A --> F["💬 dialog"] A --> G["📝 console"] A --> H["❌ pageerror"]

Real Example: Handling Popups

// Before clicking, set up the listener
const popupPromise = page.waitForEvent('popup');
await page.click('button#open-new-window');
const popup = await popupPromise;
await popup.waitForLoadState();
console.log('Popup URL:', popup.url());

Think of it like this: You tell your guard, “A delivery truck is coming. Wait by the gate, and when it arrives, bring me the package.”


💬 Console Message Handling: Reading the Notes

What Are Console Messages?

Websites talk to developers by writing notes in the browser’s console. These notes can say:

  • “Everything is fine!” ✅
  • “Hmm, this might be a problem…” ⚠️
  • “SOMETHING WENT WRONG!” ❌

Why Should We Listen?

Imagine your website is a restaurant kitchen. The chefs (JavaScript code) sometimes shout things:

  • “Order ready!” (log)
  • “We’re running low on salt!” (warning)
  • “THE STOVE IS ON FIRE!” (error)

You want to hear ALL of these!

Capturing Console Messages

// Listen to ALL messages
page.on('console', msg => {
  console.log(`[${msg.type()}]: ${msg.text()}`);
});

// Or filter by type
page.on('console', msg => {
  if (msg.type() === 'error') {
    console.log('ERROR found:', msg.text());
  }
});

Checking for Problems in Tests

const errors = [];

page.on('console', msg => {
  if (msg.type() === 'error') {
    errors.push(msg.text());
  }
});

await page.goto('https://example.com');
await page.click('#submit-form');

// After test, check for errors
expect(errors).toHaveLength(0);

The Analogy: Your guard keeps a notepad. Every time the kitchen shouts something bad, they write it down. At the end of the day, you check the notepad!


💥 Page Error Handling: Spotting the Broken Windows

What Are Page Errors?

Sometimes JavaScript on a webpage completely breaks. Not a small warning—a full crash! Like:

  • Trying to use something that doesn’t exist
  • Dividing by zero
  • Running code that has typos

The pageerror Event

page.on('pageerror', error => {
  console.log('Page crashed with:', error.message);
});

Real Example: Catching Page Errors

const pageErrors = [];

page.on('pageerror', error => {
  pageErrors.push({
    message: error.message,
    stack: error.stack
  });
});

await page.goto('https://buggy-website.com');

// Check if page had any crashes
if (pageErrors.length > 0) {
  console.log('Found', pageErrors.length, 'errors!');
  pageErrors.forEach(err => {
    console.log(' -', err.message);
  });
}

Difference: Console Error vs Page Error

graph LR A["Console Error"] --> B["Someone reported<br>a problem"] C["Page Error"] --> D["Something actually<br>broke/crashed"]
Console Error Page Error
console.error("Oops") ReferenceError: x is not defined
Intentional message Unintentional crash
Code continues running Code might stop

🛡️ Error Handling in Tests: Your Safety Net

Why Do Tests Need Error Handling?

Tests can fail for many reasons:

  • Element not found
  • Timeout waiting for something
  • Network problems
  • Unexpected popups

Without proper handling, one failure can ruin everything!

Try-Catch: Your Basic Safety Net

test('login test', async ({ page }) => {
  try {
    await page.goto('https://example.com/login');
    await page.fill('#username', 'user@test.com');
    await page.fill('#password', 'secret123');
    await page.click('#login-btn');
    await expect(page).toHaveURL('/dashboard');
  } catch (error) {
    console.log('Login failed:', error.message);
    await page.screenshot({ path: 'login-failure.png' });
    throw error; // Re-throw to fail the test
  }
});

Soft Assertions: Don’t Stop at First Failure

Sometimes you want to check MANY things and see ALL failures, not just the first one.

test('check all fields', async ({ page }) => {
  await page.goto('https://example.com/profile');

  // Soft assertions continue even if one fails
  await expect.soft(page.locator('#name'))
    .toHaveValue('John');
  await expect.soft(page.locator('#email'))
    .toHaveValue('john@example.com');
  await expect.soft(page.locator('#phone'))
    .toHaveValue('123-456-7890');

  // See ALL failures at the end
});

Think of it like this: A teacher checking a test with soft assertions marks ALL wrong answers, not just the first one!

Handling Timeouts Gracefully

test('wait for slow element', async ({ page }) => {
  await page.goto('https://slow-website.com');

  try {
    // Wait up to 10 seconds
    await page.waitForSelector('#slow-content', {
      timeout: 10000
    });
  } catch (error) {
    if (error.message.includes('Timeout')) {
      console.log('Element took too long!');
      // Maybe try an alternative action
      await page.reload();
    } else {
      throw error; // Different error, re-throw
    }
  }
});

🎲 Flaky Test Handling: Taming the Unpredictable

What Are Flaky Tests?

A flaky test is like a light switch that sometimes works and sometimes doesn’t. Same test, same code, but:

  • ✅ Monday: PASS
  • ❌ Tuesday: FAIL
  • ✅ Wednesday: PASS
  • ❌ Thursday: FAIL

Why Do Tests Become Flaky?

graph TD A["🎲 Flaky Test Causes"] --> B["⏱️ Timing Issues"] A --> C["🌐 Network Delays"] A --> D["🎯 Animation/Transitions"] A --> E["📊 Test Data Changes"] A --> F["🖥️ Resource Limits"]

Solution 1: Automatic Retries

// In playwright.config.js
export default {
  retries: 2, // Retry failed tests twice

  // Different retries for CI vs local
  retries: process.env.CI ? 2 : 0,
};

Like this: If the light switch doesn’t work, try flipping it again!

Solution 2: Better Waiting Strategies

Bad: Hard-coded waits

// DON'T DO THIS!
await page.waitForTimeout(5000);

Good: Wait for specific conditions

// Wait until element is visible
await page.waitForSelector('#result', {
  state: 'visible'
});

// Wait until network is idle
await page.waitForLoadState('networkidle');

// Wait until specific text appears
await expect(page.locator('#status'))
  .toHaveText('Complete');

Solution 3: test.describe.configure for Retries

test.describe('checkout flow', () => {
  // This specific group retries 3 times
  test.describe.configure({ retries: 3 });

  test('complete purchase', async ({ page }) => {
    // This test will retry up to 3 times
  });
});

Solution 4: Mark Known Flaky Tests

// This test is known to be flaky
test('sometimes fails', async ({ page }) => {
  test.fixme(); // Skip but track it
});

// Or skip entirely
test.skip('broken test', async ({ page }) => {
  // Won't run
});

Solution 5: Trace on First Retry

// In playwright.config.js
export default {
  use: {
    trace: 'on-first-retry',
  },
};

This records a detailed trace ONLY when a test fails and is being retried. Perfect for debugging flaky tests!


🎯 Putting It All Together

Here’s a complete example using ALL the concepts:

import { test, expect } from '@playwright/test';

test('robust shopping test', async ({ page }) => {
  // Collect all issues
  const consoleErrors = [];
  const pageErrors = [];

  // Set up listeners FIRST
  page.on('console', msg => {
    if (msg.type() === 'error') {
      consoleErrors.push(msg.text());
    }
  });

  page.on('pageerror', error => {
    pageErrors.push(error.message);
  });

  // Handle potential popup
  page.on('dialog', async dialog => {
    await dialog.accept();
  });

  try {
    // Navigate and interact
    await page.goto('https://shop.example.com');
    await page.click('#add-to-cart');

    // Wait properly (not with timeout!)
    await expect(page.locator('#cart-count'))
      .toHaveText('1');

    await page.click('#checkout');

    // Soft assertions for form validation
    await expect.soft(page.locator('#total'))
      .toBeVisible();
    await expect.soft(page.locator('#pay-btn'))
      .toBeEnabled();

  } catch (error) {
    // Take screenshot on failure
    await page.screenshot({
      path: `failure-${Date.now()}.png`
    });
    throw error;
  }

  // Final checks
  expect(consoleErrors).toHaveLength(0);
  expect(pageErrors).toHaveLength(0);
});

🎉 Summary: Your Guardian’s Toolkit

Tool What It Does When to Use
page.on('event') Listens for events Downloads, popups, dialogs
page.on('console') Catches console messages Debug info, warnings
page.on('pageerror') Catches JS crashes Find broken code
try-catch Handles test failures Take screenshots, cleanup
expect.soft() Collects all failures Check multiple things
retries Runs test again Flaky tests
waitForSelector Smart waiting Replace hardcoded waits

🚀 You’re Ready!

You now have a complete toolkit for handling anything that goes wrong in your Playwright tests. Remember:

  1. Set up listeners early - Before navigating to pages
  2. Wait smartly - Never use hardcoded timeouts
  3. Catch errors gracefully - Take screenshots, log details
  4. Use retries wisely - For genuinely flaky tests, not broken code
  5. Collect evidence - Console logs, errors, traces

Your tests are now protected by the best security guard in town! 🎭✨

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.