Build an Automated Test Suite

Automate browser testing across Chromium, Firefox, and WebKit with Playwright.

Introduction

30 Second Summary

Every time you manually click through a web app to check that buttons work, forms submit, and pages load correctly, you are doing repetitive work that a computer could handle in seconds. Now imagine running one command and watching a robot browser perform all those checks for you across Chrome, Firefox, and Safari simultaneously.

In this project, you will build a complete end-to-end test suite using Playwright that automatically tests a web application across three browser engines. You will write tests that add items, complete them, and verify the UI updates correctly.

What You'll Build

Run a single command in your terminal and watch six automated tests fly through Chromium, Firefox, and WebKit in seconds, then open a visual HTML report that shows you exactly what passed and what failed.

By the end of this project, you'll have:

  • A single terminal command that runs 6+ tests across 3 browsers simultaneously, replacing 15+ minutes of manual clicking with seconds of automated checks.
  • A visual HTML report showing pass/fail status, timing, and browser coverage for every test in your suite.
  • Hands-on experience with Trace Viewer to step through a failed test action-by-action with DOM snapshots, network activity, and console logs.
  • A headed mode workflow where you watch Playwright interact with the app in real time, connecting the dots between code and browser actions.
  • Secret Mission: Use Playwright's Codegen tool to record your browser actions and auto-generate test code from your manual workflows.

Are there any prerequisites?

You need basic JavaScript knowledge (variables, functions, and async/await). No prior testing experience is required.

Step 1 walks you through installing everything from scratch, including Node.js, VS Code, and Playwright.

Before We Start

Before we write any code, let's lock in what you're about to build and why it matters.

Set Up Your Testing Environment

Every automated test in this project runs on Node.js, the JavaScript runtime that powers Playwright. Without these tools installed, you cannot write or execute any test code.

In this step, you will install Node.js, set up VS Code as your code editor, and scaffold a Playwright project that comes with example tests ready to run across three browsers.

In this step, get ready to:
  • Install Node.js on your machine.
  • Set up VS Code and initialize a Playwright project.
  • Verify your setup by running the scaffold tests across three browsers.
Install Node.js

Node.js is the runtime that lets you execute JavaScript outside a browser. Playwright and its test runner both depend on it.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or PowerShell (Windows) and press Enter to open it.
  • Check whether Node.js is already installed by running this command:
node --version

✔️ I see version 24 or higher

Node.js is ready. Skip ahead to the next substep.

ⓧ I see an older version

Your Node.js is outdated. Playwright 1.62 requires Node.js 22.x, 24.x, or 26.x.

  • Download the v24.18.1 LTS installer from nodejs.org/en/download.
  • Run the installer with default settings. It replaces your current version automatically.
  • Close your terminal completely and open a new one.
  • Run node --version again to confirm it prints v24 or higher.

Why do I need to close my terminal?

Your terminal caches the path to the old Node.js binary when it first opens. Closing and reopening forces it to find the newly installed version.

ⓧ Command not found

Node.js is not installed yet. Let's fix that.

  • Download the v24.18.1 LTS installer from nodejs.org/en/download.
  • Run the installer with default settings. This also installs npm, the package manager you will use next.
  • Close your terminal completely and open a new one.
  • Run node --version to confirm it prints v24 or higher.

Still seeing command not found?

Make sure you opened a brand new terminal window after running the installer. If it still fails, restart your computer and try again.

help me troubleshoot why node --version is not working after I installed Node.js

Install VS Code and initialize Playwright

VS Code is the code editor where you will write and manage your tests. Its integrated terminal lets you run commands without switching windows.

  • Download and install VS Code from code.visualstudio.com.
  • Once installed, press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Visual Studio Code and press Enter to open it.

Now create a project folder and open it in VS Code.

  • In your terminal (the one you used for the Node.js check), move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir playwright-tests
cd playwright-tests
  • In VS Code, click File in the top menu bar.
  • Click Open Folder.
  • Navigate to your Desktop, select the playwright-tests folder, and click Open.
  • Open the integrated terminal by pressing Ctrl+` (backtick key, same on macOS and Windows).

What is the integrated terminal?

VS Code has a built-in terminal panel at the bottom of the window. It opens directly inside your project folder, so every command you run is already in the right place.

Now initialize Playwright inside your project. This command is interactive and will ask you a few questions.

  • Initialize Playwright by running this command in the VS Code integrated terminal:
npm init playwright@latest

What prompts will I see?

The command asks four questions. Answer each one as it appears:

  • Do you want to use TypeScript or JavaScript? Choose TypeScript (the default).
  • Where to put your end-to-end tests? Press Enter to accept the default tests folder.
  • Add a GitHub Actions workflow? Type false and press Enter to skip it.
  • Install Playwright browsers? Press Enter to accept true (the default). This downloads Chromium, Firefox, and WebKit.

Browser download takes a moment

Playwright downloads three browser engines (about 500MB total). This can take a few minutes depending on your internet connection. Let it finish completely before moving on.

Once the command finishes, you should see new files appear in the VS Code Explorer sidebar on the left.

  • Confirm the scaffold files exist by running:
ls

You should see playwright.config.ts, package.json, package-lock.json, and a tests folder in the output.

Don't see these files?

Make sure the npm init playwright@latest command finished without errors. If it failed partway through, delete the node_modules folder and run the command again.

Check that your terminal is inside the playwright-tests folder by running pwd (macOS) or cd (Windows). It should show a path ending in playwright-tests.

help me troubleshoot why npm init playwright@latest failed or did not create the expected files

Verify the setup works

Your Playwright project is scaffolded. The scaffold includes an example test file at tests/example.spec.ts that navigates to the Playwright website and checks a few things.

Before you run the tests, what do you think will happen? The example file has 2 tests, and Playwright is configured to run against 3 browsers (Chromium, Firefox, WebKit). How many total test runs do you expect?

  • Run the test suite by executing this command:
npx playwright test

You should see 6 tests pass (2 tests multiplied by 3 browsers). The terminal prints something like 6 passed with a time in seconds.

Why 6 tests from 1 file?

Playwright runs every test against each configured browser project. Your playwright.config.ts defines three projects: chromium, firefox, and webkit.

Two tests multiplied by three browsers gives six total runs. This is how Playwright delivers cross-browser coverage with zero extra code.

  • Open the HTML test report by running:
npx playwright show-report

Your browser opens a dashboard showing each test result broken down by browser. You can see pass/fail status, timing, and which browser ran each test.

The report auto-opens on failure

When tests fail, the HTML report opens automatically. When all tests pass, you need to run npx playwright show-report manually to see it.

  • As a final check, confirm your Playwright version by running:
npx playwright --version

You should see a version number starting with 1.62 printed in the terminal.

Tests failing or timing out?

If tests fail with a network error, check your internet connection. The example tests navigate to https://playwright.dev/ which requires internet access.

If you see a timeout error, the browser binaries may not have downloaded fully. Run npx playwright install to re-download them, then try npx playwright test again.

help me fix my Playwright tests that are failing or timing out after a fresh install

Your testing environment is fully set up. You have Node.js, VS Code, and Playwright working together with tests running across three browsers. Next up, you will run these tests in headed mode and watch the browser automation happen in real time.

Run Your First Real Test in Headed Mode

Your testing environment is installed and ready. In Step 1, you set up Node.js, VS Code, and Playwright, and even ran the scaffold test headlessly in the terminal.

But where was the browser? The test ran so fast you never actually saw it. In this step, you'll run the same test in headed mode and watch Playwright open a real browser window and interact with a webpage automatically. This is the moment that connects "clicking around manually" with "code that clicks for you."

In this step, get ready to:
  • Run the example test in headed mode and watch the browser interact with a page.
  • Read the example test code and identify its three parts: navigate, act, assert.
  • Run tests across all three browsers and explore the HTML report.
Run the example test in headed mode

By default, Playwright runs tests in headless mode. No browser window appears. For a tester transitioning from manual testing, that makes it hard to understand what the automation is actually doing.

Headed mode opens a visible browser so you can watch each action happen in real time.

Before you run the next command, what do you expect to see? Will a browser window appear? Will it navigate somewhere?

  • Run the example test in headed mode on Chromium by running this command:
npx playwright test --project=chromium --headed

A Chromium browser window opens, navigates to https://playwright.dev/, clicks the "Get started" link, and closes automatically. The terminal shows green checkmarks indicating the tests passed.

What just happened?

That was the test from tests/example.spec.ts running visually. Every action that Playwright performed (navigate, click, assert) happened in about one second. This is what you would normally do by hand, now fully automated.

Browser didn't open?

Make sure you included both the --project=chromium and --headed flags. Without --headed, the test runs invisibly in the background.

If you see an error about missing browsers, run npx playwright install to re-download the browser binaries.

Still stuck? Help me troubleshoot why the headed browser isn't opening

Read and understand the example test

Now that you have seen what the test does visually, let's look at the code that made it happen.

  • In VS Code, open tests/example.spec.ts from the Explorer sidebar.

Here is the code you should see:

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

test('has title', async ({ page }) => {
  await page.goto('https://playwright.dev/');
  await expect(page).toHaveTitle(/Playwright/);
});

test('get started link', async ({ page }) => {
  await page.goto('https://playwright.dev/');
  await page.getByRole('link', { name: 'Get started' }).click();
  await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
});

What are the three parts of a Playwright test?

Every Playwright test follows a three-part pattern:

  • Navigate: page.goto() opens a URL in the browser, just like you typing an address into the address bar.
  • Act: getByRole(...).click() interacts with elements on the page. This is the equivalent of you clicking a link or button.
  • Assert: expect(...).toBeVisible() verifies the page looks correct. This replaces you visually checking that the right thing appeared.

Notice there are zero waits or sleeps in the code. Playwright auto-waits for elements to be ready before acting on them. This is what eliminates flaky tests.

Run on all three browsers and view the report

So far you have only tested on Chromium. Playwright's config file defines three browser projects: Chromium, Firefox, and WebKit. Running without the --project flag tests on all three simultaneously.

  • Run the tests across all configured browsers by running this command:
npx playwright test

Notice the test count in the terminal output. You have 2 tests running across 3 browsers, so you should see 6 total test results. That is 3x the coverage from a single command.

Why test across multiple browsers?

Different browser engines render pages differently. A button that works in Chrome might behave differently in Safari (which uses WebKit). Testing across all three engines catches browser-specific bugs without you having to manually open each one.

Now let's see the results in a visual dashboard. What do you think the HTML report will show?

  • Open the HTML test report by running this command:
npx playwright show-report

Your browser opens an HTML dashboard. You should see all tests listed with green checkmarks, organized by browser project (chromium, firefox, webkit). Each entry shows the test name, which browser ran it, and how long it took.

The report only auto-opens on failure

When all tests pass, Playwright does not auto-open the report. That is why you run npx playwright show-report manually. When a test fails in a future step, the report will open automatically to show you what went wrong.

Report not opening in your browser?

The report serves on a local port. If your browser does not open automatically, check the terminal for a URL like http://localhost:9323 and open it manually.

If you see a message about the port being in use, press Ctrl+C to stop any previous report server, then run the command again.

Need more help? Help me open the Playwright HTML report

You just ran automated tests across three browser engines and viewed the results in a professional HTML dashboard. Next up, you'll write your own tests from scratch against a real web application.

Write Your Own Tests Against TodoMVC

In the previous step, you watched Playwright's example test run against the Playwright docs site. That proved your setup works, but testing someone else's site is not real QA work.

Real-world testing means forms, user input, dynamic UI updates, and verifying that the app responds correctly. In this step, you will write your own tests from scratch against a TodoMVC app that exercises all of those patterns.

In this step, get ready to:
  • Create a new test file targeting a TodoMVC app.
  • Write three tests that add items, verify counts, and complete todos.
  • Run your tests in headed mode and watch the browser execute your code.
Create a new test file

The TodoMVC app at https://demo.playwright.dev/todomvc/#/ is maintained by the Playwright team. It has a text input, a dynamic list, and checkboxes. This makes it perfect for practicing the navigate-act-assert pattern you saw in the example test.

  • In VS Code's Explorer sidebar, right-click the tests folder and select New File.
  • Name the file todo.spec.ts.
  • Add your first test by pasting the following code into the new file:
import { test, expect } from '@playwright/test';

test('should add a single todo item', async ({ page }) => {
  // Navigate to the TodoMVC app
  await page.goto('https://demo.playwright.dev/todomvc/#/');

  // Find the input field by its placeholder text
  const input = page.getByPlaceholder('What needs to be done?');
  await input.fill('Buy groceries');
  await input.press('Enter');

  // Verify the new todo appears in the list
  await expect(page.getByText('Buy groceries')).toBeVisible();
});

What does this test do?

This test follows the same three-part pattern you saw in the example test: navigate, act, assert.

  • page.goto(...) opens the TodoMVC app in the browser.
  • page.getByPlaceholder('What needs to be done?') finds the input field the way a user would see it.
  • input.fill(...) types text and input.press('Enter') submits it.
  • expect(...).toBeVisible() confirms the todo text now appears on the page.
  • Save todo.spec.ts.
  • Run your new test in headed mode to see it work:
npx playwright test tests/todo.spec.ts --project=chromium --headed

A Chromium browser window opens, navigates to the TodoMVC app, types "Buy groceries" into the input, presses Enter, and closes. The terminal shows 1 test passed.

Test failing or browser not opening?

  • Make sure you saved todo.spec.ts before running the command.
  • Check that the file is inside the tests folder, not the project root.
  • Confirm your import line matches exactly: import { test, expect } from '@playwright/test'.

Help me debug why my Playwright test is failing

Add a multi-step test

A single-item test proves the basic flow works. Now you will test what happens when a user adds multiple items and interacts with them.

  • In todo.spec.ts, add this second test below the closing }); of your first test:
test('should add multiple todo items and show count', async ({ page }) => {
  await page.goto('https://demo.playwright.dev/todomvc/#/');
  const input = page.getByPlaceholder('What needs to be done?');

  // Add two todo items
  await input.fill('Buy groceries');
  await input.press('Enter');
  await input.fill('Water flowers');
  await input.press('Enter');

  // Verify both items appear and the counter updates
  await expect(page.getByText('Buy groceries')).toBeVisible();
  await expect(page.getByText('Water flowers')).toBeVisible();
  await expect(page.getByText('2 items left')).toBeVisible();
});

What does this test check?

This test adds two items and then verifies three things: both item texts are visible, and the app's counter displays "2 items left". Testing the counter confirms the app's state management works correctly, not just the UI rendering.

  • Now add a third test below the second one that tests completing a todo:
test('should complete a todo item', async ({ page }) => {
  await page.goto('https://demo.playwright.dev/todomvc/#/');
  const input = page.getByPlaceholder('What needs to be done?');
  await input.fill('Buy groceries');
  await input.press('Enter');

  // Click the checkbox to mark the item complete
  const todoCheckbox = page.getByRole('checkbox', { name: 'Toggle Todo' });
  await todoCheckbox.click();

  // Verify the checkbox is now checked
  await expect(todoCheckbox).toBeChecked();
});

What does this test check?

This test exercises a different interaction: clicking a checkbox. getByRole('checkbox', { name: 'Toggle Todo' }) finds the checkbox the same way a screen reader would identify it.

The assertion toBeChecked() confirms the checkbox state changed after clicking. This is a web-first assertion that auto-retries until the condition is true.

  • Save todo.spec.ts.

Seeing red squiggly lines in VS Code?

  • Make sure each test function is at the top level of the file, not accidentally nested inside the first test's curly braces.
  • Verify every opening ( and { has a matching close.

Help me fix syntax errors in my Playwright test file

Run your test file and confirm all pass

You now have three tests in todo.spec.ts that cover adding items, verifying counts, and completing todos. Time to run them all in headed mode and watch the browser work.

Before you run this, how many browser windows do you think will open? Will they open one at a time or all at once?

  • Run all three tests in headed mode:
npx playwright test tests/todo.spec.ts --project=chromium --headed

You will see Chromium open and perform each test's actions in sequence. The terminal prints three green checkmarks and "3 passed" when all tests complete.

One or more tests failing?

  • Check that each test navigates to the URL independently with its own page.goto(...) line.
  • Make sure the placeholder text matches exactly: What needs to be done? (capital W, question mark at the end).
  • Verify the checkbox locator uses { name: 'Toggle Todo' } with capital T on both words.

Help me figure out why my TodoMVC test is failing

✔️ Awesome, I've got everything!

Great. Double check you have saved todo.spec.ts and all three tests pass.

ⓧ I'd like to double check the full code

Your tests/todo.spec.ts file should contain these three blocks in order:

  • The import { test, expect } from '@playwright/test' line at the top.
  • Test 1: 'should add a single todo item' that fills, presses Enter, and asserts the text is visible.
  • Test 2: 'should add multiple todo items and show count' that adds two items and asserts both are visible plus the counter.
  • Test 3: 'should complete a todo item' that adds an item, clicks the checkbox, and asserts it is checked.

Scroll up and confirm each chunk matches what you pasted. Each test must have its own page.goto(...) line at the start.

You now have a real test suite that automates what you would normally test by hand. Next, you will discover what happens when locators are fragile and learn how to make them resilient.

Hit a Brittle Locator and Fix It

Your tests pass across all three browsers. But there is a hidden problem lurking in how you find elements on the page.

If your locators depend on CSS class names, a single UI refactor by a developer can break every test in your suite overnight. In this step, you will deliberately write a brittle locator, see it pass, understand why it is fragile, then replace it with a resilient alternative that survives change.

In this step, get ready to:
  • Write a test using a CSS selector and see why it is fragile.
  • Replace the brittle locator with a resilient one that mirrors how users see the page.
  • Organize your tests with describe blocks and a beforeEach hook to eliminate repeated code.
Introduce a brittle locator

The TodoMVC input field has a CSS class called .new-todo. You can target it directly with page.locator('.new-todo') and it will work. But class names are implementation details that developers change during refactors, redesigns, or framework upgrades.

  • In VS Code, open tests/todo.spec.ts and add this new test at the bottom of the file:
test('should use resilient locators', async ({ page }) => {
  await page.goto('https://demo.playwright.dev/todomvc/#/');
  // Using a CSS class selector to find the input
  const input = page.locator('.new-todo');
  await input.fill('Learn Playwright');
  await input.press('Enter');

  await expect(page.getByText('Learn Playwright')).toBeVisible();
  await expect(page.getByText('1 item left')).toBeVisible();
});

What does this code do?

This test adds a todo item and verifies it appears in the list. The key difference from your other tests is page.locator('.new-todo'). This targets the input by its CSS class name instead of using a user-facing attribute like the placeholder text.

  • Save tests/todo.spec.ts.
  • Run the new test in headed mode by running this command:
npx playwright test tests/todo.spec.ts --project=chromium --headed

All four tests pass, including the one using the CSS selector. The browser fills in the input and adds the todo item without any issues.

Why is this a problem if it passes?

The test works today because the class .new-todo happens to exist on the input. But if a developer renames that class to .todo-input during a redesign, your test breaks instantly. The app still works fine for users, but your test suite reports a false failure.

Resilient locators like getByPlaceholder and getByRole find elements the way a user would: by what they see on screen. Class names are invisible to users, so they make poor locators.

Test failing instead of passing?

Make sure the CSS selector string is exactly '.new-todo' with the dot prefix. Without the dot, Playwright looks for an HTML element called new-todo which does not exist.

Help me debug this CSS selector issue

Replace with a resilient locator

Now that you have seen the fragile approach work, replace it with the same resilient pattern your other tests use.

  • In tests/todo.spec.ts, find this line inside your new test:
  const input = page.locator('.new-todo');
  • Replace it with this resilient locator that targets the placeholder text users actually see:
  const input = page.getByPlaceholder('What needs to be done?');
  • Also remove the // Using a CSS class selector to find the input comment since it no longer applies.
  • Save tests/todo.spec.ts and run the test again:
npx playwright test tests/todo.spec.ts --project=chromium --headed

The test still passes. The behaviour is identical, but now the locator survives any class name change because it targets what the user sees: the placeholder text.

What are resilient locators?

Playwright provides built-in locator methods that mirror how a human finds elements on a page:

  • getByPlaceholder finds inputs by their placeholder text.
  • getByRole finds elements by their accessible role (button, checkbox, link).
  • getByText finds elements by their visible text content.

These survive CSS refactors because they target user-facing attributes, not implementation details.

Seeing a locator mismatch error?

Double check that the placeholder string matches exactly: 'What needs to be done?' with a capital W and a question mark at the end. Playwright matches placeholder text literally.

Help me fix my getByPlaceholder locator

  • Review your other three tests quickly. They already use getByPlaceholder, getByRole, and getByText. No CSS selectors remain in your test file.
Organize tests with describe blocks and beforeEach

Every test repeats the same page.goto line to navigate to the TodoMVC app. Playwright provides test.describe to group related tests and test.beforeEach to run shared setup before every test automatically.

  • Select all the content in tests/todo.spec.ts and replace it with the following restructured code. Start with the import, describe block, and first two tests:
import { test, expect } from '@playwright/test';

test.describe('Todo operations', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('https://demo.playwright.dev/todomvc/#/');
  });

  test('should add a single todo item', async ({ page }) => {
    const input = page.getByPlaceholder('What needs to be done?');
    await input.fill('Buy groceries');
    await input.press('Enter');

    await expect(page.getByText('Buy groceries')).toBeVisible();
  });

  test('should add multiple todo items and show count', async ({ page }) => {
    const input = page.getByPlaceholder('What needs to be done?');

    await input.fill('Buy groceries');
    await input.press('Enter');

    await input.fill('Water flowers');
    await input.press('Enter');

    await expect(page.getByText('Buy groceries')).toBeVisible();
    await expect(page.getByText('Water flowers')).toBeVisible();
    await expect(page.getByText('2 items left')).toBeVisible();
  });

What changed here?

  • test.describe('Todo operations', () => { ... }) groups all your tests under a named suite. This shows up as a collapsible section in reports.
  • test.beforeEach runs before every test in the describe block. It navigates to the TodoMVC URL so no individual test needs to repeat that line.
  • Each test no longer has its own page.goto call. The hook handles it automatically.
  • Now add the remaining two tests directly below, still inside the describe block:
  test('should complete a todo item', async ({ page }) => {
    const input = page.getByPlaceholder('What needs to be done?');
    await input.fill('Buy groceries');
    await input.press('Enter');

    const todoCheckbox = page.getByRole('checkbox', { name: 'Toggle Todo' });
    await todoCheckbox.click();

    await expect(todoCheckbox).toBeChecked();
  });

  test('should use resilient locators', async ({ page }) => {
    const input = page.getByPlaceholder('What needs to be done?');
    await input.fill('Learn Playwright');
    await input.press('Enter');

    await expect(page.getByText('Learn Playwright')).toBeVisible();
    await expect(page.getByText('1 item left')).toBeVisible();
  });
});

What do these tests cover?

  • The third test adds an item, clicks the checkbox to complete it, and asserts the checkbox is checked. It uses getByRole('checkbox', { name: 'Toggle Todo' }) to find the checkbox by its accessible name.
  • The fourth test is the one you fixed earlier. It now uses getByPlaceholder instead of the brittle CSS selector.
  • The closing }); ends the describe block. Every test is now inside this group.
  • Save tests/todo.spec.ts.

Before you run the full suite, predict: how many total test runs will you see in the terminal output? Remember, Playwright runs every test across chromium, firefox, and webkit.

  • Run all tests across all browsers by running this command:
npx playwright test tests/todo.spec.ts

You should see 12 tests pass (4 tests multiplied by 3 browsers). Every test runs against chromium, firefox, and webkit with zero CSS selectors remaining in your code.

Seeing syntax errors or failing tests?

The most common issue is a missing or extra bracket. Make sure your file has exactly one }); at the very end that closes the describe block. Each test should be indented one level inside the describe.

Also verify that no test still has its own await page.goto(...) line. The beforeEach hook handles navigation for all tests now.

Help me fix my describe block syntax

✔️ Awesome, I've got everything!

Great. Double check you have saved tests/todo.spec.ts and all 12 test runs pass (4 tests across 3 browsers).

ⓧ I'd like to double check the full code

Your tests/todo.spec.ts should contain these sections in order:

  • The import line: import { test, expect } from '@playwright/test';.
  • A test.describe('Todo operations', () => { wrapper around everything.
  • A test.beforeEach hook that navigates to https://demo.playwright.dev/todomvc/#/.
  • Test 1: 'should add a single todo item' (fills 'Buy groceries', asserts visible).
  • Test 2: 'should add multiple todo items and show count' (fills two items, asserts both visible plus '2 items left').
  • Test 3: 'should complete a todo item' (fills item, clicks checkbox, asserts checked).
  • Test 4: 'should use resilient locators' (fills 'Learn Playwright', asserts visible plus '1 item left').
  • A closing }); for the describe block.

No test should have its own page.goto line. No test should use page.locator('.new-todo'). All locators should use getByPlaceholder, getByRole, or getByText.

Your tests now use locators that survive CSS refactors, and your code is organized with shared setup logic. Next up, you will intentionally break a test and use Playwright's Trace Viewer to debug the failure visually.

Debug a Failure with Trace Viewer

Your tests pass across all three browsers. That is great for the happy path, but what happens when a test fails in a real project?

When something goes wrong, staring at a terminal error message is rarely enough. Playwright's Trace Viewer lets you step through each action of a failed test with full DOM snapshots, network activity, and console logs. In this step, you will intentionally break a test, then use the Trace Viewer to investigate exactly what happened.

In this step, get ready to:
  • Break a test on purpose and run it with tracing enabled.
  • Open the Trace Viewer and step through the failed action with DOM snapshots.
  • Fix the test and confirm a fully green report across all browsers.
Intentionally break a test and run with tracing

To see what the Trace Viewer can do, you need a failure to investigate. You will change an assertion to expect wrong text, then run the test with tracing turned on.

  • In VS Code, open tests/todo.spec.ts.
  • Inside the 'should add a single todo item' test, find this assertion line:
await expect(page.getByText('Buy groceries')).toBeVisible();
  • Change 'Buy groceries' to 'Buy food', so the line now looks like this:
await expect(page.getByText('Buy food')).toBeVisible();
  • Save tests/todo.spec.ts.

Before you run this, what do you think will happen? The test fills the input with 'Buy groceries' but now asserts for 'Buy food'. Will it pass or fail?

  • Run the test with tracing enabled by running this command:
npx playwright test tests/todo.spec.ts --project=chromium --trace on

The test fails as expected. The terminal shows an error indicating that 'Buy food' was never found on the page.

Why use --trace on here?

By default, Playwright only records traces on the first retry of a failed test. Since you have zero retries configured locally, no trace would be captured without this flag.

The --trace on flag forces Playwright to record a trace for every test run, giving you something to open in the Trace Viewer.

Test passed instead of failing?

Double-check that you changed the assertion text to 'Buy food' and saved the file. If the file was not saved, Playwright runs the old code.

Help me figure out why my intentionally broken test is still passing.

Open the Trace Viewer and investigate

Now that you have a failing test with a trace recorded, open the HTML report to explore what went wrong visually.

  • Open the HTML report by running this command:
npx playwright show-report

Your browser opens with the HTML report showing the failed test in red.

  • Click on the failed test (should add a single todo item) to open the detailed view.
  • You will see the error message showing which assertion failed and what text was expected vs. what was found.
  • Click the trace icon next to the test file name to open the Trace Viewer.

What am I looking at in the Trace Viewer?

The Trace Viewer is split into three panels. On the left you see a list of every action your test performed (goto, fill, press, expect). In the centre you see a DOM snapshot showing the exact state of the page at that moment.

On the right side, tabs show details for each action. The Actions tab shows timing and parameters. The Console tab shows any browser console messages. The Network tab shows HTTP requests made during that action.

  • Click through each action on the left side of the Trace Viewer.
  • Watch the DOM snapshot in the centre update to show the page state before and after each action.
  • Click the final expect action to see exactly where the test failed. The snapshot shows 'Buy groceries' in the list, but the test was looking for 'Buy food'.
  • Click the Console tab on the right to check for any browser console messages.
  • Click the Network tab to see the HTTP requests the page made during the test.

You can now see the exact moment of failure with full page context. This is far more powerful than re-reading a terminal error message.

Interact with the DOM snapshot

The Trace Viewer's DOM snapshot is fully interactive. You can right-click and open browser DevTools to inspect HTML, CSS, and element properties, just like you would on a live page.

Fix the test and run the full suite clean

You have seen how to investigate a failure. Now fix the intentional error and confirm everything passes.

  • In tests/todo.spec.ts, find the broken assertion line:
await expect(page.getByText('Buy food')).toBeVisible();
  • Change 'Buy food' back to 'Buy groceries', so the line reads:
await expect(page.getByText('Buy groceries')).toBeVisible();
  • Save tests/todo.spec.ts.

Before you run the full suite, do you expect all tests to pass across all three browsers now?

  • Run the complete test suite across all browsers by running this command:
npx playwright test

All tests pass with green checkmarks across chromium, firefox, and webkit.

  • Open the HTML report to confirm a fully green dashboard by running this command:
npx playwright show-report

Your browser shows the HTML report with every test passing across all three browsers. No red, no failures.

Still seeing a failure in the report?

Make sure you changed 'Buy food' back to 'Buy groceries' and saved the file before running npx playwright test.

If the report still shows old results, close the report tab in your browser and run npx playwright show-report again to load the latest results.

Help me figure out why my tests are still failing after I fixed the assertion.

You now know how to record a trace, open the Trace Viewer, and step through a failure action-by-action. Next up, you will tackle the Secret Mission and learn how to record browser actions with Codegen to auto-generate test code.

Secret mission

Record Tests with Codegen

What if you could turn your manual testing sessions into automated test code without writing a single line? Playwright's Codegen tool records your browser actions and generates ready-to-run test scripts. Use it to bridge your manual QA expertise with automation.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely locally, so there are no ongoing costs.

Resources you used:

  • Your local Playwright project folder (test files, config, and node_modules).
  • Downloaded browser binaries (Chromium, Firefox, WebKit) stored locally (~500MB total disk space).

Keep everything running

No action needed. Choose this if you plan to keep writing and running Playwright tests.

  • Your project folder stays on your Desktop, ready to open in VS Code anytime.
  • Your downloaded browser binaries remain cached so future test runs start instantly.
  • Node.js and VS Code remain installed on your system for other projects.

Pause - I'll come back to this later

Nothing to pause. Everything is local and uses no background processes or ongoing resources.

  • Your project folder and browser binaries sit idle on disk until you return.
  • To resume, open your project folder in VS Code and run npx playwright test in the integrated terminal.

Delete - I don't want to use this again

Remove all project files and browser binaries to free up disk space.

  • Remove the downloaded Playwright browser binaries by running this command in your terminal:
npx playwright uninstall
  • Delete your project folder. On macOS or Linux, run:
cd ~/Desktop
rm -rf your-playwright-project-folder

On Windows, run:

cd ~\Desktop
rmdir /s /q your-playwright-project-folder

What about Node.js and VS Code?

Node.js and VS Code are general-purpose tools you will likely use for other projects. There is no need to uninstall them unless you are certain you will not use them again.

That's a Wrap!

That's a Wrap!

Nice work! You just built a professional automated test suite that validates a web application across three browsers in seconds.

What used to take 15+ minutes of repetitive manual clicking now runs with a single terminal command.

You've learned how to:

  • Write end-to-end tests that navigate pages, fill forms, click elements, and assert outcomes across Chromium, Firefox, and WebKit simultaneously.
  • Use resilient locators (getByRole, getByPlaceholder, getByText) instead of brittle CSS selectors to eliminate flaky tests.
  • Debug a test failure visually using Playwright's Trace Viewer with DOM snapshots, network logs, and action timelines.
  • Secret Mission: Recorded browser actions with Codegen to auto-generate test code from manual workflows.

Ready to quiz yourself?