@midscene/test 1.16.0 → 1.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/cli/index.mjs +389 -129
- package/dist/es/cli/index.mjs.map +1 -1
- package/dist/es/test-cli.mjs +377 -118
- package/dist/es/test-cli.mjs.map +1 -1
- package/dist/lib/cli/index.js +381 -118
- package/dist/lib/cli/index.js.map +1 -1
- package/dist/lib/test-cli.js +376 -117
- package/dist/lib/test-cli.js.map +1 -1
- package/dist/templates/test/android/README.md +2 -0
- package/dist/templates/test/android/README.zh.md +2 -0
- package/dist/templates/test/android/midscene.config.ts +28 -31
- package/dist/templates/test/computer/README.md +2 -0
- package/dist/templates/test/computer/README.zh.md +2 -0
- package/dist/templates/test/computer/midscene.config.ts +31 -34
- package/dist/templates/test/harmony/README.md +2 -0
- package/dist/templates/test/harmony/README.zh.md +2 -0
- package/dist/templates/test/harmony/midscene.config.ts +28 -31
- package/dist/templates/test/ios/README.md +2 -0
- package/dist/templates/test/ios/README.zh.md +2 -0
- package/dist/templates/test/ios/midscene.config.ts +28 -31
- package/dist/templates/test/web/README.md +16 -0
- package/dist/templates/test/web/README.zh.md +16 -0
- package/dist/templates/test/web/cases/todo.yaml +2 -0
- package/dist/templates/test/web/midscene.config.ts +34 -12
- package/dist/types/cli/index.d.ts +32 -11
- package/dist/types/index.d.ts +3 -3
- package/package.json +13 -13
|
@@ -17,3 +17,5 @@ Run tests with `{{TEST_COMMAND}}`. Read `midscene-node-spec.android.md` for the
|
|
|
17
17
|
After changing Node registrations in `midscene.config.ts`, run `{{RUN_NODES_COMMAND}}` to refresh the spec. This loads the configuration without connecting to a device or running tests. Extension factories must only acquire runtime resources inside Node execution.
|
|
18
18
|
|
|
19
19
|
Tests run serially and stop after the first failed case (`test.bail: 1`). Reports are saved and connections are released on failure. Reports are written to `midscene_run/report/`.
|
|
20
|
+
|
|
21
|
+
Each test file creates its own Agent through `fileSetup` and releases it afterward. The Project is limited to one file at a time because this starter connects to a single device or desktop. Before increasing the Project limit, provide independent devices/displays through your resource allocator.
|
|
@@ -9,3 +9,5 @@
|
|
|
9
9
|
连接设备并用 `adb devices` 确认;可设置 `ANDROID_DEVICE_ID` 指定设备。`cases/todo.yaml` 打开在线 React TodoMVC,添加三个任务、删除一个、完成另一个、筛选已完成任务,并校验可见任务和未完成数量。设备需要能访问该网站;`beforeEach` 先执行 `midscene.config.ts` 中的 `todo.setup`,打开 All 视图并等待输入框加载,再由 `aiAssert` 确认列表为空(包含已完成任务)且没有遮挡弹窗。已有任务会保留;检查失败时,用例会在修改任务前停止。请切换到 All,删除全部任务(包括已完成任务),或仅清除 TodoMVC 网站的数据;关闭遮挡弹窗后重新运行。模型或连接异常应先按报告中的原因排查。AI 检查可能误判,失败时请结合报告确认页面状态。
|
|
10
10
|
|
|
11
11
|
执行 `{{TEST_COMMAND}}` 运行测试。Node 列表见 `midscene-node-spec.android.md`;修改配置中的 Node 后执行 `{{RUN_NODES_COMMAND}}` 刷新。用例串行运行,首次失败即停止后续用例(`test.bail: 1`);失败时仍会保存报告并释放连接。报告位于 `midscene_run/report/`。
|
|
12
|
+
|
|
13
|
+
每份测试文件通过 `fileSetup` 创建独立 Agent,结束后释放。此模板连接一台设备或一个桌面,因此 Project 的 `maxConcurrentFiles` 固定为 1;提高此上限前,需要通过资源分配脚本为并发文件提供独立设备或桌面。
|
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
import { fileURLToPath } from 'node:url';
|
|
2
2
|
import { AndroidAgent, agentFromAdbDevice } from '@midscene/android';
|
|
3
3
|
import { type NodeExecutionContext, defineNode, z } from '@midscene/test';
|
|
4
|
-
import {
|
|
4
|
+
import { defineFileSetup, defineTestProject } from '@midscene/test/config';
|
|
5
5
|
import { createMidsceneNodes } from '@midscene/test/midscene';
|
|
6
6
|
import { config as loadEnv } from 'dotenv';
|
|
7
7
|
|
|
8
8
|
loadEnv({ path: fileURLToPath(new URL('.env', import.meta.url)) });
|
|
9
9
|
|
|
10
|
-
interface
|
|
10
|
+
interface FileContext {
|
|
11
11
|
agent: AndroidAgent;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
const getAgent = ({
|
|
15
|
-
context,
|
|
16
|
-
}: NodeExecutionContext<unknown, ProjectContext>) => {
|
|
14
|
+
const getAgent = ({ context }: NodeExecutionContext<unknown, FileContext>) => {
|
|
17
15
|
return context.agent;
|
|
18
16
|
};
|
|
19
17
|
|
|
20
|
-
const
|
|
18
|
+
const fileSetup = defineFileSetup<FileContext, undefined>({
|
|
21
19
|
name: 'android',
|
|
22
20
|
async setup({ env, onTeardown }) {
|
|
23
21
|
const agent = await agentFromAdbDevice(env.ANDROID_DEVICE_ID || undefined);
|
|
@@ -27,43 +25,42 @@ const setup = defineProjectSetup<ProjectContext>({
|
|
|
27
25
|
});
|
|
28
26
|
|
|
29
27
|
const todoSetupInputSchema = z.strictObject({});
|
|
30
|
-
const todoSetup = defineNode<typeof todoSetupInputSchema, void,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
};
|
|
49
|
-
},
|
|
28
|
+
const todoSetup = defineNode<typeof todoSetupInputSchema, void, FileContext>({
|
|
29
|
+
name: 'todo.setup',
|
|
30
|
+
title: 'Prepare TodoMVC for the initial-state check',
|
|
31
|
+
description:
|
|
32
|
+
'Open React TodoMVC in All view and wait for its input without changing existing tasks.',
|
|
33
|
+
inputSchema: todoSetupInputSchema,
|
|
34
|
+
async execute({ context, signal }) {
|
|
35
|
+
signal.throwIfAborted();
|
|
36
|
+
await context.agent.launch('https://todomvc.com/examples/react/dist/#/');
|
|
37
|
+
await context.agent.aiWaitFor(
|
|
38
|
+
'The React TodoMVC page has loaded and the "What needs to be done?" input is visible.',
|
|
39
|
+
{ timeoutMs: 30_000, abortSignal: signal },
|
|
40
|
+
);
|
|
41
|
+
signal.throwIfAborted();
|
|
42
|
+
return {
|
|
43
|
+
summary:
|
|
44
|
+
'Opened TodoMVC in All view; existing tasks were left unchanged.',
|
|
45
|
+
};
|
|
50
46
|
},
|
|
51
|
-
);
|
|
47
|
+
});
|
|
52
48
|
|
|
53
|
-
export default defineTestProject<
|
|
49
|
+
export default defineTestProject<FileContext, undefined>({
|
|
54
50
|
projects: [
|
|
55
51
|
{
|
|
56
52
|
name: 'android',
|
|
57
|
-
|
|
53
|
+
fileSetup,
|
|
54
|
+
maxConcurrentFiles: 1,
|
|
58
55
|
files: { include: ['cases/**/*.{yaml,yml}'] },
|
|
59
56
|
},
|
|
60
57
|
],
|
|
61
58
|
nodes: [
|
|
62
59
|
todoSetup,
|
|
63
|
-
...createMidsceneNodes<
|
|
60
|
+
...createMidsceneNodes<FileContext>({
|
|
64
61
|
agentClass: AndroidAgent,
|
|
65
62
|
getAgent,
|
|
66
63
|
}),
|
|
67
64
|
],
|
|
68
|
-
test: {
|
|
65
|
+
test: { maxConcurrentFiles: 1, bail: 1, testTimeout: 720_000 },
|
|
69
66
|
});
|
|
@@ -17,3 +17,5 @@ Run tests with `{{TEST_COMMAND}}`. Read `midscene-node-spec.computer.md` for the
|
|
|
17
17
|
After changing Node registrations in `midscene.config.ts`, run `{{RUN_NODES_COMMAND}}` to refresh the spec. This loads the configuration without connecting to a device or running tests. Extension factories must only acquire runtime resources inside Node execution.
|
|
18
18
|
|
|
19
19
|
Tests run serially and stop after the first failed case (`test.bail: 1`). Reports are saved and connections are released on failure. Reports are written to `midscene_run/report/`.
|
|
20
|
+
|
|
21
|
+
Each test file creates its own Agent through `fileSetup` and releases it afterward. The Project is limited to one file at a time because this starter connects to a single device or desktop. Before increasing the Project limit, provide independent devices/displays through your resource allocator.
|
|
@@ -9,3 +9,5 @@
|
|
|
9
9
|
按照[桌面环境指南](https://midscenejs.com/platforms/desktop.html)准备权限和依赖。可设置 `COMPUTER_DISPLAY_ID` 选择显示器;无头 Linux 需要 Xvfb 和 `MIDSCENE_COMPUTER_HEADLESS_LINUX`。`cases/todo.yaml` 在 macOS 使用 Safari、在 Windows/Linux 使用 Chrome 打开在线 React TodoMVC,添加三个任务、删除一个、完成另一个、筛选已完成任务,并校验任务名称及输入框占位文字。桌面需要能访问该网站;`beforeEach` 先执行 `midscene.config.ts` 中的 `todo.setup`,打开 All 视图并等待输入框加载,再由 `aiAssert` 确认列表为空(包含已完成任务)且没有遮挡弹窗。已有任务会保留;检查失败时,用例会在修改任务前停止。请切换到 All,删除全部任务(包括已完成任务),或仅清除 TodoMVC 网站的数据;关闭遮挡弹窗后重新运行。模型或连接异常应先按报告中的原因排查。AI 检查可能误判,失败时请结合报告确认页面状态。
|
|
10
10
|
|
|
11
11
|
执行 `{{TEST_COMMAND}}` 运行测试。Node 列表见 `midscene-node-spec.computer.md`;修改配置中的 Node 后执行 `{{RUN_NODES_COMMAND}}` 刷新。用例串行运行,首次失败即停止后续用例(`test.bail: 1`);失败时仍会保存报告并释放连接。报告位于 `midscene_run/report/`。
|
|
12
|
+
|
|
13
|
+
每份测试文件通过 `fileSetup` 创建独立 Agent,结束后释放。此模板连接一台设备或一个桌面,因此 Project 的 `maxConcurrentFiles` 固定为 1;提高此上限前,需要通过资源分配脚本为并发文件提供独立设备或桌面。
|
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
import { fileURLToPath } from 'node:url';
|
|
2
2
|
import { ComputerAgent, agentForComputer } from '@midscene/computer';
|
|
3
3
|
import { type NodeExecutionContext, defineNode, z } from '@midscene/test';
|
|
4
|
-
import {
|
|
4
|
+
import { defineFileSetup, defineTestProject } from '@midscene/test/config';
|
|
5
5
|
import { createMidsceneNodes } from '@midscene/test/midscene';
|
|
6
6
|
import { config as loadEnv } from 'dotenv';
|
|
7
7
|
|
|
8
8
|
loadEnv({ path: fileURLToPath(new URL('.env', import.meta.url)) });
|
|
9
9
|
|
|
10
|
-
interface
|
|
10
|
+
interface FileContext {
|
|
11
11
|
agent: ComputerAgent;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
const getAgent = ({
|
|
15
|
-
context,
|
|
16
|
-
}: NodeExecutionContext<unknown, ProjectContext>) => {
|
|
14
|
+
const getAgent = ({ context }: NodeExecutionContext<unknown, FileContext>) => {
|
|
17
15
|
return context.agent;
|
|
18
16
|
};
|
|
19
17
|
|
|
20
|
-
const
|
|
18
|
+
const fileSetup = defineFileSetup<FileContext, undefined>({
|
|
21
19
|
name: 'computer',
|
|
22
20
|
async setup({ env, onTeardown }) {
|
|
23
21
|
const agent = await agentForComputer({
|
|
@@ -29,46 +27,45 @@ const setup = defineProjectSetup<ProjectContext>({
|
|
|
29
27
|
});
|
|
30
28
|
|
|
31
29
|
const todoSetupInputSchema = z.strictObject({});
|
|
32
|
-
const todoSetup = defineNode<typeof todoSetupInputSchema, void,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
};
|
|
54
|
-
},
|
|
30
|
+
const todoSetup = defineNode<typeof todoSetupInputSchema, void, FileContext>({
|
|
31
|
+
name: 'todo.setup',
|
|
32
|
+
title: 'Prepare TodoMVC for the initial-state check',
|
|
33
|
+
description:
|
|
34
|
+
'Open React TodoMVC in All view and wait for its input without changing existing tasks.',
|
|
35
|
+
inputSchema: todoSetupInputSchema,
|
|
36
|
+
async execute({ context, signal }) {
|
|
37
|
+
signal.throwIfAborted();
|
|
38
|
+
await context.agent.aiAct(
|
|
39
|
+
'Open a browser and navigate to https://todomvc.com/examples/react/dist/#/. On macOS use Safari with Cmd+Space and Cmd+L; on Windows or Linux use Chrome with the Windows key and Ctrl+L. Ensure the All filter is selected if filters are visible. Only navigate and select All; leave every Todo unchanged.',
|
|
40
|
+
{ abortSignal: signal },
|
|
41
|
+
);
|
|
42
|
+
await context.agent.aiWaitFor(
|
|
43
|
+
'The React TodoMVC page has loaded and the "What needs to be done?" input is visible.',
|
|
44
|
+
{ timeoutMs: 30_000, abortSignal: signal },
|
|
45
|
+
);
|
|
46
|
+
signal.throwIfAborted();
|
|
47
|
+
return {
|
|
48
|
+
summary:
|
|
49
|
+
'Opened TodoMVC in All view; existing tasks were left unchanged.',
|
|
50
|
+
};
|
|
55
51
|
},
|
|
56
|
-
);
|
|
52
|
+
});
|
|
57
53
|
|
|
58
|
-
export default defineTestProject<
|
|
54
|
+
export default defineTestProject<FileContext, undefined>({
|
|
59
55
|
projects: [
|
|
60
56
|
{
|
|
61
57
|
name: 'computer',
|
|
62
|
-
|
|
58
|
+
fileSetup,
|
|
59
|
+
maxConcurrentFiles: 1,
|
|
63
60
|
files: { include: ['cases/**/*.{yaml,yml}'] },
|
|
64
61
|
},
|
|
65
62
|
],
|
|
66
63
|
nodes: [
|
|
67
64
|
todoSetup,
|
|
68
|
-
...createMidsceneNodes<
|
|
65
|
+
...createMidsceneNodes<FileContext>({
|
|
69
66
|
agentClass: ComputerAgent,
|
|
70
67
|
getAgent,
|
|
71
68
|
}),
|
|
72
69
|
],
|
|
73
|
-
test: {
|
|
70
|
+
test: { maxConcurrentFiles: 1, bail: 1, testTimeout: 360_000 },
|
|
74
71
|
});
|
|
@@ -17,3 +17,5 @@ Run tests with `{{TEST_COMMAND}}`. Read `midscene-node-spec.harmony.md` for the
|
|
|
17
17
|
After changing Node registrations in `midscene.config.ts`, run `{{RUN_NODES_COMMAND}}` to refresh the spec. This loads the configuration without connecting to a device or running tests. Extension factories must only acquire runtime resources inside Node execution.
|
|
18
18
|
|
|
19
19
|
Tests run serially and stop after the first failed case (`test.bail: 1`). Reports are saved and connections are released on failure. Reports are written to `midscene_run/report/`.
|
|
20
|
+
|
|
21
|
+
Each test file creates its own Agent through `fileSetup` and releases it afterward. The Project is limited to one file at a time because this starter connects to a single device or desktop. Before increasing the Project limit, provide independent devices/displays through your resource allocator.
|
|
@@ -9,3 +9,5 @@
|
|
|
9
9
|
连接设备并用 `hdc list targets` 确认;可设置 `HARMONY_DEVICE_ID` 指定设备,必要时设置 `HDC_HOME`。`cases/todo.yaml` 在浏览器打开在线 React TodoMVC,添加三个任务、删除一个、完成另一个、筛选已完成任务,并校验可见任务和未完成数量。设备需要能访问该网站;`beforeEach` 先执行 `midscene.config.ts` 中的 `todo.setup`,打开 All 视图并等待输入框加载,再由 `aiAssert` 确认列表为空(包含已完成任务)且没有遮挡弹窗。已有任务会保留;检查失败时,用例会在修改任务前停止。请切换到 All,删除全部任务(包括已完成任务),或仅清除 TodoMVC 网站的数据;关闭遮挡弹窗后重新运行。模型或连接异常应先按报告中的原因排查。AI 检查可能误判,失败时请结合报告确认页面状态。
|
|
10
10
|
|
|
11
11
|
执行 `{{TEST_COMMAND}}` 运行测试。Node 列表见 `midscene-node-spec.harmony.md`;修改配置中的 Node 后执行 `{{RUN_NODES_COMMAND}}` 刷新。用例串行运行,首次失败即停止后续用例(`test.bail: 1`);失败时仍会保存报告并释放连接。报告位于 `midscene_run/report/`。
|
|
12
|
+
|
|
13
|
+
每份测试文件通过 `fileSetup` 创建独立 Agent,结束后释放。此模板连接一台设备或一个桌面,因此 Project 的 `maxConcurrentFiles` 固定为 1;提高此上限前,需要通过资源分配脚本为并发文件提供独立设备或桌面。
|
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
import { fileURLToPath } from 'node:url';
|
|
2
2
|
import { HarmonyAgent, agentFromHdcDevice } from '@midscene/harmony';
|
|
3
3
|
import { type NodeExecutionContext, defineNode, z } from '@midscene/test';
|
|
4
|
-
import {
|
|
4
|
+
import { defineFileSetup, defineTestProject } from '@midscene/test/config';
|
|
5
5
|
import { createMidsceneNodes } from '@midscene/test/midscene';
|
|
6
6
|
import { config as loadEnv } from 'dotenv';
|
|
7
7
|
|
|
8
8
|
loadEnv({ path: fileURLToPath(new URL('.env', import.meta.url)) });
|
|
9
9
|
|
|
10
|
-
interface
|
|
10
|
+
interface FileContext {
|
|
11
11
|
agent: HarmonyAgent;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
const getAgent = ({
|
|
15
|
-
context,
|
|
16
|
-
}: NodeExecutionContext<unknown, ProjectContext>) => {
|
|
14
|
+
const getAgent = ({ context }: NodeExecutionContext<unknown, FileContext>) => {
|
|
17
15
|
return context.agent;
|
|
18
16
|
};
|
|
19
17
|
|
|
20
|
-
const
|
|
18
|
+
const fileSetup = defineFileSetup<FileContext, undefined>({
|
|
21
19
|
name: 'harmony',
|
|
22
20
|
async setup({ env, onTeardown }) {
|
|
23
21
|
const agent = await agentFromHdcDevice(env.HARMONY_DEVICE_ID || undefined);
|
|
@@ -27,43 +25,42 @@ const setup = defineProjectSetup<ProjectContext>({
|
|
|
27
25
|
});
|
|
28
26
|
|
|
29
27
|
const todoSetupInputSchema = z.strictObject({});
|
|
30
|
-
const todoSetup = defineNode<typeof todoSetupInputSchema, void,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
};
|
|
49
|
-
},
|
|
28
|
+
const todoSetup = defineNode<typeof todoSetupInputSchema, void, FileContext>({
|
|
29
|
+
name: 'todo.setup',
|
|
30
|
+
title: 'Prepare TodoMVC for the initial-state check',
|
|
31
|
+
description:
|
|
32
|
+
'Open React TodoMVC in All view and wait for its input without changing existing tasks.',
|
|
33
|
+
inputSchema: todoSetupInputSchema,
|
|
34
|
+
async execute({ context, signal }) {
|
|
35
|
+
signal.throwIfAborted();
|
|
36
|
+
await context.agent.launch('https://todomvc.com/examples/react/dist/#/');
|
|
37
|
+
await context.agent.aiWaitFor(
|
|
38
|
+
'The React TodoMVC page has loaded and the "What needs to be done?" input is visible.',
|
|
39
|
+
{ timeoutMs: 30_000, abortSignal: signal },
|
|
40
|
+
);
|
|
41
|
+
signal.throwIfAborted();
|
|
42
|
+
return {
|
|
43
|
+
summary:
|
|
44
|
+
'Opened TodoMVC in All view; existing tasks were left unchanged.',
|
|
45
|
+
};
|
|
50
46
|
},
|
|
51
|
-
);
|
|
47
|
+
});
|
|
52
48
|
|
|
53
|
-
export default defineTestProject<
|
|
49
|
+
export default defineTestProject<FileContext, undefined>({
|
|
54
50
|
projects: [
|
|
55
51
|
{
|
|
56
52
|
name: 'harmony',
|
|
57
|
-
|
|
53
|
+
fileSetup,
|
|
54
|
+
maxConcurrentFiles: 1,
|
|
58
55
|
files: { include: ['cases/**/*.{yaml,yml}'] },
|
|
59
56
|
},
|
|
60
57
|
],
|
|
61
58
|
nodes: [
|
|
62
59
|
todoSetup,
|
|
63
|
-
...createMidsceneNodes<
|
|
60
|
+
...createMidsceneNodes<FileContext>({
|
|
64
61
|
agentClass: HarmonyAgent,
|
|
65
62
|
getAgent,
|
|
66
63
|
}),
|
|
67
64
|
],
|
|
68
|
-
test: {
|
|
65
|
+
test: { maxConcurrentFiles: 1, bail: 1, testTimeout: 720_000 },
|
|
69
66
|
});
|
|
@@ -17,3 +17,5 @@ Run tests with `{{TEST_COMMAND}}`. Read `midscene-node-spec.ios.md` for the avai
|
|
|
17
17
|
After changing Node registrations in `midscene.config.ts`, run `{{RUN_NODES_COMMAND}}` to refresh the spec. This loads the configuration without connecting to a device or running tests. Extension factories must only acquire runtime resources inside Node execution.
|
|
18
18
|
|
|
19
19
|
Tests run serially and stop after the first failed case (`test.bail: 1`). Reports are saved and connections are released on failure. Reports are written to `midscene_run/report/`.
|
|
20
|
+
|
|
21
|
+
Each test file creates its own Agent through `fileSetup` and releases it afterward. The Project is limited to one file at a time because this starter connects to a single device or desktop. Before increasing the Project limit, provide independent devices/displays through your resource allocator.
|
|
@@ -9,3 +9,5 @@
|
|
|
9
9
|
启动 WebDriverAgent。可设置 `WDA_HOST` 和 `WDA_PORT`,或用 `WDA_BASE_URL` 指定带路径前缀的网关地址。远程 MJPEG 流可设置 `WDA_MJPEG_URL`,直连则可设置 `WDA_MJPEG_PORT`。`cases/todo.yaml` 打开在线 React TodoMVC,添加三个任务、删除一个、完成另一个、筛选已完成任务,并校验可见任务和未完成数量。设备需要能访问该网站;`beforeEach` 先执行 `midscene.config.ts` 中的 `todo.setup`,打开 All 视图并等待输入框加载,再由 `aiAssert` 确认列表为空(包含已完成任务)且没有遮挡弹窗。已有任务会保留;检查失败时,用例会在修改任务前停止。请切换到 All,删除全部任务(包括已完成任务),或仅清除 TodoMVC 网站的数据;关闭遮挡弹窗后重新运行。模型或连接异常应先按报告中的原因排查。AI 检查可能误判,失败时请结合报告确认页面状态。
|
|
10
10
|
|
|
11
11
|
执行 `{{TEST_COMMAND}}` 运行测试。Node 列表见 `midscene-node-spec.ios.md`;修改配置中的 Node 后执行 `{{RUN_NODES_COMMAND}}` 刷新。用例串行运行,首次失败即停止后续用例(`test.bail: 1`);失败时仍会保存报告并释放连接。报告位于 `midscene_run/report/`。
|
|
12
|
+
|
|
13
|
+
每份测试文件通过 `fileSetup` 创建独立 Agent,结束后释放。此模板连接一台设备或一个桌面,因此 Project 的 `maxConcurrentFiles` 固定为 1;提高此上限前,需要通过资源分配脚本为并发文件提供独立设备或桌面。
|
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
import { fileURLToPath } from 'node:url';
|
|
2
2
|
import { IOSAgent, agentFromWebDriverAgent } from '@midscene/ios';
|
|
3
3
|
import { type NodeExecutionContext, defineNode, z } from '@midscene/test';
|
|
4
|
-
import {
|
|
4
|
+
import { defineFileSetup, defineTestProject } from '@midscene/test/config';
|
|
5
5
|
import { createMidsceneNodes } from '@midscene/test/midscene';
|
|
6
6
|
import { config as loadEnv } from 'dotenv';
|
|
7
7
|
|
|
8
8
|
loadEnv({ path: fileURLToPath(new URL('.env', import.meta.url)) });
|
|
9
9
|
|
|
10
|
-
interface
|
|
10
|
+
interface FileContext {
|
|
11
11
|
agent: IOSAgent;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
const getAgent = ({
|
|
15
|
-
context,
|
|
16
|
-
}: NodeExecutionContext<unknown, ProjectContext>) => {
|
|
14
|
+
const getAgent = ({ context }: NodeExecutionContext<unknown, FileContext>) => {
|
|
17
15
|
return context.agent;
|
|
18
16
|
};
|
|
19
17
|
|
|
20
|
-
const
|
|
18
|
+
const fileSetup = defineFileSetup<FileContext, undefined>({
|
|
21
19
|
name: 'ios',
|
|
22
20
|
async setup({ env, onTeardown }) {
|
|
23
21
|
const wdaBaseUrl = env.WDA_BASE_URL || undefined;
|
|
@@ -56,40 +54,39 @@ const setup = defineProjectSetup<ProjectContext>({
|
|
|
56
54
|
});
|
|
57
55
|
|
|
58
56
|
const todoSetupInputSchema = z.strictObject({});
|
|
59
|
-
const todoSetup = defineNode<typeof todoSetupInputSchema, void,
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
};
|
|
78
|
-
},
|
|
57
|
+
const todoSetup = defineNode<typeof todoSetupInputSchema, void, FileContext>({
|
|
58
|
+
name: 'todo.setup',
|
|
59
|
+
title: 'Prepare TodoMVC for the initial-state check',
|
|
60
|
+
description:
|
|
61
|
+
'Open React TodoMVC in All view and wait for its input without changing existing tasks.',
|
|
62
|
+
inputSchema: todoSetupInputSchema,
|
|
63
|
+
async execute({ context, signal }) {
|
|
64
|
+
signal.throwIfAborted();
|
|
65
|
+
await context.agent.launch('https://todomvc.com/examples/react/dist/#/');
|
|
66
|
+
await context.agent.aiWaitFor(
|
|
67
|
+
'The React TodoMVC page has loaded and the "What needs to be done?" input is visible.',
|
|
68
|
+
{ timeoutMs: 30_000, abortSignal: signal },
|
|
69
|
+
);
|
|
70
|
+
signal.throwIfAborted();
|
|
71
|
+
return {
|
|
72
|
+
summary:
|
|
73
|
+
'Opened TodoMVC in All view; existing tasks were left unchanged.',
|
|
74
|
+
};
|
|
79
75
|
},
|
|
80
|
-
);
|
|
76
|
+
});
|
|
81
77
|
|
|
82
|
-
export default defineTestProject<
|
|
78
|
+
export default defineTestProject<FileContext, undefined>({
|
|
83
79
|
projects: [
|
|
84
80
|
{
|
|
85
81
|
name: 'ios',
|
|
86
|
-
|
|
82
|
+
fileSetup,
|
|
83
|
+
maxConcurrentFiles: 1,
|
|
87
84
|
files: { include: ['cases/**/*.{yaml,yml}'] },
|
|
88
85
|
},
|
|
89
86
|
],
|
|
90
87
|
nodes: [
|
|
91
88
|
todoSetup,
|
|
92
|
-
...createMidsceneNodes<
|
|
89
|
+
...createMidsceneNodes<FileContext>({ agentClass: IOSAgent, getAgent }),
|
|
93
90
|
],
|
|
94
|
-
test: {
|
|
91
|
+
test: { maxConcurrentFiles: 1, bail: 1, testTimeout: 720_000 },
|
|
95
92
|
});
|
|
@@ -17,3 +17,19 @@ Run tests with `{{TEST_COMMAND}}`. Read `midscene-node-spec.web.md` for the avai
|
|
|
17
17
|
After changing Node registrations in `midscene.config.ts`, run `{{RUN_NODES_COMMAND}}` to refresh the spec. This loads the configuration without connecting to a device or running tests. Extension factories must only acquire runtime resources inside Node execution.
|
|
18
18
|
|
|
19
19
|
Tests run serially and stop after the first failed case (`test.bail: 1`). Reports are saved and the browser is closed on failure. Reports are written to `midscene_run/report/`.
|
|
20
|
+
|
|
21
|
+
## How preparation works
|
|
22
|
+
|
|
23
|
+
| Stage | Frequency | What this example does |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| `setup` (project configuration) | Once per project | Starts a shared Browser |
|
|
26
|
+
| `fileSetup` (project configuration) | Once per file | Creates a separate BrowserContext and Page; returns the Node `context` |
|
|
27
|
+
| `beforeAll` (YAML) | Once before the file's cases | Sets the viewport; the first `getAgent` call creates the Agent for later Nodes to reuse |
|
|
28
|
+
|
|
29
|
+
`fileSetup` applies to every file; each file declares its own `beforeAll`. The business Node `todo.setup` is unrelated to the `setup` configuration field.
|
|
30
|
+
|
|
31
|
+
## Usage notes
|
|
32
|
+
|
|
33
|
+
- Raise `test.maxConcurrentFiles` to run files concurrently. Cases within a file stay sequential.
|
|
34
|
+
- Retries do not repeat these three stages; `beforeEach` resets business state.
|
|
35
|
+
- File cleanup destroys the Agent and closes the BrowserContext. The Browser closes after all file cleanup. Isolate backend accounts and data separately.
|
|
@@ -11,3 +11,19 @@
|
|
|
11
11
|
`cases/todo.yaml` 使用在线 React TodoMVC。每条用例先由 `todo.setup` 打开 All 视图、清空页面存储并重新加载,通过 DOM 确认列表为空,再由 `todo.seed` 添加三个任务;第一条用例校验初始化状态,第二条用 AI 删除和完成任务,再校验最终状态。`todo.captureState` 在每条用例后记录页面状态。运行时需要能访问 TodoMVC 网站。
|
|
12
12
|
|
|
13
13
|
执行 `{{TEST_COMMAND}}` 运行测试。Node 列表见 `midscene-node-spec.web.md`;修改配置中的 Node 后执行 `{{RUN_NODES_COMMAND}}` 刷新。用例串行运行,首次失败即停止后续用例(`test.bail: 1`);失败时仍会保存报告并关闭浏览器。报告位于 `midscene_run/report/`。
|
|
14
|
+
|
|
15
|
+
## 准备工作如何执行
|
|
16
|
+
|
|
17
|
+
| 阶段 | 次数 | 本例中的工作 |
|
|
18
|
+
| --- | --- | --- |
|
|
19
|
+
| `setup`(项目配置) | 项目一次 | 启动共享 Browser |
|
|
20
|
+
| `fileSetup`(项目配置) | 每份文件一次 | 创建独立 BrowserContext、Page,返回 Node 使用的 `context` |
|
|
21
|
+
| `beforeAll`(YAML) | 文件用例开始前一次 | 设置视口;首次调用 `getAgent` 创建 Agent,后续 Node 复用 |
|
|
22
|
+
|
|
23
|
+
`fileSetup` 自动应用到每份文件;`beforeAll` 由各文件声明。业务 Node `todo.setup` 与配置项 `setup` 无关。
|
|
24
|
+
|
|
25
|
+
## 注意事项
|
|
26
|
+
|
|
27
|
+
- 提高 `test.maxConcurrentFiles` 可并发运行文件,文件内用例仍顺序执行。
|
|
28
|
+
- 重试不重跑上述三个阶段;`beforeEach` 负责重置业务状态。
|
|
29
|
+
- 文件结束后先销毁 Agent、关闭 BrowserContext;所有文件清理完再关闭 Browser。后端账号和数据需自行隔离。
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
# fileSetup has prepared this file's page; getAgent creates its Agent on first use.
|
|
1
2
|
beforeAll:
|
|
2
3
|
- setViewportSize:
|
|
3
4
|
width: 1280
|
|
4
5
|
height: 720
|
|
5
6
|
|
|
7
|
+
# Reset business state for every case attempt, including retries.
|
|
6
8
|
beforeEach:
|
|
7
9
|
- todo.setup: {}
|
|
8
10
|
- todo.seed:
|
|
@@ -1,26 +1,34 @@
|
|
|
1
1
|
import { fileURLToPath } from 'node:url';
|
|
2
2
|
import type { NodeExecutionContext } from '@midscene/test';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
defineFileSetup,
|
|
5
|
+
defineProjectSetup,
|
|
6
|
+
defineTestProject,
|
|
7
|
+
} from '@midscene/test/config';
|
|
4
8
|
import { createMidsceneNodes } from '@midscene/test/midscene';
|
|
5
9
|
import { PlaywrightAgent } from '@midscene/web/playwright/agent';
|
|
6
10
|
import { config as loadEnv } from 'dotenv';
|
|
7
|
-
import { type Page, chromium } from 'playwright';
|
|
11
|
+
import { type Browser, type Page, chromium } from 'playwright';
|
|
8
12
|
import { createTodoNodes } from './nodes/todo';
|
|
9
13
|
|
|
10
14
|
loadEnv({ path: fileURLToPath(new URL('.env', import.meta.url)) });
|
|
11
15
|
|
|
12
|
-
interface
|
|
16
|
+
interface FileContext {
|
|
13
17
|
page: Page;
|
|
14
18
|
agent?: PlaywrightAgent;
|
|
15
19
|
}
|
|
16
20
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
21
|
+
interface ProjectContext {
|
|
22
|
+
browser: Browser;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Create the Agent on first use; reuse it within this file.
|
|
26
|
+
const getAgent = ({ context }: NodeExecutionContext<unknown, FileContext>) => {
|
|
20
27
|
context.agent ??= new PlaywrightAgent(context.page);
|
|
21
28
|
return context.agent;
|
|
22
29
|
};
|
|
23
30
|
|
|
31
|
+
// Once per project: keep the shared Browser until every file has cleaned up.
|
|
24
32
|
const setup = defineProjectSetup<ProjectContext>({
|
|
25
33
|
name: 'web',
|
|
26
34
|
async setup({ env, onTeardown }) {
|
|
@@ -28,8 +36,21 @@ const setup = defineProjectSetup<ProjectContext>({
|
|
|
28
36
|
headless: env.HEADLESS !== 'false',
|
|
29
37
|
});
|
|
30
38
|
onTeardown(() => browser.close());
|
|
31
|
-
|
|
32
|
-
|
|
39
|
+
return { browser };
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Before each file's beforeAll: create its session, page, and Node context.
|
|
44
|
+
const fileSetup = defineFileSetup<FileContext, ProjectContext>({
|
|
45
|
+
name: 'web-file',
|
|
46
|
+
async setup({ projectContext, signal, onTeardown }) {
|
|
47
|
+
if (!projectContext)
|
|
48
|
+
throw new Error('Web Project setup did not create a browser.');
|
|
49
|
+
const browserContext = await projectContext.browser.newContext();
|
|
50
|
+
onTeardown(() => browserContext.close());
|
|
51
|
+
signal.throwIfAborted();
|
|
52
|
+
const page = await browserContext.newPage();
|
|
53
|
+
const context: FileContext = { page };
|
|
33
54
|
onTeardown(async () => {
|
|
34
55
|
await context.agent?.destroy();
|
|
35
56
|
});
|
|
@@ -37,20 +58,21 @@ const setup = defineProjectSetup<ProjectContext>({
|
|
|
37
58
|
},
|
|
38
59
|
});
|
|
39
60
|
|
|
40
|
-
export default defineTestProject<ProjectContext>({
|
|
61
|
+
export default defineTestProject<FileContext, ProjectContext>({
|
|
41
62
|
projects: [
|
|
42
63
|
{
|
|
43
64
|
name: 'web',
|
|
44
65
|
setup,
|
|
66
|
+
fileSetup,
|
|
45
67
|
files: { include: ['cases/**/*.{yaml,yml}'] },
|
|
46
68
|
},
|
|
47
69
|
],
|
|
48
70
|
nodes: [
|
|
49
|
-
...createTodoNodes<
|
|
50
|
-
...createMidsceneNodes<
|
|
71
|
+
...createTodoNodes<FileContext>({ getPage: (context) => context.page }),
|
|
72
|
+
...createMidsceneNodes<FileContext>({
|
|
51
73
|
agentClass: PlaywrightAgent,
|
|
52
74
|
getAgent,
|
|
53
75
|
}),
|
|
54
76
|
],
|
|
55
|
-
test: {
|
|
77
|
+
test: { maxConcurrentFiles: 1, bail: 1, testTimeout: 600_000 },
|
|
56
78
|
});
|