Projects are what turn Playwright knowledge into interview-ready experience. These five build on each other, from a beginner demo suite to an enterprise automation platform, and each one is something you can put on GitHub and talk through in an interview.

Project 1: Beginner Demo Suite

Maps to: Fresher (0–1y) · Modules 1–13, 20, 22

A first automated suite against a public demo site. Goal: get green tests running with good habits from day one.

Build a small, reliable UI test suite for a login-based demo app, using semantic locators and web-first assertions.

Locators, actions, web-first assertions, test runner basics, config, screenshots.

Node LTS, VS Code, Playwright installed (npm init playwright@latest). See 00-setup/setup-guide.md.

https://www.saucedemo.com (stable public demo; users listed on its login page).

  • Valid login (standard_user)
  • Invalid login (wrong password → error)
  • Locked-out user (error message)
  • Inventory page loads with 6 products
  • Add one item → cart badge shows 1

Flat and simple — no POM yet (that’s Project 2). One spec file per feature, shared beforeEach to navigate.

project-1/
├── tests/
│ ├── login.spec.ts
│ └── inventory.spec.ts
├── playwright.config.ts
└── package.json

npm init playwright@latest project-1cd project-1npx playwright test        # confirm the sample runs
  1. Configure baseURL: 'https://www.saucedemo.com' in config.
  2. Write login.spec.ts: valid, invalid, locked-out cases.
  3. Write inventory.spec.ts: product count + add-to-cart.
  4. Enable screenshot: 'only-on-failure'.
  5. Run headed (--headed) to watch, then headless.
// tests/login.spec.tsimport { test, expect } from '@playwright/test';test.beforeEach(async ({ page }) => { await page.goto('/'); });test('valid login', async ({ page }) => {  await page.getByPlaceholder('Username').fill('standard_user');  await page.getByPlaceholder('Password').fill('secret_sauce');  await page.getByRole('button', { name: 'Login' }).click();  await expect(page).toHaveURL(/inventory/);});test('invalid password shows error', async ({ page }) => {  await page.getByPlaceholder('Username').fill('standard_user');  await page.getByPlaceholder('Password').fill('wrong');  await page.getByRole('button', { name: 'Login' }).click();  await expect(page.getByText(/Username and password do not match/)).toBeVisible();});test('locked-out user is blocked', async ({ page }) => {  await page.getByPlaceholder('Username').fill('locked_out_user');  await page.getByPlaceholder('Password').fill('secret_sauce');  await page.getByRole('button', { name: 'Login' }).click();  await expect(page.getByText(/locked out/)).toBeVisible();});
// tests/inventory.spec.tsimport { test, expect } from '@playwright/test';test.beforeEach(async ({ page }) => {  await page.goto('/');  await page.getByPlaceholder('Username').fill('standard_user');  await page.getByPlaceholder('Password').fill('secret_sauce');  await page.getByRole('button', { name: 'Login' }).click();});test('shows 6 products', async ({ page }) => {  await expect(page.locator('.inventory_item')).toHaveCount(6);});test('add to cart updates badge', async ({ page }) => {  await page.getByRole('button', { name: 'Add to cart' }).first().click();  await expect(page.locator('.shopping_cart_badge')).toHaveText('1');});
None yet — intentionally. (Introduced in Project 2.)

Static demo credentials from the site’s login page.

None (Project 3+).

Optional stretch: add the GitHub Actions workflow from Module 53 (single browser, no shards).

None (Project 5).

reporter: 'html'; open with npx playwright show-report.

  • Choosing stable locators (prefer role/placeholder over CSS ids).
  • Avoiding hard waits (use web-first assertions).

Add the full purchase flow (checkout → confirmation) as extra practice.

All 5 tests pass headless; HTML report opens; a forced failure produces a screenshot.

Repo playwright-saucedemo-basics with README (what it tests + report screenshot).

“My first suite used web-first assertions and semantic locators from the start, so it was stable without any waitForTimeout.”

Advertisement

Project 2: E-commerce POM Framework

Maps to: SDET (1–2y) · Modules 22–29, 27, 28, 48

Refactor into a real framework: Page Object Model, fixtures, data-driven tests, tags, and reporting.

Turn the beginner scripts into a maintainable POM framework with reusable page objects and fixtures.

POM, fixtures (DI), hooks, data-driven testing, tags/annotations, HTML reporting.

Project 1 complete; comfort with TypeScript classes.

https://www.saucedemo.com — full purchase journey.

  • Login (data-driven across the site’s user types)
  • Inventory: add/remove items, sorting
  • Cart: verify items and count
  • Checkout: fill info → overview → finish → confirmation

project-2/
├── tests/
│ ├── login.spec.ts
│ ├── cart.spec.ts
│ └── checkout.spec.ts
├── pages/
│ ├── LoginPage.ts
│ ├── InventoryPage.ts
│ ├── CartPage.ts
│ └── CheckoutPage.ts
├── fixtures/test.ts
├── data/users.json
└── playwright.config.ts

Copy Project 1, add pages/, fixtures/, data/. Configure baseURL.

  1. Create LoginPage, InventoryPage, CartPage, CheckoutPage (locators + actions only).
  2. Build fixtures/test.ts injecting these page objects.
  3. Convert specs to use fixtures.
  4. Data-drive login from users.json.
  5. Tag @smoke/@regression; enable HTML report.
// pages/LoginPage.tsimport { Page, Locator } from '@playwright/test';export class LoginPage {  private user: Locator; private pass: Locator; private btn: Locator;  constructor(private page: Page) {    this.user = page.getByPlaceholder('Username');    this.pass = page.getByPlaceholder('Password');    this.btn  = page.getByRole('button', { name: 'Login' });  }  async goto() { await this.page.goto('/'); }  async login(u: string, p: string) {    await this.user.fill(u); await this.pass.fill(p); await this.btn.click();  }}
// fixtures/test.tsimport { test as base, expect } from '@playwright/test';import { LoginPage } from '../pages/LoginPage';import { InventoryPage } from '../pages/InventoryPage';import { CartPage } from '../pages/CartPage';import { CheckoutPage } from '../pages/CheckoutPage';type Fixtures = {  loginPage: LoginPage; inventoryPage: InventoryPage;  cartPage: CartPage; checkoutPage: CheckoutPage;};export const test = base.extend<Fixtures>({  loginPage: async ({ page }, use) => use(new LoginPage(page)),  inventoryPage: async ({ page }, use) => use(new InventoryPage(page)),  cartPage: async ({ page }, use) => use(new CartPage(page)),  checkoutPage: async ({ page }, use) => use(new CheckoutPage(page)),});export { expect };
// tests/checkout.spec.tsimport { test, expect } from '../fixtures/test';test('complete purchase @smoke', async ({ page, loginPage, inventoryPage, cartPage, checkoutPage }) => {  await loginPage.goto();  await loginPage.login('standard_user', 'secret_sauce');  await inventoryPage.add('Sauce Labs Backpack');  await inventoryPage.openCart();  await cartPage.checkout();  await checkoutPage.fillInfo('Asha', 'QA', '560001');  await checkoutPage.finish();  await expect(page.getByText('Thank you for your order!')).toBeVisible();});
// tests/login.spec.ts (data-driven)import { test, expect } from '../fixtures/test';import users from '../data/users.json';for (const u of users) {  test(`login: ${u.name} @regression`, async ({ page, loginPage }) => {    await loginPage.goto();    await loginPage.login(u.username, 'secret_sauce');    if (u.expectSuccess) await expect(page).toHaveURL(/inventory/);    else await expect(page.getByText(u.error)).toBeVisible();  });}

Every page is a class; fixtures inject them. Assertions stay in tests.

users.json for the site’s user types (standard, locked_out, problem, etc.).

None yet.

Add the GitHub Actions workflow (single or 2 browsers); publish the HTML report artifact.

Optional.

HTML report; tag-based runs (--grep @smoke).

  • Keeping assertions out of page objects.
  • Name-scoped locators for add/remove (avoid .nth()).

Add sorting tests and a remove-from-cart flow; add a Header component.

Purchase flow green; data-driven login covers all user types; changing a locator touches only its page class.

Repo playwright-ecommerce-pom with an architecture note in the README.

“I refactored scripts into POM + fixtures; a UI id change now touches one class, not every test. Login is data-driven from JSON.”

Project 3: Full Test Framework

Maps to: SDET (2–3y) · Modules 23, 26, 30–34, 44, 45, 53

A complete framework: components, API-based setup, storageState auth, cross-browser, and CI on GitHub Actions.

Build a scalable framework combining UI + API setup, auth reuse, components, and cross-browser CI.

Advanced POM + components, worker/test fixtures, API client, storageState, projects/cross-browser, sharded CI.

Project 2 complete; understanding of fixtures and config projects.

https://www.saucedemo.com for UI + a public API (e.g., reqres.in) to demonstrate API setup/verification patterns. (In a real job, both hit the same app.)

  • storageState auth (login once, reuse)
  • Component objects (Header/ProductCard)
  • API-seeded data pattern + API verification pattern
  • Cross-browser (chromium/firefox/webkit)
  • CI on GitHub Actions with sharding + merged report

project-3/
├── tests/{smoke,regression,api}/
├── pages/ {BasePage,LoginPage,InventoryPage,CartPage,CheckoutPage}.ts
├── components/{Header,ProductCard}.ts
├── fixtures/test.ts
├── utils/{api.ts,logger.ts}
├── data/{factories.ts,users.json}
├── auth/ # storageState (gitignored)
├── auth.setup.ts
├── .github/workflows/playwright.yml
└── playwright.config.ts

Extend Project 2; add components/, utils/, auth.setup.ts, and a CI workflow.

  1. Add BasePage + Header/ProductCard components.
  2. Write auth.setup.ts to log in and save storageState.
  3. Configure setup project + browser projects depending on it (Module 26).
  4. Add utils/api.ts (Api client) and a factory for data.
  5. Write an API spec (CRUD) and a UI+API integration spec.
  6. Add the GitHub Actions workflow with 2–3 shards + merged report.
// auth.setup.tsimport { test as setup } from '@playwright/test';setup('authenticate', async ({ page }) => {  await page.goto('/');  await page.getByPlaceholder('Username').fill('standard_user');  await page.getByPlaceholder('Password').fill('secret_sauce');  await page.getByRole('button', { name: 'Login' }).click();  await page.waitForURL(/inventory/);  await page.context().storageState({ path: 'auth/user.json' });});
// playwright.config.ts (excerpt)projects: [  { name: 'setup', testMatch: /auth\.setup\.ts/ },  { name: 'chromium', use: { ...devices['Desktop Chrome'], storageState: 'auth/user.json' }, dependencies: ['setup'] },  { name: 'firefox',  use: { ...devices['Desktop Firefox'], storageState: 'auth/user.json' }, dependencies: ['setup'] },  { name: 'webkit',   use: { ...devices['Desktop Safari'],  storageState: 'auth/user.json' }, dependencies: ['setup'] },],
// utils/api.tsimport { APIRequestContext } from '@playwright/test';export class Api {  constructor(private request: APIRequestContext, private base = process.env.API_URL!) {}  async createUser(data: object) {    const res = await this.request.post(`${this.base}/users`, { data });    if (!res.ok()) throw new Error(`createUser ${res.status()}`);    return res.json();  }}
BasePage + components composed into pages; fixtures inject pages, api, and (via storageState) an authed session.

Faker factory for dynamic data; JSON for static reference; unique data for parallel safety.

API CRUD spec + one UI+API integration spec (seed via API, verify in UI or vice versa).

GitHub Actions: matrix shards → blob reports → merge to HTML artifact (Module 53).

Optional stretch (full in Project 5).

HTML + JUnit; merged report artifact from shards.

  • storageState freshness (regenerate each run).
  • Cross-browser differences (assert outcomes, not internals).
  • Parallel-safe data.

Add mobile projects (Pixel 7/iPhone 14) and network-blocking of third-party calls.

Login happens once via storageState; suite green on 3 browsers; CI runs sharded and publishes a merged report.

Repo playwright-full-framework with a CI badge and downloadable report.

“Auth runs once via a setup project + storageState; every browser project depends on it. CI shards across runners and merges into one HTML report.”

Project 4: Enterprise UI + API Suite

Maps to: Senior SDET (3–4y) · Modules 32–36, 45, 52, 55, 57, 59

A senior-level suite emphasizing UI+API integration, mocking, parallel scale, multi-environment config, patterns, and Jenkins.

