Configure test projects
midscene.config.ts is the project configuration file for Midscene Test. Use it to configure browser or device initialization, register Nodes for test cases to call, and set execution options such as concurrency and timeouts. This guide covers configuration structure, Agent and platform integration, multiple execution projects, and programmatic execution.
Configuration structure
In midscene.config.ts, use defineTestProject() to define and export your project configuration. An Execution Project selects cases and supplies their runtime environment.
Create browser or device resources in setup and register the corresponding Agent Nodes through nodes. These determine the capabilities available to each execution project.
The example below starts with a single execution project. To manage multiple execution projects or register different Nodes for each project, see Configure multiple execution projects.
Configure the runtime environment
Complete example: Playwright
The complete configuration below shows how to create a browser page, connect a Midscene Agent, and register built-in Nodes for an execution project. After creating a project with the scaffold, use this example to understand and adjust midscene.config.ts.
To add Midscene Test to an existing project manually, first install @midscene/test, @midscene/web, and playwright (pnpm add -D @midscene/test @midscene/web playwright), then run pnpm exec playwright install chromium to install the browser. Before running tests, follow Model configuration to set the required environment variables, including your API Key.
The configuration connects these parts:
setupcreates the browser and page and registers cleanup functions throughonTeardown().contextholds runtime resources. Nodes obtain the Agent throughgetAgent, which creates an instance on first use and stores it incontext.agentfor subsequent calls. Playwright Nodes use that Agent’s page.nodesregisters common AI operations and Playwright operations for YAML cases to call.projectsdeclares an execution project namedchromium, connecting itssetupto the YAML files undercases/.
You can now write YAML test cases and run tests. The next section explains how these resources are shared and cleaned up.
Create and clean up shared resources with setup
Each execution project runs setup once before executing its YAML files. All Nodes in the project share the returned context. In the example above, the context holds the browser and page, and Nodes access the resources they need through the getAgent callback.
Shared resources are not automatically recreated before each case. To reset pages, cookies, or business state, call the appropriate Nodes through YAML lifecycle hooks. For reading and updating shared data in custom Nodes, see Share context across Nodes.
After creating a browser, Agent, or other resource, immediately register its cleanup function with onTeardown(). The framework attempts the registered cleanup functions when the project finishes, even if setup or test execution fails. Cleanup runs in reverse registration order: in the Playwright example above, the Agent is destroyed before the browser closes.
The onTeardown() callbacks registered here run when the project finishes. Cleanup functions registered inside Nodes have their own execution scope.
Project lifecycle
Project, file, and case lifecycle
When running tests through the CLI or runTestProject(), each execution project has its own lifecycle. The following shows normal execution for a project with two YAML files. YAML hooks that are not declared are skipped:
setup belongs to the execution project and runs once before its YAML files execute. beforeAll and afterAll belong to an individual YAML file, running before and after that file's cases. They are not global hooks for the entire project. beforeEach and afterEach surround each attempt of each case.
If a case fails and retries remain, the framework finishes that attempt's afterEach and Node cleanup before repeating beforeEach → steps → afterEach → Node cleanup. Retries do not rerun project setup or file beforeAll. See Handling failures for skip and cleanup rules. If setup fails, the project's YAML files do not execute, but the framework still attempts cleanup callbacks already registered in setup.
The scope of onTeardown() depends on where it is registered:
Within each scope, callbacks run in reverse registration order. See Resource lifecycle and cleanup for implementing Node cleanup.
Context sharing scope
The context returned by setup is reused throughout the current execution project. Nodes in all YAML files, lifecycle hooks, cases, and retries receive the same object. The framework does not copy, clear, or recreate it between files, cases, or retries.
For example, after a Node writes an order ID to context.orderId, later Nodes can read it, including Nodes in the next case. Reset case-specific data in preparation steps or remove it during cleanup. A retry does not automatically restore the initial context. Resetting resources such as browser pages and login state also depends on the project implementation.
Each execution project calls its own setup. Even if projects reference the same setup definition, they invoke it separately, so create resources and the returned object inside the setup function. Returning the same mutable object from module scope would share state across projects. For a complete example, see Share context across Nodes.
Register built-in Nodes
Common AI operations and Agent integration
Use createMidsceneNodes() from @midscene/test/midscene to register the following common Nodes: aiAct, aiTap,
aiAssert, aiBoolean, aiNumber, aiString, aiAsk, recordToReport,
and wait.
When calling createMidsceneNodes(), use agentClass to specify the Agent class and a getAgent callback to supply the Agent instance at runtime:
agentClass is the sole source of Agent-backed Node definitions. The factory throws during registration if the class does not expose
getTestRunnerNodeDefinitions(). PlaywrightAgent, PlaywrightPageAgent, PlaywrightBrowserAgent, AndroidAgent, IOSAgent, and HarmonyAgent register their common and platform Node definitions together. The base Agent, other Web Agents, and ComputerAgent only provide common Agent Nodes.
For an Android/iOS registration example, see Configure multiple execution projects.
When integrating an Agent on device platforms such as Android or iOS, observe resource ownership. Each device instance belongs to one Agent. Agent.destroy() also destroys its device. Create separate resources for each execution project, and do not reuse a device after its Agent is destroyed.
Register each platform through createMidsceneNodes(): specify its Agent class with agentClass and supply the instance through getAgent. You can choose your own field names for the project context.
Once registered, these Nodes can be called from YAML. See Calling other Nodes for parameter syntax, and consult the project-generated Node reference for specific fields.
Playwright
Use createMidsceneNodes({ agentClass: PlaywrightAgent, getAgent }) to automatically register gotoUrl, setCookies, clearCookies, and setViewportSize. Before using these Nodes, install playwright in your project. It is a peer dependency of @midscene/web:
Cookies are read from process.env by default. Only configure Agent testRunner options such as getEnv, getCookieProfile, or resolveStorageStatePath when you need custom cookie sources. The context in these callbacks is the current Agent.
setCookies does not accept cookie values in YAML. Midscene Test persists every
Node input in the run result. An inline cookie would be copied into that
record.
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.
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 Midscene Test 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.
For navigation parameters and URL resolution, see Navigate with gotoUrl.
Android
To use the device presets below, declare an agent of the corresponding platform type in ProjectContext and include that instance in the object returned by setup. Each platform example is independent; choose the one you need.
Use createMidsceneNodes({ agentClass: AndroidAgent, getAgent }) to register launch, terminate, runAdbShell, back, home, and recentApps. The supplied Agent must provide the methods these Nodes call:
The complete response from runAdbShell is saved in the Node result.
iOS
Use createMidsceneNodes({ agentClass: IOSAgent, getAgent }) to register launch, terminate,
runWdaRequest, home, and appSwitcher:
The complete response from runWdaRequest is saved in the Node result.
HarmonyOS
Use createMidsceneNodes({ agentClass: HarmonyAgent, getAgent }) to register launch, terminate,
runHdcShell, back, home, and recentApps:
The complete response from runHdcShell is saved in the Node result.
Configure execution projects
An execution project connects a runtime environment to its cases. Even a single execution project can configure case selection, variables, and retries. Declare multiple execution projects to run across browsers, devices, or environments.
Select cases and control execution
See Run tests for CLI project selection and configuration file options, and Configure timeouts and error handling for step overrides.
The scaffold generates a configuration that selects cases/**/*.{yaml,yml} by default. Without a files configuration, Midscene Test recursively searches for **/*.{yaml,yml} under the test directory.
Configure multiple execution projects
To run tests across different browsers or devices, configure multiple Execution Projects with defineTestProject().
Nodes registered in the top-level nodes field apply to every Project; the field defaults to [] when omitted. Within an individual Project, you can also register local Nodes using the nodes field alongside setup and files. A local Node applies only to that Project and replaces the entire global definition with the same name. Other global Nodes remain available. Duplicate names within the same registration layer cause an error.
When configuring both Android and iOS projects, register each platform Agent's official Nodes within its Project. The example below imports two independent setups from ./setup, each managing its own resources and returning { agent }. It imports shared business Nodes from ./nodes:
Both Projects can use the same YAML file. During case collection, input validation, and test execution, launch and other platform Nodes use the definitions that apply to the current Project. A Project's local Nodes do not affect other Projects.
Concurrency and isolation
The scheduling unit is an execution project. test.maxConcurrency limits the number of active projects and defaults to 1. YAML files, cases, and steps within a project run sequentially. Increasing this setting does not make cases within a single project concurrent.
To run different cases concurrently, assign them to multiple projects using directories or tags, and create separate resources for each project. Reuse the setup and Node definitions from the Playwright example above and replace its defineTestProject() configuration with the following to run checkout and account cases concurrently:
Both projects call playwrightSetup separately, creating their own browser, page, and context. The checkout project runs checkout cases sequentially, and the account project runs account cases sequentially, while the two projects can progress concurrently. For device tests, concurrent projects should connect to different devices to avoid controlling the same device at the same time.
The framework does not automatically divide cases between projects. If two projects match the same YAML file, each project runs that file once. This is useful for validating the same cases on different platforms or environments. To distribute cases for concurrent execution, use non-overlapping files or tags selection rules.
A project occupies a concurrency slot from initialization through cleanup. Once a slot is released, the scheduler starts the next pending project. Scheduling currently uses asynchronous execution within the same Node.js process, without creating a separate process per project. Project implementations must still isolate module globals, external accounts, and test data.
Generate a Markdown reference for each Project
Run midscene-test nodes to generate a Markdown reference of the available Nodes. Use --project to select an execution project and list the Nodes available after combining its global and local registrations:
Without a selector, nodes generates a shared reference only when every Project has the same effective Node set. If the sets differ, it reports an error asking you to select a Project instead of merging potentially different definitions with the same name.
For reference contents and general command options, see Read the project operation reference.
Timeouts and cancellation
A step timeout aborts that step's signal. When running tests through the CLI, SIGINT (for example, pressing Ctrl+C) or SIGTERM aborts the run and forwards cancellation to the currently executing steps.
Nodes receive cancellation notifications through AbortSignal. Asynchronous operations inside a Node do not stop automatically when the signal is aborted. Custom Nodes need to pass signal to APIs that support cancellation or explicitly check its state. See Asynchronous operations, errors, and cancellation for usage.
After a run is interrupted, the framework still attempts to run afterEach, afterAll, and cleanup functions registered with onTeardown(). When executing Nodes in afterEach and afterAll, if the run's signal has already been aborted, the framework uses a new signal that has not been aborted so cleanup operations can continue. Step timeout settings still apply to these cleanup steps.
Cleanup functions registered with onTeardown() do not receive a new signal. Avoid reusing the original step's signal in these functions: a timeout or run interruption may have already aborted it, causing cleanup requests to fail immediately.
Programmatic APIs
You can also use APIs to integrate Midscene Test into other tools, such as a local test panel with a graphical interface. The following APIs let you load project configuration, run an entire project, or execute an individual case:
loadTestProject(): asynchronously loads the TypeScript project configuration frommidscene.config.ts. Midscene Test does not support synchronous loading.runTestProject(): asynchronously discovers, runs, and summarizes the entire project.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.

