test(web): eliminate timeout flakiness in the web test suite (closes #1596)

Root-cause and deterministically fix the recurring 5s-timeout flakes that
surfaced under v8-instrumented, concurrent load. All fixes are test-only; no
product source changed. No test was skipped or disabled, and the per-file
coverage gate (>=90 on all four dims) still holds.

Common root cause across the modal/form/screen suites: userEvent.setup()
schedules a real setTimeout between keystrokes. Under CPU contention those
yields balloon, so multi-field interactions blew past the 5s per-test
timeout. Fix: userEvent.setup({ delay: null }) removes the per-keystroke
real-timer dependence (typing dispatches synchronously), making the suites
load-independent.

Per file:
- ServerConfigModal / ServerImportConfigModal / ServerImportJsonModal /
  ResourcesScreen / InspectorView: userEvent.setup({ delay: null }).
- PromptArgumentsForm (completions): replaced wall-clock sleeps sized just
  past the 300ms completion debounce (await setTimeout(400)) — which race
  the debounce timer under load — with awaited conditions (findBy/waitFor)
  on the real rendered outcome, plus delay:null. Negative assertions now
  lean on the component's synchronous state/timer teardown instead of a
  timed window, so they are deterministic regardless of machine speed.
- inspectorClient integration "tracks stderr logs": the child's stderr is
  piped out-of-band from the tool's JSON-RPC response, so reading the log
  synchronously after callTool raced the stderr chunk. Wrapped the assertion
  in vi.waitFor so it polls until the line lands.

Verified: web validate + 6x test:coverage (4 sequential + 2 concurrent
under load) + standalone test:integration, all green (229 files / 3237
tests per coverage run; 799 integration tests), zero intermittent failures.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S3fTN8H3R8YV4yUGvZjYnX
This commit is contained in:
cliffhall 2026-07-01 21:11:11 -04:00
commit 4bad110942

View file

@ -114,7 +114,7 @@ describe("ServerImportJsonModal", () => {
});
it("builds the config with env overrides and calls onAddServer", async () => {
const user = userEvent.setup();
const user = userEvent.setup({ delay: null });
const onAddServer = vi.fn().mockResolvedValue(undefined);
const onClose = vi.fn();
renderWithMantine(
@ -142,7 +142,7 @@ describe("ServerImportJsonModal", () => {
});
it("honors a server name override", async () => {
const user = userEvent.setup();
const user = userEvent.setup({ delay: null });
const onAddServer = vi.fn().mockResolvedValue(undefined);
renderWithMantine(
<ServerImportJsonModal
@ -163,7 +163,7 @@ describe("ServerImportJsonModal", () => {
});
it("lets the user pick among multiple packages", async () => {
const user = userEvent.setup();
const user = userEvent.setup({ delay: null });
const onAddServer = vi.fn().mockResolvedValue(undefined);
renderWithMantine(
<ServerImportJsonModal
@ -182,7 +182,7 @@ describe("ServerImportJsonModal", () => {
});
it("surfaces an onAddServer rejection instead of closing", async () => {
const user = userEvent.setup();
const user = userEvent.setup({ delay: null });
const onAddServer = vi.fn().mockRejectedValue(new Error("disk full"));
const onClose = vi.fn();
renderWithMantine(
@ -241,7 +241,7 @@ describe("ServerImportJsonModal", () => {
});
it("loads server.json from a chosen file", async () => {
const user = userEvent.setup();
const user = userEvent.setup({ delay: null });
renderWithMantine(
<ServerImportJsonModal
opened
@ -284,7 +284,7 @@ describe("ServerImportJsonModal", () => {
});
it("re-opens File Contents when the textarea is cleared", async () => {
const user = userEvent.setup();
const user = userEvent.setup({ delay: null });
renderWithMantine(
<ServerImportJsonModal
opened
@ -301,7 +301,7 @@ describe("ServerImportJsonModal", () => {
});
it("rejects an invalid id override and blocks Add Server", async () => {
const user = userEvent.setup();
const user = userEvent.setup({ delay: null });
const onAddServer = vi.fn();
renderWithMantine(
<ServerImportJsonModal
@ -322,7 +322,7 @@ describe("ServerImportJsonModal", () => {
});
it("closes via the Escape key (no Cancel button)", async () => {
const user = userEvent.setup();
const user = userEvent.setup({ delay: null });
const onClose = vi.fn();
renderWithMantine(
<ServerImportJsonModal