Build a robust UI+API framework that runs across environments, uses mocking for edge cases, scales in parallel, and integrates with Jenkins.

UI+API integration, network interception + mocking, design patterns (Factory/Strategy/Facade), parallel/sharding, env management, Jenkins CI.

Project 3 complete; comfort with fixtures, API testing, and CI.

A demo storefront for UI + its API (or saucedemo UI + reqres.in API as stand-ins). Treat them as one system under test.

  • UI+API integration flows (seed via API, act via UI, verify via API)
  • Mocked edge cases (empty state, 500 error, large dataset)
  • Multi-environment runs (QA/UAT) via env config
  • LoginStrategy (UI vs API) pattern
  • Parallel + sharding; Jenkins pipeline

project-4/
├── tests/{integration,mocked,api,regression}/
├── pages/ components/ fixtures/
├── strategies/LoginStrategy.ts # UI + API login
├── utils/{api.ts,logger.ts}
├── data/factories.ts
├── config/env.ts # env loading + validation
├── .env.qa .env.uat .env.example
├── Jenkinsfile
└── playwright.config.ts

Extend Project 3; add strategies/, config/env.ts, .env.*, and a Jenkinsfile.

  1. Add config/env.ts with required-var validation; load .env.<TEST_ENV>.
  2. Implement LoginStrategy (UiLogin + ApiLogin) and inject via fixture.
  3. Write integration specs (API seed → UI act → API verify).
  4. Write mocked specs (empty/500/large) via route.fulfill.
  5. Configure parallel workers; add a Jenkinsfile (Module 52) with sharded stages.
// strategies/LoginStrategy.tsimport { Page } from '@playwright/test';export interface LoginStrategy { login(page: Page, u: {email:string;password:string}): Promise<void>; }export class UiLogin implements LoginStrategy {  async login(page: Page, u) {    await page.goto('/login');    await page.getByLabel('Email').fill(u.email);    await page.getByLabel('Password').fill(u.password);    await page.getByRole('button', { name: 'Login' }).click();  }}export class ApiLogin implements LoginStrategy {  async login(page: Page, u) {    // obtain token via API, seed it so the app is authenticated without UI    await page.addInitScript(() => localStorage.setItem('token', 'seeded'));  }}
// tests/mocked/products.spec.tsimport { test, expect } from '../../fixtures/test';test('empty product list shows empty state', async ({ page }) => {  await page.route('**/api/products', r => r.fulfill({ status: 200, body: '[]' }));  await page.goto('/products');  await expect(page.getByText('No products found')).toBeVisible();});test('server error shows banner', async ({ page }) => {  await page.route('**/api/products', r => r.fulfill({ status: 500, body: '{}' }));  await page.goto('/products');  await expect(page.getByRole('alert')).toBeVisible();});
// tests/integration/order.spec.tsimport { test, expect } from '../../fixtures/test';import { userFactory } from '../../data/factories';test('order created via UI persists in backend @regression', async ({ page, request, api }) => {  const user = await api.createUser(userFactory());   // API seed  // ...UI: login (ApiLogin), add item, checkout...  const res = await request.get(`/api/orders?user=${user.id}`);  // API verify  expect(res.status()).toBe(200);  expect((await res.json()).length).toBeGreaterThan(0);});
Pages + components; fixtures inject api, a chosen LoginStrategy, and data.
Factories (Faker) for unique data; env-specific reference data; strict isolation.

Heavy: seeding, verification, and mocking edge cases the UI can’t easily reach.

Jenkins pipeline (Module 52): parameterized env/suite, sharded parallel stages, JUnit + HTML published, secrets via credentials.

Run Jenkins agent on the Playwright image (Module 52/54).

JUnit for the Jenkins dashboard; HTML + archived traces.

  • Keeping mocks schema-accurate.
  • Env safety (validation + no accidental PROD).
  • Parallel isolation with API-seeded data cleanup.

Add a Slack/custom reporter; add contract (JSON-schema) validation on key endpoints.

Runs green on QA and UAT via TEST_ENV; mocked edge cases pass deterministically; Jenkins pipeline sharded + green with published reports.

Repo playwright-enterprise-ui-api with a Jenkinsfile, env docs, and architecture diagram.

“Most tests authenticate via an ApiLogin strategy for speed; login tests use UiLogin. I mock edge cases like 500s deterministically, run against QA/UAT via env config, and gate releases through a sharded Jenkins pipeline.”

Project 5: Enterprise Automation Platform

Maps to: Architect / 5y+ · Modules 54–59, plus everything prior

The capstone: an enterprise-grade platform with Docker, full CI/CD, DB validation, dashboards, governance, and reusable shared modules.

Design and build a production-grade automation platform that other teams could adopt: reliable at scale, containerized, observable, and governed.

Everything — architecture, patterns, Docker, DB validation, sharded CI/CD, reporting/dashboards, governance/standards.

Projects 1–4 complete; confidence with architecture and CI/CD.

A multi-feature demo app (or your own small full-stack app) with a UI, REST API, and a database you can query for verification.

  • Layered enterprise architecture (Module 56)
  • Design patterns applied (Factory/Builder/Strategy/Facade/DI)
  • Docker + docker-compose (app + tests)
  • DB validation (verify DB state after UI/API actions)
  • Cross-browser + mobile; parallel + sharding
  • CI/CD (GitHub Actions AND/OR Jenkins) with dashboards + notifications
  • Governance: linting, standards, branch protection, docs

project-5/
├── tests/{smoke,regression,api,e2e,db}/
├── pages/ components/ fixtures/
├── strategies/ utils/{api,db,logger}.ts reporters/
├── data/{factories,builders}.ts
├── config/env.ts
├── scripts/{seed,cleanup,merge-reports}.ts
├── Dockerfile docker-compose.yml
├── .github/workflows/ (or Jenkinsfile)
├── eslint.config.js .prettierrc tsconfig.json
├── docs/{onboarding.md,conventions.md,architecture.md}
└── playwright.config.ts

Compose the app + a database + the test container; wire env; add DB client util.

  1. Containerize tests (Dockerfile, Module 54) and app+db via docker-compose.
  2. Add utils/db.ts (a read-only client to verify records).
  3. Implement patterns: builders.ts (OrderBuilder), strategies/ (login), a CheckoutFacade.
  4. Write DB-validation specs (UI/API action → assert DB row).
  5. Full CI: sharded matrix, merged report, custom reporter (Slack/summary), branch protection.
  6. Add ESLint/Prettier/strict tsconfig; write docs/.
// utils/db.ts  (read-only verification; never mutate prod-like data blindly)import { Client } from 'pg';export async function getOrderCount(userId: string): Promise<number> {  const client = new Client({ connectionString: process.env.DB_URL });  await client.connect();  try {    const res = await client.query('SELECT COUNT(*) FROM orders WHERE user_id = $1', [userId]);    return Number(res.rows[0].count);  } finally {    await client.end();  }}
// tests/db/order.spec.tsimport { test, expect } from '../../fixtures/test';import { getOrderCount } from '../../utils/db';import { userFactory } from '../../data/factories';test('checkout writes an order row @e2e', async ({ page, request, api }) => {  const user = await api.createUser(userFactory());  const before = await getOrderCount(user.id);  // ...UI checkout for this user...  const after = await getOrderCount(user.id);  expect(after).toBe(before + 1);        // DB-level verification});
# docker-compose.yml (excerpt)services:  db:    { image: postgres:16, environment: { POSTGRES_PASSWORD: test } }  app:   { image: myapp:latest, depends_on: [db], ports: ["3000:3000"] }  tests:    build: .    depends_on: [app]    environment:      BASE_URL: http://app:3000      DB_URL: postgres://postgres:test@db:5432/postgres    command: npx playwright test
Full layered architecture; fixtures inject pages, api, db client, strategies, and data.
Factories + builders; unique data; API seed + DB verify + cleanup scripts.
Seeding, verification, mocking; plus DB as a third verification layer.
Sharded matrix (GitHub Actions) and/or Jenkins; merged HTML report; custom reporter posts a summary; branch protection gates merges.
docker-compose spins up db + app + tests; the Playwright image runs the suite (Module 54).
HTML + JUnit + blob(merge) + custom reporter (Slack/summary); dashboards from JUnit trends.
  • DB verification without polluting shared data (unique users + cleanup).
  • Compose networking and health/readiness ordering.
  • Keeping the suite fast despite three verification layers.

Publish shared fixtures/components as an internal npm package; add visual regression; add accessibility checks.

docker compose up runs the full suite green (UI+API+DB) across browsers, sharded in CI, with a merged report and a posted summary; linting enforced; docs complete.

Repo playwright-automation-platform with architecture diagram, compose setup, CI badges, sample dashboard screenshot, and docs/.

“I built a containerized platform with three verification layers — UI, API, and DB. It runs via docker-compose, shards in CI, merges reports, posts a summary to Slack, and enforces standards through linting and branch protection. Shared fixtures are packaged for other teams to consume.”

Capstone Projects

Level: L5. Prove everything by building.

What is it?

Five progressive, portfolio-worthy projects that integrate every skill in this course — from a first automated suite to an enterprise platform with Docker + CI + API + DB.

Why do we need it?

Employers hire on demonstrated ability. A GitHub portfolio of real frameworks (with CI badges and reports) beats any certificate. Each project maps to a job level.

The five projects (detailed specs in projects/)

Project 1 — Beginner Demo Suite → maps to Fresher
Project 2 — E-commerce POM Framework → maps to 1–2y SDET
Project 3 — Full Test Framework → maps to 2–3y SDET
Project 4 — Enterprise UI + API Suite → maps to 3–4y Senior SDET
Project 5 — Enterprise Automation Platform → maps to 5y+ / Architect

Each project file follows a 20-point structure: objective, skills used, prerequisites, target app, scope, architecture, setup, step-by-step build, key code, POM/fixtures, data strategy, API usage, CI/CD, Docker, reporting, challenges, extensions, acceptance criteria, portfolio presentation, and interview talking points.

How to use them

  • Build them in order; each adds concepts on the previous.
  • Push each to its own GitHub repo with a README, CI workflow, and a live report artifact.
  • Treat acceptance criteria as your “definition of done.”

Skill coverage map

P1: locators, actions, assertions, first config
P2: POM, fixtures, data-driven, tags, HTML report
P3: components, API setup, storageState, cross-browser, CI (GitHub Actions)
P4: UI+API integration, mocking, network, parallel/sharding, env management, Jenkins
P5: enterprise architecture, patterns, Docker, DB validation, dashboards, governance

Portfolio presentation (per repo)

  • Clear README: what it tests, stack, how to run, screenshots of the report.
  • CI badge (passing), sample HTML report (link/artifact), architecture diagram.
  • A short “what I learned / trade-offs” section — shows engineering judgment.

Common Mistakes

  • Building only Project 1 and stopping (no seniority signal).
  • No README/CI → recruiters can’t evaluate it.
  • Copy-paste without understanding (fails in interviews).
  • Testing a throwaway app with no realistic flows.

Best Practices

  • One repo per project; incremental complexity.
  • Real public demo apps (saucedemo, etc.) or a small app you build.
  • Green CI + downloadable report on every repo.
  • Document trade-offs and architecture decisions.

Interview Questions

  • Q: Walk me through a project you built. A: Use Project 3/4 — state scope, architecture, reliability/speed choices, CI, and results.
  • Q: What was the hardest bug? A: Pull a real troubleshooting story (flaky race / CI-only failure) with the fix.

Practice Exercise

Commit to building all five over the coming weeks (roadmaps in roadmaps/). Start Project 1 today; open a public repo and wire CI before writing many tests.

Acceptance (course completion)

You’ve completed the course when Projects 1–4 are built, green in CI, documented, and you can whiteboard the Project 5 architecture confidently.

Real-World Scenario

A career-switcher landed an SDET role largely on a public repo of Projects 2–4: the interviewer browsed the code live, saw POM + fixtures + sharded CI + a clean report, and spent the interview discussing the candidate’s decisions rather than quizzing basics.

Detailed, buildable specs for each of the five projects are in the projects/ folder.

FAQs

Which Playwright project is best for a resume?

A page-object framework against a demo e-commerce site with API setup, CI on GitHub Actions and an HTML report, published on GitHub with a clear README.

Where can I practise Playwright for free?

On demo sites built for automation practice; see websites to practise automation, which all work with Playwright too.

Note: reqres.in now asks for a free API key sent in an x-api-key header; check its site, or use another practice API such as JSONPlaceholder or Restful Booker.