Skip to content

test: Playwright end-to-end, JMeter staged load and Lighthouse review - #6

Merged
nickgreengithub merged 1 commit into
mainfrom
test/e2e-load-a11y
Aug 16, 2026
Merged

nickgreengithub merged 1 commit into
mainfrom
test/e2e-load-a11y

Conversation

@nickgreengithub

Copy link
Copy Markdown
Owner

All three testing strands the rubric asks for, plus the fixes the results prompted. The repo had no tests before this.

Playwright — 10 tests, all passing

  • Server use case: create an announcement through the real authoring form, then verify via API and via the published RSS that it actually reached the feed. Then update, delete, and confirm it left the feed too. Plus validation: empty title → 422, unknown channel → 400.
  • Client use case: fetch and render a feed in the reader, switch channels, and confirm a mistyped channel 404s rather than serving a valid-but-empty feed.
  • One test polls a feed then asserts the dashboard count moved — the dashboard's core claim, verified end to end.

JMeter — staged load

Stage Clients Requests Failed Mean p99 Req/s
x1 1 60 0 4 ms 13 223
x10 10 300 0 4 ms 19 319
x100 100 1,500 0 3 ms 11 305
x1000 1,000 6,000 0 3 ms 9 201
x10000† 2,000 30,000 0 58 ms 149 499

Nothing degrades until 2,000 clients, where latency rises ~20× while throughput also rises and nothing fails — queueing, not breakage. Likely cause: SQLite serialising the RequestLog write.

Stated plainly in load/README.md: this is 10,000 client sessions at 2,000 concurrency, not 10,000 concurrent threads. The machine refuses to create more than ~4,100 OS threads (pthread_create ... EAGAIN). That capped the load generator, not the server — which never failed a request at any stage.

Lighthouse

Accessibility was already 100 on all four pages — and all four had a real defect. label-content-name-mismatch is reported but carries no score weight, so the number showed nothing. The header title announced "Home" while displaying the assessment title; every post row's aria-label was a subset of its visible text. Both broke voice control. Both fixed by letting visible content be the accessible name.

Also downscaled an oversized image: home 89 → 96, dashboard 94 → 97, client 92 → 94.

Full before/after reports in docs/lighthouse/; manual review (keyboard, contrast, reduced motion, table semantics) in docs/accessibility.md.

CI

Pull requests now run the e2e suite and upload the report on failure — main is gated, not trusted.

🤖 Generated with Claude Code

Three kinds of evidence that the application works, plus the changes the
results prompted. Before this the repository had no tests at all.

Playwright (10 tests, e2e/):
  server.spec.ts drives the authoring interface a person actually uses to
  create an announcement, then checks the API and the published RSS agree
  with what the interface claimed. A UI test asserting only on the UI can
  pass while the feed it is meant to publish stays empty. It also covers
  update, delete, and the two validation cases worth having — an empty title
  and a post filed to a channel that does not exist.
  client.spec.ts drives the subscriber half: fetch a feed in the reader,
  render its items, switch channel, and confirm a mistyped channel returns
  404 rather than a valid but permanently empty feed. One test polls a feed
  and then asserts the dashboard's count moved, which is the dashboard's
  central claim — that its figures are measured — checked end to end.

  Playwright starts a production build itself, because dev mode compiles on
  first request and the first assertion in every file would be waiting on a
  compiler.

JMeter (load/):
  One parameterised plan rather than five near-identical thread groups, which
  would drift apart the first time one was edited. Stages x1 through x1000
  are true concurrency; every stage returned 200 for every request.

  Nothing happens until two thousand clients. From 1 to 1000 the mean stays
  at 3-4 ms and the p99 improves as caches warm. At 2000 the mean rises to
  58 ms and the p99 to 149 ms while throughput rises to ~499 req/s — that is
  queueing, not breakage, and the likely constraint is SQLite serialising the
  RequestLog write on every request. It is the honest cost of the one-process
  design chosen in Assessment 2.

  The x10000 stage is 10,000 client sessions at 2,000 concurrency, not
  10,000 concurrent threads, because this machine will not create more than
  ~4,100 OS threads in one process — pthread_create fails with EAGAIN partway
  through the ramp. That is a limit of the load generator, not the server,
  which was never pushed to failure. load/README.md says so explicitly rather
  than quietly labelling the stage x10000 and moving on.

Lighthouse (docs/accessibility.md, docs/lighthouse/):
  Accessibility scored 100 on all four pages before any change — and all four
  were failing label-content-name-mismatch, which is reported but carries no
  weight in the score. Reading only the number would have found nothing.

  The site title announced "Home" while displaying the assessment title, and
  every post row's aria-label was a subset of the text visible inside it. In
  both cases a screen reader said something different from the screen, and
  someone driving the browser by voice could not activate the control by
  reading it aloud. Both now take their accessible name from their visible
  content, so the two cannot disagree.

  noticeboard.jpg was 900px and 306 KB in a 370px slot; it is now 740px and
  194 KB. Home went 89 to 96, dashboard 94 to 97, client 92 to 94.

CI now runs the end-to-end tests on every pull request and uploads the report
on failure, so main is gated rather than trusted.

Co-Authored-By: Claude <noreply@anthropic.com>
@nickgreengithub
nickgreengithub merged commit 81b9f1b into main Aug 16, 2026
3 checks passed
@nickgreengithub
nickgreengithub deleted the test/e2e-load-a11y branch August 16, 2026 23:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant