mobile-debug-mcp 0.12.8 → 0.14.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 +2 -2
- package/dist/android/interact.js +12 -0
- package/dist/ios/interact.js +51 -0
- package/dist/ios/manage.js +5 -106
- package/dist/ios/utils.js +0 -49
- package/dist/server.js +74 -30
- package/dist/tools/interact.js +17 -20
- package/dist/tools/scroll_to_element.js +98 -0
- package/docs/CHANGELOG.md +10 -0
- package/docs/tools/TOOLS.md +3 -3
- package/docs/tools/interact.md +31 -0
- package/package.json +1 -1
- package/src/android/interact.ts +13 -0
- package/src/ios/interact.ts +56 -1
- package/src/ios/manage.ts +6 -93
- package/src/ios/utils.ts +0 -47
- package/src/server.ts +78 -25
- package/src/tools/interact.ts +19 -18
- package/src/tools/scroll_to_element.ts +110 -0
- package/src/types.ts +0 -1
- package/test/device/observe/run-scroll-test-android.ts +24 -0
- package/test/unit/observe/scroll_to_element.test.ts +129 -0
- package/test/unit/observe/wait_for_element_mock.ts +1 -1
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { ToolsInteract } from '../../../src/tools/interact.js'
|
|
2
|
+
import { ToolsObserve } from '../../../src/tools/observe.js'
|
|
3
|
+
|
|
4
|
+
const origGet = (ToolsObserve as any).getUITreeHandler
|
|
5
|
+
const origSwipe = (ToolsInteract as any).swipeHandler
|
|
6
|
+
|
|
7
|
+
async function runTests() {
|
|
8
|
+
// Use a stable logger to avoid test harness replacing console.log between calls
|
|
9
|
+
console.log = (...args: any[]) => { try { process.stdout.write(args.map(a => (typeof a === 'string' ? a : JSON.stringify(a))).join(' ') + '\n') } catch {} }
|
|
10
|
+
console.log('Starting tests for scroll_to_element...')
|
|
11
|
+
|
|
12
|
+
// Test 1: Element found immediately
|
|
13
|
+
console.log('\nTest 1: Element found immediately')
|
|
14
|
+
(ToolsObserve as any).getUITreeHandler = async () => ({
|
|
15
|
+
device: { platform: 'android', id: 'mock', osVersion: '12', model: 'Pixel', simulator: true },
|
|
16
|
+
screen: '',
|
|
17
|
+
resolution: { width: 1080, height: 1920 },
|
|
18
|
+
elements: [{
|
|
19
|
+
text: 'Target',
|
|
20
|
+
type: 'Button',
|
|
21
|
+
contentDescription: null,
|
|
22
|
+
clickable: true,
|
|
23
|
+
enabled: true,
|
|
24
|
+
visible: true,
|
|
25
|
+
bounds: [0, 0, 100, 100],
|
|
26
|
+
resourceId: null
|
|
27
|
+
}]
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const res1 = await ToolsInteract.scrollToElementHandler({ platform: 'android', selector: { text: 'Target' }, direction: 'down', maxScrolls: 5, scrollAmount: 0.7 })
|
|
31
|
+
console.log('Result:', res1.success === true ? 'PASS' : 'FAIL')
|
|
32
|
+
console.log('scrollsPerformed:', (res1 as any).scrollsPerformed)
|
|
33
|
+
|
|
34
|
+
// Test 2: Element found after scrolling
|
|
35
|
+
console.log('\nTest 2: Element found after scrolling')
|
|
36
|
+
let calls = 0
|
|
37
|
+
(ToolsObserve as any).getUITreeHandler = async () => {
|
|
38
|
+
calls++
|
|
39
|
+
if (calls < 3) {
|
|
40
|
+
return {
|
|
41
|
+
device: { platform: 'android', id: 'mock', osVersion: '12', model: 'Pixel', simulator: true },
|
|
42
|
+
screen: '',
|
|
43
|
+
resolution: { width: 1080, height: 1920 },
|
|
44
|
+
elements: []
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
device: { platform: 'android', id: 'mock', osVersion: '12', model: 'Pixel', simulator: true },
|
|
49
|
+
screen: '',
|
|
50
|
+
resolution: { width: 1080, height: 1920 },
|
|
51
|
+
elements: [{
|
|
52
|
+
text: 'Target',
|
|
53
|
+
type: 'Button',
|
|
54
|
+
contentDescription: null,
|
|
55
|
+
clickable: true,
|
|
56
|
+
enabled: true,
|
|
57
|
+
visible: true,
|
|
58
|
+
bounds: [0, 0, 100, 100],
|
|
59
|
+
resourceId: null
|
|
60
|
+
}]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Stub swipe so it doesn't try to call adb/idb
|
|
65
|
+
(ToolsInteract as any).swipeHandler = async () => ({ success: true })
|
|
66
|
+
|
|
67
|
+
const res2 = await ToolsInteract.scrollToElementHandler({ platform: 'android', selector: { text: 'Target' }, direction: 'down', maxScrolls: 5, scrollAmount: 0.7 })
|
|
68
|
+
console.log('Result:', res2.success === true ? 'PASS' : 'FAIL')
|
|
69
|
+
console.log('calls:', calls, calls >= 3 ? 'PASS' : 'FAIL')
|
|
70
|
+
|
|
71
|
+
// Test 3: UI unchanged stops early
|
|
72
|
+
console.log('\nTest 3: UI unchanged stops early')
|
|
73
|
+
(ToolsObserve as any).getUITreeHandler = async () => ({
|
|
74
|
+
device: { platform: 'android', id: 'mock', osVersion: '12', model: 'Pixel', simulator: true },
|
|
75
|
+
screen: '',
|
|
76
|
+
resolution: { width: 1080, height: 1920 },
|
|
77
|
+
elements: []
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
(ToolsInteract as any).swipeHandler = async () => ({ success: true })
|
|
81
|
+
|
|
82
|
+
const res3 = await ToolsInteract.scrollToElementHandler({ platform: 'android', selector: { text: 'Missing' }, direction: 'down', maxScrolls: 5, scrollAmount: 0.7 })
|
|
83
|
+
console.log('Result:', res3.success === false && (res3 as any).attempts === 1 ? 'PASS' : 'FAIL')
|
|
84
|
+
console.log('Reason:', (res3 as any).reason || JSON.stringify(res3))
|
|
85
|
+
|
|
86
|
+
// Test 4: Offscreen element scrolls into view
|
|
87
|
+
console.log('\nTest 4: Offscreen element scrolls into view')
|
|
88
|
+
const ai = new (await import('../../../src/android/interact.js')).AndroidInteract()
|
|
89
|
+
const origObserveGet = ai['observe'].getUITree
|
|
90
|
+
const origAiSwipe = ai.swipe
|
|
91
|
+
let swiped = false
|
|
92
|
+
let swipeCalled = 0
|
|
93
|
+
;(ai['observe'] as any).getUITree = async () => {
|
|
94
|
+
if (!swiped) {
|
|
95
|
+
return {
|
|
96
|
+
device: { platform: 'android', id: 'mock', osVersion: '12', model: 'Pixel', simulator: true },
|
|
97
|
+
screen: '',
|
|
98
|
+
resolution: { width: 1080, height: 1920 },
|
|
99
|
+
elements: [ { text: null, type: 'android.view.View', resourceId: null, contentDescription: null, bounds: [0,0,1080,200], visible: true } ]
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
device: { platform: 'android', id: 'mock', osVersion: '12', model: 'Pixel', simulator: true },
|
|
104
|
+
screen: '',
|
|
105
|
+
resolution: { width: 1080, height: 1920 },
|
|
106
|
+
elements: [{ text: 'OffscreenTarget', type: 'android.widget.Button', contentDescription: null, clickable: true, enabled: true, visible: true, bounds: [100,400,300,460], resourceId: null }]
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
;(ai as any).swipe = async () => { swipeCalled++; swiped = true; return { success: true } }
|
|
110
|
+
|
|
111
|
+
const r4 = await ai.scrollToElement({ text: 'OffscreenTarget' }, 'down', 3, 0.7, 'mock')
|
|
112
|
+
const ok4 = r4 && (r4 as any).success === true && (r4 as any).scrollsPerformed === 1 && swipeCalled === 1
|
|
113
|
+
console.log('Result:', ok4 ? 'PASS' : 'FAIL')
|
|
114
|
+
console.log(' success:', (r4 as any).success, 'scrollsPerformed:', (r4 as any).scrollsPerformed, 'swipeCalled:', swipeCalled)
|
|
115
|
+
|
|
116
|
+
;(ai['observe'] as any).getUITree = origObserveGet
|
|
117
|
+
;(ai as any).swipe = origAiSwipe
|
|
118
|
+
|
|
119
|
+
// Restore
|
|
120
|
+
(ToolsObserve as any).getUITreeHandler = origGet
|
|
121
|
+
;(ToolsInteract as any).swipeHandler = origSwipe
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Ensure console.log is a function (some test runners replace it)
|
|
125
|
+
if (typeof console.log !== 'function') {
|
|
126
|
+
console.log = (...args: any[]) => { try { process.stdout.write(args.map(a => (typeof a === 'string' ? a : JSON.stringify(a))).join(' ') + '\n') } catch { /* swallow */ } }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
runTests().catch(console.error)
|
|
@@ -95,7 +95,7 @@ async function runTests() {
|
|
|
95
95
|
const elapsed4 = Date.now() - start4;
|
|
96
96
|
console.log("Result:", result4.found === false && result4.error === "ADB Connection Failed" ? "PASS" : "FAIL");
|
|
97
97
|
console.log("Error Message:", result4.error);
|
|
98
|
-
console.log("Elapsed time (should be <
|
|
98
|
+
console.log("Elapsed time (should be < 1000ms):", elapsed4, elapsed4 < 1000 ? "PASS" : "FAIL");
|
|
99
99
|
|
|
100
100
|
// Restore
|
|
101
101
|
(AndroidObserve as any).prototype.getUITree = originalGetUITree;
|