fix: decouple trace updates from scroll
This commit is contained in:
+129
-27
@@ -204,6 +204,7 @@ const state = {
|
||||
conversationScrollPosition: { top: 0, left: 0 },
|
||||
traceScrollPositions: new Map(),
|
||||
traceScrollUserActiveUntil: new Map(),
|
||||
traceProgrammaticScrollWrites: 0,
|
||||
fullTraceReplayInFlight: new Set(),
|
||||
resultReconciliationInFlight: new Set(),
|
||||
canceledTraces: new Set(),
|
||||
@@ -1001,6 +1002,7 @@ function initCommandBar() {
|
||||
state.conversationScrollPosition = { top: 0, left: 0 };
|
||||
state.traceScrollPositions.clear();
|
||||
state.traceScrollUserActiveUntil.clear();
|
||||
state.traceProgrammaticScrollWrites = 0;
|
||||
state.fullTraceReplayInFlight.clear();
|
||||
state.chatMessages = [];
|
||||
state.conversationId = null;
|
||||
@@ -1572,7 +1574,7 @@ function updateMessageTrace(messageId, snapshot, options = {}) {
|
||||
const sessionId = snapshot.sessionId ?? runnerTrace.sessionId ?? current.sessionId;
|
||||
const threadId = snapshot.threadId ?? runnerTrace.threadId ?? current.threadId;
|
||||
noteCurrentRequestTraceActivity(snapshot);
|
||||
state.chatMessages[index] = {
|
||||
const nextMessage = {
|
||||
...current,
|
||||
runnerTrace,
|
||||
traceId: snapshot.traceId ?? current.traceId,
|
||||
@@ -1580,6 +1582,7 @@ function updateMessageTrace(messageId, snapshot, options = {}) {
|
||||
threadId,
|
||||
updatedAt: snapshot.updatedAt ?? current.updatedAt
|
||||
};
|
||||
state.chatMessages[index] = nextMessage;
|
||||
if (sessionId) state.sessionId = sessionId;
|
||||
if (threadId) state.threadId = threadId;
|
||||
if (runnerTrace.sessionStatus) state.sessionStatus = runnerTrace.sessionStatus;
|
||||
@@ -1590,14 +1593,14 @@ function updateMessageTrace(messageId, snapshot, options = {}) {
|
||||
state.currentRequest.threadId = threadId;
|
||||
}
|
||||
if (runnerTrace.eventsCompacted === true && runnerTrace.fullTraceLoaded !== true) {
|
||||
maybeReplayFullTraceForMessage(state.chatMessages[index]);
|
||||
maybeReplayFullTraceForMessage(nextMessage);
|
||||
}
|
||||
if (shouldReconcileCodeAgentResult(state.chatMessages[index], runnerTrace)) {
|
||||
if (shouldReconcileCodeAgentResult(nextMessage, runnerTrace)) {
|
||||
reconcileCodeAgentResult(messageId, { quiet: options.quiet === true });
|
||||
}
|
||||
if (options.quiet !== true) {
|
||||
renderCodeAgentSummary();
|
||||
renderConversation();
|
||||
patchMessageTracePanel(nextMessage);
|
||||
renderRecords(state.liveSurface);
|
||||
}
|
||||
}
|
||||
@@ -2436,7 +2439,7 @@ function restoreConversationScrollPosition(options = {}) {
|
||||
if (options.deferred === true && Date.now() < state.conversationScrollUserActiveUntil) return;
|
||||
const position = state.conversationScrollPosition;
|
||||
if (!position) return;
|
||||
el.conversationList.scrollTop = scrollTopForPosition(el.conversationList, position);
|
||||
writeConversationScrollTop(scrollTopForPosition(el.conversationList, position));
|
||||
el.conversationList.scrollLeft = Math.min(position.left, Math.max(0, el.conversationList.scrollWidth - el.conversationList.clientWidth));
|
||||
}
|
||||
|
||||
@@ -2460,7 +2463,7 @@ function restoreTraceScrollPositions(root = el.conversationList, options = {}) {
|
||||
if (options.deferred === true && isTraceScrollUserActive(list.dataset.traceUiKey)) continue;
|
||||
const position = state.traceScrollPositions.get(list.dataset.traceUiKey);
|
||||
if (!position) continue;
|
||||
list.scrollTop = scrollTopForPosition(list, position);
|
||||
writeTraceScrollTop(list, scrollTopForPosition(list, position));
|
||||
list.scrollLeft = Math.min(position.left, Math.max(0, list.scrollWidth - list.clientWidth));
|
||||
}
|
||||
}
|
||||
@@ -2485,6 +2488,65 @@ function isTraceScrollUserActive(traceUiKey) {
|
||||
return Date.now() < Number(state.traceScrollUserActiveUntil.get(traceUiKey) ?? 0);
|
||||
}
|
||||
|
||||
function writeConversationScrollTop(top) {
|
||||
if (el.conversationList.scrollTop === top) return;
|
||||
state.traceProgrammaticScrollWrites += 1;
|
||||
el.conversationList.scrollTop = top;
|
||||
}
|
||||
|
||||
function writeTraceScrollTop(list, top) {
|
||||
if (!list || list.scrollTop === top) return;
|
||||
state.traceProgrammaticScrollWrites += 1;
|
||||
list.scrollTop = top;
|
||||
}
|
||||
|
||||
function patchMessageTracePanel(message) {
|
||||
const traceUiKey = messageTraceUiKey(message);
|
||||
if (!traceUiKey) return false;
|
||||
const panel = el.conversationList.querySelector(`.message-trace[data-trace-ui-key="${cssEscape(traceUiKey)}"]`);
|
||||
if (!panel) return false;
|
||||
const replacement = messageTracePanel(message);
|
||||
if (!replacement) {
|
||||
panel.remove();
|
||||
return true;
|
||||
}
|
||||
patchTracePanelElement(panel, replacement);
|
||||
return true;
|
||||
}
|
||||
|
||||
function patchTracePanelElement(panel, replacement) {
|
||||
panel.open = replacement.open;
|
||||
const currentSummary = panel.querySelector("summary");
|
||||
const nextSummary = replacement.querySelector("summary");
|
||||
if (currentSummary && nextSummary) {
|
||||
currentSummary.className = nextSummary.className;
|
||||
currentSummary.textContent = nextSummary.textContent;
|
||||
}
|
||||
|
||||
const currentToolbar = panel.querySelector(".message-trace-toolbar");
|
||||
const nextToolbar = replacement.querySelector(".message-trace-toolbar");
|
||||
if (currentToolbar && nextToolbar) {
|
||||
currentToolbar.replaceChildren(...nextToolbar.childNodes);
|
||||
} else if (!currentToolbar && nextToolbar) {
|
||||
const list = panel.querySelector(".message-trace-events");
|
||||
panel.insertBefore(nextToolbar, list);
|
||||
} else if (currentToolbar && !nextToolbar) {
|
||||
currentToolbar.remove();
|
||||
}
|
||||
|
||||
const currentList = panel.querySelector(".message-trace-events[data-trace-ui-key]");
|
||||
const nextList = replacement.querySelector(".message-trace-events[data-trace-ui-key]");
|
||||
if (currentList && nextList) {
|
||||
currentList.dataset.traceMode = nextList.dataset.traceMode ?? "";
|
||||
patchTraceEventList(currentList, [...nextList.children]);
|
||||
}
|
||||
}
|
||||
|
||||
function cssEscape(value) {
|
||||
if (window.CSS && typeof window.CSS.escape === "function") return window.CSS.escape(value);
|
||||
return String(value).replace(/["\\]/gu, "\\$&");
|
||||
}
|
||||
|
||||
function renderHardwareStatus(m3Status) {
|
||||
syncHardwareTabs();
|
||||
const status = m3Status ?? sourceFallbackM3Status();
|
||||
@@ -4589,6 +4651,7 @@ function messageTracePanel(message) {
|
||||
const details = document.createElement("details");
|
||||
details.className = "message-trace";
|
||||
const traceUiKey = messageTraceUiKey(message);
|
||||
if (traceUiKey) details.dataset.traceUiKey = traceUiKey;
|
||||
const storedOpen = traceUiKey ? state.traceDetailsOpen.get(traceUiKey) : undefined;
|
||||
details.open = typeof storedOpen === "boolean" ? storedOpen : defaultTraceDetailsOpen(message);
|
||||
if (traceUiKey) {
|
||||
@@ -4658,24 +4721,49 @@ function messageTraceCountText(trace, rawTotal, loadedTotal, readableTotal) {
|
||||
}
|
||||
|
||||
function renderTraceEventList(list, rows) {
|
||||
list.replaceChildren();
|
||||
for (const row of rows) {
|
||||
const item = document.createElement("li");
|
||||
item.className = `message-trace-row tone-border-${toneClass(row.tone)}`;
|
||||
const header = document.createElement("div");
|
||||
header.className = "message-trace-line";
|
||||
header.textContent = row.header;
|
||||
item.append(header);
|
||||
if (row.body) {
|
||||
const body = document.createElement("pre");
|
||||
body.className = "message-trace-body";
|
||||
body.textContent = row.body;
|
||||
item.append(body);
|
||||
patchTraceEventList(list, rows.map(traceEventRowNode));
|
||||
}
|
||||
|
||||
function patchTraceEventList(list, nextItems) {
|
||||
const currentItems = [...list.children];
|
||||
const max = Math.max(currentItems.length, nextItems.length);
|
||||
for (let index = 0; index < max; index += 1) {
|
||||
const current = currentItems[index];
|
||||
const next = nextItems[index];
|
||||
if (!next) {
|
||||
current?.remove();
|
||||
continue;
|
||||
}
|
||||
list.append(item);
|
||||
if (!current) {
|
||||
list.append(next);
|
||||
continue;
|
||||
}
|
||||
if (current.dataset.traceRowKey === next.dataset.traceRowKey) continue;
|
||||
current.replaceWith(next);
|
||||
}
|
||||
}
|
||||
|
||||
function traceEventRowNode(row) {
|
||||
const item = document.createElement("li");
|
||||
item.className = `message-trace-row tone-border-${toneClass(row.tone)}`;
|
||||
item.dataset.traceRowKey = traceRowKey(row);
|
||||
const header = document.createElement("div");
|
||||
header.className = "message-trace-line";
|
||||
header.textContent = row.header;
|
||||
item.append(header);
|
||||
if (row.body) {
|
||||
const body = document.createElement("pre");
|
||||
body.className = "message-trace-body";
|
||||
body.textContent = row.body;
|
||||
item.append(body);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
function traceRowKey(row) {
|
||||
return `${row.header}\n${row.body ?? ""}\n${row.tone ?? ""}`;
|
||||
}
|
||||
|
||||
function installWorkbenchTestHooks() {
|
||||
if (!isLocalWorkbenchTestHost() || !new URLSearchParams(window.location.search).has("hwlab-test-hooks")) return;
|
||||
window.__hwlabWorkbenchTestHooks = {
|
||||
@@ -4685,9 +4773,10 @@ function installWorkbenchTestHooks() {
|
||||
reconcileTraceResultFixture,
|
||||
latestAgentMessageText,
|
||||
traceScrollMetrics,
|
||||
tracePanelText,
|
||||
traceDomIdentity,
|
||||
setTraceScrollTop,
|
||||
setConversationScrollTop
|
||||
setConversationScrollTop,
|
||||
resetProgrammaticScrollWriteCount
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4759,7 +4848,7 @@ function appendTraceEvents(count = 1) {
|
||||
trace.events = [...trace.events, ...additions];
|
||||
trace.eventCount = trace.events.length;
|
||||
trace.lastEvent = trace.events.at(-1);
|
||||
renderConversation();
|
||||
patchMessageTracePanel(message);
|
||||
}
|
||||
|
||||
async function reconcileTraceResultFixture(result) {
|
||||
@@ -4866,15 +4955,28 @@ function traceScrollMetrics() {
|
||||
};
|
||||
}
|
||||
|
||||
function tracePanelText() {
|
||||
function traceDomIdentity() {
|
||||
const conversation = el.conversationList;
|
||||
const panel = conversation.querySelector(".message-trace[data-trace-ui-key]");
|
||||
const list = conversation.querySelector(".message-trace-events[data-trace-ui-key]");
|
||||
const firstRow = list?.querySelector(".message-trace-row") ?? null;
|
||||
if (panel && !panel.__hwlabTraceIdentity) panel.__hwlabTraceIdentity = `panel-${Date.now()}-${Math.random()}`;
|
||||
if (list && !list.__hwlabTraceIdentity) list.__hwlabTraceIdentity = `list-${Date.now()}-${Math.random()}`;
|
||||
if (firstRow && !firstRow.__hwlabTraceIdentity) firstRow.__hwlabTraceIdentity = `row-${Date.now()}-${Math.random()}`;
|
||||
return {
|
||||
count: conversation.querySelector(".message-trace-count")?.textContent ?? "",
|
||||
rows: [...conversation.querySelectorAll(".message-trace-row")].map((row) => row.textContent ?? ""),
|
||||
bodies: [...conversation.querySelectorAll(".message-trace-body")].map((body) => body.textContent ?? "")
|
||||
panel: panel?.__hwlabTraceIdentity ?? null,
|
||||
list: list?.__hwlabTraceIdentity ?? null,
|
||||
firstRow: firstRow?.__hwlabTraceIdentity ?? null,
|
||||
rowCount: list?.querySelectorAll(".message-trace-row").length ?? 0,
|
||||
programmaticScrollWrites: state.traceProgrammaticScrollWrites
|
||||
};
|
||||
}
|
||||
|
||||
function resetProgrammaticScrollWriteCount() {
|
||||
state.traceProgrammaticScrollWrites = 0;
|
||||
return state.traceProgrammaticScrollWrites;
|
||||
}
|
||||
|
||||
function setTraceScrollTop(top, { user = true } = {}) {
|
||||
const list = el.conversationList.querySelector(".message-trace-events[data-trace-ui-key]");
|
||||
if (!list) return traceScrollMetrics();
|
||||
|
||||
@@ -14,7 +14,7 @@ const defaultCredentials = Object.freeze({
|
||||
});
|
||||
|
||||
test("final assistant response renders basic markdown without raw html injection", async () => {
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const browser = await launchBrowser();
|
||||
const server = await startStaticServer(webRoot);
|
||||
try {
|
||||
const page = await browser.newPage({
|
||||
@@ -67,7 +67,7 @@ test("final assistant response renders basic markdown without raw html injection
|
||||
});
|
||||
|
||||
test("trace scroll stays user controlled while trace updates append", async () => {
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const browser = await launchBrowser();
|
||||
const server = await startStaticServer(webRoot);
|
||||
try {
|
||||
const page = await browser.newPage({
|
||||
@@ -83,17 +83,19 @@ test("trace scroll stays user controlled while trace updates append", async () =
|
||||
const initial = await traceMetrics(page);
|
||||
assert.ok(initial.traceScrollHeight > initial.traceClientHeight + 200, JSON.stringify(initial));
|
||||
|
||||
await page.evaluate(() => window.__hwlabWorkbenchTestHooks.setTraceScrollTop(1_000_000, { user: false }));
|
||||
await page.evaluate(() => window.__hwlabWorkbenchTestHooks.appendTraceEvents(8));
|
||||
await page.waitForTimeout(80);
|
||||
const pinned = await traceMetrics(page);
|
||||
assert.ok(pinned.traceBottomGap <= 2, `trace should stay pinned at bottom when already at bottom: ${JSON.stringify(pinned)}`);
|
||||
const beforeIdentity = await page.evaluate(() => window.__hwlabWorkbenchTestHooks.traceDomIdentity());
|
||||
await page.evaluate(() => window.__hwlabWorkbenchTestHooks.resetProgrammaticScrollWriteCount());
|
||||
|
||||
const userMiddle = await page.evaluate(() => window.__hwlabWorkbenchTestHooks.setTraceScrollTop(180, { user: true }));
|
||||
await page.evaluate(() => window.__hwlabWorkbenchTestHooks.appendTraceEvents(12));
|
||||
await page.waitForTimeout(120);
|
||||
const afterAppend = await traceMetrics(page);
|
||||
assert.ok(Math.abs(afterAppend.traceTop - userMiddle.traceTop) <= 8, `trace update fought the user's scroll position: before=${JSON.stringify(userMiddle)} after=${JSON.stringify(afterAppend)}`);
|
||||
const afterIdentity = await page.evaluate(() => window.__hwlabWorkbenchTestHooks.traceDomIdentity());
|
||||
assert.equal(afterIdentity.panel, beforeIdentity.panel, "trace update must not replace the trace panel DOM node");
|
||||
assert.equal(afterIdentity.list, beforeIdentity.list, "trace update must not replace the trace list DOM node");
|
||||
assert.equal(afterIdentity.firstRow, beforeIdentity.firstRow, "trace update must not replace stable existing rows");
|
||||
assert.equal(afterIdentity.programmaticScrollWrites, 0, `trace update wrote scrollTop: ${JSON.stringify(afterIdentity)}`);
|
||||
|
||||
const userMoved = await page.evaluate(() => {
|
||||
const before = window.__hwlabWorkbenchTestHooks.traceScrollMetrics();
|
||||
@@ -103,121 +105,16 @@ test("trace scroll stays user controlled while trace updates append", async () =
|
||||
await page.waitForTimeout(120);
|
||||
const afterUserMove = await traceMetrics(page);
|
||||
assert.ok(afterUserMove.traceTop >= userMoved.traceTop - 8, `trace update should not snap back after user scrolls down: before=${JSON.stringify(userMoved)} after=${JSON.stringify(afterUserMove)}`);
|
||||
const afterUserMoveIdentity = await page.evaluate(() => window.__hwlabWorkbenchTestHooks.traceDomIdentity());
|
||||
assert.equal(afterUserMoveIdentity.programmaticScrollWrites, 0, `trace update wrote scrollTop after user move: ${JSON.stringify(afterUserMoveIdentity)}`);
|
||||
|
||||
const conversationMiddle = await page.evaluate(() => window.__hwlabWorkbenchTestHooks.setConversationScrollTop(40, { user: true }));
|
||||
await page.evaluate(() => window.__hwlabWorkbenchTestHooks.appendTraceEvents(4));
|
||||
await page.waitForTimeout(120);
|
||||
const afterConversationAppend = await traceMetrics(page);
|
||||
assert.ok(Math.abs(afterConversationAppend.conversationTop - conversationMiddle.conversationTop) <= 8, `conversation scroll should not snap on trace update: before=${JSON.stringify(conversationMiddle)} after=${JSON.stringify(afterConversationAppend)}`);
|
||||
|
||||
await page.close();
|
||||
} finally {
|
||||
await browser.close();
|
||||
await server.close();
|
||||
}
|
||||
});
|
||||
|
||||
test("trace display full means complete readable timeline, not compacted result window", async () => {
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const server = await startStaticServer(webRoot);
|
||||
try {
|
||||
const page = await browser.newPage({
|
||||
viewport: { width: 1366, height: 768 },
|
||||
deviceScaleFactor: 1
|
||||
});
|
||||
await page.goto(`${server.url}?hwlab-test-hooks=1`, { waitUntil: "domcontentloaded", timeout: 15000 });
|
||||
await login(page);
|
||||
await page.waitForFunction(() => window.__hwlabWorkbenchTestHooks, null, { timeout: 12000 });
|
||||
|
||||
await page.evaluate(() => {
|
||||
const gatewayPayload = (id, operationId, status = "succeeded") => JSON.stringify({
|
||||
jsonrpc: "2.0",
|
||||
id,
|
||||
result: {
|
||||
accepted: true,
|
||||
status,
|
||||
operationId,
|
||||
gatewaySessionId: "gws_test",
|
||||
resourceId: "res_windows_host",
|
||||
capabilityId: "cap_windows_cmd_exec",
|
||||
dispatch: {
|
||||
shellExecuted: true,
|
||||
dispatchStatus: status,
|
||||
exitCode: status === "succeeded" ? 0 : 1,
|
||||
durationMs: 1200,
|
||||
command: "powershell -EncodedCommand <redacted>",
|
||||
stdout: `stdout for ${operationId}`,
|
||||
stderr: ""
|
||||
}
|
||||
}
|
||||
});
|
||||
window.__hwlabWorkbenchTestHooks.seedTraceMessage({
|
||||
eventsCompacted: true,
|
||||
eventCount: 600,
|
||||
events: [
|
||||
{
|
||||
label: "request:accepted-short-connection",
|
||||
status: "started",
|
||||
message: "accepted"
|
||||
},
|
||||
{
|
||||
label: "item/commandExecution:completed",
|
||||
type: "tool_call",
|
||||
status: "completed",
|
||||
toolName: "commandExecution",
|
||||
command: "node /app/tools/hwlab-gateway-shell.mjs --json --timeout-ms 120000",
|
||||
exitCode: 0,
|
||||
durationMs: 1234,
|
||||
outputBytes: 42,
|
||||
stdoutSummary: "gateway stdout ok",
|
||||
stderrSummary: ""
|
||||
},
|
||||
{
|
||||
label: "item/commandExecution/outputDelta",
|
||||
type: "tool_call",
|
||||
status: "output_chunk",
|
||||
outputSummary: `${gatewayPayload("req_1", "op_one")}${gatewayPayload("req_2", "op_two")}`
|
||||
}
|
||||
],
|
||||
assistantStreams: [
|
||||
{
|
||||
label: "assistant:stream",
|
||||
status: "streaming",
|
||||
chunkCount: 3,
|
||||
text: "first readable sentence middle detail final conclusion",
|
||||
updatedAt: "2026-05-26T12:29:28.000Z",
|
||||
waitingFor: "turn/completed"
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
await page.waitForSelector(".message-trace-count", { timeout: 12000 });
|
||||
const replaying = await tracePanelText(page);
|
||||
assert.match(replaying.count, /完整 trace 回放中/);
|
||||
assert.doesNotMatch(replaying.count, /压缩窗口|显示全部/);
|
||||
assert.ok(replaying.rows.some((row) => row.includes("tool cmd :: node /app/tools/hwlab-gateway-shell.mjs --json") && row.includes("exit=0") && row.includes("s=1.2") && row.includes("out=42B")), replaying.rows.join("\n"));
|
||||
assert.doesNotMatch(replaying.rows.join("\n"), /node \/ app \/ tools/);
|
||||
assert.match(replaying.bodies.join("\n"), /stdout:\s*gateway stdout ok/);
|
||||
assert.equal(replaying.rows.filter((row) => row.includes("tool gateway.shell")).length, 2, replaying.rows.join("\n---\n"));
|
||||
assert.ok(replaying.rows.some((row) => row.includes("op=op_one")), replaying.rows.join("\n"));
|
||||
assert.ok(replaying.rows.some((row) => row.includes("op=op_two")), replaying.rows.join("\n"));
|
||||
assert.match(replaying.bodies.join("\n"), /first readable sentence middle detail final conclusion/);
|
||||
|
||||
await page.evaluate(() => {
|
||||
const message = window.__hwlabWorkbenchTestHooks.tracePanelText();
|
||||
const rows = message.rows.length;
|
||||
window.__hwlabWorkbenchTestHooks.seedTraceMessage({
|
||||
fullTraceLoaded: true,
|
||||
events: Array.from({ length: rows }, (_, index) => ({
|
||||
label: `full:readable:${index + 1}`,
|
||||
status: "completed",
|
||||
message: `full readable event ${index + 1}`
|
||||
}))
|
||||
});
|
||||
});
|
||||
const full = await tracePanelText(page);
|
||||
assert.match(full.count, /显示全部可读事件/);
|
||||
assert.doesNotMatch(full.count, /完整 trace 回放中|压缩窗口/);
|
||||
const afterConversationIdentity = await page.evaluate(() => window.__hwlabWorkbenchTestHooks.traceDomIdentity());
|
||||
assert.equal(afterConversationIdentity.programmaticScrollWrites, 0, `trace update wrote scrollTop after conversation scroll: ${JSON.stringify(afterConversationIdentity)}`);
|
||||
|
||||
await page.close();
|
||||
} finally {
|
||||
@@ -227,7 +124,7 @@ test("trace display full means complete readable timeline, not compacted result
|
||||
});
|
||||
|
||||
test("trace result-ready fixture is reconciled into final assistant result", async () => {
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const browser = await launchBrowser();
|
||||
const server = await startStaticServer(webRoot);
|
||||
try {
|
||||
const page = await browser.newPage({
|
||||
@@ -305,7 +202,7 @@ test("trace result-ready fixture is reconciled into final assistant result", asy
|
||||
|
||||
test("restored pending trace polls result endpoint without token or live provider", async () => {
|
||||
const traceId = "trc_restore_result_ready";
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const browser = await launchBrowser();
|
||||
const server = await startStaticServer(webRoot, {
|
||||
[`/v1/agent/chat/result/${traceId}`]: completedResultFixture(traceId, "刷新恢复后来自 result endpoint 的回复。"),
|
||||
[`/v1/agent/chat/trace/${traceId}`]: {
|
||||
@@ -398,7 +295,7 @@ test("restored client idle timeout is corrected by completed result polling", as
|
||||
}
|
||||
}
|
||||
});
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const browser = await launchBrowser();
|
||||
try {
|
||||
const page = await browser.newPage({
|
||||
viewport: { width: 1366, height: 768 },
|
||||
@@ -464,8 +361,9 @@ async function traceMetrics(page) {
|
||||
return page.evaluate(() => window.__hwlabWorkbenchTestHooks.traceScrollMetrics());
|
||||
}
|
||||
|
||||
async function tracePanelText(page) {
|
||||
return page.evaluate(() => window.__hwlabWorkbenchTestHooks.tracePanelText());
|
||||
async function launchBrowser() {
|
||||
const executablePath = process.env.HWLAB_PLAYWRIGHT_CHROMIUM || process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH || undefined;
|
||||
return chromium.launch({ headless: true, executablePath });
|
||||
}
|
||||
|
||||
async function expectCount(page, selector, expected) {
|
||||
|
||||
Reference in New Issue
Block a user