QA Knowledge Base

Playwright
Reference
Hub

Total Hours 20 hrs
Sessions 20 × 60 min
Review 15 min each
Pace Self-directed
Principle 80/20 Focus
The 80/20 approach: Most Playwright work — writing locators, handling async flows, assertions, page fixtures, and CI integration — is covered by about 20% of the API. This plan front-loads exactly that core. By session 10 you'll be productive on real projects. Sessions 11–20 add the professional layer that separates a tester from a test engineer.
Quick Access
Jump to the patterns and topics used most often
Common Tasks
Find What You Need
Search by keyword or filter by topic
Toolkit
Common Playwright Task Patterns
Locator Patterns
/* Best Practice Hierarchy */
getByRole('button', { name: 'Submit' })
getByLabel('Email')
getByText('Confirm Order')

/* Fallback */
locator('[data-testid="submit-btn"]')

/* Avoid when possible */
locator('.btn.primary')
locator('//div[3]/button[2]')
    
Date / Time Handling
/* Add 3 days */
const future = new Date();
future.setDate(future.getDate() + 3);

/* Round to nearest 15 minutes */
let minutes = future.getMinutes();
let rounded = Math.round(minutes / 15) * 15;

if (rounded === 60) {
  rounded = 0;
  future.setHours(future.getHours() + 1);
}

/* Handle midnight rollover */
if (future.getHours() === 0 && rounded === 0) {
  future.setDate(future.getDate() + 1);
}

/* Format (MM/DD/YYYY) */
const date = future.toLocaleDateString('en-US');

/* Format time */
const time = `${future.getHours()}:${String(rounded).padStart(2,'0')}`;
Wait / Stability Patterns
/* Wait for element */
await expect(locator).toBeVisible();

/* Wait for loading spinner to disappear */
await expect(loadBox).not.toBeVisible();

/* Wait for navigation */
await page.waitForURL('**/orders');

/* Wait for API response */
await page.waitForResponse(resp =>
  resp.url().includes('/api/orders') && resp.status() === 200
);

/* Wait for specific state (not just visibility) */
await expect(locator).toHaveText('Completed');

/* Wait for count */
await expect(rows).toHaveCount(10);

/* Avoid this */
await page.waitForTimeout(2000); // ❌ flaky
Fix Flaky Test Patterns
A Retry Block uses expect(...).toPass() to retry a code block until it succeeds or times out. This is useful for slow-loading images, delayed rendering, and unstable UI readiness.
/* Retry Until Stable */
const chart = page.locator('#revenue-chart');

await expect(async () => {
  await expect(chart).toBeVisible();
  await expect(chart).toHaveAttribute('data-rendered', 'true');
}).toPass({
  intervals: [1000, 2000, 5000],
  timeout: 30000
});

/* Wait for Full Page Stability */
await page.waitForLoadState('load');
await page.waitForLoadState('networkidle');

/* Wait for Spinner / Overlay to Disappear */
await page.locator('.loading-spinner').waitFor({ state: 'hidden', timeout: 10000 });
/* Avoid This */
await page.waitForTimeout(3000); // ❌ flaky
await page.goto('/dashboard');

await FlakyFixer.waitForStability(page);

await FlakyFixer.retryUntil(async () => {
  const chart = page.locator('#revenue-chart');
  await expect(chart).toBeVisible();
  await expect(chart).toHaveAttribute('data-rendered', 'true');
});

await page.getByRole('button', { name: 'Generate Report' }).click();

const spinner = page.locator('.loading-spinner');
await FlakyFixer.waitForVanished(spinner, 60_000);
Auth / Storage State Patterns
/* Save signed-in state once */
await page.goto('/login');
await page.getByLabel('Email').fill(process.env.USER_EMAIL);
await page.getByLabel('Password').fill(process.env.USER_PASSWORD);
await page.getByRole('button', { name: 'Sign in' }).click();

await expect(page).toHaveURL(/dashboard|home/);
await page.context().storageState({ path: 'playwright/.auth/user.json' });
use: {
  storageState: 'playwright/.auth/user.json'
}
/* Admin project */
{
  name: 'admin',
  use: { storageState: 'playwright/.auth/admin.json' }
}

/* Standard user project */
{
  name: 'user',
  use: { storageState: 'playwright/.auth/user.json' }
}
/* Faster than UI login when supported */
const requestContext = await request.newContext();

const response = await requestContext.post('/api/login', {
  data: {
    email: process.env.USER_EMAIL,
    password: process.env.USER_PASSWORD
  }
});

await expect(response).toBeOK();
/* Avoid logging in through the UI in every test */
test('every test logs in from scratch', async ({ page }) => {
  await page.goto('/login');
  ...
}); // ❌ slow and flaky
/* global.setup.js */
import { chromium, expect } from '@playwright/test';

export default async function globalSetup() {
  const browser = await chromium.launch();
  const page = await browser.newPage();

  await page.goto('/login');
  await page.getByLabel('Email').fill(process.env.USER_EMAIL);
  await page.getByLabel('Password').fill(process.env.USER_PASSWORD);
  await page.getByRole('button', { name: 'Sign in' }).click();

  await expect(page).toHaveURL(/dashboard|home/);
  await page.context().storageState({ path: 'playwright/.auth/user.json' });

  await browser.close();
}
Mock API Patterns
/* Mock a successful API response */
await page.route('**/api/v1/menu**', async route => {
  await route.fulfill({
    status: 200,
    contentType: 'application/json',
    body: JSON.stringify({
      items: [
        { id: 1, name: 'Turkey Sandwich' },
        { id: 2, name: 'Chicken Wrap' }
      ]
    })
  });
});
/* Force empty results */
await page.route('**/api/v1/menu**', async route => {
  await route.fulfill({
    status: 200,
    contentType: 'application/json',
    body: JSON.stringify({ items: [] })
  });
});
/* Simulate server failure */
await page.route('**/api/v1/menu**', async route => {
  await route.fulfill({
    status: 500,
    contentType: 'application/json',
    body: JSON.stringify({ message: 'Internal Server Error' })
  });
});
/* Wait for a real API response */
const response = await page.waitForResponse(resp =>
  resp.url().includes('/api/v1/menu') && resp.status() === 200
);
/* Avoid depending on unstable backend data for UI state tests */
// ❌ test passes or fails depending on real API data
await page.route('**/api/v1/menu**', async route => {
  await route.fulfill({
    status: 200,
    contentType: 'application/json',
    body: JSON.stringify({
      items: [{ id: 1, name: 'Mocked Sandwich' }]
    })
  });
});

await page.goto('/menu');
await expect(page.getByText('Mocked Sandwich')).toBeVisible();
Debug Failure Patterns
/* Pause test execution and inspect live */
await page.pause();

/* Log current URL for context */
console.log(await page.url());

/* Capture visible page state */
await page.screenshot({ path: 'debug-screenshot.png', fullPage: true });
/* playwright.config.js */
use: {
  trace: 'on-first-retry',
  screenshot: 'only-on-failure',
  video: 'retain-on-failure'
}

/* Open trace after run */
npx playwright show-trace trace.zip
/* Run in headed debug mode */
npx playwright test --debug

/* Or use UI mode */
npx playwright test --ui
/* Capture locator text */
const bannerText = await page.locator('#editTrip p').textContent();
console.log('Trip banner:', bannerText);

/* Capture element count */
const rows = page.locator('table tbody tr');
console.log('Row count:', await rows.count());
/* Browser console logging */
page.on('console', msg => console.log('BROWSER LOG:', msg.text()));

/* Failed request logging */
page.on('requestfailed', request => {
  console.log('REQUEST FAILED:', request.url());
});
/* Avoid guessing why a test failed */
// ❌ adding random waitForTimeout calls
// ❌ changing locators before checking trace / UI / console evidence
test('debug failing checkout', async ({ page }) => {
  page.on('console', msg => console.log('BROWSER LOG:', msg.text()));
  page.on('requestfailed', request => {
    console.log('REQUEST FAILED:', request.url());
  });

  await page.goto('/checkout');
  await page.pause();

  await page.screenshot({ path: 'checkout-debug.png', fullPage: true });

  const errorBanner = page.locator('.error-banner');
  console.log('Error visible:', await errorBanner.isVisible());
});
Run in CI Patterns
# GitHub Actions basic Playwright run
name: Playwright Tests

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    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 tests
        run: npx playwright test
# Upload report even if tests fail
- name: Upload Playwright Report
  if: always()
  uses: actions/upload-artifact@v4
  with:
    name: playwright-report
    path: playwright-report/
    retention-days: 7
