mobile-debug-mcp 0.20.1 → 0.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/interact/android.js +0 -27
  2. package/dist/interact/index.js +145 -124
  3. package/dist/interact/ios.js +0 -26
  4. package/dist/scripts/capture_ui_after_tap.mjs +43 -0
  5. package/dist/scripts/check_play_observe.mjs +18 -0
  6. package/dist/scripts/check_play_substring.mjs +38 -0
  7. package/dist/scripts/dump_ui_tree.mjs +20 -0
  8. package/dist/scripts/observe-test.mjs +32 -0
  9. package/dist/scripts/press_and_observe.mjs +90 -0
  10. package/dist/scripts/press_and_wait_ui.mjs +85 -0
  11. package/dist/scripts/test_generate_and_wait.mjs +123 -0
  12. package/dist/server.js +15 -25
  13. package/dist/system/gradle.js +4 -4
  14. package/dist/utils/android/utils.js +2 -2
  15. package/dist/utils/resolve-device.js +5 -0
  16. package/docs/CHANGELOG.md +7 -1
  17. package/docs/tools/interact.md +7 -27
  18. package/package.json +2 -2
  19. package/src/interact/android.ts +1 -32
  20. package/src/interact/index.ts +98 -78
  21. package/src/interact/ios.ts +1 -31
  22. package/src/server.ts +18 -25
  23. package/src/system/gradle.ts +4 -4
  24. package/src/utils/android/utils.ts +2 -2
  25. package/src/utils/resolve-device.ts +6 -0
  26. package/test/interact/device/run-real-test.ts +3 -19
  27. package/test/interact/unit/{observe_until.test.ts → wait_for_ui.test.ts} +6 -6
  28. package/test/observe/device/wait_for_element_real.ts +3 -80
  29. package/test/observe/unit/wait_for_element_mock.ts +2 -104
  30. package/test/observe/unit/wait_for_ui_edge_cases.test.ts +41 -0
  31. package/test/observe/unit/wait_for_ui_stability.test.ts +30 -0
  32. package/test/unit/index.ts +27 -15
  33. package/test/interact/device/observe_until_device.ts +0 -24
@@ -1,104 +1,2 @@
1
- import { AndroidInteract } from '../../../src/interact/index.js';
2
- import { AndroidObserve } from '../../../src/observe/index.js';
3
-
4
- const originalGetUITree = (AndroidObserve as any).prototype.getUITree;
5
-
6
- async function runTests() {
7
- console.log("Starting tests for wait_for_element...");
8
- const interact = new AndroidInteract();
9
-
10
- console.log("\nTest 1: Element found immediately");
11
- (AndroidObserve as any).prototype.getUITree = async () => ({
12
- device: { platform: "android", id: "mock", osVersion: "12", model: "Pixel", simulator: true },
13
- screen: "",
14
- resolution: { width: 1080, height: 1920 },
15
- elements: [{
16
- text: "Target",
17
- type: "Button",
18
- contentDescription: null,
19
- clickable: true,
20
- enabled: true,
21
- visible: true,
22
- bounds: [0, 0, 100, 100],
23
- resourceId: null
24
- }]
25
- });
26
-
27
- const start1 = Date.now();
28
- const result1 = await interact.waitForElement("Target", 1000);
29
- const elapsed1 = Date.now() - start1;
30
- console.log("Result:", result1.found === true ? "PASS" : "FAIL");
31
- console.log("Element:", result1.element ? "FOUND" : "MISSING");
32
- console.log("Elapsed:", elapsed1, "ms");
33
-
34
- console.log("\nTest 2: Element not found (timeout)");
35
- (AndroidObserve as any).prototype.getUITree = async () => ({
36
- device: { platform: "android", id: "mock", osVersion: "12", model: "Pixel", simulator: true },
37
- screen: "",
38
- resolution: { width: 1080, height: 1920 },
39
- elements: []
40
- });
41
-
42
- const start2 = Date.now();
43
- const result2 = await interact.waitForElement("Target", 1200);
44
- const elapsed2 = Date.now() - start2;
45
- console.log("Result:", result2.found === false ? "PASS" : "FAIL");
46
- console.log("Elapsed time (should be >= 1200ms):", elapsed2, elapsed2 >= 1200 ? "PASS" : "FAIL");
47
-
48
- console.log("\nTest 3: Element found after polling");
49
- let calls = 0;
50
- (AndroidObserve as any).prototype.getUITree = async () => {
51
- calls++;
52
- if (calls < 3) {
53
- return {
54
- device: { platform: "android", id: "mock", osVersion: "12", model: "Pixel", simulator: true },
55
- screen: "",
56
- resolution: { width: 1080, height: 1920 },
57
- elements: []
58
- };
59
- }
60
- return {
61
- device: { platform: "android", id: "mock", osVersion: "12", model: "Pixel", simulator: true },
62
- screen: "",
63
- resolution: { width: 1080, height: 1920 },
64
- elements: [{
65
- text: "Target",
66
- type: "Button",
67
- contentDescription: null,
68
- clickable: true,
69
- enabled: true,
70
- visible: true,
71
- bounds: [0, 0, 100, 100],
72
- resourceId: null
73
- }]
74
- };
75
- };
76
-
77
- const start3 = Date.now();
78
- const result3 = await interact.waitForElement("Target", 2000);
79
- const elapsed3 = Date.now() - start3;
80
- console.log("Result:", result3.found === true ? "PASS" : "FAIL");
81
- console.log("Calls:", calls, calls >= 3 ? "PASS" : "FAIL");
82
- console.log("Elapsed time (should be >= 1000ms):", elapsed3, elapsed3 >= 1000 ? "PASS" : "FAIL");
83
-
84
- console.log("\nTest 4: Error handling (fast failure)");
85
- (AndroidObserve as any).prototype.getUITree = async () => ({
86
- device: { platform: "android", id: "mock", osVersion: "12", model: "Pixel", simulator: true },
87
- screen: "",
88
- resolution: { width: 0, height: 0 },
89
- elements: [],
90
- error: "ADB Connection Failed"
91
- });
92
-
93
- const start4 = Date.now();
94
- const result4 = await interact.waitForElement("Target", 5000);
95
- const elapsed4 = Date.now() - start4;
96
- console.log("Result:", result4.found === false && result4.error === "ADB Connection Failed" ? "PASS" : "FAIL");
97
- console.log("Error Message:", result4.error);
98
- console.log("Elapsed time (should be < 1000ms):", elapsed4, elapsed4 < 1000 ? "PASS" : "FAIL");
99
-
100
- // Restore
101
- (AndroidObserve as any).prototype.getUITree = originalGetUITree;
102
- }
103
-
104
- runTests().catch(console.error);
1
+ // wait_for_element tests removed tool deprecated
2
+ console.log('wait_for_element unit tests removed');
@@ -0,0 +1,41 @@
1
+ import { ToolsInteract } from '../../../../src/interact/index.js'
2
+ import * as Observe from '../../../../src/observe/index.js'
3
+
4
+ async function run() {
5
+ console.log('Unit: wait_for_ui edge cases')
6
+
7
+ const origFind = (ToolsInteract as any).findElementHandler
8
+ const origFp = (Observe as any).ToolsObserve.getScreenFingerprintHandler
9
+
10
+ try {
11
+ // 1) Immediate absence should pass for match='absent'
12
+ (ToolsInteract as any).findElementHandler = async () => ({ found: false })
13
+ const r1 = await (ToolsInteract as any).waitForUIHandler({ type: 'ui', query: 'Nothing', timeoutMs: 2000, pollIntervalMs: 100, stability_ms: 200, match: 'absent', platform: 'android' })
14
+ console.log('Immediate absent test:', r1 && (r1 as any).success ? 'PASS' : 'FAIL', JSON.stringify({ poll_count: (r1 as any).poll_count, duration_ms: (r1 as any).duration_ms, stable_duration_ms: (r1 as any).stable_duration_ms, matchSource: (r1 as any).matchSource }, null, 2))
15
+
16
+ // 2) Boundary stability: condition becomes true and stays exactly long enough
17
+ // Use pollInterval 100ms and stability 300ms -> need ~3 consecutive trues
18
+ let seq2 = [false, true, true, true]
19
+ (ToolsInteract as any).findElementHandler = async () => ({ found: seq2.shift() ?? true })
20
+ const r2 = await (ToolsInteract as any).waitForUIHandler({ type: 'ui', query: 'Boundary', timeoutMs: 2000, pollIntervalMs: 100, stability_ms: 300, match: 'present', platform: 'android' })
21
+ console.log('Boundary stability test:', r2 && (r2 as any).success ? 'PASS' : 'FAIL', JSON.stringify({ poll_count: (r2 as any).poll_count, duration_ms: (r2 as any).duration_ms, stable_duration_ms: (r2 as any).stable_duration_ms, matchSource: (r2 as any).matchSource }, null, 2))
22
+
23
+ // 3) Long flicker that never stabilizes should timeout/fail
24
+ // Sequence toggles true/false repeatedly
25
+ let seq3 = [false, true, false, true, false, true, false]
26
+ (ToolsInteract as any).findElementHandler = async () => ({ found: seq3.shift() ?? false })
27
+ const r3 = await (ToolsInteract as any).waitForUIHandler({ type: 'ui', query: 'Flicker', timeoutMs: 1200, pollIntervalMs: 150, stability_ms: 400, match: 'present', platform: 'android' })
28
+ console.log('Long flicker timeout test:', !(r3 && (r3 as any).success) ? 'PASS' : 'FAIL', JSON.stringify({ poll_count: (r3 as any).poll_count, duration_ms: (r3 as any).duration_ms, stable_duration_ms: (r3 as any).stable_duration_ms, matchSource: (r3 as any).matchSource }, null, 2))
29
+
30
+ // 4) Very short stability requirement should pass quickly
31
+ (ToolsInteract as any).findElementHandler = async () => ({ found: true })
32
+ const r4 = await (ToolsInteract as any).waitForUIHandler({ type: 'ui', query: 'ShortStable', timeoutMs: 2000, pollIntervalMs: 200, stability_ms: 50, match: 'present', platform: 'android' })
33
+ console.log('Short stability test:', r4 && (r4 as any).success ? 'PASS' : 'FAIL', JSON.stringify({ poll_count: (r4 as any).poll_count, duration_ms: (r4 as any).duration_ms, stable_duration_ms: (r4 as any).stable_duration_ms, matchSource: (r4 as any).matchSource }, null, 2))
34
+
35
+ } finally {
36
+ (ToolsInteract as any).findElementHandler = origFind
37
+ (Observe as any).ToolsObserve.getScreenFingerprintHandler = origFp
38
+ }
39
+ }
40
+
41
+ run().catch(e=>{ console.error(e); process.exit(1) })
@@ -0,0 +1,30 @@
1
+ import { ToolsInteract } from '../../../../src/interact/index.js'
2
+ import * as Observe from '../../../../src/observe/index.js'
3
+
4
+ async function run() {
5
+ console.log('Unit: wait_for_ui stability behavior')
6
+
7
+ const origFind = (ToolsInteract as any).findElementHandler
8
+ const origFp = (Observe as any).ToolsObserve.getScreenFingerprintHandler
9
+
10
+ try {
11
+ // Simulate UI flicker: present, absent, present, then stable
12
+ const seq = [false, true, false, true, true, true]
13
+ (ToolsInteract as any).findElementHandler = async () => ({ found: seq.shift() ?? true })
14
+
15
+ const res = await (ToolsInteract as any).waitForUIHandler({ type: 'ui', query: 'X', timeoutMs: 5000, pollIntervalMs: 100, stability_ms: 500, platform: 'android' })
16
+ const ok = res && (res as any).success
17
+ console.log('Flicker stability test:', ok ? 'PASS' : 'FAIL', JSON.stringify((res as any).telemetry || {}, null, 2))
18
+
19
+ // Simulate immediate stable presence
20
+ (ToolsInteract as any).findElementHandler = async () => ({ found: true })
21
+ const res2 = await (ToolsInteract as any).waitForUIHandler({ type: 'ui', query: 'Y', timeoutMs: 2000, pollIntervalMs: 100, stability_ms: 300, platform: 'android' })
22
+ console.log('Immediate stable test:', res2 && (res2 as any).success ? 'PASS' : 'FAIL', JSON.stringify((res2 as any).telemetry || {}, null, 2))
23
+
24
+ } finally {
25
+ (ToolsInteract as any).findElementHandler = origFind
26
+ (Observe as any).ToolsObserve.getScreenFingerprintHandler = origFp
27
+ }
28
+ }
29
+
30
+ run().catch(e=>{ console.error(e); process.exit(1) })
@@ -1,18 +1,30 @@
1
1
  // Aggregator entrypoint for unit tests (updated to new test layout)
