Try to fix E2E test port stall
Some checks are pending
E2E Tests / e2e (push) Waiting to run
Tests and checks on push / run-checks-and-tests (push) Waiting to run
Updates translation progress / update-translation-progress-issue (push) Waiting to run

This commit is contained in:
Kalle
2026-08-01 21:23:00 +03:00
parent a8a877fa02
commit b67ae68d51
3 changed files with 30 additions and 9 deletions

View File

@@ -10,7 +10,7 @@ description: Run, debug, and manage Playwright e2e tests. Use when running e2e t
- Tests live in `e2e/*.spec.ts`, config in `playwright.config.ts`
- Page objects live in `e2e/pages/<feature>/` — every spec uses them; conventions in `docs/dev/e2e-page-objects.md`, gotchas in `docs/dev/e2e-page-objects-migration.md`
- Global setup (`e2e/global-setup.ts`) builds the app (skipped when no build input changed since the last e2e build — tracked via `.e2e-build-marker`), creates/migrates per-worker databases (via `scripts/ensure-test-db.ts`: pending migrations are applied, drifted databases are rebuilt), and starts one server per worker
- Port calculation: `E2E_BASE_PORT = PORT (from .env) + 500`. Default PORT is typically 4001, so base port = 4501. Worker N uses port base+N
- Port calculation: `E2E_BASE_PORT = PORT (from .env) + 500`. Worker N uses port base+N, except ports on the WHATWG fetch bad port list (e.g. 6679) are skipped — see `e2eWorkerPort` in `e2e/helpers/playwright.ts`
- Worker count: `E2E_WORKERS` env, defaulting to `min(8, max(4, cores - 2))`
- Worker databases: `db-test-e2e-<N>.sqlite3` in the project root; every test starts from a wiped database holding only the admin (Sendou) and N-ZAP users, and builds its own data with the `factories` fixture
- MinIO (S3-compatible storage) is started via Docker Compose if not already running

View File

@@ -2,7 +2,7 @@ import { type ChildProcess, execSync, spawn } from "node:child_process";
import fs from "node:fs";
import type { FullConfig } from "@playwright/test";
import { ensureMigratedDb } from "../scripts/ensure-test-db";
import { E2E_BASE_PORT } from "./helpers/playwright";
import { E2E_BASE_PORT, e2eWorkerPort } from "./helpers/playwright";
const DEBUG = process.env.E2E_DEBUG === "true";
const SERVER_PROCESSES: ChildProcess[] = [];
@@ -182,12 +182,13 @@ async function globalSetup(config: FullConfig) {
// Prepare databases and start servers for each worker
const serverPromises: Promise<void>[] = [];
// Kill any existing processes on our ports before starting
// Kill any existing processes on our ports before starting; sweep beyond the
// current worker count so leftovers from a run with more workers also die
// biome-ignore lint/suspicious/noConsole: CLI script output
console.log("Cleaning up any existing processes on e2e ports...");
const killedSomething = killProcessesOnPorts(
E2E_BASE_PORT,
E2E_BASE_PORT + workerCount - 1,
e2eWorkerPort(Math.max(workerCount, 8) - 1),
);
if (killedSomething) {
// Wait briefly for ports to be released
@@ -195,7 +196,7 @@ async function globalSetup(config: FullConfig) {
}
for (let i = 0; i < workerCount; i++) {
const port = E2E_BASE_PORT + i;
const port = e2eWorkerPort(i);
const dbPath = `db-test-e2e-${i}.sqlite3`;
ensureMigratedDb(dbPath);
@@ -255,12 +256,13 @@ async function globalSetup(config: FullConfig) {
);
}
// Store server processes globally for teardown before awaiting readiness so
// a failed startup still gets every already-spawned server cleaned up
global.__E2E_SERVERS__ = SERVER_PROCESSES;
// Wait for all servers to be ready
await Promise.all(serverPromises);
// Store server processes globally for teardown
global.__E2E_SERVERS__ = SERVER_PROCESSES;
// biome-ignore lint/suspicious/noConsole: CLI script output
console.log("\nAll servers started successfully!\n");
}

View File

@@ -24,6 +24,25 @@ export const E2E_BASE_PORT = Number(process.env.PORT || 5173) + 500;
export const MOBILE_VIEWPORT = { width: 375, height: 667 };
export const TABLET_VIEWPORT = { width: 768, height: 1024 };
/** Registered (>=1024) ports on the WHATWG fetch bad port list: Node's fetch
* fails on them with "bad port" and Chromium with ERR_UNSAFE_PORT, so no worker
* server may listen on one (e.g. base port 6673 would put worker 6 on 6679). */
const UNSAFE_PORTS = new Set([
1719, 1720, 1723, 2049, 3659, 4045, 4190, 5060, 5061, 5432, 5500, 5938, 6000,
6566, 6665, 6666, 6667, 6668, 6669, 6679, 6697, 10080,
]);
/** The port of the given worker's server: base port + index, skipping unsafe ports. */
export function e2eWorkerPort(workerIndex: number) {
let port = E2E_BASE_PORT - 1;
for (let i = 0; i <= workerIndex; i++) {
do {
port++;
} while (UNSAFE_PORTS.has(port));
}
return port;
}
type WorkerFixtures = {
workerPort: number;
workerBaseURL: string;
@@ -49,7 +68,7 @@ export const test = base.extend<TestFixtures, WorkerFixtures>({
workerPort: [
// biome-ignore lint/correctness/noEmptyPattern: Playwright requires object destructuring
async ({}, use, workerInfo) => {
const port = E2E_BASE_PORT + workerInfo.parallelIndex;
const port = e2eWorkerPort(workerInfo.parallelIndex);
await use(port);
},
{ scope: "worker" },