import assert from "node:assert/strict"; import { test } from "bun:test"; import { nodeWebObserveRunnerSource } from "./hwlab-node-web-observe-runner-source"; import { nodeWebObserveRunnerTraceReadabilitySource } from "./hwlab-node-web-observe-runner-trace-readability-source"; test("Workbench 产品 Trace 可读性源码解析独立 YAML 时序并识别终态", () => { const source = nodeWebObserveRunnerTraceReadabilitySource(); const build = new Function(`${source}\nreturn { parseWorkbenchTraceReadabilityConfig, workbenchTraceReadabilityTerminalStatus, workbenchTraceReadabilityFailures, workbenchTraceReadabilityRetention };`) as () => { parseWorkbenchTraceReadabilityConfig: (raw: string) => Record; workbenchTraceReadabilityTerminalStatus: (value: string) => boolean; workbenchTraceReadabilityFailures: (value: Record) => Array>; workbenchTraceReadabilityRetention: (running: Record, terminal: Record) => Record; }; const helpers = build(); assert.deepEqual(helpers.parseWorkbenchTraceReadabilityConfig(JSON.stringify({ enabled: true, runningCardTimeoutMs: 15_000, disclosureActionTimeoutMs: 15_000, disclosureOpenTimeoutMs: 10_000, runningReadableTimeoutMs: 480_000, terminalTimeoutMs: 480_000, domStableQuietMs: 2_000, domStableTimeoutMs: 6_000, pollIntervalMs: 100, })), { enabled: true, runningCardTimeoutMs: 15_000, disclosureActionTimeoutMs: 15_000, disclosureOpenTimeoutMs: 10_000, runningReadableTimeoutMs: 480_000, terminalTimeoutMs: 480_000, domStableQuietMs: 2_000, domStableTimeoutMs: 6_000, pollIntervalMs: 100, valuesRedacted: true, }); assert.equal(helpers.workbenchTraceReadabilityTerminalStatus("completed"), true); assert.equal(helpers.workbenchTraceReadabilityTerminalStatus("running"), false); assert.deepEqual(helpers.workbenchTraceReadabilityFailures({ scope: { conversationCount: 1, debugPanelCountInsideConversation: 0 }, startedDuringRunning: true, disclosure: { openBefore: true, autoReadable: true }, running: { observed: true, snapshot: { rowCount: 2, sourceAuthorityRowCount: 2, projectedAuthorityRowCount: 0 } }, terminalArrival: { observed: true }, terminal: { stable: true, snapshot: { disclosure: { open: true }, rowCount: 3, sourceAuthorityRowCount: 3, readableSourceRowCount: 3, projectedAuthorityRowCount: 0, }, }, runningRetention: { passed: true }, retainedRunningRowCount: 2, }), []); assert.deepEqual( helpers.workbenchTraceReadabilityFailures({ scope: { conversationCount: 1, debugPanelCountInsideConversation: 0 }, startedDuringRunning: true, disclosure: { openBefore: false, autoReadable: false }, running: { observed: true, snapshot: { rowCount: 2, sourceAuthorityRowCount: 2, projectedAuthorityRowCount: 0 } }, terminalArrival: { observed: true }, terminal: { stable: true, snapshot: { disclosure: { open: true }, rowCount: 2, sourceAuthorityRowCount: 2, readableSourceRowCount: 2, projectedAuthorityRowCount: 0 } }, runningRetention: { passed: true }, retainedRunningRowCount: 1, }).map((failure) => failure.code), ["trace_not_auto_expanded"], ); assert.deepEqual(helpers.workbenchTraceReadabilityRetention({ rowIdentities: ["source:1:tool"], disclosure: { eventCount: 121 }, sourceSeqMin: 1, sourceSeqMax: 120, }, { rowIdentities: ["source:121:terminal"], disclosure: { eventCount: 122, rowWindowed: true }, sourceSeqMin: 2, sourceSeqMax: 121, }), { mode: "window-progress", passed: true, retainedRowCount: 0, retainedConcreteRowCount: 0, runningEventCount: 121, terminalEventCount: 122, runningSourceSeqMin: 1, runningSourceSeqMax: 120, terminalSourceSeqMin: 2, terminalSourceSeqMax: 121, valuesRedacted: true, }); assert.deepEqual(helpers.workbenchTraceReadabilityRetention({ rowIdentities: ["source:18:trace-noise-summary"], disclosure: { eventCount: 25, rowWindowed: false }, sourceSeqMin: 18, sourceSeqMax: 18, }, { rowIdentities: ["source:21:event:evt_fixture", "source:42:terminal:evt_terminal"], disclosure: { eventCount: 50, rowWindowed: false }, sourceSeqMin: 21, sourceSeqMax: 42, }), { mode: "summary-progress", passed: true, retainedRowCount: 0, retainedConcreteRowCount: 0, runningEventCount: 25, terminalEventCount: 50, runningSourceSeqMin: 18, runningSourceSeqMax: 18, terminalSourceSeqMin: 21, terminalSourceSeqMax: 42, valuesRedacted: true, }); assert.deepEqual(helpers.workbenchTraceReadabilityRetention({ rowIdentities: ["source:18:event:evt_running"], disclosure: { eventCount: 25, rowWindowed: false }, sourceSeqMin: 18, sourceSeqMax: 18, }, { rowIdentities: ["source:21:event:evt_terminal"], disclosure: { eventCount: 50, rowWindowed: false }, sourceSeqMin: 21, sourceSeqMax: 21, }), { mode: "identity-overlap", passed: false, retainedRowCount: 0, retainedConcreteRowCount: 0, valuesRedacted: true, }); }); test("运行卡片命中后使用 traceId 稳定定位,不随状态变更漂移", async () => { const source = nodeWebObserveRunnerTraceReadabilitySource(); const runningTimeline = { status: "running", getAttribute(name: string) { return name === "data-status" ? this.status : null; }, }; const cardElement = (traceId: string, timeline: typeof runningTimeline) => ({ getAttribute(name: string) { return name === "data-trace-id" ? traceId : null; }, querySelector() { return timeline; }, }); const selectedTraceId = "trc_current"; const stableCard = { async count() { return 1; }, locator() { return { count: async () => 1 }; }, async getAttribute(name: string) { return name === "data-trace-id" ? selectedTraceId : null; }, }; const cards = { async count() { return 2; }, async evaluateAll(callback: (elements: unknown[]) => unknown) { return callback([ cardElement("trc_old", { ...runningTimeline, status: "completed" }), cardElement(selectedTraceId, runningTimeline), ]); }, }; const conversation = { locator(selector: string) { if (selector === ':scope > .message-card[data-role="agent"][data-trace-id]') return cards; if (selector === '[data-testid="workbench-kafka-debug-panel"]') return { count: async () => 0 }; if (selector.includes(`[data-trace-id="${selectedTraceId}"]`)) return { first: () => stableCard }; throw new Error(`unexpected selector: ${selector}`); }, }; const conversationRoot = { first: () => conversation, count: async () => 1, }; const page = { locator(selector: string) { if (selector === "#conversation-list") return conversationRoot; throw new Error(`unexpected page selector: ${selector}`); }, }; const build = new Function("page", "sleep", `${source}\nreturn { workbenchTraceReadabilityWaitForCurrentProductCard };`) as ( pageValue: typeof page, sleepValue: () => Promise, ) => { workbenchTraceReadabilityWaitForCurrentProductCard: (timeoutMs: number, pollIntervalMs: number) => Promise>; }; const helpers = build(page, async () => {}); const target = await helpers.workbenchTraceReadabilityWaitForCurrentProductCard(1_000, 1); runningTimeline.status = "completed"; assert.equal(target.traceId, selectedTraceId); assert.equal(target.card, stableCard); assert.equal(await target.card.getAttribute("data-trace-id"), selectedTraceId); }); test("生成的 observer runner 包含产品 Trace 命令并通过 Node 语法检查", async () => { const source = nodeWebObserveRunnerSource(); assert.match(source, /validateWorkbenchTraceReadability/u); assert.match(source, /#conversation-list/u); assert.match(source, /debugPanelCountInsideConversation/u); assert.match(source, /trace_not_auto_expanded/u); const child = Bun.spawn(["node", "--input-type=module", "--check", "-"], { stdin: "pipe", stdout: "pipe", stderr: "pipe" }); child.stdin.write(source); child.stdin.end(); const [stderr, exitCode] = await Promise.all([new Response(child.stderr).text(), child.exited]); assert.equal(exitCode, 0, stderr); });