Adds the last deferred #299/#363 follow-up: the flows that CANNOT be expressed
as curl calls. Scope rule (the durable part) — assert only what the curl
harness structurally cannot reach:
1. client-side form validation (the Setup confirm-password gate is pure React
state and makes no request, so there is no HTTP contract to assert)
2. AuthGate's RENDERED states (Setup vs Login vs app)
3. the session cookie authenticating the SPA's OWN /api XHRs — curl proves the
cookie works for curl, not that the app sends it
4. sign-out through the UserMenu back to the login gate
New: web/e2e/boot-gate.spec.ts, web/playwright.config.ts, scripts/e2e-ui.sh
(owns the whole lifecycle: fresh config dir -> boot -> specs -> always kill).
Runs as a second step of the EXISTING advisory `functional-e2e` job rather than
a new job: the dominant cost there is `npm ci` + the Release build, both already
done, so this adds ~5s instead of duplicating a heavy job. It boots its own
fresh instance on port 8410 because the first spec asserts the one-shot Setup
gate that the curl step has already claimed on its config dir.
Determinism (the issue asked for it explicitly): `serial`, `workers: 1`,
`retries: 0` even in CI — a retry would let a flaky flow merge looking green.
Measured 5 consecutive clean runs, ~2s each.
Pins all five `container:` jobs to the toolchain image built by the preceding
commit, which bakes `chromium-headless-shell`.
Non-obvious coupling fixed: vitest's default include glob would have collected
web/e2e/*.spec.ts and run it under jsdom. Excluded `e2e/**` by spreading
`configDefaults.exclude` rather than narrowing `include` to `src/**`, because
web/scripts/ holds a real vitest test an src-only include would silently stop
running.
`RebuildSearchIndexHandler` logs one of two mutually-exclusive lines just before
`SystemStartup.SearchIndexIsReady()`:
fresh config -> "Done migrating search index in {Duration}"
reused config -> "Search index is already version {Version}"
The probe watched only the first, so a reused dir waited out the full 120s
timeout and then killed a perfectly healthy server. Widened to a `grep -Eq`
alternation; the handler's if/else is exhaustive, so the pair covers every path
to readiness.
Verified with a negative control: on a reused dir the server is ready in 2s via
the "already version" line, and the OLD probe string is genuinely ABSENT from
that run's log — so the old code would have hung, i.e. the fix is load-bearing
rather than incidentally passing.
The "prefer a fresh config dir" guidance stays: that guards state bleed, which
is a separate concern from the probe hanging.
- `wait "$PID"` in the cleanup trap was a NO-OP: the server is a grandchild
(launched in e2e-local.sh's subshell, which then exits), so `wait` fails
instantly and was swallowed by `|| true` — cleanup did not actually ensure the
port was released, exactly what its comment claimed. Replaced with a bounded
`kill -0` poll, then SIGKILL.
- Added a port pre-flight check: previously an occupied port surfaced as a 120s
readiness timeout that reads like a broken build. Now fails in 0s naming the
PIDs, and warns against blanket-killing `dotnet ErsatzTV.dll` (that reaps
other sessions' servers).
- UI-E2E: 5x clean (3 specs, ~2s); back-to-back runs pass with no manual cleanup
- curl harness unaffected by the boot-script change: 45/45 PASS
- web: 983 tests / 105 files green; typecheck + lint clean
- vitest collection verified: excludes web/e2e, still collects web/scripts
- Dockerfile sequence + browser launch validated verbatim in a container on the
real amd64 base before committing; chromium launches as root with NO sandbox
opt-out needed
- decisions validator green; catalog regenerated
- docs/decisions.md TOC repaired: it had drifted to 69 of 97 records and held a
dangling anchor to the #72 record that #415 superseded into archive/.
Regenerated with a generator validated against the 68 existing anchors (0
mismatches) -> 97/97, no dangling, no duplicates.
Docs: docs/e2e-local.md (new "UI-E2E harness" section), docs/ci-cd.md (toolchain
image + UI-E2E step), docs/testing.md, docs/README.md, docs/decisions.md
(new `ci.ui-e2e-harness` record; `ci.functional-e2e-harness` amended — its Rule
said "curl-only", now accurate).
Refs #445 #533
83 lines
2.0 KiB
Plaintext
83 lines
2.0 KiB
Plaintext
*.swp
|
|
*.*~
|
|
project.lock.json
|
|
.DS_Store
|
|
# Code-coverage output (dotnet test --results-directory ./coverage, ersatztv#15)
|
|
/coverage/
|
|
*.pyc
|
|
.worktrees/
|
|
|
|
# Claude Code
|
|
.mcp/
|
|
.mcp.json
|
|
.agents/
|
|
plugins/
|
|
nupkg/
|
|
|
|
# Visual Studio Code
|
|
.vscode
|
|
|
|
# Rider
|
|
.idea
|
|
|
|
# User-specific files
|
|
*.suo
|
|
*.user
|
|
*.userosscache
|
|
*.sln.docstates
|
|
|
|
# Build results
|
|
[Dd]ebug/
|
|
[Dd]ebugPublic/
|
|
[Rr]elease/
|
|
[Rr]eleases/
|
|
x64/
|
|
x86/
|
|
build/
|
|
bld/
|
|
[Bb]in/
|
|
[Oo]bj/
|
|
[Oo]ut/
|
|
msbuild.log
|
|
msbuild.err
|
|
msbuild.wrn
|
|
|
|
# Visual Studio 2015
|
|
.vs/
|
|
|
|
*.sqlite3*
|
|
# Core dumps. MUST stay anchored/qualified (ersatztv#485): a bare `core` matches any path
|
|
# component named `core`, and on a case-insensitive filesystem (macOS default) that includes
|
|
# every `*/Core/` source directory — silently excluding NEW files under e.g.
|
|
# ErsatzTV.Scanner/Core/ from `git add -A`. Tracked files are unaffected, so the symptom is a
|
|
# clean local build and a CI checkout that fails to compile.
|
|
#
|
|
# Both patterns are anchored to the repo root ON PURPOSE — an unanchored `core.[0-9]*` would
|
|
# re-introduce exactly the silent-exclusion class this fixes. Tradeoff, accepted: a dump written
|
|
# into a SUBdirectory is no longer ignored (the old bare `core` did catch those). In practice the
|
|
# processes that could drop one, run from the repo root or from `bin/` — and `[Bb]in/` already covers
|
|
# the latter. An un-ignored dump is visible noise; a wrongly-ignored source file is not.
|
|
/core
|
|
/core.[0-9]*
|
|
|
|
scripts/generate-api-sdk/swagger.json
|
|
scripts/download-test-content.sh
|
|
|
|
docker-compose.override.yml
|
|
|
|
ErsatzTV/wwwroot/v2/
|
|
ErsatzTV/wwwroot/app/
|
|
web/dist/
|
|
web/node_modules
|
|
|
|
# E2E / screenshot scratch (from Playwright/live-E2E runs) — never committed
|
|
/*.png
|
|
.playwright-mcp/
|
|
# UI-E2E run artifacts: traces/screenshots Playwright writes on failure (outputDir in
|
|
# web/playwright.config.ts), plus the report dir it would use if a reporter is ever added (#445).
|
|
web/e2e/.output/
|
|
web/playwright-report/
|
|
|
|
# Per-session worktree-ownership marker (H7, ersatztv#303) — local, never committed
|
|
.claude-worktree-owner
|