ci: rebalance test workers

This commit is contained in:
Peter Steinberger
2026-04-22 22:26:02 +01:00
parent 65ae1e54de
commit 77dbc1cda6
20 changed files with 460 additions and 111 deletions

View File

@@ -16,7 +16,7 @@ function listContractTests(rootDir = "src/channels/plugins/contracts"): string[]
describe("scripts/lib/channel-contract-test-plan.mjs", () => {
it("splits channel contracts into focused shards", () => {
const suffixes = ["a", "b", "c", "d"];
const suffixes = ["a", "b", "c"];
expect(
createChannelContractTestShards().map((shard) => ({
@@ -51,7 +51,7 @@ describe("scripts/lib/channel-contract-test-plan.mjs", () => {
const surfaceRegistryFiles = shard.includePatterns.filter((pattern) =>
pattern.includes("/surfaces-only.registry-backed-shard-"),
);
expect(surfaceRegistryFiles.length).toBeLessThanOrEqual(2);
expect(surfaceRegistryFiles.length).toBeLessThanOrEqual(3);
}
});
});

View File

@@ -73,6 +73,7 @@ describe("scripts/lib/ci-node-test-plan.mjs", () => {
"test/vitest/vitest.secrets.config.ts",
"test/vitest/vitest.logging.config.ts",
"test/vitest/vitest.process.config.ts",
"test/vitest/vitest.runtime-config.config.ts",
],
requiresDist: false,
shardName: "core-runtime-infra",
@@ -92,7 +93,6 @@ describe("scripts/lib/ci-node-test-plan.mjs", () => {
configs: [
"test/vitest/vitest.acp.config.ts",
"test/vitest/vitest.cron.config.ts",
"test/vitest/vitest.runtime-config.config.ts",
"test/vitest/vitest.shared-core.config.ts",
"test/vitest/vitest.tasks.config.ts",
"test/vitest/vitest.utils.config.ts",

View File

@@ -0,0 +1,49 @@
import { describe, expect, it } from "vitest";
import { summarizeRunTimings } from "../../scripts/ci-run-timings.mjs";
describe("scripts/ci-run-timings.mjs", () => {
it("separates queue time from job duration", () => {
const summary = summarizeRunTimings(
{
conclusion: "success",
createdAt: "2026-04-22T10:00:00Z",
jobs: [
{
completedAt: "2026-04-22T10:01:20Z",
conclusion: "success",
name: "slow",
startedAt: "2026-04-22T10:00:20Z",
status: "completed",
},
{
completedAt: "2026-04-22T10:01:00Z",
conclusion: "success",
name: "queued",
startedAt: "2026-04-22T10:00:50Z",
status: "completed",
},
{
completedAt: "2026-04-22T10:00:01Z",
conclusion: "skipped",
name: "matrix.check_name",
startedAt: "2026-04-22T10:00:01Z",
status: "completed",
},
],
status: "completed",
updatedAt: "2026-04-22T10:01:30Z",
},
2,
);
expect(summary.wallSeconds).toBe(90);
expect(summary.byDuration.map((job) => [job.name, job.durationSeconds])).toEqual([
["slow", 60],
["queued", 10],
]);
expect(summary.byQueue.map((job) => [job.name, job.queueSeconds])).toEqual([
["queued", 50],
["slow", 20],
]);
});
});

View File

@@ -173,12 +173,27 @@ describe("scripts/test-extension.mjs", () => {
expect(plan.hasTests).toBe(true);
});
it("keeps non-provider extensions on the shared extensions vitest config", () => {
const plan = resolveExtensionTestPlan({ targetArg: "firecrawl", cwd: process.cwd() });
it("resolves broad dedicated extension groups onto their narrow vitest configs", () => {
expect(resolveExtensionTestPlan({ targetArg: "browser", cwd: process.cwd() }).config).toBe(
"test/vitest/vitest.extension-browser.config.ts",
);
expect(resolveExtensionTestPlan({ targetArg: "qa-lab", cwd: process.cwd() }).config).toBe(
"test/vitest/vitest.extension-qa.config.ts",
);
expect(resolveExtensionTestPlan({ targetArg: "vydra", cwd: process.cwd() }).config).toBe(
"test/vitest/vitest.extension-media.config.ts",
);
expect(resolveExtensionTestPlan({ targetArg: "firecrawl", cwd: process.cwd() }).config).toBe(
"test/vitest/vitest.extension-misc.config.ts",
);
});
expect(plan.extensionId).toBe("firecrawl");
it("keeps unmatched non-provider extensions on the shared extensions vitest config", () => {
const plan = resolveExtensionTestPlan({ targetArg: "codex", cwd: process.cwd() });
expect(plan.extensionId).toBe("codex");
expect(plan.config).toBe("test/vitest/vitest.extensions.config.ts");
expect(plan.roots).toContain(bundledPluginRoot("firecrawl"));
expect(plan.roots).toContain(bundledPluginRoot("codex"));
expect(plan.hasTests).toBe(true);
});
@@ -260,12 +275,16 @@ describe("scripts/test-extension.mjs", () => {
"bluebubbles",
"acpx",
"diffs",
"browser",
"qa-lab",
"vydra",
],
});
expect(batch.extensionIds).toEqual([
"acpx",
"bluebubbles",
"browser",
"diffs",
"feishu",
"firecrawl",
@@ -276,9 +295,11 @@ describe("scripts/test-extension.mjs", () => {
"memory-core",
"msteams",
"openai",
"qa-lab",
"slack",
"telegram",
"voice-call",
"vydra",
"whatsapp",
"zalo",
"zalouser",
@@ -298,6 +319,13 @@ describe("scripts/test-extension.mjs", () => {
roots: [bundledPluginRoot("bluebubbles")],
testFileCount: expect.any(Number),
},
{
config: "test/vitest/vitest.extension-browser.config.ts",
estimatedCost: expect.any(Number),
extensionIds: ["browser"],
roots: [bundledPluginRoot("browser")],
testFileCount: expect.any(Number),
},
{
config: "test/vitest/vitest.extension-diffs.config.ts",
estimatedCost: expect.any(Number),
@@ -340,6 +368,13 @@ describe("scripts/test-extension.mjs", () => {
roots: [bundledPluginRoot("mattermost")],
testFileCount: expect.any(Number),
},
{
config: "test/vitest/vitest.extension-media.config.ts",
estimatedCost: expect.any(Number),
extensionIds: ["vydra"],
roots: [bundledPluginRoot("vydra")],
testFileCount: expect.any(Number),
},
{
config: "test/vitest/vitest.extension-memory.config.ts",
estimatedCost: expect.any(Number),
@@ -347,6 +382,13 @@ describe("scripts/test-extension.mjs", () => {
roots: [bundledPluginRoot("memory-core")],
testFileCount: expect.any(Number),
},
{
config: "test/vitest/vitest.extension-misc.config.ts",
estimatedCost: expect.any(Number),
extensionIds: ["firecrawl"],
roots: [bundledPluginRoot("firecrawl")],
testFileCount: expect.any(Number),
},
{
config: "test/vitest/vitest.extension-msteams.config.ts",
estimatedCost: expect.any(Number),
@@ -361,6 +403,13 @@ describe("scripts/test-extension.mjs", () => {
roots: [bundledPluginRoot("openai")],
testFileCount: expect.any(Number),
},
{
config: "test/vitest/vitest.extension-qa.config.ts",
estimatedCost: expect.any(Number),
extensionIds: ["qa-lab"],
roots: [bundledPluginRoot("qa-lab")],
testFileCount: expect.any(Number),
},
{
config: "test/vitest/vitest.extension-slack.config.ts",
estimatedCost: expect.any(Number),
@@ -396,13 +445,6 @@ describe("scripts/test-extension.mjs", () => {
roots: [bundledPluginRoot("zalo"), bundledPluginRoot("zalouser")],
testFileCount: expect.any(Number),
},
{
config: "test/vitest/vitest.extensions.config.ts",
estimatedCost: expect.any(Number),
extensionIds: ["firecrawl"],
roots: [bundledPluginRoot("firecrawl")],
testFileCount: expect.any(Number),
},
]);
});

View File

@@ -20,6 +20,7 @@ import { createCronVitestConfig } from "./vitest/vitest.cron.config.ts";
import { createDaemonVitestConfig } from "./vitest/vitest.daemon.config.ts";
import { createExtensionAcpxVitestConfig } from "./vitest/vitest.extension-acpx.config.ts";
import { createExtensionBlueBubblesVitestConfig } from "./vitest/vitest.extension-bluebubbles.config.ts";
import { createExtensionBrowserVitestConfig } from "./vitest/vitest.extension-browser.config.ts";
import { createExtensionChannelsVitestConfig } from "./vitest/vitest.extension-channels.config.ts";
import { createExtensionDiffsVitestConfig } from "./vitest/vitest.extension-diffs.config.ts";
import { createExtensionDiscordVitestConfig } from "./vitest/vitest.extension-discord.config.ts";
@@ -29,11 +30,14 @@ import { createExtensionIrcVitestConfig } from "./vitest/vitest.extension-irc.co
import { createExtensionLineVitestConfig } from "./vitest/vitest.extension-line.config.ts";
import { createExtensionMatrixVitestConfig } from "./vitest/vitest.extension-matrix.config.ts";
import { createExtensionMattermostVitestConfig } from "./vitest/vitest.extension-mattermost.config.ts";
import { createExtensionMediaVitestConfig } from "./vitest/vitest.extension-media.config.ts";
import { createExtensionMemoryVitestConfig } from "./vitest/vitest.extension-memory.config.ts";
import { createExtensionMessagingVitestConfig } from "./vitest/vitest.extension-messaging.config.ts";
import { createExtensionMiscVitestConfig } from "./vitest/vitest.extension-misc.config.ts";
import { createExtensionMsTeamsVitestConfig } from "./vitest/vitest.extension-msteams.config.ts";
import { createExtensionProviderOpenAiVitestConfig } from "./vitest/vitest.extension-provider-openai.config.ts";
import { createExtensionProvidersVitestConfig } from "./vitest/vitest.extension-providers.config.ts";
import { createExtensionQaVitestConfig } from "./vitest/vitest.extension-qa.config.ts";
import { createExtensionSignalVitestConfig } from "./vitest/vitest.extension-signal.config.ts";
import { createExtensionSlackVitestConfig } from "./vitest/vitest.extension-slack.config.ts";
import { createExtensionTelegramVitestConfig } from "./vitest/vitest.extension-telegram.config.ts";
@@ -188,6 +192,7 @@ describe("scoped vitest configs", () => {
const defaultExtensionAcpxConfig = createExtensionAcpxVitestConfig({});
const defaultExtensionBlueBubblesConfig = createExtensionBlueBubblesVitestConfig({});
const defaultExtensionChannelsConfig = createExtensionChannelsVitestConfig({});
const defaultExtensionBrowserConfig = createExtensionBrowserVitestConfig({});
const defaultExtensionDiffsConfig = createExtensionDiffsVitestConfig({});
const defaultExtensionDiscordConfig = createExtensionDiscordVitestConfig({});
const defaultExtensionFeishuConfig = createExtensionFeishuVitestConfig({});
@@ -196,11 +201,14 @@ describe("scoped vitest configs", () => {
const defaultExtensionLineConfig = createExtensionLineVitestConfig({});
const defaultExtensionMatrixConfig = createExtensionMatrixVitestConfig({});
const defaultExtensionMattermostConfig = createExtensionMattermostVitestConfig({});
const defaultExtensionMediaConfig = createExtensionMediaVitestConfig({});
const defaultExtensionMemoryConfig = createExtensionMemoryVitestConfig({});
const defaultExtensionMiscConfig = createExtensionMiscVitestConfig({});
const defaultExtensionMsTeamsConfig = createExtensionMsTeamsVitestConfig({});
const defaultExtensionMessagingConfig = createExtensionMessagingVitestConfig({});
const defaultExtensionProviderOpenAiConfig = createExtensionProviderOpenAiVitestConfig({});
const defaultExtensionProvidersConfig = createExtensionProvidersVitestConfig({});
const defaultExtensionQaConfig = createExtensionQaVitestConfig({});
const defaultExtensionSignalConfig = createExtensionSignalVitestConfig({});
const defaultExtensionSlackConfig = createExtensionSlackVitestConfig({});
const defaultExtensionTelegramConfig = createExtensionTelegramVitestConfig({});
@@ -602,6 +610,22 @@ describe("scoped vitest configs", () => {
).toBe(true);
});
it("keeps broad dedicated extension groups out of the shared extensions lane", () => {
const extensionExcludes = defaultExtensionsConfig.test?.exclude ?? [];
expect(defaultExtensionBrowserConfig.test?.include).toContain("browser/**/*.test.ts");
expect(defaultExtensionMediaConfig.test?.include).toContain("vydra/**/*.test.ts");
expect(defaultExtensionMiscConfig.test?.include).toContain("firecrawl/**/*.test.ts");
expect(defaultExtensionQaConfig.test?.include).toContain("qa-lab/**/*.test.ts");
for (const file of [
"browser/src/browser/pw.test.ts",
"vydra/src/index.test.ts",
"firecrawl/src/index.test.ts",
"qa-lab/src/index.test.ts",
]) {
expect(extensionExcludes.some((pattern) => path.matchesGlob(file, pattern))).toBe(true);
}
});
it("normalizes gateway include patterns relative to the scoped dir", () => {
expect(defaultGatewayConfig.test?.dir).toBe(path.join(process.cwd(), "src", "gateway"));
expect(defaultGatewayConfig.test?.include).toEqual(["**/*.test.ts"]);

View File

@@ -0,0 +1,5 @@
export const browserExtensionTestRoots = ["extensions/browser"];
export function isBrowserExtensionRoot(root) {
return browserExtensionTestRoots.includes(root);
}

View File

@@ -1,8 +1,27 @@
import { browserExtensionTestRoots } from "./vitest.extension-browser-paths.mjs";
import { loadPatternListFromEnv } from "./vitest.pattern-file.ts";
import { createScopedVitestConfig } from "./vitest.scoped-config.ts";
export default createScopedVitestConfig(["browser/**/*.test.ts"], {
dir: "extensions",
name: "extension-browser",
passWithNoTests: true,
setupFiles: ["test/setup.extensions.ts"],
});
export function loadIncludePatternsFromEnv(
env: Record<string, string | undefined> = process.env,
): string[] | null {
return loadPatternListFromEnv("OPENCLAW_VITEST_INCLUDE_FILE", env);
}
export function createExtensionBrowserVitestConfig(
env: Record<string, string | undefined> = process.env,
) {
return createScopedVitestConfig(
loadIncludePatternsFromEnv(env) ??
browserExtensionTestRoots.map((root) => `${root}/**/*.test.ts`),
{
dir: "extensions",
env,
name: "extension-browser",
passWithNoTests: true,
setupFiles: ["test/setup.extensions.ts"],
},
);
}
export default createExtensionBrowserVitestConfig();

View File

@@ -0,0 +1,16 @@
export const mediaExtensionTestRoots = [
"extensions/alibaba",
"extensions/deepgram",
"extensions/elevenlabs",
"extensions/fal",
"extensions/image-generation-core",
"extensions/runway",
"extensions/talk-voice",
"extensions/video-generation-core",
"extensions/vydra",
"extensions/xiaomi",
];
export function isMediaExtensionRoot(root) {
return mediaExtensionTestRoots.includes(root);
}

View File

@@ -1,22 +1,27 @@
import { mediaExtensionTestRoots } from "./vitest.extension-media-paths.mjs";
import { loadPatternListFromEnv } from "./vitest.pattern-file.ts";
import { createScopedVitestConfig } from "./vitest.scoped-config.ts";
export default createScopedVitestConfig(
[
"alibaba/**/*.test.ts",
"deepgram/**/*.test.ts",
"elevenlabs/**/*.test.ts",
"fal/**/*.test.ts",
"image-generation-core/**/*.test.ts",
"runway/**/*.test.ts",
"talk-voice/**/*.test.ts",
"video-generation-core/**/*.test.ts",
"vydra/**/*.test.ts",
"xiaomi/**/*.test.ts",
],
{
dir: "extensions",
name: "extension-media",
passWithNoTests: true,
setupFiles: ["test/setup.extensions.ts"],
},
);
export function loadIncludePatternsFromEnv(
env: Record<string, string | undefined> = process.env,
): string[] | null {
return loadPatternListFromEnv("OPENCLAW_VITEST_INCLUDE_FILE", env);
}
export function createExtensionMediaVitestConfig(
env: Record<string, string | undefined> = process.env,
) {
return createScopedVitestConfig(
loadIncludePatternsFromEnv(env) ??
mediaExtensionTestRoots.map((root) => `${root}/**/*.test.ts`),
{
dir: "extensions",
env,
name: "extension-media",
passWithNoTests: true,
setupFiles: ["test/setup.extensions.ts"],
},
);
}
export default createExtensionMediaVitestConfig();

View File

@@ -0,0 +1,29 @@
export const miscExtensionTestRoots = [
"extensions/arcee",
"extensions/brave",
"extensions/device-pair",
"extensions/diagnostics-otel",
"extensions/duckduckgo",
"extensions/exa",
"extensions/firecrawl",
"extensions/fireworks",
"extensions/kilocode",
"extensions/litellm",
"extensions/llm-task",
"extensions/lobster",
"extensions/opencode",
"extensions/opencode-go",
"extensions/openshell",
"extensions/perplexity",
"extensions/phone-control",
"extensions/searxng",
"extensions/synthetic",
"extensions/tavily",
"extensions/thread-ownership",
"extensions/vercel-ai-gateway",
"extensions/webhooks",
];
export function isMiscExtensionRoot(root) {
return miscExtensionTestRoots.includes(root);
}

View File

@@ -1,35 +1,26 @@
import { miscExtensionTestRoots } from "./vitest.extension-misc-paths.mjs";
import { loadPatternListFromEnv } from "./vitest.pattern-file.ts";
import { createScopedVitestConfig } from "./vitest.scoped-config.ts";
export default createScopedVitestConfig(
[
"arcee/**/*.test.ts",
"brave/**/*.test.ts",
"device-pair/**/*.test.ts",
"diagnostics-otel/**/*.test.ts",
"duckduckgo/**/*.test.ts",
"exa/**/*.test.ts",
"firecrawl/**/*.test.ts",
"fireworks/**/*.test.ts",
"kilocode/**/*.test.ts",
"litellm/**/*.test.ts",
"llm-task/**/*.test.ts",
"lobster/**/*.test.ts",
"opencode/**/*.test.ts",
"opencode-go/**/*.test.ts",
"openshell/**/*.test.ts",
"perplexity/**/*.test.ts",
"phone-control/**/*.test.ts",
"searxng/**/*.test.ts",
"synthetic/**/*.test.ts",
"tavily/**/*.test.ts",
"thread-ownership/**/*.test.ts",
"vercel-ai-gateway/**/*.test.ts",
"webhooks/**/*.test.ts",
],
{
dir: "extensions",
name: "extension-misc",
passWithNoTests: true,
setupFiles: ["test/setup.extensions.ts"],
},
);
export function loadIncludePatternsFromEnv(
env: Record<string, string | undefined> = process.env,
): string[] | null {
return loadPatternListFromEnv("OPENCLAW_VITEST_INCLUDE_FILE", env);
}
export function createExtensionMiscVitestConfig(
env: Record<string, string | undefined> = process.env,
) {
return createScopedVitestConfig(
loadIncludePatternsFromEnv(env) ?? miscExtensionTestRoots.map((root) => `${root}/**/*.test.ts`),
{
dir: "extensions",
env,
name: "extension-misc",
passWithNoTests: true,
setupFiles: ["test/setup.extensions.ts"],
},
);
}
export default createExtensionMiscVitestConfig();

View File

@@ -0,0 +1,5 @@
export const qaExtensionTestRoots = ["extensions/qa-channel", "extensions/qa-lab"];
export function isQaExtensionRoot(root) {
return qaExtensionTestRoots.includes(root);
}

View File

@@ -1,8 +1,26 @@
import { qaExtensionTestRoots } from "./vitest.extension-qa-paths.mjs";
import { loadPatternListFromEnv } from "./vitest.pattern-file.ts";
import { createScopedVitestConfig } from "./vitest.scoped-config.ts";
export default createScopedVitestConfig(["qa-channel/**/*.test.ts", "qa-lab/**/*.test.ts"], {
dir: "extensions",
name: "extension-qa",
passWithNoTests: true,
setupFiles: ["test/setup.extensions.ts"],
});
export function loadIncludePatternsFromEnv(
env: Record<string, string | undefined> = process.env,
): string[] | null {
return loadPatternListFromEnv("OPENCLAW_VITEST_INCLUDE_FILE", env);
}
export function createExtensionQaVitestConfig(
env: Record<string, string | undefined> = process.env,
) {
return createScopedVitestConfig(
loadIncludePatternsFromEnv(env) ?? qaExtensionTestRoots.map((root) => `${root}/**/*.test.ts`),
{
dir: "extensions",
env,
name: "extension-qa",
passWithNoTests: true,
setupFiles: ["test/setup.extensions.ts"],
},
);
}
export default createExtensionQaVitestConfig();

View File

@@ -2,18 +2,22 @@ import { BUNDLED_PLUGIN_TEST_GLOB } from "./vitest.bundled-plugin-paths.ts";
import { extensionExcludedChannelTestGlobs } from "./vitest.channel-paths.mjs";
import { acpxExtensionTestRoots } from "./vitest.extension-acpx-paths.mjs";
import { blueBubblesExtensionTestRoots } from "./vitest.extension-bluebubbles-paths.mjs";
import { browserExtensionTestRoots } from "./vitest.extension-browser-paths.mjs";
import { diffsExtensionTestRoots } from "./vitest.extension-diffs-paths.mjs";
import { feishuExtensionTestRoots } from "./vitest.extension-feishu-paths.mjs";
import { ircExtensionTestRoots } from "./vitest.extension-irc-paths.mjs";
import { matrixExtensionTestRoots } from "./vitest.extension-matrix-paths.mjs";
import { mattermostExtensionTestRoots } from "./vitest.extension-mattermost-paths.mjs";
import { mediaExtensionTestRoots } from "./vitest.extension-media-paths.mjs";
import { memoryExtensionTestRoots } from "./vitest.extension-memory-paths.mjs";
import { messagingExtensionTestRoots } from "./vitest.extension-messaging-paths.mjs";
import { miscExtensionTestRoots } from "./vitest.extension-misc-paths.mjs";
import { msTeamsExtensionTestRoots } from "./vitest.extension-msteams-paths.mjs";
import {
providerExtensionTestRoots,
providerOpenAiExtensionTestRoots,
} from "./vitest.extension-provider-paths.mjs";
import { qaExtensionTestRoots } from "./vitest.extension-qa-paths.mjs";
import { telegramExtensionTestRoots } from "./vitest.extension-telegram-paths.mjs";
import { voiceCallExtensionTestRoots } from "./vitest.extension-voice-call-paths.mjs";
import { whatsAppExtensionTestRoots } from "./vitest.extension-whatsapp-paths.mjs";
@@ -42,16 +46,20 @@ export function createExtensionsVitestConfig(
...extensionExcludedChannelTestGlobs,
...acpxExtensionTestRoots.map((root) => `${root.replace(/^extensions\//u, "")}/**`),
...blueBubblesExtensionTestRoots.map((root) => `${root.replace(/^extensions\//u, "")}/**`),
...browserExtensionTestRoots.map((root) => `${root.replace(/^extensions\//u, "")}/**`),
...diffsExtensionTestRoots.map((root) => `${root.replace(/^extensions\//u, "")}/**`),
...feishuExtensionTestRoots.map((root) => `${root.replace(/^extensions\//u, "")}/**`),
...ircExtensionTestRoots.map((root) => `${root.replace(/^extensions\//u, "")}/**`),
...matrixExtensionTestRoots.map((root) => `${root.replace(/^extensions\//u, "")}/**`),
...mattermostExtensionTestRoots.map((root) => `${root.replace(/^extensions\//u, "")}/**`),
...mediaExtensionTestRoots.map((root) => `${root.replace(/^extensions\//u, "")}/**`),
...memoryExtensionTestRoots.map((root) => `${root.replace(/^extensions\//u, "")}/**`),
...messagingExtensionTestRoots.map((root) => `${root.replace(/^extensions\//u, "")}/**`),
...miscExtensionTestRoots.map((root) => `${root.replace(/^extensions\//u, "")}/**`),
...msTeamsExtensionTestRoots.map((root) => `${root.replace(/^extensions\//u, "")}/**`),
...providerOpenAiExtensionTestRoots.map((root) => `${root.replace(/^extensions\//u, "")}/**`),
...providerExtensionTestRoots.map((root) => `${root.replace(/^extensions\//u, "")}/**`),
...qaExtensionTestRoots.map((root) => `${root.replace(/^extensions\//u, "")}/**`),
...telegramExtensionTestRoots.map((root) => `${root.replace(/^extensions\//u, "")}/**`),
...voiceCallExtensionTestRoots.map((root) => `${root.replace(/^extensions\//u, "")}/**`),
...whatsAppExtensionTestRoots.map((root) => `${root.replace(/^extensions\//u, "")}/**`),