From 8ca8e016fac28f9b56855f7f1071737188878be9 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Mon, 18 Nov 2024 11:28:16 +0100 Subject: [PATCH] fix(site): wait until port is available in e2e (#15537) Related: https://github.com/coder/internal/issues/212 This PR modifies the logic responsible for creating a server in E2E tests to check if the port is free. Alternatively, we could refactor the framework to dynamically create server instances, but this solution might be a cheaper quick win. Note: I'll leave it as is now, it might be worth asking somebody with a frontend skillset to double-check this contribution. --------- Signed-off-by: Danny Kopping Co-authored-by: Danny Kopping --- site/e2e/helpers.ts | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/site/e2e/helpers.ts b/site/e2e/helpers.ts index 6a7a4d937d..8f69b90900 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"; @@ -687,6 +688,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: @@ -695,6 +698,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");