TL;DR
Playwright is a free, open-source tool from Microsoft that controls a real browser and clicks through your app like a user would. When Claude or Cursor writes tests for your project, it almost certainly writes Playwright tests. Run npx playwright test to see if your app actually works.
What Is Playwright? (Plain English)
Imagine you hired someone to sit at a computer, open your website in a browser, and manually click every button, fill out every form, and verify that every page loads correctly — every single time you pushed new code. That person would be doing what Playwright does, except Playwright does it in seconds, without complaining, and for free.
Playwright is a browser automation library made by Microsoft. It's free, open source, and it controls real browsers — Chrome, Firefox, and Safari — programmatically. You write a test script (or your AI writes it for you), and Playwright opens a browser, navigates to your app, clicks around, fills in forms, and checks that things work the way they should.
This type of testing has a name: end-to-end testing. If you want the full breakdown of what that means, check out What Is End-to-End Testing? — but the short version is: it tests the whole user experience, from the moment someone lands on your page to the moment they complete a task.
Here's why this matters more than you might think:
- Unit tests check that a function returns the right value. They don't check if the button actually works when a user clicks it.
- Visual checks confirm the button exists and looks right. They don't confirm the click triggers the correct action.
- Playwright tests click the button. In a real browser. And check what happens next.
If you've been vibe coding for more than a few weeks, you've probably seen something like this in a file your AI generated:
import { test, expect } from '@playwright/test';
test('user can submit contact form', async ({ page }) => {
await page.goto('http://localhost:3000/contact');
await page.fill('input[name="email"]', 'chuck@example.com');
await page.fill('textarea[name="message"]', 'Hello!');
await page.click('button[type="submit"]');
await expect(page.locator('.success-message')).toBeVisible();
});
That's a Playwright test. It opens your contact page, fills in the email and message fields, clicks submit, and checks that a success message appears. If your form is broken — wrong API endpoint, missing validation, buggy JavaScript — this test will fail and tell you exactly where.
Key Facts
- Made by Microsoft, completely free and open source
- Tests Chrome (Chromium), Firefox, and Safari (WebKit) — all three from one codebase
- Write tests in JavaScript, TypeScript, Python, Java, or C#
- Works with any web app: React, Next.js, Vue, Angular, plain HTML
- Built-in test runner called Playwright Test — no extra setup needed
- Used by Microsoft, GitHub, and thousands of dev teams worldwide
You Asked AI to Build a Feature — Here's What the Tests Look Like
Let's say you told Claude: "Build me a login page with email and password fields. If the credentials are wrong, show an error. If they're right, redirect to the dashboard."
Claude builds it. Now you ask: "Write Playwright tests for this login page."
Here's roughly what Claude generates — and what each part actually does:
import { test, expect } from '@playwright/test';
test.describe('Login Page', () => {
test('shows error for wrong password', async ({ page }) => {
await page.goto('/login'); // 1. Opens the login page
await page.fill('#email', 'chuck@example.com'); // 2. Types in the email field
await page.fill('#password', 'wrongpassword'); // 3. Types in the password field
await page.click('button[type="submit"]'); // 4. Clicks the Submit button
await expect(page.locator('.error-message'))
.toContainText('Invalid credentials'); // 5. Checks the error message appears
});
test('redirects to dashboard on success', async ({ page }) => {
await page.goto('/login');
await page.fill('#email', 'chuck@example.com');
await page.fill('#password', 'correctpassword123');
await page.click('button[type="submit"]');
await expect(page).toHaveURL('/dashboard'); // 6. Checks the URL changed
});
test('disables submit button while loading', async ({ page }) => {
await page.goto('/login');
await page.fill('#email', 'chuck@example.com');
await page.fill('#password', 'correctpassword123');
await page.click('button[type="submit"]');
await expect(page.locator('button[type="submit"]'))
.toBeDisabled(); // 7. Checks button is disabled
});
});
Let's break down what you're actually looking at:
page.goto('/login') — Playwright opens a real browser and navigates to that URL. This is like you typing the address in Chrome.
page.fill('#email', 'chuck@example.com') — Playwright finds the input field with the ID email and types that value into it. The # is a CSS selector — same thing your browser uses to find elements.
page.click('button[type="submit"]') — Playwright clicks the submit button. Not just triggers a JavaScript event — it actually moves a simulated cursor to the button and clicks it.
expect(page.locator('.error-message')).toContainText('Invalid credentials') — This is the assertion. The test is asking: "Does an element with the class error-message exist, and does it contain that text?" If it doesn't, the test fails.
These tests protect you. Every time you push new code — or every time Claude "fixes" something and accidentally breaks authentication — these tests run automatically and catch the regression before your users do.
The Typical Vibe Coder Workflow
Here's how this actually plays out day-to-day:
- Claude writes the feature (login page, checkout flow, whatever)
- You ask Claude: "Write Playwright tests for this"
- You run
npx playwright test - Tests pass → ship it
- Tests fail → copy the error output, paste it back to Claude, it fixes the code or the test
- Repeat until green
It's a tight loop. The tests aren't extra work — they're the quality gate that prevents you from discovering your checkout is broken when a real customer tries to buy something.
The codegen Trick: Record Your Tests Without Writing Code
Here's the thing most people don't know about Playwright: you don't have to write the tests by hand. Playwright has a built-in recorder called codegen that watches what you do in the browser and writes the test code for you.
Run this command:
npx playwright codegen http://localhost:3000
Two windows open: your app in a browser, and a Playwright Inspector window showing live code. Now just use your app normally — click buttons, fill in forms, navigate around. Playwright watches every action you take and generates the corresponding test code in real time.
When you're done, copy that code into a test file. Clean it up if needed, add assertions, and you have a working test suite — without writing a single line yourself.
Pro Tip
Run npx playwright codegen --save-storage=auth.json http://localhost:3000/login to record your login flow and save the auth state. Then future tests can start already logged in — no need to re-authenticate in every test.
What codegen is Great For
- Complex user flows — multi-step forms, wizard flows, drag-and-drop interfaces
- Pages with lots of elements — codegen picks the best selector for each element automatically
- When you don't know the selectors — you click the button, Playwright figures out how to reference it in code
- Getting a starting point — record the flow, then hand the output to Claude and say "add assertions and edge cases"
The Test File Structure
Once you've got test code — whether from Claude, codegen, or writing it yourself — you put it in a file ending in .spec.ts (or .spec.js). Playwright finds these automatically.
Your project structure ends up looking like this:
my-project/
├── src/ # Your app code
├── tests/
│ ├── login.spec.ts # Login tests
│ ├── checkout.spec.ts # Checkout flow tests
│ └── homepage.spec.ts # Homepage tests
├── playwright.config.ts # Playwright configuration
└── package.json
Run all tests: npx playwright test
Run one file: npx playwright test tests/login.spec.ts
Visual mode: npx playwright test --ui
Watch the browser: npx playwright test --headed
What AI Gets Wrong About Playwright
Claude and Cursor are excellent at writing Playwright tests. But they make the same mistakes repeatedly. Knowing these patterns means you can catch them before they waste your time.
Mistake 1: Two Elements With the Same Selector
Error you'll see:
Error: locator.click: Error: strict mode violation: locator('button')
resolved to 2 elements
What happened: Claude wrote a test that clicks button, but your page has two buttons — Submit and Cancel. Playwright runs in "strict mode" by default, meaning if a selector matches more than one element, it refuses to guess which one you meant.
Fix: Ask Claude to use a more specific selector. Instead of button, use button[type="submit"] or button:has-text("Submit") or give the button a unique ID and select by that.
Mistake 2: Not Waiting for Things to Load
Error you'll see:
Error: Timeout 30000ms exceeded.
waiting for locator('.dashboard-content')
What happened: Claude wrote a test that checks for something immediately after clicking, but your app does an API call before showing that element. The test clicked, then immediately looked for the result before the server responded.
Fix: Playwright has automatic waiting built in — it will wait for elements to appear before asserting. But sometimes Claude skips using await expect(...).toBeVisible() and instead uses raw page.locator() without waiting. The fix is usually to change the assertion to use expect(...).toBeVisible(), which waits automatically.
Mistake 3: Hardcoded Localhost URLs
What happens: Claude writes every test with page.goto('http://localhost:3000/...'). This works on your machine. It breaks in CI/CD where the URL might be different.
Fix: Use a playwright.config.ts with a baseURL setting, then write tests using relative paths like page.goto('/login'). Claude should do this automatically — if it doesn't, ask it to "update the tests to use baseURL from playwright.config.ts."
Mistake 4: Tests That Depend on Each Other
What happens: Claude writes Test 1 that creates a user, Test 2 that logs in as that user, Test 3 that updates the profile. They pass in order. Run them individually or in a different order — they all fail.
Fix: Each test should be able to run independently. Ask Claude to "make each test self-contained — no test should depend on state from another test." This usually means using test fixtures or setup/teardown code to create fresh data for each test.
Mistake 5: Testing the Wrong Thing
What happens: Claude writes a test that checks if an element exists (toBeVisible()) instead of checking if it works. The button is visible but broken — the test passes, the feature fails.
Fix: Push Claude toward behavioral assertions. Not "does this element exist" but "does clicking this button change the URL?" Not "is the form rendered" but "does submitting the form show a success message?" Ask: "Write tests that verify behavior, not just presence."
Mistake 6: No Error Screenshots
What happens: A test fails in CI. You get a stack trace. But you have no idea what the page actually looked like when it failed.
Fix: Add this to your playwright.config.ts:
export default defineConfig({
use: {
screenshot: 'only-on-failure', // Screenshot when tests fail
video: 'retain-on-failure', // Video of the failing test run
trace: 'on-first-retry', // Detailed trace on retry
},
});
Now when a test fails, Playwright saves a screenshot and video of exactly what happened. Claude should include this in any config it generates — if it doesn't, add it yourself.
Playwright vs Cypress: Which One Does Your AI Use?
If you've searched "browser testing JavaScript" before, you've probably seen Cypress mentioned. It was the dominant tool for a long time. Playwright has largely overtaken it for new projects. Here's the honest comparison:
| Feature | Playwright | Cypress |
|---|---|---|
| Browsers supported | Chrome, Firefox, Safari | Chrome-based only |
| Cost | Free, open source | Free (paid dashboard tier) |
| Speed | Faster (parallel by default) | Slower (serial by default) |
| What AI writes by default | Playwright (as of 2024+) | Older tutorials, some AI still |
| Languages | JS, TS, Python, Java, C# | JavaScript/TypeScript only |
| GitHub Actions support | Official action available | Yes, good support |
| Learning curve | Moderate (let AI handle it) | Slightly lower for beginners |
The bottom line: If you're starting a new project today, use Playwright. If your AI writes tests and you didn't specify, it almost certainly wrote Playwright. If you have an existing Cypress project that's working, there's no urgent reason to migrate — Cypress still works fine.
The reason Playwright won is mostly the cross-browser story. Cypress only tests Chrome-based browsers. Playwright tests Chrome, Firefox, and Safari (via WebKit) from one test suite. If your users are on Safari — and iPhone users are on Safari — that matters.
Running Playwright in CI/CD (GitHub Actions)
Running tests locally is useful. Running them automatically on every code push is how professional teams catch bugs before they reach production. Even if you're a solo builder, setting this up is worth it — especially when Claude is generating large amounts of code.
If you're hosting your code on GitHub, you can use GitHub Actions to run your Playwright tests automatically on every push. Not sure how GitHub Actions works? The concept connects to your broader deployment infrastructure — it's part of the same ecosystem.
Here's what a basic GitHub Actions workflow file looks like for Playwright:
# .github/workflows/playwright.yml
name: Playwright Tests
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 30
What this does:
- Triggers on every push to
mainordevelop, and on pull requests - Sets up a fresh Ubuntu environment with Node.js
- Installs your project dependencies
- Downloads the browsers Playwright needs (Chrome, Firefox, Safari)
- Runs all your Playwright tests
- Saves the test report as an artifact — so you can download and view it if something fails
Save that file as .github/workflows/playwright.yml in your project, push it, and GitHub will run your tests automatically from now on.
Watch Out
Playwright downloads full browser binaries (~300MB). This makes your CI/CD setup time longer on the first run. You can cache the browser downloads to speed things up — ask Claude to "add Playwright browser caching to my GitHub Actions workflow."
What Happens When Tests Fail in CI
Your push shows a red X on GitHub. You click into the Actions run, download the playwright-report artifact, and you get a full HTML report showing:
- Which test failed
- The exact error message
- A screenshot of what the page looked like when it failed
- A video of the test running (if you enabled it)
- A trace file you can open in Playwright's Trace Viewer for step-by-step replay
Copy the error message, paste it to Claude, and ask it to fix the failing test or the underlying bug. That's the loop. That's the whole workflow.
This kind of AI-driven development workflow — where Claude writes the code AND the tests, and you close the loop on failures — is the core pattern of effective vibe coding in 2026.
What to Learn Next
Playwright is a big topic. Here's a practical progression for vibe coders:
Start Here (Do These First)
- Install Playwright in your project:
npm init playwright@latest— follow the prompts, accept the defaults - Run the example tests:
npx playwright test— see what a passing test looks like - Try codegen:
npx playwright codegen http://localhost:3000— record a flow through your own app - Ask Claude to write tests: "Write Playwright tests for [feature name]" — run them, fix what fails
Then Go Deeper
- Playwright UI Mode (
npx playwright test --ui) — a visual interface for running and debugging tests, great for vibe coders - Test fixtures — reusable setup code so you don't repeat yourself in every test
- API testing — Playwright can also test REST APIs directly, not just browser UIs. Useful if your AI also builds backend APIs
- Page Object Model — a pattern for organizing tests that makes large test suites manageable
- Authentication helpers — storing login state so tests don't have to re-authenticate every time
Useful Resources
- playwright.dev — the official docs, actually quite good
- Getting Started guide — walks you through installation and your first test
- The Playwright VS Code extension — adds a test runner and debugger directly in your editor
And if you're worried about security as your test suite grows and your dependencies pile up — worth reading about dependency scanning to make sure the packages your AI installs aren't introducing vulnerabilities.
FAQ
What is Playwright used for?
Playwright is used for end-to-end browser testing. It automates a real browser — Chrome, Firefox, or Safari — to click through your web app the same way a real user would. It catches bugs that unit tests miss, like buttons that exist in the code but don't actually work when clicked. It's also used for web scraping and browser automation, but its primary job in most codebases is testing.
Do I need to know coding to use Playwright?
Not much. The codegen command lets you record your browser actions and Playwright writes the test code for you. You can also ask Claude or Cursor to write Playwright tests — describe what you want tested, paste the generated code into your project, run it, and paste errors back to your AI when things fail. You don't need to understand every line of Playwright syntax to use it effectively.
Is Playwright free?
Yes, completely. Playwright is free and open source, maintained by Microsoft. You install it with npm install @playwright/test and it downloads the browsers it needs automatically. There's no paid tier, no usage limits, no sign-up required. Microsoft Playwright Testing is a separate paid cloud service for running large test suites at scale — but you don't need it to get started, and most vibe coders never will.
Playwright vs Cypress — which should I use?
For new projects in 2026, Playwright is generally the better choice. It tests Chrome, Firefox, and Safari from one codebase, runs tests faster in parallel, supports more languages, and is what most AI coding tools like Claude and Cursor write by default. Cypress still works great and has a slightly gentler learning curve for pure beginners — but if your AI is already writing Playwright tests, just roll with Playwright.
How do I run Playwright tests?
Run npx playwright test in your project's terminal. Playwright finds your .spec.ts files automatically and runs them. Add --ui for a visual dashboard where you can run tests individually, or --headed to watch the browser open and click through your app in real time. If a test fails, Playwright shows you exactly which assertion failed and why — copy that error and paste it to Claude.