Skip to content

feat(rstest): add @midscene/rstest package#2450

Draft
fi3ework wants to merge 3 commits into
web-infra-dev:mainfrom
fi3ework:feat/rstest-integration
Draft

feat(rstest): add @midscene/rstest package#2450
fi3ework wants to merge 3 commits into
web-infra-dev:mainfrom
fi3ework:feat/rstest-integration

Conversation

@fi3ework
Copy link
Copy Markdown
Member

@fi3ework fi3ework commented May 8, 2026

Summary

Introduce @midscene/rstest — Midscene exposed as native rstest fixtures. Destructure agent, page, browser, agentForPage directly from the test signature; the package handles browser lifecycle, per-test agent construction, and per-file HTML report merging.

import { test as base } from '@midscene/rstest/playwright';
import { expect } from '@rstest/core';

const test = base.extend({ url: 'http://localhost:5173/' });

test('adds a todo', async ({ agent }) => {
  await agent.aiAct("type 'Study AI' in the input and press Enter");
  await agent.aiAssert('the list contains exactly one item: "Study AI"');
});

Two browser providers ship behind subpath exports:

  • @midscene/rstest/playwright
  • @midscene/rstest/puppeteer

Fixtures

Fixture Notes
midsceneOptions headless / viewport / launchOptions / contextOptions / gotoOptions / agentOptions. Repo-wide defaults via defineMidsceneDefaults in a setupFile; per-file override via test.extend({ midsceneOptions }).
url Target URL the default page fixture navigates to. Empty string disables auto-navigation.
browser File-scoped, lazy — not launched unless something destructures it (or anything depending on it).
context (playwright only) Fresh BrowserContext per test. Override for custom storage / state.
page Auto-navigated to url. Override the fixture for custom page lifecycles (persistent context, trace orchestration, …). Code after await use(page) is teardown.
agent PlaywrightAgent / PuppeteerAgent bound to page. Report collection automatic.
agentForPage(page, opts?) Build secondary agents for popups / additional contexts. Reports merged alongside the primary's.

Reporter

MidsceneReporter prints Midscene report: midscene_run/report/<file>.html after each test file. Wire into reporters: ['default', new MidsceneReporter()] in rstest.config.ts.

Notes for reviewers

  • rstest 0.9.9 doesn't export TestAPIs / TestContext / Fixtures — `packages/rstest/src/test-api-types.ts` mirrors the public surface from public types only, so the emitted `.d.ts` doesn't leak unexported internals. Delete the file when rstest exposes those types.
  • Browser singleton is promise-based to stay correct under `test.concurrent` (two concurrent tests would otherwise both see `_browser === null` and double-launch).
  • `__reportMeta` private fixture is shared between `agent` and `agentForPage` so the timestamp baked into report filenames stays consistent within one test. Hidden from the public type via the final cast in each provider.
  • `isolate: true` is required (rstest default). The package keeps file-scoped state at module top-level, which only works because each test file evaluates its own module graph.

Validation

  • `pnpm -w run lint`
  • `npx nx typecheck @midscene/rstest`
  • `npx nx test @midscene/rstest` — 18 / 18
  • `npx nx build @midscene/rstest` (d.ts inspection confirms `__reportMeta` is omitted from public types)

Test plan

  • `npx rstest run` against the example demo project
  • Midscene HTML report path is printed after each test file
  • `test.extend({ page })` with `chromium.launchPersistentContext` works end-to-end
  • Puppeteer subpath works without Playwright installed
  • `test.concurrent` doesn't double-launch the browser
  • A file with only non-AI tests doesn't launch a browser at all (lazy guarantee)

fi3ework added 3 commits May 21, 2026 15:44
Introduce @midscene/rstest, a Rstest plugin that drives a Midscene
Agent through a familiar describe / it flow. Each it block gets a
fresh agent; a custom reporter prints the merged Midscene HTML report
path after each test file finishes.

Two browser providers ship behind subpath exports:
- @midscene/rstest/playwright
- @midscene/rstest/puppeteer

createWebTest(url, options?) returns { agent } valid inside it(...).
Options pass directly through to the underlying provider — only
headless and viewport are curated as convenience fields:

- headless / viewport: shortcuts for the highest-frequency knobs.
- launchOptions / contextOptions / gotoOptions: resolvers that forward
  to Playwright / Puppeteer's own option types. Object form
  shallow-merges over midscene defaults; function form
  (defaults) => options takes full control.
- agentOptions: forwarded to PlaywrightAgent / PuppeteerAgent.
- setup: escape hatch for lifecycles the defaults don't cover
  (persistent context, CDP connect, fixture reuse, custom trace/video
  orchestration).
…face

Surface raw browser primitives and a secondary-agent factory on
`WebTestContext`, matching the native @midscene/web/playwright fixture:

- `ctx.page` — raw Playwright/Puppeteer Page (test-scoped)
- `ctx.browser` — file-scoped Browser for spinning up extra contexts
- `ctx.agentForPage(page, opts?)` — wrap any page in a midscene agent;
  its report is auto-merged with the primary's at afterEach

Re-export `overrideAIConfig` and `WebPageAgentOpt` from each provider
entry so programmatic config overrides don't need a second import from
`@midscene/web/*`.

Drop the public `AgentBundle` / `PageBundle` types in favor of an
internal `TestFixture` shape; the lifecycle bundle is now a
provider-implementation detail. Rename the demo to `playground.test.ts`
to match the monorepo's `demo/playground.*` convention.

Docs (en + zh) gain a new "Configure midscene" section covering env
vars, `agentOptions`, `overrideAIConfig`, and `DEBUG=midscene:*,rstest:*`,
plus examples for the new context surface.
Reshape the public API from ambient `createWebTest(url)` hooks to
rstest fixtures destructured straight off the test signature:

- `agent`, `page`, `browser`, `context`, `agentForPage` are typed
  fixtures composed via dependency injection.
- File-scoped browser is launched lazily — files that don't reach
  through to `agent` skip browser startup entirely.
- Per-file URL via `test.extend({ url })`; custom page lifecycles
  via overriding the `page` fixture (replaces the `setup` callback).
- Promise-based browser singleton avoids the TOCTOU race that would
  double-launch under `test.concurrent`.
- Shared `__reportMeta` private fixture stabilizes the timestamp
  embedded in primary + secondary report filenames within one test.

`test-api-types.ts` works around rstest 0.9.9 not exporting its
`TestAPIs` / `TestContext` / `Fixtures` types — delete the file once
they become public.

Drops `createWebTest` and `registerLifecycle` exports.
@fi3ework fi3ework force-pushed the feat/rstest-integration branch from 337625c to b3c5101 Compare May 21, 2026 10:41
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