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.
- package/dist/interact/android.js +0 -27
- package/dist/interact/index.js +145 -124
- package/dist/interact/ios.js +0 -26
- package/dist/scripts/capture_ui_after_tap.mjs +43 -0
- package/dist/scripts/check_play_observe.mjs +18 -0
- package/dist/scripts/check_play_substring.mjs +38 -0
- package/dist/scripts/dump_ui_tree.mjs +20 -0
- package/dist/scripts/observe-test.mjs +32 -0
- package/dist/scripts/press_and_observe.mjs +90 -0
- package/dist/scripts/press_and_wait_ui.mjs +85 -0
- package/dist/scripts/test_generate_and_wait.mjs +123 -0
- package/dist/server.js +15 -25
- package/dist/system/gradle.js +4 -4
- package/dist/utils/android/utils.js +2 -2
- package/dist/utils/resolve-device.js +5 -0
- package/docs/CHANGELOG.md +7 -1
- package/docs/tools/interact.md +7 -27
- package/package.json +2 -2
- package/src/interact/android.ts +1 -32
- package/src/interact/index.ts +98 -78
- package/src/interact/ios.ts +1 -31
- package/src/server.ts +18 -25
- package/src/system/gradle.ts +4 -4
- package/src/utils/android/utils.ts +2 -2
- package/src/utils/resolve-device.ts +6 -0
- package/test/interact/device/run-real-test.ts +3 -19
- package/test/interact/unit/{observe_until.test.ts → wait_for_ui.test.ts} +6 -6
- package/test/observe/device/wait_for_element_real.ts +3 -80
- package/test/observe/unit/wait_for_element_mock.ts +2 -104
- package/test/observe/unit/wait_for_ui_edge_cases.test.ts +41 -0
- package/test/observe/unit/wait_for_ui_stability.test.ts +30 -0
- package/test/unit/index.ts +27 -15
- package/test/interact/device/observe_until_device.ts +0 -24
|
@@ -1,104 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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) })
|
package/test/unit/index.ts
CHANGED
|
@@ -1,18 +1,30 @@
|
|
|
1
1
|
// Aggregator entrypoint for unit tests (updated to new test layout)
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import
|
|
5
|
-
import
|
|
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
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import
|
|
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
|
-
|
|
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
|
-
})()
|