#117-e2e (alt) — E2E-Tests laufen gegen das gebaute Container-Image statt gegen den Vite-Dev-Server #141
Labels
No labels
priority/could
priority/must
priority/should
priority/wont
status/blocked
status/claimed
status/done-migrated
type/bug
type/feature
type/infra
type/tech-debt
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
robert/todo#141
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Story: E2E-Tests laufen gegen das gebaute Container-Image statt gegen den Vite-Dev-Server
As a Entwickler dieses Repos,
I want to dass die Playwright-E2E-Suite in CI gegen das fertig gebaute Produktions-Image läuft (ein
einziger Prozess auf Port 8080, der sowohl die SPA aus
wwwrootals auch/apiausliefert), statt gegen dasheutige Paar aus
dotnet run+ Vite-Dev-Server,so that die E2E-Läufe stabil werden und das getestet wird, was tatsächlich deployt wird.
Depends on: nichts Offenes. Das Image (
Dockerfile) und die Single-Origin-Auslieferung existieren bereits:Program.csruftUseStaticFiles()auf und hat einenMapFallback, der für alle Nicht-/api-Pfadeindex.htmlliefert (Deep-Links wie/invite/:tokenund/list/:idfunktionieren also im Container).docker-compose.dev.ymlfährt genau diese Topologie schon für den lokalen Review-Container.Motivation (gemessen, nicht vermutet)
Sechs vollständige chromium-Läufe mit
--retries=0in einer ruhigen Umgebung: fünf Läufe grün (je ~1,4 min),ein Lauf rot mit 2 Fehlern (2,3 min). Der rote Lauf war genau der, in dem parallel
vitest/tscCPU-Lasterzeugt haben. Die zwei Fehler betrafen unterschiedliche Specs an unterschiedlichen Zeilen, beide vom
Typ "Element erschien nie" — die Signatur, die
07_team_coach_memory.mdausdrücklich als Contentionklassifiziert (im Gegensatz zur immer-gleichen-Zeile-Signatur, die ein echter Codebug wäre).
Ursache ist also CPU-Knappheit während des Testlaufs. Der Vite-Dev-Server ist dabei der größte vermeidbare
Verbraucher: Transform-on-demand pro Request, HMR und
CHOKIDAR_USEPOLLING=1— Polling, das per Definitiondauerhaft CPU verbrennt und nur existiert, um das inotify-Limit zu umgehen.
Ein Image-Build verschiebt die teure Arbeit vor den Testlauf. Die Testphase selbst wird ruhig. Genau das
ist der Unterschied, der zählt: Last während des Laufs bricht Tests, eine Bauphase davor nicht.
Nachweis (lokal gemessen, ohne Docker)
Die Docker-spezifischen Teile sind hier nicht prüfbar (kein Docker im Sandbox-Environment). Prüfbar war
dagegen die Topologie, um die es eigentlich geht: Frontend-Build mit leerem
VITE_API_URLnachCqsTodo.WebApi/wwwroot, Release-Backend auf einem einzigen Origin (Port 5000), kein Vite, keinASPNETCORE_ENVIRONMENT=CI(also auch keinUseCors()), Playwright überE2E_TARGET_URL.--retries=0225 Test-Ausführungen, 0 Fehler.
Der Lastlauf ist das eigentliche Ergebnis. Die Dev-Server-Topologie ist unter genau dieser Bedingung
gescheitert (2 Fehler, unterschiedliche Specs, unterschiedliche Zeilen). Die Single-Origin-Topologie wird
unter Last nur langsamer (57 s statt 31 s), nicht rot. Das ist der Gegentest, den
07_team_coach_memory.mdverlangt ("A hypothesis that 'explains' a flaky pattern isn't confirmed untilyou've tried to break it") — die Hypothese hat gehalten.
Nebenbei: ~31 s statt ~84 s pro chromium-Lauf, also gut 2,5x schneller, weil die teure Arbeit vor den
Lauf wandert.
Weiter verifiziert:
/→ 200, Deep-Link/list/1→ 200 überMapFallback,/api/nope→ 404.VITE_API_URLenthält keine absolute Backend-URL im Bundle (Same-Origin bestätigt).WebSocket connected for ${t}überlebt die Minifizierung —invitation.spec.tsfunktioniert.Acceptance criteria
/apiauf demselbenOrigin). Kein Vite-Dev-Server, kein zweiter
webServer-Eintrag, kein Port 5173 im CI-Lauf.VITE_API_URList im Image-Build leer, damit das Frontend relative Same-Origin-Pfade nutzt. Eingesetzter Wert würde jeden Request cross-origin machen und alles brechen.
Program.cs(
app.Environment.EnvironmentName == "CI") undVITE_API_URL/VITE_API_TARGET/CHOKIDAR_USEPOLLINGin
playwright.config.tswerden entfernt oder nachweislich obsolet — sie existieren ausschließlichwegen der Dev-Server-Trennung.
wie gehabt, sofern die CI-Umgebung es starten kann.
pageB.goto(inviteLink)und der Reload von/list/:id(siehe
default-list.spec.ts) müssen überMapFallbackbedient werden.invitation.spec.tswartet aufWebSocket connected for TodoListDto. Vite stripptconsoleper Default nicht — muss aber gegen dasechte Minified-Bundle verifiziert werden, nicht angenommen.
npm run devbleiben der lokale Entwicklungs-Loop.Diese Story ändert nur den CI-Pfad.
--retries=0) gegen die neue Topologie, dokumentiert, mitVergleich der Laufzeit gegen die heutigen ~1,4 min. → siehe Nachweis-Tabelle oben.
App:FrontendBaseUrlmuss dem eigenen Origin entsprechen. Default isthttp://localhost:5173;Invite-Links werden daraus gebaut, also navigiert
invitation.spec.tssonst auf einen toten Port.Im Container: auf die Container-URL setzen. (Beim lokalen Nachweis über
App__FrontendBaseUrlgesetzt.)Stand / was noch fehlt
Geliefert: der Single-Origin-Modus in
playwright.config.ts. IstE2E_TARGET_URLgesetzt, zeigtbaseURLdorthin undwebServerist leer — Playwright startet dann nichts selbst. Das ist der Mechanismus,den sowohl der Container als auch der lokale Nachweis oben benutzen.
Bewusst nicht geliefert: die Änderung an
.gitea/workflows/ci.yml. Zum Zeitpunkt dieser Story lag inderselben Datei uncommittete Arbeit einer parallelen Session (Entfernen des Redis-Service zugunsten eines
DB-gestützten Session-Caches), die exakt denselben
e2e-Job-Block anfasst. Dort zu schreiben hätte derenArbeit überschrieben. Die CI-Änderung gehört als eigener Commit nach dem Landen der Redis-Entfernung:
e2e-Job dasopenapi-spec-Artefakt herunterladen (derfrontend-Job macht das bereits) und nachReactUi/openapi/swagger.jsonlegen — dasDockerfileruftnpm run generate:apiselbst auf, brauchtden Spec aber im Build-Kontext.
docker buildmit leeremVITE_API_URLals Build-Arg.--network hostunter Linux, damit dieservices:-Postgres erreichbar ist), mitApp__FrontendBaseUrlauf die Container-URL./api/healthwarten, dannE2E_TARGET_URL=http://localhost:8080 npx playwright test --project=....Program.csund dieVITE_API_URL/VITE_API_TARGET/CHOKIDAR_USEPOLLING-Blöcke inplaywright.config.tsentfernen.Nicht verifizierbar aus dieser Umgebung: alles ab Schritt 2 (kein Docker), sowie das Netzwerk-Verhalten
gegenüber
services:-Containern. Der erste echte CI-Lauf ist der eigentliche Test dafür — bewusst alsseparater, kleiner Commit, damit ein Rückbau billig bleibt (siehe die
max-parallel/needs-Episode in07_team_coach_memory.md).Bekannte Risiken / offene Punkte
e2e-Job macht heute wedernpm run buildnochdotnet publish. Das Image desdocker-Jobs wiederzuverwenden ist keine Lösung: der läuft nur aufmasterund hängt an
backend-test— E2E liefe dann auf Branches gar nicht mehr. Realistische Minderung istLayer-Caching.
npm run buildbraucht die generierten API-Typen. Dere2e-Job lädt dasopenapi-spec-Artefakt heutenicht herunter,
tsc -bwürde also an fehlenden Typen scheitern. Der Job braucht zusätzlichdownload-artifact+npm run generate:api— imDockerfileist das bereits gelöst (RUN npm run generate:api), der Spec muss aber vordocker buildanReactUi/openapi/swagger.jsonliegen.services:sind aus dem Job erreichbar; ein selbst gestarteterContainer braucht
--network host(Linux) oder muss dem Job-Netzwerk beitreten.reconnectTimeout-Kommentarin
changeSubscription.tsbeschreibt. Timing ändert sich real — vermutlich zum Besseren, aber es ist eineVerhaltensänderung, keine reine Infrastruktur-Umstellung.
Out of scope
docker-Job selbst (needs: [backend-build, backend-test, frontend], keine2e) — dass ein kaputtesFrontend trotz rotem
e2edeployt, ist ein echtes Problem, aber eine eigene Story.Claimed for this go-cycle (2026-08-18). Plan: extend .gitea/workflows/ci.yml e2e job to build the Docker image and run Playwright against it (single-origin mode, already landed in playwright.config.ts) instead of the Vite dev-server pair, per the remaining steps in this issue.
Security pre-review (Infrastructure cycle) — CI e2e job change
Change: e2e job builds+runs the real Docker image (same one the docker job ships) and points
Playwright at it, instead of dotnet run + npm run dev.
or needed here; the image is only built+run locally within the job, never pushed.
supply-chain surface.
(), which is torn down with the ephemeral job runner —
nothing is exposed beyond the job's own lifetime, no new public exposure.
used for the dotnet run path, just passed to a container instead of a process env.
the docker job's own comment), not something this change expands — it only starts consuming
a capability every job already has.
the shared self-hosted runner across job runs.
No blocking findings.
Pushed
26d8dad: e2e job now builds+runs the app image per matrix leg on the job container's own docker network (Docker-outside-of-Docker), instead ofdotnet run+npm run dev. Playwright's existing single-origin webServer health gate (E2E_TARGET_URL, already landed) handles the startup wait. Deliberately left the CI-only CORS branch (Program.cs) and VITE_API_URL/CHOKIDAR_USEPOLLING (playwright.config.ts) in place until a real CI run confirms this green, per this issue's own AC. Watching the run now.Concurrent-session collision, noted for the record: a parallel run pushed
26d8dadimplementingthis same issue (single-origin e2e via the built container image) while this session was mid-
implementation. Compared both: the concurrent version correctly tags the container/image name per
matrix leg (todo-e2e:PROJECT_SLUG-SHA), which mine did not — a real bug avoided, since this repo's
runner pool executes multiple e2e legs concurrently on the same host and a fixed name would have
collided. Adopted
26d8dadas-is rather than merging two implementations of the same fix; my ownattempt is kept on branch backup-117e2e-my-attempt for reference, not merged.
Continuing from here: watching the real CI run on
26d8dad, then closing this issue once confirmedgreen and the deferred Program.cs/playwright.config.ts cleanup (issue's own AC) is done.
Diagnosed and fixed in
35f77b5: run 305 (localhost-independent version) failed every leg withSystem.UnauthorizedAccessException: "Not logged in" flooding the app logs after the first
request — the session cookie (SecurePolicy = CookieSecurePolicy.Always, unconditional,
Program.cs) was never actually stored by the browser, because the app was reachable at a named
container hostname (app-desktop-hd etc.), not localhost — and browsers only grant plain-HTTP
origins the secure-context exception needed for a Secure cookie when the origin is literally
localhost/127.0.0.1. Switched the app container to share the job container's own network
namespace (--network container:DESKTOP-SGTBQDJ) so it's reachable at plain localhost:8080, matching
the old dotnet-run topology this replaces. Watching the resulting CI run now.
Run 306 confirmed the localhost fix worked (32/33 desktop specs passing, Mobile Chrome fully
green). One isolated failure remained: invitation.spec.ts (Your email address must be verified
before joining a list) — e2e/utils/db.ts verifyUserEmail() reads
process.env.ConnectionStrings__TodoDatabase directly in the Playwright process to bypass the
emailed-link flow, which was only being passed to the app container, not the Run E2E tests
step itself, so it silently no-opped. Fixed in
053c91a. Watching the next run now.Closing out. Summary:
legs passed. This is the primary proof the container-based single-origin approach works end
to end (the two real bugs found along the way — the health-wait race and the Secure-cookie/
localhost issue — are both documented above with their fixes).
ad7989d): 3 rerun attempts. chromium passed on attempt 1,Mobile Chrome passed on attempts 1 and 2 — independent confirmation the cleanup itself (dead
CI-only branches removed from Program.cs/playwright.config.ts) does not regress anything.
All remaining failures across all 3 attempts failed at the identical step
("Install Playwright browsers", downloading from storage.googleapis.com) — a sustained
external outage unrelated to this repo's code, not a flake that a 4th retry is likely to
fix differently from the first 3. Will be reconfirmed naturally by the next commit's CI run.
Scope delivered: e2e job now builds and runs the real Docker image (single-origin), the
Secure-cookie/localhost and Playwright-DB-env-var bugs found along the way are fixed, and the
now-dead CI-only special cases in Program.cs/playwright.config.ts are removed. Local dev
(Aspire + npm run dev) is unaffected.