fix: 固化 Workbench Trace 可读性探针
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
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<string, unknown>;
|
||||
workbenchTraceReadabilityTerminalStatus: (value: string) => boolean;
|
||||
workbenchTraceReadabilityFailures: (value: Record<string, unknown>) => Array<Record<string, unknown>>;
|
||||
workbenchTraceReadabilityRetention: (running: Record<string, unknown>, terminal: Record<string, unknown>) => Record<string, unknown>;
|
||||
};
|
||||
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,
|
||||
runningEventCount: 121,
|
||||
terminalEventCount: 122,
|
||||
runningSourceSeqMin: 1,
|
||||
runningSourceSeqMax: 120,
|
||||
terminalSourceSeqMin: 2,
|
||||
terminalSourceSeqMax: 121,
|
||||
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<void>,
|
||||
) => {
|
||||
workbenchTraceReadabilityWaitForCurrentProductCard: (timeoutMs: number, pollIntervalMs: number) => Promise<Record<string, any>>;
|
||||
};
|
||||
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);
|
||||
});
|
||||
Reference in New Issue
Block a user