Root cause of bug: web Dockerfile copied .next/static to the wrong path
in the runtime stage. The Next.js 16 standalone server (CMD 'node
web/server.js' from /app/web) runs from /app/web/web/server.js because
'standalone' wraps the source directory. It serves /_next/static/* from
'./web/.next/static' (relative to the standalone server's location),
not from './.next/static' (which is what the previous COPY produced).
Symptom: in the deployed Docker stack at http://localhost:3050 every
client-side JS chunk under /_next/static/chunks/* returned HTTP 404
with content-type text/plain. The browser refused to execute the
chunks (strict MIME), so the SPA never hydrated. All Playwright tests
that ask for any dynamic UI text on a (app)/ page would time out
because AuthGuard never ran in the browser.
Discovery path: deployed compose stack via 'docker compose up -d
--build' + 'scripts/e2e-docker-test.sh' (backend API 9/9 ✓), then ran
Playwright against NOTELETT_WEB_PORT=3050. settings.spec failed with
'product configuration section' not visible. Page snapshot showed
just <skip-to-content link> + toast region — no other content. Console
logs revealed every /_next/static/chunks/* was 404 with text/plain.
'docker exec ls' showed BUILD_ID at /app/web/web/.next/BUILD_ID and
static at /app/web/.next/static — wrong path. Moved static into the
standalone tree and chunks now serve 200 with application/javascript.
Fix:
web/Dockerfile: change
COPY --from=builder /app/web/.next/static ./.next/static
to
COPY --from=builder /app/web/.next/static ./web/.next/static
with explanatory comment so this doesn't regress.
Test hardening (these tests were dev-server-only by accident — they
worked locally because Next.js dev did not enforce the same static
path layout; the bug above hid them in production builds too):
web/e2e/accessibility.spec.ts — 'focus-visible ring appears on tab
navigation' was navigating to /dashboard which AuthGuard correctly
redirects when unauthenticated, leaving the DOM empty (AuthGuard
returns null until verifySessionAndReadiness completes) so Tab
presses focused nothing. Switched to /login which is unauthenticated
by design and has known focusable form inputs.
web/e2e/settings.spec.ts — 'shows product configuration section'
expected /settings to render content without auth. Now obtains real
tokens from platform-service via API, seeds them via addInitScript,
and falls back to test.skip with a clear message if platform-service
is not reachable.
Verified:
- All 31 Playwright tests across navigation/accessibility/dashboard/
search/settings/smart-actions/reviews specs PASS against the
deployed Docker stack at :3050.
- 'pnpm run verify': backend 380/380, web 96/96, mobile 97/97.
- 'bash scripts/e2e-docker-test.sh': 9/9 backend API CRUD steps pass.
- 'curl -sI http://localhost:3050/_next/static/chunks/app/error-*.js'
now returns 200 + application/javascript.
Not migrated: e2e/release-flows.spec.ts and e2e/visual-regression.spec.ts
intentionally remain dev-server-targeted. release-flows.spec uses
page.route() to mock backend responses and is meant to test the UI in
isolation against a dev server. visual-regression.spec needs baseline
regeneration after the UI5-UI8 migration; this is a separate workstream
tracked in docs/UI_UX_PLATFORM_CORE_ROADMAP.md.
Audit of the full E2E suite (43 specs) surfaced four issues that were
hiding behind 'all 96/96 web unit tests pass' but actually meant the
browser-level coverage was broken end-to-end. All four are fixed and
the suite now passes 43/43.
1. Port conflict silently testing wrong app. playwright.config.ts hard-
coded baseURL=http://localhost:3000 with reuseExistingServer:true on
non-CI hosts. When the dev host had ANY service on :3000 (Grafana,
chronomind, etc), Playwright happily ran the entire E2E suite
against the wrong app and reported the unrelated failures as
'real'. Now honors NOTELETT_WEB_PORT env (default 3000) so a
contributor can opt into any free port and Playwright drives both
baseURL and the dev-server PORT consistently.
2. Missing test dependency. web/e2e/accessibility.spec.ts imports
@axe-core/playwright but web/package.json never declared it.
The accessibility coverage was DOA — every CI run that included
this spec would module-not-found-error before a single check ran.
Added @axe-core/playwright to devDependencies.
3. Mock that never fires. smart-actions.spec.ts 'history API mock
returns items' used page.route() to mock /api/note-prompts/history
then bypassed the mock entirely with page.request.get() (which uses
Playwright's separate request context, not the browser context that
page.route intercepts). The request went to the dev server and got
404. Replaced with page.goto + page.evaluate(fetch(...)) so the
browser-side fetch hits the page.route mock as intended.
4. Missing visual-regression baselines. visual-regression.spec.ts had
no committed baseline screenshots for dashboard / workspaces /
search. First run on a clean host always reported 'snapshot doesn't
exist, writing actual'. Generated and committed darwin baselines.
Verified end-to-end (NOTELETT_WEB_PORT=3050 against this host's free
port):
43 passed (34.8s)
Total test-tier counts on main now:
backend unit + integration (memory) 380/380
backend cosmos emulator (live) 4/4
web vitest 96/96
mobile vitest 97/97
web playwright e2e 43/43
---
TOTAL 620/620