SDET Onboarding Program
One intensive month to become an SDET capable of building production-ready test frameworks from scratch, with TypeScript, Playwright, and a quality-first mindset.
The testing challenge
ParaBank is a full-featured demo banking application with both a web UI and a REST API. It supports user registration, account management, fund transfers, bill payment, and transaction history — making it a rich target for end-to-end quality coverage. Your job is to build a production-ready Playwright framework that covers API correctness, UI flows, edge cases, and integrates into CI/CD.
What you will build
- A Playwright TypeScript framework — a single project that evolves across all 4 weeks
- A custom fixture layer: BasePage (Proxy pattern), BaseAPI, and a unified
test.extend()registry - Helper modules for auth, accounts, transfers, and typed test data with overloading
- 30+ tests across API, UI, edge cases, and banking domain flows
- A custom reporter, CI/CD pipeline, and advanced testing patterns with real observability
One framework — evolving every week
What you will build and who you will become
The entire program revolves around one application: ParaBank. You don't build the app — you test it. Your framework starts bare and grows into a complete, maintainable, CI-integrated quality layer.
What you will build
- A TypeScript-first Playwright framework with strict mode, ESLint, and Prettier configured from day one
- A BasePage Proxy pattern that wraps
Pageand exposes named actions for every page in ParaBank - A BaseAPI class and typed fixture layer using
test.extend()— shared across every test file - Domain-organized helper modules: auth, accounts, transfers, and testData with TypeScript overloading
- A custom Playwright reporter that outputs structured JSON with retry counts and flaky detection
- A GitHub Actions CI/CD pipeline with sharding, artifact upload, and environment-aware configuration
Who you become by the end
- You design test frameworks that are maintainable, scalable, and trustworthy in production
- You catch bugs at the right layer: API, UI, and edge case — with minimal overlap and maximum signal
- You understand Playwright internals deeply enough to extend, debug, and optimize the framework under pressure
Golden rule: no throwaway test files. Every test written in week 1 lives in the week 4 framework — refactored, not replaced.
Foundation & First Tests
Set up the framework skeleton and write your first tests directly against ParaBank — no abstractions yet. The goal is to get Playwright running against both the UI and the REST API before any architecture is introduced.
Required project structure — week 1 baseline
Environment & setup deliverables
- Initialize project with
npm init playwright@latestand TypeScript template - Configure
tsconfig.jsonwithstrict: true,module: commonjs,target: ES2020 - Configure ESLint with TypeScript plugin and
@typescript-eslint/recommendedruleset - Configure Prettier with consistent rules (single quotes, trailing comma, 2-space indent)
playwright.config.ts:baseURLfromprocess.env.BASE_URL, retries set to 1 in CI- Add
.env+.env.example:BASE_URL,API_BASE_URL,TEST_USERNAME,TEST_PASSWORD - Add
dotenvloading inplaywright.config.tsviarequire('dotenv').config() - Scripts in
package.json:test,test:headed,test:ui,lint - Add
.gitignore: node_modules, playwright-report, test-results, .env - README with quick start, prerequisites, and test run instructions
First test deliverables
- AUTH-001: valid credentials → account overview page visible, welcome message present
- AUTH-002: invalid credentials → error message visible, still on login page
- AUTH-003: unauthenticated access to account page → redirect to login
- ACC-001: REST API — GET accounts for authenticated customer → list returned, at least one account
- ACC-002: UI — open new CHECKING account → confirmation message and new account in list
- TRN-001: UI — view transaction history for default account → table visible, transactions listed
- Each test follows Arrange-Act-Assert structure — setup, action, assertion clearly separated
- No shared state between tests — each test operates independently
- All tests pass with
npx playwright testagainst the running ParaBank instance
Week 1 deliverable: a running Playwright project with TypeScript, linting, env config, and 9 passing tests. No fixtures, no helpers — raw request and page objects only. The mess you feel here is intentional: week 2 fixes it.
Framework Architecture
Refactor the week-1 tests into a proper framework. Introduce the BasePage Proxy pattern, a BaseAPI abstraction over the ParaBank REST API, and a typed fixture layer that all future tests will share.
Custom fixtures — test.extend()
- Create
BasePageclass using the Proxy pattern: wrapspage, delegates allPagemethods transparently, exposes named high-level actions - Create
LoginPage extends BasePagewithlogin(username, password),expectOverview(),expectError() - Create
AccountsPage extends BasePagewithexpectAccountList(),openNewAccount(type, fromId),clickAccount(id) - Create
TransferPage extends BasePagewithtransfer(fromId, toId, amount),expectSuccess() - Create
BaseAPIclass: wrapsAPIRequestContext, sets base URL, adds typedget/postmethods targeting ParaBank REST endpoints - Create
fixtures/index.ts: registerloginPage,accountsPage,transferPage,apiviabase.extend<MyFixtures>() - Re-export
expectfrom fixtures file so all tests import from one place
Helper modules
- Create
helpers/auth.ts:login(api, username, password)→ returns session-scoped customer ID - Create
helpers/accounts.ts:getAccounts(api, customerId),openAccount(api, customerId, type, fromId),getBalance(api, accountId) - Create
helpers/transfers.ts:transfer(api, fromId, toId, amount),getTransactions(api, accountId) - Create
helpers/testData.ts:uniqueUsername(),uniquePassword(),buildRegistrationData(),buildTransferPayload(fromId, toId, amount) - Add TypeScript function overloading to
buildTransferPayload:(fromId, toId, amount),(fromId, toId)→ defaults to $100,(overrides: Partial<Payload>)→ merged - All helpers are fully typed: no
any, return types declared, input params validated by TypeScript - Refactor week-1 tests to use fixtures and helpers — no raw
pageorrequestin test bodies - ESLint passes with zero warnings on the entire project after refactor
Week 2 deliverable: a proper framework layer. Test bodies read like business actions — no Playwright internals leak into specs. All week-1 tests still pass after refactoring.
Test Coverage
Expand the suite to cover the full ParaBank surface: account management, fund transfers, bill payment, transaction filtering, loan requests, and end-to-end banking flows. Hygiene and isolation are non-negotiable.
Test hygiene rules (enforce these)
- No shared mutable state — each test registers its own user or uses isolated accounts
- Cleanup in
afterEachorafterAll— do not rely on server resets between runs - Selectors follow priority: role > label > test-id > CSS — never brittle XPath
- Wait strategies: use Playwright's built-in auto-waits, never
waitForTimeout - Test naming follows
Domain > Feature > Action: e.g.Accounts > Transfer > Insufficient funds returns error
Account & transfer edge cases
- ACC-003: transfer funds between own accounts → both balances updated correctly
- ACC-004: transfer amount exceeding balance → error response, balances unchanged
- ACC-005: open SAVINGS account → new account appears in GET /accounts with correct type
- ACC-006: transfer with invalid amount (negative) → API returns 4xx, no transaction created
Bill payment tests
- BILL-001: pay a bill with valid payee and amount → success confirmation visible, transaction in history
- BILL-002: pay a bill with missing payee name → validation error, no transaction created
Transaction history tests
- TRN-002: filter transactions by date range → only transactions within range returned
- TRN-003: filter transactions by amount range → correct subset returned
- TRN-004: GET /accounts/{id}/transactions → response matches typed TypeScript interface shape
Loan request tests
- LOAN-001: request loan with sufficient income → approved status in response
- LOAN-002: request loan with 0 down payment and high amount → denied or error response
E2E banking flow tests
- E2E-001: register new user → login → open account → transfer funds → verify both balances updated
- E2E-002: login → navigate to bill payment → pay bill → verify transaction appears in history
- E2E-003: login → request loan → verify new loan account appears in account list
Week 3 deliverable: 30+ passing tests covering API correctness, UI flows, edge cases, and complete banking E2E journeys. Zero flaky tests. ESLint clean.
Production QA
Elevate the framework from a passing test suite to a production-grade quality system. Choose two of three specialization areas — Reporter, CI/CD, and Advanced Testing — and deliver real depth in each.
Custom Reporter
Implement a Reporter interface in TypeScript. Emit structured JSON with per-test retry counts, flaky detection, status breakdown, and execution metadata. Output a final summary to the console.
CI/CD Pipeline
GitHub Actions workflow with 3 shards, HTML artifact upload, environment matrix (staging / prod), and Slack or email notification on failure. Playwright config adapted for each environment.
Advanced Testing
Network interception (page.route()), visual regression snapshots for key ParaBank pages, accessibility audits with @axe-core/playwright, and performance budget assertions.
Reporter deliverables
- Implement
CustomReporterclass implementing Playwright'sReporterinterface - Track per-test: title, status, duration, retry count, flaky flag
- Output
test-results/report.jsonononEnd()— structured, machine-readable - Console summary: total / passed / failed / flaky / skipped with indicators
- Register reporter in
playwright.config.tsalongside the built-in HTML reporter
CI/CD deliverables
- GitHub Actions workflow: triggers on push to
mainand on PRs - 3-shard matrix job:
--shard=1/3,2/3,3/3— parallel execution - Upload
playwright-report/as artifact per shard — downloadable from Actions UI - Environment matrix: staging and production with separate
BASE_URLsecrets - Playwright config:
retries: 2in CI,workers: 1on production shard to avoid side effects
Week 4 deliverable: a framework a team could hand off to a CI system and trust. Reporter surfaces flaky tests. Pipeline is green. Every architectural decision is explainable under review.
- Initialize project with
npm init playwright@latestand TypeScript template - Configure
tsconfig.jsonwithstrict: true - Configure ESLint with
@typescript-eslint/recommendedruleset - Configure Prettier with consistent project-wide rules
playwright.config.ts:baseURLfrom env, retries in CI- Add
.env+.env.example:BASE_URL,API_BASE_URL,TEST_USERNAME,TEST_PASSWORD - Add
dotenvloading inplaywright.config.ts - Scripts in
package.json: test, test:headed, test:ui, lint - Add
.gitignore: node_modules, playwright-report, test-results, .env - README with quick start, prerequisites, and test run instructions
- AUTH-001: valid login → account overview visible
- AUTH-002: invalid credentials → error visible, on login page
- AUTH-003: unauthenticated access → redirect to login
- ACC-001: REST API — GET accounts → list returned
- ACC-002: open new CHECKING account → confirmation and list updated
- TRN-001: view transaction history → table visible
- Tests follow Arrange-Act-Assert structure
- No shared state between tests
- All week-1 tests pass with
npx playwright test
- Create
BasePagewith Proxy pattern wrappingpage - Create
LoginPage extends BasePagewith named action methods - Create
AccountsPage extends BasePagewith named action methods - Create
TransferPage extends BasePagewith named action methods - Create
BaseAPIwrappingAPIRequestContextfor ParaBank REST endpoints - Create
fixtures/index.tswithbase.extend<MyFixtures>() - Re-export
expectfrom fixtures file - Create
helpers/auth.ts:login(api, username, password) - Create
helpers/accounts.ts: getAccounts, openAccount, getBalance - Create
helpers/transfers.ts: transfer, getTransactions - Create
helpers/testData.ts: uniqueUsername, buildRegistrationData, buildTransferPayload with overloads - All helpers fully typed — no
any - Refactor week-1 tests to use fixtures — no raw
pageorrequestin test bodies - Refactor week-1 tests to use helpers — no inline data construction
- ESLint passes with zero warnings after refactor
- No shared mutable state — each test uses isolated accounts or users
- Cleanup in
afterEachorafterAllwhere applicable - Selectors follow priority: role > label > test-id > CSS
- No
waitForTimeout— only Playwright auto-waits - Test naming follows
Domain > Feature > Actionconvention - ACC-003: transfer between own accounts → both balances updated
- ACC-004: transfer exceeds balance → error, balances unchanged
- ACC-005: open SAVINGS account → correct type in GET /accounts
- ACC-006: transfer negative amount → 4xx, no transaction created
- BILL-001: pay valid bill → transaction in history
- BILL-002: pay bill with missing payee → validation error
- TRN-002: filter by date range → correct transactions returned
- TRN-003: filter by amount range → correct subset returned
- TRN-004: transactions response matches typed TypeScript interface
- LOAN-001: request loan with sufficient income → approved
- LOAN-002: request loan with 0 down payment → denied/error
- E2E-001: register → login → open account → transfer → verify balances
- E2E-002: login → bill payment → verify transaction in history
- E2E-003: login → request loan → new loan account in account list
- Implement
CustomReporterclass implementing Playwright'sReporterinterface - Track per-test: title, status, duration, retry count, flaky flag
- Output
test-results/report.jsonononEnd()— structured JSON - Console summary: total / passed / failed / flaky / skipped
- Register reporter in
playwright.config.tsalongside HTML reporter
- GitHub Actions workflow: triggers on push to
mainand on PRs - 3-shard matrix job:
--shard=1/3,2/3,3/3 - Upload
playwright-report/as artifact per shard - Environment matrix: staging and production with separate secrets
- Playwright config adapted for CI:
retries: 2,workers: 1on prod shard
- Network interception:
page.route()to simulate API error responses for transfer failures - Visual regression: snapshot tests for login, account overview, and transfer confirmation pages
- Accessibility audit:
@axe-core/playwrighton login, accounts, and bill payment pages - Performance budget: assert page load < 3s via
page.evaluate()+PerformanceObserver - Storage state: save authenticated state via
storageStateto skip login in test setup - Global setup: seed required test accounts in
globalSetup.tsbefore suite runs
Application Under Test — ParaBank
ParaBank simulates a full retail banking application with both a UI and a complete REST API. Understanding both layers is critical to testing at the right level and catching real bugs.
System overview
Key REST endpoints
GET /login/{username}/{password}→ customer object withidGET /customers/{id}/accounts→ array of account objectsPOST /createAccount?customerId&newAccountType&fromAccountId→ new accountGET /accounts/{id}/transactions→ transaction historyPOST /transfer?fromAccountId&toAccountId&amount→ transfer resultPOST /billpay?accountId&amount+ payee body → payment resultPOST /requestloan?customerId&amount&downPayment&fromAccountId→ loan decision
Key behaviors to verify
- Balance consistency: after any transfer, debit + credit must equal the original amounts
- Transaction ordering: transactions appear chronologically in history
- Session isolation: UI session and REST API Basic Auth are independent — design fixtures to handle both
- Account types: CHECKING (
0) and SAVINGS (1) behave differently for loan eligibility - Error handling: insufficient funds, invalid account IDs, and missing payee fields all return distinct error shapes
Test design tip: ParaBank's REST API and UI share the same business logic. Use the API to set up state fast, then verify behavior through the UI — don't duplicate the same assertion at both layers.
Playwright — Core
Playwright Docs
Getting started, test structure, fixtures, locators, and the test runner API — the primary reference for the entire program.
playwright.dev/docs/intro DocsPlaywright Fixtures
How test.extend() works, fixture scopes (test/worker), and the dependency injection model. Required reading for week 2.
playwright.dev/docs/test-fixtures DocsCustom Reporters
The Reporter interface, onBegin / onTestEnd / onEnd lifecycle hooks, and how to register a custom reporter in config.
playwright.dev/docs/test-reporters DocsAPIRequestContext
The request object used in BaseAPI. Methods: get, post, put, delete, fetch. Core of the REST API test layer.
playwright.dev/docs/api/class-apirequestcontext DocsNetwork Interception
page.route(), request interception, response mocking, and abort conditions — used in week 4 advanced testing.
playwright.dev/docs/network DocsTest Sharding & CI
How --shard works, merging blob reports across shards, and GitHub Actions matrix strategy for parallel test runs.
playwright.dev/docs/test-shardingTypeScript & Node
TypeScript Function Overloads
The function overloading syntax used in buildTransferPayload and other helper functions in week 2.
typescriptlang.org/docs/handbook/functions DocsTypeScript Strict Mode
What strict mode enables: strictNullChecks, noImplicitAny, strictFunctionTypes. Mandatory configuration for the framework.
typescriptlang.org/tsconfig#strict DocsESLint Configuration
How to configure ESLint with @typescript-eslint plugin, recommended rules, and custom overrides.
eslint.org/docs/latest/use/configureCI/CD & GitHub Actions
GitHub Actions Matrix
Matrix strategy for running jobs across multiple configurations — used for the shard and environment matrix in week 4.
docs.github.com/en/actions DocsGitHub Artifacts
Upload and download artifacts between jobs — used to collect playwright-report/ from each shard.
docs.github.com/en/actions/artifacts DocsPlaywright CI Introduction
Official guide for running Playwright in CI with recommended config settings for retries, workers, and reporters.
playwright.dev/docs/ci-intro