net::ERR_NO_BUFFER_SPACE stopped a release at its first page with nothing else running, and the unchanged rerun passed. Every page load of the suite now goes through one helper, held by a test.
83 lines
3.4 KiB
TypeScript
83 lines
3.4 KiB
TypeScript
// 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([]);
|
|
});
|
|
});
|