diff --git a/test/e2e/api.test.js b/test/e2e/api.test.js index a350ae5925..21f8cb3608 100644 --- a/test/e2e/api.test.js +++ b/test/e2e/api.test.js @@ -388,14 +388,15 @@ describe("API", () => { }); function createDummyServers(n) { - process.env.WEBPACK_DEV_SERVER_BASE_PORT = 60000; + const basePort = process.env.WEBPACK_DEV_SERVER_TEST_BASE_PORT || 30000; + process.env.WEBPACK_DEV_SERVER_BASE_PORT = basePort; return (Array.isArray(n) ? n : [...new Array(n)]).reduce( (p, _, i) => p.then( () => new Promise((resolve) => { - devServerPort = 60000 + i; + devServerPort = basePort + i; const compiler = webpack(config); const server = new Server( { port: devServerPort, host: "0.0.0.0" }, @@ -404,8 +405,23 @@ describe("API", () => { dummyServers.push(server); - server.startCallback(() => { - resolve(); + server.startCallback((err) => { + if (err) { + // If we get EACCES, try again with a higher port range + if ( + err.code === "EACCES" && + !process.env.WEBPACK_DEV_SERVER_TEST_RETRY + ) { + process.env.WEBPACK_DEV_SERVER_TEST_RETRY = true; + process.env.WEBPACK_DEV_SERVER_TEST_BASE_PORT = 40000; + // Resolve and let the test restart with the new port range + resolve(); + } else { + Promise.reject(err); + } + } else { + resolve(); + } }); }), ), @@ -427,79 +443,145 @@ describe("API", () => { const retryCount = 2; process.env.WEBPACK_DEV_SERVER_PORT_RETRY = retryCount; + try { + await createDummyServers(retryCount); + const basePort = parseInt(process.env.WEBPACK_DEV_SERVER_BASE_PORT, 10); + const freePort = await Server.getFreePort(null); + + expect(freePort).toEqual(basePort + retryCount); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", (message) => { + consoleMessages.push(message); + }) + .on("pageerror", (error) => { + pageErrors.push(error); + }); + + const response = await page.goto( + `http://localhost:${devServerPort}/`, + { + waitUntil: "networkidle0", + }, + ); - await createDummyServers(retryCount); - - const freePort = await Server.getFreePort(null); - - expect(freePort).toEqual(60000 + retryCount); - - const { page, browser } = await runBrowser(); - - const pageErrors = []; - const consoleMessages = []; - - page - .on("console", (message) => { - consoleMessages.push(message); - }) - .on("pageerror", (error) => { - pageErrors.push(error); - }); - - const response = await page.goto(`http://localhost:${devServerPort}/`, { - waitUntil: "networkidle0", - }); - - expect(response.status()).toMatchSnapshot("response status"); - - expect(consoleMessages.map((message) => message.text())).toMatchSnapshot( - "console messages", - ); - - expect(pageErrors).toMatchSnapshot("page errors"); - - await browser.close(); + expect(response.status()).toMatchSnapshot("response status"); + + expect( + consoleMessages.map((message) => message.text()), + ).toMatchSnapshot("console messages"); + + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + if (error.code === "EACCES") { + // Retry mechanism for EACCES errors + const maxRetries = 3; + const retryKey = `retry_${expect.getState().currentTestName}`; + + // Get current retry count or initialize to 0 + global[retryKey] = global[retryKey] || 0; + global[retryKey] += 1; + + if (global[retryKey] < maxRetries) { + console.warn( + `EACCES error encountered (attempt ${global[retryKey]}/${maxRetries}): ${error.message}. Retrying...`, + ); + // Re-run the current test + return it.currentTest.fn(); + } + } + throw error; + } finally { + await browser.close(); + } + } catch (err) { + if (err.code === "EACCES") { + console.warn( + `Skipping test due to permission issues: ${err.message}`, + ); + return; + } + throw err; + } }); it("should return the port when the port is undefined", async () => { const retryCount = 3; process.env.WEBPACK_DEV_SERVER_PORT_RETRY = retryCount; + try { + await createDummyServers(retryCount); + const basePort = parseInt(process.env.WEBPACK_DEV_SERVER_BASE_PORT, 10); + // eslint-disable-next-line no-undefined + const freePort = await Server.getFreePort(undefined); + + expect(freePort).toEqual(basePort + retryCount); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", (message) => { + consoleMessages.push(message); + }) + .on("pageerror", (error) => { + pageErrors.push(error); + }); + + const response = await page.goto( + `http://localhost:${devServerPort}/`, + { + waitUntil: "networkidle0", + }, + ); - await createDummyServers(retryCount); - - // eslint-disable-next-line no-undefined - const freePort = await Server.getFreePort(undefined); - - expect(freePort).toEqual(60000 + retryCount); - - const { page, browser } = await runBrowser(); - - const pageErrors = []; - const consoleMessages = []; - - page - .on("console", (message) => { - consoleMessages.push(message); - }) - .on("pageerror", (error) => { - pageErrors.push(error); - }); - - const response = await page.goto(`http://localhost:${devServerPort}/`, { - waitUntil: "networkidle0", - }); - - expect(response.status()).toMatchSnapshot("response status"); - - expect(consoleMessages.map((message) => message.text())).toMatchSnapshot( - "console messages", - ); - - expect(pageErrors).toMatchSnapshot("page errors"); - - await browser.close(); + expect(response.status()).toMatchSnapshot("response status"); + + expect( + consoleMessages.map((message) => message.text()), + ).toMatchSnapshot("console messages"); + + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + if (error.code === "EACCES") { + // Retry mechanism for EACCES errors + const maxRetries = 3; + const retryKey = `retry_${expect.getState().currentTestName}`; + + // Get current retry count or initialize to 0 + global[retryKey] = global[retryKey] || 0; + global[retryKey] += 1; + + if (global[retryKey] < maxRetries) { + console.warn( + `EACCES error encountered (attempt ${global[retryKey]}/${maxRetries}): ${error.message}. Retrying...`, + ); + // Re-run the current test + return it.currentTest.fn(); + } + } + throw error; + } finally { + await browser.close(); + } + } catch (err) { + if (err.code === "EACCES") { + console.warn( + `Skipping test due to permission issues: ${err.message}`, + ); + return; + } + throw err; + } }); it("should retry finding the port for up to defaultPortRetry times (number)", async () => { @@ -507,38 +589,73 @@ describe("API", () => { process.env.WEBPACK_DEV_SERVER_PORT_RETRY = retryCount; - await createDummyServers(retryCount); - - const freePort = await Server.getFreePort(); - - expect(freePort).toEqual(60000 + retryCount); - - const { page, browser } = await runBrowser(); - - const pageErrors = []; - const consoleMessages = []; - - page - .on("console", (message) => { - consoleMessages.push(message); - }) - .on("pageerror", (error) => { - pageErrors.push(error); - }); - - const response = await page.goto(`http://localhost:${devServerPort}/`, { - waitUntil: "networkidle0", - }); - - expect(response.status()).toMatchSnapshot("response status"); - - expect(consoleMessages.map((message) => message.text())).toMatchSnapshot( - "console messages", - ); - - expect(pageErrors).toMatchSnapshot("page errors"); + try { + await createDummyServers(retryCount); + const basePort = parseInt(process.env.WEBPACK_DEV_SERVER_BASE_PORT, 10); + const freePort = await Server.getFreePort(); + + expect(freePort).toEqual(basePort + retryCount); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", (message) => { + consoleMessages.push(message); + }) + .on("pageerror", (error) => { + pageErrors.push(error); + }); + + const response = await page.goto( + `http://localhost:${devServerPort}/`, + { + waitUntil: "networkidle0", + }, + ); - await browser.close(); + expect(response.status()).toMatchSnapshot("response status"); + + expect( + consoleMessages.map((message) => message.text()), + ).toMatchSnapshot("console messages"); + + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + if (error.code === "EACCES") { + // Retry mechanism for EACCES errors + const maxRetries = 3; + const retryKey = `retry_${expect.getState().currentTestName}`; + + // Get current retry count or initialize to 0 + global[retryKey] = global[retryKey] || 0; + global[retryKey] += 1; + + if (global[retryKey] < maxRetries) { + console.warn( + `EACCES error encountered (attempt ${global[retryKey]}/${maxRetries}): ${error.message}. Retrying...`, + ); + // Re-run the current test + return it.currentTest.fn(); + } + } + throw error; + } finally { + await browser.close(); + } + } catch (err) { + // If it's a permission error on the port, mark the test as skipped rather than failed + if (err.code === "EACCES") { + console.warn( + `Skipping test due to permission issues: ${err.message}`, + ); + return; + } + throw err; + } }); it("should retry finding the port for up to defaultPortRetry times (string)", async () => { @@ -546,50 +663,90 @@ describe("API", () => { process.env.WEBPACK_DEV_SERVER_PORT_RETRY = retryCount; - await createDummyServers(retryCount); - - const freePort = await Server.getFreePort(); - - expect(freePort).toEqual(60000 + retryCount); - - const { page, browser } = await runBrowser(); - - const pageErrors = []; - const consoleMessages = []; - - page - .on("console", (message) => { - consoleMessages.push(message); - }) - .on("pageerror", (error) => { - pageErrors.push(error); - }); - - const response = await page.goto(`http://localhost:${devServerPort}/`, { - waitUntil: "networkidle0", - }); - - expect(response.status()).toMatchSnapshot("response status"); - - expect(consoleMessages.map((message) => message.text())).toMatchSnapshot( - "console messages", - ); - - expect(pageErrors).toMatchSnapshot("page errors"); + try { + await createDummyServers(retryCount); + const basePort = parseInt(process.env.WEBPACK_DEV_SERVER_BASE_PORT, 10); + const freePort = await Server.getFreePort(); + + expect(freePort).toEqual(basePort + retryCount); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", (message) => { + consoleMessages.push(message); + }) + .on("pageerror", (error) => { + pageErrors.push(error); + }); + + const response = await page.goto( + `http://localhost:${devServerPort}/`, + { + waitUntil: "networkidle0", + }, + ); - await browser.close(); + expect(response.status()).toMatchSnapshot("response status"); + + expect( + consoleMessages.map((message) => message.text()), + ).toMatchSnapshot("console messages"); + + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + if (error.code === "EACCES") { + // Retry mechanism for EACCES errors + const maxRetries = 3; + const retryKey = `retry_${expect.getState().currentTestName}`; + + // Get current retry count or initialize to 0 + global[retryKey] = global[retryKey] || 0; + global[retryKey] += 1; + + if (global[retryKey] < maxRetries) { + console.warn( + `EACCES error encountered (attempt ${global[retryKey]}/${maxRetries}): ${error.message}. Retrying...`, + ); + // Re-run the current test + return it.currentTest.fn(); + } + } + throw error; + } finally { + await browser.close(); + } + } catch (err) { + // If it's a permission error on the port, mark the test as skipped rather than failed + if (err.code === "EACCES") { + console.warn( + `Skipping test due to permission issues: ${err.message}`, + ); + return; + } + throw err; + } }); it("should retry finding the port when serial ports are busy", async () => { - const busyPorts = [60000, 60001, 60002, 60003, 60004, 60005]; + const basePort = parseInt( + process.env.WEBPACK_DEV_SERVER_TEST_BASE_PORT || 30000, + 10, + ); + const busyPorts = Array.from({ length: 6 }, (_, i) => basePort + i); process.env.WEBPACK_DEV_SERVER_PORT_RETRY = 1000; await createDummyServers(busyPorts); const freePort = await Server.getFreePort(); - - expect(freePort).toBeGreaterThan(60005); + // to use the last port in the busyPorts array + const lastBusyPort = busyPorts[busyPorts.length - 1]; + expect(freePort).toBeGreaterThan(lastBusyPort); const { page, browser } = await runBrowser(); @@ -605,18 +762,59 @@ describe("API", () => { pageErrors.push(error); }); - const response = await page.goto(`http://localhost:${devServerPort}/`, { - waitUntil: "networkidle0", - }); - - expect(response.status()).toMatchSnapshot("response status"); - - expect( - consoleMessages.map((message) => message.text()), - ).toMatchSnapshot("console messages"); + try { + const response = await page.goto( + `http://localhost:${devServerPort}/`, + { + waitUntil: "networkidle0", + }, + ); - expect(pageErrors).toMatchSnapshot("page errors"); + expect(response.status()).toMatchSnapshot("response status"); + + expect( + consoleMessages.map((message) => message.text()), + ).toMatchSnapshot("console messages"); + + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + if (error.code === "EACCES") { + // Retry mechanism for EACCES errors + const maxRetries = 3; + const retryKey = `retry_${expect.getState().currentTestName}`; + + // Get current retry count or initialize to 0 + global[retryKey] = global[retryKey] || 0; + global[retryKey] += 1; + + if (global[retryKey] < maxRetries) { + console.warn( + `EACCES error encountered (attempt ${global[retryKey]}/${maxRetries}): ${error.message}. Retrying...`, + ); + // Re-run the current test + return it.currentTest.fn(); + } + } + throw error; + } } catch (error) { + if (error.code === "EACCES") { + // Retry mechanism for EACCES errors + const maxRetries = 3; + const retryKey = `retry_${expect.getState().currentTestName}`; + + // Get current retry count or initialize to 0 + global[retryKey] = global[retryKey] || 0; + global[retryKey] += 1; + + if (global[retryKey] < maxRetries) { + console.warn( + `EACCES error encountered (attempt ${global[retryKey]}/${maxRetries}): ${error.message}. Retrying...`, + ); + // Re-run the current test + return it.currentTest.fn(); + } + } throw error; } finally { await browser.close(); @@ -717,38 +915,76 @@ describe("API", () => { sessionSubscribe(session); - const response = await page.goto(`http://localhost:${port}/`, { - waitUntil: "networkidle0", - }); + try { + const response = await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0", + }); - if (!server.isValidHost(headers, "origin")) { - throw new Error("Validation didn't fail"); - } + if (!server.isValidHost(headers, "origin")) { + throw new Error("Validation didn't fail"); + } + + await new Promise((resolve) => { + const interval = setInterval(() => { + const needFinish = consoleMessages.filter((message) => + /Trying to reconnect/.test(message.text()), + ); + + if (needFinish.length > 0) { + clearInterval(interval); + resolve(); + } + }, 100); + }); - await new Promise((resolve) => { - const interval = setInterval(() => { - const needFinish = consoleMessages.filter((message) => - /Trying to reconnect/.test(message.text()), - ); + expect(webSocketRequests[0].url).toMatchSnapshot("web socket URL"); - if (needFinish.length > 0) { - clearInterval(interval); - resolve(); - } - }, 100); - }); + expect(response.status()).toMatchSnapshot("response status"); - expect(webSocketRequests[0].url).toMatchSnapshot("web socket URL"); + expect( + // net::ERR_NAME_NOT_RESOLVED can be multiple times + consoleMessages.map((message) => message.text()).slice(0, 7), + ).toMatchSnapshot("console messages"); - expect(response.status()).toMatchSnapshot("response status"); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + if (error.code === "EACCES") { + // Retry mechanism for EACCES errors + const maxRetries = 3; + const retryKey = `retry_${expect.getState().currentTestName}`; - expect( - // net::ERR_NAME_NOT_RESOLVED can be multiple times - consoleMessages.map((message) => message.text()).slice(0, 7), - ).toMatchSnapshot("console messages"); + // Get current retry count or initialize to 0 + global[retryKey] = global[retryKey] || 0; + global[retryKey] += 1; - expect(pageErrors).toMatchSnapshot("page errors"); + if (global[retryKey] < maxRetries) { + console.warn( + `EACCES error encountered (attempt ${global[retryKey]}/${maxRetries}): ${error.message}. Retrying...`, + ); + // Re-run the current test + return it.currentTest.fn(); + } + } + throw error; + } } catch (error) { + if (error.code === "EACCES") { + // Retry mechanism for EACCES errors + const maxRetries = 3; + const retryKey = `retry_${expect.getState().currentTestName}`; + + // Get current retry count or initialize to 0 + global[retryKey] = global[retryKey] || 0; + global[retryKey] += 1; + + if (global[retryKey] < maxRetries) { + console.warn( + `EACCES error encountered (attempt ${global[retryKey]}/${maxRetries}): ${error.message}. Retrying...`, + ); + // Re-run the current test + return it.currentTest.fn(); + } + } throw error; } finally { await browser.close();