/* playwright.config.js */
use: {
  trace: 'on-first-retry',
  screenshot: 'only-on-failure',
  video: 'retain-on-failure'
}
# GitHub Actions env example
- name: Run tests
  run: npx playwright test
  env:
    BASE_URL: ${{ secrets.BASE_URL }}
    USER_EMAIL: ${{ secrets.USER_EMAIL }}
    USER_PASSWORD: ${{ secrets.USER_PASSWORD }}
# Run one folder or tag in CI
- name: Run regression suite
  run: npx playwright test Regression

# Or run tagged tests
- name: Run smoke tests
  run: npx playwright test --grep "@smoke"
# Avoid this
- using npm install instead of npm ci in CI
- skipping playwright install --with-deps
- not uploading artifacts after failures
- hardcoding credentials in workflow files
name: Playwright Regression

on:
  workflow_dispatch:

jobs:
  regression:
    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 browsers
        run: npx playwright install --with-deps

      - name: Run regression
        run: npx playwright test Regression --reporter=line
        env:
          CI: 'true'

      - name: Upload report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: playwright-report
          path: playwright-report/
Learning Progress
0 of 20 sessions complete 0 / 20 hours
Phase 1
Foundations & Core Concepts
01
Environment Setup & First Test
Core Setup Node.js
Install Playwright, scaffold a project, and run your first passing test against a live URL. Understand the project structure and config file.
  • Node.js + npm prerequisites check
  • npm init playwright@latest — what it creates and why
  • playwright.config.js anatomy: baseURL, browsers, timeout
  • Writing a minimal test with test(), page.goto(), expect()
  • Running tests: npx playwright test, --ui flag, --headed
  • Reading the HTML report: npx playwright show-report
15-min Review Self-check questions
  • What does playwright.config.js control, and which 3 settings matter most day-to-day?
  • What's the difference between --ui and --headed?
  • Write from memory: a 5-line test that visits example.com and checks the title.
02
Locators — The Core Skill
Core Selectors DOM
Master the Locator API — the single most important concept in Playwright. Understand why locators beat raw selectors and how to write resilient ones.
  • Locator vs ElementHandle — always use Locator
  • Built-in role locators: getByRole(), getByText(), getByLabel(), getByPlaceholder(), getByTestId()
  • CSS and XPath as fallbacks — when and why
  • Chaining locators: page.locator('.card').getByRole('button')
  • The Playwright Inspector for picking locators live
  • Locator best-practice hierarchy (ARIA > text > test-id > CSS)
15-min Review Self-check questions
  • When would you use getByTestId over getByRole?
  • What is auto-waiting and how does it relate to locators?
  • Name 3 signs that a locator will be brittle in production.
03
Actions & Auto-Waiting
Core Interactions Async
Learn how Playwright auto-waits before every action, and how to use every major interaction method: click, fill, select, check, hover, keyboard, drag.
  • How auto-wait works: actionability checks (visible, stable, enabled)
  • click(), fill(), type(), press(), selectOption()
  • check() / uncheck() for checkboxes and radios
  • hover(), focus(), blur()
  • dragAndDrop() and mouse events
  • page.keyboard and page.mouse for low-level control
  • Uploading files with setInputFiles()
15-min Review Self-check questions
  • What are the 4 actionability checks Playwright performs before clicking?
  • What's the difference between fill() and type()?
  • How would you upload multiple files at once?
04
Assertions with expect()
Core Assertions expect
Write precise, auto-retrying assertions that form the backbone of every test. Know the full set of web-first matchers and when to use soft assertions.
  • Web-first matchers: toBeVisible(), toHaveText(), toHaveValue(), toBeEnabled(), toBeChecked()
  • URL and page matchers: toHaveURL(), toHaveTitle()
  • Count matchers: toHaveCount()
  • Attribute matchers: toHaveAttribute(), toHaveClass()
  • Soft assertions with expect.soft()
  • Negation with .not
  • Custom assertion timeout overrides
15-min Review Self-check questions
  • Why do web-first assertions auto-retry, and what's the default retry window?
  • When should you use expect.soft() instead of a normal assertion?
  • What's the difference between toHaveText() and toContainText()?
05
Test Structure, Hooks & Fixtures
Core Architecture Fixtures
Understand how to structure test files professionally using describe blocks, hooks, and Playwright's powerful fixture system for reusable setup/teardown.
  • test.describe() for grouping related tests
  • test.beforeEach(), test.afterEach(), test.beforeAll(), test.afterAll()
  • Built-in fixtures: page, context, browser, request
  • Creating custom fixtures with test.extend()
  • Fixture scope: test vs worker-scoped
  • test.use() for per-suite configuration overrides
15-min Review Self-check questions
  • What's the difference between a test-scoped and worker-scoped fixture?
  • When would you use beforeAll vs beforeEach?
  • Create a custom loggedInPage fixture from memory.
Phase 2
Real-World Patterns
06
Page Object Model (POM)
Pattern Architecture Maintainability
Implement the Page Object Model — the industry-standard pattern for maintainable test suites. Build a real POM layer for a multi-page app.
  • Why POM: encapsulation, DRY, readable tests
  • TypeScript class structure for a Page Object
  • Constructors receiving Page instance
  • Exposing actions as methods, locators as getters
  • Composing page objects across navigation flows
  • POM + custom fixtures: the professional combo
  • Component Objects for reusable UI fragments
15-min Review Self-check questions
  • What goes in a Page Object vs what stays in the test file?
  • How do you integrate POM classes with Playwright fixtures?
  • What's the risk of putting assertions inside page objects?
07
Network Interception & Mocking
Pattern Network API
Intercept, mock, modify, and spy on network requests. This skill lets you test UI states that are hard to reproduce with a real backend.
  • page.route() to intercept and fulfill requests
  • Returning mock JSON responses with route.fulfill()
  • Modifying requests in flight with route.continue()
  • Aborting requests with route.abort()
  • Waiting for network events: page.waitForResponse()
  • HAR recording and replay for snapshot testing
  • API testing with request fixture (no browser needed)
15-min Review Self-check questions
  • What's the difference between route.fulfill() and route.continue()?
  • When would you use HAR recording instead of manual mocking?
  • How do you test an empty-state UI when the API normally returns data?
08
Authentication Strategies
Pattern Auth Performance
Implement fast, reliable authentication in your test suite. Learn to reuse auth state across tests to eliminate slow repeated logins.
  • Storage state: saving and loading cookies + localStorage
  • page.context().storageState() for saving session
  • Global setup file pattern for one-time login
  • storageState in playwright.config.js
  • Multiple user roles with multiple storage state files
  • API-based login (faster than UI login) for auth setup
  • Handling JWT tokens and session refresh
15-min Review Self-check questions
  • Why is API-based login faster than UI login for test setup?
  • How do you handle tests that need to run as two different user roles?
  • What gets saved in a storageState file — cookies only or more?
09
Waiting, Timeouts & Flake Prevention
Pattern Reliability Debugging
Understand Playwright's timeout hierarchy and write tests that never use waitForTimeout(). Learn to diagnose and fix flaky tests systematically.
  • Timeout hierarchy: global → test → action → expect
  • page.waitForURL(), page.waitForLoadState()
  • locator.waitFor() for explicit element waiting
  • page.waitForFunction() for custom conditions
  • Why waitForTimeout() causes flakes — and alternatives
  • Retries with test.retries — when appropriate
  • Using --trace on to debug flakes
15-min Review Self-check questions
  • Name all 4 levels of the Playwright timeout hierarchy.
  • A test passes locally but fails in CI 30% of the time. What's your diagnostic process?
  • What's the correct alternative to await page.waitForTimeout(2000)?
10
Debugging Tools & Trace Viewer
Tooling Debug Trace
Become fluent with Playwright's debugging toolkit. After this session, a failing test is a puzzle you can always solve — not a mystery.
  • Playwright Inspector: PWDEBUG=1 — step through tests live
  • Trace Viewer: recording, opening, and reading a trace
  • Screenshots on failure: screenshot: 'only-on-failure'
  • Video recording configuration
  • page.pause() for breakpoint-style debugging
  • UI Mode (--ui): live reload and trace viewer built in
  • VS Code Playwright extension — run, debug, pick locators
15-min Review Milestone check — you're halfway!
  • Walk through the trace viewer: what 5 types of info can you see in a trace?
  • What's the fastest way to pick a locator for an element you've never seen before?
  • At this point you should be able to: write tests, use locators, assert, structure with POM, handle auth, mock APIs, and debug failures. What's the weakest of these for you?
Phase 3
Advanced Capabilities
11
Visual Regression Testing
Advanced Screenshots Visual
Add screenshot comparison to your test suite to catch visual regressions. Understand thresholds, masking, and managing snapshot updates across platforms.
  • expect(page).toHaveScreenshot() — full page snapshots
  • expect(locator).toHaveScreenshot() — component-level
  • Snapshot update workflow: --update-snapshots
  • Threshold and pixel diff options
  • Masking dynamic content with mask option
  • Cross-platform snapshot challenges (OS-specific rendering)
  • Integrating with Percy or Argos for cloud-based diffs
15-min Review Self-check questions
  • How do you handle elements with dynamic content (timestamps, ads) in visual tests?
  • What are the tradeoffs of storing snapshots in git vs a cloud service?
  • When would visual testing add more value than assertion-based testing?
12
API Testing with Playwright
Advanced API REST
Use Playwright's APIRequestContext to test REST APIs directly — no browser needed. Combine API + UI tests for full integration testing.
  • request fixture for API-only tests
  • GET, POST, PUT, DELETE with request.get/post/put/delete()
  • Setting headers, auth tokens, and base URL
  • Parsing JSON responses and asserting status codes
  • Using API calls to seed test data before UI tests
  • Using API calls to clean up after UI tests
  • playwright.request.newContext() for standalone API contexts
15-min Review Self-check questions
  • What's the advantage of API-based test data setup over UI-based?
  • How do you share authentication state between API and browser contexts?
  • Write a test that creates a user via API, then verifies they appear in the UI.
13
Parallel Execution & Test Configuration
Advanced Performance Config
Configure Playwright for fast parallel test runs. Understand workers, sharding, and projects — the trio that controls test execution speed.
  • Workers: fullyParallel, workers config options
  • Test isolation and state management with parallel tests
  • Projects: running the same suite across multiple browsers
  • Named projects for different environments (dev, staging, prod)
  • Sharding for CI: --shard=1/4 to split across machines
  • Test tagging and filtering: --grep, test.tag
  • Slow tests annotation and retry strategy per project
15-min Review Self-check questions
  • What makes two tests "safe" to run in parallel vs unsafe?
  • How does sharding differ from running with multiple workers?
  • When would you create separate Playwright projects for the same test suite?
14
Handling Complex UI Scenarios
Advanced iFrames Dialogs Tabs
Handle the tricky UI scenarios that trip up most beginners: iframes, popups, new tabs, alert dialogs, file downloads, and shadow DOM.
  • iFrames: frameLocator() — the modern approach
  • Popups and new tabs: page.waitForEvent('popup')
  • Alert/confirm/prompt dialogs: page.on('dialog', ...)
  • File downloads: page.waitForEvent('download')
  • Shadow DOM: locator.shadowRoot() vs CSS piercing
  • Multi-page workflows across browser contexts
  • Handling infinite scroll and lazy-loaded content
15-min Review Self-check questions
  • What's the correct way to interact with an element inside a nested iframe?
  • How do you assert that a download completed and check the filename?
  • What's the event pattern for handling a link that opens in a new tab?
15
CI/CD Integration
Advanced CI/CD GitHub Actions
Ship a working GitHub Actions workflow that runs Playwright tests on every PR. Understand artifacts, caching, and environment variable handling in CI.
  • Playwright official GitHub Actions YAML template
  • Installing browsers in CI: npx playwright install --with-deps
  • Caching browser binaries for faster CI runs
  • Uploading HTML reports and traces as artifacts
  • Environment variables: .env locally vs CI secrets
  • Sharding across multiple CI machines
  • Configuring for GitLab CI, CircleCI, Azure DevOps (overview)
15-min Review Self-check questions
  • Why do Playwright tests need --with-deps in CI but not locally?
  • Where do you store sensitive test credentials in GitHub Actions?
  • How do you make the HTML report accessible after a CI failure?
Phase 4
Professional & Capstone
16
TypeScript Patterns for Test Engineers
TypeScript Types DX
Apply TypeScript to make your test suite type-safe and self-documenting. Focus on the subset of TypeScript that matters most for test engineering.
  • Typing fixtures with test.extend<{}>()
  • Interfaces for Page Object classes and data models
  • Enums and union types for test data constants
  • Generic helpers for repeated assertion patterns
  • Utility types: Partial<>, Pick<> for test data builders
  • tsconfig best practices for test projects
  • Type-safe environment variables with a config module
