snapdrive-ios 0.1.0
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/LICENSE +21 -0
- package/README.ja.md +95 -0
- package/README.md +95 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +265 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/command-executor.d.ts +15 -0
- package/dist/core/command-executor.d.ts.map +1 -0
- package/dist/core/command-executor.js +64 -0
- package/dist/core/command-executor.js.map +1 -0
- package/dist/core/element-finder.d.ts +81 -0
- package/dist/core/element-finder.d.ts.map +1 -0
- package/dist/core/element-finder.js +246 -0
- package/dist/core/element-finder.js.map +1 -0
- package/dist/core/idb-client.d.ts +68 -0
- package/dist/core/idb-client.d.ts.map +1 -0
- package/dist/core/idb-client.js +327 -0
- package/dist/core/idb-client.js.map +1 -0
- package/dist/core/image-differ.d.ts +55 -0
- package/dist/core/image-differ.d.ts.map +1 -0
- package/dist/core/image-differ.js +211 -0
- package/dist/core/image-differ.js.map +1 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +9 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/report-generator.d.ts +31 -0
- package/dist/core/report-generator.d.ts.map +1 -0
- package/dist/core/report-generator.js +675 -0
- package/dist/core/report-generator.js.map +1 -0
- package/dist/core/scenario-runner.d.ts +54 -0
- package/dist/core/scenario-runner.d.ts.map +1 -0
- package/dist/core/scenario-runner.js +701 -0
- package/dist/core/scenario-runner.js.map +1 -0
- package/dist/core/simctl-client.d.ts +64 -0
- package/dist/core/simctl-client.d.ts.map +1 -0
- package/dist/core/simctl-client.js +214 -0
- package/dist/core/simctl-client.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces/config.interface.d.ts +37 -0
- package/dist/interfaces/config.interface.d.ts.map +1 -0
- package/dist/interfaces/config.interface.js +14 -0
- package/dist/interfaces/config.interface.js.map +1 -0
- package/dist/interfaces/element.interface.d.ts +49 -0
- package/dist/interfaces/element.interface.d.ts.map +1 -0
- package/dist/interfaces/element.interface.js +5 -0
- package/dist/interfaces/element.interface.js.map +1 -0
- package/dist/interfaces/index.d.ts +7 -0
- package/dist/interfaces/index.d.ts.map +1 -0
- package/dist/interfaces/index.js +7 -0
- package/dist/interfaces/index.js.map +1 -0
- package/dist/interfaces/scenario.interface.d.ts +101 -0
- package/dist/interfaces/scenario.interface.d.ts.map +1 -0
- package/dist/interfaces/scenario.interface.js +5 -0
- package/dist/interfaces/scenario.interface.js.map +1 -0
- package/dist/server.d.ts +28 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +943 -0
- package/dist/server.js.map +1 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +5 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logger.d.ts +24 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +50 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +67 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Masami
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.ja.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/images/header.png">
|
|
4
|
+
<source media="(prefers-color-scheme: light)" srcset="docs/images/header.png">
|
|
5
|
+
<img src="docs/images/header.png" alt="SnapDrive" width="800" style="max-width: 100%; height: auto;">
|
|
6
|
+
</picture>
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="README.md">English</a> | <a href="README.ja.md">日本語</a>
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# SnapDrive
|
|
16
|
+
|
|
17
|
+
iOS用のスナップショットテストツール。AI Agentを活用してiOS Simulatorを自律的に操作し、テストシナリオとBaselineを自動生成します。UIの変更にも柔軟に対応でき、テストケースの陳腐化を防ぎます。
|
|
18
|
+
|
|
19
|
+
## 必要環境
|
|
20
|
+
|
|
21
|
+
- macOS + Xcode
|
|
22
|
+
- Node.js 20+
|
|
23
|
+
- Python 3.x + fb-idb
|
|
24
|
+
|
|
25
|
+
## セットアップ
|
|
26
|
+
|
|
27
|
+
### 1. fb-idb (Python) をインストール
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install fb-idb
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 2. Claude Desktop/Codeに設定
|
|
34
|
+
|
|
35
|
+
`.mcp.json`に追加:
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"mcpServers": {
|
|
40
|
+
"snapdrive": {
|
|
41
|
+
"command": "npx",
|
|
42
|
+
"args": ["snapdrive-ios-mcp"]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
環境変数を設定する場合:
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"mcpServers": {
|
|
53
|
+
"snapdrive": {
|
|
54
|
+
"command": "npx",
|
|
55
|
+
"args": ["snapdrive-ios-mcp"],
|
|
56
|
+
"env": {
|
|
57
|
+
"SNAPDRIVE_RESULTS_DIR": "/path/to/your/project/results",
|
|
58
|
+
"SNAPDRIVE_LOG_LEVEL": "debug"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 環境変数
|
|
66
|
+
|
|
67
|
+
| 変数 | 説明 | デフォルト |
|
|
68
|
+
|------|------|-----------|
|
|
69
|
+
| `SNAPDRIVE_RESULTS_DIR` | 結果出力先 | `./results` |
|
|
70
|
+
| `SNAPDRIVE_LOG_LEVEL` | ログレベル (debug/info/warn/error) | `info` |
|
|
71
|
+
|
|
72
|
+
## バージョン管理
|
|
73
|
+
|
|
74
|
+
`.snapdrive`ディレクトリはGitにコミットしてください:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
git add .snapdrive/
|
|
78
|
+
git commit -m "Add SnapDrive test cases and baselines"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
テストシナリオとBaselineをチームで共有することで、全環境で一貫したビジュアルリグレッションテストが可能になります。
|
|
82
|
+
|
|
83
|
+
> **Note**: `results/`ディレクトリはテスト実行結果のため、`.gitignore`に追加してください。
|
|
84
|
+
|
|
85
|
+
## ドキュメント
|
|
86
|
+
|
|
87
|
+
- [使い方ガイド](docs/usage.md) - 基本的な操作方法
|
|
88
|
+
- [テストケース](docs/test-cases.md) - 構造化テストの作成と実行
|
|
89
|
+
- [CLI](docs/cli.md) - コマンドラインツールの使い方
|
|
90
|
+
- [Fastlane統合](docs/fastlane.md) - FastlaneでのCI/CD連携
|
|
91
|
+
- [MCPツール一覧](docs/tools.md) - 利用可能なツールのリファレンス
|
|
92
|
+
|
|
93
|
+
## ライセンス
|
|
94
|
+
|
|
95
|
+
MIT
|
package/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/images/header.png">
|
|
4
|
+
<source media="(prefers-color-scheme: light)" srcset="docs/images/header.png">
|
|
5
|
+
<img src="docs/images/header.png" alt="SnapDrive" width="800" style="max-width: 100%; height: auto;">
|
|
6
|
+
</picture>
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="README.md">English</a> | <a href="README.ja.md">日本語</a>
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# SnapDrive
|
|
16
|
+
|
|
17
|
+
Snapshot testing tool for iOS. Leverages AI Agents to autonomously operate iOS Simulator, automatically generating test scenarios and baselines. Adapts flexibly to UI changes, preventing test case decay.
|
|
18
|
+
|
|
19
|
+
## Requirements
|
|
20
|
+
|
|
21
|
+
- macOS + Xcode
|
|
22
|
+
- Node.js 20+
|
|
23
|
+
- Python 3.x + fb-idb
|
|
24
|
+
|
|
25
|
+
## Setup
|
|
26
|
+
|
|
27
|
+
### 1. Install fb-idb (Python)
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install fb-idb
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 2. Configure Claude Desktop/Code
|
|
34
|
+
|
|
35
|
+
Add to `.mcp.json`:
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"mcpServers": {
|
|
40
|
+
"snapdrive": {
|
|
41
|
+
"command": "npx",
|
|
42
|
+
"args": ["snapdrive-ios-mcp"]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
With environment variables:
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"mcpServers": {
|
|
53
|
+
"snapdrive": {
|
|
54
|
+
"command": "npx",
|
|
55
|
+
"args": ["snapdrive-ios-mcp"],
|
|
56
|
+
"env": {
|
|
57
|
+
"SNAPDRIVE_RESULTS_DIR": "/path/to/your/project/results",
|
|
58
|
+
"SNAPDRIVE_LOG_LEVEL": "debug"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Environment Variables
|
|
66
|
+
|
|
67
|
+
| Variable | Description | Default |
|
|
68
|
+
|----------|-------------|---------|
|
|
69
|
+
| `SNAPDRIVE_RESULTS_DIR` | Results output directory | `./results` |
|
|
70
|
+
| `SNAPDRIVE_LOG_LEVEL` | Log level (debug/info/warn/error) | `info` |
|
|
71
|
+
|
|
72
|
+
## Version Control
|
|
73
|
+
|
|
74
|
+
The `.snapdrive` directory should be committed to Git:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
git add .snapdrive/
|
|
78
|
+
git commit -m "Add SnapDrive test cases and baselines"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
This allows your team to share test scenarios and baselines, ensuring consistent visual regression testing across all environments.
|
|
82
|
+
|
|
83
|
+
> **Note**: The `results/` directory contains test execution outputs and should be added to `.gitignore`.
|
|
84
|
+
|
|
85
|
+
## Documentation
|
|
86
|
+
|
|
87
|
+
- [Usage Guide](docs/usage.md) - Basic operations
|
|
88
|
+
- [Test Cases](docs/test-cases.md) - Creating and running structured tests
|
|
89
|
+
- [CLI](docs/cli.md) - Command-line tool usage
|
|
90
|
+
- [Fastlane Integration](docs/fastlane.md) - CI/CD with Fastlane
|
|
91
|
+
- [MCP Tools Reference](docs/tools.md) - Available tools reference
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
MIT
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;GAGG"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* SnapDrive CLI
|
|
4
|
+
* Run tests directly from command line without Claude/MCP
|
|
5
|
+
*/
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { mkdir } from 'node:fs/promises';
|
|
8
|
+
import { IDBClient } from './core/idb-client.js';
|
|
9
|
+
import { SimctlClient } from './core/simctl-client.js';
|
|
10
|
+
import { ElementFinder } from './core/element-finder.js';
|
|
11
|
+
import { ImageDiffer } from './core/image-differ.js';
|
|
12
|
+
import { ScenarioRunner } from './core/scenario-runner.js';
|
|
13
|
+
import { ReportGenerator } from './core/report-generator.js';
|
|
14
|
+
import { Logger } from './utils/logger.js';
|
|
15
|
+
const VERSION = '0.1.0';
|
|
16
|
+
function printHelp() {
|
|
17
|
+
console.log(`
|
|
18
|
+
SnapDrive CLI v${VERSION}
|
|
19
|
+
iOS Simulator UI testing tool
|
|
20
|
+
|
|
21
|
+
Usage:
|
|
22
|
+
snapdrive <command> [options]
|
|
23
|
+
|
|
24
|
+
Commands:
|
|
25
|
+
list List all test cases
|
|
26
|
+
run <test-case-id> Run a specific test case
|
|
27
|
+
run --all Run all test cases
|
|
28
|
+
help Show this help message
|
|
29
|
+
version Show version
|
|
30
|
+
|
|
31
|
+
Options:
|
|
32
|
+
--all Run all test cases
|
|
33
|
+
--update-baselines Update baseline screenshots instead of comparing
|
|
34
|
+
--snapdrive-dir <path> Path to .snapdrive directory (default: ./.snapdrive)
|
|
35
|
+
--results-dir <path> Path to results directory (default: ./results)
|
|
36
|
+
--device <udid> Target simulator UDID
|
|
37
|
+
--verbose Enable verbose logging
|
|
38
|
+
|
|
39
|
+
Examples:
|
|
40
|
+
snapdrive list
|
|
41
|
+
snapdrive run login-flow
|
|
42
|
+
snapdrive run login-flow --update-baselines
|
|
43
|
+
snapdrive run --all
|
|
44
|
+
snapdrive run --all --verbose
|
|
45
|
+
`);
|
|
46
|
+
}
|
|
47
|
+
function parseArgs(args) {
|
|
48
|
+
const options = {
|
|
49
|
+
command: 'help',
|
|
50
|
+
};
|
|
51
|
+
let i = 0;
|
|
52
|
+
while (i < args.length) {
|
|
53
|
+
const arg = args[i];
|
|
54
|
+
if (arg === 'list') {
|
|
55
|
+
options.command = 'list';
|
|
56
|
+
}
|
|
57
|
+
else if (arg === 'run') {
|
|
58
|
+
options.command = 'run';
|
|
59
|
+
}
|
|
60
|
+
else if (arg === 'help' || arg === '--help' || arg === '-h') {
|
|
61
|
+
options.command = 'help';
|
|
62
|
+
}
|
|
63
|
+
else if (arg === 'version' || arg === '--version' || arg === '-v') {
|
|
64
|
+
options.command = 'version';
|
|
65
|
+
}
|
|
66
|
+
else if (arg === '--all' || arg === '-a') {
|
|
67
|
+
options.all = true;
|
|
68
|
+
}
|
|
69
|
+
else if (arg === '--update-baselines' || arg === '-u') {
|
|
70
|
+
options.updateBaselines = true;
|
|
71
|
+
}
|
|
72
|
+
else if (arg === '--verbose') {
|
|
73
|
+
options.verbose = true;
|
|
74
|
+
}
|
|
75
|
+
else if (arg === '--snapdrive-dir' && args[i + 1]) {
|
|
76
|
+
options.snapdriveDir = args[++i];
|
|
77
|
+
}
|
|
78
|
+
else if (arg === '--results-dir' && args[i + 1]) {
|
|
79
|
+
options.resultsDir = args[++i];
|
|
80
|
+
}
|
|
81
|
+
else if (arg === '--device' && args[i + 1]) {
|
|
82
|
+
options.deviceUdid = args[++i];
|
|
83
|
+
}
|
|
84
|
+
else if (!arg.startsWith('-') && options.command === 'run' && !options.testCaseId) {
|
|
85
|
+
options.testCaseId = arg;
|
|
86
|
+
}
|
|
87
|
+
i++;
|
|
88
|
+
}
|
|
89
|
+
return options;
|
|
90
|
+
}
|
|
91
|
+
async function createRunner(verbose) {
|
|
92
|
+
const logLevel = verbose ? 'debug' : 'info';
|
|
93
|
+
const logger = new Logger('snapdrive-cli', logLevel);
|
|
94
|
+
const idbClient = new IDBClient({}, undefined, logger);
|
|
95
|
+
const simctlClient = new SimctlClient({}, undefined, logger);
|
|
96
|
+
const elementFinder = new ElementFinder();
|
|
97
|
+
const imageDiffer = new ImageDiffer(logger);
|
|
98
|
+
const scenarioRunner = new ScenarioRunner({
|
|
99
|
+
idbClient,
|
|
100
|
+
simctlClient,
|
|
101
|
+
elementFinder,
|
|
102
|
+
imageDiffer,
|
|
103
|
+
logger,
|
|
104
|
+
});
|
|
105
|
+
const reportGenerator = new ReportGenerator(logger);
|
|
106
|
+
return { scenarioRunner, reportGenerator, logger };
|
|
107
|
+
}
|
|
108
|
+
async function listTestCases(options) {
|
|
109
|
+
const snapdriveDir = options.snapdriveDir ?? join(process.cwd(), '.snapdrive');
|
|
110
|
+
const { scenarioRunner } = await createRunner(options.verbose ?? false);
|
|
111
|
+
console.log(`\nLooking for test cases in: ${snapdriveDir}/test-cases\n`);
|
|
112
|
+
const testCases = await scenarioRunner.listTestCases(snapdriveDir);
|
|
113
|
+
if (testCases.length === 0) {
|
|
114
|
+
console.log('No test cases found.');
|
|
115
|
+
console.log('\nCreate a test case with Claude:');
|
|
116
|
+
console.log(' "login-flowという名前でテストケースを作成して"');
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
console.log(`Found ${testCases.length} test case(s):\n`);
|
|
120
|
+
console.log('─'.repeat(60));
|
|
121
|
+
for (const tc of testCases) {
|
|
122
|
+
console.log(` ${tc.id}`);
|
|
123
|
+
console.log(` Name: ${tc.scenario.name}`);
|
|
124
|
+
if (tc.scenario.description) {
|
|
125
|
+
console.log(` Description: ${tc.scenario.description}`);
|
|
126
|
+
}
|
|
127
|
+
console.log(` Steps: ${tc.scenario.steps.length}`);
|
|
128
|
+
console.log('');
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async function runTests(options) {
|
|
132
|
+
const snapdriveDir = options.snapdriveDir ?? join(process.cwd(), '.snapdrive');
|
|
133
|
+
const baseResultsDir = options.resultsDir ?? join(process.cwd(), 'results');
|
|
134
|
+
const resultsDir = join(baseResultsDir, new Date().toISOString().replace(/[:.]/g, '-'));
|
|
135
|
+
const { scenarioRunner, reportGenerator } = await createRunner(options.verbose ?? false);
|
|
136
|
+
// Ensure results directory exists (do not clean up previous results)
|
|
137
|
+
await mkdir(resultsDir, { recursive: true });
|
|
138
|
+
await mkdir(join(resultsDir, 'screenshots'), { recursive: true });
|
|
139
|
+
await mkdir(join(resultsDir, 'diffs'), { recursive: true });
|
|
140
|
+
let testCases;
|
|
141
|
+
if (options.all) {
|
|
142
|
+
testCases = await scenarioRunner.listTestCases(snapdriveDir);
|
|
143
|
+
if (testCases.length === 0) {
|
|
144
|
+
console.error('No test cases found.');
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
console.log(`\nRunning all ${testCases.length} test case(s)...\n`);
|
|
148
|
+
}
|
|
149
|
+
else if (options.testCaseId) {
|
|
150
|
+
const tcPath = join(snapdriveDir, 'test-cases', options.testCaseId);
|
|
151
|
+
try {
|
|
152
|
+
const testCase = await scenarioRunner.loadTestCase(tcPath);
|
|
153
|
+
testCases = [testCase];
|
|
154
|
+
console.log(`\nRunning test case: ${options.testCaseId}\n`);
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
console.error(`Error: Test case not found: ${options.testCaseId}`);
|
|
158
|
+
console.error(`Expected path: ${tcPath}`);
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
console.error('Error: Specify a test case ID or use --all');
|
|
164
|
+
console.error('Usage: snapdrive run <test-case-id>');
|
|
165
|
+
console.error(' snapdrive run --all');
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
const startTime = new Date();
|
|
169
|
+
const results = {
|
|
170
|
+
runId: new Date().toISOString().replace(/[:.]/g, '-'),
|
|
171
|
+
startTime: startTime.toISOString(),
|
|
172
|
+
endTime: '',
|
|
173
|
+
durationMs: 0,
|
|
174
|
+
totalTests: testCases.length,
|
|
175
|
+
passed: 0,
|
|
176
|
+
failed: 0,
|
|
177
|
+
results: [],
|
|
178
|
+
resultsDir,
|
|
179
|
+
};
|
|
180
|
+
console.log('─'.repeat(60));
|
|
181
|
+
for (const testCase of testCases) {
|
|
182
|
+
const mode = options.updateBaselines ? '[UPDATE BASELINES]' : '[COMPARE]';
|
|
183
|
+
console.log(`\n${mode} ${testCase.scenario.name}`);
|
|
184
|
+
try {
|
|
185
|
+
const result = await scenarioRunner.runTestCase(testCase, {
|
|
186
|
+
deviceUdid: options.deviceUdid,
|
|
187
|
+
updateBaselines: options.updateBaselines ?? false,
|
|
188
|
+
resultsDir,
|
|
189
|
+
testCasePath: testCase.path,
|
|
190
|
+
});
|
|
191
|
+
results.results.push(result);
|
|
192
|
+
if (result.success) {
|
|
193
|
+
results.passed++;
|
|
194
|
+
console.log(` ✓ PASSED (${(result.durationMs / 1000).toFixed(2)}s)`);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
results.failed++;
|
|
198
|
+
console.log(` ✗ FAILED (${(result.durationMs / 1000).toFixed(2)}s)`);
|
|
199
|
+
// Show failed steps
|
|
200
|
+
for (const step of result.steps) {
|
|
201
|
+
if (!step.success) {
|
|
202
|
+
console.log(` - Step ${step.stepIndex + 1} (${step.action}): ${step.error}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// Show checkpoint diffs
|
|
206
|
+
for (const cp of result.checkpoints) {
|
|
207
|
+
if (!cp.match) {
|
|
208
|
+
console.log(` - Checkpoint "${cp.name}": ${cp.differencePercent.toFixed(2)}% different`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
results.failed++;
|
|
215
|
+
console.log(` ✗ ERROR: ${String(error)}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const endTime = new Date();
|
|
219
|
+
results.endTime = endTime.toISOString();
|
|
220
|
+
results.durationMs = endTime.getTime() - startTime.getTime();
|
|
221
|
+
console.log('\n' + '─'.repeat(60));
|
|
222
|
+
console.log('\nSummary:');
|
|
223
|
+
console.log(` Total: ${results.totalTests}`);
|
|
224
|
+
console.log(` Passed: ${results.passed}`);
|
|
225
|
+
console.log(` Failed: ${results.failed}`);
|
|
226
|
+
console.log(` Duration: ${(results.durationMs / 1000).toFixed(2)}s`);
|
|
227
|
+
// Generate report if comparing (not updating baselines)
|
|
228
|
+
if (!options.updateBaselines) {
|
|
229
|
+
const reportPath = await reportGenerator.generateReport(results);
|
|
230
|
+
console.log(`\n📄 Report: ${reportPath}`);
|
|
231
|
+
}
|
|
232
|
+
console.log(`📁 Results: ${resultsDir}`);
|
|
233
|
+
console.log('');
|
|
234
|
+
// Exit with error code if tests failed
|
|
235
|
+
if (results.failed > 0) {
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
async function main() {
|
|
240
|
+
const args = process.argv.slice(2);
|
|
241
|
+
const options = parseArgs(args);
|
|
242
|
+
try {
|
|
243
|
+
switch (options.command) {
|
|
244
|
+
case 'list':
|
|
245
|
+
await listTestCases(options);
|
|
246
|
+
break;
|
|
247
|
+
case 'run':
|
|
248
|
+
await runTests(options);
|
|
249
|
+
break;
|
|
250
|
+
case 'version':
|
|
251
|
+
console.log(`snapdrive v${VERSION}`);
|
|
252
|
+
break;
|
|
253
|
+
case 'help':
|
|
254
|
+
default:
|
|
255
|
+
printHelp();
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
console.error('Error:', String(error));
|
|
261
|
+
process.exit(1);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
main();
|
|
265
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command executor for running subprocess commands
|
|
3
|
+
*/
|
|
4
|
+
import type { CommandResult, ExecuteOptions } from '../interfaces/config.interface.js';
|
|
5
|
+
import { type ILogger } from '../utils/logger.js';
|
|
6
|
+
export interface ICommandExecutor {
|
|
7
|
+
execute(command: string, args: string[], options?: ExecuteOptions): Promise<CommandResult>;
|
|
8
|
+
}
|
|
9
|
+
export declare class CommandExecutor implements ICommandExecutor {
|
|
10
|
+
private logger;
|
|
11
|
+
constructor(logger?: ILogger);
|
|
12
|
+
execute(command: string, args: string[], options?: ExecuteOptions): Promise<CommandResult>;
|
|
13
|
+
}
|
|
14
|
+
export declare const commandExecutor: CommandExecutor;
|
|
15
|
+
//# sourceMappingURL=command-executor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-executor.d.ts","sourceRoot":"","sources":["../../src/core/command-executor.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACvF,OAAO,EAAE,KAAK,OAAO,EAAU,MAAM,oBAAoB,CAAC;AAE1D,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CACL,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,aAAa,CAAC,CAAC;CAC3B;AAED,qBAAa,eAAgB,YAAW,gBAAgB;IACtD,OAAO,CAAC,MAAM,CAAU;gBAEZ,MAAM,CAAC,EAAE,OAAO;IAItB,OAAO,CACX,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,aAAa,CAAC;CA6D1B;AAED,eAAO,MAAM,eAAe,iBAAwB,CAAC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command executor for running subprocess commands
|
|
3
|
+
*/
|
|
4
|
+
import { spawn } from 'node:child_process';
|
|
5
|
+
import { Logger } from '../utils/logger.js';
|
|
6
|
+
export class CommandExecutor {
|
|
7
|
+
logger;
|
|
8
|
+
constructor(logger) {
|
|
9
|
+
this.logger = logger ?? new Logger('command-executor');
|
|
10
|
+
}
|
|
11
|
+
async execute(command, args, options = {}) {
|
|
12
|
+
const { timeoutMs = 30000, cwd, env } = options;
|
|
13
|
+
const fullCommand = `${command} ${args.join(' ')}`;
|
|
14
|
+
this.logger.debug(`Executing: ${fullCommand}`);
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
let stdout = '';
|
|
17
|
+
let stderr = '';
|
|
18
|
+
let timedOut = false;
|
|
19
|
+
const proc = spawn(command, args, {
|
|
20
|
+
cwd,
|
|
21
|
+
env: env ? { ...process.env, ...env } : process.env,
|
|
22
|
+
shell: false,
|
|
23
|
+
});
|
|
24
|
+
const timeoutId = setTimeout(() => {
|
|
25
|
+
timedOut = true;
|
|
26
|
+
proc.kill('SIGTERM');
|
|
27
|
+
this.logger.warn(`Command timed out after ${timeoutMs}ms: ${fullCommand}`);
|
|
28
|
+
}, timeoutMs);
|
|
29
|
+
proc.stdout.on('data', (data) => {
|
|
30
|
+
stdout += data.toString();
|
|
31
|
+
});
|
|
32
|
+
proc.stderr.on('data', (data) => {
|
|
33
|
+
stderr += data.toString();
|
|
34
|
+
});
|
|
35
|
+
proc.on('close', (code) => {
|
|
36
|
+
clearTimeout(timeoutId);
|
|
37
|
+
const exitCode = code ?? (timedOut ? -1 : 0);
|
|
38
|
+
if (exitCode !== 0 && !timedOut) {
|
|
39
|
+
this.logger.debug(`Command failed with exit code ${exitCode}: ${fullCommand}`, {
|
|
40
|
+
stderr: stderr.slice(0, 200),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
resolve({
|
|
44
|
+
stdout,
|
|
45
|
+
stderr,
|
|
46
|
+
exitCode,
|
|
47
|
+
timedOut,
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
proc.on('error', (err) => {
|
|
51
|
+
clearTimeout(timeoutId);
|
|
52
|
+
this.logger.error(`Command error: ${fullCommand}`, { error: err.message });
|
|
53
|
+
resolve({
|
|
54
|
+
stdout,
|
|
55
|
+
stderr: `${stderr}\nError: ${err.message}`,
|
|
56
|
+
exitCode: -1,
|
|
57
|
+
timedOut: false,
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export const commandExecutor = new CommandExecutor();
|
|
64
|
+
//# sourceMappingURL=command-executor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-executor.js","sourceRoot":"","sources":["../../src/core/command-executor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Element finder - search and locate UI elements
|
|
3
|
+
*/
|
|
4
|
+
import type { AccessibilityElement, Point, ElementSearchResult, ElementPredicate } from '../interfaces/element.interface.js';
|
|
5
|
+
export interface IElementFinder {
|
|
6
|
+
findByLabel(elements: AccessibilityElement[], label: string): AccessibilityElement[];
|
|
7
|
+
findByLabelContains(elements: AccessibilityElement[], partial: string): AccessibilityElement[];
|
|
8
|
+
findByType(elements: AccessibilityElement[], type: string): AccessibilityElement[];
|
|
9
|
+
findByRole(elements: AccessibilityElement[], role: string): AccessibilityElement[];
|
|
10
|
+
findByPredicate(elements: AccessibilityElement[], predicate: ElementPredicate): AccessibilityElement[];
|
|
11
|
+
findBest(elements: AccessibilityElement[], predicate: ElementPredicate, screenCenter?: Point): ElementSearchResult;
|
|
12
|
+
getCenterPoint(element: AccessibilityElement): Point;
|
|
13
|
+
getAllLabels(elements: AccessibilityElement[]): string[];
|
|
14
|
+
findScrollRegion(elements: AccessibilityElement[]): {
|
|
15
|
+
centerX: number;
|
|
16
|
+
centerY: number;
|
|
17
|
+
} | null;
|
|
18
|
+
}
|
|
19
|
+
export declare class ElementFinder implements IElementFinder {
|
|
20
|
+
/**
|
|
21
|
+
* Find elements with exact label match
|
|
22
|
+
*/
|
|
23
|
+
findByLabel(elements: AccessibilityElement[], label: string): AccessibilityElement[];
|
|
24
|
+
/**
|
|
25
|
+
* Find elements with partial label match
|
|
26
|
+
*/
|
|
27
|
+
findByLabelContains(elements: AccessibilityElement[], partial: string): AccessibilityElement[];
|
|
28
|
+
/**
|
|
29
|
+
* Find elements by type
|
|
30
|
+
*/
|
|
31
|
+
findByType(elements: AccessibilityElement[], type: string): AccessibilityElement[];
|
|
32
|
+
/**
|
|
33
|
+
* Find elements by role
|
|
34
|
+
*/
|
|
35
|
+
findByRole(elements: AccessibilityElement[], role: string): AccessibilityElement[];
|
|
36
|
+
/**
|
|
37
|
+
* Find elements matching a predicate
|
|
38
|
+
*/
|
|
39
|
+
findByPredicate(elements: AccessibilityElement[], predicate: ElementPredicate): AccessibilityElement[];
|
|
40
|
+
/**
|
|
41
|
+
* Find the best matching element and return search result with tap coordinates
|
|
42
|
+
*/
|
|
43
|
+
findBest(elements: AccessibilityElement[], predicate: ElementPredicate, screenCenter?: Point): ElementSearchResult;
|
|
44
|
+
/**
|
|
45
|
+
* Calculate center point of element's frame
|
|
46
|
+
*/
|
|
47
|
+
getCenterPoint(element: AccessibilityElement): Point;
|
|
48
|
+
/**
|
|
49
|
+
* Get all labels from elements (for debugging/suggestions)
|
|
50
|
+
*/
|
|
51
|
+
getAllLabels(elements: AccessibilityElement[]): string[];
|
|
52
|
+
/**
|
|
53
|
+
* Check if element matches predicate
|
|
54
|
+
*/
|
|
55
|
+
private matchesPredicate;
|
|
56
|
+
/**
|
|
57
|
+
* Rank elements by priority:
|
|
58
|
+
* 1. Buttons and tappable elements first
|
|
59
|
+
* 2. Enabled elements before disabled
|
|
60
|
+
* 3. Closer to screen center
|
|
61
|
+
*/
|
|
62
|
+
private rankElements;
|
|
63
|
+
/**
|
|
64
|
+
* Check if element is button-like (more likely to be tappable)
|
|
65
|
+
*/
|
|
66
|
+
private isButtonLike;
|
|
67
|
+
/**
|
|
68
|
+
* Calculate distance from element center to screen center
|
|
69
|
+
*/
|
|
70
|
+
private distanceToCenter;
|
|
71
|
+
/**
|
|
72
|
+
* Find the best scroll region by analyzing element frames
|
|
73
|
+
* Returns center point of the largest container that likely contains scrollable content
|
|
74
|
+
*/
|
|
75
|
+
findScrollRegion(elements: AccessibilityElement[]): {
|
|
76
|
+
centerX: number;
|
|
77
|
+
centerY: number;
|
|
78
|
+
} | null;
|
|
79
|
+
}
|
|
80
|
+
export declare const elementFinder: ElementFinder;
|
|
81
|
+
//# sourceMappingURL=element-finder.d.ts.map
|