What Is Playwright? End-to-End Testing for AI-Built Apps

Your AI built the app. Playwright opens a real browser and proves it actually works.

TL;DR

Playwright is a free tool from Microsoft that opens a real web browser, clicks buttons, fills out forms, and checks that your app works — automatically. It's what AI tools generate when you ask for "end-to-end tests." You don't write the tests by hand. You ask AI to write them, then you run them to catch bugs before your users do.

Why AI Coders Need to Know This

Here's a scenario every vibe coder has lived: You asked AI to build a signup form. It looks great. You click around, everything seems fine. You ship it. Then a user emails you — "I can't sign up." Turns out the form submits but the confirmation page never loads. Or the password field silently rejects special characters. Or the button works in Chrome but does nothing in Safari.

You didn't catch it because you tested it the same way every human does — you clicked a few things, it looked right, you moved on. That's not testing. That's hoping.

Playwright replaces hoping with knowing. It opens an actual web browser (not a simulation — a real browser), walks through your app step by step, and tells you exactly what passed and what broke. It clicks every button. Fills every form. Checks every page. And it does it in seconds, every single time.

Think of it like a building inspection. You built the house. You think the plumbing works. But the inspector turns on every faucet, flushes every toilet, and runs every dishwasher cycle. That's what Playwright does for your app.

The reason this matters specifically for AI coders: you didn't write the code. AI did. You understand what it's supposed to do, but you can't manually review thousands of lines of generated code to spot bugs. Playwright gives you a way to verify the end result without reading every line.

And here's the kicker — AI is excellent at writing Playwright tests. You describe what your app should do, AI generates the test code, and Playwright runs it. It's the closest thing to "just make sure it works" that exists in software.

Real Scenario

What you asked AI:

"I built a task management app with Next.js. Users can sign up, log in, create tasks, mark them complete, and delete them. Can you write end-to-end tests using Playwright to make sure all of that actually works?"

This is a perfectly normal prompt. You've got a working app. You want to make sure the core flows — signup, login, creating tasks, completing tasks, deleting tasks — all work the way they should. Not just today, but tomorrow after you ask AI to add new features.

Here's what AI came back with.

What AI Generated

AI generated a Playwright test file that walks through your app like a real user would. Here's the core of what it produced:

// tests/task-app.spec.ts
import { test, expect } from '@playwright/test';

test.describe('Task Management App', () => {

  test('user can sign up for a new account', async ({ page }) => {
    await page.goto('http://localhost:3000/signup');

    await page.getByLabel('Email').fill('newuser@example.com');
    await page.getByLabel('Password').fill('SecurePass123!');
    await page.getByRole('button', { name: 'Sign Up' }).click();

    await expect(page.getByText('Welcome! Your account is ready.')).toBeVisible();
  });

  test('user can log in and see their dashboard', async ({ page }) => {
    await page.goto('http://localhost:3000/login');

    await page.getByLabel('Email').fill('newuser@example.com');
    await page.getByLabel('Password').fill('SecurePass123!');
    await page.getByRole('button', { name: 'Log In' }).click();

    await expect(page).toHaveURL(/.*dashboard/);
    await expect(page.getByRole('heading', { name: 'My Tasks' })).toBeVisible();
  });

  test('user can create a new task', async ({ page }) => {
    // Assumes user is already logged in (setup in beforeEach or fixture)
    await page.goto('http://localhost:3000/dashboard');

    await page.getByPlaceholder('Add a new task...').fill('Buy lumber for deck project');
    await page.getByRole('button', { name: 'Add Task' }).click();

    await expect(page.getByText('Buy lumber for deck project')).toBeVisible();
  });

  test('user can mark a task as complete', async ({ page }) => {
    await page.goto('http://localhost:3000/dashboard');

    const task = page.getByText('Buy lumber for deck project');
    await task.locator('..').getByRole('checkbox').check();

    await expect(task).toHaveCSS('text-decoration', /line-through/);
  });

  test('user can delete a task', async ({ page }) => {
    await page.goto('http://localhost:3000/dashboard');

    const taskRow = page.getByText('Buy lumber for deck project').locator('..');
    await taskRow.getByRole('button', { name: 'Delete' }).click();

    await expect(page.getByText('Buy lumber for deck project')).not.toBeVisible();
  });

});

That's a complete test suite. Five tests. Each one opens a browser, does something a real user would do, and checks that the right thing happened. Let's break down what each piece means.

Understanding Each Part

You don't need to memorize any of this. But when a test fails and AI tells you "the locator didn't match," you need to know what that means. Here's the decoder ring.

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

This loads the tools you need from Playwright — the test function (which defines each test) and expect (which checks that something is true). Think of test as defining an inspection item and expect as the pass/fail criteria.

