Skip to content

chore(workflow): migrate unit test runner from vitest to rstest#2698

Open
fi3ework wants to merge 17 commits into
web-infra-dev:mainfrom
fi3ework:midscene-dev2
Open

chore(workflow): migrate unit test runner from vitest to rstest#2698
fi3ework wants to merge 17 commits into
web-infra-dev:mainfrom
fi3ework:midscene-dev2

Conversation

@fi3ework

@fi3ework fi3ework commented Jun 22, 2026

Copy link
Copy Markdown
Member

What

Migrate the repository's own unit-test tooling from vitest to rstest (@rstest/core 0.11.0) across every package and app.

  • Each project gets an rstest.config.ts (replacing vitest.config.ts).
  • Shared boilerplate — coverage, dotenv loading, jsdom storage polyfill, style stubs, @silvia-odwyer/photon externals, and the __VERSION__ define — is factored into scripts/rstest-*.ts instead of being copy-pasted per package.
  • vitest / @vitest/* are fully removed from every package.json and the lockfile. The only remaining vitest string is the // @vitest-environment jsdom docblock (24 files), which rstest natively honors.

Rebased onto latest main

This branch was rebased onto the latest main (41 commits ahead of the original base). The rebase pulled in 17 new test files and 7 auto-merged files that upstream had authored against vitest; all were migrated to rstest as part of this branch.

Upgraded to rstest 0.11.0 and pruned workarounds

Bumped @rstest/core 0.10.6 → 0.11.0. The following workarounds the earlier migration carried are no longer needed and were removed after verifying in-repo:

Workaround (was needed on 0.10.6) Fixed by
rs.useFakeTimers({ toFake: ['Date'] }) before setSystemTime rstest#1463 — setSystemTime now works standalone
join(__dirname, …) instead of new URL('./x', import.meta.url) for fixtures rstest#1464 — new URL()/wasm resolve from on-disk source
regex-on-src assertion for an inlined svg asset rstest#1464 (same)
moving test { timeout } options to the 3rd arg rstest#1453 — it(name, options, fn) (options as 2nd arg) accepted again
explicit rs.mock('x', { mock: true }) rspack#14418 — bare rs.mock('x') now auto-automocks

Also adapted to a 0.11.0 breaking change: dropped the removed pool.minWorkers field (rstest#1451) from the CLI yaml runner (packages/cli/src/framework/rstest-runner.ts).

Known rstest limitations still worked around (kept, re-verified on 0.11.0)

These are genuine rstest gaps that still reproduce on 0.11.0; the workarounds remain, each with a TODO pointing at the tracking issue:

  1. Variable dynamic imports escape rs.mockrs.mock() is applied by an rspack build-time transform, so source that does const m = 'undici'; await import(m) (used to dodge bundler static analysis) loads the real module at runtime and the mock never applies. Affected: apps/studio launch-electron (switched to static string-literal imports), and skipped assertions in core proxy-configuration and ios agent (device-class override). Re-verified: the mocked ProxyAgent received 0 calls. → [Bug]: rs.mock is bypassed for modules loaded via dynamic import() with a non-literal specifier rstest#1454
  2. Mocking a dep used transitively inside an externalized workspace pkg — a rs.mock() on @midscene/shared/env doesn't reach the env singleton read by @midscene/core/utils when that package is externalized, so the spy is never hit. Affected: apps/studio keeps output.bundleDependencies: true; skipped legacy-cache assertions in cli create-yaml-player and one playground server-interact case (the equivalent assertions run in focused suites that mock only @midscene/shared/env). Re-verified: spy received 0 calls. → [Bug]: rs.mock() does not prevent evaluation of an externalized dependency (real module still runs) rstest#1456

No pre-existing test was newly skipped by this migration beyond the two rstest gaps above.

Validation

  • pnpm run lint (biome check + check-dependency-version) — pass
  • build (nx run-many --target=build, 27 projects) — pass
  • unit tests — every package's suite verified green on 0.11.0 individually (core, shared, web-integration 362, studio 330, computer, visualizer, webdriver, android, ios, harmony, cli, playground family, report app, playground-app). Note: under a 17-way nx run-many fan-out, core report-merge-count can flake on temp-dir/timing contention (heavy filesystem I/O); it passes standalone and within the full core suite.

https://claude.ai/code/session_018fJUtMHskx7MtXcnWBELwE

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d9b3ffaae3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/evaluation/rstest.config.ts Outdated
Comment on lines +14 to +16
coverage: createCoverageConfig(__dirname),
testEnvironment: 'node',
include: ['tests/**/*.test.ts'],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Externalize photon in evaluation rstest config

