Skip to content

Commit 6268eee

Browse files
authored
test: wire Control UI suite into CI, fix its broken tests, drop dead harness surface (#104361)
* test: wire Control UI suite into CI, fix its broken tests, drop dead harness surface - fix 7 Control UI tests broken on main: module-mock factories are unreliable under isolate:false shared workers; use RealtimeTalkSession prototype spies and real jsdom storage instead (chat-realtime, chat-pull-requests) - add gated checks-ui CI job (runUiTests changed-scope output): chromium provisioning, lint:ui:no-raw-window-open, pnpm --dir ui test; ~30s on a 4vcpu runner, runs only when ui-affecting paths change - add weekly node22-compat workflow exercising the supported lower Node bound with the same command set as the dispatch-only ci.yml job - delete the unreachable channels entry in EXTENSION_TEST_CONFIG_ROUTES and the never-populated extensionRoutedChannelTestFiles override machinery (born empty in 2ccb5cf); resolved globs are provably identical - guard run-vitest.mjs hardcoded path maps with existence tests so renames cannot silently drop extended stall watchdogs - remove unenforced coverage thresholds; test:coverage is informational, docs updated (reference/test, help/testing, plugins/sdk-testing) Closes #104321 * test: pin OPENCLAW_CI_RUN_UI_TESTS in the manifest env expectation * test(ui): capture the realtime start spy instead of referencing the unbound prototype method * test(ui): raise ui vitest timeouts for real-browser layout tests on small CI runners
1 parent aeab8e7 commit 6268eee

19 files changed

Lines changed: 213 additions & 117 deletions

.github/workflows/ci.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ jobs:
100100
run_check_additional: ${{ steps.manifest.outputs.run_check_additional }}
101101
run_check_docs: ${{ steps.manifest.outputs.run_check_docs }}
102102
run_control_ui_i18n: ${{ steps.manifest.outputs.run_control_ui_i18n }}
103+
run_ui_tests: ${{ steps.manifest.outputs.run_ui_tests }}
103104
run_native_i18n: ${{ steps.manifest.outputs.run_native_i18n }}
104105
run_checks_windows: ${{ steps.manifest.outputs.run_checks_windows }}
105106
checks_windows_matrix: ${{ steps.manifest.outputs.checks_windows_matrix }}
@@ -246,6 +247,7 @@ jobs:
246247
OPENCLAW_CI_RUN_NODE_FAST_CI_ROUTING: ${{ github.event_name == 'workflow_dispatch' && 'false' || steps.changed_scope.outputs.run_node_fast_ci_routing || 'false' }}
247248
OPENCLAW_CI_RUN_SKILLS_PYTHON: ${{ github.event_name == 'workflow_dispatch' && 'true' || steps.changed_scope.outputs.run_skills_python || 'false' }}
248249
OPENCLAW_CI_RUN_CONTROL_UI_I18N: ${{ github.event_name == 'workflow_dispatch' && 'true' || steps.changed_scope.outputs.run_control_ui_i18n || 'false' }}
250+
OPENCLAW_CI_RUN_UI_TESTS: ${{ github.event_name == 'workflow_dispatch' && 'true' || steps.changed_scope.outputs.run_ui_tests || 'false' }}
249251
OPENCLAW_CI_RUN_NATIVE_I18N: ${{ github.event_name == 'workflow_dispatch' && 'true' || steps.changed_scope.outputs.run_native_i18n || 'false' }}
250252
OPENCLAW_CI_CHECKOUT_REVISION: ${{ steps.checkout_ref.outputs.sha }}
251253
OPENCLAW_CI_REPOSITORY: ${{ github.repository }}
@@ -314,6 +316,7 @@ jobs:
314316
const runSkillsPython = parseBoolean(process.env.OPENCLAW_CI_RUN_SKILLS_PYTHON) && !docsOnly;
315317
const runControlUiI18n =
316318
parseBoolean(process.env.OPENCLAW_CI_RUN_CONTROL_UI_I18N) && !docsOnly;
319+
const runUiTests = parseBoolean(process.env.OPENCLAW_CI_RUN_UI_TESTS) && !docsOnly;
317320
const runNativeI18n =
318321
parseBoolean(process.env.OPENCLAW_CI_RUN_NATIVE_I18N) && !docsOnly;
319322
const checksFastCoreTasks = [];
@@ -384,6 +387,7 @@ jobs:
384387
run_check_additional: runNodeFull,
385388
run_check_docs: docsChanged && eventName !== "push",
386389
run_control_ui_i18n: runControlUiI18n,
390+
run_ui_tests: runUiTests,
387391
run_native_i18n: runNativeI18n,
388392
run_skills_python_job: runSkillsPython,
389393
run_checks_windows: runWindows,
@@ -884,6 +888,31 @@ jobs:
884888
- name: Check Apple app i18n catalogs
885889
run: pnpm apple:i18n:check
886890

891+
checks-ui:
892+
permissions:
893+
contents: read
894+
name: checks-ui
895+
needs: [preflight]
896+
if: needs.preflight.outputs.run_ui_tests == 'true'
897+
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
898+
timeout-minutes: 20
899+
steps:
900+
- *linux_node_checkout_step
901+
- name: Setup Node environment
902+
uses: ./.github/actions/setup-node-env
903+
with:
904+
node-version: "24.x"
905+
install-bun: "false"
906+
907+
- name: Install Playwright Chromium
908+
run: node scripts/ensure-playwright-chromium.mjs
909+
910+
- name: Lint Control UI window.open usage
911+
run: pnpm lint:ui:no-raw-window-open
912+
913+
- name: Test Control UI
914+
run: pnpm --dir ui test
915+
887916
checks-fast-core:
888917
permissions:
889918
contents: read
@@ -2194,6 +2223,7 @@ jobs:
21942223
- security-fast
21952224
- pnpm-store-warmup
21962225
- build-artifacts
2226+
- checks-ui
21972227
- checks-fast-core
21982228
- checks-fast-plugin-contracts-shard
21992229
- checks-fast-channel-contracts-shard
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Node 22 Compat
2+
3+
on:
4+
schedule:
5+
- cron: "23 5 * * 1"
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: read
10+
11+
jobs:
12+
compat:
13+
if: github.repository == 'openclaw/openclaw' || github.event_name == 'workflow_dispatch'
14+
runs-on: ubuntu-24.04
15+
timeout-minutes: 60
16+
steps:
17+
- name: Checkout repository
18+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
19+
20+
- name: Setup Node environment
21+
uses: ./.github/actions/setup-node-env
22+
with:
23+
node-version: "22.19.0"
24+
install-bun: "false"
25+
26+
- name: Configure Node test resources
27+
run: echo "OPENCLAW_VITEST_MAX_WORKERS=2" >> "$GITHUB_ENV"
28+
29+
- name: Run Node 22 compatibility
30+
env:
31+
NODE_OPTIONS: --max-old-space-size=8192
32+
run: |
33+
pnpm build
34+
pnpm ui:build
35+
node openclaw.mjs --help
36+
node openclaw.mjs status --json --timeout 1
37+
pnpm test:build:singleton

docs/help/testing.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Most days:
3737

3838
When you touch tests or want extra confidence:
3939

40-
- Coverage gate: `pnpm test:coverage`
40+
- Informational V8 coverage report: `pnpm test:coverage`
4141
- E2E suite: `pnpm test:e2e`
4242

4343
## Test Temp Directories

docs/plugins/sdk-testing.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ patterns is recommended.
346346

347347
## Test configuration
348348

349-
OpenClaw uses Vitest 4 with V8 coverage thresholds. For plugin tests:
349+
OpenClaw uses Vitest 4 with informational V8 coverage reporting. For plugin tests:
350350

351351
```bash
352352
# Run all tests

docs/reference/test.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ Test wrapper runs end with a short `[test] passed|failed|skipped ... in ...` sum
8383
| `pnpm test:changed` | Cheap smart changed-test run: precise targets from direct test edits, sibling `*.test.ts` files, explicit source mappings, and the local import graph. Broad/config/package changes are skipped unless they map to precise tests. |
8484
| `OPENCLAW_TEST_CHANGED_BROAD=1 pnpm test:changed` | Explicit broad changed-test run; use when a test harness/config/package edit should fall back to Vitest's broader changed-test behavior. |
8585
| `pnpm test:force` | Frees the configured OpenClaw gateway port (default `18789`), then runs the full suite with an isolated gateway port so server tests do not collide with a running instance. |
86-
| `pnpm test:coverage` | Unit suite with V8 coverage (`vitest.unit.config.ts`). Default-unit-lane gate, not whole-repo coverage: `coverage.all` is `false` and thresholds are lines/functions/statements 70%, branches 55%, scoped to non-fast unit tests with sibling source files. |
86+
| `pnpm test:coverage` | Emits an informational V8 coverage report for the default unit lane (`vitest.unit.config.ts`); no coverage thresholds are enforced. |
8787
| `pnpm test:coverage:changed` | Unit coverage only for files changed since `origin/main`. |
8888
| `pnpm changed:lanes` | Shows the architectural lanes triggered by the diff against `origin/main`. |
8989
| `pnpm check:changed` | Delegates to Crabbox/Testbox by default outside CI, then runs the smart changed check gate inside the remote child: typecheck, lint, and guard commands for affected lanes. Does not run Vitest; use `pnpm test:changed` or `pnpm test <target>` for test proof. |

scripts/ci-changed-scope.d.mts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export type ChangedScope = {
77
runSkillsPython: boolean;
88
runChangedSmoke: boolean;
99
runControlUiI18n: boolean;
10+
runUiTests: boolean;
1011
};
1112

1213
export type InstallSmokeScope = {

scripts/ci-changed-scope.mjs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { appendFileSync } from "node:fs";
44
import { isDirectRunUrl } from "./lib/direct-run.mjs";
55
import { resolveMergeHeadDiffBase } from "./lib/merge-head-diff-base.mjs";
66

7-
/** @typedef {{ runNode: boolean; runMacos: boolean; runIosBuild: boolean; runAndroid: boolean; runWindows: boolean; runSkillsPython: boolean; runChangedSmoke: boolean; runControlUiI18n: boolean }} ChangedScope */
7+
/** @typedef {{ runNode: boolean; runMacos: boolean; runIosBuild: boolean; runAndroid: boolean; runWindows: boolean; runSkillsPython: boolean; runChangedSmoke: boolean; runControlUiI18n: boolean; runUiTests: boolean }} ChangedScope */
88
/** @typedef {{ runFastOnly: boolean; runPluginContracts: boolean; runCiRouting: boolean }} NodeFastScope */
99
/** @typedef {{ runFastInstallSmoke: boolean; runFullInstallSmoke: boolean }} InstallSmokeScope */
1010

@@ -17,6 +17,7 @@ const FULL_SCOPE = {
1717
runSkillsPython: true,
1818
runChangedSmoke: true,
1919
runControlUiI18n: true,
20+
runUiTests: true,
2021
};
2122

2223
const EMPTY_SCOPE = {
@@ -28,6 +29,7 @@ const EMPTY_SCOPE = {
2829
runSkillsPython: false,
2930
runChangedSmoke: false,
3031
runControlUiI18n: false,
32+
runUiTests: false,
3133
};
3234

3335
const DOCS_PATH_RE = /^(docs\/|.*\.mdx?$)/;
@@ -54,6 +56,8 @@ const TEST_ONLY_PATH_RE =
5456
/(^test\/|\/test\/|\/tests\/|(?:^|\/)[^/]+\.(?:test|spec|test-utils|test-support|test-harness|e2e-harness)\.[cm]?[jt]sx?$)/;
5557
const CONTROL_UI_I18N_SCOPE_RE =
5658
/^(ui\/src\/i18n\/|scripts\/control-ui-i18n\.ts$|\.github\/workflows\/control-ui-locale-refresh\.yml$)/;
59+
const CONTROL_UI_TEST_SCOPE_RE =
60+
/^(ui\/|test\/vitest\/vitest\.shared\.config\.ts$|scripts\/ensure-playwright-chromium\.mjs$)/;
5761
const NATIVE_I18N_SCOPE_RE =
5862
/^(?:apps\/\.i18n\/|apps\/android\/app\/src\/main\/|apps\/ios\/|apps\/macos\/Sources\/|apps\/shared\/OpenClawKit\/Sources\/|scripts\/(?:android-app-i18n|apple-app-i18n|native-app-i18n)\.ts$|test\/scripts\/(?:android-app-i18n|apple-app-i18n|native-app-i18n)\.test\.ts$|\.github\/workflows\/(?:ci|native-app-locale-refresh)\.yml$)/;
5963
const NATIVE_ONLY_RE =
@@ -92,6 +96,7 @@ export function detectChangedScope(changedPaths) {
9296
let runSkillsPython = false;
9397
let runChangedSmoke = false;
9498
let runControlUiI18n = false;
99+
let runUiTests = false;
95100
let hasNonDocs = false;
96101
let hasNonNativeNonDocs = false;
97102

@@ -153,6 +158,10 @@ export function detectChangedScope(changedPaths) {
153158
runControlUiI18n = true;
154159
}
155160

161+
if (CONTROL_UI_TEST_SCOPE_RE.test(path)) {
162+
runUiTests = true;
163+
}
164+
156165
if (!NATIVE_ONLY_RE.test(path)) {
157166
hasNonNativeNonDocs = true;
158167
}
@@ -171,6 +180,7 @@ export function detectChangedScope(changedPaths) {
171180
runSkillsPython,
172181
runChangedSmoke,
173182
runControlUiI18n,
183+
runUiTests,
174184
};
175185
}
176186

@@ -341,6 +351,7 @@ export function writeGitHubOutput(
341351
"utf8",
342352
);
343353
appendFileSync(outputPath, `run_control_ui_i18n=${scope.runControlUiI18n}\n`, "utf8");
354+
appendFileSync(outputPath, `run_ui_tests=${scope.runUiTests}\n`, "utf8");
344355
appendFileSync(outputPath, `run_native_i18n=${runNativeI18n}\n`, "utf8");
345356
}
346357

scripts/lib/extension-test-plan.mjs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import { spawnSync } from "node:child_process";
33
import fs from "node:fs";
44
import path from "node:path";
5-
import { channelTestRoots } from "../../test/vitest/vitest.channel-paths.mjs";
65
import { isAcpxExtensionRoot } from "../../test/vitest/vitest.extension-acpx-paths.mjs";
76
import { isActiveMemoryExtensionRoot } from "../../test/vitest/vitest.extension-active-memory-paths.mjs";
87
import { isBrowserExtensionRoot } from "../../test/vitest/vitest.extension-browser-paths.mjs";
@@ -67,7 +66,6 @@ const EXTENSION_TEST_COST_MULTIPLIERS = {
6766
"test/vitest/vitest.extensions.config.ts": 1.1,
6867
};
6968
const EXTENSION_TEST_CONFIG_ROUTES = [
70-
[(root) => channelTestRoots.includes(root), "test/vitest/vitest.extension-channels.config.ts"],
7169
[isActiveMemoryExtensionRoot, "test/vitest/vitest.extension-active-memory.config.ts"],
7270
[isAcpxExtensionRoot, "test/vitest/vitest.extension-acpx.config.ts"],
7371
[isBrowserExtensionRoot, "test/vitest/vitest.extension-browser.config.ts"],

scripts/run-vitest.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ const TOOLING_DOCKER_VITEST_CONFIG = "test/vitest/vitest.tooling-docker.config.t
3535
const TOOLING_VITEST_CONFIG = "test/vitest/vitest.tooling.config.ts";
3636
const GATEWAY_CORE_VITEST_CONFIG = "test/vitest/vitest.gateway-core.config.ts";
3737
const GATEWAY_VITEST_CONFIG = "test/vitest/vitest.gateway.config.ts";
38-
const VITEST_CONFIG_NO_OUTPUT_TIMEOUT_MS = new Map([
38+
export const VITEST_CONFIG_NO_OUTPUT_TIMEOUT_MS = new Map([
3939
["test/vitest/vitest.e2e.config.ts", DEFAULT_LONG_RUNNING_VITEST_NO_OUTPUT_TIMEOUT_MS],
4040
[GATEWAY_VITEST_CONFIG, DEFAULT_LONG_RUNNING_VITEST_NO_OUTPUT_TIMEOUT_MS],
4141
["test/vitest/vitest.ui-e2e.config.ts", DEFAULT_LONG_RUNNING_VITEST_NO_OUTPUT_TIMEOUT_MS],
@@ -51,7 +51,7 @@ const VITEST_CONFIG_NO_OUTPUT_TIMEOUT_MS = new Map([
5151
["test/vitest/vitest.infra.config.ts", DEFAULT_EXTRA_LONG_RUNNING_VITEST_NO_OUTPUT_TIMEOUT_MS],
5252
[GATEWAY_CORE_VITEST_CONFIG, DEFAULT_EXTRA_LONG_RUNNING_VITEST_NO_OUTPUT_TIMEOUT_MS],
5353
]);
54-
const TOOLING_EXCLUDED_TESTS = new Set([
54+
export const TOOLING_EXCLUDED_TESTS = new Set([
5555
...boundaryTestFiles,
5656
"test/scripts/docker-build-helper.test.ts",
5757
"test/scripts/openclaw-e2e-instance.test.ts",

0 commit comments

Comments
 (0)