15-min Review Self-check questions
  • How do you type a custom fixture that returns a logged-in page object?
  • Write a UserData interface with 5 fields for a test data builder.
  • What's the benefit of typing your environment config vs using process.env.X directly?
17
Performance Testing & Accessibility
a11y Performance Axe
Add accessibility and basic performance measurements to your test suite. These are force-multipliers that many teams overlook until it's expensive to fix.
  • Accessibility testing with @axe-core/playwright
  • checkA11y() — configuring rules, disabling false positives
  • Performance metrics via Chrome DevTools Protocol (CDP)
  • page.evaluate(() => performance.timing)
  • Core Web Vitals: LCP, CLS, FID — measuring in tests
  • Lighthouse CLI integration for performance budgets
  • Network throttling with browser contexts
15-min Review Self-check questions
  • What does checkA11y actually check — and what does it NOT check?
  • How would you set a performance budget (e.g., LCP under 2.5s) in a Playwright test?
  • What's one accessibility violation that automated tools always catch, and one they never catch?
18
Reporting, Observability & Test Data
Reporting Allure Test Data
Build professional-grade reporting and test data management. Learn to make test results readable for non-engineers and scale test data safely.
  • Built-in reporters: html, json, junit, dot, line
  • Configuring multiple reporters simultaneously
  • Allure Playwright reporter — installation and rich reports
  • test.info().attach() — adding custom artifacts to reports
  • Test data strategies: static fixtures, factories, faker.js
  • Data isolation: unique data per test run (timestamps, UUIDs)
  • Cleaning up test data after runs
15-min Review Self-check questions
  • Why is unique test data per run important for parallel test execution?
  • How do you attach a screenshot with a custom name to an Allure report?
  • What's the difference between a JUnit reporter and an HTML reporter in CI context?
19
Component Testing & Playwright Experimental
Components React Experimental
Explore Playwright's component testing for React/Vue/Svelte. Understand when to use component tests vs E2E and how they complement each other.
  • Component testing setup: @playwright/experimental-ct-react
  • Mounting components: mount() fixture
  • Passing props, slots, and event handlers in tests
  • Component vs E2E: the testing trophy applied to Playwright
  • When component testing beats Storybook testing
  • Browser extension testing overview
  • Playwright's roadmap and upcoming features (check release notes)
15-min Review Self-check questions
  • What's the key difference between a component test and a unit test for the same React component?
  • When would you choose component tests over E2E tests, and vice versa?
  • What does "experimental" mean for production adoption decisions?
20
Capstone: Full Test Suite End-to-End
Capstone Integration Portfolio
Build a complete, professional test suite from scratch as your portfolio project — integrating every pattern learned across all 20 sessions.
You are the QA Lead responsible for validating a production-ready web application with authentication, API integrations, and a full user workflow. Your goal is to design and implement a test strategy that ensures confidence in both functionality and reliability.
  • Target app: saucedemo.com or automationexercise.com
  • Design Page Objects for all major pages (login, inventory, cart, checkout)
  • Implement custom fixtures for authentication (stored state) and reusable page objects
  • Build a meaningful test suite (15+ tests) covering core and edge scenarios
  • Add API validation (e.g., verify product data via API before UI assertions)
  • Implement visual regression snapshot(s) on key UI components
  • Create a GitHub Actions workflow to run tests and publish HTML reports
  • Write a README explaining setup, execution, and CI usage
  • Decide what NOT to automate and justify your choices based on risk and value
  • Choose between API vs UI validation where applicable and explain why
  • Define your flake prevention strategy (waiting patterns, retries, stability checks)
Repo
Publish to GitHub (Portfolio Project)
Push your solution as a public repository — this becomes your primary Playwright portfolio artifact.
Reference
All 19 previous sessions + playwright.dev
Use previous sessions as your foundation. Build from understanding first — reference documentation only when necessary.
15-min Final Review 🎉 You've completed 20 hours of Playwright
  • Which of the 4 phases felt strongest? Which was weakest — revisit that phase's sessions.
  • What would you add next? (Mobile testing, Electron, BDD, cloud execution like BrowserStack)
  • Push the capstone repository — this is now a portfolio-level artifact demonstrating your QA and automation capabilities.
// Back to top button