test(ui): isolate full chat-pane lifecycle tests to fix core-runtime-media-ui flake (#112566)

The core-runtime-media-ui shard runs its ui config non-isolated for speed, but the
full chat-pane lifecycle tests instantiate the pane component, which relies on
chat-thread/chat-message module-level singletons (thread-state map, confirmation
dismisser WeakMap, module-scoped document context-menu listeners) and spies on those
modules. Under the shared non-isolated graph a stateful predecessor file can leave
those modules duplicated, so the pane binds to a different instance than the test's
spy/registry -- producing order-dependent flakes: `removeEventListener`-not-called
teardown assertions or 120s session-lifecycle hangs. Reproduced deterministically on
a Linux Node 24 Testbox (MAX_WORKERS=1); isolating a single file only shifted the
failure to a sibling pane test, so the whole full-pane family shares the fragility.

Route the 7 full-pane lifecycle test files through a new isolated jsdom lane
(vitest.ui-isolated.config.ts, isolate: true) for a fresh module graph; the other
~370 ui tests stay fast and non-isolated. Registered in both shard registries
(ci-node-test-plan.mjs, vitest.test-shards.mjs) and excluded from vitest.ui.config.ts.

Verified on Testbox: the deterministic single-worker media-ui shard goes from failing
to 5182 passed / 0 failed. Test-infrastructure only; no product code changes.
This commit is contained in:
Peter Steinberger
2026-07-22 00:14:33 -07:00
committed by GitHub
parent c24a215fe4
commit f2a3371656
6 changed files with 48 additions and 1 deletions

View File

@@ -1129,6 +1129,7 @@ const SPLIT_NODE_SHARDS = new Map([
"test/vitest/vitest.media-understanding.config.ts",
"test/vitest/vitest.tui.config.ts",
"test/vitest/vitest.ui.config.ts",
"test/vitest/vitest.ui-isolated.config.ts",
"test/vitest/vitest.wizard.config.ts",
],
requiresDist: false,

View File

@@ -664,6 +664,7 @@ describe("scripts/lib/ci-node-test-plan.mjs", () => {
"test/vitest/vitest.media-understanding.config.ts",
"test/vitest/vitest.tui.config.ts",
"test/vitest/vitest.ui.config.ts",
"test/vitest/vitest.ui-isolated.config.ts",
"test/vitest/vitest.wizard.config.ts",
],
requiresDist: false,

View File

@@ -4731,6 +4731,7 @@ describe("scripts/test-projects full-suite sharding", () => {
"test/vitest/vitest.tui.config.ts",
"test/vitest/vitest.tui-pty.config.ts",
"test/vitest/vitest.ui.config.ts",
"test/vitest/vitest.ui-isolated.config.ts",
"test/vitest/vitest.utils.config.ts",
"test/vitest/vitest.wizard.config.ts",
"test/vitest/vitest.gateway-core.config.ts",

View File

@@ -84,6 +84,7 @@ export const fullSuiteVitestShards = [
"test/vitest/vitest.tui.config.ts",
"test/vitest/vitest.tui-pty.config.ts",
"test/vitest/vitest.ui.config.ts",
"test/vitest/vitest.ui-isolated.config.ts",
"test/vitest/vitest.utils.config.ts",
"test/vitest/vitest.wizard.config.ts",
],

View File

@@ -0,0 +1,23 @@
// Vitest ui-isolated config runs jsdom ui tests that need a fresh module graph.
// The shared ui shard runs non-isolated for speed, but tests that spy on module
// internals and assert the component uses that spy must not share a module cache
// with stateful predecessor files (see UI_ISOLATED_TEST_FILES).
import { createScopedVitestConfig } from "./vitest.scoped-config.ts";
import { jsdomOptimizedDeps } from "./vitest.shared.config.ts";
import { UI_ISOLATED_TEST_FILES } from "./vitest.ui.config.ts";
export function createUiIsolatedVitestConfig(env?: Record<string, string | undefined>) {
return createScopedVitestConfig(UI_ISOLATED_TEST_FILES, {
deps: jsdomOptimizedDeps,
environment: "jsdom",
env,
excludeUnitFastTests: false,
includeOpenClawRuntimeSetup: false,
isolate: true,
name: "ui-isolated",
setupFiles: ["ui/src/test-helpers/lit-warnings.setup.ts"],
useNonIsolatedRunner: false,
});
}
export default createUiIsolatedVitestConfig();

View File

@@ -2,12 +2,32 @@
import { createScopedVitestConfig } from "./vitest.scoped-config.ts";
import { jsdomOptimizedDeps } from "./vitest.shared.config.ts";
// Full chat-pane lifecycle tests instantiate the pane component, which relies on
// chat-thread/chat-message module-level singletons (thread state maps, module-scoped
// document context-menu listeners) and spies on those modules. Under the non-isolated
// ui runner a stateful predecessor file can leave those modules duplicated across the
// shared graph, so the pane binds to a different instance than the test's spy/registry
// — surfacing as flaky teardown assertions or 120s session-lifecycle hangs, depending
// on file order. These tests run in the isolated ui lane for a fresh module graph;
// keep this list in sync with vitest.ui-isolated.config.ts's include.
export const UI_ISOLATED_TEST_FILES = [
"ui/src/pages/chat/chat-pane-history.test.ts",
"ui/src/pages/chat/chat-pane-lifecycle.test.ts",
"ui/src/pages/chat/chat-pane-pull-requests.test.ts",
"ui/src/pages/chat/chat-pane.message-cut.test.ts",
"ui/src/pages/chat/chat-pane.read-marker.test.ts",
"ui/src/pages/chat/chat-pane.session-discussion.test.ts",
"ui/src/pages/chat/chat-pane.test.ts",
];
export function createUiVitestConfig(
env?: Record<string, string | undefined>,
options?: { includePatterns?: string[]; name?: string },
) {
const includePatterns = options?.includePatterns ?? ["ui/src/**/*.test.ts"];
const exclude = options?.includePatterns ? [] : ["ui/src/**/*.e2e.test.ts"];
const exclude = options?.includePatterns
? []
: ["ui/src/**/*.e2e.test.ts", ...UI_ISOLATED_TEST_FILES];
return createScopedVitestConfig(includePatterns, {
deps: jsdomOptimizedDeps,
environment: "jsdom",