Skip to content

Repository files navigation

qa-playground

A Next.js application paired with a production-grade Playwright test suite — built as a working demonstration of authentication, session management, and role-based access control (RBAC) testing patterns.

The app is deliberately small; the tests are the point. The repository showcases how I structure E2E coverage for a real product: Page Object Model, authentication state reuse via storageState, tag-based test scoping, cross-browser execution, and CI integration.


What this project demonstrates

Area Implementation
Page Object Model LoginPage with readonly locators and intent-revealing methods (source)
Auth state reuse Setup project authenticates once per role, persists session to disk, downstream tests skip the login flow (source)
Custom test fixtures Type-safe extensions of testloginPage, loggedInUser, loggedInAdmin — composed and lazily built per test (source)
RBAC / route guards Verifies admin vs. user access boundaries, redirect rules, and unauthenticated access (source)
Session integrity JWT shape validation, localStorage persistence, expiry handling, logout cleanup (source)
Accessibility (WCAG 2.0 / 2.1 / 2.2 AA) Automated axe-core audits on every page — public, user-authenticated, admin-authenticated (source)
User-facing locators getByRole, getByLabel, getByTestId — never CSS or XPath
Test tagging @smoke, @functional, @security, @auth for scoped CI runs
Cross-browser Chromium, Firefox, WebKit
CI GitHub Actions, all browsers, retries on failure, HTML + JUnit + JSON reports
Code hygiene TypeScript strict mode (noUnusedLocals, noUnusedParameters), ESLint, Knip dead-code detection

Quick start

# Install
npm install
npx playwright install --with-deps

# Run the app + full test suite
npm test

# Or run a scoped subset
npm run test:smoke         # smoke checks only
npm run test:auth          # everything tagged @auth
npm run test:security      # security-focused tests
npm run test:functional    # functional coverage

The dev server boots automatically (see webServer in playwright.config.ts).


All test scripts

Script What it does
npm test Full suite, all browsers
npm run test:chromium Chromium only
npm run test:firefox Firefox only
npm run test:webkit WebKit only
npm run test:headed Chromium with visible browser
npm run test:ui Playwright UI mode (live test runner)
npm run test:debug Step-through debugger
npm run test:smoke @smoke tagged tests
npm run test:functional @functional tagged tests
npm run test:non-functional @non-functional tagged tests
npm run test:security @security tagged tests
npm run test:auth @auth tagged tests
npm run test:a11y @a11y accessibility audits (axe-core)
npm run test:report Open the last HTML report

Project-quality scripts:

Script What it does
npm run lint ESLint over the codebase
npm run typecheck TypeScript strict check, no emit
npm run knip Detect unused files, exports, and dependencies

Architecture notes

Authentication state reuse (storageState)

A dedicated setup project runs once before the main test run. It logs in as each role, captures localStorage to disk (playwright/.auth/admin.json, user.json), and exits.

Downstream tests declare which session they need:

test.use({ storageState: 'playwright/.auth/admin.json' });

That test starts already-authenticated — no login form, no redundant API calls. Force a logged-out state when needed:

test.use({ storageState: { cookies: [], origins: [] } });

This is the difference between a 30-test suite that takes 90 seconds and one that takes 6 minutes.

Page Object Model

LoginPage exposes the intent of a page, not its DOM. Tests read like product specs:

await loginPage.loginAsAdmin();
await expect(page).toHaveURL(/\/dashboard\/admin$/);

Locators live as readonly properties on the page object — defined once, reused everywhere, refactor-safe.

Tag-based filtering

Every test carries one or more tags:

test('admin can sign in', { tag: ['@smoke', '@functional', '@auth'] }, async () => { ... });

The npm run test:* scripts grep tags so CI can run a 30-second smoke check on every PR and a full regression on merge to main.

CI

.github/workflows/playwright.yml runs the full suite across all browsers on every push. Retries enabled (2× on CI), traces collected on first retry, HTML report uploaded as an artifact for download.


Test users

The app ships with two seeded accounts for testing:

Role Email Password Lands on
Admin admin@test.com Admin123! /dashboard/admin
User user@test.com User123! /dashboard/profile

Authentication is mocked with a fake JWT — the focus is on the test patterns, not on building a real auth backend.


Tech stack

App: Next.js 16 · React 19 · TypeScript 5 · Tailwind CSS 4

Tests: Playwright 1.59 · Page Object Model · storageState setup projects

Quality: ESLint · TypeScript strict · Knip · GitHub Actions


Repository layout

app/                       Next.js app (pages, layouts, components)
components/                Shared UI
lib/auth.tsx               Auth provider + fake-JWT mock
tests/
  auth.setup.ts            One-time login per role → storageState files
  auth-authorization.spec.ts   RBAC, route guards, redirects
  auth-session.spec.ts     Session persistence, JWT shape, expiry
  login.spec.ts            Login form happy path + credential rejection
  pages/auth/LoginPage.ts  Page Object for the login page
playwright.config.ts       Test runner config (projects, reporters, webServer)
.github/workflows/         CI pipeline
knip.json                  Dead-code detection config

Working with dependencies

CI runs npm ci on Linux, but local development happens on macOS / Windows. Some native packages (sharp, axe-core) ship platform-specific optional binaries — when npm install runs on macOS, it can omit the linux-x64 variants from package-lock.json, breaking CI with:

npm error Missing: @emnapi/runtime — Missing: @emnapi/core

After adding, removing, or updating any dependency, run:

npm install              # update node_modules for your local platform
npm run lock:refresh     # rewrite the lock with all platform variants included

Then commit package.json and package-lock.json together. CI will install deterministically with npm ci and the cross-platform lock keeps the runner happy.

License

MIT

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages