mobile-debug-mcp 0.10.0 → 0.11.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/README.md +3 -1
- package/dist/android/interact.js +1 -145
- package/dist/android/manage.js +137 -0
- package/dist/android/observe.js +131 -86
- package/dist/android/run.js +187 -0
- package/dist/android/utils.js +134 -144
- package/dist/ios/interact.js +1 -168
- package/dist/ios/manage.js +145 -0
- package/dist/ios/observe.js +108 -1
- package/dist/ios/run.js +200 -0
- package/dist/ios/utils.js +17 -116
- package/dist/server.js +27 -17
- package/dist/tools/interact.js +21 -71
- package/dist/tools/manage.js +180 -0
- package/dist/tools/observe.js +23 -69
- package/dist/tools/run.js +180 -0
- package/docs/CHANGELOG.md +4 -0
- package/package.json +1 -1
- package/src/android/interact.ts +2 -155
- package/src/android/manage.ts +135 -0
- package/src/android/observe.ts +127 -95
- package/src/android/utils.ts +144 -146
- package/src/ios/interact.ts +2 -174
- package/src/ios/manage.ts +143 -0
- package/src/ios/observe.ts +109 -1
- package/src/ios/utils.ts +18 -120
- package/src/server.ts +28 -17
- package/src/tools/interact.ts +23 -62
- package/src/tools/manage.ts +171 -0
- package/src/tools/observe.ts +24 -74
- package/test/integration/logstream-real.ts +5 -4
- package/test/unit/build.test.ts +84 -0
- package/test/unit/build_and_install.test.ts +132 -0
- package/test/unit/install.test.ts +2 -2
- package/test/unit/logstream.test.ts +8 -9
package/dist/server.js
CHANGED
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { ToolsManage } from './tools/manage.js';
|
|
5
6
|
import { ToolsInteract } from './tools/interact.js';
|
|
6
7
|
import { ToolsObserve } from './tools/observe.js';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { AndroidObserve } from './android/observe.js';
|
|
10
|
-
import { iOSObserve } from './ios/observe.js';
|
|
8
|
+
import { AndroidManage } from './android/manage.js';
|
|
9
|
+
import { iOSManage } from './ios/manage.js';
|
|
11
10
|
const server = new Server({
|
|
12
11
|
name: "mobile-debug-mcp",
|
|
13
12
|
version: "0.7.0"
|
|
@@ -375,7 +374,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
375
374
|
try {
|
|
376
375
|
if (name === "start_app") {
|
|
377
376
|
const { platform, appId, deviceId } = args;
|
|
378
|
-
const res = await (platform === 'android' ? new
|
|
377
|
+
const res = await (platform === 'android' ? new AndroidManage().startApp(appId, deviceId) : new iOSManage().startApp(appId, deviceId));
|
|
379
378
|
const response = {
|
|
380
379
|
device: res.device,
|
|
381
380
|
appStarted: res.appStarted,
|
|
@@ -385,25 +384,25 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
385
384
|
}
|
|
386
385
|
if (name === "terminate_app") {
|
|
387
386
|
const { platform, appId, deviceId } = args;
|
|
388
|
-
const res = await (platform === 'android' ? new
|
|
387
|
+
const res = await (platform === 'android' ? new AndroidManage().terminateApp(appId, deviceId) : new iOSManage().terminateApp(appId, deviceId));
|
|
389
388
|
const response = { device: res.device, appTerminated: res.appTerminated };
|
|
390
389
|
return wrapResponse(response);
|
|
391
390
|
}
|
|
392
391
|
if (name === "restart_app") {
|
|
393
392
|
const { platform, appId, deviceId } = args;
|
|
394
|
-
const res = await (platform === 'android' ? new
|
|
393
|
+
const res = await (platform === 'android' ? new AndroidManage().restartApp(appId, deviceId) : new iOSManage().restartApp(appId, deviceId));
|
|
395
394
|
const response = { device: res.device, appRestarted: res.appRestarted, launchTimeMs: res.launchTimeMs };
|
|
396
395
|
return wrapResponse(response);
|
|
397
396
|
}
|
|
398
397
|
if (name === "reset_app_data") {
|
|
399
398
|
const { platform, appId, deviceId } = args;
|
|
400
|
-
const res = await (platform === 'android' ? new
|
|
399
|
+
const res = await (platform === 'android' ? new AndroidManage().resetAppData(appId, deviceId) : new iOSManage().resetAppData(appId, deviceId));
|
|
401
400
|
const response = { device: res.device, dataCleared: res.dataCleared };
|
|
402
401
|
return wrapResponse(response);
|
|
403
402
|
}
|
|
404
403
|
if (name === "install_app") {
|
|
405
404
|
const { platform, appPath, deviceId } = args;
|
|
406
|
-
const res = await
|
|
405
|
+
const res = await ToolsManage.installAppHandler({ platform, appPath, deviceId });
|
|
407
406
|
const response = {
|
|
408
407
|
device: res.device,
|
|
409
408
|
installed: res.installed,
|
|
@@ -412,6 +411,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
412
411
|
};
|
|
413
412
|
return wrapResponse(response);
|
|
414
413
|
}
|
|
414
|
+
if (name === 'build_and_install') {
|
|
415
|
+
const { platform, projectPath, deviceId, timeout } = args;
|
|
416
|
+
const res = await ToolsManage.buildAndInstallHandler({ platform, projectPath, deviceId, timeout });
|
|
417
|
+
// res: { ndjson, result }
|
|
418
|
+
return {
|
|
419
|
+
content: [
|
|
420
|
+
{ type: 'text', text: res.ndjson },
|
|
421
|
+
{ type: 'text', text: JSON.stringify(res.result, null, 2) }
|
|
422
|
+
]
|
|
423
|
+
};
|
|
424
|
+
}
|
|
415
425
|
if (name === "get_logs") {
|
|
416
426
|
const { platform, appId, deviceId, lines } = args;
|
|
417
427
|
const res = await ToolsObserve.getLogsHandler({ platform, appId, deviceId, lines });
|
|
@@ -424,7 +434,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
424
434
|
}
|
|
425
435
|
if (name === "list_devices") {
|
|
426
436
|
const { platform, appId } = (args || {});
|
|
427
|
-
const res = await
|
|
437
|
+
const res = await ToolsManage.listDevicesHandler({ platform, appId });
|
|
428
438
|
return wrapResponse(res);
|
|
429
439
|
}
|
|
430
440
|
if (name === "capture_screenshot") {
|
|
@@ -439,37 +449,37 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
439
449
|
}
|
|
440
450
|
if (name === "get_ui_tree") {
|
|
441
451
|
const { platform, deviceId } = args;
|
|
442
|
-
const res = await (platform
|
|
452
|
+
const res = await ToolsObserve.getUITreeHandler({ platform, deviceId });
|
|
443
453
|
return wrapResponse(res);
|
|
444
454
|
}
|
|
445
455
|
if (name === "get_current_screen") {
|
|
446
456
|
const { deviceId } = (args || {});
|
|
447
|
-
const res = await
|
|
457
|
+
const res = await ToolsObserve.getCurrentScreenHandler({ deviceId });
|
|
448
458
|
return wrapResponse(res);
|
|
449
459
|
}
|
|
450
460
|
if (name === "wait_for_element") {
|
|
451
461
|
const { platform, text, timeout, deviceId } = (args || {});
|
|
452
|
-
const res = await
|
|
462
|
+
const res = await ToolsInteract.waitForElementHandler({ platform, text, timeout, deviceId });
|
|
453
463
|
return wrapResponse(res);
|
|
454
464
|
}
|
|
455
465
|
if (name === "tap") {
|
|
456
466
|
const { platform, x, y, deviceId } = (args || {});
|
|
457
|
-
const res = await
|
|
467
|
+
const res = await ToolsInteract.tapHandler({ platform, x, y, deviceId });
|
|
458
468
|
return wrapResponse(res);
|
|
459
469
|
}
|
|
460
470
|
if (name === "swipe") {
|
|
461
471
|
const { x1, y1, x2, y2, duration, deviceId } = (args || {});
|
|
462
|
-
const res = await
|
|
472
|
+
const res = await ToolsInteract.swipeHandler({ x1, y1, x2, y2, duration, deviceId });
|
|
463
473
|
return wrapResponse(res);
|
|
464
474
|
}
|
|
465
475
|
if (name === "type_text") {
|
|
466
476
|
const { text, deviceId } = (args || {});
|
|
467
|
-
const res = await
|
|
477
|
+
const res = await ToolsInteract.typeTextHandler({ text, deviceId });
|
|
468
478
|
return wrapResponse(res);
|
|
469
479
|
}
|
|
470
480
|
if (name === "press_back") {
|
|
471
481
|
const { deviceId } = (args || {});
|
|
472
|
-
const res = await
|
|
482
|
+
const res = await ToolsInteract.pressBackHandler({ deviceId });
|
|
473
483
|
return wrapResponse(res);
|
|
474
484
|
}
|
|
475
485
|
if (name === 'start_log_stream') {
|
package/dist/tools/interact.js
CHANGED
|
@@ -1,89 +1,39 @@
|
|
|
1
|
-
import { promises as fs } from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
1
|
import { resolveTargetDevice } from '../resolve-device.js';
|
|
4
2
|
import { AndroidInteract } from '../android/interact.js';
|
|
5
3
|
import { iOSInteract } from '../ios/interact.js';
|
|
6
4
|
export class ToolsInteract {
|
|
7
|
-
static async
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const stat = await fs.stat(appPath).catch(() => null);
|
|
11
|
-
if (stat && stat.isDirectory()) {
|
|
12
|
-
const files = (await fs.readdir(appPath).catch(() => []));
|
|
13
|
-
if (files.some(f => f.endsWith('.xcodeproj') || f.endsWith('.xcworkspace'))) {
|
|
14
|
-
chosenPlatform = 'ios';
|
|
15
|
-
}
|
|
16
|
-
else if (files.includes('gradlew') || files.includes('build.gradle') || files.includes('settings.gradle') || (files.includes('app') && (await fs.stat(path.join(appPath, 'app')).catch(() => null)))) {
|
|
17
|
-
chosenPlatform = 'android';
|
|
18
|
-
}
|
|
19
|
-
else {
|
|
20
|
-
chosenPlatform = 'android';
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
else if (typeof appPath === 'string') {
|
|
24
|
-
const ext = path.extname(appPath).toLowerCase();
|
|
25
|
-
if (ext === '.apk')
|
|
26
|
-
chosenPlatform = 'android';
|
|
27
|
-
else if (ext === '.ipa' || ext === '.app')
|
|
28
|
-
chosenPlatform = 'ios';
|
|
29
|
-
else
|
|
30
|
-
chosenPlatform = 'android';
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
catch {
|
|
34
|
-
chosenPlatform = 'android';
|
|
35
|
-
}
|
|
36
|
-
if (chosenPlatform === 'android') {
|
|
5
|
+
static async waitForElementHandler({ platform, text, timeout, deviceId }) {
|
|
6
|
+
const effectiveTimeout = timeout ?? 10000;
|
|
7
|
+
if (platform === 'android') {
|
|
37
8
|
const resolved = await resolveTargetDevice({ platform: 'android', deviceId });
|
|
38
|
-
|
|
39
|
-
const result = await androidInteract.installApp(appPath, resolved.id);
|
|
40
|
-
return result;
|
|
9
|
+
return await new AndroidInteract().waitForElement(text, effectiveTimeout, resolved.id);
|
|
41
10
|
}
|
|
42
11
|
else {
|
|
43
12
|
const resolved = await resolveTargetDevice({ platform: 'ios', deviceId });
|
|
44
|
-
|
|
45
|
-
const result = await iosInteract.installApp(appPath, resolved.id);
|
|
46
|
-
return result;
|
|
13
|
+
return await new iOSInteract().waitForElement(text, effectiveTimeout, resolved.id);
|
|
47
14
|
}
|
|
48
15
|
}
|
|
49
|
-
static async
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
16
|
+
static async tapHandler({ platform, x, y, deviceId }) {
|
|
17
|
+
const effectivePlatform = platform || 'android';
|
|
18
|
+
if (effectivePlatform === 'android') {
|
|
19
|
+
const resolved = await resolveTargetDevice({ platform: 'android', deviceId });
|
|
20
|
+
return await new AndroidInteract().tap(x, y, resolved.id);
|
|
53
21
|
}
|
|
54
22
|
else {
|
|
55
|
-
const resolved = await resolveTargetDevice({ platform: 'ios',
|
|
56
|
-
return await new iOSInteract().
|
|
23
|
+
const resolved = await resolveTargetDevice({ platform: 'ios', deviceId });
|
|
24
|
+
return await new iOSInteract().tap(x, y, resolved.id);
|
|
57
25
|
}
|
|
58
26
|
}
|
|
59
|
-
static async
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return await new AndroidInteract().terminateApp(appId, resolved.id);
|
|
63
|
-
}
|
|
64
|
-
else {
|
|
65
|
-
const resolved = await resolveTargetDevice({ platform: 'ios', appId, deviceId });
|
|
66
|
-
return await new iOSInteract().terminateApp(appId, resolved.id);
|
|
67
|
-
}
|
|
27
|
+
static async swipeHandler({ x1, y1, x2, y2, duration, deviceId }) {
|
|
28
|
+
const resolved = await resolveTargetDevice({ platform: 'android', deviceId });
|
|
29
|
+
return await new AndroidInteract().swipe(x1, y1, x2, y2, duration, resolved.id);
|
|
68
30
|
}
|
|
69
|
-
static async
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return await new AndroidInteract().restartApp(appId, resolved.id);
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
const resolved = await resolveTargetDevice({ platform: 'ios', appId, deviceId });
|
|
76
|
-
return await new iOSInteract().restartApp(appId, resolved.id);
|
|
77
|
-
}
|
|
31
|
+
static async typeTextHandler({ text, deviceId }) {
|
|
32
|
+
const resolved = await resolveTargetDevice({ platform: 'android', deviceId });
|
|
33
|
+
return await new AndroidInteract().typeText(text, resolved.id);
|
|
78
34
|
}
|
|
79
|
-
static async
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
return await new AndroidInteract().resetAppData(appId, resolved.id);
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
85
|
-
const resolved = await resolveTargetDevice({ platform: 'ios', appId, deviceId });
|
|
86
|
-
return await new iOSInteract().resetAppData(appId, resolved.id);
|
|
87
|
-
}
|
|
35
|
+
static async pressBackHandler({ deviceId }) {
|
|
36
|
+
const resolved = await resolveTargetDevice({ platform: 'android', deviceId });
|
|
37
|
+
return await new AndroidInteract().pressBack(resolved.id);
|
|
88
38
|
}
|
|
89
39
|
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { resolveTargetDevice, listDevices } from '../resolve-device.js';
|
|
4
|
+
import { AndroidManage } from '../android/manage.js';
|
|
5
|
+
import { iOSManage } from '../ios/manage.js';
|
|
6
|
+
export class ToolsManage {
|
|
7
|
+
static async buildAppHandler({ platform, projectPath, variant }) {
|
|
8
|
+
// delegate to platform-specific build implementations
|
|
9
|
+
const chosen = platform || 'android';
|
|
10
|
+
if (chosen === 'android') {
|
|
11
|
+
const android = new AndroidManage();
|
|
12
|
+
const artifact = await android.build(projectPath, variant);
|
|
13
|
+
return artifact;
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
const ios = new iOSManage();
|
|
17
|
+
const artifact = await ios.build(projectPath, variant);
|
|
18
|
+
return artifact;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
static async installAppHandler({ platform, appPath, deviceId }) {
|
|
22
|
+
let chosenPlatform = platform;
|
|
23
|
+
try {
|
|
24
|
+
const stat = await fs.stat(appPath).catch(() => null);
|
|
25
|
+
if (stat && stat.isDirectory()) {
|
|
26
|
+
// If the directory itself looks like an .app bundle, treat as iOS
|
|
27
|
+
if (appPath.endsWith('.app')) {
|
|
28
|
+
chosenPlatform = 'ios';
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
const files = (await fs.readdir(appPath).catch(() => []));
|
|
32
|
+
if (files.some(f => f.endsWith('.xcodeproj') || f.endsWith('.xcworkspace'))) {
|
|
33
|
+
chosenPlatform = 'ios';
|
|
34
|
+
}
|
|
35
|
+
else if (files.includes('gradlew') || files.includes('build.gradle') || files.includes('settings.gradle') || (files.includes('app') && (await fs.stat(path.join(appPath, 'app')).catch(() => null)))) {
|
|
36
|
+
chosenPlatform = 'android';
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
chosenPlatform = 'android';
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else if (typeof appPath === 'string') {
|
|
44
|
+
const ext = path.extname(appPath).toLowerCase();
|
|
45
|
+
if (ext === '.apk')
|
|
46
|
+
chosenPlatform = 'android';
|
|
47
|
+
else if (ext === '.ipa' || ext === '.app')
|
|
48
|
+
chosenPlatform = 'ios';
|
|
49
|
+
else
|
|
50
|
+
chosenPlatform = 'android';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
chosenPlatform = 'android';
|
|
55
|
+
}
|
|
56
|
+
if (chosenPlatform === 'android') {
|
|
57
|
+
const resolved = await resolveTargetDevice({ platform: 'android', deviceId });
|
|
58
|
+
const androidRun = new AndroidManage();
|
|
59
|
+
const result = await androidRun.installApp(appPath, resolved.id);
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
const resolved = await resolveTargetDevice({ platform: 'ios', deviceId });
|
|
64
|
+
const iosRun = new iOSManage();
|
|
65
|
+
const result = await iosRun.installApp(appPath, resolved.id);
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
static async startAppHandler({ platform, appId, deviceId }) {
|
|
70
|
+
if (platform === 'android') {
|
|
71
|
+
const resolved = await resolveTargetDevice({ platform: 'android', appId, deviceId });
|
|
72
|
+
return await new AndroidManage().startApp(appId, resolved.id);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
const resolved = await resolveTargetDevice({ platform: 'ios', appId, deviceId });
|
|
76
|
+
return await new iOSManage().startApp(appId, resolved.id);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
static async terminateAppHandler({ platform, appId, deviceId }) {
|
|
80
|
+
if (platform === 'android') {
|
|
81
|
+
const resolved = await resolveTargetDevice({ platform: 'android', appId, deviceId });
|
|
82
|
+
return await new AndroidManage().terminateApp(appId, resolved.id);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
const resolved = await resolveTargetDevice({ platform: 'ios', appId, deviceId });
|
|
86
|
+
return await new iOSManage().terminateApp(appId, resolved.id);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
static async restartAppHandler({ platform, appId, deviceId }) {
|
|
90
|
+
if (platform === 'android') {
|
|
91
|
+
const resolved = await resolveTargetDevice({ platform: 'android', appId, deviceId });
|
|
92
|
+
return await new AndroidManage().restartApp(appId, resolved.id);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
const resolved = await resolveTargetDevice({ platform: 'ios', appId, deviceId });
|
|
96
|
+
return await new iOSManage().restartApp(appId, resolved.id);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
static async resetAppDataHandler({ platform, appId, deviceId }) {
|
|
100
|
+
if (platform === 'android') {
|
|
101
|
+
const resolved = await resolveTargetDevice({ platform: 'android', appId, deviceId });
|
|
102
|
+
return await new AndroidManage().resetAppData(appId, resolved.id);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
const resolved = await resolveTargetDevice({ platform: 'ios', appId, deviceId });
|
|
106
|
+
return await new iOSManage().resetAppData(appId, resolved.id);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
static async buildAndInstallHandler({ platform, projectPath, deviceId, timeout }) {
|
|
110
|
+
const events = [];
|
|
111
|
+
const pushEvent = (obj) => events.push(JSON.stringify(obj));
|
|
112
|
+
const effectiveTimeout = timeout ?? 180000; // reserved for future streaming/timeouts
|
|
113
|
+
void effectiveTimeout;
|
|
114
|
+
// determine platform if not provided by inspecting path
|
|
115
|
+
let chosenPlatform = platform;
|
|
116
|
+
try {
|
|
117
|
+
const stat = await fs.stat(projectPath).catch(() => null);
|
|
118
|
+
if (!chosenPlatform) {
|
|
119
|
+
if (stat && stat.isDirectory()) {
|
|
120
|
+
const files = (await fs.readdir(projectPath).catch(() => []));
|
|
121
|
+
if (files.some(f => f.endsWith('.xcodeproj') || f.endsWith('.xcworkspace')))
|
|
122
|
+
chosenPlatform = 'ios';
|
|
123
|
+
else
|
|
124
|
+
chosenPlatform = 'android';
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
const ext = path.extname(projectPath).toLowerCase();
|
|
128
|
+
if (ext === '.apk')
|
|
129
|
+
chosenPlatform = 'android';
|
|
130
|
+
else if (ext === '.ipa' || ext === '.app')
|
|
131
|
+
chosenPlatform = 'ios';
|
|
132
|
+
else
|
|
133
|
+
chosenPlatform = 'android';
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
chosenPlatform = chosenPlatform || 'android';
|
|
139
|
+
}
|
|
140
|
+
pushEvent({ type: 'build', status: 'started', platform: chosenPlatform });
|
|
141
|
+
let buildRes;
|
|
142
|
+
try {
|
|
143
|
+
buildRes = await ToolsManage.buildAppHandler({ platform: chosenPlatform, projectPath });
|
|
144
|
+
if (buildRes && buildRes.error) {
|
|
145
|
+
pushEvent({ type: 'build', status: 'failed', error: buildRes.error });
|
|
146
|
+
return { ndjson: events.join('\n') + '\n', result: { success: false, error: buildRes.error } };
|
|
147
|
+
}
|
|
148
|
+
pushEvent({ type: 'build', status: 'finished', artifactPath: buildRes.artifactPath });
|
|
149
|
+
}
|
|
150
|
+
catch (e) {
|
|
151
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
152
|
+
pushEvent({ type: 'build', status: 'failed', error: msg });
|
|
153
|
+
return { ndjson: events.join('\n') + '\n', result: { success: false, error: msg } };
|
|
154
|
+
}
|
|
155
|
+
// Install phase
|
|
156
|
+
const artifact = buildRes.artifactPath || projectPath;
|
|
157
|
+
pushEvent({ type: 'install', status: 'started', artifactPath: artifact, deviceId });
|
|
158
|
+
let installRes;
|
|
159
|
+
try {
|
|
160
|
+
installRes = await ToolsManage.installAppHandler({ platform: chosenPlatform, appPath: artifact, deviceId });
|
|
161
|
+
if (installRes && installRes.installed === true) {
|
|
162
|
+
pushEvent({ type: 'install', status: 'finished', artifactPath: artifact, device: installRes.device });
|
|
163
|
+
return { ndjson: events.join('\n') + '\n', result: { success: true, artifactPath: artifact, device: installRes.device, output: installRes.output } };
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
pushEvent({ type: 'install', status: 'failed', error: installRes.error || 'unknown' });
|
|
167
|
+
return { ndjson: events.join('\n') + '\n', result: { success: false, error: installRes.error || 'install failed' } };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
catch (e) {
|
|
171
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
172
|
+
pushEvent({ type: 'install', status: 'failed', error: msg });
|
|
173
|
+
return { ndjson: events.join('\n') + '\n', result: { success: false, error: msg } };
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
static async listDevicesHandler({ platform, appId }) {
|
|
177
|
+
const devices = await listDevices(platform, appId);
|
|
178
|
+
return { devices };
|
|
179
|
+
}
|
|
180
|
+
}
|
package/dist/tools/observe.js
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
|
-
import { resolveTargetDevice
|
|
1
|
+
import { resolveTargetDevice } from '../resolve-device.js';
|
|
2
2
|
import { AndroidObserve } from '../android/observe.js';
|
|
3
3
|
import { iOSObserve } from '../ios/observe.js';
|
|
4
|
-
import { AndroidInteract } from '../android/interact.js';
|
|
5
|
-
import { iOSInteract } from '../ios/interact.js';
|
|
6
4
|
export class ToolsObserve {
|
|
5
|
+
static async getUITreeHandler({ platform, deviceId }) {
|
|
6
|
+
if (platform === 'android') {
|
|
7
|
+
const resolved = await resolveTargetDevice({ platform: 'android', deviceId });
|
|
8
|
+
return await new AndroidObserve().getUITree(resolved.id);
|
|
9
|
+
}
|
|
10
|
+
else {
|
|
11
|
+
const resolved = await resolveTargetDevice({ platform: 'ios', deviceId });
|
|
12
|
+
return await new iOSObserve().getUITree(resolved.id);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
static async getCurrentScreenHandler({ deviceId }) {
|
|
16
|
+
const resolved = await resolveTargetDevice({ platform: 'android', deviceId });
|
|
17
|
+
return await new AndroidObserve().getCurrentScreen(resolved.id);
|
|
18
|
+
}
|
|
7
19
|
static async getLogsHandler({ platform, appId, deviceId, lines }) {
|
|
8
20
|
if (platform === 'android') {
|
|
9
21
|
const resolved = await resolveTargetDevice({ platform: 'android', appId, deviceId });
|
|
@@ -25,93 +37,35 @@ export class ToolsObserve {
|
|
|
25
37
|
const sid = sessionId || 'default';
|
|
26
38
|
if (effectivePlatform === 'android') {
|
|
27
39
|
const resolved = await resolveTargetDevice({ platform: 'android', appId: packageName, deviceId });
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
return await startAndroidLogStream(packageName, level || 'error', resolved.id, sid);
|
|
40
|
+
// Delegate to AndroidObserve's log stream methods
|
|
41
|
+
return await new AndroidObserve().startLogStream(packageName, level || 'error', resolved.id, sid);
|
|
31
42
|
}
|
|
32
43
|
else {
|
|
33
44
|
const resolved = await resolveTargetDevice({ platform: 'ios', appId: packageName, deviceId });
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
return await startIOSLogStream(packageName, resolved.id, sid);
|
|
45
|
+
// Delegate to iOSObserve for starting log streams
|
|
46
|
+
return await new iOSObserve().startLogStream(packageName, resolved.id, sid);
|
|
37
47
|
}
|
|
38
48
|
}
|
|
39
49
|
static async readLogStreamHandler({ platform, sessionId, limit, since }) {
|
|
40
50
|
const effectivePlatform = platform || 'android';
|
|
41
51
|
const sid = sessionId || 'default';
|
|
42
52
|
if (effectivePlatform === 'android') {
|
|
43
|
-
|
|
44
|
-
return await readLogStreamLines(sid, limit ?? 100, since);
|
|
53
|
+
return await new AndroidObserve().readLogStream(sid, limit ?? 100, since);
|
|
45
54
|
}
|
|
46
55
|
else {
|
|
47
|
-
|
|
48
|
-
return await readIOSLogStreamLines(sid, limit ?? 100, since);
|
|
56
|
+
return await new iOSObserve().readLogStream(sid, limit ?? 100, since);
|
|
49
57
|
}
|
|
50
58
|
}
|
|
51
59
|
static async stopLogStreamHandler({ platform, sessionId }) {
|
|
52
60
|
const effectivePlatform = platform || 'android';
|
|
53
61
|
const sid = sessionId || 'default';
|
|
54
62
|
if (effectivePlatform === 'android') {
|
|
55
|
-
|
|
56
|
-
return await stopAndroidLogStream(sid);
|
|
57
|
-
}
|
|
58
|
-
else {
|
|
59
|
-
const { stopIOSLogStream } = await import('../ios/utils.js');
|
|
60
|
-
return await stopIOSLogStream(sid);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
static async getUITreeHandler({ platform, deviceId }) {
|
|
64
|
-
if (platform === 'android') {
|
|
65
|
-
const resolved = await resolveTargetDevice({ platform: 'android', deviceId });
|
|
66
|
-
return await new AndroidObserve().getUITree(resolved.id);
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
const resolved = await resolveTargetDevice({ platform: 'ios', deviceId });
|
|
70
|
-
return await new iOSObserve().getUITree(resolved.id);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
static async getCurrentScreenHandler({ deviceId }) {
|
|
74
|
-
const resolved = await resolveTargetDevice({ platform: 'android', deviceId });
|
|
75
|
-
return await new AndroidObserve().getCurrentScreen(resolved.id);
|
|
76
|
-
}
|
|
77
|
-
static async waitForElementHandler({ platform, text, timeout, deviceId }) {
|
|
78
|
-
const effectiveTimeout = timeout ?? 10000;
|
|
79
|
-
if (platform === 'android') {
|
|
80
|
-
const resolved = await resolveTargetDevice({ platform: 'android', deviceId });
|
|
81
|
-
return await new AndroidInteract().waitForElement(text, effectiveTimeout, resolved.id);
|
|
82
|
-
}
|
|
83
|
-
else {
|
|
84
|
-
const resolved = await resolveTargetDevice({ platform: 'ios', deviceId });
|
|
85
|
-
return await new iOSInteract().waitForElement(text, effectiveTimeout, resolved.id);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
static async tapHandler({ platform, x, y, deviceId }) {
|
|
89
|
-
const effectivePlatform = platform || 'android';
|
|
90
|
-
if (effectivePlatform === 'android') {
|
|
91
|
-
const resolved = await resolveTargetDevice({ platform: 'android', deviceId });
|
|
92
|
-
return await new AndroidInteract().tap(x, y, resolved.id);
|
|
63
|
+
return await new AndroidObserve().stopLogStream(sid);
|
|
93
64
|
}
|
|
94
65
|
else {
|
|
95
|
-
|
|
96
|
-
return await new iOSInteract().tap(x, y, resolved.id);
|
|
66
|
+
return await new iOSObserve().stopLogStream(sid);
|
|
97
67
|
}
|
|
98
68
|
}
|
|
99
|
-
static async swipeHandler({ x1, y1, x2, y2, duration, deviceId }) {
|
|
100
|
-
const resolved = await resolveTargetDevice({ platform: 'android', deviceId });
|
|
101
|
-
return await new AndroidInteract().swipe(x1, y1, x2, y2, duration, resolved.id);
|
|
102
|
-
}
|
|
103
|
-
static async typeTextHandler({ text, deviceId }) {
|
|
104
|
-
const resolved = await resolveTargetDevice({ platform: 'android', deviceId });
|
|
105
|
-
return await new AndroidInteract().typeText(text, resolved.id);
|
|
106
|
-
}
|
|
107
|
-
static async pressBackHandler({ deviceId }) {
|
|
108
|
-
const resolved = await resolveTargetDevice({ platform: 'android', deviceId });
|
|
109
|
-
return await new AndroidInteract().pressBack(resolved.id);
|
|
110
|
-
}
|
|
111
|
-
static async listDevicesHandler({ platform, appId }) {
|
|
112
|
-
const devices = await listDevices(platform, appId);
|
|
113
|
-
return { devices };
|
|
114
|
-
}
|
|
115
69
|
static async captureScreenshotHandler({ platform, deviceId }) {
|
|
116
70
|
const effectivePlatform = platform || 'android';
|
|
117
71
|
if (effectivePlatform === 'android') {
|