test(ui): fix timing flake in SettingsPage Dynamic Tools tests#1931
Conversation
The SettingsPage Dynamic Tools tests waited for the toggle's presence,
but the toggle renders unconditionally from first paint (unchecked,
disabled until settingsLoaded), so waitFor(getDynamicToolsToggle)
resolved before the mocked getSettings() flushed into React state.
Assertions and userEvent clicks (a no-op on a disabled input) then
raced the async settings load — a race the immediately-resolved mocks
usually masked locally but that fired under CI load ('is disabled and
reflects the env value when locked' failed on runs of #1802/#1803).
Fix: wait for the settled state instead of presence — an enabled
toggle for the unlocked tests, the GAIA_DYNAMIC_TOOLS lock note for
the locked test. The API mocks now resolve on a 30ms macrotask, like
a real HTTP response, so the race stays exposed: before the fix that
delay failed 5/7 tests deterministically. Also mock getSystemStatus
with a valid SystemStatus instead of 'null as never', which threw a
TypeError inside the component on every test.
|
CI evidence: the full "Test Electron Framework and Apps" workflow — including the "Test Agent UI Components (Vitest)" job this PR fixes — is green on this branch: run 28820012621 (manually dispatched after updating the branch from main). 🔍 Why a manual dispatch, and history of the earlier red run
|
|
Verdict: Approve This is a clean, test-only fix for a real CI flake (#1933) in the SettingsPage Dynamic Tools tests. The old tests waited for the toggle merely to exist — but it renders from first paint (disabled until The root-cause writeup and the deterministic 30 ms repro (kept in-tree as the regression guard) are exactly what a flake fix should carry, and it also quietly fixes the 🔍 Technical detailsVerified against the component:
Strengths:
No issues found. |
|
Live real-surface verification of the behavior the flaky test asserts — real Agent UI (built frontend +
Tested by driving the served UI in a headless browser against the locked backend; screenshot captured in-session. |
Fixes #1933.
The Agent UI Vitest job could fail on unrelated PRs:
SettingsPage.test.tsx > is disabled and reflects the env value when lockedflaked on CI (Dependabot #1802 run 28763820070 and #1803) while passing on identical code elsewhere, eroding trust in a red check. The tests waited for the toggle to exist, but it renders from first paint (unchecked, disabled until settings load), so assertions and clicks raced the asyncgetSettings()flush — clicks on the still-disabled toggle silently no-op. Five of the seven tests carried the race; CI load determined whether it fired. After: the tests wait for the settled state (toggle enabled, or the lock note for the locked case), and the API mocks resolve one macrotask later — like real HTTP — so the race stays permanently exposed instead of masked. (No env-var or cross-file state leakage was involved; the "env value" in the test name is theGAIA_DYNAMIC_TOOLSlock note rendered by the UI.)Proof
Race reproduced deterministically on unfixed code by giving the mocks a realistic 30 ms response latency (kept in this PR as the regression guard) — including the exact assertion that failed in CI:
With the fix, stable under shuffle and repetition (run in
src/gaia/apps/webui):15 consecutive runs of the file: 0 failures.
npx tsc --noEmitclean. Also fixed:getSystemStatuswas mocked asnull, throwing aTypeErrorinside the component in every test's stderr; it now returns a validSystemStatus.Test plan
cd src/gaia/apps/webui && npx vitest run— 109/109 passnpx vitest run --sequence.shuffle(any seed) — 109/109 pass