生命周期与并发
项目生命周期
setup、fileSetup 与 beforeAll
三者按 setup → fileSetup → beforeAll → 用例 的顺序依次执行,未配置的环节会自动跳过:
例如,当一个执行项目包含两份 YAML 文件时,setup 只会执行 1 次,而 fileSetup 和 beforeAll 会各执行 2 次(开启并发不会改变它们的执行次数)。
fileSetup 只需在项目配置中声明一次,即可自动作用于该项目下的所有 YAML 文件;而 beforeAll 则由各 YAML 文件按需声明。虽然自定义 Node 也可以在 beforeAll 中创建资源并写入 context,但框架不会为 beforeAll 自动隔离出独立的文件级 context(若未配置 fileSetup,该项目下的所有文件仍会共用 setup 返回的同一个 context 对象)。
YAML 中的生命周期钩子
生命周期钩子用于在用例执行前后准备环境、重置状态或清理数据。四个钩子均为可选配置,与 cases 处于同一 层级,内部步骤的写法与用例 steps 完全相同:
在下面的 Web 示例中,每条用例开始前都会打开商城首页,结束后清除 Cookie。其中 data.prepare 和 data.cleanup 是团队自行实现并注册的自定义 Node,分别用于准备和清理测试商品;gotoUrl、clearCookies、aiAct 和 aiAssert 则是 Web 脚手架默认注册的官方 Node:
项目、文件与用例的生命周期
通过 CLI 或 runTestProject() 运行测试时,每个执行项目都拥有完整的独立生命周期。下图展示了一个包含两份 YAML 文件的执行项目在串行模式下的完整执行顺序(未声明的 YAML 钩子会自动跳过):
其中,beforeAll 和 afterAll 在每份 YAML 文件中各执行一次;beforeEach 和 afterEach 则包裹每条用例的每一次执行尝试(attempt)。开启文件并发后,不同文件可以并行推进,但单个文件内部依然严格遵循上述顺序。
当某条用例触发失败重试时,会直接复用当前文件的运行环境,仅重新执行 beforeEach → steps → afterEach → 本次尝试注册的 Node 清理,而不会重新运行项目级 setup、fileSetup 或 beforeAll。关于触发重试的条件与失败跳过规则,请参阅下文失败时如何执行。
通过 onTeardown() 注册的清理函数,其触发时机取决于注册位置:
同一作用范围内的清理函数按注册顺序的逆序(后进先出)依次执行。如果某个清理函数抛出错误,框架仍会继续执行剩余的清理函数;如果某个清理函数尚未执行完毕,后续清理函数会等待其结束。关于在自定义 Node 中注册清理函数的具体写法,请参阅资源的生命周期与清理。
context 的共享范围
Node 在 execute({ context }) 中收到的 context 取决于项目的配置方式:
在同一个共享范围内,所有 Node 接收到的都是同一个 context 对象引用——因此前一条用例写入 context.orderId 后,同一范围内的后续用例也能读到该属性。框架不会在用例切换或失败重试时自动重置 context、浏览器页面或登录状态;每条用例所需的初始状态应当在 beforeEach(或通过 onTeardown())中显式重置。
注意:请务必在 setup() 函数体内实例化资源并返回新对象。即使多个执行项目或多个文件复用了同一个 defineProjectSetup / defineFileSetup 定义,框架也会在每次初始化时分别调用 setup();但如果 setup() 直接返回模块顶层的同一个可变对象,不同项目或文件之间仍会意外共享状态。关于跨 Node 传递数据的完整示例,请参阅跨 Node 共享上下文。
失败时如何执行
不同阶段发生错误时,框架的后续处理策略如下:
在任意阶段内部,一旦某个步骤失败,默认会跳过该阶段的剩余步骤(若配置了 continue-on-error: true,则会继续执行同阶段的后续步骤,但最终状态仍记为失败)。由于前置准备步骤可能中途失败,清理逻辑应当具备容错能力,妥善处理“资源只初始化了一半”的情况。
当某条用例执行失败时,默认会继续执行文件中的下一条用例(因此有先后依赖关系的业务步骤应当写在同一个用例中,而不应拆成多条用例)。如果配置了 test.bail(失败用例数阈值),当累计失败的用例数达到该阈值后,框架会停止调度新的用例和重试,并在当前正在执行的尝试清理完毕后收尾退出。
当底层资源发生故障,或已注册的清理函数执行失败、超时未退出时,当前文件会立即停止后续重试与剩余用例。此时其他文件能否继续运行,取决于故障资源的共享范围:
普通的 afterEach 步骤失败仅按当前用例失败处理;只要底层操作已正常退出且 onTeardown() 清理函数执行完毕,该用例仍可按配置进入下一次重试。但如果发生测试报告写入失败等框架基础设施错误,则会直接中止整次测试运行。
并发与资源隔离
Midscene Test 通过两个全局配置项共同控制并发度:
maxConcurrentFiles:整次运行期间允许同时处于活动状态的 YAML 文件总数上限。maxConcurrentProjects:整次运行期间允许同时处于活动状态(从开始setup到清理完毕)的执行项目总数上限。
两个字段省略时默认均为 1(彼此独立,不会互相推导);设为 null 表示不限制该项,设为数字时必须为正整数。常见组合的效果如下:
注意:并发调度基于单个 Node.js 进程内的异步并发。仅调高 maxConcurrentProjects 并不会提升文件并发数;同时,并发度也并非越高越快,实际配置时需综合考虑机器内存、浏览器实例开销、物理设备数量以及大模型 API 的并发限流(QPM/TPM)。
使用 maxConcurrentFiles 开启文件并发
与文件并发相关的完整配置项如下:
例如,当配置了 test: { maxConcurrentFiles: 3, maxConcurrentProjects: 2 },且 Web 与 Android 两个项目的 maxConcurrentFiles 均设为 2 时,调度器同一时刻可以运行“2 份 Web 文件 + 1 份 Android 文件”或“1 份 Web 文件 + 2 份 Android 文件”——全局活动文件总数始终不超过 3(项目数不会与全局文件上限相乘)。
并发名额(Slot)的占用与释放遵循以下规则:
- 文件名额:每份文件从开始执行
fileSetup(含等待设备分配)起,历经用例执行与重试,直到文件级清理彻底完成为止,全程占用 1 个文件名额。 - 项目启动:启动一个新执行项目时,调度器会先同时申请 1 个项目名额和首份文件的 1 个文件名额,随后执行项目级
setup;该项目下的其余文件在setup完成前处于等待状态,不提前占用文件名额。多个已就绪的项目会轮流领取文件名额。 - 项目释放:执行项目在最后一份文件完成自身清理并执行完项目级清理后,才会释放其占用的项目名额(及最后一个文件名额)。因此,当
maxConcurrentProjects: 1时,必须等前一个项目彻底清理完毕才会启动下一个项目;而当maxConcurrentProjects > 1且maxConcurrentFiles: 1时,不同项目的文件可以在各自已就绪的项目环境之间交错执行。 - 中断收尾:触发取消或达到
bail阈值后,已初始化但当前没有活动文件的项目也会在全局文件上限约束下依次执行项目级清理。
从旧版并发配置迁移
旧版配置项 test.maxConcurrency 已移除(加载含该字段的配置会直接报错并提示迁移方式)。由于旧版的 maxConcurrency 控制的是项目级并发,迁移时不能简单将其改名为 maxConcurrentFiles。例如,若要保留旧版 maxConcurrency: 2(两个项目并发、但每个项目内部文件串行)的行为,应配置为:
补充说明:
fileSetup、projects[].maxConcurrentFiles以及准备/清理超时均可独立配置,无需显式声明全局并发字段。当项目的有效文件并发上限为1时,fileSetup可以省略,所有文件直接共用项目级setup返回的环境。- 多个执行项目之间不会自动分片(Shard)均分用例:如果同一份 YAML 文件同时被两个执行项目匹配选中,它会在两个项目中分别完整执行一次。如需将用例分批交由不同项目运行,请配置互不重叠的
files或tags规则。 - 旧版
tasks/flow格式的 YAML 脚本仍通过midscene命令运行,其原有的concurrent/--concurrent参数、前置 setup 脚本和浏览器复用行为保持不变,与 Midscene Test 的setup/fileSetup机制互不影响。迁移指南请参阅迁移到 Midscene Test。
超时与取消机制
超时配置
Midscene Test 提供三个维度的超时控制:
即使未开启并发,上述超时限制依然生效。当整次运行因用户取消或达到 bail 阈值而中止时,所有剩余的清理范围将共用一个基于 teardownTimeout 的收尾时限。
取消与清理
当单个步骤超时,或 CLI 收到 SIGINT(如 Ctrl+C)/ SIGTERM 信号时,框架会触发当前步骤的 signal(AbortSignal)。由于 JavaScript 中的异步操作不会自动强行终止,自定义 Node 需要将 signal 透传给支持取消的底层 API,或在循环中主动检查取消状态,详见异步操作、错误与取消。
测试运行被中断后,框架仍会尽力完成资源清理:
afterEach和afterAll钩子中的 Node 会收到一个尚未取消的独立清理signal(依然受步骤超时约束),并且会等待同一个 Agent 上尚未退出的旧操作结束后再执行,避免并发操作冲突。- 通过
onTeardown()注册的清理函数不接收signal参数;请勿在onTeardown()回调中复用原步骤的signal(因为原步骤的signal此时可能已处于取消状态)。
需要注意,超时配置仅限制框架等待结果的时长。如果超时发生后底层的操作或清理仍未 真正退出,框架会在报告中记录 cleanup pending 并继续跟踪该资源,直到操作实际结束后再释放,同时停止启动任何新的测试文件。
使用注意事项
- 按需选择初始化层级:没有跨文件共享资源时,可以只配置
fileSetup;文件串行执行(有效文件并发上限为1)时,可以省略fileSetup,让所有文件共用项目级setup返回的环境。 - 开启文件并发必须隔离资源:一旦项目的有效文件并发上限大于
1(包含null),就必须配置fileSetup。每个并发文件应使用独立的页面、Agent 和设备,测试涉及的后端账号与业务数据也需做好隔离。 - 失败重试不会重跑外层初始化:用例重试期间不会重新执行
setup、fileSetup和beforeAll;每次尝试依赖的初始状态应在beforeEach中重置。 - 创建资源后立即注册清理:每当创建了浏览器、设备连接或测试数据,应立即调用
onTeardown()登记清理逻辑,并确保清理函数能妥善处理初始化中途失败的情况。

