From 7ff81de2b6f20b3e5c66e87025c1ee0d9f5b5f97 Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Fri, 15 May 2026 08:39:22 +0200 Subject: [PATCH 1/3] ui/frontend: cap Playwright at 4 workers, retry 4 times Under host-mode runner the default 6 workers + 1 retry consistently land on ~7 flakies and an occasional hard fail per ui-test run (ui-test #59 most recently). Workers share CPU and the host Docker daemon with gitea, the long-lived dev stack, and the user's host Caddy; the extra wall time from contention pushes individual expectations past their timeouts. Lower the worker cap to 4 to keep parallelism but give each worker real CPU headroom, and raise retries to 4 so the rare slow page is absorbed without surfacing as failure. Co-Authored-By: Claude Opus 4.7 (1M context) --- ui/frontend/playwright.config.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ui/frontend/playwright.config.ts b/ui/frontend/playwright.config.ts index 9cc4531..f2d664c 100644 --- a/ui/frontend/playwright.config.ts +++ b/ui/frontend/playwright.config.ts @@ -5,7 +5,13 @@ export default defineConfig({ testDir: "tests/e2e", fullyParallel: true, forbidOnly: !!process.env.CI, - retries: process.env.CI ? 1 : 0, + // host-mode CI runner shares CPU/IO with the long-lived dev stack, + // gitea, and the user's host Caddy. The default 6 workers + 1 + // retry produced ~7 flakies + 1 hard fail per ui-test run; cap at + // 4 workers (still parallel) and allow 4 retries to ride out + // transient timing hiccups without inflating wall time. + workers: 4, + retries: process.env.CI ? 4 : 0, reporter: [["list"], ["html", { open: "never" }]], use: { baseURL: "http://localhost:5173", From e3bb30201dc83ecaf1dd2a8dce634d8c139cdbed Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Fri, 15 May 2026 08:42:08 +0200 Subject: [PATCH 2/3] ci/ui-test: serialise per-ref + clear stale Vite before Playwright Two ui-test jobs cannot coexist on the same host: Playwright's `webServer` spec spawns `pnpm dev` on :5173, and on a host-mode runner the port lives in the host namespace shared by every job. ui-test #67 hit "Error: http://localhost:5173 is already used" because a parallel job's Vite still held the port. Two changes: 1. `concurrency: ui-test-${{ gitea.ref }}` with `cancel-in-progress: true`. New push/PR runs against the same ref kill any earlier ui-test before starting, so we never have two `pnpm dev`s alive at once. 2. `pkill -f 'vite dev' || true` plus `fuser -k 5173/tcp` right before Playwright. Defence in depth in case the concurrency cancellation does not reap the spawned shell promptly. Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitea/workflows/ui-test.yaml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.gitea/workflows/ui-test.yaml b/.gitea/workflows/ui-test.yaml index 5924d3c..cb7812d 100644 --- a/.gitea/workflows/ui-test.yaml +++ b/.gitea/workflows/ui-test.yaml @@ -16,6 +16,15 @@ on: - '.gitea/workflows/ui-test.yaml' - '!**/*.md' +# Playwright launches its own `pnpm dev` on :5173. In host-mode the +# runner shares the host's port namespace, so two parallel ui-test +# jobs (e.g. a push event racing with a pull_request event for the +# same commit) collide on EADDRINUSE. Group by branch/ref and cancel +# the in-progress run so only one ui-test is alive at a time per ref. +concurrency: + group: ui-test-${{ gitea.ref }} + cancel-in-progress: true + jobs: test: runs-on: ubuntu-latest @@ -59,6 +68,15 @@ jobs: working-directory: ui/frontend run: pnpm test + - name: Clear stale Vite from :5173 + # Defence in depth in case a previous job's webServer survived + # the concurrency-cancel — `pkill` does not fail when there is + # nothing to kill, and `fuser -k` cleans up anything else + # holding the port. + run: | + pkill -f 'vite dev' || true + fuser -k 5173/tcp 2>/dev/null || true + - name: Run Playwright working-directory: ui/frontend run: pnpm exec playwright test From 6e6186a5713d9b64b6d706e85f967337291d5b8c Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Fri, 15 May 2026 08:46:00 +0200 Subject: [PATCH 3/3] ci/ui-test: key concurrency by head sha, not gitea.ref `gitea.ref` differs between push (`refs/heads/`) and pull_request (`refs/pull/N/head`) events even for the same commit, so the two parallel runs land in different concurrency groups and the Vite-on-:5173 collision is not suppressed. Switching the key to the head sha (`gitea.event.pull_request.head.sha || gitea.sha`) collapses both events into one bucket, leaving exactly one ui-test alive per commit. Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitea/workflows/ui-test.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitea/workflows/ui-test.yaml b/.gitea/workflows/ui-test.yaml index cb7812d..fe40048 100644 --- a/.gitea/workflows/ui-test.yaml +++ b/.gitea/workflows/ui-test.yaml @@ -19,10 +19,10 @@ on: # Playwright launches its own `pnpm dev` on :5173. In host-mode the # runner shares the host's port namespace, so two parallel ui-test # jobs (e.g. a push event racing with a pull_request event for the -# same commit) collide on EADDRINUSE. Group by branch/ref and cancel -# the in-progress run so only one ui-test is alive at a time per ref. +# same commit) collide on EADDRINUSE. Group by the head commit so +# push and pull_request events for the same sha share one bucket. concurrency: - group: ui-test-${{ gitea.ref }} + group: ui-test-${{ gitea.event.pull_request.head.sha || gitea.sha }} cancel-in-progress: true jobs: