android2harmony 0.1.3 → 0.1.5
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/README.md +1 -407
- package/agents/self-tester.md +376 -339
- package/dist/index.js +131 -586
- package/dist/index.js.map +4 -4
- package/package.json +36 -37
- package/skills/a2h-resource-convert/SKILL.md +902 -0
- package/skills/a2h-resource-convert/references/code-vector-icon-rules.md +335 -0
- package/skills/{hmos-resources-convert → a2h-resource-convert}/references/conversion-rules.md +15 -2
- package/skills/{hmos-resources-convert → a2h-resource-convert}/references/dependency-analysis-rules.md +24 -2
- package/skills/a2h-resource-convert/references/lottie-conversion-rules.md +219 -0
- package/skills/{hmos-resources-convert → a2h-resource-convert}/references/resource-mapping-rules.md +27 -2
- package/skills/{hmos-resources-convert → a2h-resource-convert}/references/xml-drawable-to-svg-rules.md +118 -1
- package/skills/a2h-resource-convert/scripts/a2h_resource_convert.js +2166 -0
- package/skills/a2h-resource-convert/scripts/code_vector_icons.js +607 -0
- package/skills/a2h-resource-convert/scripts/package.json +3 -0
- package/skills/a2h-resource-convert/scripts/svg_fidelity_check.js +632 -0
- package/skills/a2h-ui-transfer/SKILL.md +420 -0
- package/skills/a2h-ui-transfer/references/conversion-procedure.md +572 -0
- package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/_/350/214/203/345/274/217/351/200/211/346/213/251/350/257/264/346/230/216.md +1 -1
- package/skills/a2h-ui-transfer/references/mvvm-v2/_/350/243/205/351/245/260/345/231/250/351/200/237/346/237/245.md +133 -0
- package/skills/{hmos-batch-ui-align/scripts/android_parse_fast.ts → a2h-ui-transfer/scripts/android_parse_fast.js} +397 -283
- package/skills/a2h-ui-transfer/scripts/arkts_static_check.js +1624 -0
- package/skills/a2h-ui-transfer/scripts/measure_pack.js +1005 -0
- package/skills/a2h-ui-transfer/scripts/package.json +3 -0
- package/skills/hmos-incremental-ui-align/README.md +251 -251
- package/skills/hmos-incremental-ui-align/SKILL.md +364 -364
- package/skills/hmos-incremental-ui-align/references/State_Model_Template.md +2 -2
- package/skills/hmos-incremental-ui-align/scripts/app_feature_verify.js +790 -0
- package/skills/hmos-incremental-ui-align/scripts/extract_checklist.js +285 -0
- package/skills/hmos-incremental-ui-align/scripts/navigation-capure.md +76 -76
- package/skills/hmos-incremental-ui-align/scripts/page_capture.js +756 -0
- package/skills/hmos-incremental-ui-align/scripts/page_capture_burst.js +155 -0
- package/skills/hmos-batch-ui-align/SKILL.md +0 -141
- package/skills/hmos-batch-ui-align/references/conversion-procedure.md +0 -217
- package/skills/hmos-incremental-ui-align/scripts/app_feature_verify.ts +0 -999
- package/skills/hmos-incremental-ui-align/scripts/extract_checklist.ts +0 -343
- package/skills/hmos-incremental-ui-align/scripts/page_capture.ts +0 -977
- package/skills/hmos-incremental-ui-align/scripts/page_capture_burst.ts +0 -188
- package/skills/hmos-resources-convert/SKILL.md +0 -654
- package/skills/hmos-resources-convert/template/AppScope/app.json5 +0 -10
- package/skills/hmos-resources-convert/template/AppScope/resources/base/element/string.json +0 -8
- package/skills/hmos-resources-convert/template/AppScope/resources/base/media/background.png +0 -0
- package/skills/hmos-resources-convert/template/AppScope/resources/base/media/foreground.png +0 -0
- package/skills/hmos-resources-convert/template/AppScope/resources/base/media/layered_image.json +0 -7
- package/skills/hmos-resources-convert/template/build-profile.json5 +0 -42
- package/skills/hmos-resources-convert/template/code-linter.json5 +0 -32
- package/skills/hmos-resources-convert/template/entry/build-profile.json5 +0 -33
- package/skills/hmos-resources-convert/template/entry/hvigorfile.ts +0 -6
- package/skills/hmos-resources-convert/template/entry/obfuscation-rules.txt +0 -23
- package/skills/hmos-resources-convert/template/entry/oh-package.json5 +0 -10
- package/skills/hmos-resources-convert/template/entry/src/main/ets/entryability/EntryAbility.ets +0 -48
- package/skills/hmos-resources-convert/template/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets +0 -16
- package/skills/hmos-resources-convert/template/entry/src/main/ets/pages/Index.ets +0 -23
- package/skills/hmos-resources-convert/template/entry/src/main/module.json5 +0 -55
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/color.json +0 -8
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/float.json +0 -8
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/string.json +0 -16
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/background.png +0 -0
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/foreground.png +0 -0
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/layered_image.json +0 -7
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/startIcon.png +0 -0
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/profile/backup_config.json +0 -3
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/profile/main_pages.json +0 -5
- package/skills/hmos-resources-convert/template/entry/src/main/resources/dark/element/color.json +0 -8
- package/skills/hmos-resources-convert/template/entry/src/mock/mock-config.json5 +0 -2
- package/skills/hmos-resources-convert/template/entry/src/ohosTest/ets/test/Ability.test.ets +0 -35
- package/skills/hmos-resources-convert/template/entry/src/ohosTest/ets/test/List.test.ets +0 -5
- package/skills/hmos-resources-convert/template/entry/src/ohosTest/module.json5 +0 -16
- package/skills/hmos-resources-convert/template/entry/src/test/List.test.ets +0 -5
- package/skills/hmos-resources-convert/template/entry/src/test/LocalUnit.test.ets +0 -33
- package/skills/hmos-resources-convert/template/hvigor/hvigor-config.json5 +0 -23
- package/skills/hmos-resources-convert/template/hvigorfile.ts +0 -6
- package/skills/hmos-resources-convert/template/oh-package-lock.json5 +0 -28
- package/skills/hmos-resources-convert/template/oh-package.json5 +0 -10
- package/tools/autotest/deps/autotest-agent-0.1.1.tgz +0 -0
- package/tools/autotest/engine/batch-launcher.ts +0 -326
- package/tools/autotest/engine/report-tool.ts +0 -773
- package/tools/autotest/engine/self-test-runner.ts +0 -1024
- package/tools/autotest/engine/testcases-tool.ts +0 -246
- package/tools/autotest/resolve-metadata-tool.ts +0 -143
- package/tools/autotest/runner/logger.ts +0 -45
- package/tools/autotest/runner/process-utils.ts +0 -41
- package/tools/autotest/validate.ts +0 -115
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mappings/android-to-harmonyOS-ui-atomic-component-mapping-reference.md +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mappings/android-to-harmonyOS-ui-interaction-mapping-reference.md +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mappings/android-to-harmonyOS-ui-layout-mapping-reference.md +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Link/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/217/214/345/220/221/345/220/214/346/255/245.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Observed/350/243/205/351/245/260/345/231/250/345/222/214@ObjectLink/350/243/205/351/245/260/345/231/250/357/274/232/345/265/214/345/245/227/347/261/273/345/257/271/350/261/241/345/261/236/346/200/247/345/217/230/345/214/226.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Prop/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/215/225/345/220/221/345/220/214/346/255/245.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Provide/350/243/205/351/245/260/345/231/250/345/222/214@Consume/350/243/205/351/245/260/345/231/250/357/274/232/344/270/216/345/220/216/344/273/243/347/273/204/344/273/266/345/217/214/345/220/221/345/220/214/346/255/245.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@State/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/347/212/266/346/200/201.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Track/350/243/205/351/245/260/345/231/250/357/274/232class/345/257/271/350/261/241/345/261/236/346/200/247/347/272/247/346/233/264/346/226/260.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Watch/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/346/233/264/346/224/271/351/200/232/347/237/245.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/AppStorage/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200/347/232/204UI/347/212/266/346/200/201/345/255/230/345/202/250.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/Environment/357/274/232/350/256/276/345/244/207/347/216/257/345/242/203/346/237/245/350/257/242.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/LocalStorage/357/274/232/351/241/265/351/235/242/347/272/247UI/347/212/266/346/200/201/345/255/230/345/202/250.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/MVVM/346/250/241/345/274/217/357/274/210V1/357/274/211.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/PersistentStorage/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm//347/256/241/347/220/206/345/272/224/347/224/250/346/213/245/346/234/211/347/232/204/347/212/266/346/200/201/346/246/202/350/277/260.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/!!/350/257/255/346/263/225/357/274/232/345/217/214/345/220/221/347/273/221/345/256/232.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Computed/350/243/205/351/245/260/345/231/250/357/274/232/350/256/241/347/256/227/345/261/236/346/200/247.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Event/350/243/205/351/245/260/345/231/250/357/274/232/350/247/204/350/214/203/347/273/204/344/273/266/350/276/223/345/207/272.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Local/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/351/203/250/347/212/266/346/200/201.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Monitor/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/344/277/256/346/224/271/345/274/202/346/255/245/347/233/221/345/220/254.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@ObservedV2/350/243/205/351/245/260/345/231/250/345/222/214@Trace/350/243/205/351/245/260/345/231/250/357/274/232/347/261/273/345/261/236/346/200/247/345/217/230/345/214/226/350/247/202/346/265/213.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Once/350/243/205/351/245/260/345/231/250/357/274/232/345/210/235/345/247/213/345/214/226/345/220/214/346/255/245/344/270/200/346/254/241.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Param/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/244/226/351/203/250/350/276/223/345/205/245.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Provider/350/243/205/351/245/260/345/231/250/345/222/214@Consumer/350/243/205/351/245/260/345/231/250/357/274/232/350/267/250/347/273/204/344/273/266/345/261/202/347/272/247/345/217/214/345/220/221/345/220/214/346/255/245.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Type/350/243/205/351/245/260/345/231/250/357/274/232/346/240/207/350/256/260/347/261/273/345/261/236/346/200/247/347/232/204/347/261/273/345/236/213.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/AppStorageV2/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200UI/347/212/266/346/200/201/345/255/230/345/202/250.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/MVVM/346/250/241/345/274/217/357/274/210V2/357/274/211.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/PersistenceV2/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2//347/212/266/346/200/201/347/256/241/347/220/206V1/345/220/221V2/350/277/201/347/247/273/344/270/216/346/267/267/347/224/250/346/214/207/345/257/274.md" +0 -0
|
@@ -1,326 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Batch launcher — the detached background process that runs the Node.js
|
|
3
|
-
* `@autotest/agent` engine programmatically via `new AutoTestAgent({...})`.
|
|
4
|
-
*
|
|
5
|
-
* Spawned detached by `self-test-runner.ts` `cmdRun` (which writes `batch.pid` +
|
|
6
|
-
* emits `{status:'RUNNING', pid}` then exits). This process:
|
|
7
|
-
*
|
|
8
|
-
* 1. constructs `new AutoTestAgent({ apiKey, modelName, baseUrl, provider,
|
|
9
|
-
* deviceSn, ip, port, agentMode })` from the `AUTOTEST_*` env vars that
|
|
10
|
-
* `cmdRun` derived from `~/.hometrans/config.json` — so the user's API_KEY
|
|
11
|
-
* / model / device flow in through the constructor, no config.yaml needed;
|
|
12
|
-
* 2. reads the testcases (JSON array or JSONL — same input as the former
|
|
13
|
-
* Python `AutoTest.batch`);
|
|
14
|
-
* 3. runs each case via `agent.runTask(test_steps, { reportDir, taskName })`;
|
|
15
|
-
* 4. writes `task_results.jsonl` + `summary.json` in the SAME schema the
|
|
16
|
-
* Python `AutoTest.batch` produced, so the HomeTrans `status` /
|
|
17
|
-
* `report-tool` contract keeps working unchanged.
|
|
18
|
-
*
|
|
19
|
-
* STDOUT/STDERR are captured to `batch_stdout.log` by the spawner; this process
|
|
20
|
-
* exits 0 on completion regardless of per-case pass/fail (a non-zero exit would
|
|
21
|
-
* be interpreted as a launch failure by `spawnBatch`).
|
|
22
|
-
*/
|
|
23
|
-
import { createRequire } from 'node:module';
|
|
24
|
-
import fs from 'node:fs';
|
|
25
|
-
import path from 'node:path';
|
|
26
|
-
import { fileURLToPath } from 'node:url';
|
|
27
|
-
import { Command } from 'commander';
|
|
28
|
-
import type {
|
|
29
|
-
AutoTestAgent,
|
|
30
|
-
AutoTestAgentConfig,
|
|
31
|
-
RunTaskOptions,
|
|
32
|
-
TaskResult,
|
|
33
|
-
} from '@autotest/agent';
|
|
34
|
-
|
|
35
|
-
const require = createRequire(import.meta.url);
|
|
36
|
-
const { AutoTestAgent: AutoTestAgentCtor } = require('@autotest/agent') as {
|
|
37
|
-
AutoTestAgent: new (config: AutoTestAgentConfig) => AutoTestAgent;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const UTF_8 = 'utf-8';
|
|
41
|
-
const CASE_TIMEOUT_MS = 10 * 60 * 1000;
|
|
42
|
-
const DEFAULT_PROMPT_VERSION = 'v2.0';
|
|
43
|
-
|
|
44
|
-
interface CaseRow {
|
|
45
|
-
uuid: string;
|
|
46
|
-
spec: string;
|
|
47
|
-
case_name: string;
|
|
48
|
-
test_steps: string;
|
|
49
|
-
execute_mode?: string;
|
|
50
|
-
[k: string]: unknown;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
interface ResultRow {
|
|
54
|
-
exec_index: number;
|
|
55
|
-
uuid: string;
|
|
56
|
-
spec: string;
|
|
57
|
-
case_name: string;
|
|
58
|
-
category: string;
|
|
59
|
-
test_steps: string;
|
|
60
|
-
report_dir: string;
|
|
61
|
-
start_time: string;
|
|
62
|
-
end_time: string;
|
|
63
|
-
duration_seconds: number;
|
|
64
|
-
status: 'PASS' | 'FAIL' | 'UNKNOWN';
|
|
65
|
-
return_code: number;
|
|
66
|
-
reason: string;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
interface LauncherArgs {
|
|
70
|
-
taskDir: string;
|
|
71
|
-
testcases: string;
|
|
72
|
-
category: string;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function sanitizeFilename(name: string): string {
|
|
76
|
-
return name.replace(/[\\/:*?"<>|]/g, '_');
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function formatTimestamp(date: Date): string {
|
|
80
|
-
const pad = (n: number) => String(n).padStart(2, '0');
|
|
81
|
-
return (
|
|
82
|
-
`${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ` +
|
|
83
|
-
`${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function readTestCases(filePath: string): CaseRow[] {
|
|
88
|
-
let content = fs.readFileSync(filePath, UTF_8);
|
|
89
|
-
if (content.charCodeAt(0) === 0xfeff) content = content.slice(1);
|
|
90
|
-
try {
|
|
91
|
-
const parsed = JSON.parse(content);
|
|
92
|
-
if (Array.isArray(parsed)) return parsed as CaseRow[];
|
|
93
|
-
throw new Error('not an array');
|
|
94
|
-
} catch {
|
|
95
|
-
// Fall back to JSONL.
|
|
96
|
-
const cases: CaseRow[] = [];
|
|
97
|
-
for (const line of content.split('\n')) {
|
|
98
|
-
const trimmed = line.trim();
|
|
99
|
-
if (!trimmed) continue;
|
|
100
|
-
try {
|
|
101
|
-
cases.push(JSON.parse(trimmed) as CaseRow);
|
|
102
|
-
} catch {
|
|
103
|
-
// skip invalid lines
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
return cases;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function parseArgs(argv: string[]): LauncherArgs {
|
|
111
|
-
const program = new Command();
|
|
112
|
-
program.exitOverride();
|
|
113
|
-
program
|
|
114
|
-
.requiredOption('--task-dir <dir>', '任务目录')
|
|
115
|
-
.requiredOption('--testcases <path>', '测试用例文件(JSON 数组或 JSONL)')
|
|
116
|
-
.option('--category <name>', '测试分类名称', 'self_test');
|
|
117
|
-
try {
|
|
118
|
-
program.parse(argv, { from: 'user' });
|
|
119
|
-
} catch (err) {
|
|
120
|
-
const e = err as { code?: string };
|
|
121
|
-
if (e.code !== 'commander.helpDisplayed') {
|
|
122
|
-
console.error('用法: node batch-launcher.js --task-dir <dir> --testcases <path> [--category <name>]');
|
|
123
|
-
process.exit(2);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
const opts = program.opts() as LauncherArgs;
|
|
127
|
-
return { taskDir: opts.taskDir, testcases: opts.testcases, category: opts.category };
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/** Construct the agent from AUTOTEST_* env vars (set by `cmdRun` from config.json). */
|
|
131
|
-
function createAgent(): AutoTestAgent {
|
|
132
|
-
const promptVersion = process.env.AUTOTEST_PROMPT_VERSION || DEFAULT_PROMPT_VERSION;
|
|
133
|
-
const envPort = process.env.AUTOTEST_PORT;
|
|
134
|
-
const envMaxSteps = process.env.AUTOTEST_MAX_STEPS;
|
|
135
|
-
const numOrUndef = (raw: string | undefined): number | undefined => {
|
|
136
|
-
if (!raw) return undefined;
|
|
137
|
-
const n = Number(raw);
|
|
138
|
-
return Number.isFinite(n) ? n : undefined;
|
|
139
|
-
};
|
|
140
|
-
// Resolve the bundled config.yaml inside @autotest/agent so the agent loads
|
|
141
|
-
// model/device/agent defaults (base_url, api_key, …) even when AUTOTEST_*
|
|
142
|
-
// env vars are not set (e.g. running batch-launcher standalone without
|
|
143
|
-
// HomeTrans's config.json → AUTOTEST_* mapping). The ConfigManager converts
|
|
144
|
-
// snake_case YAML keys to camelCase before zod validation, so `base_url` in
|
|
145
|
-
// the YAML becomes `baseUrl` as the schema expects.
|
|
146
|
-
const agentMain = require.resolve('@autotest/agent');
|
|
147
|
-
const configPath = path.join(path.dirname(agentMain), 'common', 'config', 'config.yaml');
|
|
148
|
-
const config: AutoTestAgentConfig = {
|
|
149
|
-
configPath,
|
|
150
|
-
apiKey: process.env.AUTOTEST_API_KEY,
|
|
151
|
-
modelName: process.env.AUTOTEST_MODEL,
|
|
152
|
-
baseUrl: process.env.AUTOTEST_BASE_URL,
|
|
153
|
-
provider: process.env.AUTOTEST_PROVIDER,
|
|
154
|
-
deviceSn: process.env.AUTOTEST_DEVICE_SN || undefined,
|
|
155
|
-
ip: process.env.AUTOTEST_IP,
|
|
156
|
-
port: numOrUndef(envPort),
|
|
157
|
-
agentMode: (process.env.AUTOTEST_MODE || undefined) as AutoTestAgentConfig['agentMode'],
|
|
158
|
-
maxSteps: numOrUndef(envMaxSteps),
|
|
159
|
-
// The SingleAgent resolves its prompt version from `model.unified.promptType`
|
|
160
|
-
// (loadPrompt(SINGLE, config.model.unified.promptType)), whose zod default is
|
|
161
|
-
// 'default' → 'vdefault' (not a real version). Inject the real version through
|
|
162
|
-
// configOverrides — the constructor's route for any extra config the user
|
|
163
|
-
// wants to set through `new`.
|
|
164
|
-
configOverrides: { model: { unified: { promptType: promptVersion } } },
|
|
165
|
-
};
|
|
166
|
-
return new AutoTestAgentCtor(config);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/** Run one task with a hard timeout (mirrors batch_runner.runWithTimeout). */
|
|
170
|
-
function runWithTimeout(
|
|
171
|
-
agent: AutoTestAgent,
|
|
172
|
-
task: string,
|
|
173
|
-
options: RunTaskOptions,
|
|
174
|
-
timeoutMs: number,
|
|
175
|
-
): Promise<TaskResult> {
|
|
176
|
-
let timer: NodeJS.Timeout | undefined;
|
|
177
|
-
const timeout = new Promise<never>((_, reject) => {
|
|
178
|
-
timer = setTimeout(
|
|
179
|
-
() => reject(new Error(`Execution timed out (exceeded ${timeoutMs / 1000} seconds)`)),
|
|
180
|
-
timeoutMs,
|
|
181
|
-
);
|
|
182
|
-
});
|
|
183
|
-
// Promise.race attaches handlers to both promises, so a late runTask
|
|
184
|
-
// resolution/rejection after timeout is never "unhandled"; finally clears
|
|
185
|
-
// the timer regardless of which settles first.
|
|
186
|
-
return Promise.race([agent.runTask(task, options), timeout]).finally(() => {
|
|
187
|
-
if (timer) clearTimeout(timer);
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
async function main(): Promise<void> {
|
|
192
|
-
const opts = parseArgs(process.argv.slice(2));
|
|
193
|
-
|
|
194
|
-
const taskDir = path.resolve(opts.taskDir);
|
|
195
|
-
fs.mkdirSync(taskDir, { recursive: true });
|
|
196
|
-
const jsonlFile = path.join(taskDir, 'task_results.jsonl');
|
|
197
|
-
|
|
198
|
-
const testCases = readTestCases(opts.testcases);
|
|
199
|
-
const total = testCases.length;
|
|
200
|
-
if (total === 0) {
|
|
201
|
-
console.log('No test cases, exiting');
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const indexWidth = String(total).length;
|
|
206
|
-
|
|
207
|
-
console.log(`Total test cases to execute: ${total}`);
|
|
208
|
-
console.log(`Test category: ${opts.category}`);
|
|
209
|
-
console.log(`Task directory: ${taskDir}`);
|
|
210
|
-
console.log('='.repeat(60));
|
|
211
|
-
|
|
212
|
-
// promptVersion is injected via the constructor's configOverrides (see
|
|
213
|
-
// createAgent) because AgentAPIImpl reads it from effectiveConfig, not the
|
|
214
|
-
// per-call option.
|
|
215
|
-
const agent = createAgent();
|
|
216
|
-
|
|
217
|
-
let pass = 0;
|
|
218
|
-
let fail = 0;
|
|
219
|
-
let unknown = 0;
|
|
220
|
-
const batchStart = new Date();
|
|
221
|
-
|
|
222
|
-
for (let idx = 1; idx <= total; idx++) {
|
|
223
|
-
const tc = testCases[idx - 1];
|
|
224
|
-
const uuid = String(tc.uuid ?? '');
|
|
225
|
-
const spec = String(tc.spec ?? '');
|
|
226
|
-
const caseName = String(tc.case_name ?? '');
|
|
227
|
-
const testSteps = String(tc.test_steps ?? '');
|
|
228
|
-
const caseMode = String(tc.execute_mode ?? '');
|
|
229
|
-
|
|
230
|
-
const dirname = uuid
|
|
231
|
-
? sanitizeFilename(`${uuid}_${caseName}`)
|
|
232
|
-
: sanitizeFilename(`${String(idx).padStart(indexWidth, '0')}_${caseName}`);
|
|
233
|
-
const reportDir = path.join(taskDir, dirname).replace(/\\/g, '/');
|
|
234
|
-
fs.mkdirSync(reportDir, { recursive: true });
|
|
235
|
-
|
|
236
|
-
console.log('\n' + '-'.repeat(60));
|
|
237
|
-
console.log(`[${opts.category}][${String(idx).padStart(indexWidth, '0')}/${total}] Execute: ${caseName}`);
|
|
238
|
-
console.log('-'.repeat(60));
|
|
239
|
-
|
|
240
|
-
const start = new Date();
|
|
241
|
-
const runOpts: RunTaskOptions = {
|
|
242
|
-
reportDir,
|
|
243
|
-
taskName: caseName,
|
|
244
|
-
...(caseMode ? { agentMode: caseMode as RunTaskOptions['agentMode'] } : {}),
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
let status: 'PASS' | 'FAIL' | 'UNKNOWN' = 'UNKNOWN';
|
|
248
|
-
let reason = '';
|
|
249
|
-
let returnCode = 0;
|
|
250
|
-
|
|
251
|
-
try {
|
|
252
|
-
const taskResult = await runWithTimeout(agent, testSteps, runOpts, CASE_TIMEOUT_MS);
|
|
253
|
-
returnCode = taskResult.status === 'failed' ? 1 : 0;
|
|
254
|
-
if (taskResult.status === 'completed') {
|
|
255
|
-
status = 'PASS';
|
|
256
|
-
pass += 1;
|
|
257
|
-
} else if (taskResult.status === 'failed') {
|
|
258
|
-
status = 'FAIL';
|
|
259
|
-
reason = taskResult.errorMessage ?? 'Execution failed';
|
|
260
|
-
fail += 1;
|
|
261
|
-
} else {
|
|
262
|
-
status = 'UNKNOWN';
|
|
263
|
-
reason = `agent status=${taskResult.status}`;
|
|
264
|
-
unknown += 1;
|
|
265
|
-
}
|
|
266
|
-
} catch (e) {
|
|
267
|
-
returnCode = -1;
|
|
268
|
-
status = 'FAIL';
|
|
269
|
-
reason = `Execution error: ${e instanceof Error ? e.message : String(e)}`;
|
|
270
|
-
fail += 1;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
const end = new Date();
|
|
274
|
-
const duration = (end.getTime() - start.getTime()) / 1000;
|
|
275
|
-
|
|
276
|
-
const row: ResultRow = {
|
|
277
|
-
exec_index: idx,
|
|
278
|
-
uuid,
|
|
279
|
-
spec,
|
|
280
|
-
case_name: caseName,
|
|
281
|
-
category: opts.category,
|
|
282
|
-
test_steps: testSteps,
|
|
283
|
-
report_dir: reportDir,
|
|
284
|
-
start_time: formatTimestamp(start),
|
|
285
|
-
end_time: formatTimestamp(end),
|
|
286
|
-
duration_seconds: Math.round(duration * 100) / 100,
|
|
287
|
-
status,
|
|
288
|
-
return_code: returnCode,
|
|
289
|
-
reason,
|
|
290
|
-
};
|
|
291
|
-
fs.appendFileSync(jsonlFile, JSON.stringify(row) + '\n', UTF_8);
|
|
292
|
-
|
|
293
|
-
const icon = status === 'PASS' ? 'OK' : status === 'FAIL' ? 'X' : '?';
|
|
294
|
-
console.log(`Result: [${icon}] ${status} | Duration: ${duration.toFixed(2)}s`);
|
|
295
|
-
if (reason && status === 'FAIL') console.log(`Reason: ${reason.slice(0, 100)}`);
|
|
296
|
-
console.log(`Report: ${reportDir}`);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
const summary = {
|
|
300
|
-
total_cases: total,
|
|
301
|
-
pass_count: pass,
|
|
302
|
-
fail_count: fail,
|
|
303
|
-
unknown_count: unknown,
|
|
304
|
-
pass_rate: `${total ? ((pass / total) * 100).toFixed(2) : '0.00'}%`,
|
|
305
|
-
category: opts.category,
|
|
306
|
-
start_time: formatTimestamp(batchStart),
|
|
307
|
-
end_time: formatTimestamp(new Date()),
|
|
308
|
-
task_dir: taskDir.replace(/\\/g, '/'),
|
|
309
|
-
};
|
|
310
|
-
fs.writeFileSync(path.join(taskDir, 'summary.json'), JSON.stringify(summary, null, 4), UTF_8);
|
|
311
|
-
|
|
312
|
-
console.log('\n' + '='.repeat(60));
|
|
313
|
-
console.log('Execution complete!');
|
|
314
|
-
console.log(`Total cases: ${total}`);
|
|
315
|
-
console.log(`Pass: ${pass}`);
|
|
316
|
-
console.log(`Fail: ${fail}`);
|
|
317
|
-
console.log(`Unknown: ${unknown}`);
|
|
318
|
-
console.log(`Pass rate: ${summary.pass_rate}`);
|
|
319
|
-
console.log(`Results file: ${jsonlFile}`);
|
|
320
|
-
console.log('='.repeat(60));
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
main().catch((err) => {
|
|
324
|
-
console.error('batch-launcher fatal:', err instanceof Error ? err.stack ?? err.message : String(err));
|
|
325
|
-
process.exit(1);
|
|
326
|
-
});
|