ci: make backend test failures visible and prevent silent Playwright hangs #570
Reference in New Issue
Block a user
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?
Problem
Two recurring CI failure modes are currently undiagnosable and slow to surface:
1. Backend — hidden failures, 17-minute hangs
Spring Boot's verbose startup output (banner + INFO for every bean per context restart) hits Gitea's 1.4 MB log cap before all 102 test classes have run. When a test in the later half fails or hangs, the log is already truncated — no failure message is visible, and the job silently occupies the runner for 10–17 minutes before timing out.
Observed in run #1636: all visible tests passed, job ran 17 min (4× normal), failure invisible.
2. Frontend — Playwright/Chromium silent crash
During
vitest run -c vitest.client-coverage.config.ts --coverage, the Chromium browser process can crash mid-test-load. Becausebirpcloses its connection without a teardown event, Vitest waits indefinitely — no output, no error. The existing[birpc] rpc is closedguard only catches teardown races; a mid-run crash bypasses it entirely.Observed in run #1637: 534 server tests passed, 3 browser tests passed, log ends mid-Svelte-compilation at
AnnotationEditOverlay.svelte, then 14 min of silence before job timeout.Plan
Backend
1. Silence Spring noise in the test log
Add to
backend/src/test/resources/application.properties:Drops log size from 3 MB+ to well under 100 KB — all 102 test classes fit inside the cap, failures are always visible.
2. Add a Surefire test timeout
Add to
maven-surefire-pluginconfig inpom.xml:A hanging integration test fails loudly in ≤2 min instead of silently consuming the CI slot for 13+ min.
3. Upload surefire XML reports as a CI artifact
Add after the backend test step in
.gitea/workflows/ci.yml:These XML files capture every test result regardless of log verbosity — independent of the log cap.
Frontend
4. Add global timeouts to the browser vitest config
Add to
vitest.client-coverage.config.ts:When Chromium crashes mid-load, Vitest times out the hanging test after 30 s and reports a diagnosable failure instead of hanging for 14 min.
Acceptance criteria
🏗️ Markus Keller — Senior Application Architect
Observations
backend/src/test/resources/application.propertiesis the correct Spring Boot convention for test-scoped config overrides. It doesn't affect the main profile.<timeout>90</timeout>element deserves closer scrutiny — see Sara's review for the technical detail. If it silently aliasesforkedProcessTimeoutInSeconds, the two values in the same block conflict.Recommendations
👨💻 Felix Brandt — Senior Fullstack Developer
Observations
backend/src/test/resources/application.propertiesdoes not exist yet — this is a new file. No merging, no conflicts.logging.level.root=WARNsilences Testcontainers image pull and startup messages (logged at INFO). Container startup failures still surface at ERROR, so they remain visible.logging.level.org.raddatz=INFOpreserves your own application logs — right call.vitest.client-coverage.config.tsadditions belong at the same level asexpectandbrowserinside thetest:block — correct Vitest 4 placement. DefaulttestTimeoutis 5 000 ms, so 30 000 ms is a clear improvement without being recklessly long.Concern:
<timeout>90</timeout>in Surefire is a deprecated alias, not a per-test timeoutIn Maven Surefire 3.x,
<timeout>is a deprecated alias for<forkedProcessTimeoutInSeconds>. Having both in the same<configuration>block creates a conflict — the effective ceiling ends up being 90 seconds (last-wins or alias precedence), not 120. With Testcontainers PostgreSQL startup taking 20–40 seconds on a cold cache, only ~50 seconds remain for all integration tests — likely insufficient.Neither element gives you per-test JUnit 5 timeouts. A single hanging
@Teststill blocks until the JVM ceiling is hit. The correct approach:junit.jupiter.execution.timeout.defaulttimes out each hanging test individually, lets healthy tests continue, and keeps the 120-second JVM ceiling as a backstop for catastrophic hangs.Recommendations
<timeout>90</timeout>. Replace with thejunit.jupiter.execution.timeout.defaultsystem property as above.<forkedProcessTimeoutInSeconds>120</forkedProcessTimeoutInSeconds>as the JVM-level safety net.🔧 Tobias Wendt — DevOps & Platform Engineer
Observations
actions/upload-artifact@v3is correct. ADR-014 (issue #557) pins artifact actions to v3; theunit-testsjob already has an explicit self-check step that blocks v4+ with a grep guard. The proposed surefire upload step is already compliant. Do not bump to v4.if: always()on the artifact upload step is essential — without it you only get artifacts on success, which is precisely when you don't need them. Correct pattern.Run backend testsinbackend-unit-testsis right. XML reports are written during test execution and are available regardless of exit code.maven-${{ hashFiles('backend/pom.xml') }}) will be invalidated once when the Surefire plugin config is added topom.xml. Expected and acceptable — one cold build.Concern:
<timeout>90</timeout>may override<forkedProcessTimeoutInSeconds>120</forkedProcessTimeoutInSeconds>If
<timeout>is a deprecated alias forforkedProcessTimeoutInSecondsin Surefire 3.x (it is), the effective JVM ceiling becomes 90 seconds, not 120. On a Testcontainers-heavy suite where container startup alone can consume 30–40 seconds, this leaves a very narrow window. See Felix's review for the correct fix.Recommendations
<timeout>vsforkedProcessTimeoutInSecondsconflict before merging (Felix's fix).🔒 Nora "NullX" Steiner — Application Security Engineer
Observations
logging.level.root=WARN: Reduces verbosity; does not expose more data. Security-relevant events in this project (auth failures, permission denials) are recorded throughAuditService— which logs at WARN or ERROR, not INFO — so they remain visible under the new log level. Spring Security's access-decision logging sits at DEBUG and is already suppressed in normal runs.No security concerns with any of the proposed changes.
🧪 Sara Holt — Senior QA Engineer
Observations
if: always()ensures they're available on failure. Zero-effort win.testTimeout: 30_000andhookTimeout: 15_000are placed correctly invitest.client-coverage.config.ts(same level asexpect:andbrowser:). Default VitesttestTimeoutis 5 000 ms — 30 s is an appropriate step-up for browser component tests with Svelte compilation overhead.Assert no birpc teardown racestep checks for[birpc] rpc is closed— a teardown race. A mid-run Chromium crash produces a different failure mode (no message, silent hang). ThetestTimeoutfix addresses the crash scenario; the birpc guard addresses the teardown scenario. They are complementary, not redundant.Concern:
<timeout>90</timeout>in Surefire is a deprecated alias, not a per-test JUnit 5 timeoutIn Maven Surefire 3.x,
<timeout>is a deprecated alias for<forkedProcessTimeoutInSeconds>. Having both in the same<configuration>block results in a conflicting or implementation-defined effective value — likely 90 seconds, overriding the intended 120. More importantly, neither element provides per-test timeouts in JUnit 5. A hanging@Testblocks until the JVM ceiling is hit — no diagnostic timeout error, no test name in the output.The correct approach for per-test JUnit 5 timeouts:
This times out each hanging test individually (with a meaningful test name in the error), lets all other tests continue running, and keeps
forkedProcessTimeoutInSeconds=120as a backstop for catastrophic JVM-level hangs.Acceptance criteria gap: AC1 lacks automated enforcement
AC1 ("Backend log output stays under 500 KB") is measurable but has no automated check. The log-level change should make this permanently true, but it's worth verifying it will hold. Adding a log-size assertion to the CI step makes AC1 a gate rather than a spot-check:
This is optional — the 500 KB threshold will be structurally guaranteed by the
WARNlog level — but it makes the acceptance criterion machine-verifiable.Recommendations
<timeout>config before merging. Remove<timeout>90</timeout>; replace with<systemPropertyVariables>containingjunit.jupiter.execution.timeout.default.🎨 Leonie Voss — UX Designer & Accessibility Strategist
This is a CI infrastructure change with no user-facing component. Checked: the proposed changes affect only
backend/src/test/resources/application.properties,pom.xml,.gitea/workflows/ci.yml, andvitest.client-coverage.config.ts— no Svelte components, no routes, no CSS, no interaction patterns, no accessibility surface, no brand decisions.No UX concerns from this side.
📋 Elicit — Requirements Engineer
Observations
Acceptance criteria quality
testTimeout; hard to reproduce deterministically in CIEdge case to clarify
The observed crash in run #1637 ended "mid-Svelte-compilation at
AnnotationEditOverlay.svelte" — before any test had been scheduled for execution. In Vitest browser mode,testTimeout: 30_000fires per-test once test execution starts. If Chromium dies before the first test is scheduled, the timeout fires on the initialization/compilation phase rather than test execution.The
hookTimeout: 15_000coversbeforeAll/beforeEachhooks, which is the closest built-in mechanism to the pre-execution phase. In practice,testTimeoutshould still cause Vitest to eventually surface a timeout error for each pending test — but the 30-second window applies per test, not to the entire hanging suite. If 20 tests are queued when Chromium dies, worst case is 20 × 30 s = 10 minutes before the suite fails. Still a significant improvement over the 14-minute observed hang, but worth verifying empirically that the worst case is acceptable.No blocking concerns — the fix is structurally correct. The edge case note is for implementation awareness only.
Implementation complete — PR opened
All four changes are implemented and all 1585 backend tests pass.
Commits
dcb2b2fbackend/src/test/resources/application.properties—WARNroot level,INFOfororg.raddatz811b80cmaven-surefire-plugintopom.xml—forkedProcessTimeoutInSeconds=120+junit.jupiter.execution.timeout.default=90 ssystem property (replaces deprecated<timeout>alias per Felix/Sara review)fd4d14fif: always(), pinned toupload-artifact@v3per ADR-01446e2e93testTimeout: 30_000+hookTimeout: 15_000tovitest.client-coverage.config.tsKey design choice
The Surefire config uses
junit.jupiter.execution.timeout.defaultas a system property rather than the deprecated<timeout>element. This gives per-test JUnit 5 timeouts (a hanging test fails with a named error after 90 s, healthy tests continue), withforkedProcessTimeoutInSeconds=120as a JVM-level backstop for catastrophic hangs.