Files
siftlode/frontend/e2e/retryConnect.ts
T
peter b9eb8458b3 test(e2e): a localhost connect the machine dropped is tried again on the test side
On a Windows host a burst of test traffic can drop a loopback connect now and then (every port,
any program — measured on the dev desktop). The browser resends it and only waits; Node (libuv)
sends a loopback SYN once and fails at ~300 ms with "connect ETIMEDOUT". `e2e/retryConnect.ts` wraps
Playwright's request context so that one error is tried again (0.5 / 1 / 2 / 4 / 8 s, the system's
own retry waits) while every other failure goes straight to the test; global.setup's health check,
login and /api/me and speed.measure's share probe go through it. Unit-tested; e2e 61/61.
2026-10-11 00:45:13 +02:00

35 lines
1.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Playwright's request context (it runs in Node), trying a connection again when it never came about —
// as every TCP stack does. Measured on the Windows dev desktop (2026-10-10, the work-group-timesheet
// suite): now and then, in bursts of 1–15 s, a connect to localhost is dropped — every port, IPv4 and
// IPv6, any program, no Docker needed; set off by any big test run on the machine. Every other client
// sends its SYN again and gets in a moment later, but Node (libuv) sends a loopback SYN only once on
// Windows and fails at ~300 ms with "connect ETIMEDOUT". The request never left this side, so trying it
// again is safe: the waits the system's own retries take (0.5, 1, 2, 4, 8 s). Anything else — a refused
// port, a reset, an answer — goes straight to the test. (The browser resends its own SYN: a page only
// waits, and nothing here can or should hide that.)
const METHODS = new Set(["get", "post", "put", "patch", "delete", "head", "fetch"]);
const NO_CONNECTION = /\bconnect ETIMEDOUT\b/;
const WAITS_MS = [500, 1000, 2000, 4000, 8000];
/** `ctx` with its request methods trying a connect that timed out again (see above). */
export function retryConnect<T extends object>(ctx: T): T {
return new Proxy(ctx, {
get(target, key) {
const v: unknown = Reflect.get(target, key, target);
if (typeof v !== "function") return v;
if (typeof key !== "string" || !METHODS.has(key)) return v.bind(target);
return async (...args: unknown[]) => {
for (let i = 0; ; i++) {
try {
return await v.apply(target, args);
} catch (e) {
const wait = WAITS_MS[i];
if (wait === undefined || !NO_CONNECTION.test(String((e as Error)?.message))) throw e;
await new Promise((r) => setTimeout(r, wait));
}
}
};
},
});
}