Negative Testing in Playwright with TypeScript
Using Try/Catch
One of the key ways of building a champion automated test suite is negative testing - making sure that your application gracefully handles errors, exceptions, and bad user behavior. This is a good way for automation to add value with testing as negative testing isn't something that manual QA does on a regular bases.
When working with Playwright and TypeScript, there isn't a direct equivalent to pytest.raises()
in Python for expecting exceptions. But that doesn't mean you're out of luck.
With TypeScript, the tried-and-true try/catch
pattern becomes a powerful way to validate that your code throws errors when it's supposed to.
Scenario: Navigating to an Invalid URL
Say you want to verify that navigating to a malformed URL triggers the appropriate error. Here's how you can do it using Playwright with TypeScript:
import { test, expect } from '@playwright/test';
test('should throw an error when navigating to an invalid URL', async ({ playwright }) => {
let errorCaught = false;
let errorMessage = '';
try {
// Attempt to navigate to an invalid URL, which should throw an error
await playwright.chromium.launch().then(async browser => {
const page = await browser.newPage();
await page.goto('httpy://www.whitehouse.gov'); // Intentionally bad protocol
await browser.close();
});
} catch (error) {
errorCaught = true;
errorMessage = error instanceof Error ? error.message : String(error);
}
// Assert that an error was caught
expect(errorCaught).toBe(true);
// Optionally, check the error message contains expected content
expect(errorMessage).toContain('net::ERR_ABORTED');
});
Why Use try/catch
Instead of Custom Matchers?
Playwright doesn't provide a built-in matcher for error expectations like pytest.raises
. However, try/catch
blocks are fully supported and give you total control over the error message, how it's caught, and what behavior you want to assert afterward.
You can go beyond just checking that an error occurred-you can validate specific error types, compare error messages, and decide whether to re-throw or suppress the error.
Tips for Cleaner Code
If you find yourself doing this often, you can extract the logic into a helper function:
async function expectError(fn: () => Promise<void>, expectedMessagePart: string) {
let errorCaught = false;
let errorMessage = '';
try {
await fn();
} catch (error) {
errorCaught = true;
errorMessage = error instanceof Error ? error.message : String(error);
}
expect(errorCaught).toBe(true);
expect(errorMessage).toContain(expectedMessagePart);
}
And use it like this:
test('should throw an error for bad URL', async ({ playwright }) => {
await expectError(async () => {
const browser = await playwright.chromium.launch();
const page = await browser.newPage();
await page.goto('httpy://www.whitehouse.gov');
await browser.close();
}, 'net::ERR_ABORTED');
});
Final Thoughts
try/catch
isn't just a fallback-it's a flexible and explicit way to perform negative testing in Playwright. It gives you visibility into what exactly went wrong and control over how to validate it. Whether you're validating error messages, failed network calls, or unhandled exceptions, try/catch
should be part of your Playwright testing toolkit.