test.describe('Task Management App', () => { ... })

This groups related tests together under a name. It's organizational — like putting related inspection items under "Plumbing" or "Electrical." It doesn't change how the tests run; it just keeps the report organized.

test('user can sign up', async ({ page }) => { ... })

This defines one test. The string is a human-readable description of what you're testing. The page part is Playwright's representation of a browser tab — it's the thing you interact with. Every test gets a fresh browser tab, like starting with a clean slate.

await page.goto('http://localhost:3000/signup')

What it does: Opens a URL in the browser. Just like typing an address in your address bar and hitting Enter.

The await keyword means "wait for this to finish before moving on." Browsers take time to load pages. Without await, Playwright would try to click buttons on a page that hasn't loaded yet. You'll see await before almost every line — just think of it as "and then."

page.getByLabel('Email')

What it does: Finds an input field by its label text. If your form has a label that says "Email" above or next to an input box, this finds that input box. Playwright offers several ways to find elements:

These are called locators. They're how Playwright finds things on the page, the same way you'd tell someone "click the big blue button that says Sign Up."

.fill('newuser@example.com')

What it does: Types text into a field. Like clicking an input box and typing on your keyboard. .fill() first clears whatever is already there, then types the new text.

.click()

What it does: Clicks the element. Same as you clicking it with your mouse. Playwright waits for the element to be visible and clickable before clicking — it won't try to click something that's hidden or still loading.

await expect(page.getByText('Welcome!')).toBeVisible()

What it does: This is the actual check — the pass/fail moment. It says: "I expect the text 'Welcome!' to be visible on the page." If it's there, the test passes. If it's not there (maybe the signup failed silently, or it shows an error instead), the test fails and tells you exactly what went wrong.

Common expect checks you'll see:

.locator('..')

What it does: Moves up to the parent element. In the delete test, AI first finds the task text, then goes up to the row that contains it (.. means "parent"), then finds the Delete button within that row. It's like saying "find the task labeled 'Buy lumber,' go to the row it's in, and click the delete button in that row."

The Big Picture

Every Playwright test follows the same three-step pattern:

  1. Go somewhere — navigate to a page
  2. Do something — click, type, check a box
  3. Check the result — did the right thing happen?

That's it. Navigate, interact, verify. Like walking through a house: go to the kitchen, turn on the faucet, check that water comes out.

What AI Gets Wrong About This

AI is very good at generating Playwright tests. It's also very good at generating Playwright tests that don't run. Here are the most common problems and what they actually mean.

1. Selectors That Don't Match Your App

This is the #1 issue. AI writes page.getByLabel('Email'), but your form uses a placeholder instead of a label. Or AI writes getByRole('button', { name: 'Submit' }), but your button actually says "Sign Up." The test fails immediately because Playwright can't find the element.

What to do: Tell AI to look at your actual HTML. Paste the relevant section of your page's source code into the chat and say "write selectors that match this actual HTML."

2. Forgetting to Start the Dev Server

Playwright tests need your app to be running. AI generates the test file but doesn't tell you to start your dev server first, or doesn't configure Playwright's webServer option to start it automatically. You run the tests and every single one fails because localhost:3000 isn't serving anything.

What to do: Ask AI: "Add a webServer config to my playwright.config.ts that starts my dev server before running tests." This makes Playwright automatically start your app, run the tests, then shut it down.

// playwright.config.ts — add this
export default defineConfig({
  webServer: {
    command: 'npm run dev',
    url: 'http://localhost:3000',
    reuseExistingServer: !process.env.CI,
  },
  // ... rest of your config
});

3. Tests That Depend on Each Other

In the example above, the "mark complete" test assumes the task from the "create task" test still exists. But Playwright runs each test in a clean browser — there's no shared state. It's like expecting the drywall to be up when the framing crew hasn't been there yet.

What to do: Each test needs to set up its own data. Tell AI: "Make each test independent. If a test needs a task to exist, create it at the beginning of that test."

4. Timing Issues (The "Flaky Test" Problem)

Sometimes a test passes, sometimes it fails. Same code, different results. This usually happens because Playwright tried to check for something before the page finished updating. Maybe the API call to create a task takes 500ms, and Playwright checked for the task at 200ms.

What to do: Playwright's built-in locators automatically wait and retry, but AI sometimes writes manual waits like await page.waitForTimeout(2000) — which is fragile. Tell AI: "Don't use waitForTimeout. Use Playwright's auto-waiting locators instead." The expect assertions automatically retry for up to 5 seconds by default.

5. Not Installing Browsers