2
- import '../observe/unit/logparse.test.ts'
3
- import '../observe/unit/logstream.test.ts'
4
- import '../observe/unit/wait_for_element_mock.ts'
5
- import '../observe/unit/get_screen_fingerprint.test.ts'
2
+ (async function() {
3
+ // Core unit tests that do not require real devices
4
+ await import('../observe/unit/logparse.test.ts')
5
+ await import('../observe/unit/logstream.test.ts')
6
+ await import('../observe/unit/get_screen_fingerprint.test.ts')
6
7
 
7
- import '../manage/unit/install.test.ts'
8
- import '../manage/unit/build.test.ts'
9
- import '../manage/unit/build_and_install.test.ts'
10
- import '../manage/unit/diagnostics.test.ts'
11
- import '../manage/unit/detection.test.ts'
12
- import '../manage/unit/mcp_disable_autodetect.test.ts'
13
- import '../interact/unit/wait_for_screen_change.test.ts'
14
- import '../observe/unit/capture_debug_snapshot.test.ts'
15
- import '../observe/unit/find_element.test.ts'
16
- import '../interact/unit/observe_until.test.ts'
8
+ await import('../manage/unit/install.test.ts')
9
+ await import('../manage/unit/build.test.ts')
10
+ await import('../manage/unit/build_and_install.test.ts')
11
+ await import('../manage/unit/diagnostics.test.ts')
12
+ await import('../manage/unit/detection.test.ts')
13
+ await import('../manage/unit/mcp_disable_autodetect.test.ts')
14
+ await import('../interact/unit/wait_for_screen_change.test.ts')
17
15
 
18
- console.log('Unit tests loaded.')
16
+ // Conditionally include device-dependent unit tests. Set SKIP_DEVICE_TESTS=1 to exclude.
17
+ if (process.env.SKIP_DEVICE_TESTS !== '1') {
18
+ try {
19
+ await import('../observe/unit/capture_debug_snapshot.test.ts')
20
+ await import('../observe/unit/find_element.test.ts')
21
+ await import('../interact/unit/wait_for_ui.test.ts')
22
+ } catch (e) {
23
+ console.warn('Skipping some device-dependent tests due to import error:', e instanceof Error ? e.message : String(e))
24
+ }
25
+ } else {
26
+ console.log('SKIP_DEVICE_TESTS=1 detected - skipping device-dependent unit tests')
27
+ }
28
+
29
+ console.log('Unit tests loaded.')
30
+ })().catch(e => { console.error(e); process.exit(1) })
@@ -1,24 +0,0 @@
1
- (async function main(){
2
- try{
3
- const inter = await import('../../src/interact/index.ts')
4
- const manage = await import('../../src/manage/index.ts')
5
- const ToolsInteract = (inter as any).ToolsInteract
6
- const ToolsManage = (manage as any).ToolsManage
7
-
8
- const ANDROID_ID = process.env.ANDROID_DEVICE || 'emulator-5554'
9
- const IOS_UDID = process.env.IOS_DEVICE || '2EFFD8FD-5D09-47CC-95F8-28BBE30AF7ED'
10
- console.log('Device test starting. Android:', ANDROID_ID, 'iOS:', IOS_UDID)
11
-
12
- // Start modul8 on both platforms if present
13
- try { await ToolsManage.startAppHandler({ platform: 'android', appId: 'com.ideamechanics.modul8', deviceId: ANDROID_ID }); console.log('Started android app (if installed)') } catch(e){ console.error('Android start skipped:', e.message || e) }
14
- try { await ToolsManage.startAppHandler({ platform: 'ios', appId: 'com.ideamechanics.modul8.Modul8', deviceId: IOS_UDID }); console.log('Started ios app (if installed)') } catch(e){ console.error('iOS start skipped:', e.message || e) }
15
-
16
- // Observe UI for Generate Session on both devices (will timeout if not present)
17
- const aRes = await ToolsInteract.observeUntilHandler({ type: 'ui', query: 'Generate Session', timeoutMs: 20000, pollIntervalMs: 500, platform: 'android', deviceId: ANDROID_ID })
18
- console.log('Android observe result:', JSON.stringify(aRes, null, 2))
19
-
20
- const iRes = await ToolsInteract.observeUntilHandler({ type: 'ui', query: 'Generate Session', timeoutMs: 20000, pollIntervalMs: 500, platform: 'ios', deviceId: IOS_UDID })
21
- console.log('iOS observe result:', JSON.stringify(iRes, null, 2))
22
-
23
- } catch (e) { console.error('ERR', e); process.exit(1) }
24
- })()