#117-e2e (alt) — E2E-Tests laufen gegen das gebaute Container-Image statt gegen den Vite-Dev-Server #141

Closed
opened 2026-08-18 13:33:45 +02:00 by lena · 7 comments
lena commented 2026-08-18 13:33:45 +02:00 (Migrated from git.butzei.de)

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 wwwroot als auch /api ausliefert), statt gegen das
heutige 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.cs ruft UseStaticFiles() auf und hat einen MapFallback, der für alle Nicht-/api-Pfade
index.html liefert (Deep-Links wie /invite/:token und /list/:id funktionieren also im Container).
docker-compose.dev.yml fährt genau diese Topologie schon für den lokalen Review-Container.

Motivation (gemessen, nicht vermutet)

Sechs vollständige chromium-Läufe mit --retries=0 in 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/tsc CPU-Last
erzeugt haben. Die zwei Fehler betrafen unterschiedliche Specs an unterschiedlichen Zeilen, beide vom
Typ "Element erschien nie" — die Signatur, die 07_team_coach_memory.md ausdrücklich als Contention
klassifiziert (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 Definition
dauerhaft 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_URL nach
CqsTodo.WebApi/wwwroot, Release-Backend auf einem einzigen Origin (Port 5000), kein Vite, kein
ASPNETCORE_ENVIRONMENT=CI (also auch kein UseCors()), Playwright über E2E_TARGET_URL.

Lauf Ergebnis Dauer
chromium, 5x sauber, --retries=0 31 passed (je) 28,7 – 32,3 s
chromium, unter CPU-Last (4 Busy-Cores) 31 passed 56,9 s
Desktop HD 33 passed 30,4 s
Mobile Chrome (Pixel 8a) 6 passed 8,7 s

225 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.md verlangt ("A hypothesis that 'explains' a flaky pattern isn't confirmed until
you'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 über MapFallback, /api/nope → 404.
  • Build mit leerem VITE_API_URL enthält keine absolute Backend-URL im Bundle (Same-Origin bestätigt).
  • Der Marker WebSocket connected for ${t} überlebt die Minifizierung — invitation.spec.ts funktioniert.

Acceptance criteria

  • Single-Origin: Playwright läuft gegen genau einen Prozess/Container (SPA + /api auf demselben
    Origin). Kein Vite-Dev-Server, kein zweiter webServer-Eintrag, kein Port 5173 im CI-Lauf.
  • VITE_API_URL ist im Image-Build leer, damit das Frontend relative Same-Origin-Pfade nutzt. Ein
    gesetzter Wert würde jeden Request cross-origin machen und alles brechen.
  • CI-only-Sonderfälle entfallen: die CORS-Verzweigung in Program.cs
    (app.Environment.EnvironmentName == "CI") und VITE_API_URL/VITE_API_TARGET/CHOKIDAR_USEPOLLING
    in playwright.config.ts werden entfernt oder nachweislich obsolet — sie existieren ausschließlich
    wegen der Dev-Server-Trennung.
  • Alle heute laufenden Projekte bleiben grün: chromium, Desktop HD, Mobile Chrome (Pixel 8a). Firefox
    wie gehabt, sofern die CI-Umgebung es starten kann.
  • Deep-Links funktionieren gegen das Image: pageB.goto(inviteLink) und der Reload von /list/:id
    (siehe default-list.spec.ts) müssen über MapFallback bedient werden.
  • Der console.log-Marker überlebt den Produktions-Build: invitation.spec.ts wartet auf
    WebSocket connected for TodoListDto. Vite strippt console per Default nicht — muss aber gegen das
    echte Minified-Bundle verifiziert werden, nicht angenommen.
  • Lokaler Workflow bleibt unverändert: Aspire + npm run dev bleiben der lokale Entwicklungs-Loop.
    Diese Story ändert nur den CI-Pfad.
  • Nachweis: wiederholte Läufe (mind. 5x --retries=0) gegen die neue Topologie, dokumentiert, mit
    Vergleich der Laufzeit gegen die heutigen ~1,4 min. → siehe Nachweis-Tabelle oben.
  • App:FrontendBaseUrl muss dem eigenen Origin entsprechen. Default ist http://localhost:5173;
    Invite-Links werden daraus gebaut, also navigiert invitation.spec.ts sonst auf einen toten Port.
    Im Container: auf die Container-URL setzen. (Beim lokalen Nachweis über App__FrontendBaseUrl gesetzt.)

Stand / was noch fehlt

Geliefert: der Single-Origin-Modus in playwright.config.ts. Ist E2E_TARGET_URL gesetzt, zeigt
baseURL dorthin und webServer ist 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 in
derselben 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 deren
Arbeit überschrieben. Die CI-Änderung gehört als eigener Commit nach dem Landen der Redis-Entfernung:

  1. Im e2e-Job das openapi-spec-Artefakt herunterladen (der frontend-Job macht das bereits) und nach
    ReactUi/openapi/swagger.json legen — das Dockerfile ruft npm run generate:api selbst auf, braucht
    den Spec aber im Build-Kontext.
  2. docker build mit leerem VITE_API_URL als Build-Arg.
  3. Container starten (--network host unter Linux, damit die services:-Postgres erreichbar ist), mit
    App__FrontendBaseUrl auf die Container-URL.
  4. Auf /api/health warten, dann E2E_TARGET_URL=http://localhost:8080 npx playwright test --project=....
  5. Erst wenn das grün ist: die CI-only-CORS-Verzweigung in Program.cs und die
    VITE_API_URL/VITE_API_TARGET/CHOKIDAR_USEPOLLING-Blöcke in playwright.config.ts entfernen.

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 als
separater, kleiner Commit, damit ein Rückbau billig bleibt (siehe die max-parallel/needs-Episode in
07_team_coach_memory.md).

Bekannte Risiken / offene Punkte

  • Build-Zeit auf dem kritischen Pfad. Der e2e-Job macht heute weder npm run build noch
    dotnet publish. Das Image des docker-Jobs wiederzuverwenden ist keine Lösung: der läuft nur auf master
    und hängt an backend-test — E2E liefe dann auf Branches gar nicht mehr. Realistische Minderung ist
    Layer-Caching.
  • npm run build braucht die generierten API-Typen. Der e2e-Job lädt das openapi-spec-Artefakt heute
    nicht herunter, tsc -b würde also an fehlenden Typen scheitern. Der Job braucht zusätzlich
    download-artifact + npm run generate:api — im Dockerfile ist das bereits gelöst (RUN npm run generate:api), der Spec muss aber vor docker build an ReactUi/openapi/swagger.json liegen.
  • Netzwerk zu den Service-Containern. services: sind aus dem Job erreichbar; ein selbst gestarteter
    Container braucht --network host (Linux) oder muss dem Job-Netzwerk beitreten.
  • Produktions-React heißt kein StrictMode-Double-Mount. Genau der Fall, den der reconnectTimeout-Kommentar
    in changeSubscription.ts beschreibt. Timing ändert sich real — vermutlich zum Besseren, aber es ist eine
    Verhaltensänderung, keine reine Infrastruktur-Umstellung.
  • Debugbarkeit sinkt (minifiziertes Bundle, keine Source-Maps im Trace).

Out of scope

  • Den lokalen Entwicklungs-Loop auf Container umstellen.
  • Die Firefox-Startprobleme lösen (separate Umgebungsfrage).
  • Der docker-Job selbst (needs: [backend-build, backend-test, frontend], kein e2e) — dass ein kaputtes
    Frontend trotz rotem e2e deployt, ist ein echtes Problem, aber eine eigene Story.
# 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 `wwwroot` als auch `/api` ausliefert), statt gegen das heutige 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.cs` ruft `UseStaticFiles()` auf und hat einen `MapFallback`, der für alle Nicht-`/api`-Pfade `index.html` liefert (Deep-Links wie `/invite/:token` und `/list/:id` funktionieren also im Container). `docker-compose.dev.yml` fährt genau diese Topologie schon für den lokalen Review-Container. ## Motivation (gemessen, nicht vermutet) Sechs vollständige chromium-Läufe mit `--retries=0` in 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`/`tsc` CPU-Last erzeugt haben. Die zwei Fehler betrafen **unterschiedliche** Specs an **unterschiedlichen** Zeilen, beide vom Typ "Element erschien nie" — die Signatur, die `07_team_coach_memory.md` ausdrücklich als Contention klassifiziert (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 Definition dauerhaft 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_URL` nach `CqsTodo.WebApi/wwwroot`, Release-Backend auf einem einzigen Origin (Port 5000), **kein** Vite, **kein** `ASPNETCORE_ENVIRONMENT=CI` (also auch kein `UseCors()`), Playwright über `E2E_TARGET_URL`. | Lauf | Ergebnis | Dauer | |------|----------|-------| | chromium, 5x sauber, `--retries=0` | 31 passed (je) | 28,7 – 32,3 s | | chromium, **unter CPU-Last (4 Busy-Cores)** | **31 passed** | 56,9 s | | Desktop HD | 33 passed | 30,4 s | | Mobile Chrome (Pixel 8a) | 6 passed | 8,7 s | 225 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.md` verlangt ("A hypothesis that 'explains' a flaky pattern isn't confirmed until you'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 über `MapFallback`, `/api/nope` → 404. - Build mit leerem `VITE_API_URL` enthält **keine** absolute Backend-URL im Bundle (Same-Origin bestätigt). - Der Marker `WebSocket connected for ${t}` überlebt die Minifizierung — `invitation.spec.ts` funktioniert. ## Acceptance criteria - [ ] **Single-Origin:** Playwright läuft gegen genau einen Prozess/Container (SPA + `/api` auf demselben Origin). Kein Vite-Dev-Server, kein zweiter `webServer`-Eintrag, kein Port 5173 im CI-Lauf. - [ ] **`VITE_API_URL` ist im Image-Build leer**, damit das Frontend relative Same-Origin-Pfade nutzt. Ein gesetzter Wert würde jeden Request cross-origin machen und alles brechen. - [ ] **CI-only-Sonderfälle entfallen:** die CORS-Verzweigung in `Program.cs` (`app.Environment.EnvironmentName == "CI"`) und `VITE_API_URL`/`VITE_API_TARGET`/`CHOKIDAR_USEPOLLING` in `playwright.config.ts` werden entfernt oder nachweislich obsolet — sie existieren ausschließlich wegen der Dev-Server-Trennung. - [ ] **Alle heute laufenden Projekte bleiben grün:** chromium, Desktop HD, Mobile Chrome (Pixel 8a). Firefox wie gehabt, sofern die CI-Umgebung es starten kann. - [ ] **Deep-Links funktionieren** gegen das Image: `pageB.goto(inviteLink)` und der Reload von `/list/:id` (siehe `default-list.spec.ts`) müssen über `MapFallback` bedient werden. - [ ] **Der console.log-Marker überlebt den Produktions-Build:** `invitation.spec.ts` wartet auf `WebSocket connected for TodoListDto`. Vite strippt `console` per Default nicht — muss aber gegen das echte Minified-Bundle verifiziert werden, nicht angenommen. - [ ] **Lokaler Workflow bleibt unverändert:** Aspire + `npm run dev` bleiben der lokale Entwicklungs-Loop. Diese Story ändert nur den CI-Pfad. - [x] **Nachweis:** wiederholte Läufe (mind. 5x `--retries=0`) gegen die neue Topologie, dokumentiert, mit Vergleich der Laufzeit gegen die heutigen ~1,4 min. → siehe Nachweis-Tabelle oben. - [ ] **`App:FrontendBaseUrl` muss dem eigenen Origin entsprechen.** Default ist `http://localhost:5173`; Invite-Links werden daraus gebaut, also navigiert `invitation.spec.ts` sonst auf einen toten Port. Im Container: auf die Container-URL setzen. (Beim lokalen Nachweis über `App__FrontendBaseUrl` gesetzt.) ## Stand / was noch fehlt **Geliefert:** der Single-Origin-Modus in `playwright.config.ts`. Ist `E2E_TARGET_URL` gesetzt, zeigt `baseURL` dorthin und `webServer` ist 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 in derselben 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 deren Arbeit überschrieben. Die CI-Änderung gehört als eigener Commit *nach* dem Landen der Redis-Entfernung: 1. Im `e2e`-Job das `openapi-spec`-Artefakt herunterladen (der `frontend`-Job macht das bereits) und nach `ReactUi/openapi/swagger.json` legen — das `Dockerfile` ruft `npm run generate:api` selbst auf, braucht den Spec aber im Build-Kontext. 2. `docker build` mit **leerem** `VITE_API_URL` als Build-Arg. 3. Container starten (`--network host` unter Linux, damit die `services:`-Postgres erreichbar ist), mit `App__FrontendBaseUrl` auf die Container-URL. 4. Auf `/api/health` warten, dann `E2E_TARGET_URL=http://localhost:8080 npx playwright test --project=...`. 5. Erst wenn das grün ist: die CI-only-CORS-Verzweigung in `Program.cs` und die `VITE_API_URL`/`VITE_API_TARGET`/`CHOKIDAR_USEPOLLING`-Blöcke in `playwright.config.ts` entfernen. **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 als separater, kleiner Commit, damit ein Rückbau billig bleibt (siehe die `max-parallel`/`needs`-Episode in `07_team_coach_memory.md`). ## Bekannte Risiken / offene Punkte - **Build-Zeit auf dem kritischen Pfad.** Der `e2e`-Job macht heute weder `npm run build` noch `dotnet publish`. Das Image des `docker`-Jobs wiederzuverwenden ist keine Lösung: der läuft nur auf `master` und hängt an `backend-test` — E2E liefe dann auf Branches gar nicht mehr. Realistische Minderung ist Layer-Caching. - **`npm run build` braucht die generierten API-Typen.** Der `e2e`-Job lädt das `openapi-spec`-Artefakt heute **nicht** herunter, `tsc -b` würde also an fehlenden Typen scheitern. Der Job braucht zusätzlich `download-artifact` + `npm run generate:api` — im `Dockerfile` ist das bereits gelöst (`RUN npm run generate:api`), der Spec muss aber vor `docker build` an `ReactUi/openapi/swagger.json` liegen. - **Netzwerk zu den Service-Containern.** `services:` sind aus dem Job erreichbar; ein selbst gestarteter Container braucht `--network host` (Linux) oder muss dem Job-Netzwerk beitreten. - **Produktions-React heißt kein StrictMode-Double-Mount.** Genau der Fall, den der `reconnectTimeout`-Kommentar in `changeSubscription.ts` beschreibt. Timing ändert sich real — vermutlich zum Besseren, aber es ist eine Verhaltensänderung, keine reine Infrastruktur-Umstellung. - **Debugbarkeit sinkt** (minifiziertes Bundle, keine Source-Maps im Trace). ## Out of scope - Den lokalen Entwicklungs-Loop auf Container umstellen. - Die Firefox-Startprobleme lösen (separate Umgebungsfrage). - Der `docker`-Job selbst (`needs: [backend-build, backend-test, frontend]`, kein `e2e`) — dass ein kaputtes Frontend trotz rotem `e2e` deployt, ist ein echtes Problem, aber eine eigene Story.
lena commented 2026-08-18 13:39:22 +02:00 (Migrated from git.butzei.de)

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.

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.
lena commented 2026-08-18 13:44:47 +02:00 (Migrated from git.butzei.de)

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.

  • Secrets: no new secret exposure. REGISTRY_TOKEN (used by the docker job to push) is not read
    or needed here; the image is only built+run locally within the job, never pushed.
  • Image trust: same Dockerfile, same base images as the already-shipping docker job — no new
    supply-chain surface.
  • Network exposure: the app container joins the job container network namespace directly
    (), which is torn down with the ephemeral job runner —
    nothing is exposed beyond the job's own lifetime, no new public exposure.
  • Credentials used (test Postgres password) are the same test-only credentials the job already
    used for the dotnet run path, just passed to a container instead of a process env.
  • docker.sock access is pre-existing runner config (bind-mounted into every job container per
    the docker job's own comment), not something this change expands — it only starts consuming
    a capability every job already has.
  • Added an always-run cleanup step () so containers do not accumulate on
    the shared self-hosted runner across job runs.

No blocking findings.

**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. - Secrets: no new secret exposure. REGISTRY_TOKEN (used by the docker job to push) is not read or needed here; the image is only built+run locally within the job, never pushed. - Image trust: same Dockerfile, same base images as the already-shipping docker job — no new supply-chain surface. - Network exposure: the app container joins the job container network namespace directly (), which is torn down with the ephemeral job runner — nothing is exposed beyond the job's own lifetime, no new public exposure. - Credentials used (test Postgres password) are the same test-only credentials the job already used for the dotnet run path, just passed to a container instead of a process env. - docker.sock access is pre-existing runner config (bind-mounted into every job container per the docker job's own comment), not something this change expands — it only starts consuming a capability every job already has. - Added an always-run cleanup step () so containers do not accumulate on the shared self-hosted runner across job runs. No blocking findings.
lena commented 2026-08-18 13:59:04 +02:00 (Migrated from git.butzei.de)

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 of dotnet 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.

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 of `dotnet 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.
lena commented 2026-08-18 16:47:40 +02:00 (Migrated from git.butzei.de)

Concurrent-session collision, noted for the record: a parallel run pushed 26d8dad implementing
this 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 26d8dad as-is rather than merging two implementations of the same fix; my own
attempt 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 confirmed
green and the deferred Program.cs/playwright.config.ts cleanup (issue's own AC) is done.

Concurrent-session collision, noted for the record: a parallel run pushed 26d8dad implementing this 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 26d8dad as-is rather than merging two implementations of the same fix; my own attempt 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 confirmed green and the deferred Program.cs/playwright.config.ts cleanup (issue's own AC) is done.
lena commented 2026-08-18 17:58:34 +02:00 (Migrated from git.butzei.de)

Diagnosed and fixed in 35f77b5: run 305 (localhost-independent version) failed every leg with
System.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.

Diagnosed and fixed in 35f77b5: run 305 (localhost-independent version) failed every leg with System.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.
lena commented 2026-08-18 18:34:57 +02:00 (Migrated from git.butzei.de)

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.

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.
lena commented 2026-08-18 20:58:21 +02:00 (Migrated from git.butzei.de)

Closing out. Summary:

  • Run 307 (before the Program.cs/playwright.config.ts cleanup): all jobs green, all 4 E2E
    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).
  • Run 309 (the cleanup commit, 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.

Closing out. Summary: - Run 307 (before the Program.cs/playwright.config.ts cleanup): all jobs green, all 4 E2E 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). - Run 309 (the cleanup commit, 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.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
robert/todo#141
No description provided.