Playwright Introduction
Installing Playwright Test, running your first cross-browser test, and recording tests with codegen.
What is Playwright?
Playwright is an open-source browser automation and testing framework from Microsoft. One API drives Chromium (Chrome, Edge), Firefox and WebKit (Safari), and its test runner, Playwright Test, includes everything a modern suite needs:
- Auto-waiting — actions wait for elements to be visible, stable and enabled.
- Web-first assertions —
expect(locator)retries until the condition is met. - Parallel execution by default, across files and browsers.
- Isolation — every test gets a fresh browser context (like a new incognito profile) in milliseconds.
- Built-in tools — codegen to record tests, UI mode, and the Trace Viewer for debugging CI failures.
- API testing and network mocking in the same framework.
Playwright has official bindings for TypeScript/JavaScript, Python, Java and .NET. This track uses TypeScript with Playwright Test, the most common setup.
Installing Playwright
mkdir playwright-demo && cd playwright-demo
npm init playwright@latest
The installer asks a few questions (TypeScript, tests folder, GitHub Actions workflow, install browsers) and creates:
tests/
example.spec.ts
playwright.config.ts
package.json
Your first test
Playwright hosts a demo TodoMVC app for practice:
// tests/todo.spec.ts
import { test, expect } from '@playwright/test';
test('adds a todo item', async ({ page }) => {
await page.goto('https://demo.playwright.dev/todomvc');
const newTodo = page.getByPlaceholder('What needs to be done?');
await newTodo.fill('Write my first Playwright test');
await newTodo.press('Enter');
await expect(page.getByTestId('todo-title')).toHaveText(['Write my first Playwright test']);
await expect(page.getByTestId('todo-count')).toContainText('1 item left');
});
test('completes a todo', async ({ page }) => {
await page.goto('https://demo.playwright.dev/todomvc');
await page.getByPlaceholder('What needs to be done?').fill('Buy milk');
await page.getByPlaceholder('What needs to be done?').press('Enter');
await page.getByTestId('todo-item').getByRole('checkbox').check();
await expect(page.getByTestId('todo-item')).toHaveClass(/completed/);
});
The page argument is a fixture: Playwright creates a fresh page for each test and closes it afterwards.
Running tests
npx playwright test # all tests, all configured browsers, headless
npx playwright test tests/todo.spec.ts # one file
npx playwright test --project=chromium # one browser
npx playwright test --headed # watch the browser
npx playwright test --ui # interactive UI mode (great for debugging)
npx playwright show-report # open the HTML report
By default the config runs every test in Chromium, Firefox and WebKit — three browsers from one test.
The config file
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
retries: process.env.CI ? 2 : 0,
reporter: 'html',
use: {
baseURL: 'https://demo.playwright.dev',
trace: 'on-first-retry', // record a trace when a test is retried
screenshot: 'only-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
{ name: 'mobile', use: { ...devices['Pixel 7'] } },
],
});
With baseURL set, tests can call page.goto('/todomvc').
Recording tests with codegen
Playwright can write a test while you click around:
npx playwright codegen https://demo.playwright.dev/todomvc
A browser opens alongside an inspector that generates code — including good locators — for every action. Codegen is excellent for learning locators and for scaffolding; always review and clean up the generated test before committing it.
Python version
The same test in Python with pytest-playwright:
pip install pytest-playwright
playwright install
# test_todo.py — run with: pytest
import re
from playwright.sync_api import Page, expect
def test_adds_a_todo(page: Page):
page.goto("https://demo.playwright.dev/todomvc")
page.get_by_placeholder("What needs to be done?").fill("Learn Playwright")
page.get_by_placeholder("What needs to be done?").press("Enter")
expect(page.get_by_test_id("todo-title")).to_have_text(["Learn Playwright"])
expect(page.get_by_test_id("todo-count")).to_have_text(re.compile("1 item left"))
Next: why Playwright tests rarely need waits — Locators & Auto-Waiting.