You install Playwright with npm, but Playwright also needs to download the actual browsers it controls. AI tells you to run npm install @playwright/test but forgets to mention npx playwright install. You get a confusing error about missing browser executables.

What to do: After installing Playwright, always run npx playwright install. This downloads Chromium, Firefox, and WebKit. It's a one-time thing (until you update Playwright).

How to Debug with AI

When a Playwright test fails, it doesn't just say "fail." It gives you a detailed report with screenshots, traces, and error messages. Here's how to use each one — and how AI can help you make sense of them.

Reading the Terminal Output

When you run npx playwright test, you'll see output like this:

  ✓ user can sign up for a new account (3.2s)
  ✓ user can log in and see their dashboard (2.8s)
  ✗ user can create a new task (5.1s)

  Error: expect(locator).toBeVisible()
  Locator: getByText('Buy lumber for deck project')
  Expected: visible
  Received: <element not found>

  Call log:
    - waiting for getByText('Buy lumber for deck project')
    - locator resolved to 0 elements
    - unexpected value "not found"

This tells you exactly what happened: Playwright looked for the text "Buy lumber for deck project" on the page and couldn't find it. The task wasn't created — or it was created with different text. Copy this entire error and paste it to AI with: "This Playwright test is failing. Here's the error. What's wrong?"

The HTML Report

Playwright generates a beautiful HTML report after every test run. Open it with:

npx playwright show-report

This opens a web page in your browser showing every test, its status, how long it took, and — crucially — screenshots of what the page looked like when the test failed. You can literally see what Playwright saw. If the test expected a "Welcome" message but the screenshot shows an error page, now you know the problem is in your app, not the test.

The Trace Viewer (Your Secret Weapon)

The trace viewer is the most powerful debugging tool Playwright offers. It records everything: every click, every network request, every page change, with screenshots at each step. Enable it by running:

npx playwright test --trace on

Then open the trace:

npx playwright show-trace test-results/trace.zip

You'll see a timeline of every action your test took, with before/after screenshots for each step. It's like having a security camera recording of the entire test. You can see exactly where things went sideways — "the click happened, the page started loading, but the API returned a 500 error."

Running Tests Visually (Headed Mode)

By default, Playwright runs browsers invisibly (headless). But you can watch it in real time:

npx playwright test --headed

A browser window pops up and you can watch Playwright clicking through your app. It's mesmerizing — and incredibly helpful for understanding what the test actually does. It's the difference between reading a building inspection report and following the inspector around the house.

Using Playwright's Code Generator

If AI is generating selectors that don't match your app, skip the middleman:

npx playwright codegen http://localhost:3000

This opens your app in a browser with a recording sidebar. You click around your app normally, and Playwright writes the test code for you in real time. It uses selectors that actually match your HTML because it's looking at the real thing. Copy that generated code and use it as the base for your tests.

What to Learn Next

Playwright is one piece of the testing puzzle. Here's where to go from here:

Frequently Asked Questions

What is Playwright used for?

Playwright is a free, open-source testing tool from Microsoft that opens a real web browser and interacts with your app the way a human would — clicking buttons, filling out forms, navigating pages, and checking that everything works correctly. It's used for end-to-end (E2E) testing, which means testing the entire flow of your app from start to finish rather than checking individual pieces in isolation.

What is the difference between Playwright and Jest or Vitest?

Jest and Vitest are unit testing tools — they test individual functions and pieces of logic in your code without opening a browser. Playwright is an end-to-end testing tool — it launches a real browser and tests your entire app the way a user would experience it. Think of it this way: Jest checks that your plumbing valve works, Playwright turns on every faucet in the house and makes sure water actually comes out.

Can AI write Playwright tests for me?

Yes, and AI is very good at generating Playwright tests. You can tell Claude, ChatGPT, or Cursor something like "Write Playwright tests for my login page" and it will generate complete test files. The catch is that AI sometimes uses selectors that don't match your actual HTML, or forgets setup steps like starting your dev server. You'll need to run the tests and fix what breaks, which is usually a quick conversation with AI.

Do I need to learn Playwright or just let AI handle it?

You don't need to memorize Playwright's API, but you do need to understand what the tests are doing. When a test fails, you need to know whether the test is wrong or your app is broken. That means understanding the basics: what page.goto does, what expect checks for, and how to read the error messages. Think of it like reading a building inspection report — you don't need to be the inspector, but you need to understand what the findings mean.

Is Playwright free to use?

Yes, Playwright is completely free and open source. It's maintained by Microsoft and released under the Apache 2.0 license. You install it with npm (Node Package Manager), and it downloads the browsers it needs automatically. There are no paid tiers, no feature gates, and no usage limits. It works with Chromium, Firefox, and WebKit (Safari's engine) out of the box.