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
Use defineTestProject() to define and export your project configuration. An Execution Project selects cases and supplies their runtime environment.
Midscene Test supports the following configuration files:
Midscene Test loads midscene.config.ts or midscene.config.mjs by default. If both files exist, it reports a conflict instead of choosing one silently; use --config <path> to select the intended file explicitly.
Prepare shared project resources in setup, prepare resources for each file in fileSetup, and register the corresponding Agent Nodes through nodes.
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
This sequential configuration uses only setup: all files in the execution project share one page. Add fileSetup when each file needs its own environment, even without concurrency.
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
defineProjectSetup() defines project preparation, assigned through the setup field. During a test run, each execution project calls its setup() function once. The example above returns a browser and page for the project's Nodes to share.
For file isolation, define per-file preparation with defineFileSetup() and assign it through fileSetup. It receives the project setup's return value and creates resources for the current file. See Isolate resources with fileSetup for the relationship and an example.
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
setup, fileSetup, and beforeAll
They run in the order setup → fileSetup → beforeAll → cases. Unconfigured parts are skipped.
In a normal run of two YAML files, setup runs once, while fileSetup and beforeAll each run twice. Concurrency limits do not change these counts.
Configure fileSetup once for all files; declare beforeAll in each file. Custom Nodes in beforeAll can also create resources, but this does not make the framework create an independent file context.
Project, file, and case lifecycle
When running tests through the CLI or runTestProject(), each execution project has its own lifecycle. The following shows normal sequential execution for a project with two YAML files. YAML hooks that are not declared are skipped:
beforeAll and afterAll each run once per YAML file; beforeEach and afterEach surround every case attempt. With file concurrency enabled, files can advance together, while each file follows the order shown above.
Case retries reuse the file environment and repeat only beforeEach → steps → afterEach → Node cleanup for this attempt. Project setup, fileSetup, and beforeAll do not rerun. See Handling failures for retry conditions and skip rules.
The scope of onTeardown() depends on where it is registered:
Within each scope, callbacks run in reverse registration order. If one throws, remaining callbacks are still attempted; if it has not finished, later cleanup waits for it. See Resource lifecycle and cleanup for implementing Node cleanup.
Context sharing scope
The configuration determines which context Nodes receive:
Nodes within the same sharing scope receive the same object. A value written to context.orderId can remain visible to later cases. The framework does not reset the context, page, or login state between cases or retries; restore the required initial state in beforeEach.
Create resources and the returned object inside the setup() function. Projects or files invoke the function separately even when they reference the same definition; returning a mutable object from module scope would still share state. See Share context across Nodes for a data-passing example.
Register official Nodes
Common AI operations and Agent integration
The Midscene Test kernel does not register Nodes implicitly. Configuration generated by midscene-test create calls createMidsceneNodes() from @midscene/test/midscene to register official Nodes for the selected platform. In handwritten configuration, explicitly add the returned definitions to top-level nodes or projects[].nodes.
createMidsceneNodes() returns the common and platform Node definitions declared by agentClass.getTestRunnerNodeDefinitions(), plus the Test-provided wait Node. Common capabilities cover AI operations, assertions and extraction, interface location, and report recording. Platform Agents add browser or device operations. The website does not maintain a duplicate exhaustive list; use the current Project's generated Node Spec for exact names and inputs.
When calling createMidsceneNodes(), use agentClass to specify the Agent class and a getAgent callback to supply the Agent instance at runtime:
createMidsceneNodes() does not mutate a global registry; its Nodes become available only after the returned array is included in project configuration. agentClass is the sole source of Agent-backed Node definitions, while getAgent describes how execution obtains an Agent instance from the current context. The factory throws during registration if the class does not expose getTestRunnerNodeDefinitions(). PlaywrightAgent, PlaywrightPageAgent, PlaywrightBrowserAgent, PuppeteerAgent, PuppeteerPageAgent, PuppeteerBrowserAgent, AndroidAgent, IOSAgent, and HarmonyAgent provide common and platform Node definitions together. The base Agent 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 Spec 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.
See the current project's Node Spec for navigation inputs.
Puppeteer
Puppeteer exposes the same gotoUrl, setCookies, clearCookies, and setViewportSize Node names. Install the puppeteer peer dependency and pass PuppeteerAgent (or PuppeteerBrowserAgent) to createMidsceneNodes():
The Playwright and Puppeteer integrations use the same Web Node schemas and execution implementation. Browser-specific code is limited to a driver adapter. For portable YAML, use domcontentloaded, load, or networkidle for gotoUrl.waitUntil; the Puppeteer adapter maps networkidle to networkidle0. Existing engine-specific values remain supported: Playwright accepts commit, while Puppeteer accepts networkidle0 and networkidle2. Using one of those values with the other driver produces an explicit error. Cookie references and result redaction also behave the same on both integrations. Relative URLs resolve against testRunner.baseURL, then against the current HTTP(S) page URL.
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.
Selected files are deduplicated and sorted by path. See Project lifecycle for execution order and retry scope, and Concurrency and isolation for concurrency settings.
Native Test always generates a combined report. The CLI prints its path after the run. See View test results for how to open it.
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.
Generate a Node Spec for each Project
Run midscene-test nodes to generate a Node Spec of the available Nodes. Use --project to select an execution project and list the Nodes available after combining its global and local registrations:
When projects is declared, omitting --project generates a separate Spec for each Execution Project, even when their Node definitions are identical.
For filenames, output rules, Spec contents, and command options, see Generate the current Project's Node Spec.
Concurrency and isolation
Two limits apply together: maxConcurrentFiles limits files across the whole run, and maxConcurrentProjects limits projects whose preparation has started but whose cleanup has not finished. Each defaults to 1 independently. Set either to null to remove that limit; numeric values must be positive integers.
Execution is asynchronous within one Node.js process. Raising only the project limit does not raise file concurrency. Removing limits does not guarantee a faster run: browser capacity, devices, and model service quotas still matter.
File concurrency with maxConcurrentFiles
For example, with test: { maxConcurrentFiles: 3, maxConcurrentProjects: 2 } and Web and Android file limits of 2 each, two Web files and one Android file can run together, or one Web file and two Android files. The project count does not multiply the global file limit.
A file occupies a slot from preparation through completed cleanup, including device waits and case retries. Starting a new project claims both a project slot and its first file slot before setup; other files in that project wait for initialization without taking additional slots. Eligible projects take turns admitting files.
A project keeps its slot between files, even when none is currently running. Its last file retains a file slot through project cleanup; the project slot is released only when cleanup actually finishes. With maxConcurrentProjects: 1, the next project starts after the previous one is fully cleaned up. Setting only maxConcurrentFiles: 1 allows files from different projects to interleave if the project limit permits it.
After cancellation or bail, cleanup for initialized projects with no active files is also scheduled within the global file limit. See Timeouts and cancellation.
Isolate resources with fileSetup
Use setup for shared project resources and fileSetup for resources owned by each file. Web tests can share one Browser while creating a separate BrowserContext, Page, and Agent per file to isolate cookies and other browser state.
The YAML uses the prepared Agent: beforeAll sets the viewport, and beforeEach opens the home page for every case:
This example creates the Agent in fileSetup. File cleanup destroys the Agent and then closes the BrowserContext. The shared Browser closes after all file cleanup.
The type parameters in defineFileSetup<FileContext, SharedContext> describe the file return value and the project return value, respectively. fileSetup returns the complete file context; it is not automatically merged with projectContext. Explicitly return shared fields that Nodes need.
The preparation function receives these arguments:
For a single configuration, omit projects and put setup and fileSetup at the top level. If a file can prepare all its resources itself, configure only fileSetup.
On Android, iOS, and other device platforms, allocate an exclusive device to each concurrent file and create a separate Device/Agent. Each Device instance belongs to one Agent, and Agent.destroy() destroys that Device; return the device allocation after destroying the Agent. Project code supplies the device pool and business-state reset. With one phone or one desktop, set that project's limit to 1; multiple Agents operating the same device still interfere with each other.
See Project lifecycle for context scope and cleanup order, and Handling failures for preparation failures.
Nodes or fileSetup must explicitly report shared-resource failures; Browser closure messages alone do not establish ownership. See Asynchronous operations, errors, and cancellation for how to report them.
Existing configurations
test.maxConcurrency has been removed; loading an old configuration reports an error with a migration example. It controlled project concurrency, so simply renaming it to maxConcurrentFiles changes behavior. To preserve an old limit of 2, set test: { maxConcurrentProjects: 2, maxConcurrentFiles: 2 } and each project's maxConcurrentFiles to 1. Without explicit projects, keep the global file limit at 1.
fileSetup, project file limits, and preparation/cleanup timeouts can be configured without explicitly setting either global limit. When a project's effective file limit is 1, fileSetup is optional and its files can share the setup return value.
Cases are not automatically divided between projects. If two projects select the same YAML file, each runs it once. Use non-overlapping files or tags rules to split the work.
Legacy tasks/flow YAML still runs through midscene. Its concurrent / --concurrent, setup-file behavior, and browser reuse remain unchanged; those settings are handled separately from native Test's setup and fileSetup. See Migrate to Midscene Test.
Timeouts and cancellation
Timeout settings
Preparation and cleanup limits also apply when concurrency settings are omitted. After cancellation or bail, cleanup scopes share a shutdown deadline based on teardownTimeout.
Cancellation and cleanup
A step timeout aborts that step's signal. The CLI cancels the run on SIGINT (such as Ctrl+C) or SIGTERM. Nodes must pass signal to APIs that support cancellation or check its state; asynchronous operations do not stop automatically. See Asynchronous operations, errors, and cancellation.
After interruption, the framework still attempts cleanup. Nodes in afterEach and afterAll use a cleanup signal that has not been aborted. Step timeouts still apply, and cleanup cannot overlap an unfinished operation on the same Agent. Cleanup Nodes should handle partially completed preparation.
Callbacks registered with onTeardown() do not receive a new signal. Avoid reusing the original step's signal, which may already be aborted.
Timeouts bound the wait for results. If operations or cleanup remain unfinished, the framework records cleanup pending, keeps tracking resources, and releases them after operations actually finish. No new files start in this state. See Handling failures for the full rules.
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(path): asynchronously loads an explicitly provided TypeScript (.ts) or JavaScript ESM (.mjs) project configuration. Without--config, the CLI discoversmidscene.config.tsormidscene.config.mjs; if both exist, it asks you to select one explicitly. 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.
Usage notes
- Configure what you need. Use only
fileSetupwhen there are no shared resources. With an effective file limit of1, omit it to share thesetupresult across files. - Concurrent files need isolation. An effective file limit above
1, including unlimited, requiresfileSetup, even if only one file is selected. Use separate pages, Agents, and devices; isolate backend accounts and data separately. - Retries do not repeat preparation.
setup,fileSetup, andbeforeAlldo not rerun. Reset state needed for each attempt inbeforeEach. - Register cleanup immediately. Call
onTeardown()after creating each resource. File cleanup precedes shared project cleanup; cleanup code must tolerate partial preparation.

