Files
pikasTech-unidesk/scripts/src/hwlab-node-web-probe-console-verify.test.ts
T
2026-07-18 13:56:20 +02:00

150 lines
7.3 KiB
TypeScript

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { test } from "bun:test";
import { rootPath } from "./config";
import { hwlabRuntimeLaneSpecForNode } from "./hwlab-node-lanes";
import { parseWebProbeConsoleVerificationProfiles } from "./hwlab-node-web-probe-console-profile";
import { nodeWebProbeConsoleVerifyScript } from "./hwlab-node-web-probe-console-verify";
import { webProbeScriptCommandGovernance } from "./hwlab-node/web-observe-scripts";
import { parseNodeWebProbeOptions } from "./hwlab-node/web-probe";
function minimalProfile(): Record<string, unknown> {
return {
navigationTimeoutMs: 30000,
settleMs: 100,
commandTimeoutSeconds: 120,
minTextLength: 8,
maxHorizontalOverflowPx: 2,
outputLimits: {
failures: 20,
pageErrors: 20,
consoleErrors: 20,
requestFailures: 20,
responseErrors: 20,
screenshots: 1,
textPreviewChars: 120,
},
viewports: [{ id: "desktop", width: 1920, height: 1080 }],
routes: [{ id: "home", path: "/home", readySelector: "#app", screenshot: true }],
workflows: {},
};
}
test("Cloud Console verification profile is owned by lane YAML", () => {
const spec = hwlabRuntimeLaneSpecForNode("v03", "NC01");
const profileName = spec.webProbe?.defaultConsoleVerificationProfile;
const profile = profileName === undefined ? undefined : spec.webProbe?.consoleVerificationProfiles?.[profileName];
assert.equal(profileName, "cloud-console");
assert.ok(profile);
assert.deepEqual(profile.viewports.map((item) => [item.width, item.height]), [[1920, 1080], [960, 600], [390, 844]]);
assert.equal(profile.routes.length, 20);
assert.equal(profile.routes.filter((item) => item.screenshot).length * profile.viewports.length + (profile.workflows.agentObserver?.views.length ?? 0), 24);
assert.deepEqual(profile.workflows.hwpod?.requiredCommandPrefixes, ["Get-FileHash .\\", "python .\\"]);
const agentrunTree = spec.webProbe?.consoleVerificationProfiles?.["agentrun-tree"];
assert.ok(agentrunTree);
assert.deepEqual(agentrunTree.routes, [{
id: "agent-runs-tree",
path: "/agents/runs?view=tree",
readySelector: ".agent-mind-map",
screenshot: false,
}]);
assert.deepEqual(agentrunTree.viewports.map((item) => [item.width, item.height]), [[1920, 1080]]);
assert.deepEqual(agentrunTree.workflows.agentObserver?.selection, {
selector: ".agent-mind-map-node[data-kind='run']",
inspectorSelector: ".agent-observer-inspector",
screenshotName: "agentrun-tree-inspector.png",
});
assert.equal(agentrunTree.outputLimits.screenshots, 2);
const tasktree = spec.webProbe?.consoleVerificationProfiles?.tasktree;
assert.ok(tasktree);
assert.deepEqual(tasktree.viewports.map((item) => [item.width, item.height]), [[1920, 1080], [960, 600], [390, 844]]);
assert.equal(tasktree.routes[0]?.readySelector, "body");
assert.equal(tasktree.workflows.tasktree?.reportSelector, ".tasktree-reports article");
assert.equal(tasktree.workflows.tasktree?.scaleSelector, ".tasktree-zoom output");
assert.equal(tasktree.workflows.tasktree?.secondsScaleLabel, "秒");
assert.equal(tasktree.workflows.tasktree?.detailSelector, ".tasktree-detail-panel");
assert.equal(tasktree.workflows.tasktree?.dockedSelector, ".tasktree-docked-detail");
assert.equal(tasktree.outputLimits.screenshots, 5);
});
test("Cloud Console profile parser rejects unknown fields and duplicate route ids", () => {
assert.throws(() => parseWebProbeConsoleVerificationProfiles({
fixture: { ...minimalProfile(), hiddenFallback: true },
}, "fixture.consoleVerificationProfiles"), /unsupported fields: hiddenFallback/u);
const duplicate = minimalProfile();
duplicate.routes = [
{ id: "same", path: "/one", readySelector: "#app", screenshot: false },
{ id: "same", path: "/two", readySelector: "#app", screenshot: false },
];
assert.throws(() => parseWebProbeConsoleVerificationProfiles({ fixture: duplicate }, "fixture.consoleVerificationProfiles"), /duplicate same/u);
});
test("generated Cloud Console runner is valid JavaScript and contains no node or lane authority", () => {
const profile = parseWebProbeConsoleVerificationProfiles({ fixture: minimalProfile() }, "fixture.consoleVerificationProfiles").fixture;
assert.ok(profile);
const script = nodeWebProbeConsoleVerifyScript("fixture", profile, "config/example.yaml#profiles.fixture");
assert.doesNotThrow(() => new Function(script.replace("export default ", "")));
const generatorSource = readFileSync(rootPath("scripts/src/hwlab-node-web-probe-console-verify.ts"), "utf8");
assert.doesNotMatch(generatorSource, /NC01|JD01|D601|\/dashboard|lab-dev\.hwpod\.com/u);
assert.match(script, /config\/example\.yaml#profiles\.fixture/u);
assert.match(script, /requestedRoute/u);
assert.match(script, /noInspectorChromeOverlap/u);
assert.match(script, /visibleEdgeCount/u);
assert.match(script, /getComputedStyle\(edge\)/u);
assert.match(script, /style\.stroke !== "none"/u);
assert.match(script, /page\.keyboard\.press\("Enter"\)/u);
assert.match(script, /verifyTasktree/u);
assert.match(script, /ganttInternalOverflowPx/u);
assert.match(script, /page\.on\("pageerror"/u);
assert.match(script, /page\.on\("requestfailed"/u);
assert.match(script, /pageErrors/u);
assert.match(script, /requestFailures/u);
assert.match(script, /detailMode/u);
assert.match(script, /return \{\s+ok,\s+status:[\s\S]*?pageErrors,\s+consoleErrors,\s+requestFailures,\s+responseErrors,[\s\S]*?profileName: config\.profileName/u);
});
test("long console verification uses the existing remote artifact job", () => {
const observeScripts = readFileSync(rootPath("scripts/src/hwlab-node/web-observe-scripts.ts"), "utf8");
const remoteArtifact = readFileSync(rootPath("scripts/src/web-probe-remote-artifact.ts"), "utf8");
assert.match(observeScripts, /options\.commandTimeoutSeconds > 55/u);
assert.match(observeScripts, /runWebProbeRemoteArtifactJob/u);
assert.match(observeScripts, /remote-artifact-download/u);
assert.match(remoteArtifact, /--allow-text-transfer/u);
assert.match(remoteArtifact, /\*\.webp\|\*\.json/u);
});
test("console-verify parses into the existing managed script action", () => {
const options = parseNodeWebProbeOptions([
"console-verify",
"--node", "NC01",
"--lane", "v03",
"--origin", "internal",
"--profile", "cloud-console",
]);
assert.equal(options.action, "script");
if (options.action !== "script") return;
assert.equal(options.originName, "internal");
assert.equal(options.viewport, "1920x1080");
assert.equal(options.commandTimeoutSeconds, 900);
assert.equal(options.suppressAdHocWarning, true);
assert.equal(options.scriptSource.kind, "generated");
assert.equal(options.scriptSource.path, "builtin:console-verify/cloud-console");
assert.match(options.commandLabel ?? "", /web-probe console-verify/u);
const governance = webProbeScriptCommandGovernance(options);
assert.deepEqual(governance.warnings, []);
assert.equal(governance.commandPromotionHint.repoOwnedTypedCommand, true);
assert.equal(governance.commandPromotionHint.action, "reuse-repo-owned-typed-command");
});
test("console-verify rejects profiles not declared by owning YAML", () => {
assert.throws(() => parseNodeWebProbeOptions([
"console-verify",
"--node", "NC01",
"--lane", "v03",
"--origin", "internal",
"--profile", "not-configured",
]), /unknown --profile not-configured/u);
});