import { createServer } from "node:http";
import { readFileSync } from "node:fs";
import type { AddressInfo } from "node:net";
import { expect, test } from "@playwright/test";
// Isolate the browser loader with the real worker. This is not a native-run
// qualification test: no Paperclip data, credentials or providers are used.
test.use({ serviceWorkers: "allow", trace: "on" });
test("a wide development module graph loads before and after service-worker takeover", async ({ page, context }) => {
test.setTimeout(60_000);
const moduleCount = 1_500;
const worker = readFileSync(new URL("../../ui/public/sw.js", import.meta.url), "utf8");
const entry = Array.from({ length: moduleCount }, (_, i) => `import "./part-${i}.js";`).join("\n") +
'\ndocument.getElementById("root").innerHTML = "Ready";';
const failures: { path: string; error: string | null; worker: boolean }[] = [];
let forwardedModules = 0;
const server = createServer((request, response) => {
const pathname = new URL(request.url ?? "/", "http://fixture.test").pathname;
response.setHeader("cache-control", "no-cache");
if (pathname === "/sw.js") {
response.setHeader("content-type", "text/javascript");
response.end(worker);
} else if (pathname === "/src/entry.js") {
response.setHeader("content-type", "text/javascript");
response.end(entry);
} else if (/^\/src\/part-\d+\.js$/.test(pathname)) {
response.setHeader("content-type", "text/javascript");
// A bounded delay keeps the import fan-out outstanding, rather than
// making the resource-pressure regression depend on host speed.
setTimeout(() => response.end("export {};"), 5);
} else {
response.setHeader("content-type", "text/html");
response.end('
');
}
});
context.on("request", request => {
if (request.serviceWorker() && new URL(request.url()).pathname.startsWith("/src/")) forwardedModules += 1;
});
context.on("requestfailed", request => failures.push({
path: new URL(request.url()).pathname,
error: request.failure()?.errorText ?? null,
worker: Boolean(request.serviceWorker()),
}));
await new Promise(resolve => server.listen(0, "127.0.0.1", resolve));
const origin = `http://127.0.0.1:${(server.address() as AddressInfo).port}`;
try {
await page.goto(origin, { waitUntil: "domcontentloaded" });
await expect(page.getByRole("main")).toHaveText("Ready");
expect(failures).toEqual([]);
await page.evaluate(async () => {
await navigator.serviceWorker.register("/sw.js");
await navigator.serviceWorker.ready;
});
await page.waitForFunction(() => Boolean(navigator.serviceWorker.controller));
// Match the full same-URL navigation in the failed live attempt.
await page.goto(origin, { waitUntil: "domcontentloaded" });
await expect(page.getByRole("main")).toHaveText("Ready");
expect(failures).toEqual([]);
expect(forwardedModules).toBe(0);
} finally {
await test.info().attach("module-loading", {
contentType: "application/json",
body: Buffer.from(JSON.stringify({ moduleCount, forwardedModules, failures })),
});
server.closeAllConnections();
await new Promise((resolve, reject) => server.close(error => error ? reject(error) : resolve()));
}
});