150 lines
7.3 KiB
TypeScript
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);
|
|
});
|