This node-target config is the only Rstest config for suites importing @midscene/shared/img that does not apply photonExternal. The evaluation tests import saveBase64Image / imageInfoOfBase64, which pulls in shared/src/img/get-photon and its @silvia-odwyer/photon dynamic import; without output.externals, Rspack tries to bundle that native/wasm package and the run can fail before tests start. Add output: { externals: photonExternal } here as in the other node test configs.

Useful? React with 👍 / 👎.

Comment thread packages/evaluation/rstest.config.ts Outdated
Comment on lines +14 to +16
coverage: createCoverageConfig(__dirname),
testEnvironment: 'node',
include: ['tests/**/*.test.ts'],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restore root .env loading for evaluation tests

The deleted Vitest config loaded ../../.env, but this replacement config no longer runs the repo-level dotenv setup. When running the package scripts such as pnpm --filter @midscene/evaluation evaluate:locator with model credentials only in the workspace .env, the Rstest worker will not inherit MIDSCENE_MODEL_*; the test files' own dotenv.config() resolves from packages/evaluation, so globalModelConfigManager.getModelConfig() can fail with missing model configuration. Add the same globalSetup used by the other AI-backed Rstest configs (Rstest documents that globalSetup env mutations are propagated to node workers: https://rstest.rs/config/test/global-setup).

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 95f0b74ba5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/core/package.json Outdated
},
"devDependencies": {
"@rslib/core": "^0.18.3",
"@rstest/core": "0.10.6",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep the test runner within the advertised Node range

This migrated package now depends on @rstest/core@0.10.6, but the lockfile for that package declares engines: {node: ^20.19.0 || >=22.12.0} while the workspace still advertises Node >=18.19.0. On a documented Node 18.x development environment, pnpm --filter @midscene/core test now invokes a runner outside the supported engine range; either bump the repo's Node requirement everywhere or use a runner version compatible with Node 18.

Useful? React with 👍 / 👎.

import { getModelRuntime } from '@/ai-model/models';
import { globalModelConfigManager } from '@midscene/shared/env';
import { expect, test, vi } from 'vitest';
import { expect, rs, test } from '@rstest/core';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Move per-test options after the callback

After switching this file to @rstest/core, the existing test(..., { timeout }, async () => ...) calls below no longer match Rstest's documented signature, which takes the callback second and options third. When AITEST=true runs the core AI tests, these cases will be registered with the options object in the callback slot instead of exercising AiLocate*; move the async function to the second argument and the timeout object to the third (or rely on rs.setConfig).

Useful? React with 👍 / 👎.

export default function setup() {
dotenv.config({
path: path.resolve(__dirname, '..', '.env'),
override: true,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve shell env overrides when loading .env

With override: true, this global setup replaces values already provided by the shell or CI whenever a workspace .env exists. That regresses the previous non-core Vitest configs where MIDSCENE_MODEL_* or related credentials passed inline (for example to compare models locally) won over .env; AI tests will silently use the stale file values inside Rstest workers. Drop the override unless a specific suite needs file values to take precedence.

Useful? React with 👍 / 👎.

Migrate the unit test runner from vitest to @rstest/core 0.11.0 and remove
workarounds that 0.11.0 fixed.

Removed workarounds (fixed upstream in rstest 0.11.0):
- setSystemTime now works without a prior useFakeTimers (rstest#1463).
- new URL(import.meta.url) fixtures and svg assets resolve from on-disk
  source, so the join(__dirname) and regex-src workarounds are dropped
  (rstest#1464).
- TestOptions is accepted as it()'s second argument again, so report
  timeout options move back to their vitest position (rstest#1453).
- Bare rs.mock('x') auto-automocks, so { mock: true } is dropped
  (rspack#14418).

Kept workarounds (still required on 0.11.0, verified):
- Sync mock factories sourcing actuals via import attributes, since async
  factories are now a hard error (rstest#1459).
- Skips for variable dynamic imports that bypass rs.mock (rstest#1454) and
  for mocking deps used inside externalized workspace packages (rstest#1456);
  studio keeps output.bundleDependencies for the same reason.

Also adapt to 0.11.0 breaking changes: drop pool.minWorkers (rstest#1451)
in the CLI yaml runner.

Claude-Session: https://claude.ai/code/session_018fJUtMHskx7MtXcnWBELwE

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2e2a42e493

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

include: testFiles,
testTimeout: 3 * 60 * 1000, // Global timeout set to 3 minutes
retry: process.env.CI ? 1 : 0, // Retry failed tests once in CI to handle AI flakiness
errors: process.env.CI ? { unhandled: false } : undefined, // showcase.test.ts is not stable

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use a supported Rstest option for unhandled errors

When CI runs the web AI suite, this no longer suppresses unhandled errors: @rstest/core@0.11.0's public config does not define an errors key, so Rstest ignores this value. The previous Vitest config used dangerouslyIgnoreUnhandledErrors, so showcase.test.ts-style unhandled errors can now fail the run despite the comment; the same migrated key also appears in the android/computer/harmony/ios configs.

Useful? React with 👍 / 👎.

Follow-up to the vitest->rstest migration: fix the type errors surfaced by
the `type-check:tests` step (tsc --noEmit over every test file).

- tsconfig: drop the `module: ES2020` override in android / android-playground
  / harmony so they inherit `ESNext` from the base config, which is required
  for the `with { rstest: 'importActual' }` import attributes.
- merge-reports / report-merging: migrate to rstest's TestResult API
  (`ctx.task.result?.state` -> `.status`; compute duration from the test's own
  local `startTime` instead of the removed `result.startTime`).
- android page.test.ts: spread the `importActual` namespace directly for the
  mocked `node:fs` default export (the namespace has no `.default`).
- studio launch-electron.test.ts: annotate the untyped `.mjs` dynamic imports
  with `@ts-expect-error`.
- coerce `skipIf(string | undefined)` conditions to boolean (rstest's `skipIf`
  is typed stricter than vitest's).
- move `describe` option objects from the 3rd argument to the 2nd
  (`describe(name, options, fn)`); rstest only accepts a number as the 3rd arg.

Claude-Session: https://claude.ai/code/session_018fJUtMHskx7MtXcnWBELwE

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5255c70522

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +18 to 20
rs.mock('@rstest/core', () => ({
test: mocks.rstestTest,
}));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not mock @rstest/core in an Rstest file

When the CLI unit suite runs this file under Rstest, this mock targets the same module that supplies the file's own beforeEach, describe, expect, rs, and test imports. Rstest documents that rs.mock is hoisted and the module is mocked even for imports written before the mock, so the returned object with only test can replace the runner API used to register these tests, leaving describe/expect/rs undefined or test as the stub instead of the real runner. Use an importActual partial mock or inject the fake test without mocking the runner module itself.

Useful? React with 👍 / 👎.

… fixes

Temporarily override @rstest/core to the pkg.pr.new preview (@b704252) that
carries the fixes for the two mock-typing bugs the migration surfaced
(rstest#1514 toHaveBeenCalledBefore/After, rstest#1515 Mocked<T> for
constructable+callable members with a `this` parameter). This clears the
`type-check:tests` errors in android page.test.ts and playground
server-interact.test.ts.

Revert this override to a normal @rstest/core release once the fixes ship.

Claude-Session: https://claude.ai/code/session_018fJUtMHskx7MtXcnWBELwE

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5575666b14

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

testTitle: `${ctx.task.name}`,
testDescription: 'description',
testDuration: (Date.now() - ctx.task.result?.startTime!) | 0,
testDuration: (Date.now() - startTime) | 0,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use the same clock for test duration

When the Android AI merge-report test finishes, startTime comes from performance.now() but this subtracts it from Date.now(). That mixes monotonic and epoch clocks, so the duration is roughly the current epoch time and the | 0 truncates it to a bogus 32-bit value in the merged report metadata. Use performance.now() - startTime here, as the web merge-report test does, or record startTime with Date.now().

Useful? React with 👍 / 👎.

@@ -54,7 +54,7 @@ describe('Test Setting', () => {
testId: `${ctx.task.name}`,
testTitle: `${ctx.task.name}`,
testDescription: 'description',
testDuration: (Date.now() - ctx.task.result?.startTime!) | 0,
testDuration: (Date.now() - startTime) | 0,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use the same clock for test duration

When the Harmony AI merge-report test finishes, startTime comes from performance.now() but this subtracts it from Date.now(). That mixes monotonic and epoch clocks, so the duration is roughly the current epoch time and the | 0 truncates it to a bogus 32-bit value in the merged report metadata. Use performance.now() - startTime here, as the web merge-report test does, or record startTime with Date.now().

Useful? React with 👍 / 👎.

fi3ework added 2 commits July 9, 2026 11:32
The drag test came in from upstream (web-infra-dev#2766) still written for vitest; port it
to rstest (`vitest` -> `@rstest/core`, `vi.*` -> `rs.*`) so the merged branch
type-checks and runs under the new test runner.

Claude-Session: https://claude.ai/code/session_018fJUtMHskx7MtXcnWBELwE

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e2a1a68711

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@@ -0,0 +1,19 @@
import path from 'node:path';
import dotenv from 'dotenv';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Make dotenv resolvable for the root setup

When any config that lists this file in globalSetup runs, Rstest loads the setup module from scripts/ before starting workers. This new root-level module imports dotenv, but the workspace root package.json does not declare dotenv (it is only declared by individual packages), so a clean pnpm install will not expose a /workspace/midscene/node_modules/dotenv symlink for this importer; suites such as core/cli/computer can fail during global setup with Cannot find module 'dotenv' before tests start. Add dotenv to the root devDependencies or avoid importing it from the root script.

Useful? React with 👍 / 👎.

The v8 coverage provider (@rstest/coverage-v8) retains per-file v8
coverage within a single worker, so memory grows with the number of
test files sharing that worker. @midscene/computer runs its 13 unit-test
files in one worker (pool.maxWorkers: 1) and the coverage run OOMs —
SIGSEGV in CI, JavaScript heap OOM locally — while the same suite passes
without coverage. rstest recommends the istanbul provider, whose coverage
processing keeps memory flat (rstest#1154); computer coverage now passes
at the default heap.

Reported the v8 provider issue upstream: web-infra-dev/rstest#1524.

Pin @rstest/coverage-istanbul to the same pkg.pr.new preview build as
@rstest/core; revert both overrides to released versions once rstest
ships the fixes.

Claude-Session: https://claude.ai/code/session_018fJUtMHskx7MtXcnWBELwE

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ab85c6bc2a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread package.json Outdated
Comment on lines +77 to +78
"@rstest/core": "https://pkg.pr.new/web-infra-dev/rstest/@rstest/core@b704252",
"@rstest/coverage-istanbul": "https://pkg.pr.new/web-infra-dev/rstest/@rstest/coverage-istanbul@b704252"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Pin Rstest to released packages

This override makes every clean pnpm install resolve both Rstest packages from the pkg.pr.new preview tarballs instead of the released 0.11.0 versions declared in the package manifests. In CI or developer environments that only allow the npm registry, or if the preview artifact/service becomes unavailable, installs fail before any build or test can run; it also means the repo is testing against unreleased code that is not represented by the dependency specs. Please use a published Rstest version or remove the preview override before merging.

Useful? React with 👍 / 👎.

The pinned pkg.pr.new preview (@rstest/coreb704252) regressed JSX to the
classic runtime for files whose environment is overridden via a
`@vitest-environment` docblock, breaking React component tests with
"React is not defined" (web-infra-dev/rstest#1530, introduced by rstest
web-infra-dev#1475). Released 0.11.0 already ships the coverage-istanbul concurrency
fix, so drop both preview overrides and fall back to the stable release.

Suppress the two type-only fixes lost by the downgrade with
@ts-expect-error, tracked to rstest#1530:
- Mocked<T> assignability to T (android page.test.ts)
- call-order matcher accepting a Mock arg (playground server-interact)

Claude-Session: https://claude.ai/code/session_018fJUtMHskx7MtXcnWBELwE

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6cb6fd6358

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

import { tmpdir } from 'node:os';
import { dirname, join } from 'node:path';
import { describe, expect, it } from 'vitest';
import { describe, expect, it } from '@rstest/core';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Move report timeouts to Rstest's supported position

When this migrated suite runs under @rstest/core@0.11.0, the two existing it(..., { timeout }, async () => ...) cases in this file (the 100-report merge and large-inline-image merge tests) are parsed with the options object in the callback slot, so those regression callbacks are not exercised correctly. Move the async callback to the second argument and put the timeout after it, or configure the timeout for the file.

Useful? React with 👍 / 👎.

fi3ework added 2 commits July 10, 2026 11:23
Picks up the fix where a test case that crashes its worker is now
reported as a failed test case (web-infra-dev/rstest#1535).

Claude-Session: https://claude.ai/code/session_018fJUtMHskx7MtXcnWBELwE
# Conflicts:
#	packages/android/tests/unit-test/page.test.ts
#	packages/core/tests/unit-test/action-param-validation.test.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fb27fd6483

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

globalSetup: [
path.resolve(__dirname, '../../scripts/rstest-dotenv-setup.ts'),
],
include: enableAiTest ? ['tests/ai/**/**.test.ts'] : basicTest,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Migrate the remaining Vitest imports

With this default Rstest include, pnpm --filter @midscene/core test still picks up files that import vitest (rg "from 'vitest'" --glob '*.{ts,tsx,mjs}' finds inspect-multi-frame, recorder-frame-sequence, ui-observer, agent-ui-observer, and dump-screenshot-sequence under packages/core/tests/unit-test). Since this commit removes all vitest package entries from the workspace manifests, those tests fail at module resolution before the suite can run; migrate the remaining imports to @rstest/core or exclude them until converted.

Useful? React with 👍 / 👎.

@@ -1,14 +1,14 @@
import type { ExecutorContext } from '@midscene/core';
import { describe, expect, it, vi } from 'vitest';
import { describe, expect, it, rs } from '@rstest/core';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Replace the leftover vi factories

This import no longer provides vi, but the later passes action-level keyboardTypeDelay case in this same file still calls vi.fn() for keyboard.type and schedulePendingVisualUpdate. When the web-integration unit suite reaches that case, it will throw ReferenceError: vi is not defined; convert those two remaining factories to rs.fn() with the rest of the file.

Useful? React with 👍 / 👎.

fi3ework added 8 commits July 10, 2026 11:57
Upstream added new test files that still imported from 'vitest'; port
them to '@rstest/core' (vi -> rs). Rewrite the async mock factory in
inspect-multi-frame.test.ts to a sync factory with a
`with { rstest: 'importActual' }` import, which rstest requires.

Also drop the now-unused `@ts-expect-error` directives that worked
around the Mocked<T> regression; @rstest/core 0.11.1 fixes it, so the
directives are flagged as unused by the type checker.

Claude-Session: https://claude.ai/code/session_018fJUtMHskx7MtXcnWBELwE
# Conflicts:
#	packages/cli/tests/unit-test/config-factory.test.ts
#	packages/cli/tests/unit-test/create-yaml-player.test.ts
#	packages/web-integration/tests/ai/web/puppeteer/tab-navigation.test.ts
#	packages/web-integration/tests/ai/web/puppeteer/test-utils.ts
#	packages/web-integration/tests/unit-test/puppeteer/agent-launcher.test.ts
#	packages/web-integration/tests/unit-test/yaml/utils.test.ts
…ssion

@rstest/core@0.11.1 drops the pluginReact automatic JSX runtime for files
whose test environment is selected via a per-file docblock (node -> jsdom),
so their JSX compiles to classic React.createElement and throws
'React is not defined' at render time. Whole-file describe.skip until the
rstest fix lands.

Also add RSTEST-MIGRATION-WORKAROUNDS.md as the central registry of all
rstest-migration skips/workarounds (JSX runtime, externalized-dependency
mocks web-infra-dev#1456, variable dynamic imports web-infra-dev#1454), each tied to a TODO(rstest)
marker in the code.

Claude-Session: https://claude.ai/code/session_018fJUtMHskx7MtXcnWBELwE
Istanbul instruments source by injecting cov_*() counters. When a function
defined in an instrumented file (e.g. src/puppeteer/base-page.ts) is passed
to Puppeteer's page.evaluate, it is serialized into the browser context where
cov_* is undefined, throwing 'ReferenceError: cov_… is not defined' and
skewing timing enough to break race-sensitive tests. The v8 provider uses the
runtime profiler without rewriting source, matching the vitest behavior these
suites relied on. Adds the @rstest/coverage-v8 dev dependency.

Claude-Session: https://claude.ai/code/session_018fJUtMHskx7MtXcnWBELwE
checkComputerEnvironment() and listDisplays() call @computer-use/libnut,
which invokes X11 XOpenDisplay. On a headless Linux runner (no DISPLAY) the
native call segfaults and hard-crashes the worker — uncatchable from JS.
Guard both cases with it.skipIf(linux && !DISPLAY); they still run locally
and on any runner with a real display.

Claude-Session: https://claude.ai/code/session_018fJUtMHskx7MtXcnWBELwE
…rumentation

Reverts the switch to the v8 provider (9f207ee): v8 OOMs the computer
package's single-worker coverage run, which is why istanbul was chosen.

Instead fix the 'cov_… is not defined' failures at the source: istanbul
injects module-scoped cov_*() counters, and base-page.ts hands closures to
Puppeteer/Playwright page.evaluate, which serializes them into the browser
realm where cov_* is undefined. Excluding base-page.ts from coverage skips
its instrumentation (it is the only file in the repo that passes inline
closures to .evaluate), so no test is skipped and the provider stays istanbul.

Claude-Session: https://claude.ai/code/session_018fJUtMHskx7MtXcnWBELwE
…tanbul hints

Under the istanbul coverage provider, `base-page.ts` closures handed to
Puppeteer/Playwright `page.evaluate` are serialized via `toString()` and run in
the browser realm, where istanbul's module-scoped `cov_*` counter does not
exist — throwing `ReferenceError: cov_… is not defined`.

The previous fix added the whole file to `coverage.exclude`, which dropped
~15% of @midscene/web's coverage (base-page.ts is ~1360 lines of Node-side
logic on top of ~10 tiny browser closures). Replace that blanket exclude with
`/* istanbul ignore next */` on each of the 10 statements that pass an inline
closure to `.evaluate(`, so istanbul skips instrumenting only those closure
subtrees while the rest of the file stays measured. Verified in a sandbox that
`@rstest/coverage-istanbul` (swc-plugin-coverage-instrument) honors the hint,
and in-repo that the coverage run no longer throws `cov_*` and base-page.ts is
measured again (~52% statements).

The provider stays `istanbul` on purpose — v8 OOMs @midscene/computer's
single-worker coverage run (web-infra-dev/rstest#1524).

Claude-Session: https://claude.ai/code/session_018fJUtMHskx7MtXcnWBELwE

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2b61b18f66

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

coverage: createCoverageConfig(__dirname),
globals: true,
testEnvironment: 'node',
include: ['tests/**/*.test.ts'],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add Photon externals to playground tests

When pnpm --filter @midscene/playground test runs, this include picks up suites such as tests/unit/recorder-ui-describer.test.ts and server tests that import @midscene/shared/img; that pulls in shared/src/img/get-photon with Photon native/wasm dynamic imports. Unlike the other node Rstest configs, this one does not set output.externals: photonExternal, so Rspack can try to bundle those Photon packages and fail before the playground suite executes. Add the shared Photon externalization here.

Useful? React with 👍 / 👎.

@quanru

quanru commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Created upstream CI relay PR #2775 at the exact source SHA 2b61b18f663dedf021d3017583dd9c92bbd81a8d so the full secret-backed workflow set can run.

Please continue review and merge on this original PR. The relay PR is draft-only and must not be merged.

@quanru

quanru commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Full upstream CI has finished on relay PR #2775 at the exact source SHA 2b61b18f663dedf021d3017583dd9c92bbd81a8d. The model API key was injected and masked, and the other model settings were non-empty, so these results are no longer affected by fork secret isolation.

Passed:

  • Lint
  • CI
  • Headless Linux (all 6 jobs)
  • Studio Headless Linux
  • AI Playwright e2e-web (basic, cache, and report stages)
  • Cloudflare Pages

Failed:

  • AI Playwright e2e-report: apps/report/e2e/detail-panel.yaml failed its aiAssert because the model saw UI Context as active instead of the expected Screenshots tab. The first failure hit the bail limit, so the remaining 6 files did not run.
  • AI unit: the only failed Nx target was @midscene/computer:test:ai. The runner reported Could not open main display; 12 test-file workers exited with SIGSEGV. Other AI test targets passed.

Runs:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants