// The e2e suite's request wrapper (e2e/retryConnect.ts): a connect that never came about is tried // again, as every TCP stack would; any other failure, or an answer, goes straight to the test. import { readdirSync, readFileSync } from "node:fs"; import { describe, expect, it, vi } from "vitest"; import { gotoRetrying, retryConnect } from "../../e2e/retryConnect"; const noConnection = () => new Error("apiRequestContext.get: connect ETIMEDOUT 127.0.0.1:8080"); describe("retryConnect", () => { it("tries a connect that timed out again, and hands back the answer that came", async () => { vi.useFakeTimers(); const get = vi .fn() .mockRejectedValueOnce(noConnection()) .mockRejectedValueOnce(noConnection()) .mockResolvedValue("ok"); const api = retryConnect({ get }); const answer = api.get("/healthz"); await vi.runAllTimersAsync(); await expect(answer).resolves.toBe("ok"); expect(get).toHaveBeenCalledTimes(3); expect(get).toHaveBeenLastCalledWith("/healthz"); vi.useRealTimers(); }); it("passes any other failure straight on, untried", async () => { const post = vi.fn().mockRejectedValue(new Error("read ECONNRESET")); await expect(retryConnect({ post }).post("/auth/password-login")).rejects.toThrow("ECONNRESET"); expect(post).toHaveBeenCalledTimes(1); }); it("gives up after its last wait, with the connect error", async () => { vi.useFakeTimers(); const get = vi.fn().mockRejectedValue(noConnection()); const answer = retryConnect({ get }).get("/api/me"); const settled = expect(answer).rejects.toThrow("ETIMEDOUT"); await vi.runAllTimersAsync(); await settled; expect(get).toHaveBeenCalledTimes(6); // the first try and five more vi.useRealTimers(); }); }); // The text Playwright gave when this stopped a release's e2e at its first page load (2026-11-04). const noBuffer = () => new Error("page.goto: net::ERR_NO_BUFFER_SPACE at http://127.0.0.1:8080/"); describe("gotoRetrying", () => { it("tries a page load the machine had no buffer for again", async () => { vi.useFakeTimers(); const goto = vi.fn().mockRejectedValueOnce(noBuffer()).mockResolvedValue("response"); const answer = gotoRetrying({ goto }, "/feed"); await vi.runAllTimersAsync(); await expect(answer).resolves.toBe("response"); expect(goto).toHaveBeenCalledTimes(2); expect(goto).toHaveBeenLastCalledWith("/feed"); vi.useRealTimers(); }); it("passes any other failure straight on, untried", async () => { const goto = vi.fn().mockRejectedValue(new Error("page.goto: net::ERR_CONNECTION_REFUSED")); await expect(gotoRetrying({ goto }, "/")).rejects.toThrow("ERR_CONNECTION_REFUSED"); expect(goto).toHaveBeenCalledTimes(1); }); it("gives up after its last wait, with the error", async () => { vi.useFakeTimers(); const goto = vi.fn().mockRejectedValue(noBuffer()); const settled = expect(gotoRetrying({ goto }, "/")).rejects.toThrow("ERR_NO_BUFFER_SPACE"); await vi.runAllTimersAsync(); await settled; expect(goto).toHaveBeenCalledTimes(6); vi.useRealTimers(); }); it("is the only way the suite loads a page", () => { const dir = new URL("../../e2e/", import.meta.url); const direct = readdirSync(dir) .filter((f) => f.endsWith(".ts") && f !== "retryConnect.ts") .filter((f) => /\.(goto|reload)\(/.test(readFileSync(new URL(f, dir), "utf-8"))); expect(direct).toEqual([]); }); });