The Complete Playwright Course
Nine hands-on lessons that take you from zero to shipping E2E tests in CI — every step animated so you can see exactly what to do. 🎬
🚀 Start the course
Install & Setup Playwright
One command scaffolds everything — config, specs, GitHub Actions workflow, and all three browser engines. 👇
Scaffold the project
Run this in a new (or existing) project folder — it asks a few questions (TypeScript? GitHub Actions?) and installs Chromium, Firefox and WebKit:
npm init playwright@latestMeet the scaffold
These are the files that appear — you'll use every one of them in this course:
Run the example suite
The scaffold ships with sample tests — run them right away to verify your setup:
npx playwright testTests run headless across all 3 browsers by default. Add --ui for the interactive UI Mode or --headed to watch.
Which command scaffolds a Playwright project?
Your First Playwright Test
test() → page fixture → page.goto(). Four steps to your first green checkmark — watch it happen live.
Create a spec file
In tests/, create a file with a .spec.ts (or .spec.js) extension:
Write the test
test() is one test case. The { page } argument is a built-in fixture that gives you an isolated browser page — no setup needed:
import { test, expect } from '@playwright/test';
test('visits the site', async ({ page }) => {
await page.goto('https://qa.imcseian.com/');
});Run it!
From your project root:
npx playwright testUseful flags: --ui (UI Mode), --headed (watch the browser), --debug (inspector), first-test.spec.ts (run one file).
What does page.goto() do?
Locators — Find Elements
Playwright's superpower: user-facing locators that are readable, resilient, and auto-waiting.
The locator pyramid
Pick the most user-facing locator that works — they survive CSS refactors:
getByRole is the top pick — it matches how users & screen readers see your app.
Chain & filter
Locators can be chained to narrow down, and reused many times:
const form = page.getByRole('form')
await form.getByLabel('Email').fill('qa@tester.io')
await form.getByRole('button', { name: 'Log in' }).click()Which locator does Playwright recommend first for buttons & links?
Actions — Interact
Type, click, check, press — every action first waits for the element to be visible, stable and enabled.
The action toolbox
A real flow — TodoMVC
Every Playwright demo starts here — adding & completing a todo:
await page.getByPlaceholder('New todo').fill('Ship v2')
await page.keyboard.press('Enter')
await page.getByRole('checkbox').check()
await expect(page.getByText('1 item left')).toBeVisible()Which method fills an input field?
Assertions with expect()
Prove your app behaves. Web-first assertions that retry automatically until they pass.
expect(locator).to…
Import expect from Playwright — assertions take a locator, not a stale element:
await expect(page.locator('.title')).toBeVisible()
await expect(page.locator('.title')).toHaveText('Welcome')
await expect(page).toHaveURL(/dashboard/)Which assertion checks exact text?
Auto-Waiting & Retries
The reason Playwright tests are not flaky: actions wait for actionability, and expect() retries.
How it works
Before acting, Playwright checks the element is visible, stable, enabled and receives events. And expect() keeps retrying (5s by default) until the assertion passes. No waitForTimeout(5000) hacks — that's an anti-pattern!
Custom timeout
Slow-loading element? Extend the timeout per assertion:
await expect(page.locator('.status'))
.toHaveText('Success', { timeout: 10_000 })The element isn't there yet. What does expect() do?
Fixtures & Hooks
Set up state once with hooks, drive tests with data, and even build your own fixtures.
Hooks — beforeEach & friends
Run setup before every test in a file — you get a fresh page fixture per test, so tests never leak state:
test.beforeEach(async ({ page }) => {
await page.goto('/login')
})Data-driven tests
Loop over a data array (or load a JSON file) and generate a test per record:
for (const u of users) {
test(`login ${u.email}`, async ({ page }) => {
await page.getByLabel('Email').fill(u.email)
})
}Built-in fixtures: page, context, browser, request. Create your own with test.extend() — e.g. an authenticated authedPage.
When does test.beforeEach run?
Codegen & Trace Viewer
Let Playwright write your tests, and debug failures with time-travel traces.
Record tests with codegen
Click and type in a real browser — Playwright writes the locators and code for you:
npx playwright codegen https://qa.imcseian.comDebug with traces
Turn on traces and every failing test gets a time-travel recording — DOM snapshots, network, console for every action:
export default defineConfig({
use: { trace: 'on-first-retry' },
})Open results with npx playwright show-report or npx playwright show-trace — you can scrub through every step like a video.
Which command records your actions as code?
Run Playwright in CI/CD
Tests are only useful if they run on every push. Headless mode + GitHub Actions = automated confidence.
Headless by default
npx playwright test is already headless — perfect for CI. Use --headed / --ui only while developing:
npx playwright test # headless, all 3 browsersGitHub Actions workflow
Create .github/workflows/playwright.yml:
name: E2E Tests
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright testEnable retries on CI with retries: process.env.CI ? 2 : 0 — failed runs upload the HTML report + traces so you can see exactly why.
Which command runs tests headless by default?
Comments
Comments
Post a Comment