From 2820cd34760007c054c04e8f9bd6f27f10be288e Mon Sep 17 00:00:00 2001 From: Edwin van den Houdt Date: Tue, 21 Jul 2026 20:07:35 +0200 Subject: [PATCH] =?UTF-8?q?fix(ci):=20e2e=20=E2=80=94=20let=20Playwright?= =?UTF-8?q?=20manage=20both=20servers=20(fixes=20the=202-hour=20hang)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The e2e job backgrounded `dotnet run &` and `ng serve &` in separate Actions steps, then `npx wait-on` (no timeout) in a later step. A process started with `&` in one step is killed when that step's shell exits, so wait-on waited forever on servers that were already gone — the job hung until the runner's hard limit (~2h; Gitea's act_runner doesn't reliably enforce timeout-minutes). Move both servers into Playwright's `webServer` (an array: backend + `npm start`), so Playwright starts them, waits for readiness, runs the suite, and tears them down in the one `npm run e2e` process — CI and local alike. The CI e2e job is now just npm ci / playwright install / npm run e2e. `reuseExistingServer` is on locally (reuses a running app, incl. the docker stack) and off in CI (fresh start). Verified locally via `CI=1 npm run e2e` against free ports + a clean db: both smoke tests pass in ~15s (no hang). The earlier local failure was leftover SQLite state (WP-22) from a dirty run resuming a Concept — CI checks out fresh, so its db is always empty. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/ci.yml | 8 +++++--- playwright.config.ts | 33 ++++++++++++++++++++++----------- scripts/ci-local.sh | 7 ++++--- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 424a3d2..8f9052e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,6 +68,11 @@ jobs: # runner checkout per run, so there's no bigregister.db (WP-22, gitignored) # left over from a prior run to leak state in; the backend creates + migrates # an empty one on this boot, same as a fresh clone always has. + # Playwright's `webServer` (playwright.config.ts) starts BOTH the backend and + # `ng serve`, waits for them, runs the suite, and tears them down — all in the + # one `npm run e2e` process. Do NOT background them as separate steps: a `&` + # process from one Actions step is dead by the next step, so `wait-on` hung + # forever (the 2-hour e2e hang). runs-on: ubuntu-latest timeout-minutes: 15 steps: @@ -81,9 +86,6 @@ jobs: dotnet-version: 10.0.x - run: npm ci - run: npx playwright install --with-deps chromium - - run: dotnet run --project backend/src/BigRegister.Api --urls http://localhost:5000 & - - run: npx ng serve --proxy-config proxy.conf.json & - - run: npx wait-on http://localhost:5000/swagger http://localhost:4200 - run: npm run e2e codeql: diff --git a/playwright.config.ts b/playwright.config.ts index ef1f86c..0106bc7 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -15,15 +15,26 @@ export default defineConfig({ trace: 'on-first-retry', }, projects: [{ name: 'chromium', use: { browserName: 'chromium' } }], - // CI starts `ng serve` + the backend as separate job steps (both need to be up - // before the suite runs); locally, boot `npm start` automatically so `npm run e2e` - // works standalone — the backend still needs `dotnet run` running separately. - webServer: process.env['CI'] - ? undefined - : { - command: 'npm start', - url: baseURL, - reuseExistingServer: true, - timeout: 120_000, - }, + // Playwright owns both servers' lifecycle — start, wait-for-ready, tear down — in the + // one `npm run e2e` process, so `npm run e2e` is self-contained locally AND in CI. + // Do NOT background them as separate CI steps: a process started with `&` in one + // Actions `run:` step is killed when that step's shell exits, so a later `wait-on` + // step hangs forever on servers that are already gone (the 2-hour e2e hang). + // `reuseExistingServer` locally lets you run against an already-running app (incl. the + // docker stack on 4200/5000); CI always starts fresh. Backend gets a longer timeout — + // `dotnet run` cold-restores+builds before it listens. + webServer: [ + { + command: 'dotnet run --project backend/src/BigRegister.Api --urls http://localhost:5000', + url: 'http://localhost:5000/swagger', + reuseExistingServer: !process.env['CI'], + timeout: 180_000, + }, + { + command: 'npm start', + url: baseURL, + reuseExistingServer: !process.env['CI'], + timeout: 120_000, + }, + ], }); diff --git a/scripts/ci-local.sh b/scripts/ci-local.sh index e9527bf..b93d09f 100755 --- a/scripts/ci-local.sh +++ b/scripts/ci-local.sh @@ -25,6 +25,7 @@ if [[ "${1:-}" == "--full" ]]; then fi printf '\n\033[1;32m✔ local CI passed\033[0m\n' -printf 'Note: the e2e job is not run here — it needs the backend + ng serve up. Run:\n' -printf ' (backend) dotnet run --project backend/src/BigRegister.Api --urls http://localhost:5000 &\n' -printf ' (frontend) npx ng serve & then npm run e2e\n' +printf 'Note: the e2e job is not chained here (it is slow). Run it standalone with:\n' +printf ' npm run e2e\n' +printf 'Playwright starts the backend + ng serve itself (playwright.config.ts webServer),\n' +printf 'reusing an already-running app on 4200/5000 if present.\n'