Merge remote-tracking branch 'origin/run-tests-and-fix-issues' into run-tests-and-fix-issues
# Conflicts: # playwright.global-setup.mjs
This commit is contained in:
+24
-167
@@ -1,20 +1,17 @@
|
||||
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 devHost = process.env.PLAYWRIGHT_DEV_HOST || "127.0.0.1";
|
||||
const baseURL = process.env.PLAYWRIGHT_BASE_URL || `http://${devHost}:${devPort}`;
|
||||
const baseURL = `http://localhost:${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"], {
|
||||
@@ -38,59 +35,6 @@ async function getListeningProcessOnWindows(port) {
|
||||
};
|
||||
}
|
||||
|
||||
async function readUnixProcessDetails(pid, fallbackCommandLine = "") {
|
||||
const [rawCommandLine, currentWorkingDirectory] = await Promise.all([
|
||||
fs.readFile(`/proc/${pid}/cmdline`).catch(() => Buffer.alloc(0)),
|
||||
fs.readlink(`/proc/${pid}/cwd`).catch(() => ""),
|
||||
]);
|
||||
const commandLine = rawCommandLine.toString("utf8").split("\0").filter(Boolean).join(" ").trim();
|
||||
const commandContext = [commandLine || fallbackCommandLine, currentWorkingDirectory].filter(Boolean).join(" | ");
|
||||
|
||||
return {
|
||||
Id: pid,
|
||||
CommandLine: commandContext || fallbackCommandLine,
|
||||
};
|
||||
}
|
||||
|
||||
function parseSsListeningProcess(stdout, port) {
|
||||
const line = stdout
|
||||
.split(/\r?\n/)
|
||||
.find((entry) => entry.includes("LISTEN") && entry.includes(`:${port}`) && entry.includes("pid="));
|
||||
if (!line) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const pidMatch = line.match(/pid=(\d+)/);
|
||||
if (!pidMatch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const commandMatch = line.match(/users:\(\("([^"]+)"/);
|
||||
return {
|
||||
pid: Number(pidMatch[1]),
|
||||
commandLine: commandMatch ? commandMatch[1] : "",
|
||||
};
|
||||
}
|
||||
|
||||
function parseNetstatListeningProcess(stdout, port) {
|
||||
const line = stdout
|
||||
.split(/\r?\n/)
|
||||
.find((entry) => entry.includes("LISTEN") && entry.match(new RegExp(`:${port}\\s`)));
|
||||
if (!line) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const pidMatch = line.match(/\s(\d+)\/([^\s]+)\s*$/);
|
||||
if (!pidMatch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
pid: Number(pidMatch[1]),
|
||||
commandLine: pidMatch[2],
|
||||
};
|
||||
}
|
||||
|
||||
async function getListeningProcessOnUnix(port) {
|
||||
try {
|
||||
const { stdout } = await execFileAsync("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-Fp", "-Fc"], {
|
||||
@@ -103,36 +47,13 @@ async function getListeningProcessOnUnix(port) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return readUnixProcessDetails(Number(pidMatch[1]), commandMatch ? commandMatch[1] : "");
|
||||
return {
|
||||
Id: Number(pidMatch[1]),
|
||||
CommandLine: commandMatch ? commandMatch[1] : "",
|
||||
};
|
||||
} catch {
|
||||
// Fall through to other tools that are commonly available on Linux runners.
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const { stdout } = await execFileAsync("ss", ["-ltnp", `sport = :${port}`], {
|
||||
cwd: process.cwd(),
|
||||
});
|
||||
const processMatch = parseSsListeningProcess(stdout, port);
|
||||
if (processMatch) {
|
||||
return readUnixProcessDetails(processMatch.pid, processMatch.commandLine);
|
||||
}
|
||||
} catch {
|
||||
// Fall through to netstat.
|
||||
}
|
||||
|
||||
try {
|
||||
const { stdout } = await execFileAsync("netstat", ["-ltnp"], {
|
||||
cwd: process.cwd(),
|
||||
});
|
||||
const processMatch = parseNetstatListeningProcess(stdout, port);
|
||||
if (processMatch) {
|
||||
return readUnixProcessDetails(processMatch.pid, processMatch.commandLine);
|
||||
}
|
||||
} catch {
|
||||
// No more fallbacks available.
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async function getListeningProcess(port) {
|
||||
@@ -164,82 +85,23 @@ async function killProcessTree(pid) {
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
async function waitForServerReady(url, 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
|
||||
);
|
||||
while (Date.now() < deadline) {
|
||||
try {
|
||||
const response = await fetch(url, { redirect: "manual" });
|
||||
if (response.status < 500) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(url, { redirect: "manual" });
|
||||
if (response.status < 500) {
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// keep polling until ready
|
||||
}
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
} catch {
|
||||
// keep polling until ready
|
||||
}
|
||||
|
||||
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);
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
}
|
||||
|
||||
throw new Error(`Timed out waiting for the local Playwright dev server at ${url}.`);
|
||||
}
|
||||
|
||||
async function buildWarmupTargets() {
|
||||
@@ -318,40 +180,35 @@ export default async function globalSetup() {
|
||||
|
||||
const serverProcess =
|
||||
process.platform === "win32"
|
||||
? spawn("cmd.exe", ["/d", "/s", "/c", `npm.cmd run dev -- --host ${devHost} --port ${devPort} --strictPort`], {
|
||||
? spawn("cmd.exe", ["/d", "/s", "/c", `npm.cmd run dev -- --host localhost --port ${devPort} --strictPort`], {
|
||||
cwd: process.cwd(),
|
||||
detached: true,
|
||||
env: {
|
||||
...process.env,
|
||||
PLAYWRIGHT: "1",
|
||||
},
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
stdio: "ignore",
|
||||
windowsHide: true,
|
||||
})
|
||||
: spawn("npm", ["run", "dev", "--", "--host", devHost, "--port", String(devPort), "--strictPort"], {
|
||||
: spawn("npm", ["run", "dev", "--", "--host", "localhost", "--port", String(devPort), "--strictPort"], {
|
||||
cwd: process.cwd(),
|
||||
detached: true,
|
||||
env: {
|
||||
...process.env,
|
||||
PLAYWRIGHT: "1",
|
||||
},
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
stdio: "ignore",
|
||||
});
|
||||
const stdoutLines = [];
|
||||
const stderrLines = [];
|
||||
const stdoutReader = captureProcessOutput(serverProcess.stdout, stdoutLines);
|
||||
const stderrReader = captureProcessOutput(serverProcess.stderr, stderrLines);
|
||||
|
||||
serverProcess.unref();
|
||||
|
||||
await fs.writeFile(pidFile, JSON.stringify({ pid: serverProcess.pid, port: devPort }), "utf8");
|
||||
|
||||
try {
|
||||
await waitForServerReady(baseURL, serverProcess, stdoutLines, stderrLines);
|
||||
await waitForServerReady(baseURL);
|
||||
await warmUpDevServer(baseURL);
|
||||
} catch (error) {
|
||||
await killProcessTree(serverProcess.pid);
|
||||
throw error;
|
||||
} finally {
|
||||
stdoutReader.close();
|
||||
stderrReader.close();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user