Fix Playwright dev server startup on runner
This commit is contained in:
@@ -2,7 +2,8 @@ import path from "node:path";
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
|
||||
const devPort = Number(process.env.PLAYWRIGHT_DEV_PORT || 5173);
|
||||
const baseURL = process.env.PLAYWRIGHT_BASE_URL || `http://localhost:${devPort}`;
|
||||
const devHost = process.env.PLAYWRIGHT_DEV_HOST || "127.0.0.1";
|
||||
const baseURL = process.env.PLAYWRIGHT_BASE_URL || `http://${devHost}:${devPort}`;
|
||||
const isCI = !!process.env.CI;
|
||||
const artifactNamespace = (process.env.PLAYWRIGHT_ARTIFACT_NAMESPACE || "").trim();
|
||||
const artifactRoot = artifactNamespace
|
||||
|
||||
+117
-12
@@ -1,17 +1,20 @@
|
||||
import { execFile, spawn } from "node:child_process";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import readline from "node:readline";
|
||||
import { promisify } from "node:util";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
const devPort = Number(process.env.PLAYWRIGHT_DEV_PORT || 5173);
|
||||
const baseURL = `http://localhost:${devPort}`;
|
||||
const devHost = process.env.PLAYWRIGHT_DEV_HOST || "127.0.0.1";
|
||||
const baseURL = process.env.PLAYWRIGHT_BASE_URL || `http://${devHost}:${devPort}`;
|
||||
const runtimeNamespace = String(process.env.PLAYWRIGHT_ARTIFACT_NAMESPACE || `port-${devPort}`)
|
||||
.trim()
|
||||
.replace(/[^a-zA-Z0-9._-]+/g, "-");
|
||||
const pidFile = path.resolve(process.cwd(), "output/playwright", `dev-server-${runtimeNamespace}.json`);
|
||||
const posViewsDirectory = path.resolve(process.cwd(), "src/views/dashboards/departmentDashboard/modules/Pos");
|
||||
const shopComponentsDirectory = path.resolve(process.cwd(), "src/components/shop");
|
||||
const serverOutputLimit = 80;
|
||||
|
||||
async function getListeningProcessOnWindows(port) {
|
||||
const { stdout } = await execFileAsync("netstat", ["-ano", "-p", "tcp"], {
|
||||
@@ -52,8 +55,46 @@ async function getListeningProcessOnUnix(port) {
|
||||
CommandLine: commandMatch ? commandMatch[1] : "",
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
// Fall through to other tools that are commonly available on Linux runners.
|
||||
}
|
||||
|
||||
try {
|
||||
const { stdout } = await execFileAsync("ss", ["-ltnp", `sport = :${port}`], {
|
||||
cwd: process.cwd(),
|
||||
});
|
||||
const line = stdout
|
||||
.split(/\r?\n/)
|
||||
.find((entry) => entry.includes("LISTEN") && entry.includes(`:${port}`) && entry.includes("pid="));
|
||||
const pidMatch = line?.match(/pid=(\d+)/);
|
||||
if (pidMatch) {
|
||||
return {
|
||||
Id: Number(pidMatch[1]),
|
||||
CommandLine: line.match(/users:\(\("([^"]+)"/)?.[1] || "",
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
// Fall through to netstat.
|
||||
}
|
||||
|
||||
try {
|
||||
const { stdout } = await execFileAsync("netstat", ["-ltnp"], {
|
||||
cwd: process.cwd(),
|
||||
});
|
||||
const line = stdout
|
||||
.split(/\r?\n/)
|
||||
.find((entry) => entry.includes("LISTEN") && entry.match(new RegExp(`:${port}\\s`)));
|
||||
const pidMatch = line?.match(/\s(\d+)\/([^\s]+)\s*$/);
|
||||
if (pidMatch) {
|
||||
return {
|
||||
Id: Number(pidMatch[1]),
|
||||
CommandLine: pidMatch[2],
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
// No more fallbacks available.
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async function getListeningProcess(port) {
|
||||
@@ -85,10 +126,61 @@ async function killProcessTree(pid) {
|
||||
}
|
||||
}
|
||||
|
||||
async function waitForServerReady(url, timeoutMs = 120_000) {
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
function captureProcessOutput(stream, lines) {
|
||||
const reader = readline.createInterface({ input: stream });
|
||||
reader.on("line", (line) => {
|
||||
lines.push(line);
|
||||
if (lines.length > serverOutputLimit) {
|
||||
lines.splice(0, lines.length - serverOutputLimit);
|
||||
}
|
||||
});
|
||||
return reader;
|
||||
}
|
||||
|
||||
function formatProcessOutputSection(label, lines) {
|
||||
if (lines.length === 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return `\n${label}:\n${lines.join("\n")}`;
|
||||
}
|
||||
|
||||
function formatServerExitError(url, exitInfo, stdoutLines, stderrLines) {
|
||||
const exitLabel = exitInfo.signal ? `signal ${exitInfo.signal}` : `code ${exitInfo.code ?? 0}`;
|
||||
return new Error(
|
||||
`The local Playwright dev server exited before ${url} became ready (${exitLabel}).${formatProcessOutputSection(
|
||||
"stdout",
|
||||
stdoutLines
|
||||
)}${formatProcessOutputSection("stderr", stderrLines)}`
|
||||
);
|
||||
}
|
||||
|
||||
async function waitForServerReady(url, serverProcess, stdoutLines, stderrLines, timeoutMs = 120_000) {
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
let exitInfo = null;
|
||||
const handleExit = (code, signal) => {
|
||||
exitInfo = {
|
||||
code,
|
||||
signal,
|
||||
};
|
||||
};
|
||||
|
||||
serverProcess.on("exit", handleExit);
|
||||
|
||||
try {
|
||||
while (Date.now() < deadline) {
|
||||
if (exitInfo || serverProcess.exitCode !== null || serverProcess.signalCode !== null) {
|
||||
throw formatServerExitError(
|
||||
url,
|
||||
exitInfo || {
|
||||
code: serverProcess.exitCode,
|
||||
signal: serverProcess.signalCode,
|
||||
},
|
||||
stdoutLines,
|
||||
stderrLines
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(url, { redirect: "manual" });
|
||||
if (response.status < 500) {
|
||||
@@ -101,7 +193,15 @@ async function waitForServerReady(url, timeoutMs = 120_000) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
}
|
||||
|
||||
throw new Error(`Timed out waiting for the local Playwright dev server at ${url}.`);
|
||||
throw new Error(
|
||||
`Timed out waiting for the local Playwright dev server at ${url}.${formatProcessOutputSection(
|
||||
"stdout",
|
||||
stdoutLines
|
||||
)}${formatProcessOutputSection("stderr", stderrLines)}`
|
||||
);
|
||||
} finally {
|
||||
serverProcess.off("exit", handleExit);
|
||||
}
|
||||
}
|
||||
|
||||
async function buildWarmupTargets() {
|
||||
@@ -180,35 +280,40 @@ export default async function globalSetup() {
|
||||
|
||||
const serverProcess =
|
||||
process.platform === "win32"
|
||||
? spawn("cmd.exe", ["/d", "/s", "/c", `npm.cmd run dev -- --host localhost --port ${devPort} --strictPort`], {
|
||||
? spawn("cmd.exe", ["/d", "/s", "/c", `npm.cmd run dev -- --host ${devHost} --port ${devPort} --strictPort`], {
|
||||
cwd: process.cwd(),
|
||||
detached: true,
|
||||
env: {
|
||||
...process.env,
|
||||
PLAYWRIGHT: "1",
|
||||
},
|
||||
stdio: "ignore",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
windowsHide: true,
|
||||
})
|
||||
: spawn("npm", ["run", "dev", "--", "--host", "localhost", "--port", String(devPort), "--strictPort"], {
|
||||
: spawn("npm", ["run", "dev", "--", "--host", devHost, "--port", String(devPort), "--strictPort"], {
|
||||
cwd: process.cwd(),
|
||||
detached: true,
|
||||
env: {
|
||||
...process.env,
|
||||
PLAYWRIGHT: "1",
|
||||
},
|
||||
stdio: "ignore",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
|
||||
serverProcess.unref();
|
||||
const stdoutLines = [];
|
||||
const stderrLines = [];
|
||||
const stdoutReader = captureProcessOutput(serverProcess.stdout, stdoutLines);
|
||||
const stderrReader = captureProcessOutput(serverProcess.stderr, stderrLines);
|
||||
|
||||
await fs.writeFile(pidFile, JSON.stringify({ pid: serverProcess.pid, port: devPort }), "utf8");
|
||||
|
||||
try {
|
||||
await waitForServerReady(baseURL);
|
||||
await waitForServerReady(baseURL, serverProcess, stdoutLines, stderrLines);
|
||||
await warmUpDevServer(baseURL);
|
||||
} catch (error) {
|
||||
await killProcessTree(serverProcess.pid);
|
||||
throw error;
|
||||
} finally {
|
||||
stdoutReader.close();
|
||||
stderrReader.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
|
||||
const baseURL = "http://localhost:4173";
|
||||
const baseURL = "http://127.0.0.1:4173";
|
||||
const isCI = !!process.env.CI;
|
||||
|
||||
process.env.PLAYWRIGHT_BASE_URL = baseURL;
|
||||
@@ -13,10 +13,7 @@ export default defineConfig({
|
||||
forbidOnly: isCI,
|
||||
retries: isCI ? 2 : 0,
|
||||
workers: isCI ? 2 : 3,
|
||||
reporter: [
|
||||
["list"],
|
||||
["html", { open: "never", outputFolder: "output/playwright/prod/report" }],
|
||||
],
|
||||
reporter: [["list"], ["html", { open: "never", outputFolder: "output/playwright/prod/report" }]],
|
||||
outputDir: "output/playwright/prod/test-results",
|
||||
use: {
|
||||
baseURL,
|
||||
|
||||
+11
-10
@@ -2,19 +2,20 @@ export const IS_DEV = import.meta.env.VITE_IS_DEV; // Set to false in production
|
||||
|
||||
// Development mode
|
||||
export const POS_STEP_1_VERSION = 2; //(IS_DEV ? 2 : 1);
|
||||
export const API_URL = (IS_DEV ? 'https://api.truckwash.io:4433' : 'https://api.truckwash.io');
|
||||
export const API_URL = IS_DEV ? "https://api.truckwash.io:4433" : "https://api.truckwash.io";
|
||||
|
||||
// Allowed origins
|
||||
export const ALLOWED_ORIGINS = [
|
||||
'https://truckwash.io',
|
||||
'https://www.truckwash.io',
|
||||
'http://localhost:5173',
|
||||
'http://localhost:4173',
|
||||
'http://localhost:4174',
|
||||
'http://127.0.0.1:4173',
|
||||
'http://127.0.0.1:4174',
|
||||
"https://truckwash.io",
|
||||
"https://www.truckwash.io",
|
||||
"http://localhost:5173",
|
||||
"http://localhost:4173",
|
||||
"http://localhost:4174",
|
||||
"http://127.0.0.1:5173",
|
||||
"http://127.0.0.1:4173",
|
||||
"http://127.0.0.1:4174",
|
||||
];
|
||||
export const MIGRATION_ORIGIN = 'https://truckwash.io';
|
||||
export const MIGRATION_ORIGIN = "https://truckwash.io";
|
||||
|
||||
// Global request queue / retry / progress configuration
|
||||
export const REQUEST_QUEUE_CONFIG = Object.freeze({
|
||||
@@ -43,7 +44,7 @@ export const REQUEST_QUEUE_CONFIG = Object.freeze({
|
||||
jitterMs: 50,
|
||||
}),
|
||||
ping: Object.freeze({
|
||||
endpoint: '/ping',
|
||||
endpoint: "/ping",
|
||||
intervalMs: 30000,
|
||||
timeoutMs: 5000,
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user