reduce e2e test flakiness
This commit is contained in:
+43
-18
@@ -12,13 +12,36 @@ import * as path from "path";
|
|||||||
export const test = base.extend<{ server: { baseURL: string }; page: Page }>({
|
export const test = base.extend<{ server: { baseURL: string }; page: Page }>({
|
||||||
server: [
|
server: [
|
||||||
async ({}, use) => {
|
async ({}, use) => {
|
||||||
|
const server = await startServer();
|
||||||
|
await use({ baseURL: server.baseURL });
|
||||||
|
await killTree(server.child);
|
||||||
|
// Clean up the DB files (including -wal / -shm).
|
||||||
|
for (const suffix of ["", "-wal", "-shm"]) {
|
||||||
|
fs.rmSync(server.dbPath + suffix, { force: true });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ scope: "test", auto: true },
|
||||||
|
],
|
||||||
|
|
||||||
|
// Provide a page whose baseURL points at this test's server.
|
||||||
|
page: async ({ browser, server }, use) => {
|
||||||
|
const context = await browser.newContext({ baseURL: server.baseURL });
|
||||||
|
const page = await context.newPage();
|
||||||
|
await use(page);
|
||||||
|
await context.close();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/** Starts a server, retrying on a fresh port if the first attempt fails to bind. */
|
||||||
|
async function startServer() {
|
||||||
|
for (let attempt = 0; attempt < 5; attempt++) {
|
||||||
const dbPath = path.join(
|
const dbPath = path.join(
|
||||||
os.tmpdir(),
|
os.tmpdir(),
|
||||||
`sustenance-e2e-${process.pid}-${Date.now()}-${Math.random()
|
`sustenance-e2e-${process.pid}-${Date.now()}-${Math.random()
|
||||||
.toString(36)
|
.toString(36)
|
||||||
.slice(2)}.db`,
|
.slice(2)}.db`,
|
||||||
);
|
);
|
||||||
const port = 3200 + Math.floor(Math.random() * 2000);
|
const port = 20000 + Math.floor(Math.random() * 30000);
|
||||||
const baseURL = `http://127.0.0.1:${port}`;
|
const baseURL = `http://127.0.0.1:${port}`;
|
||||||
|
|
||||||
const child = spawn(
|
const child = spawn(
|
||||||
@@ -33,33 +56,35 @@ export const test = base.extend<{ server: { baseURL: string }; page: Page }>({
|
|||||||
// Point SEED_CONFIG at a nonexistent file so no default user is created.
|
// Point SEED_CONFIG at a nonexistent file so no default user is created.
|
||||||
SEED_CONFIG: path.join(os.tmpdir(), "sustenance-e2e-no-seed.json"),
|
SEED_CONFIG: path.join(os.tmpdir(), "sustenance-e2e-no-seed.json"),
|
||||||
},
|
},
|
||||||
stdio: "ignore",
|
stdio: ["ignore", "ignore", "pipe"],
|
||||||
// Run in its own process group so we can kill the whole tree.
|
// Run in its own process group so we can kill the whole tree.
|
||||||
detached: true,
|
detached: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let stderr = "";
|
||||||
|
child.stderr?.on("data", (chunk) => {
|
||||||
|
stderr += chunk.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
await waitForServer(baseURL, child);
|
await waitForServer(baseURL, child);
|
||||||
|
return { baseURL, child, dbPath };
|
||||||
await use({ baseURL });
|
} catch (error) {
|
||||||
|
// The server may have failed to bind (port collision). Clean up and retry.
|
||||||
await killTree(child);
|
await killTree(child);
|
||||||
// Clean up the DB files (including -wal / -shm).
|
|
||||||
for (const suffix of ["", "-wal", "-shm"]) {
|
for (const suffix of ["", "-wal", "-shm"]) {
|
||||||
fs.rmSync(dbPath + suffix, { force: true });
|
fs.rmSync(dbPath + suffix, { force: true });
|
||||||
}
|
}
|
||||||
},
|
if (attempt === 4) {
|
||||||
{ scope: "test", auto: true },
|
throw new Error(
|
||||||
],
|
`server failed to start after retries; last stderr:\n${stderr}\n${error}`,
|
||||||
|
);
|
||||||
// Provide a page whose baseURL points at this test's server.
|
}
|
||||||
page: async ({ browser, server }, use) => {
|
}
|
||||||
const context = await browser.newContext({ baseURL: server.baseURL });
|
}
|
||||||
const page = await context.newPage();
|
throw new Error("unreachable");
|
||||||
await use(page);
|
}
|
||||||
await context.close();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
async function waitForServer(baseURL: string, child: ChildProcess) {
|
async function waitForServer(baseURL: string, child: ChildProcess) {
|
||||||
const deadline = Date.now() + 60_000;
|
const deadline = Date.now() + 60_000;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { defineConfig } from "@playwright/test";
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
testDir: "./tests",
|
testDir: "./tests",
|
||||||
timeout: 30_000,
|
timeout: 30_000,
|
||||||
retries: 0,
|
retries: 2,
|
||||||
use: {
|
use: {
|
||||||
trace: "on-first-retry",
|
trace: "on-first-retry",
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user