diff --git a/site/e2e/helpers.ts b/site/e2e/helpers.ts index c5ac7f1abde65..f099d9ff39a5a 100644 --- a/site/e2e/helpers.ts +++ b/site/e2e/helpers.ts @@ -1,5 +1,6 @@ import { type ChildProcess, exec, spawn } from "node:child_process"; import { randomUUID } from "node:crypto"; +import net from "node:net"; import path from "node:path"; import { Duplex } from "node:stream"; import { type BrowserContext, type Page, expect, test } from "@playwright/test"; @@ -685,6 +686,8 @@ export class Awaiter { export const createServer = async ( port: number, ): Promise> => { + await waitForPort(port); // Wait until the port is available + const e = express(); // We need to specify the local IP address as the web server // tends to fail with IPv6 related error: @@ -693,6 +696,44 @@ export const createServer = async ( return e; }; +async function waitForPort( + port: number, + host = "0.0.0.0", + timeout = 30000, +): Promise { + const start = Date.now(); + while (Date.now() - start < timeout) { + const available = await isPortAvailable(port, host); + if (available) { + return; + } + console.warn(`${host}:${port} is in use, checking again in 1s`); + await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second before retrying + } + throw new Error( + `Timeout: port ${port} is still in use after ${timeout / 1000} seconds.`, + ); +} + +function isPortAvailable(port: number, host = "0.0.0.0"): Promise { + return new Promise((resolve) => { + const probe = net + .createServer() + .once("error", (err: NodeJS.ErrnoException) => { + if (err.code === "EADDRINUSE") { + resolve(false); // port is in use + } else { + resolve(false); // some other error occurred + } + }) + .once("listening", () => { + probe.close(); + resolve(true); // port is available + }) + .listen(port, host); + }); +} + export const findSessionToken = async (page: Page): Promise => { const cookies = await page.context().cookies(); const sessionCookie = cookies.find((c) => c.name === "coder_session_token");