fix(test): discover UI unit tests with browser in their names (#163493)

* fix(test): discover UI unit tests with browser in their names

Use the native full-basename selector while preserving the dependency-free planning matcher. Compare registered root and package inventories so browser-import unit tests keep their Node owner.

* fix(test): align project inventory with native discovery
This commit is contained in:
Peter Steinberger 2026-10-02 08:20:53 -05:00 • committed by GitHub
parent f1d9422fbd
commit aa9d51a24f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 203 additions and 48 deletions

View file

@ -20,9 +20,26 @@ const ctx = await createVitest({
try {
const emptyDiscoveryAllowed = Boolean(ctx.config.passWithNoTests);
const specifications = await ctx.globTestSpecifications();
process.env.OPENCLAW_VITEST_POST_SHARD_INCLUDE_FILE = includeFile;
const paths = (files) =>
files.map((file) => path.relative(process.cwd(), file.moduleId).replaceAll("\\", "/")).sort();
const packageNodeFiles = paths(
specifications.filter((file) => ["unit", "unit-node"].includes(file.project.name)),
);
const rootCtx = await createVitest({
config: path.resolve("test/vitest/vitest.ui.config.ts"),
watch: false,
reporters: [],
configLoader: "runner",
api: false,
cache: false,
});
let rootNodeFiles;
try {
rootNodeFiles = paths(await rootCtx.globTestSpecifications());
} finally {
await rootCtx.close();
}
process.env.OPENCLAW_VITEST_POST_SHARD_INCLUDE_FILE = includeFile;
const rows = [];
for (const index of [undefined, 1, 2, 3]) {
ctx.config.shard = index ? { index, count: 3 } : undefined;
@ -215,6 +232,8 @@ try {
output,
JSON.stringify({
discovered: paths(specifications),
packageNodeFiles,
rootNodeFiles,
rows,
scheduling,
empty: { modules: empty.testModules.length, errors: empty.unhandledErrors.length },

View file

@ -2,6 +2,7 @@
import { globSync } from "node:fs";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { globSync as nativeGlobSync } from "tinyglobby";
import { fullSuiteVitestShards } from "./vitest/vitest.test-shards.mjs";
type VitestTestConfig = {
@ -81,9 +82,12 @@ async function listConfigTestFiles(config: VitestConfig, includeFile?: string):
const exclude = (testConfig.exclude ?? []).map((pattern) =>
path.isAbsolute(pattern) ? toRepoPath(path.relative(dir, pattern)) : toRepoPath(pattern),
);
return globSync(testConfig.include ?? [], { cwd: dir, exclude }).map((file) =>
toRepoPath(path.relative(process.cwd(), path.resolve(dir, file))),
);
return nativeGlobSync(testConfig.include ?? [], {
cwd: dir,
ignore: exclude,
dot: true,
expandDirectories: false,
}).map((file) => toRepoPath(path.relative(process.cwd(), path.resolve(dir, file))));
}
export async function listVitestConfigTestFiles(

View file

@ -3,8 +3,10 @@ import os from "node:os";
import path from "node:path";
import { minimatch } from "minimatch";
import { BUNDLED_PLUGIN_TEST_GLOB } from "openclaw/plugin-sdk/test-fixtures";
import { describe, expect, it } from "vitest";
import { globSync } from "tinyglobby";
import { afterEach, describe, expect, it } from "vitest";
import { resolveConfig } from "vitest/node";
import { useAutoCleanupTempDirTracker } from "./helpers/temp-dir.js";
import { normalizeConfigPath } from "./helpers/vitest-config-paths.js";
import { createAgentsCoreIsolatedVitestConfig } from "./vitest/vitest.agents-core-isolated.config.ts";
import { createAgentsCoreVitestConfig } from "./vitest/vitest.agents-core.config.ts";
@ -228,6 +230,7 @@ describe("createScopedVitestConfig", () => {
});
describe("scoped vitest configs", () => {
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
const defaultCliProcessConfig = createCliProcessVitestConfig({});
const defaultCliConfig = createCliVitestConfig({});
const defaultExtensionTelegramConfig = createExtensionTelegramVitestConfig({});
@ -511,17 +514,28 @@ describe("scoped vitest configs", () => {
it("normalizes ui include patterns relative to the scoped dir", () => {
const testConfig = requireTestConfig(defaultUiConfig);
expect(testConfig.dir).toBe(process.cwd());
for (const [file, included] of [
const files = [
["ui/src/pages/chat/chat-view.test.ts", true],
["ui/src/components/form-controls.browser.test.ts", true],
["ui/src/components/markdown-mermaid.runtime.browser.test.ts", false],
["extensions/workboard/browser/catalog.test.ts", true],
["extensions/workboard/browser/native.browser.test.ts", false],
] as const) {
expect(
testConfig.include?.some((pattern) => minimatch(file, pattern)),
file,
).toBe(included);
] as const;
const tempDir = tempDirs.make("openclaw-ui-scoped-");
for (const [file] of files) {
const target = path.join(tempDir, file);
fs.mkdirSync(path.dirname(target), { recursive: true });
fs.writeFileSync(target, "");
}
const discovered = new Set(
globSync(testConfig.include ?? [], {
cwd: tempDir,
dot: true,
expandDirectories: false,
}),
);
for (const [file, included] of files) {
expect(discovered.has(file), file).toBe(included);
}
expect(testConfig.exclude).toContain("ui/src/**/*.e2e.test.ts");
expect(testConfig.exclude).toContain("extensions/*/browser/**/*.e2e.test.ts");

View file

@ -1,7 +1,8 @@
// Vitest UI package config tests validate UI package test project settings.
import { globSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { globSync as nativeGlobSync } from "tinyglobby";
import { afterEach, describe, expect, it, vi } from "vitest";
import type { BrowserProviderOption } from "vitest/node";
import { resolveCiTestRuntimeSelections } from "../scripts/lib/ci-test-runtime.mts";
@ -15,7 +16,7 @@ import { runVitestShutdownCommand } from "./helpers/vitest-shutdown-command.js";
import { loadVitestPerformanceConfig } from "./vitest/vitest.performance-config.ts";
import { DEFAULT_VITEST_TEST_TIMEOUT_MS } from "./vitest/vitest.timeouts.ts";
import { createUiIsolatedVitestConfig } from "./vitest/vitest.ui-isolated.config.ts";
import { uiTimingTestFiles } from "./vitest/vitest.ui-paths.mjs";
import { uiNodeDrivenBrowserTestFiles, uiTimingTestFiles } from "./vitest/vitest.ui-paths.mjs";
import { createUiTimingVitestConfig } from "./vitest/vitest.ui-timing.config.ts";
import { createUiVitestConfig } from "./vitest/vitest.ui.config.ts";
@ -34,6 +35,19 @@ type ExpectedTestConfig = ReturnType<typeof loadVitestPerformanceConfig> & {
sequence?: { groupOrder?: number };
};
function globTestFiles(
patterns: string | string[],
options: { cwd?: string; exclude?: string[] } = {},
): string[] {
// Use the same discovery engine and options as Vitest, including for scoped owners.
return nativeGlobSync(patterns, {
cwd: options.cwd,
ignore: options.exclude,
dot: true,
expandDirectories: false,
});
}
function requireTestConfig(config: unknown): ExpectedTestConfig {
if (!config || typeof config !== "object" || !("test" in config) || !config.test) {
throw new Error("expected ui package vitest test config");
@ -117,6 +131,8 @@ describe("ui package vitest config", () => {
expect(result.stdout).toContain("[ui-runtime] file shuffle seed: 37");
const report = JSON.parse(readFileSync(output, "utf8")) as {
discovered: string[];
packageNodeFiles: string[];
rootNodeFiles: string[];
rows: Array<{
original: string[];
selected: Record<string, Array<{ runtime: string; files: string[] }>>;
@ -150,6 +166,12 @@ describe("ui package vitest config", () => {
};
};
expect(report.discovered.length).toBeGreaterThan(1000);
// Package isolated/timing/Chromium projects retain their separate owners.
expect(report.rootNodeFiles).toEqual(report.packageNodeFiles);
expect(report.rootNodeFiles).toContain(
"ui/src/pages/chat/talk/shared.browser-import.test.ts",
);
expect(report.rootNodeFiles).toEqual(expect.arrayContaining(uiNodeDrivenBrowserTestFiles));
expect(report.rows).toHaveLength(4);
expect(report.empty).toEqual({ modules: 0, errors: 0 });
expect(report.emptyDiscoveryAllowed).toBe(false);
@ -242,18 +264,18 @@ describe("ui package vitest config", () => {
const rootIsolated = requireTestConfig(createUiIsolatedVitestConfig({}));
expect(packageIsolated?.isolate).toBe(true);
expect(rootIsolated.isolate).toBe(true);
const packageFiles = globSync(packageIsolated?.include ?? [], {
const packageFiles = globTestFiles(packageIsolated?.include ?? [], {
cwd: path.join(process.cwd(), "ui"),
exclude: packageIsolated?.exclude,
}).map((file) => path.posix.normalize(`ui/${file.replaceAll("\\", "/")}`));
expect(packageFiles.length).toBeGreaterThan(0);
const rootFiles = globSync(rootIsolated.include ?? [], { exclude: rootIsolated.exclude }).map(
(file) => file.replaceAll("\\", "/"),
);
const rootFiles = globTestFiles(rootIsolated.include ?? [], {
exclude: rootIsolated.exclude,
}).map((file) => file.replaceAll("\\", "/"));
expect(rootFiles.toSorted()).toEqual(packageFiles.toSorted());
const rootShared = requireTestConfig(createUiVitestConfig({}));
expect(
globSync(rootShared.include ?? [], { exclude: rootShared.exclude }).filter((file) =>
globTestFiles(rootShared.include ?? [], { exclude: rootShared.exclude }).filter((file) =>
packageFiles.includes(file.replaceAll("\\", "/")),
),
).toEqual([]);
@ -341,18 +363,18 @@ describe("ui package vitest config", () => {
expect(timing?.sequence?.groupOrder).toBeGreaterThan(project.sequence?.groupOrder ?? 0);
}
const selected = projects.flatMap((project) =>
globSync(project.include ?? [], {
globTestFiles(project.include ?? [], {
cwd: path.join(process.cwd(), "ui"),
exclude: project.exclude,
}).map((file) => `ui/${file.replaceAll("\\", "/")}`),
);
const rootTiming = requireTestConfig(createUiTimingVitestConfig({}));
const rootShared = requireTestConfig(createUiVitestConfig({}));
const rootFiles = globSync(rootShared.include ?? [], { exclude: rootShared.exclude });
const rootFiles = globTestFiles(rootShared.include ?? [], { exclude: rootShared.exclude });
for (const project of [rootShared, requireTestConfig(createUiIsolatedVitestConfig({}))]) {
expect(rootTiming.sequence?.groupOrder).toBeGreaterThan(project.sequence?.groupOrder ?? 0);
}
expect(globSync(rootTiming.include ?? [], { exclude: rootTiming.exclude })).toEqual(
expect(globTestFiles(rootTiming.include ?? [], { exclude: rootTiming.exclude })).toEqual(
uiTimingTestFiles,
);
for (const file of uiTimingTestFiles) {
@ -391,23 +413,23 @@ describe("ui package vitest config", () => {
const browser = projects.find((project) => project.browser?.enabled);
const node = projects.find((project) => project.name === "unit-node");
const root = requireTestConfig(createUiVitestConfig());
const nativeFiles = globSync(browser?.include ?? [], {
const nativeFiles = globTestFiles(browser?.include ?? [], {
cwd: uiRoot,
exclude: browser?.exclude,
}).map((file) => `ui/${file}`);
const nodeFiles = globSync(node?.include ?? [], {
const nodeFiles = globTestFiles(node?.include ?? [], {
cwd: uiRoot,
exclude: node?.exclude,
})
.filter((file) => file.endsWith(".browser.test.ts"))
.map((file) => `ui/${file}`);
const rootFiles = globSync(root.include ?? [], { exclude: root.exclude });
const rootFiles = globTestFiles(root.include ?? [], { exclude: root.exclude });
expect(nativeFiles).toContain("ui/src/components/markdown-mermaid.runtime.browser.test.ts");
expect(nodeFiles).toContain("ui/src/components/form-controls.browser.test.ts");
expect(rootFiles.filter((file) => nativeFiles.includes(file))).toEqual([]);
expect(rootFiles).toEqual(expect.arrayContaining(nodeFiles));
expect([...nativeFiles, ...nodeFiles].toSorted()).toEqual(
globSync("ui/src/**/*.browser.test.ts").toSorted(),
globTestFiles("ui/src/**/*.browser.test.ts").toSorted(),
);
const unpartitionedBrowser = requireTestConfig(createUiBrowserVitestConfig({}));
expect(unpartitionedBrowser.browser?.provider?.prewarm).toEqual(expect.any(Function));
@ -451,9 +473,9 @@ describe("ui package vitest config", () => {
const scopedRoot = requireTestConfig(
createUiVitestConfig({ OPENCLAW_VITEST_INCLUDE_FILE: includeFile }),
);
expect(globSync(scopedRoot.include ?? [], { exclude: scopedRoot.exclude }).toSorted()).toEqual(
nodeFiles.toSorted(),
);
expect(
globTestFiles(scopedRoot.include ?? [], { exclude: scopedRoot.exclude }).toSorted(),
).toEqual(nodeFiles.toSorted());
});
it.each([
@ -491,7 +513,7 @@ describe("ui package vitest config", () => {
expect(config.root).toBe(uiRoot);
const selected = (requireTestConfig(config).projects ?? []).flatMap((project) => {
const test = requireTestConfig(project);
return globSync(test.include ?? [], { cwd: uiRoot, exclude: test.exclude }).map((file) =>
return globTestFiles(test.include ?? [], { cwd: uiRoot, exclude: test.exclude }).map((file) =>
path.posix.normalize(`ui/${file.replaceAll("\\", "/")}`),
);
});

View file

@ -2,6 +2,19 @@ import path from "node:path";
type GlobMatcher = (value: string, pattern: string) => boolean;
export const nonBrowserTestBasenamePattern = "!(*.browser.test.ts|!(*.test.ts))";
export function resolveNonBrowserTestPattern(pattern: string): string | null {
if (!pattern.endsWith(nonBrowserTestBasenamePattern)) {
return null;
}
const prefix = pattern.slice(0, -nonBrowserTestBasenamePattern.length);
if (prefix && !prefix.endsWith("/") && !prefix.endsWith("\\")) {
return null;
}
return prefix + "!(*.browser).test.ts";
}
export function filterFilesByPatterns(
files: readonly string[],
include: readonly string[],
@ -113,10 +126,11 @@ function isAtOrUnder(value: string, root: string): boolean {
function patternIsFullyUnderDirectory(pattern: string, root: string): boolean {
const normalized = pattern.trim().replaceAll("\\", "/").replace(/^\.\//u, "");
if (!normalized.endsWith(".test.ts")) {
const testPattern = resolveNonBrowserTestPattern(normalized) ?? normalized;
if (!testPattern.endsWith(".test.ts")) {
return false;
}
const literalPrefix = literalPrefixForGlobPattern(normalized).replace(/\/+$/u, "");
const literalPrefix = literalPrefixForGlobPattern(testPattern).replace(/\/+$/u, "");
return isAtOrUnder(literalPrefix, root);
}

View file

@ -2,8 +2,14 @@ import { spawnSync } from "node:child_process";
import { copyFileSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { describe, expect, it, vi } from "vitest";
import { filterFilesByPatterns, intersectIncludePatterns } from "./vitest.include-patterns.ts";
import { globSync } from "tinyglobby";
import { afterEach, describe, expect, it, vi } from "vitest";
import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js";
import {
filterFilesByPatterns,
intersectIncludePatterns,
nonBrowserTestBasenamePattern,
} from "./vitest.include-patterns.ts";
import {
collectVitestExcludePatterns,
matchesVitestCliSelection,
@ -31,16 +37,18 @@ describe("native CLI selection", () => {
"--input-type=module",
"--eval",
`import { matchesVitestGlob } from './test/vitest/vitest.pattern-file.ts';
import { filterFilesByPatterns } from './test/vitest/vitest.include-patterns.ts';
import { filterFilesByPatterns, nonBrowserTestBasenamePattern } from './test/vitest/vitest.include-patterns.ts';
console.log(JSON.stringify(filterFilesByPatterns(
['ui/src/example.test.ts', 'ui/src/example.browser.test.ts'],
['ui/src/**/!(*.browser).test.ts'], [], matchesVitestGlob
['ui/src/example.test.ts', 'ui/src/example.browser-import.test.ts', 'ui/src/example.browser.test.ts'],
['ui/src/**/' + nonBrowserTestBasenamePattern], [], matchesVitestGlob
)));`,
],
{ cwd: root, encoding: "utf8", env: { ...process.env, NODE_OPTIONS: "", NODE_PATH: "" } },
);
expect(result.status, result.stderr).toBe(0);
expect(result.stdout.trim()).toBe('["ui/src/example.test.ts"]');
expect(result.stdout.trim()).toBe(
'["ui/src/example.test.ts","ui/src/example.browser-import.test.ts"]',
);
} finally {
rmSync(root, { recursive: true, force: true });
}
@ -48,13 +56,33 @@ describe("native CLI selection", () => {
it.each([
{ file: "ui/src/pages/devices/capability-chips.test.ts", selected: true },
{ file: "ui/src/pages/devices/capability.browser-import.test.ts", selected: true },
{ file: "ui/src/pages/devices.browser/capability.test.ts", selected: true },
{ file: "ui/src/pages/devices/capability.browser-helper.browser.test.ts", selected: false },
{ file: "ui/src/pages/devices/capability-chips.browser.test.ts", selected: false },
])("preserves UI extglob ownership for $file", ({ file, selected }) => {
const include = ["ui/src/**/!(*.browser).test.ts"];
const include = ["ui/src/**/" + nonBrowserTestBasenamePattern];
const expected = selected ? [file] : [];
expect(narrowIncludePatternsForCli(include, ["node", "vitest", "run", file])).toEqual(expected);
expect(intersectIncludePatterns(include, [file], matchesVitestGlob)).toEqual(expected);
expect(matchesVitestCliSelection(file, include, ["run", file], "", {})).toBe(selected);
expect(
narrowIncludePatternsForCli(include, ["node", "vitest", "run", path.resolve(file)]),
).toEqual(selected ? [path.resolve(file).replaceAll("\\", "/")] : []);
});
it("keeps non-test paths outside UI discovery when CLI narrowing is unavailable", () => {
const file = "ui/src/pages/devices/capability.ts";
const absoluteFile = path.resolve(file);
const include = ["ui/src/**/" + nonBrowserTestBasenamePattern];
expect(narrowIncludePatternsForCli(include, ["node", "vitest", "run", file])).toEqual([]);
expect(
narrowIncludePatternsForCli(include, ["node", "vitest", "run", absoluteFile]),
).toBeNull();
expect(intersectIncludePatterns(include, [file], matchesVitestGlob)).toEqual([]);
for (const target of [file, absoluteFile]) {
expect(matchesVitestCliSelection(file, include, ["run", target], "", {})).toBe(false);
}
});
const infraFile = "src/infra/sqlite-worker-operation-attachment.test.ts";
@ -198,6 +226,52 @@ describe("batch file selection", () => {
});
describe("intersectIncludePatterns", () => {
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
it("preserves native non-browser test discovery through directory selection", () => {
const root = tempDirs.make("vitest-ui-selector-");
const uiFiles = [
"ui/src/ordinary.test.ts",
"ui/src/shared.browser-import.test.ts",
"ui/src/folder.browser/ordinary.test.ts",
"ui/src/shared.browser.contract.test.ts",
];
const pluginFiles = ["extensions/example/browser/shared.browser-import.test.ts"];
const files = [
...uiFiles,
...pluginFiles,
"ui/src/only.browser.test.ts",
"ui/src/helper.browser-helper.browser.test.ts",
"ui/src/readme.ts",
"extensions/example/browser/only.browser.test.ts",
"other/example.test.ts",
];
for (const file of files) {
const target = path.join(root, file);
mkdirSync(path.dirname(target), { recursive: true });
writeFileSync(target, "");
}
const owner = [
"ui/src/**/" + nonBrowserTestBasenamePattern,
"extensions/*/browser/**/" + nonBrowserTestBasenamePattern,
];
for (const { requested, expected } of [
{ requested: ["ui/src/**/*.test.ts"], expected: uiFiles },
{ requested: ["extensions/**/*.test.ts"], expected: pluginFiles },
{ requested: ["**/*.test.ts"], expected: [...uiFiles, ...pluginFiles] },
]) {
const selected = intersectIncludePatterns(owner, requested, matchesVitestGlob);
expect(filterFilesByPatterns(files, selected!, [], matchesVitestGlob).toSorted()).toEqual(
expected.toSorted(),
);
expect(
globSync(selected!, { cwd: root, dot: true, expandDirectories: false })
.map((file) => file.replaceAll("\\", "/"))
.toSorted(),
).toEqual(expected.toSorted());
}
});
it("projects arbitrary candidate globs onto a finite literal owner", () => {
const owner = [
"ui/src/e2e/chat.e2e.test.ts",
@ -214,13 +288,13 @@ describe("intersectIncludePatterns", () => {
).toEqual(["ui/src/e2e/chat.e2e.test.ts", "ui/src/e2e/chat.capture.e2e.test.ts"]);
});
it("retains the ambiguity guard for glob-owned inventories", () => {
expect(() =>
intersectIncludePatterns(
["ui/src/**/*.e2e.test.ts"],
["ui/src/e2e/*.e2e.test.ts"],
matchesVitestGlob,
),
).toThrow("cannot safely intersect non-literal include path");
it.each([
["ui/src/**/*.e2e.test.ts", "ui/src/e2e/*.e2e.test.ts"],
["ui/src/**/!(*.browser.test.ts)", "ui/src/**/*.test.ts"],
["ui/src/**/prefix" + nonBrowserTestBasenamePattern, "ui/src/**/*.test.ts"],
])("retains the ambiguity guard for glob-owned inventory %s", (owner, requested) => {
expect(() => intersectIncludePatterns([owner], [requested], matchesVitestGlob)).toThrow(
"cannot safely intersect non-literal include path",
);
});
});

View file

@ -4,7 +4,7 @@ import { createRequire } from "node:module";
import path from "node:path";
import type { Minimatch } from "minimatch";
import { collectVitestFileFilters } from "../../scripts/lib/vitest-cli-mode.mts";
import { narrowIncludePatterns } from "./vitest.include-patterns.ts";
import { narrowIncludePatterns, resolveNonBrowserTestPattern } from "./vitest.include-patterns.ts";
const repoRoot = path.resolve(import.meta.dirname, "../..");
const require = createRequire(import.meta.url);
@ -32,6 +32,11 @@ export function isSharedVitestExcludedPath(file: string, scopedDir = ""): boolea
}
export function matchesVitestGlob(value: string, pattern: string): boolean {
// Preserve the original Node/Bun spelling while native discovery uses the full basename.
const nonBrowser = resolveNonBrowserTestPattern(pattern);
if (nonBrowser) {
return matchesVitestGlob(value, nonBrowser);
}
// CI plans tests before installing dependencies; keep Node's matcher dependency-free.
if (!process.versions.bun) {
return path.matchesGlob(value, pattern);

View file

@ -1,6 +1,7 @@
// Vitest ui config wires the ui test shard.
import type { ViteUserConfig } from "vitest/config";
import { controlUiLocaleModulesPlugin } from "../../ui/config/control-ui-locales.ts";
import { nonBrowserTestBasenamePattern } from "./vitest.include-patterns.ts";
import { createScopedVitestConfig } from "./vitest.scoped-config.ts";
import { jsdomOptimizedDeps } from "./vitest.shared.config.ts";
import { uiIsolatedTestFiles } from "./vitest.ui-isolated-paths.mjs";
@ -14,7 +15,9 @@ import {
// Explicit nameable return type: inference reaches vite-internal names (TS4058/TS4082).
export function createUiVitestConfig(env?: Record<string, string | undefined>): ViteUserConfig {
const includePatterns = [
...controlUiTestGlobs.map((pattern) => pattern.replace("*.test.ts", "!(*.browser).test.ts")),
...controlUiTestGlobs.map((pattern) =>
pattern.replace("*.test.ts", nonBrowserTestBasenamePattern),
),
...uiNodeDrivenBrowserTestFiles,
];
// Isolated files must never enter the shared module graph, including scoped runs.