CoursePart 1 — JavaScript for PlaywrightLesson 1.16
Lesson 1.16Part 1 — JavaScript for Playwright

try / catch

Catching errors before they crash your test — and deciding what to do.

Lesson 1.16of 24
Next

Step 1

The idea

01

When an asynchronous operation fails — network error, element not found, timeout — it throws an error. If nothing catches that error, your entire test crashes with an ugly stack trace.

try/catch lets you handle errors gracefully: "try to do this — if it fails, catch the error and do something useful instead."

Think of it like a safety net under a trapeze act. The performer tries the move. If they fall, the net catches them — they don't hit the floor.

Step 2

See it run

02
Output
Attempting to load page...
Page loaded successfully
---
Attempting to load page...
Failed to load: Network timeout after 5000ms
Retrying with a backup URL...
Backup loaded successfully
try-catch.js
// Simulated async functions
function loadPage(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (url.includes("bad")) {
        reject(new Error("Network timeout after 5000ms"));
      } else {
        resolve(`Page loaded: ${url}`);
      }
    }, 100);
  });
}

// Without try/catch — an error here crashes the whole script
// await loadPage("bad-url");  // ← would crash

// WITH try/catch — errors are handled gracefully
async function runTest(url) {
  console.log("Attempting to load page...");

  try {
    const result = await loadPage(url);
    console.log("Page loaded successfully");
  } catch (error) {
    console.log("Failed to load:", error.message);
    console.log("Retrying with a backup URL...");
    const backup = await loadPage("https://backup.example.com");
    console.log("Backup loaded successfully");
  }
}

runTest("https://good-url.com");

setTimeout(() => {
  console.log("---");
  runTest("https://bad-url.com");
}, 200);
Terminal
$ node try-catch.js

Step 3

Now you try

03

Add a finally block to the example above:

js
try {
  // your code
} catch (error) {
  // handle error
} finally {
  console.log("Test run complete — regardless of success or failure");
}

finally always runs — whether try succeeded or catch ran. It's perfect for cleanup: closing browsers, resetting state, writing logs.

ℹ️Note
In Playwright, you rarely need manual try/catch because Playwright already throws helpful errors when things fail. But understanding it lets you read error-handling code and write robust test utilities.

Step 4

Why a tester cares

04

try/catch is used whenever you're reading user input or external data that might not be in the right format. Here's a JSON parser with proper error handling:

parse-input.js
async function loadConfig(jsonString) {
  try {
    const config = JSON.parse(jsonString);

    // Validate required fields exist
    if (!config.apiUrl) throw new Error("Missing required field: apiUrl");
    if (!config.timeout) throw new Error("Missing required field: timeout");

    console.log("Config loaded successfully");
    console.log(`  API: ${config.apiUrl}`);
    console.log(`  Timeout: ${config.timeout}ms`);
    return config;

  } catch (error) {
    console.error(`Config error: ${error.message}`);
    return null;  // return null instead of crashing
  } finally {
    console.log("--- load attempt complete ---");
  }
}

// Test with valid JSON
await loadConfig('{"apiUrl":"https://api.example.com","timeout":5000}');

// Test with invalid JSON
await loadConfig('this is not json');

// Test with missing field
await loadConfig('{"apiUrl":"https://api.example.com"}');
Terminal
$ node parse-input.js

The finally block always runs — perfect for cleanup or logging. The function never crashes: it either returns the config or returns null. That's graceful error handling.

Recap

3 key points
  • 1try { } contains code that might fail. catch (error) { } handles the failure.
  • 2finally { } always runs — useful for cleanup regardless of outcome.
  • 3Re-throw the error (throw error) if you want to handle it AND still mark the test as failed.