Module 13: Capstone Project
Welcome to the final module. Across the previous modules you have built every piece of a professional test automation framework: Page Object Model, custom fixtures, API testing, authentication state management, visual and accessibility testing, and a full CI pipeline. This module ties everything together into a single capstone project.
By the end you will have a complete, portfolio-ready test suite you can walk through in any interview — and a framework you can fork and apply to any new application.
🎬 Video coming soon
Capstone goal
Section titled “Capstone goal”The goal is to build a complete, professional test automation framework for a real e-commerce application — TestMarket Lab — by applying every technique from the course:
- Page Object Model with a shared
BasePageand per-page classes - Custom fixtures that inject page objects, API helpers, and authentication state
- API seeding to set up and tear down test data without relying on UI flows
- Auth
storageStateso each test starts already logged in — no login on every run - CI pipeline that runs on every push and pull request across multiple browsers
The result is not just a passing test suite. It is a demonstration that you can design, structure, and maintain automation at a professional level.
Remember: the capstone proves you can design and maintain a real framework — POM + fixtures + API seeding + auth storageState + multi-browser CI — not just make tests pass.
Recommended architecture
Section titled “Recommended architecture”A well-structured capstone framework looks like this:
capstone-tests/├── pages/│ ├── BasePage.js ← shared locator helpers + navigation│ ├── LoginPage.js│ ├── ProductPage.js│ ├── CartPage.js│ ├── CheckoutPage.js│ └── AdminDashboardPage.js├── fixtures/│ ├── auth.fixture.js ← injects customerPage / adminPage│ └── test-data.fixture.js ← injects apiHelper + testData factories├── utils/│ ├── ApiHelper.js ← wraps every REST endpoint│ └── TestData.js ← factory methods for test data├── tests/│ ├── auth/│ ├── shop/│ ├── admin/│ ├── api/│ └── visual/├── auth/│ ├── customer.json ← saved storageState (gitignored)│ └── admin.json├── playwright.config.js└── package.jsonWhy this structure?
Every directory has a single responsibility. Tests do not set up their own data or handle authentication — fixtures do that. Page objects do not contain assertions — tests do. This separation makes failures easy to diagnose and new features easy to add.
Remember: one responsibility per directory — fixtures own setup and auth, page objects own locators, tests own assertions; that separation is what makes failures easy to diagnose.
Test-plan and coverage thinking
Section titled “Test-plan and coverage thinking”Before writing a single test, answer two questions:
What to test
Section titled “What to test”Focus on critical user journeys first:
| Priority | Area | Why |
|---|---|---|
| High | Login / logout | Everything else depends on authentication |
| High | Add to cart + checkout | Core business flow; regressions here cost money |
| High | Product search and filtering | High traffic, often broken by frontend changes |
| Medium | User profile management | Lower risk but used frequently |
| Medium | Admin: add / edit / delete product | Admin errors affect all users |
| Low | Empty states and error pages | Important but low probability paths |
Risk-based prioritisation
Section titled “Risk-based prioritisation”Ask: “If this breaks in production, what is the impact?” A broken checkout is catastrophic. A broken “about us” page is low impact. Automate in that order.
A practical split for this project:
- UI tests — critical user journeys, visual regression on key pages
- API tests — data creation, validation rules, error cases
- Auth tests — one test per role to confirm the state file is valid
Aim for 40–50 tests total. Quality over quantity.
Remember: prioritise by risk — automate critical journeys (login, checkout, search) first, split UI vs API by what each tests best, and aim for ~40–50 quality tests, not quantity.
Structuring the repository
Section titled “Structuring the repository”Key files to set up before writing any tests
Section titled “Key files to set up before writing any tests”playwright.config.js — configure browsers, webServer, and CI-aware settings:
import { defineConfig } from '@playwright/test';
export default defineConfig({ testDir: './tests', fullyParallel: false, forbidOnly: !!process.env.CI, retries: process.env.CI ? 1 : 0, workers: 1, reporter: [['html', { open: 'never' }]], use: { baseURL: 'http://localhost:3000', trace: 'on-first-retry', screenshot: 'only-on-failure', }, webServer: { command: 'npm start', cwd: '../testmarket-lab', url: 'http://localhost:3000', reuseExistingServer: !process.env.CI, }, projects: [ { name: 'setup', testMatch: /.*\.setup\.js/ }, { name: 'chromium', use: { browserName: 'chromium' }, dependencies: ['setup'], }, { name: 'firefox', use: { browserName: 'firefox' }, dependencies: ['setup'], }, ],});pages/BasePage.js — every page class extends this:
class BasePage { constructor(page) { this.page = page; }
async navigate(path) { await this.page.goto(path); }
async waitForFlashMessage(type = 'success') { return this.page.locator(`.flash-${type}`).waitFor(); }}
module.exports = BasePage;fixtures/auth.fixture.js — creates storageState files on first run, reuses them afterwards:
const { test: base } = require('@playwright/test');const path = require('path');
const CUSTOMER_AUTH = path.resolve(__dirname, '../auth/customer.json');const ADMIN_AUTH = path.resolve(__dirname, '../auth/admin.json');
exports.test = base.extend({ customerPage: async ({ browser }, use) => { const context = await browser.newContext({ storageState: CUSTOMER_AUTH }); const page = await context.newPage(); await use(page); await context.close(); }, adminPage: async ({ browser }, use) => { const context = await browser.newContext({ storageState: ADMIN_AUTH }); const page = await context.newPage(); await use(page); await context.close(); },});Remember: set up the load-bearing files first — playwright.config.js (webServer + CI-aware settings + setup project), BasePage, and the auth fixture — before writing any feature tests.
Suggested build order
Section titled “Suggested build order”Work through the framework in this order. Each step builds on the previous one and gives you a working, runnable suite at every stage.
Step 1 — Bootstrap the project (30 min)
Section titled “Step 1 — Bootstrap the project (30 min)”mkdir capstone-tests && cd capstone-testsnpm init playwright@latestnpm installnpx playwright install chromiumCreate the directory structure: pages/, fixtures/, utils/, tests/, auth/.
Add auth/ to .gitignore — storageState files contain session cookies and must never be committed.
Step 2 — BasePage and first page object (45 min)
Section titled “Step 2 — BasePage and first page object (45 min)”Write BasePage.js. Then write LoginPage.js extending it. Write a single smoke test that opens the app and verifies the page title. Run it. Make it green.
Do not move on until this test passes reliably.
Step 3 — Auth setup project and storageState (45 min)
Section titled “Step 3 — Auth setup project and storageState (45 min)”Create tests/auth/auth.setup.js. This is a Playwright setup project — it runs before the main tests and saves customer.json and admin.json. Add this project to playwright.config.js with dependencies: ['setup'] on your main browser projects.
Run the suite twice. The second run should be noticeably faster — login is skipped for every test.
Step 4 — ApiHelper and test data fixture (60 min)
Section titled “Step 4 — ApiHelper and test data fixture (60 min)”Write utils/ApiHelper.js with methods for every endpoint the tests will use: createProduct, deleteProduct, getProducts, createOrder. Write utils/TestData.js with factory methods that return realistic test data objects.
Write fixtures/test-data.fixture.js that extends the base test with apiHelper and testData injected. Write two API tests using these fixtures. Run them.
Step 5 — Core UI page objects and tests (90 min)
Section titled “Step 5 — Core UI page objects and tests (90 min)”Add page objects for: ProductPage, CartPage, CheckoutPage. For each one, write at least two tests:
- Happy path (flow completes successfully)
- Edge case or validation (empty cart, invalid form input, etc.)
Use the customerPage fixture throughout — do not call page.goto('/login') in any test.
Step 6 — Admin tests (45 min)
Section titled “Step 6 — Admin tests (45 min)”Add AdminDashboardPage. Write tests that use the adminPage fixture to:
- Add a product
- Edit an existing product
- Delete a product (and verify it is gone)
Use API seeding (via apiHelper) to set up the product, then test the admin UI action on it.
Step 7 — Visual regression (30 min)
Section titled “Step 7 — Visual regression (30 min)”Add at least two visual snapshot tests for critical pages (home page, product detail page). Run them once to generate baseline images. Commit the baselines. Run again to confirm no diff.
Step 8 — CI pipeline (30 min)
Section titled “Step 8 — CI pipeline (30 min)”Add .github/workflows/playwright-tests.yml. Push to GitHub. Watch the first run. Fix any environment issues. Confirm the HTML report is uploaded as an artifact.
Remember: build in runnable slices — bootstrap → BasePage → auth setup → ApiHelper → UI tests → admin → visual → CI — keeping the suite green at every step.
Definition of done — self-assessment checklist
Section titled “Definition of done — self-assessment checklist”Before calling the capstone complete, verify each item:
Framework structure
- Directory tree matches the recommended architecture
-
BasePage.jsexists and every page class extends it - No raw
page.goto('/login')calls in test files — auth is handled by fixtures -
auth/is in.gitignore
Test coverage
- At least 5 critical UI user journeys covered
- At least 3 API tests (happy path + error case)
- Both customer and admin roles tested via storageState
- At least 2 visual regression baselines committed
Configuration and CI
-
playwright.config.jsusesprocess.env.CIforretries,forbidOnly,reuseExistingServer -
trace: 'on-first-retry'configured - CI workflow triggers on push and pull request
- Playwright report uploaded as artifact with
if: always()
Debugging
- You can open a trace file and navigate the action timeline
- You know how to interpret a CI log and download the report artifact
Interview readiness
- You can walk through the directory tree from memory
- You can explain why POM, why fixtures, why storageState
- You can say your project numbers without notes: page classes, tests, browsers, CI steps
- You have rehearsed a 30-second project pitch out loud
Remember: “done” is structure + coverage + CI + debugging fluency + an interview-ready walkthrough — tick every box before you call the capstone finished.
Project repositories
Section titled “Project repositories”The capstone application — TestMarket Lab — lives in its own public repository, separate from this course site. Clone it and run it locally to write your tests against.
| Repository | Link |
|---|---|
| Capstone application (TestMarket Lab) | testmarket-lab |
| Reference test framework | Published later — build your own capstone first |
BrauzerLab practice
Section titled “BrauzerLab practice”Reinforce two key techniques from this capstone in an interactive environment before building the real suite. No installation required.
Exercises
Section titled “Exercises”Work through these exercises in your capstone-tests project directory.
Exercise 1 — Add a new page object: ProfilePage (20 min)
Section titled “Exercise 1 — Add a new page object: ProfilePage (20 min)”The app has a profile page at /auth/profile with an editable name/email form. Add a page object for it.
Create pages/ProfilePage.js:
const BasePage = require('./BasePage');const { expect } = require('@playwright/test');
class ProfilePage extends BasePage { constructor(page) { super(page); this.nameInput = page.locator('#profile_name'); this.emailInput = page.locator('#profile_email'); this.saveButton = page.locator('button:has-text("Update Profile")'); }
async goto() { await this.navigate('/auth/profile'); }
async updateName(newName) { await this.nameInput.clear(); await this.nameInput.fill(newName); await this.saveButton.click(); }
async assertNameUpdated(expectedName) { await this.waitForFlashMessage('success'); await expect(this.nameInput).toHaveValue(expectedName); }}
module.exports = ProfilePage;Then write a test in tests/shop/profile.spec.js using the customerPage fixture. Confirm the test passes.
-
ProfilePage.jsexists and extendsBasePage - All three locators defined
-
updateName()clears, fills, and clicks - Test passes with
customerPagefixture
Exercise 2 — New API test: order creation (20 min)
Section titled “Exercise 2 — New API test: order creation (20 min)”Add a createOrder method to ApiHelper.js:
async createOrder(customerEmail, items) { const response = await this.request.post(`${this.baseURL}/api/orders`, { data: { email: customerEmail, items }, }); return { status: response.status(), body: await response.json() };}items is an array of { product_id, quantity } — grab a real product_id from GET /api/products first. The endpoint resolves prices from the products table and computes the order total.
Then write three tests in tests/api/orders-api.spec.js:
POST /api/orderscreates an order — expect status201and a body withidanditemsGET /api/ordersreturns the order just createdPOST /api/orderswith emptyitemsarray — expect status400
-
createOrdermethod added toApiHelper - All three tests pass
Exercise 3 — Debug with trace viewer (20 min)
Section titled “Exercise 3 — Debug with trace viewer (20 min)”- Introduce a deliberate bug in an existing test (change a locator or an assertion value).
- Run with
--trace on:npx playwright test --trace on --project chromium tests/shop/cart.spec.js - Open the trace:
npx playwright show-trace test-results/<test-name>/trace.zip - Answer: what action failed? What did the DOM look like? What was the error message?
- Fix the bug and confirm the test passes.
- Deliberate bug introduced
- Trace generated and inspected
- Root cause identified from the trace
- Bug fixed, test passes
Exercise 4 — Interview practice (15 min)
Section titled “Exercise 4 — Interview practice (15 min)”Say each answer out loud — not in your head.
Q1: “Tell me about a test automation project you’ve built.”
Write your 30-second pitch and practice it:
________________________________________________________________________________________________________________________________________________Checklist:
- Mentions Page Object Model with BasePage inheritance
- Mentions custom fixtures (auth + test data)
- Mentions API tests and database reset
- Mentions CI/CD with GitHub Actions
- Mentions trace viewer for debugging
- Includes concrete numbers (page classes, tests, browsers)
Q2: “What was the hardest problem you solved?”
Write your answer (auth state, flaky CI tests, locator strategy, webServer config — pick what’s real for you):
________________________________________________________________________________________________Q3: “How do you decide what to automate?”
Write your answer (critical journeys, risk-based priority, API vs UI tradeoffs):
________________________________________________________________________________________________Exercise 5 — Post-course roadmap (10 min)
Section titled “Exercise 5 — Post-course roadmap (10 min)”Fill in your personal next steps:
Short-term (next 2 weeks):
- Deploy the framework to GitHub and confirm CI runs on push
- Add 2 more page objects for uncovered pages
- Add 2 more API tests for uncovered endpoints
Medium-term (next 1–2 months):
- Apply the framework to a different web application (your own project or a work project)
- Add visual regression tests for 3 more critical pages
- Write a short blog post or LinkedIn article about what you built
Long-term (next 3–6 months):
- Apply for QA Automation / SDET roles with the framework on your GitHub profile
- Mentor someone else who is starting out
- Contribute Playwright tests to an open-source project
What comes next — congratulations
Section titled “What comes next — congratulations”This is the last module. You have now built a complete Playwright automation framework from scratch:
- Page Object Model with a shared
BasePageand inheritance - Custom fixtures that inject auth, API helpers, and test data
- API testing with a dedicated
ApiHelperand factory methods - Authentication state via
storageState— login once per role, reuse everywhere - Visual regression, accessibility, and mobile emulation
- CI/CD with GitHub Actions, multi-browser, trace capture, and report artifacts
That is a real portfolio project. Most candidates in interviews talk about theory. You have code you can walk through, numbers you can cite, and architectural decisions you can defend.
Your immediate next actions:
- Push the framework to a public GitHub repository
- Add the repository link to your resume and LinkedIn profile
- Practice your 30-second project pitch until it feels natural
Go build something great.
Want one more senior skill on top? Module 14 — Database verification
shows you how to prove an action persisted correctly by reading the database directly — the
“but did it actually save?” check that most suites, and most courses, skip. New to SQL? The
SQL basics reference covers the SELECT and JOIN queries it uses.