test: harden Windows cursor diagnostic

This commit is contained in:
EtienneLescot
2026-05-05 21:57:03 +02:00
parent f76fb423be
commit 9b85cacec7
2 changed files with 62 additions and 22 deletions
@@ -18,7 +18,7 @@ export interface WindowsCursorSampleEvent {
width: number; width: number;
height: number; height: number;
} | null; } | null;
asset?: WindowsCursorAssetPayload; asset: WindowsCursorAssetPayload | null;
} }
export interface WindowsCursorReadyEvent { export interface WindowsCursorReadyEvent {
+61 -21
View File
@@ -3,10 +3,25 @@ import fs from "node:fs";
import os from "node:os"; import os from "node:os";
import path from "node:path"; import path from "node:path";
const SAMPLE_INTERVAL_MS = Number(process.env.CURSOR_TEST_SAMPLE_INTERVAL_MS ?? 25); function readPositiveIntEnv(name, fallback) {
const DURATION_MS = Number(process.env.CURSOR_TEST_DURATION_MS ?? 1800); const raw = process.env[name];
const SCREEN_FRAME_INTERVAL_MS = Number(process.env.CURSOR_TEST_SCREEN_FRAME_INTERVAL_MS ?? 100); if (raw === undefined) {
const READY_TIMEOUT_MS = 5000; return fallback;
}
const parsed = Number(raw);
if (!Number.isFinite(parsed) || parsed <= 0) {
console.warn(`[cursor-native-test] ignoring invalid ${name}=${raw}; using ${fallback}`);
return fallback;
}
return Math.floor(parsed);
}
const SAMPLE_INTERVAL_MS = readPositiveIntEnv("CURSOR_TEST_SAMPLE_INTERVAL_MS", 25);
const DURATION_MS = readPositiveIntEnv("CURSOR_TEST_DURATION_MS", 1800);
const SCREEN_FRAME_INTERVAL_MS = readPositiveIntEnv("CURSOR_TEST_SCREEN_FRAME_INTERVAL_MS", 100);
const READY_TIMEOUT_MS = readPositiveIntEnv("CURSOR_TEST_READY_TIMEOUT_MS", 5000);
const OUTPUT_DIR = const OUTPUT_DIR =
process.env.CURSOR_TEST_OUTPUT_DIR ?? process.env.CURSOR_TEST_OUTPUT_DIR ??
path.join(os.tmpdir(), `openscreen-cursor-native-${Date.now()}`); path.join(os.tmpdir(), `openscreen-cursor-native-${Date.now()}`);
@@ -550,22 +565,32 @@ while ($stopwatch.ElapsedMilliseconds -le ${durationMs + 700}) {
`; `;
} }
function waitForReady(events) { function createReadyWaiter() {
return new Promise((resolve, reject) => { let settled = false;
const startedAt = Date.now(); let resolveReady = null;
const timer = setInterval(() => { const promise = new Promise((resolve, reject) => {
if (events.some((event) => event.type === "ready")) { const timer = setTimeout(() => {
clearInterval(timer); if (settled) {
resolve();
return; return;
} }
settled = true;
reject(new Error("Timed out waiting for cursor sampler readiness."));
}, READY_TIMEOUT_MS);
if (Date.now() - startedAt > READY_TIMEOUT_MS) { resolveReady = () => {
clearInterval(timer); if (settled) {
reject(new Error("Timed out waiting for cursor sampler readiness.")); return;
} }
}, 25); settled = true;
clearTimeout(timer);
resolve();
};
}); });
return {
promise,
resolve: () => resolveReady?.(),
};
} }
function writeAssets(assets, outputDir) { function writeAssets(assets, outputDir) {
@@ -1113,10 +1138,15 @@ function findPlaywrightChromiumExecutable(defaultPath) {
const candidates = fs const candidates = fs
.readdirSync(baseDir, { withFileTypes: true }) .readdirSync(baseDir, { withFileTypes: true })
.filter((entry) => entry.isDirectory() && entry.name.startsWith("chromium-")) .filter((entry) => entry.isDirectory() && entry.name.startsWith("chromium-"))
.map((entry) => path.join(baseDir, entry.name, "chrome-win64", "chrome.exe")) .map((entry) => ({
.filter((candidate) => fs.existsSync(candidate)) executablePath: path.join(baseDir, entry.name, "chrome-win64", "chrome.exe"),
.sort() revision: Number.parseInt(entry.name.slice("chromium-".length), 10),
.reverse(); }))
.filter(
(candidate) => Number.isFinite(candidate.revision) && fs.existsSync(candidate.executablePath),
)
.sort((a, b) => b.revision - a.revision)
.map((candidate) => candidate.executablePath);
return candidates[0] ?? defaultPath; return candidates[0] ?? defaultPath;
} }
@@ -1169,6 +1199,7 @@ const events = [];
const assets = new Map(); const assets = new Map();
let lineBuffer = ""; let lineBuffer = "";
let stoppingSampler = false; let stoppingSampler = false;
const readyWaiter = createReadyWaiter();
const sampler = spawnPowerShell(buildSamplerScript(), { const sampler = spawnPowerShell(buildSamplerScript(), {
onStdout: (chunk) => { onStdout: (chunk) => {
lineBuffer += chunk; lineBuffer += chunk;
@@ -1181,8 +1212,17 @@ const sampler = spawnPowerShell(buildSamplerScript(), {
continue; continue;
} }
const event = JSON.parse(trimmed); let event;
try {
event = JSON.parse(trimmed);
} catch {
process.stderr.write(`[cursor-native-test] dropping non-JSON line: ${trimmed}\n`);
continue;
}
events.push(event); events.push(event);
if (event.type === "ready") {
readyWaiter.resolve();
}
if (event.asset?.id && !assets.has(event.asset.id)) { if (event.asset?.id && !assets.has(event.asset.id)) {
assets.set(event.asset.id, event.asset); assets.set(event.asset.id, event.asset);
} }
@@ -1197,7 +1237,7 @@ const sampler = spawnPowerShell(buildSamplerScript(), {
let screenRecorder = null; let screenRecorder = null;
try { try {
await waitForReady(events); await readyWaiter.promise;
screenRecorder = spawnPowerShell(buildScreenRecorderScript(OUTPUT_DIR, DURATION_MS), { screenRecorder = spawnPowerShell(buildScreenRecorderScript(OUTPUT_DIR, DURATION_MS), {
onStderr: (chunk) => { onStderr: (chunk) => {
if (!chunk.startsWith("#< CLIXML") && !chunk.startsWith("<Objs")) { if (!chunk.startsWith("#< CLIXML") && !chunk.startsWith("<Objs")) {