Automate the Web with Playwright
Build a Playwright toolkit spanning testing, scripting, and AI agent control.
Introduction
30 Second Summary
Every time you manually click through a website to check if a form works or to copy data from a page, you are doing something a script could handle in seconds.
In this project, you will use Playwright to automate browsers in three distinct ways. You will write end-to-end tests that validate a web app, build a data extraction script, and connect an AI agent that drives a live browser through natural language.
What You'll Build
You will type a plain English sentence into Claude Desktop and watch it open a browser, click through a real web app, and report what it finds, all powered by the same Playwright engine behind your test suite and scripts.
By the end of this project, you'll have:
- A Playwright test suite that you run with one command, watching it create, complete, and filter todos across Chromium, Firefox, and WebKit.
- A standalone Node.js automation script that opens a headless browser, pulls data from a live web page, and saves structured results to a JSON file you can inspect.
- Claude Desktop connected to the Playwright MCP server, letting you navigate web pages and interact with elements by typing natural language instead of writing code.
- Secret Mission: Have Claude perform a complex browser workflow and generate a reusable Playwright test from the session.
Do I need any experience before starting?
This project assumes basic JavaScript knowledge and familiarity with the command line. You will also need Node.js, VS Code, and Claude Desktop installed on your machine before starting.
Before We Start
Before we dive in, let's lock in what you're about to build and why it matters.
This project covers three distinct ways to use the same tool. Taking a moment to understand all three upfront helps you see how each piece connects as you build.
Set Up Playwright
You know what you're building and why. Now it's time to get your hands on the tool that makes it all possible.
Playwright is a browser automation framework that can drive Chromium, Firefox, and WebKit from a single API. In this step, you'll install Playwright, run its built-in example test across all three browsers, and explore the HTML test report.
In this step, get ready to:
- Create a new Playwright project using the official scaffolding command.
- Run the example test across Chromium, Firefox, and WebKit.
- View the HTML test report dashboard.
Create your Playwright project
Every Playwright project starts with a single scaffolding command that installs the test framework, downloads browser binaries, and generates starter files. You'll run this inside a new folder on your Desktop.
- Open your terminal. 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.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir playwright-demo
cd playwright-demo
- Initialize your Playwright project by running:
npm init playwright@latest
What will this command ask me?
The scaffold wizard will present four prompts. Answer them exactly as follows:
- Choose between TypeScript or JavaScript: select JavaScript.
- Name of your tests folder: press Enter to accept the default tests folder.
- Add a GitHub Actions workflow: enter false to skip this.
- Install Playwright browsers: press Enter to accept the default (yes). This downloads Chromium, Firefox, and WebKit binaries.
The browser download may take a minute or two depending on your internet connection. When it finishes, you'll see a success message in the terminal.
- Confirm the scaffold created the expected files by running:
ls
You should see playwright.config.js, package.json, package-lock.json, and a tests folder in the output.
Don't see these files?
Make sure you ran npm init playwright@latest from inside the playwright-demo folder on your Desktop. If the command failed mid-way, delete the folder and start fresh.
Help me troubleshoot my Playwright installation
Open the project in VS Code and run the example test
The scaffold created a starter test file that verifies Playwright is wired up correctly. Before writing your own tests, you'll run this one to prove the tool works on your machine.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type VS Code and press Enter to open it.
- In VS Code, click File in the top menu bar.
- Click Open Folder.
- Select the playwright-demo folder on your Desktop, then click Open.
In the Explorer sidebar, you'll see the files the scaffold created. Take a look at the key ones:
- playwright.config.js is the central configuration file. It tells Playwright which browsers to test against (Chromium, Firefox, WebKit), how many retries to allow, and what reporter to use.
- tests/example.spec.js is the starter test. It navigates to the Playwright website and checks the page title.
- package.json lists @playwright/test as a dev dependency.
What is a test config?
The playwright.config.js file is like a control panel for your test suite. It defines three browser "projects" (Chromium, Firefox, WebKit), so every test you write automatically runs across all three browsers without any extra work.
Now let's run the example test to make sure everything is working. Before you do, think about this: the config targets three browsers. How many test results do you expect to see?
- Open the integrated terminal in VS Code by pressing Ctrl+` (backtick key, same on macOS and Windows).
- Run the example test by running:
npx playwright test
You should see output showing the test passed across all three browsers. Look for a line like "3 passed" confirming Chromium, Firefox, and WebKit all succeeded.
Seeing test failures?
If one or more browsers fail, check that you selected "yes" to install browsers during the scaffold step. You can install them manually by running:
npx playwright install
Help me fix my failing Playwright tests
View the HTML test report
Playwright generates an HTML report after every test run. This report gives you a visual dashboard where you can filter by browser, see pass/fail status, and drill into individual test steps.
- Open the HTML report by running:
npx playwright show-report
Your browser opens with the Playwright HTML report. You should see a dashboard listing the example test with results for each browser (Chromium, Firefox, WebKit), all showing a green passed status.
What can I do in this report?
The HTML report is filterable by browser and test status (passed, failed, skipped, flaky). You can click on any test to see its individual steps, timing, and any errors. For now, everything should be green.
When you're done exploring, go back to your terminal and press Ctrl+C to stop the report server.
Report didn't open?
The report auto-opens only when tests fail. Since all tests passed, you may need to manually navigate to the URL shown in the terminal (usually http://localhost:9323).
Help me open the Playwright HTML report
Your Playwright project is ready to go. You've got a working installation, three browsers at your command, and a visual report to track your results. Next up, you'll write your own tests against a real web application.
Write Your First Real Tests
Your Playwright project is set up and the example test passed across three browsers. The example test proved the tool works, but it tests someone else's site.
In this step, you will write your own tests against the TodoMVC demo app. This teaches you the three building blocks of every Playwright test: locators (finding elements), actions (interacting with them), and assertions (verifying the result).
In this step, get ready to:
- Write a test that adds three todo items and verifies they appear in the list.
- Write a test that marks a todo as completed and verifies the CSS class changes.
- Run both tests and confirm they pass.
Create the test file and add your first test
The TodoMVC demo app is a simple todo list hosted by the Playwright team at https://demo.playwright.dev/todomvc/. It lets you add, complete, and filter todos. Your first test will add three todos and check they all appear.
- In VS Code, right-click the tests folder in the Explorer sidebar.
- Click New File.
- Name the file todo.spec.js and press Enter.
- Paste the following code into todo.spec.js:
const { test, expect } = require('@playwright/test');
test.describe('TodoMVC', () => {
// Navigate to the TodoMVC demo app before each test
test.beforeEach(async ({ page }) => {
await page.goto('https://demo.playwright.dev/todomvc/');
});
test('should add three todo items', async ({ page }) => {
// Locate the todo input by its placeholder text
const input = page.getByPlaceholder('What needs to be done?');
// Type each todo and press Enter to submit
await input.fill('Buy groceries');
await input.press('Enter');
await input.fill('Clean house');
await input.press('Enter');
await input.fill('Read a book');
await input.press('Enter');
// Assert that the list now contains exactly three items
const todoItems = page.getByTestId('todo-item');
await expect(todoItems).toHaveCount(3);
});
});
What does this code do?
This file sets up a test suite for the TodoMVC demo app. Here is what each piece does:
- test.describe groups related tests together under the name "TodoMVC".
- test.beforeEach navigates to the TodoMVC URL before every test runs, so each test starts on a fresh page.
- getByPlaceholder('What needs to be done?') is a locator. It finds the input field by matching its placeholder text, the same way a user would spot it on the page.
- fill and press('Enter') are actions. They type text into the input and submit each todo.
- toHaveCount(3) is a web-first assertion. It automatically retries until the page has exactly three todo items, instead of checking once and failing if the DOM has not updated yet.
- Save todo.spec.js.
- Run this first test against Chromium by running the following command in your terminal:
npx playwright test tests/todo.spec.js --project=chromium
You should see 1 passed in the terminal output. Your first real Playwright test is working.
Test failing or file not found?
- Make sure the file is saved as todo.spec.js (not .ts) inside the tests folder, not the project root.
- Check that the require('@playwright/test') import at the top of your file matches exactly. A missing @playwright/ prefix will cause a module-not-found error.
- Confirm you are running the command from your project folder (the one containing package.json).
Help me debug a failing Playwright test in todo.spec.js
Add a test for completing a todo
Adding todos is only half the story. A real todo app lets you mark items as done. Your second test will create a todo, click its checkbox, and verify the item is visually marked as completed.
- In tests/todo.spec.js, scroll to the bottom of the file.
- Find the closing }); of the first test (the line before the final }); that closes the test.describe block).
- Add the following second test below it, still inside the test.describe block (before its final });):
test('should mark a todo as completed', async ({ page }) => {
// Add a single todo item
const input = page.getByPlaceholder('What needs to be done?');
await input.fill('Buy groceries');
await input.press('Enter');
// Find the todo and click its checkbox
const todoItem = page.getByTestId('todo-item');
const checkbox = todoItem.getByRole('checkbox');
await checkbox.click();
// Assert the item now has the 'completed' CSS class
await expect(todoItem).toHaveClass(/completed/);
});
What does this test do?
- getByTestId('todo-item') finds the todo list item using its data-testid attribute. This is a stable selector that does not break when CSS classes or layout change.
- getByRole('checkbox') finds the toggle checkbox the same way a screen reader would, by its accessibility role. This makes the test resilient to HTML structure changes.
- toHaveClass(/completed/) uses a regular expression to check that the completed CSS class was added to the todo item after clicking the checkbox.
- Save todo.spec.js.
Run the full test suite
Both tests are in place. Before you run them, think about what you expect to see. You have two tests in one file. Do you think both will pass on the first try?
- Run both tests against Chromium by running this command:
npx playwright test tests/todo.spec.js --project=chromium
You should see 2 passed in the terminal output. Both tests ran against the live TodoMVC demo and verified the results automatically.
Why only Chromium?
The --project=chromium flag runs tests in one browser instead of all three. This keeps feedback fast while you are writing and iterating. You can drop the flag anytime to run against Chromium, Firefox, and WebKit together.
Seeing a failure on the second test?
- Make sure the second test is inside the test.describe block, not after its closing });. Tests outside the describe block will not use the beforeEach hook and will fail to navigate to the page.
- Check that toHaveClass(/completed/) uses forward slashes (a regular expression), not quote marks. Quotes would match the exact string "completed" rather than checking if the class list contains it.
- Verify you have exactly one }); closing each test and one final }); closing the describe block. Mismatched braces are the most common cause of syntax errors.
Help me fix my failing Playwright test
✔️ Awesome, I've got everything!
Great. Double check you have saved tests/todo.spec.js and both tests pass.
ⓧ I'd like to double check the full code
Your tests/todo.spec.js file should contain three main sections. Scroll up through this step and confirm each one is present in your file, in this order:
- The require import at the top, plus the test.describe wrapper and test.beforeEach hook.
- The first test, 'should add three todo items', with three fill/press pairs and a toHaveCount(3) assertion.
- The second test, 'should mark a todo as completed', with a checkbox click and a toHaveClass(/completed/) assertion.
- A final }); closing the test.describe block at the bottom of the file.
You now have two tests that create and complete todos on a real web app. Next up, you will push these tests further by adding filtering. You will also discover why Playwright's auto-waiting assertions matter when the DOM updates asynchronously.
Hit the Wall and Fix It
Your two tests prove that Playwright can create todos and mark them complete. The next challenge is testing what happens when you filter the list.
The TodoMVC app has filter buttons that show only active or completed items. Testing these filters exposes a common trap in browser automation: the UI updates asynchronously, and a test that reads the DOM too early gets stale data.
In this step, get ready to:
- Write a filter test that uses a naive assertion approach.
- Replace the naive assertion with Playwright's web-first assertions.
- Run the full test suite and confirm all tests pass reliably.
Write the naive filter test
You want to verify that clicking the "Active" filter hides completed todos. The most intuitive approach is to grab the text of the visible todo items and compare them directly.
- In VS Code, switch back to tests/todo.spec.js.
- Find the closing }); of the 'should mark a todo as completed' test.
- Add this new test below it, still inside the test.describe block:
test('should filter active todos', async ({ page }) => {
const input = page.getByPlaceholder('What needs to be done?');
// Create two todos
await input.fill('Buy groceries');
await input.press('Enter');
await input.fill('Clean house');
await input.press('Enter');
// Complete the first todo
const firstTodo = page.getByTestId('todo-item').nth(0);
const firstCheckbox = firstTodo.getByRole('checkbox');
await firstCheckbox.click();
// Click the Active filter link
await page.getByRole('link', { name: 'Active' }).click();
// Grab the todo texts right now and compare
const todoTexts = await page.getByTestId('todo-item').allTextContents();
expect(todoTexts).toEqual(['Clean house']);
});
What does this test do?
This test creates two todos, completes "Buy groceries" by clicking its checkbox, then clicks the Active filter link. The filter should hide completed items, leaving only "Clean house" visible.
The last two lines use allTextContents() to grab the text of all visible todo items at that instant, then expect(...).toEqual(...) to compare the result. This is the naive approach.
- Save tests/todo.spec.js.
Before you run this test, what do you think will happen? Will the assertion correctly catch only "Clean house" after filtering?
- Run the new test by running this command:
npx playwright test tests/todo.spec.js --project=chromium
The test likely passes. It looks correct. So where is the wall?
This test is a ticking time bomb
Look at the assertion lines. allTextContents() grabs a one-time snapshot of the DOM. It does not retry. The plain expect(todoTexts).toEqual(...) check runs immediately on whatever data came back.
If the filter has not finished re-rendering when that line runs, allTextContents() returns stale data and the test fails. On this fast demo app it happens to pass. On a real app with network requests, database queries, or complex state management, this exact pattern causes flaky tests that pass on Monday and fail on Tuesday with no code change.
The Playwright docs call this out directly: non-retrying assertions can lead to flaky tests. Prefer auto-retrying web-first assertions whenever possible.
Replace the naive assertion with web-first assertions
Playwright's web-first assertions solve this by automatically retrying until the condition is met or a timeout is reached. Instead of reading the DOM once and hoping, the assertion keeps re-checking.
- In tests/todo.spec.js, scroll down to the 'should filter active todos' test.
- Find these two lines at the bottom of the test:
// Grab the todo texts right now and compare
const todoTexts = await page.getByTestId('todo-item').allTextContents();
expect(todoTexts).toEqual(['Clean house']);
- Delete those three lines (the comment and the two assertion lines) and replace them with the following:
// Web-first assertion - waits for the URL to update
await expect(page).toHaveURL(/.*#\/active/);
// Web-first assertions - auto-retry until the DOM matches
const visibleTodos = page.getByTestId('todo-item');
await expect(visibleTodos).toHaveCount(1);
await expect(visibleTodos).toHaveText(['Clean house']);
Why are web-first assertions better?
- toHaveURL(/.*#\/active/) waits until the page URL matches the regex. This confirms the filter navigation actually happened before checking anything else.
- toHaveCount(1) retries until exactly one todo item is visible. If the DOM is still mid-update, it waits up to 5 seconds instead of failing instantly.
- toHaveText(['Clean house']) retries until the visible item's text matches. Each of these assertions auto-retries, making the test resilient to timing.
Notice the await keyword before every expect. Web-first assertions are asynchronous. The await is what tells Playwright to keep retrying. Without it, the assertion runs once and returns immediately.
- Save tests/todo.spec.js.
Seeing a syntax error?
- Make sure the new assertions are still inside the test function's curly braces.
- Check that you deleted all three lines of the old assertion (the comment, the const todoTexts line, and the expect(todoTexts) line).
- Verify the regex /.*#\/active/ has a backslash before the forward slash.
Help me fix a syntax error in my Playwright filter test
Run the full test suite
With the web-first assertions in place, all three tests should pass reliably. This time, you will run without the --project flag so the tests execute across all three browsers.
Before you run this, do you expect all three tests to pass across Chromium, Firefox, and WebKit?
- Run the full test suite by running this command:
npx playwright test tests/todo.spec.js
You should see all three tests pass across all three browsers. Nine total checks, all green.
Some tests failing?
- Run npx playwright show-report to open the HTML report and see which specific test failed on which browser.
- If the filter test fails, confirm you are using await expect(...) (not expect(...) without await) for every web-first assertion.
- If a different test fails, check that your test.beforeEach block is still navigating to https://demo.playwright.dev/todomvc/.
Help me debug my failing Playwright tests
✔️ Awesome, I've got everything!
Great. Double check you have saved tests/todo.spec.js.
ⓧ I'd like to double check the full code
Your tests/todo.spec.js file is now over 30 lines, so scroll up through your file and confirm each piece is present in this order:
- The require('@playwright/test') import at the top.
- A test.describe('TodoMVC', ...) block wrapping everything.
- A test.beforeEach that navigates to https://demo.playwright.dev/todomvc/.
- Test 1: 'should add three todo items' with three fill/press pairs and a toHaveCount(3) assertion.
- Test 2: 'should mark a todo as completed' with a checkbox click and toHaveClass(/completed/) assertion.
- Test 3: 'should filter active todos' with two todos, a checkbox click, a filter click, and three web-first assertions: toHaveURL, toHaveCount(1), toHaveText(['Clean house']).
You just experienced the core reason Playwright's web-first assertions exist. Next up, you will leave the test runner behind entirely and use Playwright as a standalone library to extract real data from a web page.
Switch to Library Mode
Your test suite is solid. You have three reliable tests that create, complete, and filter todos with web-first assertions that never flake.
Tests are great for validating behavior, but what if you want to automate a browser for a completely different reason? Scraping data from a page, filling out a form programmatically, or generating a report. In this step, you will use Playwright as a plain Node.js library to write a standalone automation script with no test runner involved.
In this step, get ready to:
- Install the Playwright library package alongside your existing test setup.
- Write a standalone script that automates a browser without a test runner.
- Extract structured data from a web page and save it as a JSON file.
Install the Playwright library and create the script
So far you have been using @playwright/test, the test-runner package. Playwright also ships a separate playwright library package that gives you the same browser automation API without a test runner, assertions, or fixtures.
- Install the Playwright library package by running this command in your terminal:
npm install playwright
You should see a short output confirming the packages were added and an audit summary with no vulnerabilities.
Seeing permission errors?
Make sure your terminal is open inside your Playwright project folder. You can confirm by checking that package.json exists in the current directory.
Help me debug my npm install error.
What is the Playwright library package?
Playwright has two separate packages on npm. @playwright/test is the full test-runner package with built-in assertions, fixtures, and parallel execution. playwright is the standalone library that gives you the browser automation API with nothing else.
You use the library when you want to script a browser from any Node.js program. No test structure required.
Now create the script file that will use this library.
- In VS Code, click File in the top menu bar.
- Click New File.
- Name the file scrape.js and save it in the root of your project folder (the same level as package.json).
- Set up the script scaffold by pasting this code into scrape.js:
const { chromium } = require('playwright');
const fs = require('fs');
(async () => {
// Launch a headless Chromium browser
const browser = await chromium.launch();
const page = await browser.newPage();
// Navigate to the TodoMVC demo app
await page.goto('https://demo.playwright.dev/todomvc/');
// Close the browser
await browser.close();
})();
What does this code do?
- const { chromium } = require('playwright') imports the Chromium browser engine from the library package (not from @playwright/test).
- The entire script is wrapped in an async IIFE (async () => { ... })() because Playwright's API is asynchronous. This pattern lets you use await at the top level.
- chromium.launch() starts a headless Chromium browser. Unlike Playwright Test, you manage the browser lifecycle yourself.
- browser.close() shuts down the browser when the script finishes. Forgetting this leaves a zombie process running.
- Save scrape.js.
- Run the script to verify the scaffold works by running this command:
node scrape.js
The script should exit silently with no errors. It launched a headless browser, navigated to the TodoMVC page, and closed. No output is expected yet.
Seeing a "Cannot find module" error?
Check that you ran npm install playwright (not @playwright/test). The library package is a separate install from the test runner.
Also confirm that scrape.js is saved in the same folder as package.json.
Help me fix my scrape.js module error.
Add todos and extract data from the page
The scaffold navigates to the TodoMVC page but does nothing with it. Your next chunk populates the page with todos and marks one as completed, the same interactions your tests perform but written as a plain script.
- In scrape.js, find this line:
// Close the browser
await browser.close();
- Add the following code above that line to create todos and mark the first one completed:
// Locate the todo input field
const input = page.getByPlaceholder('What needs to be done?');
// Add three todo items
const todos = ['Buy groceries', 'Clean house', 'Read a book'];
for (const todo of todos) {
await input.fill(todo);
await input.press('Enter');
}
// Mark the first todo as completed
const firstCheckbox = page.getByTestId('todo-item').nth(0).getByRole('checkbox');
await firstCheckbox.click();
What does this code do?
- page.getByPlaceholder('What needs to be done?') locates the input field the same way your tests did. The Playwright library exposes the exact same locator API as Playwright Test.
- The for...of loop fills and submits each todo. In your tests, you wrote each fill/press pair manually. A script can loop since there are no test boundaries to worry about.
- The checkbox click marks "Buy groceries" as completed so the extracted data includes both completed and active items.
- Save scrape.js and run the script again:
node scrape.js
The script should exit cleanly again with no errors. It created three todos and completed one behind the scenes in the headless browser. No visible output yet because you have not told it to print anything.
Now add the extraction logic. This is where the script does something your tests never did: it pulls structured data out of the page.
- In scrape.js, find this line again:
// Close the browser
await browser.close();
- Add the following code above that line to extract the todo data:
// Extract todo text and completion status from the DOM
const results = await page.getByTestId('todo-item').evaluateAll((items) => {
return items.map((item) => ({
text: item.querySelector('label').textContent,
completed: item.classList.contains('completed'),
}));
});
What does evaluateAll do?
evaluateAll finds all elements matching the locator, then runs a callback function directly inside the browser. The callback receives the raw DOM elements, not Playwright locator objects.
Inside the callback, item.querySelector('label').textContent grabs the todo's display text. item.classList.contains('completed') checks whether the item has been marked done. The callback returns a plain JavaScript array that Playwright sends back to your Node.js script.
Write the results to a file and verify the output
The extracted data is sitting in the results variable. The final piece writes it to a JSON file so you have a permanent record of the scrape.
- In scrape.js, find this line:
// Close the browser
await browser.close();
- Add the following code above that line to write the results and log them:
// Save results to a JSON file
fs.writeFileSync('results.json', JSON.stringify(results, null, 2));
console.log('Saved results to results.json:');
console.log(results);
What does this code do?
- fs.writeFileSync writes the data synchronously to results.json in your project folder.
- JSON.stringify(results, null, 2) converts the JavaScript array into a nicely indented JSON string. The 2 adds two-space indentation so the file is human-readable.
- The two console.log calls print the results directly to your terminal so you can see them immediately.
- Save scrape.js.
Before you run the script, think about what you expect to see. The script creates three todos, marks the first one completed, then extracts their text and status. What should the output look like?
- Run the script:
node scrape.js
You should see an array of three objects printed to your terminal. "Buy groceries" has completed: true. "Clean house" and "Read a book" have completed: false.
- Confirm the JSON file was created by running:
cat results.json
You should see the same three objects formatted as indented JSON, with each item containing a text field and a completed field.
Seeing an empty array or wrong data?
Make sure your extraction code uses getByTestId('todo-item') and not a CSS selector. The TodoMVC app uses data-testid="todo-item" on its list items.
Check that the extraction code appears after the todo creation loop and the checkbox click. If it runs before the todos exist, the array will be empty.
Help me debug my scrape.js output.
✔️ Awesome, I've got everything!
Great. Double check you have saved scrape.js and confirmed results.json exists with three todo items.
ⓧ I'd like to double check the full code
Here is the complete scrape.js file. Compare it to yours line by line:
const { chromium } = require('playwright');
const fs = require('fs');
(async () => {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://demo.playwright.dev/todomvc/');
const input = page.getByPlaceholder('What needs to be done?');
const todos = ['Buy groceries', 'Clean house', 'Read a book'];
for (const todo of todos) {
await input.fill(todo);
await input.press('Enter');
}
const firstCheckbox = page.getByTestId('todo-item').nth(0).getByRole('checkbox');
await firstCheckbox.click();
const results = await page.getByTestId('todo-item').evaluateAll((items) => {
return items.map((item) => ({
text: item.querySelector('label').textContent,
completed: item.classList.contains('completed'),
}));
});
fs.writeFileSync('results.json', JSON.stringify(results, null, 2));
console.log('Saved results to results.json:');
console.log(results);
await browser.close();
})();
You now have two Playwright skills: writing structured tests and scripting with the library. Next up, you will take your hands off the keyboard entirely and let Claude drive the browser for you.
Connect Claude to a Browser with Playwright MCP
In the previous step, you wrote a standalone script that launches a browser, fills in todos, and extracts data. Every line of that automation was written by hand.
The Playwright MCP server flips this workflow. Instead of writing code, you describe what you want in natural language and Claude drives the browser for you. Claude reads pages through accessibility snapshots and interacts with elements by referencing their IDs. The same Playwright engine powers everything under the hood.
In this step, get ready to:
- Configure the Playwright MCP server in Claude Desktop.
- Watch Claude navigate to a web page and add todo items through natural language.
- Have Claude complete a todo, filter the list, and take a screenshot.
Add the Playwright MCP server to Claude Desktop
The Model Context Protocol (MCP) lets AI assistants connect to external tools through a standardized interface. The Playwright MCP server gives Claude direct browser access. Claude reads each page as a structured tree of interactive elements (an accessibility snapshot) and performs actions by referencing element IDs.
- Open Claude Desktop.
- Click the Claude menu in your system menu bar (macOS) or the hamburger menu (Windows).
- Select Settings.
- Click the Developer tab in the left sidebar.
- Click Edit Config to open the configuration file in your default text editor.
Where is this config file?
The Edit Config button opens claude_desktop_config.json automatically. On macOS, this file lives at ~/Library/Application Support/Claude/claude_desktop_config.json. On Windows, it lives at %APPDATA%\Claude\claude_desktop_config.json.
If the file does not exist yet, clicking Edit Config creates it for you.
- Replace the contents of the file with the following JSON:
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest"]
}
}
}
What does this config do?
This tells Claude Desktop to start a Playwright MCP server whenever you launch the app. "command": "npx" uses Node.js to run the server. "@playwright/mcp@latest" points to the Playwright MCP package.
The server launches in headed mode by default, meaning you will see a real browser window open when Claude interacts with a page.
- Save the file and close the text editor.
- Fully quit Claude Desktop. On macOS, use Cmd+Q. On Windows, right-click the Claude icon in the system tray and select Quit.
- Relaunch Claude Desktop.
Claude Desktop needs a full restart to load new MCP server configurations. Simply closing the window is not enough.
Server not loading after restart?
- Double-check your JSON for syntax errors. A missing comma or bracket will silently prevent the server from loading.
- Make sure Node.js is available to Claude Desktop. GUI apps on Windows sometimes cannot find npx because they do not load your shell profile. If you see path-related errors in the Claude logs, try using the full path to npx in the command field instead.
- On Windows, if you see errors referencing ${APPDATA}, add an env key to your config with the expanded APPDATA path (e.g. C:\Users\yourname\AppData\Roaming).
Still stuck? Help me troubleshoot the Playwright MCP server not connecting in Claude Desktop.
Ask Claude to navigate and add todos
With the MCP server loaded, Claude can now control a browser directly. You give instructions in plain English and Claude translates them into Playwright actions using the page's accessibility snapshot.
- In Claude Desktop, start a new conversation.
- Type the following message and send it:
Navigate to https://demo.playwright.dev/todomvc/ and add three todo items: Buy groceries, Clean house, and Read a book.
A browser window opens in headed mode. Watch as Claude reads the page's accessibility snapshot, locates the input field, types each todo, and presses Enter after each one.
What are accessibility snapshots?
Instead of looking at screenshots, Claude reads a structured tree of every interactive element on the page. Each element gets a unique reference ID (like ref=e5 or ref=e10). Claude uses these refs to click, type, and interact with specific elements deterministically.
This is fundamentally different from screenshot-based approaches. Accessibility snapshots are precise and structured, so Claude knows exactly which element to target.
- Confirm that the browser shows three todo items: Buy groceries, Clean house, and Read a book.
Browser not opening?
- If Claude responds with text but no browser opens, the MCP server may not have loaded. Go back to Settings > Developer and confirm the Playwright server appears in the list.
- Claude may ask for permission to use the Playwright tools the first time. Approve the tool use when prompted.
Help me figure out why Claude is not opening a browser when I ask it to navigate to a page.
Complete a todo, filter the list, and take a screenshot
Now try a more complex interaction. You will ask Claude to do the same workflow you tested manually in Step 3: complete a todo, filter by active items, and verify the result.
- In the same Claude Desktop conversation, type the following message and send it:
Mark "Buy groceries" as completed, then click the "Active" filter, and take a screenshot to verify only the uncompleted todos are visible.
Before Claude finishes, think about what you expect to see in the screenshot. How many todos should be visible after filtering by "Active"?
Claude clicks the checkbox next to "Buy groceries", clicks the Active filter link, and takes a screenshot. The screenshot appears directly in your chat. You should see two uncompleted todos: Clean house and Read a book.
Compare this to your manual test
In Step 3, you wrote a Playwright test that did the same thing: created todos, completed one, clicked the Active filter, and asserted the visible count. That test was about 20 lines of code.
Here, Claude did the entire workflow from two sentences of English. The same Playwright engine handled both. The difference is who wrote the automation: you or the AI.
Screenshot not appearing?
- If Claude describes what it sees but does not include a screenshot, ask it again explicitly: "Please take a screenshot of the current page."
- Make sure the browser window is still open. If you closed it manually, ask Claude to navigate to the URL again.
Help me get Claude to take a screenshot of the browser through the Playwright MCP server.
Secret mission
Generate a Test from an MCP Session
You've written tests by hand and watched Claude drive a browser. What happens when you combine both? Direct Claude through a complex TodoMVC workflow via MCP, then have it generate a runnable Playwright test from that session.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely on your local machine, so there are no ongoing cloud costs or subscriptions to worry about. Decide whether to keep your resources as they are, pause them to come back later, or delete them entirely.
Resources you used:
- Project folder on your Desktop containing test files, the scrape script, and results.json.
- Playwright browsers (Chromium, Firefox, WebKit) downloaded to a system cache directory.
- Playwright MCP server entry in your Claude Desktop config file (claude_desktop_config.json).
Keep everything running
No action needed. Choose this if you plan to keep experimenting with Playwright tests, writing more automation scripts, or using the MCP server with Claude Desktop.
- Your project folder stays on your Desktop with all test files, scripts, and node_modules intact.
- The Playwright MCP server remains configured in Claude Desktop and will be available in future conversations.
- Downloaded browsers stay cached on your machine and are reused by future Playwright runs.
Pause - I'll come back to this later
Free up disk space by removing cached browsers while keeping your project files for later.
- Remove the cached Playwright browsers by running this command from inside your project folder:
npx playwright uninstall
This frees disk space without touching your test files or scripts. When you come back, run npx playwright install to re-download the browsers.
Delete - I don't want to use this again
Remove all project resources and start fresh. Follow each step below.
- Remove the cached Playwright browsers by running this command from inside your project folder:
npx playwright uninstall
- Remove the Playwright MCP server entry from Claude Desktop.
- Open Claude Desktop.
- Click Settings.
- Select Developer, then click Edit Config.
- Delete the "playwright" block from the "mcpServers" object. If it was your only MCP server, replace the file contents with {}.
- Save the file, then fully quit and relaunch Claude Desktop so the change takes effect.
- Delete the project folder from your Desktop. Open your terminal and run:
macOS
cd ~/Desktop
rm -rf [[PROJECT_FOLDER="your project folder name"]]
Windows
cd ~\Desktop
Remove-Item -Recurse -Force [[PROJECT_FOLDER="your project folder name"]]
Nice Work!
Nice Work!
Nice work! You have built a complete browser automation toolkit that spans all three of Playwright's modes. You went from writing your first test to having an AI agent drive a browser through natural language.
You've learned how to:
- Write end-to-end tests against a live web app using resilient locators (getByRole, getByPlaceholder, getByTestId) and web-first assertions (toHaveCount, toHaveText, toHaveClass), and learned why auto-waiting matters by experiencing and fixing a flaky test.
- Used Playwright as a standalone Node.js library to write an automation script that launches a headless browser, extracts structured data from a web page, and saves it to a JSON file.
- Connected Claude Desktop to the Playwright MCP server, enabling AI-driven browser automation through natural language and structured accessibility snapshots.
- Secret Mission: Had Claude perform a complex multi-step browser workflow and generate a reusable Playwright test from the session.
Ready to quiz yourself?