Skip to content

feat(testing-framework): config-driven framework suite runner (#2509 PR1)#2570

Closed
quanru wants to merge 6 commits into
mainfrom
codex/testing-framework-config-runner
Closed

feat(testing-framework): config-driven framework suite runner (#2509 PR1)#2570
quanru wants to merge 6 commits into
mainfrom
codex/testing-framework-config-runner

Conversation

@quanru

@quanru quanru commented May 29, 2026

Copy link
Copy Markdown
Collaborator

Summary

Implements the Framework Config Runner MVP from the UI Testing Framework topic (#2509). Adds a new self-contained package @midscene/testing-framework that turns a midscene.config.ts plus natural-language YAML cases into an Rstest run.

Scope is strictly limited to what #2509 and the midscene-example codex/add-framework-setup-demo demos describe. No midscene test/midscene emit command, no extra DSL/watch/reporters, and no changes to the existing YAML CLI.

What's included

  • defineMidsceneConfig — type-only helper that returns the config unchanged.
  • runMidsceneSuite — loads the config, discovers cases, runs them through Rstest, writes the summary. Designed so a project's run-suite.ts collapses to await runMidsceneSuite().
  • Config loading & validation of the documented fields: target, testDir, include, exclude, testRunner, output, agentOptions, setup, yamlSteps. Errors on missing testDir/include, on target + setup together, and on custom steps that override built-in step names.
  • Case discovery via glob: include/exclude, .yaml/.yml/.test.ts, stable sort.
  • Default targets: web (PlaywrightAgent: launch → context(viewport) → goto(url) → agent, teardown closes context/browser) and android (agentFromAdbDevice with deviceId/launch/device-option passthrough). Custom setup({ projectDir, agentOptions }) takes precedence.
  • Custom yamlSteps interleaved with built-in steps in authored order; multi-key built-in steps (e.g. aiInput + value) are handled; unknown steps throw a clear error; state is shared across the suite.
  • Rstest integration: each YAML case maps to one Rstest test, generated into a single virtual suite module so the agent is created once (beforeAll), torn down once (afterAll, even on failure), and state is shared. Reuses the same runRstest + VirtualModulesPlugin mechanism as the existing CLI YAML runner instead of a bespoke concurrent runner. testRunner.{maxConcurrency,bail,testTimeout,retry} map to Rstest config.

Design notes

  • The package is self-contained and does not depend on @midscene/cli: the long-term direction is cli → testing-framework (the planned emit PR), so a reverse dependency would create a cycle. The existing packages/cli/src/framework/* YAML path is left untouched.
  • @rstest/core is a peer dependency (*); the pkg.pr.new preview is only a devDependency for local validation and never a published hard dependency. @rsbuild/core is resolved at runtime relative to the consumer's @rstest/core.

Validation

  • npx nx build testing-framework — ✅ (incl. d.ts)
  • packages/testing-framework unit tests — ✅ 33/33 (config load/validate, discovery, web/android setup, custom setup, yamlSteps ordering/unknown/state, Rstest param mapping, summary)
  • Real end-to-end through actual Rstest (mock agent fixture): each YAML case ran as a real Rstest test; execution trace confirmed suite-level setup once, built-in via runYaml, custom steps invoked, shared state, and teardown once — including teardown still running when every case fails, with exitCode=1 and a failed summary.
  • npx nx test cli — ✅ 148/148 (existing midscene ./script.yaml / --config semantics unchanged; no CLI source touched)
  • pnpm run lint — ✅

Out of scope / follow-ups

  • PR2: midscene emit ./project-folder — emit an explicit Rstest project and have the CLI consume this package.
  • Replace the @rstest/core pkg.pr.new preview with a published version before release (consumer/emitted projects).
  • output.reportDir is typed for fidelity with the doc but not yet consumed (reports come from agentOptions); case.test.ts has minimal discovery + type entry only, no extra DSL.
  • Migrating the midscene-example demos' run-suite.ts to runMidsceneSuite() lives in the example repo.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented May 29, 2026

Copy link
Copy Markdown

Deploying midscene with  Cloudflare Pages  Cloudflare Pages

Latest commit: 69db689
Status: ✅  Deploy successful!
Preview URL: https://459eacf5.midscene.pages.dev
Branch Preview URL: https://codex-testing-framework-conf.midscene.pages.dev

View logs

@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: 5b9258795a

ℹ️ 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 +117 to +119
await options.agent.runYaml(
dumpSingleStepTask(`${options.caseName}:${stepName}`, step),
);

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 Preserve YAML variables across built-in steps

When yamlSteps is configured, every built-in step is sent through a separate agent.runYaml() call here. runYaml() creates a fresh ScriptPlayer each time, and ScriptPlayer resolves $variables from its per-player result, so a built-in step like aiQuery: ...; name: product followed later by aiAssert: "$product ..." fails with the variable undefined as soon as any custom step handler is registered. This regresses normal YAML flows that work through runBuiltinYamlCase; the split execution needs to carry the accumulated result forward or avoid splitting built-in segments that depend on each other.

Useful? React with 👍 / 👎.

@quanru quanru marked this pull request as draft May 29, 2026 06:54
Base automatically changed from codex/rstest-yaml-runner to main June 1, 2026 07:37
quanru added 4 commits June 1, 2026 16:47
Add the `@midscene/testing-framework` package implementing the Framework
Config Runner MVP from #2509:

- `defineMidsceneConfig` and `runMidsceneSuite`
- load `midscene.config.ts` (testDir/include/exclude, testRunner, output,
  agentOptions, target, setup, yamlSteps) with validation
- YAML case discovery (glob include/exclude, .yaml/.yml/.test.ts, stable sort)
- default web (PlaywrightAgent) and android (agentFromAdbDevice) targets,
  plus fully custom setup
- custom yamlSteps interleaved with built-in steps in authored order
- each YAML case runs as one Rstest test via runRstest + virtual modules,
  with suite-level setup/teardown and shared state
When a project's midscene.config.ts imports playwright (or
@midscene/web/playwright), playwright's bundled @vitest/expect copy
defines Symbol.for('$$jest-matchers-object') on globalThis as a
non-configurable property. Calling runRstest in the same process then
fails with TypeError: Cannot redefine property because Rstest's own
@vitest/expect copy tries to redefine the same symbol.

Spawn a child process via child_process.fork() that loads runRstest
fresh, never having imported playwright. The parent forwards stdout
lines while reserving a sentinel-prefixed line for the structured run
result.
Mirror @midscene/cli behaviour so a project's midscene.config.ts can
rely on env-driven model configuration without the user having to
source .env manually before tsx run-suite.ts.

Defaults: look for .env in cwd and the config directory, skip missing
files silently, do not override existing process.env. The new
config.env field opts a project out (enabled: false) or points the
loader at a custom list of files (path).
After rebasing onto main (which introduced new top-level packages like
@rstest/core via #2537), pnpm install regenerated entries for the
testing-framework's @rstest/core dependency that were lost during the
lockfile auto-merge.
@quanru quanru force-pushed the codex/testing-framework-config-runner branch from 5eadd1c to 0c666f0 Compare June 1, 2026 09:15
quanru added 2 commits June 2, 2026 11:18
…ted"

The worker silenced all rstest reporters to keep stdout clean, which also
hid per-test errors. When a case never produced its own result file
(e.g. a `beforeAll` hook threw before any case ran), the framework only
reported "Not executed" with no clue about the real cause.

Walk `result.files[]` to extract both case-level and suite-level errors
(tagged with `kind`), forward them through `WorkerOutput.testErrors`, and
fall through case match -> suite errors -> unhandledErrors in
`readCaseResult` so failures always carry a real stack.
Replace runMidsceneSuite + fork worker with an in-process runMidsceneTest:
the runner only resolves the config path, while the user's midscene.config
(which may import Playwright) is imported solely inside the Rstest worker via
a generated bootstrap module, so Playwright's @vitest/expect global no longer
collides with Rstest's copy and forking is unnecessary.

- registerMidsceneSuite (mode A bootstrap) discovers cases with top-level
  await and registers one test per YAML case against a shared agent
- defineMidsceneCaseTest + emitRstestProject export a native Rstest project
- runCase returns its result instead of throwing; summary counts a retried
  case once
- add midscene-testing-framework bin (test/emit)
@quanru

quanru commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator Author

已被 #2582 取代:#2582 是重整后的干净单提交版本(原生 in-process rstest、删除 standalone bin、README 仅保留 midscene emit)。本 PR 的旧散碎历史(含 fork 方案与 midscene-testing-framework bin)不再需要,故关闭。后续在 #2582 / #2583 / #2584 跟进。

@quanru quanru closed this Jun 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant