mobile-debug-mcp 0.23.0 → 0.24.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/dist/interact/index.js +133 -57
- package/dist/server/common.js +66 -0
- package/dist/server/tool-definitions.js +921 -0
- package/dist/server/tool-handlers.js +320 -0
- package/dist/server-core.js +4 -801
- package/docs/CHANGELOG.md +3 -0
- package/docs/tools/TOOLS.md +15 -7
- package/docs/tools/interact.md +270 -107
- package/docs/tools/manage.md +39 -38
- package/docs/tools/observe.md +30 -8
- package/docs/tools/system.md +1 -1
- package/package.json +1 -1
- package/src/interact/index.ts +186 -58
- package/src/server/common.ts +95 -0
- package/src/server/tool-definitions.ts +921 -0
- package/src/server/tool-handlers.ts +365 -0
- package/src/server-core.ts +4 -844
- package/src/types.ts +59 -6
- package/test/unit/interact/expect_tools.test.ts +77 -0
- package/test/unit/interact/tap_element.test.ts +23 -6
- package/test/unit/server/contract.test.ts +26 -0
- package/test/unit/server/response_shapes.test.ts +69 -4
package/src/types.ts
CHANGED
|
@@ -132,14 +132,67 @@ export interface TapResponse {
|
|
|
132
132
|
error?: string;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
export
|
|
135
|
+
export type ActionFailureCode =
|
|
136
|
+
| 'ELEMENT_NOT_FOUND'
|
|
137
|
+
| 'ELEMENT_NOT_INTERACTABLE'
|
|
138
|
+
| 'TIMEOUT'
|
|
139
|
+
| 'NAVIGATION_NO_CHANGE'
|
|
140
|
+
| 'AMBIGUOUS_TARGET'
|
|
141
|
+
| 'STALE_REFERENCE'
|
|
142
|
+
| 'UNKNOWN'
|
|
143
|
+
|
|
144
|
+
export interface ActionTargetResolved {
|
|
145
|
+
elementId: string | null;
|
|
146
|
+
text: string | null;
|
|
147
|
+
resource_id: string | null;
|
|
148
|
+
accessibility_id: string | null;
|
|
149
|
+
class: string | null;
|
|
150
|
+
bounds: [number, number, number, number] | null;
|
|
151
|
+
index: number | null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface ActionExecutionResult {
|
|
155
|
+
action_id: string;
|
|
156
|
+
timestamp: number;
|
|
157
|
+
action_type: string;
|
|
158
|
+
target: {
|
|
159
|
+
selector: Record<string, unknown> | null;
|
|
160
|
+
resolved: ActionTargetResolved | null;
|
|
161
|
+
};
|
|
162
|
+
success: boolean;
|
|
163
|
+
failure_code?: ActionFailureCode;
|
|
164
|
+
retryable?: boolean;
|
|
165
|
+
ui_fingerprint_before?: string | null;
|
|
166
|
+
ui_fingerprint_after?: string | null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export interface TapElementResponse extends ActionExecutionResult {}
|
|
170
|
+
|
|
171
|
+
export interface ExpectScreenResponse {
|
|
172
|
+
success: boolean;
|
|
173
|
+
observed_screen: {
|
|
174
|
+
fingerprint: string | null;
|
|
175
|
+
screen: string | null;
|
|
176
|
+
};
|
|
177
|
+
expected_screen: {
|
|
178
|
+
fingerprint: string | null;
|
|
179
|
+
screen: string | null;
|
|
180
|
+
};
|
|
181
|
+
confidence: number;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export interface ExpectElementVisibleResponse {
|
|
136
185
|
success: boolean;
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
186
|
+
selector: {
|
|
187
|
+
text?: string;
|
|
188
|
+
resource_id?: string;
|
|
189
|
+
accessibility_id?: string;
|
|
190
|
+
contains?: boolean;
|
|
142
191
|
};
|
|
192
|
+
element_id: string | null;
|
|
193
|
+
element?: ActionTargetResolved | null;
|
|
194
|
+
failure_code?: 'TIMEOUT' | 'ELEMENT_NOT_FOUND' | 'UNKNOWN';
|
|
195
|
+
retryable?: boolean;
|
|
143
196
|
}
|
|
144
197
|
|
|
145
198
|
export interface SwipeResponse {
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import assert from 'assert'
|
|
2
|
+
import { ToolsInteract } from '../../../src/interact/index.js'
|
|
3
|
+
import * as Observe from '../../../src/observe/index.js'
|
|
4
|
+
|
|
5
|
+
async function run() {
|
|
6
|
+
console.log('Starting expect_* unit tests...')
|
|
7
|
+
const originalGetScreenFingerprintHandler = (Observe as any).ToolsObserve.getScreenFingerprintHandler
|
|
8
|
+
const originalGetCurrentScreenHandler = (Observe as any).ToolsObserve.getCurrentScreenHandler
|
|
9
|
+
const originalWaitForUIHandler = (ToolsInteract as any).waitForUIHandler
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
;(Observe as any).ToolsObserve.getScreenFingerprintHandler = async () => ({ fingerprint: 'fp_home', activity: 'com.example.HomeActivity' })
|
|
13
|
+
let expectScreen = await ToolsInteract.expectScreenHandler({ platform: 'android', fingerprint: 'fp_home' })
|
|
14
|
+
assert.deepStrictEqual(expectScreen, {
|
|
15
|
+
success: true,
|
|
16
|
+
observed_screen: { fingerprint: 'fp_home', screen: 'com.example.HomeActivity' },
|
|
17
|
+
expected_screen: { fingerprint: 'fp_home', screen: null },
|
|
18
|
+
confidence: 1
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
;(Observe as any).ToolsObserve.getCurrentScreenHandler = async () => ({
|
|
22
|
+
activity: 'com.example.HomeActivity',
|
|
23
|
+
shortActivity: 'HomeActivity'
|
|
24
|
+
})
|
|
25
|
+
expectScreen = await ToolsInteract.expectScreenHandler({ platform: 'android', screen: 'HomeActivity' })
|
|
26
|
+
assert.strictEqual(expectScreen.success, true)
|
|
27
|
+
assert.strictEqual(expectScreen.observed_screen.screen, 'HomeActivity')
|
|
28
|
+
assert.strictEqual(expectScreen.confidence, 1)
|
|
29
|
+
|
|
30
|
+
;(ToolsInteract as any).waitForUIHandler = async () => ({
|
|
31
|
+
status: 'success',
|
|
32
|
+
element: {
|
|
33
|
+
text: 'Ready',
|
|
34
|
+
resource_id: 'rid_ready',
|
|
35
|
+
accessibility_id: null,
|
|
36
|
+
class: 'TextView',
|
|
37
|
+
bounds: [0, 0, 10, 10],
|
|
38
|
+
index: 0,
|
|
39
|
+
elementId: 'el_ready'
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
const expectElementVisible = await ToolsInteract.expectElementVisibleHandler({
|
|
43
|
+
selector: { text: 'Ready' },
|
|
44
|
+
platform: 'android'
|
|
45
|
+
})
|
|
46
|
+
assert.strictEqual(expectElementVisible.success, true)
|
|
47
|
+
assert.strictEqual(expectElementVisible.element_id, 'el_ready')
|
|
48
|
+
assert.strictEqual(expectElementVisible.element?.resource_id, 'rid_ready')
|
|
49
|
+
|
|
50
|
+
;(ToolsInteract as any).waitForUIHandler = async () => ({
|
|
51
|
+
status: 'timeout',
|
|
52
|
+
error: { code: 'ELEMENT_NOT_FOUND', message: 'Condition visible not satisfied within timeout' }
|
|
53
|
+
})
|
|
54
|
+
const timeoutResult = await ToolsInteract.expectElementVisibleHandler({
|
|
55
|
+
selector: { text: 'Missing' },
|
|
56
|
+
platform: 'android'
|
|
57
|
+
})
|
|
58
|
+
assert.deepStrictEqual(timeoutResult, {
|
|
59
|
+
success: false,
|
|
60
|
+
selector: { text: 'Missing' },
|
|
61
|
+
element_id: null,
|
|
62
|
+
failure_code: 'TIMEOUT',
|
|
63
|
+
retryable: true
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
console.log('expect_* unit tests passed')
|
|
67
|
+
} finally {
|
|
68
|
+
;(Observe as any).ToolsObserve.getScreenFingerprintHandler = originalGetScreenFingerprintHandler
|
|
69
|
+
;(Observe as any).ToolsObserve.getCurrentScreenHandler = originalGetCurrentScreenHandler
|
|
70
|
+
;(ToolsInteract as any).waitForUIHandler = originalWaitForUIHandler
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
run().catch((error) => {
|
|
75
|
+
console.error(error)
|
|
76
|
+
process.exit(1)
|
|
77
|
+
})
|
|
@@ -5,11 +5,18 @@ import * as Observe from '../../../src/observe/index.js'
|
|
|
5
5
|
async function run() {
|
|
6
6
|
console.log('Starting tap_element unit tests...')
|
|
7
7
|
const originalGetUITreeHandler = (Observe as any).ToolsObserve.getUITreeHandler
|
|
8
|
+
const originalGetScreenFingerprintHandler = (Observe as any).ToolsObserve.getScreenFingerprintHandler
|
|
8
9
|
const originalTapHandler = (ToolsInteract as any).tapHandler
|
|
9
10
|
const originalComputeElementId = (ToolsInteract as any)._computeElementId
|
|
10
11
|
;(ToolsInteract as any)._resetResolvedUiElementsForTests()
|
|
11
12
|
|
|
12
13
|
try {
|
|
14
|
+
let fingerprintCalls = 0
|
|
15
|
+
;(Observe as any).ToolsObserve.getScreenFingerprintHandler = async () => {
|
|
16
|
+
fingerprintCalls++
|
|
17
|
+
return { fingerprint: 'fp_mock' }
|
|
18
|
+
}
|
|
19
|
+
|
|
13
20
|
;(Observe as any).ToolsObserve.getUITreeHandler = async () => ({
|
|
14
21
|
device: { platform: 'android', id: 'mock-device' },
|
|
15
22
|
elements: [
|
|
@@ -34,7 +41,12 @@ async function run() {
|
|
|
34
41
|
}
|
|
35
42
|
|
|
36
43
|
const tapSuccess = await ToolsInteract.tapElementHandler({ elementId: successElementId })
|
|
37
|
-
assert.
|
|
44
|
+
assert.strictEqual(tapSuccess.success, true)
|
|
45
|
+
assert.strictEqual(tapSuccess.action_type, 'tap_element')
|
|
46
|
+
assert.strictEqual(tapSuccess.target.selector?.elementId, successElementId)
|
|
47
|
+
assert.strictEqual(tapSuccess.target.resolved?.elementId, successElementId)
|
|
48
|
+
assert.strictEqual(tapSuccess.ui_fingerprint_before, 'fp_mock')
|
|
49
|
+
assert.strictEqual(tapSuccess.ui_fingerprint_after, 'fp_mock')
|
|
38
50
|
assert.deepStrictEqual(tapped, { platform: 'android', x: 10, y: 10, deviceId: 'mock-device' })
|
|
39
51
|
|
|
40
52
|
;(Observe as any).ToolsObserve.getUITreeHandler = async () => ({
|
|
@@ -52,7 +64,8 @@ async function run() {
|
|
|
52
64
|
})
|
|
53
65
|
const hiddenResult = await ToolsInteract.tapElementHandler({ elementId: waitHidden.element.elementId })
|
|
54
66
|
assert.strictEqual(hiddenResult.success, false)
|
|
55
|
-
assert.strictEqual(hiddenResult.
|
|
67
|
+
assert.strictEqual(hiddenResult.failure_code, 'ELEMENT_NOT_INTERACTABLE')
|
|
68
|
+
assert.strictEqual(hiddenResult.retryable, true)
|
|
56
69
|
|
|
57
70
|
;(Observe as any).ToolsObserve.getUITreeHandler = async () => ({
|
|
58
71
|
device: { platform: 'android', id: 'mock-device' },
|
|
@@ -69,7 +82,7 @@ async function run() {
|
|
|
69
82
|
})
|
|
70
83
|
const disabledResult = await ToolsInteract.tapElementHandler({ elementId: waitDisabled.element.elementId })
|
|
71
84
|
assert.strictEqual(disabledResult.success, false)
|
|
72
|
-
assert.strictEqual(disabledResult.
|
|
85
|
+
assert.strictEqual(disabledResult.failure_code, 'ELEMENT_NOT_INTERACTABLE')
|
|
73
86
|
|
|
74
87
|
;(Observe as any).ToolsObserve.getUITreeHandler = async () => ({
|
|
75
88
|
device: { platform: 'android', id: 'mock-device' },
|
|
@@ -77,7 +90,7 @@ async function run() {
|
|
|
77
90
|
})
|
|
78
91
|
const notFoundResult = await ToolsInteract.tapElementHandler({ elementId: successElementId })
|
|
79
92
|
assert.strictEqual(notFoundResult.success, false)
|
|
80
|
-
assert.strictEqual(notFoundResult.
|
|
93
|
+
assert.strictEqual(notFoundResult.failure_code, 'STALE_REFERENCE')
|
|
81
94
|
|
|
82
95
|
;(ToolsInteract as any)._resetResolvedUiElementsForTests()
|
|
83
96
|
const targetIndex = 25
|
|
@@ -124,7 +137,7 @@ async function run() {
|
|
|
124
137
|
})
|
|
125
138
|
const shiftedIndexResult = await ToolsInteract.tapElementHandler({ elementId: indexedWait.element.elementId })
|
|
126
139
|
assert.strictEqual(shiftedIndexResult.success, false)
|
|
127
|
-
assert.strictEqual(shiftedIndexResult.
|
|
140
|
+
assert.strictEqual(shiftedIndexResult.failure_code, 'STALE_REFERENCE')
|
|
128
141
|
|
|
129
142
|
;(ToolsInteract as any)._resetResolvedUiElementsForTests()
|
|
130
143
|
const cacheLimit = (ToolsInteract as any)._maxResolvedUiElements as number
|
|
@@ -151,15 +164,19 @@ async function run() {
|
|
|
151
164
|
}
|
|
152
165
|
|
|
153
166
|
assert.ok(oldestElementId, 'Oldest element ID should be captured')
|
|
167
|
+
const fingerprintCallsBeforeEvictedTap = fingerprintCalls
|
|
154
168
|
const evictedResult = await ToolsInteract.tapElementHandler({ elementId: oldestElementId as string })
|
|
155
169
|
assert.strictEqual(evictedResult.success, false)
|
|
156
|
-
assert.strictEqual(evictedResult.
|
|
170
|
+
assert.strictEqual(evictedResult.failure_code, 'STALE_REFERENCE')
|
|
171
|
+
assert.strictEqual(evictedResult.ui_fingerprint_before, null)
|
|
172
|
+
assert.strictEqual(fingerprintCalls, fingerprintCallsBeforeEvictedTap)
|
|
157
173
|
|
|
158
174
|
console.log('tap_element unit tests passed')
|
|
159
175
|
} finally {
|
|
160
176
|
;(ToolsInteract as any)._resetResolvedUiElementsForTests()
|
|
161
177
|
;(ToolsInteract as any)._computeElementId = originalComputeElementId
|
|
162
178
|
;(Observe as any).ToolsObserve.getUITreeHandler = originalGetUITreeHandler
|
|
179
|
+
;(Observe as any).ToolsObserve.getScreenFingerprintHandler = originalGetScreenFingerprintHandler
|
|
163
180
|
;(ToolsInteract as any).tapHandler = originalTapHandler
|
|
164
181
|
}
|
|
165
182
|
}
|
|
@@ -8,6 +8,8 @@ async function run() {
|
|
|
8
8
|
assert.strictEqual(serverInfo.name, 'mobile-debug-mcp')
|
|
9
9
|
assert.strictEqual(names.length, uniqueNames.size, 'tool names should be unique')
|
|
10
10
|
assert(names.includes('wait_for_ui'))
|
|
11
|
+
assert(names.includes('expect_screen'))
|
|
12
|
+
assert(names.includes('expect_element_visible'))
|
|
11
13
|
assert(names.includes('capture_screenshot'))
|
|
12
14
|
assert(names.includes('get_ui_tree'))
|
|
13
15
|
assert(names.includes('tap_element'))
|
|
@@ -16,6 +18,14 @@ async function run() {
|
|
|
16
18
|
assert(waitForUI, 'wait_for_ui should be registered')
|
|
17
19
|
assert.strictEqual((waitForUI as any).inputSchema.properties.timeout_ms.default, 60000)
|
|
18
20
|
assert.strictEqual((waitForUI as any).inputSchema.properties.condition.default, 'exists')
|
|
21
|
+
assert.match((waitForUI as any).description, /resolve elements/i)
|
|
22
|
+
assert.match((waitForUI as any).description, /must not be used alone to confirm action success/i)
|
|
23
|
+
assert.match((waitForUI as any).description, /follow with expect_\*/i)
|
|
24
|
+
|
|
25
|
+
const waitForScreenChange = toolDefinitions.find((tool) => tool.name === 'wait_for_screen_change')
|
|
26
|
+
assert(waitForScreenChange, 'wait_for_screen_change should be registered')
|
|
27
|
+
assert.match((waitForScreenChange as any).description, /does not verify correctness of the resulting state/i)
|
|
28
|
+
assert.match((waitForScreenChange as any).description, /follow with expect_screen/i)
|
|
19
29
|
|
|
20
30
|
const captureDebugSnapshot = toolDefinitions.find((tool) => tool.name === 'capture_debug_snapshot')
|
|
21
31
|
assert(captureDebugSnapshot, 'capture_debug_snapshot should be registered')
|
|
@@ -33,6 +43,22 @@ async function run() {
|
|
|
33
43
|
const tapElement = toolDefinitions.find((tool) => tool.name === 'tap_element')
|
|
34
44
|
assert(tapElement, 'tap_element should be registered')
|
|
35
45
|
assert.deepStrictEqual((tapElement as any).inputSchema.required, ['elementId'])
|
|
46
|
+
assert.match((tapElement as any).description, /RESOLVE → ACT → WAIT \(if needed\) → EXPECT/)
|
|
47
|
+
assert.match((tapElement as any).description, /If needed, wait for transition using wait_for_\*/)
|
|
48
|
+
assert.match((tapElement as any).description, /Verify outcome using expect_\*/)
|
|
49
|
+
|
|
50
|
+
const expectScreen = toolDefinitions.find((tool) => tool.name === 'expect_screen')
|
|
51
|
+
assert(expectScreen, 'expect_screen should be registered')
|
|
52
|
+
assert.match((expectScreen as any).description, /Primary and authoritative verification tool/i)
|
|
53
|
+
assert.match((expectScreen as any).description, /final verification step/i)
|
|
54
|
+
assert.match((expectScreen as any).description, /Returns structured binary success\/failure only/i)
|
|
55
|
+
|
|
56
|
+
const expectElementVisible = toolDefinitions.find((tool) => tool.name === 'expect_element_visible')
|
|
57
|
+
assert(expectElementVisible, 'expect_element_visible should be registered')
|
|
58
|
+
assert.deepStrictEqual((expectElementVisible as any).inputSchema.required, ['selector'])
|
|
59
|
+
assert.match((expectElementVisible as any).description, /Primary and authoritative verification tool/i)
|
|
60
|
+
assert.match((expectElementVisible as any).description, /selector is the primary input/i)
|
|
61
|
+
assert.match((expectElementVisible as any).description, /Returns structured binary success\/failure only/i)
|
|
36
62
|
|
|
37
63
|
await assert.rejects(() => handleToolCall('unknown_tool'), /Unknown tool: unknown_tool/)
|
|
38
64
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import assert from 'assert'
|
|
2
2
|
import { handleToolCall } from '../../../src/server-core.js'
|
|
3
3
|
import { ToolsManage } from '../../../src/manage/index.js'
|
|
4
|
+
import { AndroidManage } from '../../../src/manage/index.js'
|
|
4
5
|
import { ToolsInteract } from '../../../src/interact/index.js'
|
|
5
6
|
import { ToolsObserve } from '../../../src/observe/index.js'
|
|
6
7
|
|
|
@@ -8,8 +9,13 @@ async function run() {
|
|
|
8
9
|
const originalInstallAppHandler = (ToolsManage as any).installAppHandler
|
|
9
10
|
const originalWaitForUIHandler = (ToolsInteract as any).waitForUIHandler
|
|
10
11
|
const originalTapElementHandler = (ToolsInteract as any).tapElementHandler
|
|
12
|
+
const originalTapHandler = (ToolsInteract as any).tapHandler
|
|
13
|
+
const originalExpectScreenHandler = (ToolsInteract as any).expectScreenHandler
|
|
14
|
+
const originalExpectElementVisibleHandler = (ToolsInteract as any).expectElementVisibleHandler
|
|
15
|
+
const originalStartApp = AndroidManage.prototype.startApp
|
|
11
16
|
const originalCaptureScreenshotHandler = (ToolsObserve as any).captureScreenshotHandler
|
|
12
17
|
const originalGetUITreeHandler = (ToolsObserve as any).getUITreeHandler
|
|
18
|
+
const originalGetScreenFingerprintHandler = (ToolsObserve as any).getScreenFingerprintHandler
|
|
13
19
|
|
|
14
20
|
try {
|
|
15
21
|
;(ToolsManage as any).installAppHandler = async () => ({
|
|
@@ -40,16 +46,70 @@ async function run() {
|
|
|
40
46
|
assert.strictEqual(waitForUIPayload.element.elementId, 'el_ready')
|
|
41
47
|
|
|
42
48
|
;(ToolsInteract as any).tapElementHandler = async () => ({
|
|
49
|
+
action_id: 'tap_element_1',
|
|
50
|
+
timestamp: 1234567890,
|
|
51
|
+
action_type: 'tap_element',
|
|
52
|
+
target: {
|
|
53
|
+
selector: { elementId: 'el_ready' },
|
|
54
|
+
resolved: { elementId: 'el_ready', text: 'Ready', resource_id: null, accessibility_id: null, class: 'Button', bounds: [0, 0, 10, 10], index: 0 }
|
|
55
|
+
},
|
|
43
56
|
success: true,
|
|
44
|
-
|
|
45
|
-
|
|
57
|
+
ui_fingerprint_before: 'fp_before',
|
|
58
|
+
ui_fingerprint_after: 'fp_after'
|
|
46
59
|
})
|
|
47
60
|
|
|
48
61
|
const tapElementResponse = await handleToolCall('tap_element', { elementId: 'el_ready' })
|
|
49
62
|
const tapElementPayload = JSON.parse((tapElementResponse as any).content[0].text)
|
|
50
63
|
assert.strictEqual(tapElementPayload.success, true)
|
|
51
|
-
assert.strictEqual(tapElementPayload.
|
|
52
|
-
assert.strictEqual(tapElementPayload.
|
|
64
|
+
assert.strictEqual(tapElementPayload.action_type, 'tap_element')
|
|
65
|
+
assert.strictEqual(tapElementPayload.target.resolved.elementId, 'el_ready')
|
|
66
|
+
assert.strictEqual(tapElementPayload.ui_fingerprint_before, 'fp_before')
|
|
67
|
+
|
|
68
|
+
;(ToolsObserve as any).getScreenFingerprintHandler = async () => ({ fingerprint: 'fp_mock', activity: 'MainActivity' })
|
|
69
|
+
;(ToolsInteract as any).tapHandler = async () => ({ success: true, x: 1, y: 2 })
|
|
70
|
+
const tapResponse = await handleToolCall('tap', { platform: 'android', x: 1, y: 2 })
|
|
71
|
+
const tapPayload = JSON.parse((tapResponse as any).content[0].text)
|
|
72
|
+
assert.strictEqual(tapPayload.success, true)
|
|
73
|
+
assert.strictEqual(tapPayload.action_type, 'tap')
|
|
74
|
+
assert.deepStrictEqual(tapPayload.target.selector, { x: 1, y: 2 })
|
|
75
|
+
assert.strictEqual(tapPayload.ui_fingerprint_before, 'fp_mock')
|
|
76
|
+
|
|
77
|
+
AndroidManage.prototype.startApp = async function () {
|
|
78
|
+
return {
|
|
79
|
+
device: { platform: 'android', id: 'emulator-5554', osVersion: '14', model: 'Pixel', simulator: true },
|
|
80
|
+
appStarted: true,
|
|
81
|
+
launchTimeMs: 123
|
|
82
|
+
} as any
|
|
83
|
+
}
|
|
84
|
+
const startAppResponse = await handleToolCall('start_app', { platform: 'android', appId: 'com.example.app' })
|
|
85
|
+
const startAppPayload = JSON.parse((startAppResponse as any).content[0].text)
|
|
86
|
+
assert.strictEqual(startAppPayload.success, true)
|
|
87
|
+
assert.strictEqual(startAppPayload.action_type, 'start_app')
|
|
88
|
+
assert.deepStrictEqual(startAppPayload.target.selector, { appId: 'com.example.app' })
|
|
89
|
+
|
|
90
|
+
;(ToolsInteract as any).expectScreenHandler = async () => ({
|
|
91
|
+
success: true,
|
|
92
|
+
observed_screen: { fingerprint: 'fp_after', screen: 'MainActivity' },
|
|
93
|
+
expected_screen: { fingerprint: 'fp_after', screen: null },
|
|
94
|
+
confidence: 1
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
const expectScreenResponse = await handleToolCall('expect_screen', { fingerprint: 'fp_after' })
|
|
98
|
+
const expectScreenPayload = JSON.parse((expectScreenResponse as any).content[0].text)
|
|
99
|
+
assert.strictEqual(expectScreenPayload.success, true)
|
|
100
|
+
assert.strictEqual(expectScreenPayload.confidence, 1)
|
|
101
|
+
|
|
102
|
+
;(ToolsInteract as any).expectElementVisibleHandler = async () => ({
|
|
103
|
+
success: true,
|
|
104
|
+
selector: { text: 'Ready' },
|
|
105
|
+
element_id: 'el_ready',
|
|
106
|
+
element: { elementId: 'el_ready', text: 'Ready', resource_id: null, accessibility_id: null, class: 'TextView', bounds: [0, 0, 10, 10], index: 0 }
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
const expectElementResponse = await handleToolCall('expect_element_visible', { selector: { text: 'Ready' } })
|
|
110
|
+
const expectElementPayload = JSON.parse((expectElementResponse as any).content[0].text)
|
|
111
|
+
assert.strictEqual(expectElementPayload.success, true)
|
|
112
|
+
assert.strictEqual(expectElementPayload.element_id, 'el_ready')
|
|
53
113
|
|
|
54
114
|
;(ToolsObserve as any).captureScreenshotHandler = async () => ({
|
|
55
115
|
device: { platform: 'ios', id: 'booted', osVersion: '18.0', model: 'Simulator', simulator: true },
|
|
@@ -82,8 +142,13 @@ async function run() {
|
|
|
82
142
|
;(ToolsManage as any).installAppHandler = originalInstallAppHandler
|
|
83
143
|
;(ToolsInteract as any).waitForUIHandler = originalWaitForUIHandler
|
|
84
144
|
;(ToolsInteract as any).tapElementHandler = originalTapElementHandler
|
|
145
|
+
;(ToolsInteract as any).tapHandler = originalTapHandler
|
|
146
|
+
;(ToolsInteract as any).expectScreenHandler = originalExpectScreenHandler
|
|
147
|
+
;(ToolsInteract as any).expectElementVisibleHandler = originalExpectElementVisibleHandler
|
|
148
|
+
AndroidManage.prototype.startApp = originalStartApp
|
|
85
149
|
;(ToolsObserve as any).captureScreenshotHandler = originalCaptureScreenshotHandler
|
|
86
150
|
;(ToolsObserve as any).getUITreeHandler = originalGetUITreeHandler
|
|
151
|
+
;(ToolsObserve as any).getScreenFingerprintHandler = originalGetScreenFingerprintHandler
|
|
87
152
|
}
|
|
88
153
|
}
|
|
89
154
|
|