编写自定义 Node
自定义 Node 将业务操作封装为用例作者可以在 YAML 中调用的步骤。本文介绍输入定义与校验、跨 Node 数据共享,以及执行与清理。
尚未创建测试项目时,请先阅读创建和使用测试项目。平台环境、Agent 接入和运行参数见配置测试项目。
- 注册 Node:定义操作、在 YAML 中调用,并通过生成 Markdown 说明书验证注册。
- Node 执行参数:区分业务参数与框架的步骤配置。
- 跨 Node 共享上下文:创建、读取和重置共享测试数据。
- 进阶用法:返回结果、处理错误与取消,以及清理资源。
注册自定义业务 Node
自定义 Node 从 YAML 接收参数并执行操作。下面以创建测试用户数据为例,将用户信息写入 JSON 文件,供测试服务或数据导入步骤加载。
1. 定义并注册 Node
创建 midscene.config.ts:
name 是 YAML 中使用的操作名称。inputSchema 定义参数,execute({ input }) 接收校验后的参数值。将 Node 加入 nodes 数组后,用例就可以调用它。扩展已有项目时,将它追加到原有的 nodes 数组即可。
inputSchema 是可选字段,但定义后,Midscene Test 会在调用 execute() 前校验参数。示例中的 z.string().min(1) 要求姓名非空,.email() 校验邮箱格式,z.strictObject() 拒绝未知字段。输入不符合要求时,会抛出 NodeInputValidationError。
TypeScript 根据 schema 推导 input 的类型。.describe() 中的字段说明会出现在生成的 Node 说明书中。
2. 在 YAML 中调用
创建 cases/user.yaml:
Midscene Test 读取 YAML,找到名为 user.create 的 Node,并调用它的 execute()。此时 input 为 { name: "Alice", email: "alice@example.com" },Node 无需自行解析 YAML 文件。
示例使用 async execute({ input }) 和 await 等待文件写入。 写入失败时会抛出错误,使 Node 执行失败。这里创建的是本地测试数据文件;需要在业务系统中创建用户时,将文件写入替换为测试数据接口或数据库调用即可。
3. 生成 Markdown 说明书,验证注册
保存配置后,生成 Markdown 格式的 Node 说明书:
命令加载项目配置,并在当前目录生成 midscene-node-reference.md。检查说明书是否包含:
- 可用 Node 列表中的
user.create。 - “创建测试用户数据文件。”这一操作说明。
name、email两个输入字段及其描述。
这一步验证 Node 是否已注册、输入 schema 是否可以导出,不会执行 Node 或创建用户数据。修改 Node 定义或注册配置后,可以重新生成说明书,供用例作者和 AI Agent 查阅。
Node 执行参数
Midscene Test 调用 execute() 时,会传入包含本次执行参数和运行信息的对象。可以通过 execute({ input, $, context }) 这样的解构写法,直接取出需要的字段。
业务参数与步骤配置
input 包含 Node 的 inputSchema 定义的业务参数。$ 包含框架处理的步骤配置,例如超时时间和发生错误后是否继续执行。
例如,为前面的 user.create 调用添加步骤配置:
框架将 $ 与业务参数分开,并规范化其中的字段名。在 execute({ input, $ }) 中,可以读取到:
inputSchema 只需声明 name 和 email,input 中不包含 $。超时和出错后是否继续执行由框架控制。continue-on-error 允许当前阶段的后续步骤在失败后继续执行,但失败的步骤仍会使整个用例失败。
可用的执行字段
execute() 接收的参数对象包含以下常用字段:
input:从 YAML 传入并经过 Zod 校验后的业务参数。$:由 Midscene Test 控制的通用 Step 属性(如规范化后的timeoutMs和continueOnError)。signal:超时或运行取消时触发的AbortSignal,在异步请求或长耗时任务中使用它以响应取消。context:在defineProjectSetup()中返回并共享的项目级运行时资源。onTeardown():注册当前 Node 所创建资源的清理函数,支持 attempt 级别或 Document 级,按 LIFO(后进先出)顺序执行。scope:标记当前 Node 执行的上下文边界,值为case或document。case或document:当前执行位置的详细运行信息。
其中,context 用于访问项目共享的资源与状态。下一节介绍如何通过它在多个 Node 之间共享数据。
跨 Node 共享上下文
Node 的每次执行都是独立调用,框架不会自动将上一次执行的结果传入下一次调用。当一个业务流程由多个 Node 完成时,它们可能需要使用同一份测试数据。例如,订单退款用例先创建订单,再打开该订单的退款页面,最后删除测试订单。下面沿着这份数据的使用过程,说明 Node 如何 协作。
创建共享上下文
Midscene Test 提供了项目级上下文机制。同一个执行项目中的 Node 可以通过共享的 context 对象访问运行资源、传递测试数据。这个对象由项目的 setup 创建并返回,框架在执行各 Node 时,将它传入 execute()。
对于上面的订单退款流程,setup 可以提供浏览器页面、应用地址和订单服务,订单 ID 则在 Node 创建订单后写入。下面的 setup.ts 展示了这个共享对象的创建方式:ProjectContext 描述它的类型,setup 负责创建并返回实际的对象。
导入的 orderService 是你自己的测试数据服务,需要实现 create 和 remove 方法。应用地址也需替换为测试环境地址。
setup 返回的 context 是当前执行项目共享的同一个对象。在 execute({ input, context }) 中,input 来自当前 YAML 步骤,context 则是这里创建的对象,此时 orderId 尚未赋值。
在 Node 中写入数据
接着创建 nodes.ts。order.prepare 使用 context 中的订单服务创建订单,再将 ID 写入 context.orderId,供后续 Node 使用:
context.orderId = order.id 修改共享对象,使后续 Node 可以读取这个 ID。返回 data 不会自动将它写入 context。
创建订单后,onTeardown() 注册清理函数,在清理时删除本次创建的订单并清除保存的 ID。回调直接使用本次调用的 order.id,因此创建与清理封装在同一个 Node 中,YAML 无需额外调用清理步骤。
在后续 Node 中读取数据
browser.openRefundPage 读取保存的 ID,打开退款页面。如果订单 ID 不存在,这个 Node 会报错。
将 setup 和 Node 一起注册,框架就会把 setup 返回的对象传给各 Node:
在 YAML 中,通过 beforeEach 调用 order.prepare,然后在用例步骤中使用保存的订单 ID:
已注册的清理函数在本次用例的 afterEach 阶段之后执行,即使 YAML 没有声明 afterEach 步骤也会执行。后续准备步骤或用例步骤失败时,框架仍会执行已注册的清理。每次重试有独立的清理范围;如果创建订单失败、尚未注册回调,则本次调用没有对应的清理函数。
Agent 接入和平台资源配置见配置测试项目。
进阶用法
以下介绍 Node 内部的执行结果、错误处理、取消和资源清理。

