// case study

Live: public demo

QA Automation Test

A cross-stack QA automation test-health dashboard aggregating results from seven independent test repos, Playwright, Selenium, Cypress, Cucumber BDD, and API testing, across Python and TypeScript. Each repo posts its own results to a small authenticated webhook right after CI runs, and the dashboard renders whatever is on disk, no rebuild, no redeploy, no polling.

Next.jsTypeScriptPlaywrightSeleniumCypressCucumberGitHub ActionsDockerTraefik

01 / Problem

Seven independent QA automation repos across five frameworks and two languages produce seven different places to check whether anything is actually passing. There was no single view of cross-stack health, and CI logs are not something anyone, including me, checks regularly. A pass/fail regression in one stack could sit unnoticed for days.

02 / Approach

A standardized results.json schema (stack, platform, pass/fail/skip counts, per-test detail) is shared across all seven repos. Each repo's CI workflow posts its results to a small authenticated webhook (POST /api/results/[stack]) right after every run, one bearer token per stack, compared with a timing-safe hash so a leaked token can only overwrite that one stack's file, never the git repo or any other stack. The dashboard itself is a Next.js app that reads results/*.json fresh on every request instead of baking data in at build time, groups cards by platform (web, API, with a mobile section ready for Appium/Maestro), color-codes by pass rate, and shows a per-test detail modal on demand. Adding a new stack later is just a new JSON file and a webhook call, no dashboard code changes.

03 / Engineering decisions

A scoped webhook token, not a git-push PAT.

Each source repo gets its own per-stack token that can only write one results file. A leaked token cannot touch the dashboard repo, other stacks, or anything else on the VPS, a narrower blast radius than the git-PAT-with-repo-write-access alternative.

Dynamic rendering, not a static export.

The homepage reads results/*.json on every request (export const dynamic = "force-dynamic") instead of freezing the data at build time. A webhook POST shows up on the next page load, with zero rebuild, redeploy, or restart.

Rate-limited and schema-validated at the edge.

Stack slugs are regex-locked to [a-z0-9-]+ (no path traversal), each stack is capped at 10 writes/minute, and every payload is checked against the same schema the dashboard renders, so a malformed or hostile request never reaches disk.

04 / Architecture

graph.log

$ ci_run_(7_independent_repos)

post_/api/results/[stack]_(per-stack_bearer_token)

├─ playwright-python

Playwright + pytest, Page Object Model

├─ playwright-js

Playwright + TypeScript, network mocking, parallel-safe

├─ selenium-python

Selenium 4 + pytest, Page Object Model

├─ cypress-js

Cypress + TypeScript, Page Object Model

├─ bdd-cucumber-playwright

Cucumber + Playwright, Gherkin feature files

├─ api-testing-python

pytest + requests + pydantic, schema validation

└─ api-testing-js

Jest + TypeScript, mirrors the Python suite

results/*.json → dashboard renders on next request

05 / Metrics

7
Source repos
5
Frameworks
0
Rebuilds per update