SDET Technical Challenge
Build a production-ready Playwright testing framework for the DemoQA Book Store — demonstrating framework design, fixture architecture, and automation best practices.
Challenge Overview
Design and build a production-quality Playwright testing framework from scratch — not just test scripts, but scalable infrastructure with fixtures, helpers, and CI/CD integration.
What this evaluates
- Architecture — separation of concerns, modularity
- Code quality — TypeScript, ESLint, no duplication
- Test quality — independence, cleanup, reliability
- Documentation — README, inline comments
- Best practices — env vars, .gitignore, commit hygiene
Suggested timeline
- Hours 1–2: Project setup, fixtures, configuration
- Hours 3–4: Helper functions and first tests
- Hours 5–6: Complete test suite, polish, docs
- Hours 7–9: Bonus challenges (optional)
Target application: DemoQA Book Store
- User registration and login
- Book catalog with search and filtering
- Book detail view (ISBN, author, publisher)
- Personal book collection management
- User profile page
- Full REST API (Account V1 + BookStore V1)
UI Base URL: https://demoqa.com/books
API Base URL: https://demoqa.com
Swagger: https://demoqa.com/swagger
Project Setup & Configuration
A properly configured project is the foundation of a maintainable framework. All tooling config must be committed — no "it works on my machine" setups.
Required deliverables
package.json— all dependencies declaredplaywright.config.ts— projects, retries, base URLtsconfig.json— path aliases (@/), strict modeeslint.config.js— Playwright recommended rules.prettierrc.json— formatting rules committed.env.example— all required vars documented.gitignore— excludes.env,node_modules, artifacts
Quick start
npm init playwright@latest npm install --save-dev @faker-js/faker dotenv mkdir -p fixtures functions tests/{ui,api,setup} reporter cp .env.example .env
Environment variables
.env.exampleBASE_URL=https://demoqa.com/books
API_BASE_URL=https://demoqa.com
TEST_USERNAME=your_test_user
TEST_PASSWORD=your_test_password
Key config points
- Path alias
@/maps to project root fullyParallel: truefor fast CI runsforbidOnly: !!process.env.CI— blocks committedtest.onlyretries: process.env.CI ? 1 : 0trace: 'on-first-retry'for debugging failures- A
setupproject saves auth state for dependent suites
Never commit .env. Only .env.example with placeholder values goes to the repo. Credentials in version control is an automatic fail.
Custom Fixtures
Fixtures are the backbone of the framework. BasePage extends Page via a Proxy — tests get native Playwright methods plus domain-specific helpers from one object.
File structure
BasePage required methods
class BasePage { async getAPI(): Promise<APIRequestContext> async getUserId(): Promise<string> async getUserData(): Promise<UserData> }
Usage in tests
test('example', async ({ customPage, apiContext }) => { await customPage.goto('/books') const api = await customPage.getAPI() const userId = await customPage.getUserId() })
BasePage — Proxy pattern
fixtures/BasePage.tsexport class BasePage { public page: Page private api: APIRequestContext | null = null constructor(page: Page) { this.page = page // Proxy delegates unknown props to the inner Page return new Proxy(this, { get(target, prop) { return prop in target ? target[prop] : target.page[prop] } }) } async getAPI(): Promise<APIRequestContext> { if (!this.api) { // DemoQA stores JWT in localStorage as userInfo.token const userInfo = await this.page.evaluate(() => JSON.parse(localStorage.getItem('userInfo') ?? '{}') ) this.api = await this.page.request.newContext({ extraHTTPHeaders: { 'Authorization': `Bearer ${userInfo.token}` } }) } return this.api } }
Helper Function Library
Build at least 3 modules with 10+ total functions. Helpers must support function overloading — the same call works from both BasePage and BaseAPI contexts.
Required modules
functions/auth.ts
registerUser(api, userData)generateToken(api, credentials)getUserProfile(api, userId)deleteUser(api, token, userId)
functions/books.ts
listBooks(api)getBook(api, isbn)addToCollection(api, userId, isbn)removeFromCollection(api, userId, isbn)clearCollection(api, userId)
functions/testData.ts
uniqueUsername()uniquePassword()generateUserData()buildAddBookPayload(userId, isbn)
File structure
Key patterns required
- Function overloading — same signature for Page and API contexts
- Proper TypeScript types — no bare
anyin signatures - Faker.js for unique, realistic test data
- JSDoc comments on complex functions
Overloading example: addToCollection(page, userId, isbn) and addToCollection(apiContext, userId, isbn) both work — the implementation resolves the API context from either fixture type.
Test Suite
Implement at least 5 independent tests. Each must be parallel-safe, leave no trace, and carry a unique annotation ID. Minimum 3 meaningful assertions per test.
Required test coverage
- Authentication — register, login, invalid credentials
- Book catalog — list all books, search, book detail view
- Collection management — add, remove, clear, duplicate handling
- User profile — correct data displayed after login
- E2E flow — register → add book → verify → remove → verify gone
Test design principles
- Every test creates its own data — no shared state
- Cleanup runs even on assertion failure
- Parallel-safe: unique credentials per run
- Unique annotation IDs on all tests
- Naming:
Domain > Feature > Action
File structure
Example test structure
tests/ui/books.spec.tstest('Books > Collection > Add book and verify in profile', { annotation: { type: 'ID', description: 'COLL-001' } }, async ({ customPage: page }) => { // Arrange const user = generateUserData() await registerUser(page, user) const api = await page.getAPI() const userId = await page.getUserId() const books = await listBooks(api) const target = books[0] // Act await addToCollection(api, userId, target.isbn) // Assert const profile = await getUserProfile(api, userId) expect(profile.books).toHaveLength(1) expect(profile.books[0].isbn).toBe(target.isbn) await page.goto('/profile') await expect(page.getByText(target.title)).toBeVisible() // Cleanup await clearCollection(api, userId) })
Documentation
Documentation is a first-class deliverable. A reviewer must be able to clone the repo, follow the README, and run tests successfully in under 10 minutes — without asking you anything.
README.md must include
- Project overview and purpose
- Prerequisites — Node version, OS
- Installation steps (
npm ci, Playwright install) - Environment variable configuration
- How to run: all tests, single file, UI mode
- Project structure explanation
- Custom fixtures usage examples
- Helper functions overview
- Coding standards followed
Test run commands to document
# Run all tests npx playwright test # Run a single spec npx playwright test tests/ui/books.spec.ts # Interactive UI mode npx playwright test --ui # View HTML report npx playwright show-report
Additional documentation
- JSDoc on complex helper functions
- Inline comments on non-obvious logic (the why, not the what)
- All env vars in
.env.examplewith descriptions
Test the README yourself: Clone your own repo into a fresh directory and follow the instructions from scratch. If you get stuck, so will the reviewer.
Bonus Challenges
Complete any of these to demonstrate advanced SDET capabilities. Each bonus must be production-quality — a working proof-of-concept, not placeholder code.
Custom Reporter
Track test history across runs, detect flaky tests (passed after retry), calculate pass rates, and generate an HTML report with a trend graph and filterable test list.
Deliverable: reporter/reporter.ts + sample HTML report
CI/CD Integration
GitHub Actions workflow running tests on PRs and nightly. Uploads artifacts (reports, traces, videos). Publishes HTML report to GitHub Pages.
Deliverable: .github/workflows/playwright.yml + screenshot of passing run
Advanced API Testing
Schema validation against the Swagger spec, response time benchmarking, error handling tests (401/403/404), request/response logging, and API mocking for edge cases.
Deliverable: tests/api/advanced.api.test.ts with 5+ tests
Visual Regression
Screenshot capture for book list, book detail, and profile pages — comparison against stored baselines with visual diff reports and masking for dynamic content.
Deliverable: Visual test suite + baseline images + update instructions
Test Data Factory
Faker.js factory pattern for domain objects (users, book payloads), data pool with isolation between parallel workers, and a cleanup strategy that handles interrupted test runs.
Deliverable: functions/testDataFactory.ts + cleanup strategy
Evaluation Criteria
Submissions are scored across five dimensions. Architecture and code quality account for more than half the total — a passing test suite with poor architecture will not score well.
Architecture & Design
30%- Proper separation of concerns
- Modularity and reusability
- Framework scalability
- Appropriate design patterns
- Clean project structure
Code Quality
25%- TypeScript best practices (no bare
any) - Zero ESLint warnings or errors
- Consistent Prettier formatting
- Proper error handling
- No code duplication (DRY)
Test Quality
25%- Independent and parallel-safe
- Proper cleanup (no data left behind)
- Meaningful, non-trivial assertions
- Robust wait strategies
- Consistent pass rate (3+ runs)
Documentation
10%- Clear, complete README
- Code comments where needed
- Helper function docs
- Setup instructions verified to work
Best Practices
10%- No hardcoded waits
- Environment variable usage
- Secure credential handling
- Git hygiene (.gitignore, commits)
- Professional commit messages
Submission Guidelines
Submit via a public GitHub repository with a clear commit history — not a single giant commit. Include the email below with your repo link, optional demo video, and hours spent.
Repository requirements
- Public GitHub repository
- Clear commit history (not one giant commit)
- No credentials committed (only
.env.example) - Tests passing on the final commit
- README with working setup instructions
- All dependencies in
package.json
Optional: demo video (5–10 min)
- Architecture walkthrough
- Tests running in parallel
- Custom fixtures usage demo
- Key design decisions explained
Do
- Commit frequently
- Test setup instructions
- Run suite 3+ times
- Use TypeScript generics
Don't
- Commit
.envfiles - Leave
console.login code - Submit failing tests
- Ignore ESLint errors
Submission email
Subject: SDET Technical Challenge — [Your Name]Name: [Your Full Name]
GitHub Repository: [URL]
Demo Video: [YouTube / Loom URL]
Estimated Hours: [X hours]
Bonus Challenges Completed:
[ ] Custom Reporter
[ ] CI/CD Integration
[ ] Advanced API Testing
[ ] Visual Regression Testing
[ ] Test Data Factory
Additional Notes:
[Trade-offs made and what you'd improve
with more time]
Do not submit with failing tests. Run the full suite 3+ times before submitting. A flaky test that fails intermittently in CI reflects as poorly as a consistently failing test.