scripts/verify-local-vm-resume.ts
Rama main · réplica de lectura del repositorio original.
// Real ComputerPanel in an isolated fake-engine workspace; only the desktop
// transport is simulated. No host Docker or user profile is accessed.
import assert from "node:assert/strict";
import { mkdirSync, mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, dirname, resolve } from "node:path";
import { launchVerificationServer, runControlOmb } from "./control-omb.ts";
import { mountPreview } from "./testing/preview-fixture.ts";
import { agentBrowser, ensureUiBrowser, sessionEnv } from "./testing/control-omb-ui.ts";
const fixture = await launchVerificationServer();
const home = mkdtempSync(join(tmpdir(), "omb-vmr-")); // Keep macOS browser sockets below their path limit.
let preview: Awaited<ReturnType<typeof mountPreview>> | undefined;
let browser: { binary: string; env: NodeJS.ProcessEnv } | undefined;
try {
console.log(JSON.stringify(fixture.info));
await runControlOmb(["new-bot", "--name", "Resume test", "--url", fixture.info.url]);
preview = await mountPreview(fixture, {
entry: "/scripts/testing/cloud-preview.tsx", route: "/__vm-resume.html", title: "Local VM resume verification", logLevel: "silent",
});
const { binary, chrome } = await ensureUiBrowser(process.env);
const env = sessionEnv({ home, session: `vm-resume-${process.pid}`, chrome });
browser = { binary, env };
const command = (...args: string[]) => agentBrowser(binary, env, args);
const evaluate = async <T = unknown>(js: string) => (await command("eval", js)).result as T;
const wait = (condition: string) => { console.log("Waiting:", condition); return command("wait", "--fn", condition); };
const evidenceDir = process.env.OMB_UI_EVIDENCE_DIR
? resolve(process.env.OMB_UI_EVIDENCE_DIR) : dirname(fixture.info.logPath);
mkdirSync(evidenceDir, { recursive: true });
const record = process.env.OMB_UI_RECORD === "1";
await command("set", "viewport", "900", "720");
await command("open", preview.previewUrl);
await wait(`document.querySelector('select[aria-label="Conversation surface"]') !== null`);
await wait(`Array.from(document.querySelectorAll('img')).some(img => img.src === window.cloudPreviewFixture.screenshot && img.complete)`);
await evaluate(`(() => { const s = document.querySelector('select[aria-label="Conversation surface"]'); s.value='vm-stopped'; s.dispatchEvent(new Event('change', {bubbles:true})); })()`);
await wait(`document.body.textContent.includes('The Local VM is stopped')`);
assert.equal(await evaluate(`document.body.textContent.includes('Stopped after inactivity')`), true);
assert.equal(await evaluate(`document.body.textContent.includes("isn't available for this bot")`), false);
await command("screenshot", "aside", join(evidenceDir, "stopped.png"));
if (record) {
// Hide only fixture controls in the video, leaving product UI untouched.
await evaluate(`document.querySelector('.fixed.left-2.top-2').style.visibility = 'hidden'`);
await command("record", "start", join(evidenceDir, "resume.mp4"));
}
await command("find", "role", "button", "click", "--name", "Start Local VM", "--exact");
await wait(`window.cloudPreviewFixture.vmStarts === 1`);
await wait(`Array.from(document.querySelectorAll('button')).some(b => b.textContent.includes('Starting Local VM') && b.disabled && b.getAttribute('aria-busy') === 'true')`);
// The POST has returned but Cua is still warming up: the button must keep
// spinning rather than become clickable or claim the desktop is ready.
await wait(`window.cloudPreviewFixture.vmReadinessReads >= 2`);
assert.equal(await evaluate(`Array.from(document.querySelectorAll('button')).some(b => b.textContent.includes('Starting Local VM') && b.disabled)`), true);
await command("screenshot", "aside", join(evidenceDir, "starting.png"));
await evaluate(`Array.from(document.querySelectorAll("button")).find(b => b.textContent === "Finish VM startup").click()`);
await wait(`Array.from(document.querySelectorAll('img')).some(img => img.src === window.cloudPreviewFixture.vmScreenshot && img.complete && img.naturalWidth > 1)`);
assert.equal(await evaluate(`window.cloudPreviewFixture.vmStarts`), 1);
assert.equal(await evaluate(`window.cloudPreviewFixture.paths.some(p => p.endsWith('/local-computer/remove') || p.endsWith('/local-computer/run'))`), false);
await command("screenshot", "aside", join(evidenceDir, "ready.png"));
if (record) {
await command("record", "stop");
await evaluate(`document.querySelector('.fixed.left-2.top-2').style.visibility = ''`);
}
await evaluate(`window.cloudPreviewFixture.resetVm(); const s = document.querySelector('select[aria-label="Panel"]'); s.value='settings'; s.dispatchEvent(new Event('change', {bubbles:true}));`);
await wait(`document.body.textContent.includes('Stopped after inactivity')`);
assert.equal(await evaluate(`document.body.textContent.includes('Delete and recreate')`), false);
await command("find", "role", "button", "click", "--name", "Start Local VM", "--exact");
await wait(`document.body.textContent.includes('Waiting for the desktop')`);
await command("find", "role", "button", "click", "--name", "Finish VM startup", "--exact");
await wait(`Array.from(document.querySelectorAll('[aria-live]')).some(el => el.textContent.trim() === 'Ready')`);
console.log("PASS: stopped explanation → one start → pending through warmup → live preview; Settings also resumes without replacement");
} catch (error) {
if (browser) console.error(JSON.stringify(await agentBrowser(browser.binary, browser.env, ["snapshot"]).catch(() => ({}))));
throw error;
} finally {
if (browser) await agentBrowser(browser.binary, browser.env, ["close"]).catch(() => {});
await preview?.close();
await fixture.close();
rmSync(home, { recursive: true, force: true });
}