Files
ersatztv/docs/e2e-local.md
T
timothy 279052ccd4
Build ErsatzTV Image / Build & test (.NET) (pull_request) Failing after 2m45s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Failing after 2s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 11s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
fix(e2e): stale-asset gotcha — rm build wwwroot before copy in e2e-local.sh (+doc)
2026-07-10 07:56:34 +02:00

5.4 KiB
Raw Blame History

Local live-E2E recipe

Purpose: how to stand up a real, running instance of this fork locally (dotnet host + built SPA) for manual or Playwright-MCP-driven end-to-end verification — no live Docker/prod dependency. Update this doc (and scripts/e2e-local.sh) in the same PR that changes any convention below.

This is for interactive/agent-driven verification, not CI (docs/ci-cd.md covers the CI pipeline, which never runs the app itself).

Why the steps are in this order

  • ErsatzTV/Startup.cs's /app SPA middleware resolves its static-file root once, at startup (SpaStaticFileRoot(), checked with Directory.Exists and swapped to a NullFileProvider if missing — see Startup.cs around the app.MapWhen(... "/app" ...) block). If wwwroot/app doesn't exist yet when the process starts, the SPA will 404 forever until you restart the process — copying the built files in after the fact does nothing for an already-running instance.
  • The dotnet host and the SPA share one port (default 8409, ETV_UI_PORT / SystemEnvironment. UiPort in ErsatzTV.Core/SystemEnvironment.cs) — there's no separate dev server/proxy in this workflow; you're testing the actual production static-hosting path.
  • A fresh config folder per run avoids state bleed (leftover channels/schedules/DB) between test sessions corrupting your assertions.

Steps

  1. Build the prerequisites (once, or after any source change):

    dotnet build ErsatzTV.sln
    cd web && npm run build && cd ..   # → ErsatzTV/wwwroot/app (vite.config.ts outDir)
    
  2. Copy wwwroot into the build output (the dotnet build output directory does not automatically pick up web/'s build artifacts placed directly into the source wwwroot):

    rm -rf ErsatzTV/bin/Debug/net10.0/wwwroot
    cp -R ErsatzTV/wwwroot ErsatzTV/bin/Debug/net10.0/wwwroot
    

    The rm matters: if the destination dir already exists (any prior run), cp -R src dst copies into it (dst/wwwroot/...) and the server silently keeps serving the previous run's stale assets. scripts/e2e-local.sh does the rm+copy for you. If you rebuild the SPA (npm run build) while the dotnet process from step 4 is already running, re-copy and then restart the process — see the "why" note above; it will not pick up new files live.

  3. Use a fresh scratch config folder — never reuse one across test runs:

    CONFIG_DIR=$(mktemp -d)
    
  4. Run the app, pointed at the scratch folder:

    cd ErsatzTV/bin/Debug/net10.0
    ETV_CONFIG_FOLDER="$CONFIG_DIR" dotnet ErsatzTV.dll
    

    Wait for the log line Done migrating search index (emitted by RebuildSearchIndexHandler in ErsatzTV.Application/Search/Commands/, with a ... in {Duration} suffix) — that's the last long-running startup step; before that, requests may 404/error. The UI and the entire /api/* surface are served on the same port, 8409 by default (override with ETV_UI_PORT).

  5. Seed data as needed via the API — no API key is required for local mutating requests by default (ApiKeyAuthorizationFilter only enforces the X-Api-Key header when Api:WriteKey is configured; it's empty/unset in a fresh local config, so writes are open). Examples:

    • Create a channel: POST /api/channels — check the current ErsatzTV/wwwroot/openapi/v1.json (or CreateChannelRequest.cs) for the exact required field list before assuming these are complete, but as of this writing it requires (among plain fields) these enums: PlayoutSource: "Generated", PlayoutMode: "Continuous", SongVideoMode: "Default", TranscodeMode: "OnDemand", IdleBehavior: "StopOnDisconnect".
    • Create a playout for an existing channel: POST /api/playouts with {"channelId": <id>, "scheduleKind": "Block"} (or "Classic"/"Scripted"/"Sequential" per ChannelPlayoutSource/schedule-kind enums — check v1.json for the current set).
  6. Tear down: kill the dotnet ErsatzTV.dll process and confirm the port is freed (lsof -i :8409 should return nothing) before starting another run — a stray process holding the port will make the next run's health check hang or fail confusingly.

Playwright MCP screenshots

If you're driving the browser via the Playwright MCP server for visual verification, screenshots land in the MCP server process's own cwd (this repo's root, not wherever you ran the dotnet process from) — expect stray *.png files at the repo root after a session; this is tolerated, not a bug to fix, but don't check them in.

Script: scripts/e2e-local.sh

A copy of this script is included in this doc's directory; it is intended to land at scripts/e2e-local.sh in the repo. It automates steps 24 above (build is assumed already done — run dotnet build / npm run build yourself first, since rebuilding on every invocation is slow and this script is meant to be re-run often during a debugging session).

Usage:

scripts/e2e-local.sh [CONFIG_DIR]
  • CONFIG_DIR defaults to a fresh mktemp -d if omitted.
  • Copies ErsatzTV/wwwrootErsatzTV/bin/Debug/net10.0/wwwroot.
  • Launches dotnet ErsatzTV.dll in the background with ETV_CONFIG_FOLDER set.
  • Waits (up to 120s) for the Done migrating search index log line.
  • Prints the PID and port, then exits leaving the server running — the caller is responsible for killing the PID when done (kill <PID>).