diff --git a/.agents/skills/unidesk-webdev/references/web-probe.md b/.agents/skills/unidesk-webdev/references/web-probe.md index ebd98210..32ab1c51 100644 --- a/.agents/skills/unidesk-webdev/references/web-probe.md +++ b/.agents/skills/unidesk-webdev/references/web-probe.md @@ -12,6 +12,7 @@ - 默认 profile 由同一 YAML 的 `defaultConsoleVerificationProfile` 选择,CLI `--profile` 只做显式 profile 选择; - 该命令用于已部署页面人工 closeout,不作为 CI/CD gate,不替代 Workbench Kafka、Trace 和性能专项 validator; - 成功或失败均以有界 `issueEvidence`、report SHA、screenshot SHA 和失败摘要收口,不再重复创建 `.state/probes/*.mjs`。 + - AgentRun 思维导图的聚焦验收使用 `--profile agentrun-tree`,直接恢复 `/agents/runs?view=tree`、等待 `.agent-mind-map` 并采集 `1920x1080` 截图。 - WebProbe 远程命令错误可见性: - 默认文本输出必须在独立 `COMMAND_OUTPUT` 段完整展示已经脱敏并限长的 `stderrTail` 与 `stdoutTail`; - 摘要表可以继续提供单行预览,但不得用表格宽度裁剪替代完整的有界错误证据; diff --git a/config/hwlab-node-lanes.yaml b/config/hwlab-node-lanes.yaml index f95651f5..2b043c5a 100644 --- a/config/hwlab-node-lanes.yaml +++ b/config/hwlab-node-lanes.yaml @@ -848,6 +848,28 @@ templates: - buttonName: 表格 queryValue: table screenshotName: agentrun-table.png + agentrun-tree: + navigationTimeoutMs: 30000 + settleMs: 1200 + commandTimeoutSeconds: 120 + minTextLength: 8 + maxHorizontalOverflowPx: 2 + outputLimits: + failures: 20 + consoleErrors: 20 + responseErrors: 20 + screenshots: 1 + textPreviewChars: 160 + viewports: + - id: desktop + width: 1920 + height: 1080 + routes: + - id: agent-runs-tree + path: /agents/runs?view=tree + readySelector: .agent-mind-map + screenshot: true + workflows: {} realtimeFanoutProfiles: pure-kafka-live: subscriberCount: 2 diff --git a/scripts/src/hwlab-node-web-probe-console-verify.test.ts b/scripts/src/hwlab-node-web-probe-console-verify.test.ts index 5d24464f..fd420bc1 100644 --- a/scripts/src/hwlab-node-web-probe-console-verify.test.ts +++ b/scripts/src/hwlab-node-web-probe-console-verify.test.ts @@ -39,6 +39,15 @@ test("Cloud Console verification profile is owned by lane YAML", () => { 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: true, + }]); + assert.deepEqual(agentrunTree.viewports.map((item) => [item.width, item.height]), [[1920, 1080]]); }); test("Cloud Console profile parser rejects unknown fields and duplicate route ids", () => { diff --git a/scripts/src/hwlab-node/web-observe-scripts.test.ts b/scripts/src/hwlab-node/web-observe-scripts.test.ts index e20b8416..86abed3b 100644 --- a/scripts/src/hwlab-node/web-observe-scripts.test.ts +++ b/scripts/src/hwlab-node/web-observe-scripts.test.ts @@ -4,7 +4,41 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { test } from "bun:test"; -import { nodeWebObserveStatusNodeScript, nodeWebObserveWaitCommandShell } from "./web-observe-scripts"; +import { hwlabRuntimeLaneSpecForNode } from "../hwlab-node-lanes"; +import { nodeWebObserveStatusNodeScript, nodeWebObserveWaitCommandShell, nodeWebProbeScriptRemoteShell } from "./web-observe-scripts"; + +test("web-probe script exports generated runner paths into the guarded child shell", () => { + const script = nodeWebProbeScriptRemoteShell({ + action: "script", + node: "NC01", + lane: "v03", + url: "https://lab-dev.hwpod.com", + timeoutMs: 30_000, + viewport: "1920x1080", + browserProxyMode: "auto", + commandTimeoutSeconds: 60, + scriptText: "export default async () => ({ ok: true });", + scriptSource: { kind: "generated", path: null, byteCount: 1, sha256: "fixture" }, + originName: "public", + originMode: "public", + originConfigPath: "fixture", + browserProxyModeSource: "yaml-origin", + }, hwlabRuntimeLaneSpecForNode("v03", "NC01"), { + username: "fixture-user", + password: "fixture-password", + }, { + loginPath: "/auth/login", + usernameField: "username", + passwordField: "password", + cookieNames: ["hwlab_session"], + }, { + envAssignments: [], + summary: {}, + }, undefined); + + assert.match(script, /rm -f "[$]user_script_b64" "[$]runner_b64"\nexport run_dir user_script runner\n/u); + assert.match(script, /UNIDESK_WEB_PROBE_RUN_DIR="[$]run_dir"[\s\S]+node "[$]runner"/u); +}); async function runStatusScript(stateDir: string, commandId: string): Promise> { const child = Bun.spawn(["bash", "-lc", nodeWebObserveStatusNodeScript(1, "NC01", "v03", commandId)], { diff --git a/scripts/src/hwlab-node/web-observe-scripts.ts b/scripts/src/hwlab-node/web-observe-scripts.ts index 7b09b703..6abbb0f1 100644 --- a/scripts/src/hwlab-node/web-observe-scripts.ts +++ b/scripts/src/hwlab-node/web-observe-scripts.ts @@ -826,6 +826,7 @@ export function nodeWebProbeScriptRemoteShell( "node -e \"const fs=require('fs'); fs.writeFileSync(process.argv[1], Buffer.from(fs.readFileSync(process.argv[2], 'utf8').replace(/\\s+/g, ''), 'base64'))\" \"$user_script\" \"$user_script_b64\"", "node -e \"const fs=require('fs'); fs.writeFileSync(process.argv[1], Buffer.from(fs.readFileSync(process.argv[2], 'utf8').replace(/\\s+/g, ''), 'base64'))\" \"$runner\" \"$runner_b64\"", "rm -f \"$user_script_b64\" \"$runner_b64\"", + "export run_dir user_script runner", webProbeGuardedLaunchShell(spec, "manual-start", [ ...webProbeProxy.envAssignments, ...webProbeBrowserEvidenceEnvAssignments(spec),