* fix(ci): skip unrelated required checks cleanly Matrix jobs had to start no-op runners to preserve interpolated required-check names. Static named jobs let GitHub report genuine skipped conclusions while keeping those names stable. * fix(ci): gate changes to install patching script * fix(ci): route cross-package test contracts narrowly * fix(ci): keep routed contracts merge-blocking * fix(server): exclude nested test utilities from builds * fix(ci): cover app-owned native and browser domains * refactor(ci): align tests with runtime domains Own browser specs, desktop specs, and Electron-only app code by stable directories so PR routing does not depend on filename conventions. Keep desktop integration coverage inside the required desktop check. * fix(ci): keep routing and desktop suites isolated The routing regression must run before dependency installation, and desktop Vitest must not collect Playwright-owned specs. * fix(ci): isolate desktop e2e runtime * perf(test): isolate Playwright workers * perf(test): parallelize Playwright within specs * fix(test): keep Playwright file scheduling * fix(test): share dynamic Metro fixture * fix(test): preserve own-host restart fixtures * fix(ci): preserve routed contracts after rebase * Stabilize Playwright wheel-scroll assertions
98 lines
2.8 KiB
TypeScript
98 lines
2.8 KiB
TypeScript
import { fork, type ChildProcess } from "node:child_process";
|
|
import { once } from "node:events";
|
|
import path from "node:path";
|
|
|
|
export interface OutdatedDaemon {
|
|
endpoint: string;
|
|
label: string;
|
|
serverId: string;
|
|
close(): Promise<void>;
|
|
}
|
|
|
|
interface OutdatedDaemonReadyMessage {
|
|
type: "ready";
|
|
endpoint: string;
|
|
serverId: string;
|
|
}
|
|
|
|
interface OutdatedDaemonErrorMessage {
|
|
type: "error";
|
|
error: string;
|
|
}
|
|
|
|
type OutdatedDaemonMessage = OutdatedDaemonReadyMessage | OutdatedDaemonErrorMessage;
|
|
|
|
export async function startOutdatedDaemon(options?: {
|
|
desktopManaged?: boolean;
|
|
daemonStatusRpcCapability?: boolean;
|
|
relayConfigCapability?: boolean;
|
|
}): Promise<OutdatedDaemon> {
|
|
const metroPort = process.env.E2E_METRO_PORT;
|
|
if (!metroPort) {
|
|
throw new Error("E2E_METRO_PORT is not set - globalSetup must run first");
|
|
}
|
|
|
|
const child = fork(
|
|
path.resolve(__dirname, "../../../../server/src/server/test-utils/outdated-daemon-process.ts"),
|
|
{
|
|
env: {
|
|
...process.env,
|
|
E2E_METRO_PORT: metroPort,
|
|
E2E_DESKTOP_MANAGED: options?.desktopManaged === true ? "1" : "0",
|
|
E2E_DAEMON_STATUS_RPC_CAPABILITY: options?.daemonStatusRpcCapability === false ? "0" : "1",
|
|
E2E_RELAY_CONFIG_CAPABILITY: options?.relayConfigCapability === false ? "0" : "1",
|
|
},
|
|
execArgv: ["--import", "tsx"],
|
|
stdio: ["ignore", "pipe", "pipe", "ipc"],
|
|
},
|
|
);
|
|
const stderr: string[] = [];
|
|
child.stderr?.on("data", (data: Buffer) => stderr.push(data.toString("utf8")));
|
|
|
|
try {
|
|
const ready = await waitForDaemon(child, stderr);
|
|
return {
|
|
endpoint: ready.endpoint,
|
|
label: options?.desktopManaged === true ? "outdated Desktop host" : "outdated host",
|
|
serverId: ready.serverId,
|
|
async close() {
|
|
if (child.exitCode !== null || child.signalCode !== null) return;
|
|
child.kill("SIGTERM");
|
|
await once(child, "exit");
|
|
},
|
|
};
|
|
} catch (error) {
|
|
child.kill("SIGTERM");
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async function waitForDaemon(
|
|
child: ChildProcess,
|
|
stderr: string[],
|
|
): Promise<OutdatedDaemonReadyMessage> {
|
|
return new Promise((resolve, reject) => {
|
|
const timeout = setTimeout(() => {
|
|
reject(new Error(`Timed out starting outdated daemon. ${stderr.join("")}`));
|
|
}, 20_000);
|
|
|
|
child.once("exit", (code, signal) => {
|
|
clearTimeout(timeout);
|
|
reject(
|
|
new Error(
|
|
`Outdated daemon exited before startup (code ${String(code)}, signal ${String(signal)}). ${stderr.join("")}`,
|
|
),
|
|
);
|
|
});
|
|
child.once("message", (message: OutdatedDaemonMessage) => {
|
|
if (message.type === "error") {
|
|
clearTimeout(timeout);
|
|
reject(new Error(message.error));
|
|
return;
|
|
}
|
|
clearTimeout(timeout);
|
|
resolve(message);
|
|
});
|
|
});
|
|
}
|