From 90c51add82fc8b40801af39da496f86a635f2d3e Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 4 Sep 2026 04:27:10 -0700 Subject: [PATCH] test: repair stable Vitest fixture ownership Capture Memory cleanup handlers independently of mock history, assert the native browser project root, and bind live browser disclosures to their run and tool identifiers. Remove the QA reference to ClawHub documentation intentionally deleted on main. Proof: 126 focused owner tests passed; the four browser-route cases passed with and without inspected synthetic captures. Scoped Codex autoreview reported no actionable P0 findings. The separate bundled fake-timers refresh defect remains unresolved; this does not claim green CI or accepted migration performance. --- extensions/memory-core/src/dreaming.test.ts | 7 +-- test/vitest-projects-config.test.ts | 2 +- ui/src/e2e/browser-route-handoff.e2e.test.ts | 49 +++++++++++++------- 3 files changed, 38 insertions(+), 20 deletions(-) diff --git a/extensions/memory-core/src/dreaming.test.ts b/extensions/memory-core/src/dreaming.test.ts index 32bca180bb0f..040b71032a12 100644 --- a/extensions/memory-core/src/dreaming.test.ts +++ b/extensions/memory-core/src/dreaming.test.ts @@ -49,12 +49,12 @@ const constants = { }; const { createTempWorkspace } = createMemoryCoreTestHarness(); -const registeredGatewayStops = new Set<() => Promise>(); +const registeredGatewayStops = new Set>(); afterEach(async () => { const stops = [...registeredGatewayStops]; registeredGatewayStops.clear(); - await Promise.all(stops.map((stop) => stop())); + await Promise.all(stops.map(async (stop) => await stop({ reason: "test" }, {}))); resetSystemEventsForTest(); }); @@ -387,7 +387,8 @@ async function triggerGatewayStop( function registerShortTermPromotionDreamingForTest(api: DreamingPluginApiTestDouble): void { registerShortTermPromotionDreaming(api as unknown as DreamingPluginApi); - registeredGatewayStops.add(() => triggerGatewayStop(api.on)); + // Cleanup outlives beforeAll mock call history, which Vitest clears before each test. + registeredGatewayStops.add(getGatewayStopHandler(api.on)); } describe("short-term dreaming config", () => { diff --git a/test/vitest-projects-config.test.ts b/test/vitest-projects-config.test.ts index 0e49a3ff7d90..cc5724af4dbb 100644 --- a/test/vitest-projects-config.test.ts +++ b/test/vitest-projects-config.test.ts @@ -421,7 +421,7 @@ describe("projects vitest config", () => { ).toContain(configPath); const { createUiBrowserVitestConfig } = await import("./vitest/vitest.ui-browser.config.ts"); const browser = createUiBrowserVitestConfig(); - expect(normalizeConfigPath(browser.root)).toBe("ui"); + expect(normalizeConfigPath(requireTestConfig(browser).root)).toBe("ui"); expect(requireTestConfig(browser).browser?.enabled).toBe(true); expect(requireTestConfig(browser).runner).toBeUndefined(); }); diff --git a/ui/src/e2e/browser-route-handoff.e2e.test.ts b/ui/src/e2e/browser-route-handoff.e2e.test.ts index c680992b2265..bb77a3126f87 100644 --- a/ui/src/e2e/browser-route-handoff.e2e.test.ts +++ b/ui/src/e2e/browser-route-handoff.e2e.test.ts @@ -4,7 +4,7 @@ import { defaultControlUiFeatureMethods, installMockGateway, } from "../test-helpers/control-ui-e2e.ts"; -import { expectRequestCountStable } from "./chat-flow.test-support.ts"; +import { captureUiProof, expectRequestCountStable } from "./chat-flow.test-support.ts"; import { openChatSidePanelType } from "./chat-side-panel.test-support.ts"; import { createControlUiE2eSuite } from "./control-ui-e2e-suite.test-support.ts"; @@ -115,7 +115,7 @@ suite.define(() => { }, }, }); - const expandTools = async () => { + const expandHistoryTools = async () => { for (const summary of await page.locator(".chat-activity-group__summary").all()) { if ((await summary.getAttribute("aria-expanded")) !== "true") { await summary.click(); @@ -131,7 +131,7 @@ suite.define(() => { await page.getByText("History is ready.", { exact: true }).waitFor(); await expectRequestCountStable(gateway, "browser.request", 0); expect(await page.locator("openclaw-browser-tab-card").count()).toBe(0); - await expandTools(); + await expandHistoryTools(); if (includeHistory) { for (const output of [ "Standalone ordinary output 0", @@ -147,7 +147,7 @@ suite.define(() => { await page.getByRole("button", { name: "Send message" }).click(); const send = await gateway.waitForRequest("chat.send"); const runId = asNullableRecord(send.params)?.idempotencyKey; - expect(typeof runId).toBe("string"); + expect.assert(typeof runId === "string"); await page.getByRole("button", { name: "Stop generating" }).waitFor(); let seq = 0; const emitTool = (data: Record) => @@ -171,20 +171,31 @@ suite.define(() => { }, }); } - // History disclosures can collapse when a new turn starts. Wait for - // the consumed live output, not a count of currently mounted rows. - const expectToolOutput = async (text: string) => { - await expect - .poll(async () => { - await expandTools(); - return page.getByText(text, { exact: true }).isVisible(); - }) - .toBe(true); + const liveTurn = page.locator(`.chat-group[data-chat-row-key*="${runId}"]`); + // Wait for this run's disclosure before acting. A page-wide positional + // scan can change targets as history and live rows mount or collapse. + const expectToolOutput = async (toolCallId: string, text: string) => { + const activity = liveTurn.locator(".chat-activity-group__summary"); + await activity.waitFor(); + if ((await activity.getAttribute("aria-expanded")) !== "true") { + await activity.click(); + } + const tool = liveTurn.locator(`[data-message-id^="tool:assistant:${toolCallId}:"]`); + await tool.locator(".chat-tool-msg-summary").click(); + const output = tool.getByText(text, { exact: true }); + await output.waitFor(); + await output.scrollIntoViewIfNeeded(); }; - await expectToolOutput("Live ordinary output 0"); - await expectToolOutput("Live ordinary output 1"); + await expectToolOutput("live-0", "Live ordinary output 0"); + await expectToolOutput("live-1", "Live ordinary output 1"); await expectRequestCountStable(gateway, "browser.request", 0); expect(await page.locator("openclaw-browser-tab-card").count()).toBe(0); + await captureUiProof( + suite, + page, + "browser-route-handoff", + `history-${includeHistory}-live.png`, + ); await openChatSidePanelType(page, "Browser"); const panel = page.locator("section.bp"); @@ -199,7 +210,7 @@ suite.define(() => { details, }, }); - await expectToolOutput("Live output after opening Browser"); + await expectToolOutput("live-after-open", "Live output after opening Browser"); expect(await page.locator("openclaw-browser-tab-card").count()).toBe(0); expect(await panel.locator('.bp-shot[alt="Configured default"]').isVisible()).toBe(true); const requests = await gateway.getRequests("browser.request"); @@ -212,6 +223,12 @@ suite.define(() => { expect(request.params).not.toHaveProperty("query.profile"); expect(asNullableRecord(request.params)?.path).not.toBe("/tabs/focus"); } + await captureUiProof( + suite, + page, + "browser-route-handoff", + `history-${includeHistory}-panel.png`, + ); // The same live transport must still carry actionable browser results. await emitTool({