Playwright Complete Guide 2026: Master Automation Testing (Free)
The complete Playwright tutorial for 2026 — install, write tests, locators, assertions, POM, CI, API + UI, visual testing, parallel runs, debugging. Code examples in TS & Python.

If you're a QA engineer looking to modernize your test automation stack, Playwright is the tool you've been waiting for. Developed by Microsoft and open-sourced in 2020, Playwright is a Node.js library that enables fast, reliable, and cross-browser end-to-end testing for modern web applications.
This Playwright testing tutorial takes you from zero knowledge to a production-ready test suite — whether you're migrating from Selenium, leaving Cypress for something more powerful, or building your automation framework from scratch. Stuck on a flaky test as you read? Drop the question in our QA Network feed — 11K+ testers, SDETs and automation engineers reply daily, and you can grab a referral inside the QA-only community.
Want guided practice after this guide? The Playwright course with TypeScript turns every topic here into 16 sections with hands-on labs and assessments, and you can try the first lessons free.
1. What Is Playwright? An Introduction for QA Engineers
Playwright is a full browser automation framework — not just a test runner. It supports Chromium (Chrome, Edge), Firefox, and WebKit (Safari) from a single unified API. Unlike older tools, it was built for the modern web: SPAs, JavaScript-heavy frontends, shadow DOM, iframes, service workers, and complex auth flows.
Key takeaway: Playwright gives QA engineers precise control over browsers, network traffic, device emulation, storage state, and more — all from one consistent API.
2. Why Choose Playwright Over Other Testing Frameworks?
- Auto-waiting — eliminates flaky timing issues; no more
sleep()calls. - Cross-browser with a single API — Chromium, Firefox and WebKit without browser-specific workarounds.
- Network interception & mocking — full control over requests, something Selenium can't do natively.
- Multiple contexts & tabs — multi-user and multi-tab journeys in one test.
- First-class TypeScript support — types ship out of the box.
- Trace Viewer — timeline debugging with DOM snapshots, network logs and screenshots.
- Speed — talks directly to browsers via CDP / Firefox / WebKit protocols, bypassing slow WebDriver.
3. Playwright Architecture: How It Works Under the Hood
Playwright talks to browsers over WebSocket connections using native debugging protocols. This gives low-latency command execution, event-driven listeners (page, network, console), and both headed and headless modes.
Your Test Code
│
▼
Playwright Node.js API
│
▼
Browser Channels (CDP / Firefox / WebKit)
│
▼
Real Browser InstancesA BrowserContext is Playwright's equivalent of an incognito profile — fully isolated cookies, localStorage and session state. This is how Playwright enables parallel testing without state bleeding.
4. Installing and Setting Up Playwright
Prerequisites: Node.js 22+ and npm/yarn/pnpm. New to setup? Follow our step-by-step Playwright installation guide for beginners for Windows, Mac and Linux walkthroughs and common-error fixes.
mkdir playwright-qa-project
cd playwright-qa-project
npm init -y
npm init playwright@latestThe interactive CLI asks you to pick TypeScript or JavaScript, the test folder, GitHub Actions workflow, and whether to install browsers. After setup you'll have:
playwright-qa-project/
├── tests/
│ └── example.spec.ts
├── playwright.config.ts
├── package.json
└── .gitignoreA typical playwright.config.ts sets fullyParallel: true, retries on CI, an HTML reporter, a baseURL, trace: 'on-first-retry', screenshots and video on failure, and projects for Chromium, Firefox, WebKit and mobile devices.
Run tests with npx playwright test or open the interactive runner with npx playwright test --ui.
5. Writing Your First Playwright Test
import { test, expect } from '@playwright/test';
test.describe('Homepage Tests', () => {
test('should display the correct page title', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveTitle(/Welcome/);
});
test('should navigate to the About page', async ({ page }) => {
await page.goto('/');
await page.getByRole('link', { name: 'About' }).click();
await expect(page).toHaveURL('/about');
});
});test.describe groups tests, test defines a single case, the destructured page fixture is your interface to the browser tab, and expect() wraps web-first assertions.
6. Playwright Locators: Finding Elements Like a Pro
Use locators in this priority order: role → label → placeholder → text → test ID → CSS/XPath.
// Role-based (recommended)
await page.getByRole('button', { name: 'Submit' }).click();
await page.getByRole('checkbox', { name: 'Remember me' }).check();
// Label-based
await page.getByLabel('Email address').fill('user@example.com');
// Placeholder / text
await page.getByPlaceholder('Search products...').fill('laptop');
await page.getByText('Welcome back!', { exact: true });
// Test ID — stable, intentional
await page.getByTestId('login-submit-btn').click();
// CSS / XPath — last resort
await page.locator('.submit-button').click();Chain locators to scope a search: page.locator('[data-testid="user-card"]').getByRole('button', { name: 'Edit' }).
7. Handling Interactions: Clicks, Forms, and Navigation
// Clicks
await page.getByRole('button', { name: 'Submit' }).click();
await page.getByText('Item').dblclick();
await page.getByText('File').click({ button: 'right' });
// Forms
await page.getByLabel('Username').fill('john_doe');
await page.getByLabel('Country').selectOption('US');
await page.getByLabel('Accept terms').check();
await page.getByLabel('Upload document').setInputFiles('./sample.pdf');
// Keyboard
await page.keyboard.press('Enter');
await page.keyboard.press('Control+A');
// Navigation
await page.goto('/dashboard');
await page.goBack();
await page.waitForURL('/success');Prefer explicit waits like waitForURL, waitForResponse or web-first assertions over waitForTimeout.
8. Assertions in Playwright
Playwright ships web-first assertions that auto-retry until the condition is met or timeout expires.
await expect(page).toHaveURL('/dashboard');
await expect(page).toHaveTitle(/Dashboard/);
await expect(page.getByText('Welcome!')).toBeVisible();
await expect(page.getByTestId('spinner')).toBeHidden();
await expect(page.getByRole('heading')).toHaveText('Dashboard');
await expect(page.getByRole('button', { name: 'Submit' })).toBeEnabled();
await expect(page.getByLabel('Remember me')).toBeChecked();
await expect(page.getByLabel('Email')).toHaveValue('user@example.com');
await expect(page.locator('.product-card')).toHaveCount(12);Soft assertions (expect.soft) collect failures without halting the test — great for asserting many dashboard widgets at once.
9. Working with Multiple Browsers in Playwright
Configure desktop and mobile projects in playwright.config.ts:
projects: [
{ name: 'chrome', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'safari', use: { ...devices['Desktop Safari'] } },
{ name: 'mobile-android', use: { ...devices['Pixel 7'] } },
{ name: 'mobile-ios', use: { ...devices['iPhone 14'] } },
]Run a specific project: npx playwright test --project=firefox.
10. Playwright Fixtures and Test Organization
Fixtures provide dependency injection and shared setup/teardown. Built-in fixtures include page, context, browser and request. Define custom fixtures by extending base:
export const test = base.extend<MyFixtures>({
loggedInPage: async ({ page }, use) => {
await page.goto('/login');
await page.getByLabel('Email').fill('user@example.com');
await page.getByLabel('Password').fill('password123');
await page.getByRole('button', { name: 'Login' }).click();
await page.waitForURL('/dashboard');
await use(page);
},
});Use beforeAll / afterAll for expensive once-per-file setup, and beforeEach / afterEach for per-test state.
11. Page Object Model (POM) with Playwright
POM is the gold-standard pattern for maintainable test automation — encapsulate selectors and actions in reusable classes.
// pages/LoginPage.ts
export class LoginPage {
readonly page: Page;
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly submitButton: Locator;
constructor(page: Page) {
this.page = page;
this.emailInput = page.getByLabel('Email');
this.passwordInput = page.getByLabel('Password');
this.submitButton = page.getByRole('button', { name: 'Login' });
}
async navigate() { await this.page.goto('/login'); }
async login(email: string, password: string) {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.submitButton.click();
}
}Recommended folder structure: tests/, pages/, fixtures/, test-data/, and playwright.config.ts at the root.
12. API Testing with Playwright
Playwright's built-in request fixture handles API testing directly.
test('GET /api/users returns 200', async ({ request }) => {
const response = await request.get('https://api.example.com/users');
expect(response.status()).toBe(200);
const body = await response.json();
expect(body.users.length).toBeGreaterThan(0);
});A powerful pattern: use the API to set up state, then verify via the UI — fast and reliable. You can also mock network calls with page.route() to test error states and loading spinners deterministically.
See our API testing interview guide for deeper coverage.
13. Visual Testing and Screenshot Comparisons
// Visual regression
await expect(page).toHaveScreenshot('homepage.png');
await expect(card).toHaveScreenshot('product-card.png', { maxDiffPixelRatio: 0.01 });Update baselines after intentional UI changes with npx playwright test --update-snapshots.
14. Handling Authentication in Playwright Tests
Strategy 1 — Save and reuse storage state. Log in once in an auth.setup.ts project, call page.context().storageState({ path: 'auth/user.json' }), then point dependent projects at that file via use: { storageState: 'auth/user.json' } with dependencies: ['setup'].
Strategy 2 — API-based login (fastest). Hit your /api/auth/login endpoint, then inject the token into localStorage with page.addInitScript before any page loads — skipping the UI entirely.
15. Parallel Testing and CI/CD Integration
Playwright runs tests in parallel by default. Tune fullyParallel and workers in the config. Use test.describe.configure({ mode: 'serial' }) to force sequential execution where needed.
A minimal GitHub Actions workflow:
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright test
- uses: actions/upload-artifact@v4
if: always()
with: { name: playwright-report, path: playwright-report/ }For large suites, shard across machines: npx playwright test --shard=1/4 through 4/4 in a matrix job.
16. Debugging Playwright Tests
npx playwright test --debug— interactive Inspector to step through actions.npx playwright test --ui— visual runner with timeline and live preview.npx playwright test --headed --slow-mo=500— watch tests run slowly.npx playwright show-trace trace.zip— Trace Viewer with DOM snapshots, network and console.await page.pause()— pause execution mid-test in headed mode.
17. Playwright Best Practices for QA Engineers
Do:
- Use role-based locators — resilient to CSS changes.
- Add
data-testidattributes for complex cases. - Use
npx playwright codegento scaffold tests fast. - Keep tests independent — never rely on execution order.
- Store secrets in environment variables.
- Set sensible global and per-test timeouts.
Don't:
- Use
page.waitForTimeout()— brittle and slow. - Mix selectors with test logic — keep them in Page Objects.
- Ignore HTML reports —
npx playwright show-reportafter every CI run.
For AI-assisted scripting, see our GitHub Copilot for QA guide.
18. Playwright vs Cypress vs Selenium: Final Verdict
| Feature | Playwright | Cypress | Selenium |
|---|---|---|---|
| Language support | JS/TS, Python, Java, C# | JS/TS | Most |
| Browser support | Chromium, Firefox, WebKit | Chromium, Firefox, Edge | All |
| Auto-waiting | Built-in | Built-in | Manual |
| Network mocking | Full | Full | Limited |
| Multi-tab | Yes | Limited | Yes |
| API testing | Built-in | Built-in | No |
| Speed | Fast | Fast | Slower |
Verdict: Playwright wins for enterprise QA teams that need cross-browser coverage, multi-language support and first-class TypeScript. Cypress remains strong for Chrome-only frontend teams. Selenium makes sense only for shops with massive existing Selenium investments.
19. Playwright in Python: Same Power, Different Syntax
Playwright ships first-class Python bindings. Every TypeScript concept above (locators, fixtures, auto-wait, tracing) has a Python equivalent — useful if your team is Django/FastAPI/Flask heavy or you're running data-pipeline tests alongside UI checks.
pip install pytest-playwright
playwright install --with-depsfrom playwright.sync_api import Page, expect
def test_login_flow(page: Page):
page.goto('https://www.softwaretestpilot.com/login')
page.get_by_label('Email').fill('sdet@example.com')
page.get_by_label('Password').fill('SecurePass2026!')
page.get_by_role('button', name='Sign in').click()
expect(page.get_by_role('heading', name='Dashboard')).to_be_visible()
# Async API — better for high-parallel suites
import asyncio
from playwright.async_api import async_playwright, expect
async def check_homepage():
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page()
await page.goto('https://www.softwaretestpilot.com')
await expect(page).to_have_title(/SoftwareTestPilot/)
await browser.close()
asyncio.run(check_homepage())
Fixtures work through pytest — override browser_context_args, page, or write your own with @pytest.fixture. Run parallel with pytest -n auto via pytest-xdist. See the official Playwright for Python docs and pair this with our Python: Selenium vs Playwright comparison.
20. Advanced Network Mocking and HAR Replay
Playwright's page.route() gives full request/response control — no proxy, no MITM cert. Combine with HAR recording to capture real traffic once and replay it deterministically forever.
// Mock a slow API to test loading states
await page.route('**/api/orders', async (route) => {
await new Promise((r) => setTimeout(r, 3000));
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ orders: [] }),
});
});
// Force a 500 to verify error UI
await page.route('**/api/checkout', (route) =>
route.fulfill({ status: 500, body: 'Internal Server Error' })
);
// Record once, replay forever
await page.routeFromHAR('./fixtures/checkout.har', { update: false });
await page.goto('/checkout');
HAR replay is the single biggest flaky-test killer in 2026 — third-party APIs, feature flags, and A/B tests all become deterministic. Regenerate the HAR with update: true when upstream contracts genuinely change.
21. Trace Viewer: The Debugging Superpower
The Trace Viewer is Playwright's killer feature. Every action, network call, console log, and DOM snapshot is captured in a single .zip — openable months later without rerunning the test.
// playwright.config.ts
export default defineConfig({
use: {
trace: 'on-first-retry', // free artifacts on flake
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
});
# Open a trace from CI
npx playwright show-trace playwright-report/data/abc.zip
# Or drag-drop into https://trace.playwright.dev
In CI, upload the playwright-report/ folder as a GitHub Actions artifact — reviewers open the trace in-browser without cloning the repo. This alone has cut our mean-time-to-diagnose from 40 min to under 5.
22. Component Testing with Playwright
Playwright Component Testing (experimental but stable in 2026) mounts React/Vue/Svelte components in a real browser — faster than Cypress CT, and it reuses your existing Playwright config.
// button.spec.tsx
import { test, expect } from '@playwright/experimental-ct-react';
import { Button } from './Button';
test('renders and fires onClick', async ({ mount }) => {
let clicked = false;
const component = await mount(
<Button onClick={() => (clicked = true)}>Save</Button>
);
await component.click();
expect(clicked).toBe(true);
await expect(component).toHaveText('Save');
});
Component tests share fixtures, tracing, and parallelism with your E2E suite — one runner, one report. Use for design-system regression, storybook-style visual checks, and edge-case props E2E can't easily hit.
23. Accessibility Testing with axe-playwright
WCAG 2.2 compliance is table stakes in 2026. @axe-core/playwright runs Deque's axe scanner in every test — catch color contrast, missing labels, and ARIA violations before shipping.
import AxeBuilder from '@axe-core/playwright';
test('homepage has no serious a11y violations', async ({ page }) => {
await page.goto('/');
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa', 'wcag21aa'])
.analyze();
const serious = results.violations.filter((v) =>
['serious', 'critical'].includes(v.impact ?? '')
);
expect(serious).toEqual([]);
});
Wire this into your smoke suite — a single a11y.spec.ts that scans every top-level route in under 30 seconds prevents 90% of regressions.
24. Combining Playwright with k6 for Full-Stack Confidence
Playwright validates user-facing correctness; k6 load-tests the backend those flows depend on. Run them together in CI and you catch both broken UX and 95th-percentile latency regressions in the same pipeline.
# .github/workflows/e2e-and-load.yml
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright test
load:
runs-on: ubuntu-latest
needs: e2e
steps:
- uses: actions/checkout@v4
- uses: grafana/setup-k6-action@v1
- run: k6 run --vus 200 --duration 5m tests/load/checkout.js
Deeper coverage: k6 load testing tutorial, k6 vs JMeter, and performance testing interview questions.
25. Migrating from Selenium to Playwright (Realistic Playbook)
A 500-test Selenium Java suite doesn't move to Playwright over a weekend. Here's what actually works in the field:
- Freeze the Selenium suite — no new tests. Everything new goes into Playwright.
- Rewrite the top 20 smoke tests first — the ones that catch 80% of prod issues. Run both suites in CI for 4 weeks.
- Adopt web-first assertions — every
Thread.sleep(2000)becomesawait expect(...).toBeVisible(). - Rebuild Page Objects around locators, not
WebElement— expose actions, not selectors. - Retire Grid, adopt shards —
--shard=1/4across matrix jobs replaces most Grid deployments. - Delete the Selenium suite only when Playwright coverage ≥ Selenium coverage for 30 days.
Reference material: Playwright vs Selenium honest comparison, Selenium interview questions (for the team you're upskilling), and Playwright interview questions.
26. Mobile and Device Emulation
// playwright.config.ts
import { devices, defineConfig } from '@playwright/test';
export default defineConfig({
projects: [
{ name: 'Mobile Safari', use: { ...devices['iPhone 14 Pro'] } },
{ name: 'Mobile Chrome', use: { ...devices['Pixel 7'] } },
{ name: 'Tablet', use: { ...devices['iPad Pro 11'] } },
],
});This covers mobile web. For native iOS/Android apps you still need Appium — see our Appium.
27. playwright.config.ts Deep Dive
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 4 : undefined,
reporter: [['html'], ['list'], ['junit', { outputFile: 'results.xml' }]],
use: {
baseURL: process.env.BASE_URL ?? 'https://www.softwaretestpilot.com',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
actionTimeout: 10_000,
navigationTimeout: 30_000,
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
],
});Tip: trace: 'on-first-retry' is the sweet spot — no overhead on green runs, full trace the moment a flaky test fails.
28. 10 Advanced Patterns Used by Senior SDETs
- One BrowserContext per persona — buyer vs seller in the same test, zero cross-talk.
- API-seeded UI tests — POST data via
request, then verify in the UI. - Network-first test design — assert what the page sent, not just what it shows.
- Snapshot only stable regions — mask timers, avatars, A/B variants.
- Per-project storage state —
admin.json,user.json,guest.json. - Tagged smoke vs regression —
test('@smoke checkout', ...)+--grep @smokein PR CI. - Soft assertions for dashboards — collect all 12 card failures before failing the test.
- Custom matchers —
expect.extend({ toBeAccessible })with axe-core. - Auto-retries with backoff on third-party flake only (Stripe, OAuth providers).
- Trace artifacts in CI — every red build gets a one-click trace URL in the PR.
29. Top 10 Playwright Mistakes to Avoid
- Sprinkling
page.waitForTimeout()instead of trusting auto-wait. - Using CSS/XPath when
getByRolewould work — brittle and a11y-blind. - Disabling
fullyParallelbecause of shared test data — fix the data, not the runner. - Logging in via the UI in every test — use storage state.
- One mega
spec.tswith 60 tests — split by feature. - Asserting in
beforeEach— failures get attributed to the wrong test. - No
tracein CI — first flake costs an hour of guessing. - Mocking everything — keep at least one true end-to-end happy path.
- Ignoring
--reporter=html— your team will love the timeline. - Not pinning the Playwright version in
package.json— minor bumps occasionally change defaults.
30. Playwright Pros and Cons (Honest Take)
After two large migrations and a dozen smaller ones, here's the no-marketing scorecard I share with engineering managers:
| Dimension | Playwright | Selenium | Cypress |
|---|---|---|---|
| Setup time (zero → first green test) | ~10 min | ~45 min | ~15 min |
| Cross-browser (Chromium, Firefox, WebKit) | ✅ Native | ✅ (Grid) | ⚠️ Chromium + Firefox only |
| Multi-tab / multi-origin in one test | ✅ | ⚠️ Painful | ❌ |
| Auto-waiting | ✅ Best-in-class | ❌ Manual | ✅ |
| Trace / time-travel debugging | ✅ Trace Viewer | ❌ | ✅ (paid for parallelization) |
| Parallel execution out of the box | ✅ Free | ⚠️ Needs Grid | ⚠️ Paid (Dashboard) |
| Language ecosystem | TS/JS, Python, .NET, Java | Java, Python, C#, JS, Ruby, Kotlin | TS/JS only |
| Community size (2026) | Large & fast-growing | Largest (legacy) | Plateauing |
| Mobile native apps | ❌ (use Appium) | ❌ (use Appium) | ❌ |
Where Playwright wins
- Greenfield projects, modern SPAs, teams already on TypeScript.
- Anywhere flaky tests are killing developer trust in CI.
- Teams that need real cross-browser (WebKit / Safari) without paying for a cloud grid.
Where Playwright is the wrong call
- Native mobile-first products — stick with Appium.
- Legacy enterprise stacks where 90% of the team writes Java/Selenium and a rewrite isn't on the roadmap — pick incremental migration.
- Teams that need IE11 / very old browser coverage (regulated industries, public sector). Selenium is still the answer.
For a side-by-side migration plan, see Playwright vs Selenium and the Selenium WebDriver pillar.
31. Playwright Cheat Sheet
| Task | Snippet |
|---|---|
| Open page | await page.goto('/path') |
| Click button | await page.getByRole('button', { name: 'Save' }).click() |
| Fill input | await page.getByLabel('Email').fill('a@b.com') |
| Assert visible | await expect(loc).toBeVisible() |
| Wait for URL | await page.waitForURL('/dashboard') |
| New tab | const [p] = await Promise.all([context.waitForEvent('page'), trigger()]) |
| iFrame | page.frameLocator('#stripe').getByLabel('Card') |
| Upload | await input.setInputFiles('file.pdf') |
| Download | const dl = await page.waitForEvent('download') |
| Run one test | npx playwright test login.spec.ts --headed |
| Debug | npx playwright test --debug |
| Trace | npx playwright show-trace trace.zip |
32. Common Playwright TypeScript Patterns
Pattern 1 — expect with retries
await expect(page.getByText('Welcome')).toBeVisible({ timeout: 10000 });Pattern 2 — Group related tests
test.describe('User authentication', () => {
test.beforeEach(async ({ page }) => { await page.goto('/login'); });
test('valid login', async ({ page }) => { /* ... */ });
test('invalid login', async ({ page }) => { /* ... */ });
});Pattern 3 — Soft assertions
test('check multiple things', async ({ page }) => {
await expect.soft(page.getByTestId('header')).toBeVisible();
await expect.soft(page.getByTestId('footer')).toBeVisible();
});Pattern 4 — Skip tests conditionally
test('mobile-only test', async ({ page, isMobile }) => {
test.skip(!isMobile, 'Mobile only');
});Pattern 5 — Test annotations
test('critical path @smoke', { tag: '@smoke' }, async ({ page }) => {
// ...
});
// npx playwright test --grep "@smoke"33. Production Framework Blueprint
Reference structure that scales past 500 tests:
tests/
e2e/ ← business flows
api/ ← service-layer tests
pages/ ← Page Objects
fixtures/ ← test fixtures + factories
data/ ← seed data and JSON schemas
playwright.config.ts
global-setup.tsFull walkthrough with code in our Playwright framework in TypeScript post.
34. Your First 60 Minutes with Playwright: A Runnable Walk-Through
Reading about Playwright and typing Playwright are different skills. This is the exact 60-minute walkthrough we give new engineers on their first day — verified against Playwright 1.48+ (2026 release line). Every step is copy-pasteable.
Minute 0-10: install and scaffold
mkdir first-playwright && cd first-playwright
npm init -y
npm init playwright@latest
# Accept defaults: TypeScript, tests/ folder, GitHub Actions workflow, Chromium+Firefox+WebKitThe generator writes a playwright.config.ts, a sample tests/example.spec.ts, and (optionally) a .github/workflows/playwright.yml. That is a production-grade skeleton in one command.
Minute 10-30: your first real test
Replace the sample with a test against the public demo site the Playwright team maintains:
import { test, expect } from playwright/test;
test(demo todo - add complete delete, async ({ page }) => {
await page.goto(https://demo.playwright.dev/todomvc);
const input = page.getByPlaceholder(What needs to be done?);
await input.fill(Ship first Playwright test);
await input.press(Enter);
const item = page.getByTestId(todo-item);
await expect(item).toHaveCount(1);
await item.getByRole(checkbox).check();
await expect(item).toHaveClass(/completed/);
});Run it with npx playwright test --headed and watch the browser walk through your test. Two commits later this pattern — getByRole / getByPlaceholder / expect().toHave*() — will become muscle memory.
Minute 30-45: the trace viewer changes everything
Break your test on purpose and rerun with tracing:
npx playwright test --trace on
npx playwright show-reportThe trace viewer gives you a full DOM snapshot at each step, network waterfall, and console log. This is the tool that makes Playwright genuinely better than Selenium in 2026 for debugging flakes.
Minute 45-60: wire it into CI
The generated playwright.yml is production-ready. Push to GitHub and you get a green check on every PR. From there you can add sharding (--shard=1/4), video capture on failure, and a JUnit reporter for your test-management tool.
What to learn next (in order)
- Locator best practices — the single highest-leverage skill.
- Page Object Model — wrap every locator in a class.
- API testing with request.
- Fixtures — the correct way to share setup across tests.
- Auth state reuse — log in once, reuse the storage state across all tests.
35. Where to Go Next
This guide is the hub. Branch out based on where you are:
- Just installing? Playwright installation guide (Windows/Mac/Linux)
- Mastering selectors? Playwright locators complete guide
- Advanced features? 7 advanced Playwright features you're not using
- Framework design? Test automation framework consulting
- Interview prep? 300 Playwright interview questions and rehearse them in the QA Community
- Resume ready? Free ATS resume review
- Hunting jobs? QA Jobs Radar
36. Conclusion
You now have a complete playbook: installation, locators, interactions, assertions, fixtures, POM, API testing, visual testing, authentication, CI/CD, debugging, Python parity, HAR replay, Trace Viewer, component and accessibility testing, k6 pairing, and a realistic Selenium migration path. Playwright is not just a testing tool — it's a complete browser-automation platform built for the demands of modern web apps.
Ready to practice automation reasoning before your next interview? Try our QA Community or browse Playwright interview questions.
