Extend and maintain Test Runner
@midscene/test supports registering business Nodes, managing runtime resources, and defining execution lifecycles. Framework maintainers can use these capabilities to integrate browsers, Agents, external tools, and business APIs into a customized testing foundation for their teams.
For an overview of the design, see Test Runner overview.
Quick start
The following example shows how to set up a simple test project that switches the browser's user-agent language.
1. Install dependencies
Create an empty project and install the Test Runner's core dependencies and driver tools:
Note: Before using a Midscene Agent, follow Model configuration to set the required model environment variables, such as your API Key.
2. Create the project files
We recommend the following basic directory structure:
3. Configure the Test Project and register Nodes
Create midscene.config.ts in the project root. This file registers reusable Nodes and defines the execution environment (Project):
4. Write and run a test case
Create cases/midscene.yaml:
Run the test from the project root:
The runner automatically loads midscene.config.ts, finds matching test cases, and runs them.
Register custom business Nodes
Use defineNode() to encapsulate complex API calls, database operations, cleanup tasks, or specialized browser interactions as named Nodes. Test case authors can then call these Nodes directly from their test cases.
Basic business Node example
The following example wraps an HTTP API that creates a test order:
After adding the Node to the configuration's nodes array, test case authors can use it directly:
Define and strictly validate input with Zod
Although inputSchema is optional, we strongly recommend defining it.
- Type inference and validation: the runner validates the input with Zod before entering
execute(). Invalid input immediately throws aNodeInputValidationError, while TypeScript provides compile-time type inference without a separate interface. - Unknown parameter rejection: use
z.strictObject()so the runner rejects unexpected fields in a test case. - Automatic reference generation: information from each field's
.describe()call is included in the generated Node reference for AI Agents and human test case authors.
Node execution context
The ctx passed to execute(ctx) contains these commonly used fields:
input: business parameters passed from YAML and validated by Zod.$: general Step properties controlled by the runner, such as normalizedtimeoutandcontinue-on-errorvalues.signal: anAbortSignaltriggered by a timeout or cancellation. Use it in asynchronous requests or long-running tasks to exit early and cleanly.context: Project-level runtime resources returned bydefineProjectSetup()and shared within the Project.history: deeply read-only, JSON-compatible history of executed Nodes. AI Agent Nodes automatically read this field to understand context.onTeardown(): registers cleanup functions for resources created by the current Node. Cleanup can use attempt or Document scope and runs in LIFO order.scope: identifies the current Node execution boundary as eithercaseordocument.caseordocument: detailed runtime information for the current execution position.
Share state across Nodes
In real-world tests, multiple Nodes often need to share state. For example, an order refund test can create an order and store its ID in beforeEach, access the ID from steps, and clean up the data in afterEach.
Define state properties in a custom ProjectContext to enable this coordination:
Integrate a Midscene Agent
@midscene/test/midscene exports six built-in system Nodes: aiAct, aiAssert, recordToReport, launch, wait, and agent.
Call createMidsceneNodes() with a getAgent callback to integrate them into your Test Runner configuration:
createMidsceneNodes() keeps launch for compatibility with existing Agent
integrations. Android and iOS projects should use the lifecycle Nodes owned by
their platform preset and set includeLaunch: false here to avoid registering
launch twice.
Register platform preset Nodes
The Test Runner publishes platform preset factories as separate entry points. Each factory receives getters instead of assuming property names in your Project Context.
For Playwright, register gotoUrl, setCookies, clearCookies, and
setViewportSize. The playwright package is an optional peer dependency of
@midscene/test; install it in projects that use this preset:
Then create the preset Nodes:
setCookies does not accept cookie values in YAML. Test Runner persists every
Node input in the run result and workflow history. An inline cookie would be
copied into those records.
Use exactly one of cookiesEnv, profile, or storageStatePath as a cookie
reference. The Node resolves the actual cookies only at execution time and
passes them directly to the Playwright BrowserContext. Its result contains only
the reference name and cookie count. Cookie names, values, and scopes are not
written to the run result or workflow history.
An environment variable may contain a Cookie header, a JSON cookie array, or
Playwright storage-state JSON. Relative storage-state paths resolve from the
current working directory by default; use resolveStorageStatePath when a
project needs a different root. References prevent Test Runner from persisting
the cookies, but the environment variable, profile, or storage-state file must
still be protected. Do not commit storage-state files containing real cookies.
gotoUrl follows Playwright's navigation semantics. When navigation completes,
HTTP 4xx and 5xx responses are returned as successful Node results with their
status code, so later steps can assert the error page. Network errors and
navigation timeouts still fail the Node.
For Android, the platform preset registers launch, terminate, and
runAdbShell. Its Agent contract requires all three capabilities:
For iOS, the platform preset registers launch, terminate, and
runWdaRequest. Its Agent contract requires all three capabilities:
runAdbShell and runWdaRequest preserve their complete response in the Node
result and workflow history. Test Runner limits only the history representation
passed to later Midscene Agent calls: oversized values become bounded previews
with their original character count, and recent entries take priority when the
total context is too large. Use command-side filtering when the complete output
is not needed in the run result.
launch and gotoUrl are intentionally not aliases. launch manages an app,
URL, or URI through a device Agent. gotoUrl navigates the current Playwright
Page and supports Web-specific baseUrl, lifecycle, and HTTP response
semantics.
Generate a Node reference
The runner provides the describe-nodes tool so test case authors, including AI Agents, can clearly discover the registered Nodes and their parameter schemas. It compiles each Node's title, description, and Zod inputSchema into a standard Markdown reference.
Run the following command to generate a reference for your team:
You can also specify a test directory or a custom configuration file:
The generated reference includes every Node registered in the active Test Project, including the six built-in system Nodes returned by createMidsceneNodes(), sorted by name. The runner automatically converts each Zod inputSchema to standard JSON Schema.
Configure and manage a Test Project
defineTestProject() is the main configuration entry point for the testing foundation and supports one or more Execution Projects:
Key configuration strategies
- Environment and resource isolation: each Execution Project has an independent
setupenvironment, such as a separate browser or a specific test device. - Multi-Project concurrency and lifecycle slots:
test.maxConcurrencycontrols the number of active Projects. The default is1.- One concurrency slot covers the entire lifecycle from Project setup through teardown.
- Within a single Project, all Workflow Documents, Cases, and Steps still run strictly in sequence to ensure deterministic tests.
- To drive multiple mobile devices or browser instances at the same time, declare multiple entries in
projectsand increase the concurrency value.
- Lifecycle cleanup: use
defineProjectSetup()to define environment preparation. Register cleanup hooks withonTeardown()so that long-lived resources are safely released in reverse LIFO order even if a test is interrupted or fails partway through.
Programmatic APIs
Most teams only need the project configuration, YAML files, and CLI. To embed the runner in another tool, such as a local GUI test panel, use these exported programmatic APIs:
loadTestProject(): asynchronously loads the TypeScript project configuration frommidscene.config.ts. The runner does not support synchronous loading.runTestProject(): asynchronously discovers, runs, and summarizes the entire project. It is exported from@midscene/test/config.CaseRunner/createCaseRunner(): directly runs one test case represented as a plain object, without file parsing or lifecycle management.runWorkflowDocument(): runs the complete lifecycle and every Case in one document.
After configuring the project, continue to Write and run test cases for test case syntax and parameters.

