mobile-debug-mcp 0.26.5 → 0.28.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.
@@ -0,0 +1,94 @@
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 verification stabilization unit tests...')
7
+
8
+ const originalGetUITreeHandler = (Observe as any).ToolsObserve.getUITreeHandler
9
+
10
+ try {
11
+ let visibilityCalls = 0
12
+ ;(Observe as any).ToolsObserve.getUITreeHandler = async () => {
13
+ visibilityCalls++
14
+ const visible = visibilityCalls >= 3
15
+ return {
16
+ device: { platform: 'android', id: 'mock-device', osVersion: '14', model: 'Pixel', simulator: true },
17
+ captured_at_ms: Date.now(),
18
+ resolution: { width: 1080, height: 2400 },
19
+ elements: [
20
+ {
21
+ text: visible ? 'Ready' : 'Loading',
22
+ contentDescription: null,
23
+ type: 'android.widget.TextView',
24
+ resourceId: 'ready_label',
25
+ clickable: false,
26
+ enabled: true,
27
+ visible,
28
+ bounds: [0, 0, 120, 40]
29
+ }
30
+ ]
31
+ }
32
+ }
33
+
34
+ const visibleResult = await ToolsInteract.waitForUIHandler({
35
+ selector: { text: 'Ready' },
36
+ condition: 'visible',
37
+ timeout_ms: 600,
38
+ poll_interval_ms: 50,
39
+ platform: 'android'
40
+ })
41
+
42
+ assert.strictEqual(visibleResult.status, 'success')
43
+ assert.ok(visibilityCalls >= 4, 'visibility should be confirmed across consecutive reads')
44
+
45
+ let stateCalls = 0
46
+ ;(Observe as any).ToolsObserve.getUITreeHandler = async () => {
47
+ stateCalls++
48
+ const value = stateCalls >= 3 ? 30 : 10
49
+ const stale = stateCalls === 2
50
+ return {
51
+ device: { platform: 'android', id: 'mock-device', osVersion: '14', model: 'Pixel', simulator: true },
52
+ captured_at_ms: stale ? Date.now() - 1000 : Date.now(),
53
+ resolution: { width: 1080, height: 2400 },
54
+ elements: [
55
+ {
56
+ text: 'Volume',
57
+ contentDescription: null,
58
+ type: 'android.widget.SeekBar',
59
+ resourceId: 'volume_slider',
60
+ clickable: true,
61
+ enabled: true,
62
+ visible: true,
63
+ bounds: [0, 0, 240, 40],
64
+ state: {
65
+ value,
66
+ raw_value: value,
67
+ value_range: { min: 0, max: 100 }
68
+ }
69
+ }
70
+ ]
71
+ }
72
+ }
73
+
74
+ const stateResult = await ToolsInteract.expectStateHandler({
75
+ selector: { text: 'Volume' },
76
+ property: 'value',
77
+ expected: 30,
78
+ platform: 'android'
79
+ })
80
+
81
+ assert.strictEqual(stateResult.success, true)
82
+ assert.strictEqual(stateResult.reason, 'value matches expected value')
83
+ assert.ok(stateCalls >= 4, 'state verification should require stable fresh reads')
84
+
85
+ console.log('verification stabilization unit tests passed')
86
+ } finally {
87
+ ;(Observe as any).ToolsObserve.getUITreeHandler = originalGetUITreeHandler
88
+ }
89
+ }
90
+
91
+ run().catch((error) => {
92
+ console.error(error)
93
+ process.exit(1)
94
+ })
@@ -1,5 +1,6 @@
1
1
  import assert from 'assert'
2
- import { requireBooleanArg } from '../../../src/server/common.js'
2
+ import { createTraceStep } from '../../../src/server/common.js'
3
+ import { buildActionExecutionResult, inferGenericFailure, requireBooleanArg } from '../../../src/server/common.js'
3
4
 
4
5
  function run() {
5
6
  assert.strictEqual(requireBooleanArg({ exact: true }, 'exact'), true)
@@ -7,6 +8,64 @@ function run() {
7
8
  assert.throws(() => requireBooleanArg({}, 'exact'), /Missing or invalid boolean argument: exact/)
8
9
  assert.throws(() => requireBooleanArg({ exact: 'true' as unknown as boolean }, 'exact'), /Missing or invalid boolean argument: exact/)
9
10
 
11
+ assert.deepStrictEqual(inferGenericFailure('semantic mismatch between inferred and raw state'), {
12
+ failureCode: 'SEMANTIC_MISMATCH',
13
+ retryable: false
14
+ })
15
+
16
+ const recoveryResult = buildActionExecutionResult({
17
+ actionType: 'tap',
18
+ sourceModule: 'server',
19
+ selector: { x: 10, y: 20 },
20
+ success: false,
21
+ uiFingerprintBefore: 'fp_before',
22
+ uiFingerprintAfter: 'fp_after',
23
+ failure: { failureCode: 'SEMANTIC_MISMATCH', retryable: false }
24
+ })
25
+ assert.strictEqual(recoveryResult.failure_code, 'SEMANTIC_MISMATCH')
26
+ assert.strictEqual(recoveryResult.recovery?.failure_class, 'SemanticMismatchFailure')
27
+ assert.strictEqual(recoveryResult.recovery?.runtime_code, 'SEMANTIC_MISMATCH')
28
+ assert.strictEqual(recoveryResult.recovery?.retry_allowed, false)
29
+ assert.strictEqual(recoveryResult.recovery?.max_recovery_attempts, 3)
30
+ assert.strictEqual(recoveryResult.recovery?.max_retry_depth, 3)
31
+ assert.strictEqual(recoveryResult.trace.final_outcome, 'failure')
32
+ assert.strictEqual(recoveryResult.trace.steps.at(-1)?.stage, 'recover')
33
+
34
+ const notInteractableResult = buildActionExecutionResult({
35
+ actionType: 'tap',
36
+ sourceModule: 'server',
37
+ selector: { x: 5, y: 5 },
38
+ success: false,
39
+ uiFingerprintBefore: 'fp_before',
40
+ uiFingerprintAfter: 'fp_after',
41
+ failure: { failureCode: 'ELEMENT_NOT_INTERACTABLE', retryable: true }
42
+ })
43
+ assert.strictEqual(notInteractableResult.failure_code, 'ELEMENT_NOT_INTERACTABLE')
44
+ assert.strictEqual(notInteractableResult.recovery?.failure_class, 'ExecutionFailure')
45
+ assert.strictEqual(notInteractableResult.recovery?.runtime_code, 'ELEMENT_NOT_INTERACTABLE')
46
+ assert.strictEqual(notInteractableResult.recovery?.retry_allowed, true)
47
+ assert.strictEqual(notInteractableResult.trace.steps[0].stage, 'resolve')
48
+ assert.strictEqual(notInteractableResult.trace.steps[0].result, 'failure')
49
+ assert.strictEqual(notInteractableResult.trace.steps.at(-1)?.stage, 'recover')
50
+
51
+ const traceSteps = [
52
+ createTraceStep({ stage: 'resolve', timestamp: 100, result: 'success', attemptIndex: 0 }),
53
+ createTraceStep({ stage: 'execute', timestamp: 200, result: 'retry', attemptIndex: 1 }),
54
+ createTraceStep({ stage: 'verify', timestamp: 300, result: 'success', attemptIndex: 2 })
55
+ ]
56
+ const tracedResult = buildActionExecutionResult({
57
+ actionType: 'tap',
58
+ sourceModule: 'server',
59
+ selector: { x: 1, y: 1 },
60
+ success: true,
61
+ uiFingerprintBefore: 'before',
62
+ uiFingerprintAfter: 'after',
63
+ details: { attempts: 3 },
64
+ traceSteps
65
+ })
66
+ assert.deepStrictEqual(tracedResult.trace.steps, traceSteps)
67
+ assert.strictEqual(tracedResult.trace.attempts, 3)
68
+
10
69
  console.log('server common tests passed')
11
70
  }
12
71