diff --git a/docs/help/testing.md b/docs/help/testing.md index 97d2c204c7e3..890bd9978c02 100644 --- a/docs/help/testing.md +++ b/docs/help/testing.md @@ -781,10 +781,10 @@ Native dependency policy: - Files: `src/**/*.e2e.test.ts`, `test/**/*.e2e.test.ts`, and bundled-plugin E2E tests under `extensions/` - Runtime defaults: - Uses Vitest `threads` with `isolate: false`, matching the rest of the repo. - - Uses adaptive workers (CI: up to 2, local: 1 by default). + - Uses one worker by default to keep non-isolated gateway state deterministic. - Runs in silent mode by default to reduce console I/O overhead. - Useful overrides: - - `OPENCLAW_E2E_WORKERS=` to force worker count (capped at 16). + - `OPENCLAW_E2E_WORKERS=` to opt into parallel workers (capped at 16). - `OPENCLAW_E2E_VERBOSE=1` to re-enable verbose console output. - Scope: - Multi-instance gateway end-to-end behavior diff --git a/docs/reference/test.md b/docs/reference/test.md index aaaf6b1c9845..d5cc6b37bfc0 100644 --- a/docs/reference/test.md +++ b/docs/reference/test.md @@ -113,7 +113,7 @@ Test wrapper runs end with a short `[test] passed|failed|skipped ... in ...` sum - Gateway tests are included in the untargeted `pnpm test` full suite; run them alone with `pnpm test:gateway`. - `pnpm test:e2e`: repo E2E aggregate = `pnpm test:e2e:gateway && pnpm test:ui:e2e`. -- `pnpm test:e2e:gateway`: gateway end-to-end smoke tests (multi-instance WS/HTTP/node pairing). Defaults to `threads` + `isolate: false` with adaptive workers in `vitest.e2e.config.ts`; tune with `OPENCLAW_E2E_WORKERS=`, verbose logs with `OPENCLAW_E2E_VERBOSE=1`. +- `pnpm test:e2e:gateway`: gateway end-to-end smoke tests (multi-instance WS/HTTP/node pairing). Defaults to `threads` + `isolate: false` with one worker in `vitest.e2e.config.ts`; opt into parallelism with `OPENCLAW_E2E_WORKERS=` (capped at 16), and enable verbose logs with `OPENCLAW_E2E_VERBOSE=1`. - `pnpm test:live`: provider live tests (Claude/Minimax/DeepSeek/z.ai/etc, gated by `*.live.test.ts`). Requires API keys and `LIVE=1` (or `OPENCLAW_LIVE_TEST=1`) to unskip; verbose output with `OPENCLAW_LIVE_TEST_QUIET=0`. ## Full Docker suite (`pnpm test:docker:all`) diff --git a/src/infra/vitest-e2e-config.test.ts b/src/infra/vitest-e2e-config.test.ts index 4f8123fe9f62..3a74381ee8d2 100644 --- a/src/infra/vitest-e2e-config.test.ts +++ b/src/infra/vitest-e2e-config.test.ts @@ -5,7 +5,7 @@ import { normalizeConfigPaths, } from "../../test/helpers/vitest-config-paths.js"; import { BUNDLED_PLUGIN_E2E_TEST_GLOB } from "../../test/vitest/vitest.bundled-plugin-paths.ts"; -import e2eConfig from "../../test/vitest/vitest.e2e.config.ts"; +import e2eConfig, { createE2EVitestConfig } from "../../test/vitest/vitest.e2e.config.ts"; describe("e2e vitest config", () => { it("runs as a standalone config instead of inheriting unit projects", () => { @@ -32,4 +32,12 @@ describe("e2e vitest config", () => { "test/setup-openclaw-runtime.ts", ]); }); + + it("serializes default e2e runs while preserving explicit worker overrides", () => { + expect(createE2EVitestConfig({}).test?.maxWorkers).toBe(1); + expect(createE2EVitestConfig({ OPENCLAW_E2E_WORKERS: "4" }).test?.maxWorkers).toBe(4); + expect(createE2EVitestConfig({ OPENCLAW_E2E_WORKERS: "99" }).test?.maxWorkers).toBe(16); + expect(createE2EVitestConfig({ OPENCLAW_E2E_WORKERS: "0" }).test?.maxWorkers).toBe(1); + expect(createE2EVitestConfig({ OPENCLAW_E2E_WORKERS: "invalid" }).test?.maxWorkers).toBe(1); + }); }); diff --git a/test/vitest/vitest.e2e.config.ts b/test/vitest/vitest.e2e.config.ts index 5e84b188056f..53f92c5a4b03 100644 --- a/test/vitest/vitest.e2e.config.ts +++ b/test/vitest/vitest.e2e.config.ts @@ -1,22 +1,17 @@ // Vitest e2e config wires the e2e test shard. -import os from "node:os"; import { defineConfig } from "vitest/config"; import { BUNDLED_PLUGIN_E2E_TEST_GLOB } from "./vitest.bundled-plugin-paths.ts"; import baseConfig from "./vitest.config.ts"; import { resolveRepoRootPath } from "./vitest.shared.config.ts"; -const base = baseConfig as unknown as Record; -const isCI = process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true"; -const cpuCount = os.cpus().length; -// Keep e2e runs cheap by default; callers can still override via OPENCLAW_E2E_WORKERS. -const defaultWorkers = isCI ? Math.min(2, Math.max(1, Math.floor(cpuCount * 0.25))) : 1; -const requestedWorkers = Number.parseInt(process.env.OPENCLAW_E2E_WORKERS ?? "", 10); -const e2eWorkers = - Number.isFinite(requestedWorkers) && requestedWorkers > 0 +function resolveE2EWorkerCount(env: Record): number { + const requestedWorkers = Number.parseInt(env.OPENCLAW_E2E_WORKERS ?? "", 10); + return Number.isFinite(requestedWorkers) && requestedWorkers > 0 ? Math.min(16, requestedWorkers) - : defaultWorkers; -const verboseE2E = process.env.OPENCLAW_E2E_VERBOSE === "1"; + : 1; +} +const base = baseConfig as unknown as Record; const baseTestWithProjects = (baseConfig as { test?: { exclude?: string[]; projects?: string[]; setupFiles?: string[] } }) .test ?? {}; @@ -33,27 +28,37 @@ const exclude = [ ...tuiPtyExcludes, ]; -export default defineConfig({ - ...base, - test: { - ...baseTest, - maxWorkers: e2eWorkers, - silent: !verboseE2E, - globalSetup: [resolveRepoRootPath("test/vitest/vitest.e2e.global-setup.ts")], - setupFiles: [ - ...new Set( - [...(baseTest.setupFiles ?? []), "test/setup-openclaw-runtime.ts"].map(resolveRepoRootPath), - ), - ], - include: [ - "test/**/*.e2e.test.ts", - "src/**/*.e2e.test.ts", - "packages/**/*.e2e.test.ts", - "src/gateway/gateway.test.ts", - "src/gateway/server.startup-matrix-migration.integration.test.ts", - "src/gateway/sessions-history-http.test.ts", - BUNDLED_PLUGIN_E2E_TEST_GLOB, - ], - exclude, - }, -}); +export function createE2EVitestConfig(env: Record = process.env) { + // Keep e2e runs deterministic by default; callers can still opt into parallelism. + const e2eWorkers = resolveE2EWorkerCount(env); + const verboseE2E = env.OPENCLAW_E2E_VERBOSE === "1"; + + return defineConfig({ + ...base, + test: { + ...baseTest, + maxWorkers: e2eWorkers, + silent: !verboseE2E, + globalSetup: [resolveRepoRootPath("test/vitest/vitest.e2e.global-setup.ts")], + setupFiles: [ + ...new Set( + [...(baseTest.setupFiles ?? []), "test/setup-openclaw-runtime.ts"].map( + resolveRepoRootPath, + ), + ), + ], + include: [ + "test/**/*.e2e.test.ts", + "src/**/*.e2e.test.ts", + "packages/**/*.e2e.test.ts", + "src/gateway/gateway.test.ts", + "src/gateway/server.startup-matrix-migration.integration.test.ts", + "src/gateway/sessions-history-http.test.ts", + BUNDLED_PLUGIN_E2E_TEST_GLOB, + ], + exclude, + }, + }); +} + +export default createE2EVitestConfig();