@midscene/test 1.16.0 → 1.16.2
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/{1~77.mjs → 1~731.mjs} +9 -9
- package/dist/es/1~731.mjs.map +1 -0
- package/dist/es/cli/index.mjs +686 -167
- package/dist/es/cli/index.mjs.map +1 -1
- package/dist/es/index.mjs +45 -1
- package/dist/es/index.mjs.map +1 -1
- package/dist/es/test-cli.mjs +1863 -206
- package/dist/es/test-cli.mjs.map +1 -1
- package/dist/lib/cli/index.js +677 -155
- package/dist/lib/cli/index.js.map +1 -1
- package/dist/lib/index.js +45 -1
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/test-cli.js +1854 -203
- package/dist/lib/test-cli.js.map +1 -1
- package/dist/templates/test/android/.env.example +1 -1
- package/dist/templates/test/android/README.md +3 -1
- package/dist/templates/test/android/README.zh.md +3 -1
- package/dist/templates/test/android/midscene.config.ts +28 -31
- package/dist/templates/test/computer/.env.example +1 -1
- package/dist/templates/test/computer/README.md +3 -1
- package/dist/templates/test/computer/README.zh.md +3 -1
- package/dist/templates/test/computer/midscene.config.ts +31 -34
- package/dist/templates/test/harmony/.env.example +1 -1
- package/dist/templates/test/harmony/README.md +3 -1
- package/dist/templates/test/harmony/README.zh.md +3 -1
- package/dist/templates/test/harmony/midscene.config.ts +28 -31
- package/dist/templates/test/ios/.env.example +1 -1
- package/dist/templates/test/ios/README.md +3 -1
- package/dist/templates/test/ios/README.zh.md +3 -1
- package/dist/templates/test/ios/midscene.config.ts +28 -31
- package/dist/templates/test/web/.env.example +1 -1
- package/dist/templates/test/web/README.md +17 -1
- package/dist/templates/test/web/README.zh.md +17 -1
- 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 +107 -11
- package/dist/types/index.d.ts +6 -3
- package/package.json +14 -13
- package/dist/es/1~77.mjs.map +0 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
这是参考 `midscene-example` TodoMVC Test Runner 用例的 Web 版 Midscene Test 项目。直接使用 Playwright 的示例请看 [playwright-demo](https://github.com/web-infra-dev/midscene-example/tree/main/playwright-demo)。
|
|
4
4
|
|
|
5
|
-
如果创建时跳过安装,执行 `{{INSTALL_COMMAND}}`。安装后会自动生成 `midscene-node-spec.web.md`;若禁用了安装脚本,手动执行 `{{RUN_NODES_COMMAND}}`。复制 `.env.example` 为 `.env`,按[模型配置指南](https://midscenejs.com/model-
|
|
5
|
+
如果创建时跳过安装,执行 `{{INSTALL_COMMAND}}`。安装后会自动生成 `midscene-node-spec.web.md`;若禁用了安装脚本,手动执行 `{{RUN_NODES_COMMAND}}`。复制 `.env.example` 为 `.env`,按[模型配置指南](https://midscenejs.com/model-api-setup.html)配置模型。
|
|
6
6
|
|
|
7
7
|
配置后,在项目目录执行 `{{VERIFY_MODEL_COMMAND}}`,检查模型连接和 Midscene 兼容性。这一步不需要浏览器或设备;若检查失败,请先参考[模型调试与可观测性](https://midscenejs.com/model-debugging-observability.html)排查,再运行测试。
|
|
8
8
|
|
|
@@ -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
|
});
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { Awaitable } from '@midscene/core/internal/test-runner';
|
|
2
|
+
import type { CaseRunOutcome as CaseRunOutcome_2 } from '@midscene/core/internal/test-runner';
|
|
3
|
+
import type { CaseRunResult as CaseRunResult_2 } from '@midscene/core/internal/test-runner';
|
|
2
4
|
import { CommonAgentTestRunnerApi } from '@midscene/core/agent/test';
|
|
3
5
|
import type * as Core from '@midscene/core/internal/test-runner';
|
|
4
6
|
import type { ExecutionProjectDefinition as ExecutionProjectDefinition_2 } from '@midscene/core/internal/test-runner';
|
|
7
|
+
import { FileSetupContext } from '@midscene/core/internal/test-runner';
|
|
8
|
+
import { FileSetupDefinition } from '@midscene/core/internal/test-runner';
|
|
5
9
|
import type { IExecutionDump } from '@midscene/core';
|
|
6
10
|
import { JsonPrimitive } from '@midscene/core/internal/test-runner';
|
|
7
11
|
import { JsonValue } from '@midscene/core/internal/test-runner';
|
|
@@ -12,9 +16,13 @@ import { ProjectSetupDefinition } from '@midscene/core/internal/test-runner';
|
|
|
12
16
|
import { ProjectTeardown } from '@midscene/core/internal/test-runner';
|
|
13
17
|
import { ProjectTeardownContext } from '@midscene/core/internal/test-runner';
|
|
14
18
|
import { ResolvedExecutionProject } from '@midscene/core/internal/test-runner';
|
|
19
|
+
import type { StepExecutionInfo } from '@midscene/core/internal/test-runner';
|
|
20
|
+
import type { StepRunResult as StepRunResult_2 } from '@midscene/core/internal/test-runner';
|
|
15
21
|
import { TestFileSelection } from '@midscene/core/internal/test-runner';
|
|
16
22
|
import { TestRunReportDocumentPlan } from '@midscene/core';
|
|
23
|
+
import type { TestRunReportMetrics } from '@midscene/core';
|
|
17
24
|
import { TestTagSelection } from '@midscene/core/internal/test-runner';
|
|
25
|
+
import type { WorkflowDocumentRunResult as WorkflowDocumentRunResult_2 } from '@midscene/core/internal/test-runner';
|
|
18
26
|
import { WorkflowError } from '@midscene/core/internal/test-runner';
|
|
19
27
|
import { WorkflowReportSource } from '@midscene/core/internal/test-runner';
|
|
20
28
|
|
|
@@ -33,33 +41,41 @@ export declare const createTestRunId: (date?: Date, uuid?: `${string}-${string}-
|
|
|
33
41
|
|
|
34
42
|
export declare const DEFAULT_TEST_FILE_SELECTION: TestFileSelection;
|
|
35
43
|
|
|
44
|
+
export declare const defineFileSetup: <TContext, TProjectContext = unknown>(definition: FileSetupDefinition<TContext, TProjectContext>) => FileSetupDefinition<TContext, TProjectContext>;
|
|
45
|
+
|
|
36
46
|
declare type DefineNodeOptions<TInput = unknown, TData = unknown, TContext = unknown> = Omit<Core.DefineNodeOptions<TInput, TData, TContext>, 'execute'> & {
|
|
37
47
|
execute(ctx: NodeExecutionContext<TInput, TContext>): Core.NodeExecutionReturn<TData>;
|
|
38
48
|
};
|
|
39
49
|
|
|
40
50
|
export declare const defineProjectSetup: <TProjectContext>(definition: ProjectSetupDefinition<TProjectContext>) => ProjectSetupDefinition<TProjectContext>;
|
|
41
51
|
|
|
42
|
-
export declare const defineTestProject: <TContext = undefined>(definition: TestProjectDefinition<TContext>) => TestProjectDefinition<TContext>;
|
|
52
|
+
export declare const defineTestProject: <TContext = undefined, TProjectContext = TContext>(definition: TestProjectDefinition<TContext, TProjectContext>) => TestProjectDefinition<TContext, TProjectContext>;
|
|
43
53
|
|
|
44
54
|
export declare const discoverTestConfig: (projectRoot: string) => string | undefined;
|
|
45
55
|
|
|
46
56
|
export declare const discoverTestFiles: (projectRoot: string, selection?: TestFileSelection) => string[];
|
|
47
57
|
|
|
48
|
-
export declare type ExecutionProjectDefinition<TContext = unknown> = Omit<ExecutionProjectDefinition_2<
|
|
58
|
+
export declare type ExecutionProjectDefinition<TContext = unknown, TProjectContext = TContext> = Omit<ExecutionProjectDefinition_2<TProjectContext>, 'nodes' | 'fileSetup'> & {
|
|
59
|
+
fileSetup?: FileSetupDefinition<TContext, TProjectContext>;
|
|
49
60
|
nodes?: readonly NodeDefinition<any, any, TContext>[];
|
|
50
61
|
};
|
|
51
62
|
|
|
63
|
+
export { FileSetupContext }
|
|
64
|
+
|
|
65
|
+
export { FileSetupDefinition }
|
|
66
|
+
|
|
52
67
|
export { JsonPrimitive }
|
|
53
68
|
|
|
54
69
|
export { JsonValue }
|
|
55
70
|
|
|
56
|
-
export declare interface LoadedExecutionProject<TProjectContext =
|
|
71
|
+
export declare interface LoadedExecutionProject<TContext = unknown, TProjectContext = TContext> extends ResolvedExecutionProject<TProjectContext> {
|
|
72
|
+
readonly fileSetup?: FileSetupDefinition<TContext, TProjectContext>;
|
|
57
73
|
/** Effective Nodes: globals plus project-local overrides. */
|
|
58
74
|
readonly nodes: NodeRegistry;
|
|
59
75
|
}
|
|
60
76
|
|
|
61
|
-
export declare interface LoadedTestProject<TContext = undefined> {
|
|
62
|
-
projects: readonly LoadedExecutionProject<TContext>[];
|
|
77
|
+
export declare interface LoadedTestProject<TContext = undefined, TProjectContext = TContext> {
|
|
78
|
+
projects: readonly LoadedExecutionProject<TContext, TProjectContext>[];
|
|
63
79
|
hasExplicitProjects: boolean;
|
|
64
80
|
test: ResolvedTestOptions;
|
|
65
81
|
output: ResolvedTestOutputDefinition;
|
|
@@ -67,7 +83,7 @@ export declare interface LoadedTestProject<TContext = undefined> {
|
|
|
67
83
|
resolveNode(name: string): NodeDefinition<any, any, TContext> | undefined;
|
|
68
84
|
}
|
|
69
85
|
|
|
70
|
-
export declare function loadTestProject<TContext = undefined>(configPath?: string): Promise<LoadedTestProject<TContext>>;
|
|
86
|
+
export declare function loadTestProject<TContext = undefined, TProjectContext = TContext>(configPath?: string): Promise<LoadedTestProject<TContext, TProjectContext>>;
|
|
71
87
|
|
|
72
88
|
declare interface MidsceneExecutionRef {
|
|
73
89
|
id?: string;
|
|
@@ -126,7 +142,10 @@ export { ProjectTeardownContext }
|
|
|
126
142
|
export { ResolvedExecutionProject }
|
|
127
143
|
|
|
128
144
|
export declare interface ResolvedTestOptions {
|
|
129
|
-
|
|
145
|
+
maxConcurrentFiles: number | null;
|
|
146
|
+
maxConcurrentProjects: number | null;
|
|
147
|
+
setupTimeout: number;
|
|
148
|
+
teardownTimeout: number;
|
|
130
149
|
bail: number;
|
|
131
150
|
testTimeout: number;
|
|
132
151
|
}
|
|
@@ -161,7 +180,14 @@ export declare interface TestExecutionProjectRunResult {
|
|
|
161
180
|
export { TestFileSelection }
|
|
162
181
|
|
|
163
182
|
export declare interface TestOptions {
|
|
164
|
-
|
|
183
|
+
/** Global active files, including preparation and cleanup. Default 1; null is unlimited. */
|
|
184
|
+
maxConcurrentFiles?: number | null;
|
|
185
|
+
/** Projects from setup through completed cleanup. Default 1; null is unlimited. */
|
|
186
|
+
maxConcurrentProjects?: number | null;
|
|
187
|
+
/** Milliseconds for each Project setup/fileSetup, including device waits. */
|
|
188
|
+
setupTimeout?: number;
|
|
189
|
+
/** Milliseconds per cleanup scope; shared deadline during abnormal shutdown. */
|
|
190
|
+
teardownTimeout?: number;
|
|
165
191
|
bail?: number;
|
|
166
192
|
testTimeout?: number;
|
|
167
193
|
}
|
|
@@ -182,9 +208,10 @@ export declare interface TestProjectCollectionError {
|
|
|
182
208
|
error: WorkflowError;
|
|
183
209
|
}
|
|
184
210
|
|
|
185
|
-
export declare interface TestProjectDefinition<TContext = undefined> {
|
|
186
|
-
setup?: ProjectSetupDefinition<
|
|
187
|
-
|
|
211
|
+
export declare interface TestProjectDefinition<TContext = undefined, TProjectContext = TContext> {
|
|
212
|
+
setup?: ProjectSetupDefinition<TProjectContext>;
|
|
213
|
+
fileSetup?: FileSetupDefinition<TContext, TProjectContext>;
|
|
214
|
+
projects?: readonly ExecutionProjectDefinition<TContext, TProjectContext>[];
|
|
188
215
|
test?: TestOptions;
|
|
189
216
|
output?: TestOutputDefinition;
|
|
190
217
|
nodes?: readonly NodeDefinition<any, any, TContext>[];
|
|
@@ -197,9 +224,12 @@ export declare interface TestProjectRunOptions {
|
|
|
197
224
|
resultDir?: string;
|
|
198
225
|
projectNames?: readonly string[];
|
|
199
226
|
onProgress?(message: string): void;
|
|
227
|
+
onEvent?(event: TestReporterEvent): void;
|
|
200
228
|
}
|
|
201
229
|
|
|
202
230
|
export declare interface TestProjectRunResult {
|
|
231
|
+
/** Shared report usage, including every attempt and lifecycle phase. */
|
|
232
|
+
metrics?: TestRunReportMetrics;
|
|
203
233
|
operationLog?: {
|
|
204
234
|
path: string;
|
|
205
235
|
snapshotAt: string;
|
|
@@ -241,6 +271,72 @@ export declare interface TestProjectRunSummary {
|
|
|
241
271
|
projectFailures: number;
|
|
242
272
|
}
|
|
243
273
|
|
|
274
|
+
/** Structured presentation facts; execution and persistence do not depend on a reporter. */
|
|
275
|
+
declare type TestReporterEvent = {
|
|
276
|
+
type: 'project-end';
|
|
277
|
+
project: TestExecutionProjectRunResult;
|
|
278
|
+
} | {
|
|
279
|
+
type: 'project-phase';
|
|
280
|
+
projectName: string;
|
|
281
|
+
phase: 'setup' | 'running' | 'teardown';
|
|
282
|
+
} | {
|
|
283
|
+
type: 'run-start';
|
|
284
|
+
projects: number;
|
|
285
|
+
documents: number;
|
|
286
|
+
cases: number;
|
|
287
|
+
projectRoot?: string;
|
|
288
|
+
configPath?: string;
|
|
289
|
+
startedAt?: string;
|
|
290
|
+
maxConcurrentProjects?: number | null;
|
|
291
|
+
maxConcurrentFiles?: number | null;
|
|
292
|
+
configuredModels?: readonly {
|
|
293
|
+
name: string;
|
|
294
|
+
intents: readonly string[];
|
|
295
|
+
}[];
|
|
296
|
+
files?: readonly {
|
|
297
|
+
documentId: string;
|
|
298
|
+
projectName: string;
|
|
299
|
+
sourcePath: string;
|
|
300
|
+
cases: number;
|
|
301
|
+
}[];
|
|
302
|
+
} | {
|
|
303
|
+
type: 'document-start';
|
|
304
|
+
documentId: string;
|
|
305
|
+
projectName: string;
|
|
306
|
+
sourcePath: string;
|
|
307
|
+
cases: number;
|
|
308
|
+
} | {
|
|
309
|
+
type: 'document-end';
|
|
310
|
+
document: WorkflowDocumentRunResult_2;
|
|
311
|
+
cases: readonly CaseRunOutcome_2[];
|
|
312
|
+
durationMs: number;
|
|
313
|
+
} | {
|
|
314
|
+
type: 'step-start';
|
|
315
|
+
projectName: string;
|
|
316
|
+
info: StepExecutionInfo;
|
|
317
|
+
} | {
|
|
318
|
+
type: 'step-end';
|
|
319
|
+
projectName: string;
|
|
320
|
+
info: StepExecutionInfo;
|
|
321
|
+
result?: StepRunResult_2;
|
|
322
|
+
} | {
|
|
323
|
+
type: 'case-attempt-end';
|
|
324
|
+
attempt: CaseRunResult_2;
|
|
325
|
+
maxAttempts: number;
|
|
326
|
+
} | {
|
|
327
|
+
type: 'case-progress';
|
|
328
|
+
outcome: CaseRunOutcome_2;
|
|
329
|
+
documentAttemptIndex: number;
|
|
330
|
+
} | {
|
|
331
|
+
type: 'case-end';
|
|
332
|
+
outcome: CaseRunOutcome_2;
|
|
333
|
+
documentRetries?: number;
|
|
334
|
+
} | {
|
|
335
|
+
type: 'document-retry';
|
|
336
|
+
document: WorkflowDocumentRunResult_2;
|
|
337
|
+
maxAttempts: number;
|
|
338
|
+
};
|
|
339
|
+
|
|
244
340
|
export { TestTagSelection }
|
|
245
341
|
|
|
246
342
|
export declare const validateTestFileSelection: (value: unknown, label?: string) => TestFileSelection | undefined;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -49,6 +49,7 @@ import { StepValue } from '@midscene/core/internal/test-runner';
|
|
|
49
49
|
import { TestFileSelection } from '@midscene/core/internal/test-runner';
|
|
50
50
|
import { TestRunReportDocumentPlan } from '@midscene/core';
|
|
51
51
|
import type { TestRunReportDump } from '@midscene/core';
|
|
52
|
+
import type { TestRunReportMetrics } from '@midscene/core';
|
|
52
53
|
import type { TestRunReportSourceIndex } from '@midscene/core';
|
|
53
54
|
import { TestTagSelection } from '@midscene/core/internal/test-runner';
|
|
54
55
|
import { WorkflowDocumentDefinition } from '@midscene/core/internal/test-runner';
|
|
@@ -129,7 +130,7 @@ export declare const createCaseRunner: <TContext = undefined>(options?: CaseRunn
|
|
|
129
130
|
|
|
130
131
|
export declare const createDocumentRuntime: <TContext = undefined>(document: CollectedWorkflowDocument, options: CreateDocumentRuntimeOptions<TContext>) => WorkflowDocumentRuntime<TContext>;
|
|
131
132
|
|
|
132
|
-
export declare type CreateDocumentRuntimeOptions<TContext = undefined> = Omit<Core.CreateDocumentRuntimeOptions<TContext>, 'documentSetup' | 'documentAttemptIndex' | 'resolveNode' | 'onStepResult' | 'onResult'> & {
|
|
133
|
+
export declare type CreateDocumentRuntimeOptions<TContext = undefined> = Omit<Core.CreateDocumentRuntimeOptions<TContext>, 'documentSetup' | 'setupTimeoutMs' | 'teardownTimeoutMs' | 'documentAttemptIndex' | 'resolveNode' | 'onStepResult' | 'onResult'> & {
|
|
133
134
|
resolveNode(name: string): NodeDefinition<any, any, TContext>;
|
|
134
135
|
onStepResult?: StepResultHandler;
|
|
135
136
|
onResult?(result: WorkflowDocumentRunResult): Core.Awaitable<unknown>;
|
|
@@ -262,7 +263,7 @@ export declare interface ResolveWorkflowVariablesOptions {
|
|
|
262
263
|
location: VariableResolutionLocation;
|
|
263
264
|
}
|
|
264
265
|
|
|
265
|
-
export declare type RunCollectedCaseOptions<TContext = undefined> = Omit<Core.RunCollectedCaseOptions<TContext>, 'reportScopeId' | 'resolveNode' | 'beforeEach' | 'afterEach' | 'onStepResult' | 'onResult'> & {
|
|
266
|
+
export declare type RunCollectedCaseOptions<TContext = undefined> = Omit<Core.RunCollectedCaseOptions<TContext>, 'reportScopeId' | 'teardownTimeoutMs' | 'resolveNode' | 'beforeEach' | 'afterEach' | 'onStepResult' | 'onResult'> & {
|
|
266
267
|
resolveNode(name: string): NodeDefinition<any, any, TContext>;
|
|
267
268
|
beforeEach?: readonly NormalizedStep[];
|
|
268
269
|
afterEach?: readonly NormalizedStep[];
|
|
@@ -272,7 +273,7 @@ export declare type RunCollectedCaseOptions<TContext = undefined> = Omit<Core.Ru
|
|
|
272
273
|
|
|
273
274
|
export declare const runWorkflowDocument: <TContext = undefined>(document: CollectedWorkflowDocument, options: RunWorkflowDocumentOptions<TContext>) => Promise<WorkflowDocumentExecutionResult>;
|
|
274
275
|
|
|
275
|
-
export declare type RunWorkflowDocumentOptions<TContext = undefined> = Omit<Core.RunWorkflowDocumentOptions<TContext>, 'documentSetup' | 'documentAttemptIndex' | 'resolveCaseReportScopeId' | 'resolveNode' | 'onStepResult' | 'onCaseStart' | 'isFatalError' | 'onCaseResult' | 'onCaseOutcome' | 'onDocumentResult' | 'createCaseRunId' | 'createDocumentRunId'> & {
|
|
276
|
+
export declare type RunWorkflowDocumentOptions<TContext = undefined> = Omit<Core.RunWorkflowDocumentOptions<TContext>, 'documentSetup' | 'setupTimeoutMs' | 'teardownTimeoutMs' | 'documentAttemptIndex' | 'resolveCaseReportScopeId' | 'resolveNode' | 'onStepResult' | 'onCaseStart' | 'isFatalError' | 'onCaseResult' | 'onCaseOutcome' | 'onDocumentResult' | 'createCaseRunId' | 'createDocumentRunId'> & {
|
|
276
277
|
resolveNode(name: string): NodeDefinition<any, any, TContext>;
|
|
277
278
|
onStepResult?: StepResultHandler;
|
|
278
279
|
onCaseStart?(collectedCase: CollectedCase): Core.Awaitable<void>;
|
|
@@ -337,6 +338,8 @@ declare interface TestProjectCollectionError {
|
|
|
337
338
|
}
|
|
338
339
|
|
|
339
340
|
declare interface TestProjectRunResult {
|
|
341
|
+
/** Shared report usage, including every attempt and lifecycle phase. */
|
|
342
|
+
metrics?: TestRunReportMetrics;
|
|
340
343
|
operationLog?: {
|
|
341
344
|
path: string;
|
|
342
345
|
snapshotAt: string;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@midscene/test",
|
|
3
3
|
"description": "Midscene Test: an extensible YAML testing framework and Node SDK.",
|
|
4
|
-
"version": "1.16.
|
|
4
|
+
"version": "1.16.2",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "https://github.com/web-infra-dev/midscene.git",
|
|
@@ -54,12 +54,13 @@
|
|
|
54
54
|
"http-server": "14.1.1",
|
|
55
55
|
"js-yaml": "4.1.0",
|
|
56
56
|
"lodash.merge": "4.6.2",
|
|
57
|
+
"string-width": "4.2.3",
|
|
57
58
|
"tinyglobby": "0.2.15",
|
|
58
59
|
"tsx": "^4.19.2",
|
|
59
60
|
"yargs": "17.7.3",
|
|
60
61
|
"zod": "^3.25.1",
|
|
61
|
-
"@midscene/core": "1.16.
|
|
62
|
-
"@midscene/shared": "1.16.
|
|
62
|
+
"@midscene/core": "1.16.2",
|
|
63
|
+
"@midscene/shared": "1.16.2"
|
|
63
64
|
},
|
|
64
65
|
"devDependencies": {
|
|
65
66
|
"@rslib/core": "^0.18.3",
|
|
@@ -71,20 +72,20 @@
|
|
|
71
72
|
"puppeteer": "24.6.0",
|
|
72
73
|
"typescript": "^5.8.3",
|
|
73
74
|
"vitest": "3.0.5",
|
|
74
|
-
"@midscene/
|
|
75
|
-
"@midscene/
|
|
76
|
-
"@midscene/
|
|
77
|
-
"@midscene/
|
|
78
|
-
"@midscene/
|
|
75
|
+
"@midscene/computer": "1.16.2",
|
|
76
|
+
"@midscene/ios": "1.16.2",
|
|
77
|
+
"@midscene/web": "1.16.2",
|
|
78
|
+
"@midscene/harmony": "1.16.2",
|
|
79
|
+
"@midscene/android": "1.16.2"
|
|
79
80
|
},
|
|
80
81
|
"peerDependencies": {
|
|
81
82
|
"playwright": "^1.45.0",
|
|
82
83
|
"puppeteer": "24.6.0",
|
|
83
|
-
"@midscene/android": "1.16.
|
|
84
|
-
"@midscene/
|
|
85
|
-
"@midscene/
|
|
86
|
-
"@midscene/
|
|
87
|
-
"@midscene/web": "1.16.
|
|
84
|
+
"@midscene/android": "1.16.2",
|
|
85
|
+
"@midscene/harmony": "1.16.2",
|
|
86
|
+
"@midscene/ios": "1.16.2",
|
|
87
|
+
"@midscene/computer": "1.16.2",
|
|
88
|
+
"@midscene/web": "1.16.2"
|
|
88
89
|
},
|
|
89
90
|
"peerDependenciesMeta": {
|
|
90
91
|
"@midscene/android": {
|
package/dist/es/1~77.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"1~77.mjs","sources":["../../src/cli/dotenv-loader.ts","../../src/cli/model-command.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport dotenv from 'dotenv';\n\nexport interface DotenvLoadOptions {\n dotenvOverride?: boolean;\n dotenvDebug?: boolean;\n cwd?: string;\n log?: (message: string) => void;\n}\n\nexport function loadDotenvConfig(options: DotenvLoadOptions = {}) {\n const dotEnvConfigFile = join(options.cwd ?? process.cwd(), '.env');\n if (!existsSync(dotEnvConfigFile)) {\n return;\n }\n\n options.log?.(` Env file: ${dotEnvConfigFile}`);\n dotenv.config({\n path: dotEnvConfigFile,\n debug: options.dotenvDebug,\n override: options.dotenvOverride,\n });\n}\n","import { runConnectivityTest } from '@midscene/core';\nimport {\n type IModelConfig,\n type TIntent,\n globalModelConfigManager,\n} from '@midscene/shared/env';\nimport chalk from 'chalk';\nimport { loadDotenvConfig } from './dotenv-loader';\n\nconst DEFAULT_OPENAI_BASE_URL = 'https://api.openai.com/v1';\nconst MODEL_VERIFY_SEPARATOR = '────────────────────────────────────────';\nconst modelCommandUsage = (commandName: string) => `Usage:\n ${commandName} model verify\n\nVerify model connectivity and Midscene compatibility for default, locate, planning, and insight models.\nReads .env from the current working directory, overriding shell environment variables.\nShows endpoints, model names, request counts, and per-check results with duration.\nReturns exit code 0 on success and 1 on failure.\n\nOptions:\n --help, -h Show this help\n`;\n\ninterface ModelCommandIO {\n stdout: (message: string) => void;\n stderr: (message: string) => void;\n}\n\ninterface ModelCommandDeps {\n loadDotenv: () => void;\n getModelConfig: (intent: TIntent) => IModelConfig;\n verifyModel: typeof runConnectivityTest;\n}\n\nexport interface CurlCommandItem {\n intents: TIntent[];\n curl: string;\n usesDefaultBaseURL: boolean;\n}\n\nfunction assertNoModelVerifyOptions(args: string[], commandName: string) {\n for (const arg of args) {\n throw new Error(`Unknown option for ${commandName} model verify: ${arg}`);\n }\n}\n\nfunction formatTable(headers: string[], rows: string[][]): string {\n const widths = headers.map((header, index) =>\n Math.max(header.length, ...rows.map((row) => row[index].length)),\n );\n const formatRow = (row: string[]) =>\n row\n .map((cell, index) => cell.padEnd(widths[index]))\n .join(' ')\n .trimEnd();\n return [formatRow(headers), ...rows.map(formatRow)].join('\\n');\n}\n\nfunction displayEndpoint(endpoint: string): string {\n try {\n const url = new URL(endpoint);\n url.username = '';\n url.password = '';\n url.search = '';\n url.hash = '';\n return url.toString();\n } catch {\n return '(invalid endpoint URL)';\n }\n}\n\nfunction shellSingleQuote(value: string): string {\n return `'${value.replace(/'/g, `'\\\\''`)}'`;\n}\n\nfunction buildChatCompletionsUrl(baseURL?: string): string {\n const normalizedBaseURL = (baseURL || DEFAULT_OPENAI_BASE_URL).replace(\n /\\/+$/,\n '',\n );\n if (normalizedBaseURL.endsWith('/chat/completions')) {\n return normalizedBaseURL;\n }\n return `${normalizedBaseURL}/chat/completions`;\n}\n\nfunction buildCurlCommand(modelConfig: IModelConfig): string {\n const payload = {\n model: modelConfig.modelName,\n messages: [{ role: 'user', content: 'What is 1+1?' }],\n };\n\n return [\n `curl -X POST ${shellSingleQuote(buildChatCompletionsUrl(modelConfig.openaiBaseURL))} \\\\`,\n ` -H ${shellSingleQuote(`Authorization: Bearer ${modelConfig.openaiApiKey || ''}`)} \\\\`,\n ` -H ${shellSingleQuote('Content-Type: application/json')} \\\\`,\n ` -d ${shellSingleQuote(JSON.stringify(payload, null, 2))}`,\n ].join('\\n');\n}\n\nfunction buildCurlDedupKey(modelConfig: IModelConfig): string {\n return JSON.stringify({\n baseURL: modelConfig.openaiBaseURL || DEFAULT_OPENAI_BASE_URL,\n apiKey: modelConfig.openaiApiKey || '',\n modelName: modelConfig.modelName,\n });\n}\n\nexport function buildModelVerifyCurlCommands(\n configs: Array<{ intent: TIntent; modelConfig: IModelConfig }>,\n): CurlCommandItem[] {\n const commandMap = new Map<string, CurlCommandItem>();\n\n for (const item of configs) {\n const key = buildCurlDedupKey(item.modelConfig);\n const existing = commandMap.get(key);\n if (existing) {\n existing.intents.push(item.intent);\n continue;\n }\n commandMap.set(key, {\n intents: [item.intent],\n curl: buildCurlCommand(item.modelConfig),\n usesDefaultBaseURL: !item.modelConfig.openaiBaseURL,\n });\n }\n\n return [...commandMap.values()];\n}\n\nfunction formatModelVerifyFailureOutput(\n message: string | undefined,\n curlCommands: CurlCommandItem[],\n): string {\n const details = message?.trim() || 'No failure details were generated.';\n const curlSection = curlCommands\n .map((item) => {\n const baseUrlNote = item.usesDefaultBaseURL\n ? ' (base URL not configured; using OpenAI SDK default)'\n : '';\n return chalk.gray(\n `# ${item.intents.join(', ')}${baseUrlNote}\\n${item.curl}`,\n );\n })\n .join('\\n\\n');\n\n return [\n chalk.red.bold('❌ Model verify failed with messages:'),\n MODEL_VERIFY_SEPARATOR,\n '',\n details,\n '',\n MODEL_VERIFY_SEPARATOR,\n 'Generated curl requests for basic API connectivity:',\n 'If the error is a basic connectivity issue, use these requests to test the base URL, API key, and model name directly.',\n 'These commands contain your API key. Do not share them publicly.',\n '',\n curlSection,\n ].join('\\n');\n}\n\nfunction getDefaultDeps(io: ModelCommandIO): ModelCommandDeps {\n return {\n loadDotenv: () => {\n loadDotenvConfig({\n dotenvDebug: true,\n dotenvOverride: true,\n log: io.stdout,\n });\n },\n getModelConfig: (intent) => globalModelConfigManager.getModelConfig(intent),\n verifyModel: runConnectivityTest,\n };\n}\n\nasync function runModelVerifyCommand(\n args: string[],\n deps: ModelCommandDeps,\n io: ModelCommandIO,\n commandName: string,\n): Promise<number> {\n try {\n if (args.includes('--help') || args.includes('-h')) {\n io.stdout(modelCommandUsage(commandName));\n return 0;\n }\n\n assertNoModelVerifyOptions(args, commandName);\n io.stdout(\n 'Model verify started. This usually completes within 30 seconds.',\n );\n deps.loadDotenv();\n io.stdout('');\n\n const defaultModelConfig = deps.getModelConfig('default');\n const locateModelConfig = deps.getModelConfig('locate');\n const planningModelConfig = deps.getModelConfig('planning');\n const insightModelConfig = deps.getModelConfig('insight');\n const curlCommands = buildModelVerifyCurlCommands([\n { intent: 'default', modelConfig: defaultModelConfig },\n { intent: 'locate', modelConfig: locateModelConfig },\n { intent: 'planning', modelConfig: planningModelConfig },\n { intent: 'insight', modelConfig: insightModelConfig },\n ]);\n\n const baseURLs = new Map<string, number>();\n const modelRows = (\n [\n ['planning', planningModelConfig, 'Text response'],\n ['insight', insightModelConfig, 'Image understanding'],\n ['locate', locateModelConfig, 'AI locate'],\n ['default', defaultModelConfig, 'Text response'],\n ] as const\n ).map(([intent, config, check]) => {\n const baseURL = config.openaiExtraConfig?.baseURL ?? config.openaiBaseURL;\n const endpoint = baseURL\n ? displayEndpoint(String(baseURL))\n : 'provider default';\n if (!baseURLs.has(endpoint)) baseURLs.set(endpoint, baseURLs.size + 1);\n return [intent, config.modelName, check, `[${baseURLs.get(endpoint)}]`];\n });\n io.stdout(\n formatTable(['Model role', 'Model', 'Check', 'Endpoint'], modelRows),\n );\n io.stdout('');\n for (const [endpoint, index] of baseURLs) {\n io.stdout(`[${index}] Base URL: ${endpoint}`);\n }\n io.stdout('\\nRunning 4 checks...\\n');\n\n const result = await deps.verifyModel({\n defaultModelConfig,\n locateModelConfig,\n planningModelConfig,\n insightModelConfig,\n });\n\n const checkLabels = {\n text: 'Text response',\n vision: 'Image understanding',\n aiLocate: 'AI locate',\n };\n io.stdout(\n formatTable(\n ['Model role', 'Check', 'Result', 'Requests', 'Time'],\n result.checks.map((check) => [\n check.intent,\n checkLabels[check.name],\n check.passed ? 'PASS' : 'FAIL',\n String(check.requestCount),\n `${check.durationMs} ms`,\n ]),\n ),\n );\n const requestEndpoints = new Map<string, Set<string>>();\n for (const check of result.checks) {\n for (const endpoint of check.endpoints) {\n const display = displayEndpoint(endpoint);\n if (!requestEndpoints.has(display))\n requestEndpoints.set(display, new Set());\n requestEndpoints.get(display)!.add(check.intent);\n }\n if (!check.passed && check.message)\n io.stdout(`\\n${check.intent}: ${check.message}`);\n }\n if (requestEndpoints.size > 0) {\n io.stdout('\\nRequest endpoints:');\n for (const [endpoint, intents] of requestEndpoints) {\n io.stdout(` ${[...intents].join(', ')}: ${endpoint}`);\n }\n }\n io.stdout('');\n const passedChecks = result.checks.filter((check) => check.passed).length;\n io.stdout(\n `Requests sent: ${result.requestCount}. Checks: ${passedChecks} passed, ${result.checks.length - passedChecks} failed.`,\n );\n\n if (result.passed) {\n io.stdout('✅ Model verify passed.');\n return 0;\n }\n\n io.stderr(formatModelVerifyFailureOutput(result.message, curlCommands));\n return 1;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n io.stderr(\n [\n MODEL_VERIFY_SEPARATOR,\n '❌ Model verify failed with messages:',\n '',\n message,\n MODEL_VERIFY_SEPARATOR,\n ].join('\\n'),\n );\n return 1;\n }\n}\n\nexport async function runModelCommand(\n rawArgs: string[],\n deps?: Partial<ModelCommandDeps>,\n io: ModelCommandIO = {\n stdout: console.log,\n stderr: console.error,\n },\n commandName = 'midscene-test',\n): Promise<number> {\n const [, action, ...restArgs] = rawArgs;\n const mergedDeps = {\n ...getDefaultDeps(io),\n ...deps,\n };\n\n if (!action || action === '--help' || action === '-h') {\n io.stdout(modelCommandUsage(commandName));\n return 0;\n }\n\n if (action === 'verify') {\n return runModelVerifyCommand(restArgs, mergedDeps, io, commandName);\n }\n\n if (action === 'eval') {\n io.stderr(\n `${commandName} model eval is not implemented yet. It is reserved for future model evaluation suites.`,\n );\n return 1;\n }\n\n io.stderr(\n `Unknown ${commandName} model command: ${action}\\n\\n${modelCommandUsage(commandName)}`,\n );\n return 1;\n}\n\nexport { loadDotenvConfig, type DotenvLoadOptions } from './dotenv-loader';\n"],"names":["loadDotenvConfig","options","dotEnvConfigFile","join","process","existsSync","dotenv","DEFAULT_OPENAI_BASE_URL","MODEL_VERIFY_SEPARATOR","modelCommandUsage","commandName","assertNoModelVerifyOptions","args","arg","Error","formatTable","headers","rows","widths","header","index","Math","row","formatRow","cell","displayEndpoint","endpoint","url","URL","shellSingleQuote","value","buildChatCompletionsUrl","baseURL","normalizedBaseURL","buildCurlCommand","modelConfig","payload","JSON","buildCurlDedupKey","buildModelVerifyCurlCommands","configs","commandMap","Map","item","key","existing","formatModelVerifyFailureOutput","message","curlCommands","details","curlSection","baseUrlNote","chalk","getDefaultDeps","io","intent","globalModelConfigManager","runConnectivityTest","runModelVerifyCommand","deps","defaultModelConfig","locateModelConfig","planningModelConfig","insightModelConfig","baseURLs","modelRows","config","check","String","result","checkLabels","requestEndpoints","display","Set","intents","passedChecks","error","runModelCommand","rawArgs","console","action","restArgs","mergedDeps"],"mappings":"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"}
|