The parent web/tsconfig.json explicitly excludes the e2e folder because
Next.js doesn't compile Playwright specs. As a result, IDE TypeScript
language servers had no project context for e2e/*.spec.ts files and
false-positive on Node globals like Buffer, process.env, and the
NodeJS namespace — which several specs use to sign fake JWTs
(Buffer.from(...).toString('base64url')) or to read NOTELETT_E2E_*
override env vars.
The new web/e2e/tsconfig.json:
- Extends the parent web/tsconfig.json so all path aliases and
react-jsx config stay consistent.
- Adds 'types': ['node', '@playwright/test'] so Node globals and
Playwright fixtures resolve.
- Resets exclude: [] so the parent's e2e exclusion doesn't recurse
in and re-exclude the very directory this config is meant to
cover (which would otherwise yield TS18003 'No inputs found').
Verified:
- npx tsc --noEmit -p web/e2e/tsconfig.json → no output (clean)
- pnpm --filter @notelett/web run typecheck → still passes
(e2e remains out of the main typecheck as before)
- Playwright run unaffected (it uses tsx, not tsc, for runtime)
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