mobile-debug-mcp 0.31.0 → 0.32.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/README.md +1 -0
- package/dist/interact/android.js +13 -8
- package/dist/interact/index.js +33 -22
- package/dist/interact/ios.js +5 -2
- package/dist/manage/android.js +20 -10
- package/dist/manage/ios.js +19 -10
- package/dist/observe/android-tree-bridge.js +192 -0
- package/dist/observe/android.js +53 -14
- package/dist/observe/index.js +22 -5
- package/dist/observe/ios.js +50 -29
- package/dist/observe/performance-metrics.js +124 -0
- package/dist/observe/snapshot-metadata.js +94 -15
- package/dist/server/common.js +14 -7
- package/dist/server/journey-schema.js +60 -0
- package/dist/server/tool-definitions.js +59 -6
- package/dist/server/tool-handlers.js +381 -56
- package/dist/server-core.js +1 -1
- package/dist/utils/exec.js +18 -5
- package/dist/utils/ios/utils.js +5 -2
- package/dist/utils/operation-budget.js +25 -0
- package/dist/utils/resolve-device.js +12 -1
- package/dist/utils/timing.js +19 -0
- package/dist/utils/ui/index.js +2 -0
- package/docs/CHANGELOG.md +6 -0
- package/docs/ROADMAP.md +9 -0
- package/docs/benchmarks/interaction-latency/README.md +26 -0
- package/docs/benchmarks/interaction-latency/final.json +1810 -0
- package/docs/benchmarks/interaction-latency/final.md +17 -0
- package/docs/benchmarks/interaction-latency/ios-validation.md +11 -0
- package/docs/benchmarks/ui-tree-performance/README.md +51 -0
- package/docs/benchmarks/ui-tree-performance/baseline.json +59546 -0
- package/docs/benchmarks/ui-tree-performance/bridge-probe.json +28 -0
- package/docs/benchmarks/ui-tree-performance/candidate.json +51691 -0
- package/docs/benchmarks/ui-tree-performance/comparison-attempt-1.json +110679 -0
- package/docs/benchmarks/ui-tree-performance/comparison.json +111230 -0
- package/docs/benchmarks/ui-tree-performance/comparison.md +41 -0
- package/docs/benchmarks/ui-tree-performance/initial-baseline.json +59560 -0
- package/docs/specs/018-interaction-latency-and-batched-journeys/spec.md +301 -0
- package/docs/specs/019-ui-tree-observation-performance/spec.md +148 -0
- package/docs/tools/interact.md +30 -0
- package/docs/tools/observe.md +1 -0
- package/package.json +1 -1
- package/skills/mobile-debug-tools-usage/SKILL.md +74 -0
- package/src/interact/android.ts +13 -8
- package/src/interact/index.ts +32 -23
- package/src/interact/ios.ts +5 -2
- package/src/manage/android.ts +19 -10
- package/src/manage/ios.ts +18 -10
- package/src/observe/android-tree-bridge.ts +170 -0
- package/src/observe/android.ts +48 -17
- package/src/observe/index.ts +21 -5
- package/src/observe/ios.ts +49 -36
- package/src/observe/performance-metrics.ts +125 -0
- package/src/observe/snapshot-metadata.ts +94 -15
- package/src/server/common.ts +13 -6
- package/src/server/journey-schema.ts +63 -0
- package/src/server/tool-definitions.ts +60 -7
- package/src/server/tool-handlers.ts +349 -54
- package/src/server-core.ts +1 -1
- package/src/types.ts +31 -2
- package/src/utils/exec.ts +13 -5
- package/src/utils/ios/utils.ts +5 -2
- package/src/utils/operation-budget.ts +22 -0
- package/src/utils/resolve-device.ts +13 -1
- package/src/utils/timing.ts +15 -0
- package/src/utils/ui/index.ts +2 -0
- package/test/device/automated/latency/benchmark.ts +136 -0
- package/test/device/automated/latency/report.ts +6 -0
- package/test/device/automated/ui-tree-performance/benchmark.ts +401 -0
- package/test/device/automated/ui-tree-performance/bridge-probe.ts +183 -0
- package/test/fixtures/latency-journey-app/AndroidManifest.xml +7 -0
- package/test/fixtures/latency-journey-app/README.md +28 -0
- package/test/fixtures/latency-journey-app/build.ts +26 -0
- package/test/fixtures/latency-journey-app/src/MainActivity.java +104 -0
- package/test/fixtures/ui-tree-bridge/AndroidManifest.xml +6 -0
- package/test/fixtures/ui-tree-bridge/build.ts +34 -0
- package/test/fixtures/ui-tree-bridge/src/TreeInstrumentation.java +119 -0
- package/test/unit/manage/restart_budget.test.ts +37 -0
- package/test/unit/observe/android_tree_acquisition.test.ts +70 -0
- package/test/unit/observe/android_tree_bridge_cleanup.test.ts +76 -0
- package/test/unit/observe/capture_debug_snapshot.test.ts +6 -0
- package/test/unit/observe/ios_tree_acquisition.test.ts +77 -0
- package/test/unit/observe/performance_metrics.test.ts +40 -0
- package/test/unit/observe/snapshot_metadata.test.ts +20 -1
- package/test/unit/observe/state_delta.test.ts +15 -0
- package/test/unit/server/benchmark_report.test.ts +4 -0
- package/test/unit/server/contract.test.ts +1 -1
- package/test/unit/server/interaction_modes.test.ts +60 -0
- package/test/unit/server/operation_budget.test.ts +45 -0
- package/test/unit/server/response_shapes.test.ts +3 -3
- package/test/unit/server/run_journey.test.ts +120 -0
- package/test/unit/server/tap_selector.test.ts +68 -0
- /package/docs/{specs-deltas/001-state-verification.md → specs/001-state-verification/spec.md} +0 -0
- /package/docs/{specs-deltas/002-richer-element-identity → specs/002-richer-element-identity/spec.md} +0 -0
- /package/docs/{specs-deltas/003-wait-and-synchronization-reliability.md → specs/003-wait-and-synchronization-reliability/spec.md} +0 -0
- /package/docs/{specs-deltas/004-action-verification-routing.md → specs/004-action-verification-routing/spec.md} +0 -0
- /package/docs/{specs-deltas/005-unified-action-execution-and-verification-model.md → specs/005-unified-action-execution-and-verification-model/spec.md} +0 -0
- /package/docs/{specs-deltas/006-runtime-action-instrumentation-and-binding-layer.md → specs/006-runtime-action-instrumentation-and-binding-layer/spec.md} +0 -0
- /package/docs/{specs-deltas/007-actionability-resolution-and-executable-target-selection.md → specs/007-actionability-resolution-and-executable-target-selection/spec.md} +0 -0
- /package/docs/{specs-deltas/008-adjustable-control-support-and-semantic-value-manipulation.md → specs/008-adjustable-control-support-and-semantic-value-manipulation/spec.md} +0 -0
- /package/docs/{specs-deltas/009-semantic-control-modeling-for-custom-and-composite-controls.md → specs/009-semantic-control-modeling-for-custom-and-composite-controls/spec.md} +0 -0
- /package/docs/{specs-deltas/010-verification-stabilization-and-temporal-convergence.md → specs/010-verification-stabilization-and-temporal-convergence/spec.md} +0 -0
- /package/docs/{specs-deltas/011-recovery-and-replanning-for-failed-or-ambiguous-interaction-flows.md → specs/011-recovery-and-replanning-for-failed-or-ambiguous-interaction-flows/spec.md} +0 -0
- /package/docs/{specs-deltas/011.1-recovery-contract-types-and-runtime-wiring-spec.md → specs/011.1-recovery-contract-types-and-runtime-wiring-spec/spec.md} +0 -0
- /package/docs/{specs-deltas/012-action-trace-and-xecution-observability.md → specs/012-action-trace-and-xecution-observability/spec.md} +0 -0
- /package/docs/{specs-deltas/013-wait-and-synchronization-reliability.md → specs/013-wait-and-synchronization-reliability/spec.md} +0 -0
- /package/docs/{specs-deltas/014-actionability-resolution.md → specs/014-actionability-resolution/spec.md} +0 -0
- /package/docs/{specs-deltas/015-adjustable-control-support.md → specs/015-adjustable-control-support/spec.md} +0 -0
- /package/docs/{specs-deltas/016-environment-tools.md → specs/016-environment-tools/spec.md} +0 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# RFC 018 — Interaction Latency, Verification Modes, and Batched Journeys
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Implemented; Android and iOS device validation passed.
|
|
6
|
+
|
|
7
|
+
## 1. Summary
|
|
8
|
+
|
|
9
|
+
This RFC reduces mobile-debug MCP round-trip latency while retaining an explicit path to the current verification guarantees.
|
|
10
|
+
|
|
11
|
+
It adds optional execution controls to action tools, compact responses, selector-based tapping, state-delta observations, and a batched `run_journey` tool. Existing callers remain compatible: omitted controls retain the current full verification and detailed response behavior.
|
|
12
|
+
|
|
13
|
+
## 2. Problem
|
|
14
|
+
|
|
15
|
+
The current path performs more device work and returns more data than most interaction turns need.
|
|
16
|
+
|
|
17
|
+
- `start_app` captures a screen fingerprint before and after launch, in addition to the launch adapter's current-screen observation.
|
|
18
|
+
- Coordinate actions capture fingerprints before and after dispatch.
|
|
19
|
+
- `tap_element` captures a fingerprint, re-reads the full UI tree to revalidate a stored handle, dispatches, then captures another fingerprint.
|
|
20
|
+
- `capture_debug_snapshot` always starts a screenshot collection, even when the caller needs only hierarchy or logs.
|
|
21
|
+
- A normal agent journey requires separate resolve, tap, wait, expect, and snapshot calls, each with a protocol round trip and repeated device metadata.
|
|
22
|
+
|
|
23
|
+
These checks are valuable when diagnosing an ambiguous result. They are unnecessary for every known-safe, immediately-followed step, and they make the server appear slow even when device execution is prompt.
|
|
24
|
+
|
|
25
|
+
## 3. Goals
|
|
26
|
+
|
|
27
|
+
1. Make the latency of `start_app`, `tap_element`, `wait_for_ui`, `capture_screenshot`, and `capture_debug_snapshot` measurable and reportable.
|
|
28
|
+
2. Let callers choose `none`, `light`, or `full` verification per interaction without weakening the compatibility default.
|
|
29
|
+
3. Make normal responses small and use `debug` only when detailed traces and device metadata are needed.
|
|
30
|
+
4. Resolve and tap a selector in one server request.
|
|
31
|
+
5. Run ordered interaction journeys in one device session and stop at the first failed assertion.
|
|
32
|
+
6. Return changed accessibility nodes rather than repeated complete trees when a prior snapshot is available.
|
|
33
|
+
7. Use UI evidence as soon as it is available; do not add a fixed settling delay after a fresh expected state is observed.
|
|
34
|
+
|
|
35
|
+
## 4. Non-goals
|
|
36
|
+
|
|
37
|
+
- Changing the meaning of existing `expect_*` tools or their stabilization rules.
|
|
38
|
+
- Inferring app-specific expected outcomes when the caller has not supplied one.
|
|
39
|
+
- Removing `capture_screenshot` or the detailed debug snapshot.
|
|
40
|
+
- Bypassing target actionability checks for selector-based actions.
|
|
41
|
+
- Making `none` appropriate for actions with an asserted business outcome.
|
|
42
|
+
- Reporting benchmark figures without a controlled device run.
|
|
43
|
+
|
|
44
|
+
## 5. Compatibility and defaults
|
|
45
|
+
|
|
46
|
+
All new inputs are optional. For every existing action request:
|
|
47
|
+
|
|
48
|
+
```text
|
|
49
|
+
verificationMode = "full"
|
|
50
|
+
responseMode = "debug"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
is the effective behavior. Existing response fields, including action envelopes, fingerprints, device metadata, and traces, remain available in that mode. No existing tool is removed or renamed.
|
|
54
|
+
|
|
55
|
+
New clients SHOULD use `responseMode: "compact"` and select the least costly verification mode that satisfies their next decision. `full` remains required for a final assertion unless that assertion is separately expressed as an `expect_*` or journey assertion.
|
|
56
|
+
|
|
57
|
+
## 6. Common interaction controls
|
|
58
|
+
|
|
59
|
+
The following optional properties are added to `start_app`, `restart_app`, `tap`, `tap_element`, `swipe`, `scroll_to_element`, `type_text`, and `press_back`. A tool that cannot apply a field MUST reject it with `INVALID_ARGUMENT`, rather than silently changing its semantics.
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
verificationMode?: "none" | "light" | "full" // default: "full"
|
|
63
|
+
responseMode?: "compact" | "debug" // default: "debug"
|
|
64
|
+
actionTimeoutMs?: number // adapter dispatch limit
|
|
65
|
+
verificationTimeoutMs?: number // verification budget
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Values are finite integers. `actionTimeoutMs` is clamped to 100–30,000 ms, and `verificationTimeoutMs` to 100–60,000 ms. The server reports effective values in debug responses. Platform adapters MUST receive and enforce `actionTimeoutMs`; it must not be a cosmetic field.
|
|
69
|
+
|
|
70
|
+
Unless supplied, the effective dispatch budget is 5,000 ms and the effective light-verification budget is 3,000 ms. `start_app` has a 10,000 ms dispatch default because launch includes an adapter-owned foreground check. `restart_app` has a 15,000 ms total dispatch budget: at most 5,000 ms for termination and the remaining budget, capped at 10,000 ms, for launch. `scroll_to_element` has a 10,000 ms total dispatch budget; the budget covers every scroll and resolve attempt, so no attempt may start after its deadline. Existing `full` calls retain their present adapter and stabilization behavior when the fields are omitted; a supplied budget bounds only work introduced by this RFC.
|
|
71
|
+
|
|
72
|
+
The server creates one deadline at dispatch start and passes its remaining duration to each nested operation. It cancels local polling and child processes when the deadline expires. A timeout before the command is issued is `ACTION_TIMEOUT`, `success: false`, `delivery_status: "not_delivered"`, and `retryable: true`. A timeout after dispatch begins is `ACTION_TIMEOUT`, `success: false`, `delivery_status: "unknown"`, and `retryable: false`: the device may have acted, so the client MUST observe state before retrying. The response includes `dispatch_started: boolean` and elapsed timing in both response modes.
|
|
73
|
+
|
|
74
|
+
### 6.1 Verification modes
|
|
75
|
+
|
|
76
|
+
| Mode | Required work | Result meaning |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| `none` | Resolve only when required to dispatch; issue the command. No post-dispatch UI read. | `success` means the adapter accepted the action. |
|
|
79
|
+
| `light` | Dispatch, then take one fresh accessibility-tree observation. | `success` means delivery succeeded; `state_delta` is best-effort evidence, not a passed assertion. |
|
|
80
|
+
| `full` | Preserve the current tool's pre/post evidence, stale-reference checks, stabilization, and trace behavior. | Existing meaning is unchanged. |
|
|
81
|
+
|
|
82
|
+
`light` MUST not wait for an arbitrary settling interval. It MAY poll only until the first fresh observation arrives, bounded by `verificationTimeoutMs`. If the observation cannot be obtained in time, return `success: true`, `verification.status: "unavailable"`, and a retryable verification diagnostic; do not recast a delivered action as a dispatch failure. `verification.status: "unavailable"` is therefore a successful action with incomplete evidence, never a failed action.
|
|
83
|
+
|
|
84
|
+
`none` and `light` return `lifecycle_state: "pending_verification"` after delivery. `full` retains current lifecycle-state behavior.
|
|
85
|
+
|
|
86
|
+
### 6.2 Response modes
|
|
87
|
+
|
|
88
|
+
`debug` preserves the existing response shape. It includes device metadata, full action trace, fingerprints, resolved target detail, timing breakdown, and adapter diagnostics when present.
|
|
89
|
+
|
|
90
|
+
`compact` returns only:
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
{
|
|
94
|
+
action_id: string,
|
|
95
|
+
action_type: string,
|
|
96
|
+
success: boolean,
|
|
97
|
+
lifecycle_state: "pending_verification" | "verified" | "failed",
|
|
98
|
+
failure_code?: string,
|
|
99
|
+
retryable?: boolean,
|
|
100
|
+
delivery_status?: "delivered" | "not_delivered" | "unknown",
|
|
101
|
+
dispatch_started: boolean,
|
|
102
|
+
verification?: { mode: "none" | "light" | "full", status: "not_requested" | "observed" | "verified" | "unavailable" | "failed" },
|
|
103
|
+
state_delta?: StateDelta,
|
|
104
|
+
timing: { total_ms: number }
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The compact form MUST retain failure and retry information. It MUST NOT emit device identifiers, full tree contents, raw adapter output, screenshots, fingerprints, or trace steps. A client can repeat an action only with a new `responseMode: "debug"`; it cannot expand a past compact result.
|
|
109
|
+
|
|
110
|
+
## 7. Selector-based tap
|
|
111
|
+
|
|
112
|
+
`tap_element` accepts exactly one of the existing `elementId` or a new `selector`; this does not change the `elementId` path.
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
selector?: {
|
|
116
|
+
text?: string,
|
|
117
|
+
resource_id?: string,
|
|
118
|
+
accessibility_id?: string,
|
|
119
|
+
contains?: boolean
|
|
120
|
+
}
|
|
121
|
+
waitFor?: {
|
|
122
|
+
condition?: "exists" | "visible" | "clickable",
|
|
123
|
+
timeoutMs?: number, // default 1,500; range 100–10,000
|
|
124
|
+
pollIntervalMs?: number // default 100; range 50–1,000
|
|
125
|
+
match?: { index?: number }
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
With a selector, the server resolves a fresh UI tree, verifies that the selected node is actionable, and taps it in the same request. `waitFor` means wait for the target before resolving; it is not post-tap verification. Without `waitFor`, resolution occurs once and fails with `ELEMENT_NOT_FOUND`, `AMBIGUOUS_TARGET`, or `ELEMENT_NOT_INTERACTABLE` as applicable. A selector match must use the same matching and index semantics as `wait_for_ui`.
|
|
130
|
+
|
|
131
|
+
When `verificationMode: "full"`, selector resolution replaces the separate client resolve call but does not skip required post-action verification. The resolved element returned in debug mode includes only the selected node, never the complete tree.
|
|
132
|
+
|
|
133
|
+
## 8. State-delta snapshots and screenshot policy
|
|
134
|
+
|
|
135
|
+
The snapshot metadata layer already tracks stable node identities and aggregate change counts. It is extended to retain the prior normalized node record by device and expose changed nodes.
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
type StateDelta = {
|
|
139
|
+
base_snapshot_revision: number | null,
|
|
140
|
+
snapshot_revision: number,
|
|
141
|
+
added: UIElement[],
|
|
142
|
+
removed: Array<{ stable_id?: string, resourceId?: string, contentDescription?: string, text?: string, index: number }>,
|
|
143
|
+
changed: UIElement[],
|
|
144
|
+
truncated: boolean
|
|
145
|
+
omitted_changes?: number
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
For each `(platform, deviceId)`, the server retains normalized node records for the latest eight revisions or ten minutes, whichever expires first. It retains at most 500 nodes per revision. A delta contains at most 200 changed nodes; additional changes set `truncated: true` and report `omitted_changes`. Revision state is process-local and is discarded on server restart, device disconnect, or retention expiry.
|
|
150
|
+
|
|
151
|
+
`get_ui_tree` adds `responseMode` and `sinceSnapshotRevision`:
|
|
152
|
+
|
|
153
|
+
- `responseMode: "debug"` remains the complete tree response.
|
|
154
|
+
- `responseMode: "compact"` with a matching `sinceSnapshotRevision` returns `state_delta`; it does not include unchanged nodes.
|
|
155
|
+
- `responseMode: "compact"` without `sinceSnapshotRevision` returns `{ delta_available: false, snapshot_revision }` and no nodes. The caller obtains a base with a debug tree.
|
|
156
|
+
- If the requested base revision is unavailable, return `{ delta_available: false, snapshot_revision, reason: "BASE_REVISION_UNAVAILABLE" }` and no nodes. The client recovers by requesting a debug tree, then resumes from its returned revision.
|
|
157
|
+
- A truncated delta is valid only as a change signal. The client MUST request a debug tree before using it to make a target-selection decision.
|
|
158
|
+
|
|
159
|
+
For a `light` action, the server takes exactly one post-dispatch tree. It computes `state_delta` against the most recent retained revision for the same device, if one exists; otherwise it returns `state_delta: undefined` and `state_delta_available: false`. It never takes a pre-dispatch tree merely to create a delta. The returned `base_snapshot_revision` tells the client whether its local state can safely apply that delta.
|
|
160
|
+
|
|
161
|
+
Within `run_journey`, an immediately following `element_visible` assertion may supply that fresh post-dispatch tree for a `light` tap. The tap reports `verification.status: "observed"` when the assertion returns a matched count, even if the assertion fails; it reports `"unavailable"` with a retryable diagnostic if the observation cannot be confirmed.
|
|
162
|
+
|
|
163
|
+
`capture_debug_snapshot` adds `includeScreenshot?: boolean`, defaulting to `true` for compatibility. New compact callers MUST set it explicitly; `includeScreenshot: false` collects no screenshot. `capture_screenshot` remains the explicit screenshot tool. No interaction tool captures a screenshot unless its request explicitly asks for one through its observation configuration.
|
|
164
|
+
|
|
165
|
+
## 9. `run_journey`
|
|
166
|
+
|
|
167
|
+
`run_journey` runs a bounded ordered sequence against one resolved platform and device. It avoids MCP round trips; it does not hold a cross-request device lock or conceal failures.
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
{
|
|
171
|
+
platform: "android" | "ios",
|
|
172
|
+
deviceId?: string,
|
|
173
|
+
responseMode?: "compact" | "debug", // default compact
|
|
174
|
+
captureOnFailure?: boolean, // default false
|
|
175
|
+
defaults?: { verificationMode?: "none" | "light" | "full", actionTimeoutMs?: number, verificationTimeoutMs?: number },
|
|
176
|
+
steps: Array<
|
|
177
|
+
{ id: string, type: "start_app", appId: string, verificationMode?: VerificationMode } |
|
|
178
|
+
{ id: string, type: "wait", selector: Selector, condition?: "exists" | "not_exists" | "visible" | "clickable", timeoutMs?: number, pollIntervalMs?: number } |
|
|
179
|
+
{ id: string, type: "tap", selector?: Selector, elementId?: string, waitFor?: WaitFor, verificationMode?: VerificationMode } |
|
|
180
|
+
{ id: string, type: "assert", assertion: ElementVisibleAssertion | ElementAbsentAssertion | ScreenFingerprintAssertion | StateEqualsAssertion, timeoutMs?: number, pollIntervalMs?: number }
|
|
181
|
+
>
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
type ElementVisibleAssertion = { kind: "element_visible", selector: Selector }
|
|
187
|
+
type ElementAbsentAssertion = { kind: "element_absent", selector: Selector }
|
|
188
|
+
type ScreenFingerprintAssertion = { kind: "screen_fingerprint", fingerprint: string }
|
|
189
|
+
type StateEqualsAssertion = {
|
|
190
|
+
kind: "state_equals",
|
|
191
|
+
property: string,
|
|
192
|
+
expected: boolean | number | string,
|
|
193
|
+
selector?: Selector,
|
|
194
|
+
elementId?: string
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Step IDs must be unique. Maximum steps: 50. A journey validates its complete schema before device work begins. `tap` requires exactly one target. `state_equals` requires exactly one of `selector` or `elementId`; every other assertion rejects irrelevant fields. `start_app` is the journey equivalent of `start_app`.
|
|
199
|
+
|
|
200
|
+
Journey assertions use a 5,000 ms timeout and 100 ms poll interval unless supplied; their ranges are 100–60,000 ms and 50–1,000 ms. `element_visible` is the existing `expect_element_visible` predicate. `element_absent` is a new journey-only predicate: zero matching nodes must be seen in two fresh trees at least 100 ms apart, within the assertion timeout. `screen_fingerprint` matches the current fingerprint on two consecutive reads 100 ms apart. `state_equals` uses the existing `expect_state` stabilization contract, with the journey's supplied or default timeout and poll interval applied as that contract's budget and observation interval; it does not add a second two-read rule. Assertions return `ASSERTION_TIMEOUT` on budget expiry and include the last observed value or matched count. A `wait` remains synchronization, not proof of a business outcome.
|
|
201
|
+
|
|
202
|
+
The journey resolves the platform and device once before the first step and passes that exact context to every step. An `elementId` is valid only when its stored platform and device ID equal the journey context. A mismatch is `STALE_REFERENCE`, no tap is dispatched, and the journey stops.
|
|
203
|
+
|
|
204
|
+
The response is:
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
{
|
|
208
|
+
success: boolean,
|
|
209
|
+
stopped_at_step_id: string | null,
|
|
210
|
+
steps: Array<ExecutedJourneyStep | NotRunJourneyStep>,
|
|
211
|
+
timing: TimingBreakdown
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
type ExecutedJourneyStep = {
|
|
215
|
+
id: string, type: string, status: "passed" | "failed",
|
|
216
|
+
success: boolean, result: CompactOrDebugStepResult, timing: TimingBreakdown
|
|
217
|
+
}
|
|
218
|
+
type NotRunJourneyStep = {
|
|
219
|
+
id: string, type: string, status: "not_run", success: false,
|
|
220
|
+
reason: "PREVIOUS_STEP_FAILED", result: null, timing: { total_ms: 0 }
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
It stops immediately after a failed assertion, failed wait, target-resolution failure, or dispatch failure. Later steps have the `NotRunJourneyStep` shape and are never attempted. An action with `verification.status: "unavailable"` has `status: "passed"` because dispatch succeeded; a following assertion decides whether its intended outcome occurred. Debug mode includes the failing step's trace and a snapshot only when `captureOnFailure: true` is passed at journey level. The failure snapshot has `includeScreenshot: false` unless the caller separately requests a screenshot.
|
|
225
|
+
|
|
226
|
+
## 10. Timing measurement and reporting
|
|
227
|
+
|
|
228
|
+
Every specified tool records monotonic durations; wall clock is used only for timestamps. The timing schema is additive:
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
type TimingBreakdown = {
|
|
232
|
+
total_ms: number,
|
|
233
|
+
device_resolution_ms?: number,
|
|
234
|
+
pre_observation_ms?: number,
|
|
235
|
+
target_resolution_ms?: number,
|
|
236
|
+
dispatch_ms?: number,
|
|
237
|
+
post_observation_ms?: number,
|
|
238
|
+
verification_ms?: number,
|
|
239
|
+
screenshot_ms?: number,
|
|
240
|
+
serialization_ms?: number
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
`debug` returns the full applicable breakdown. `compact` returns `total_ms`; `run_journey` additionally returns each step's `total_ms`. Errors still return elapsed timing.
|
|
245
|
+
|
|
246
|
+
A checked-in benchmark script must emit JSON and Markdown to `docs/benchmarks/interaction-latency/`; reports contain commit SHA, server version, platform, device model/OS, app build identifier, sample count, warm-up policy, verification/response modes, and p50/p95/min/max for each timing component. Raw data must contain no screenshots, UI text, app package identifiers, or device serials.
|
|
247
|
+
|
|
248
|
+
## 11. Benchmark protocol and acceptance criteria
|
|
249
|
+
|
|
250
|
+
The repository owns an Android benchmark fixture at `test/fixtures/latency-journey-app/`. It is self-contained, has no network dependency, and exposes stable accessibility IDs for a repeatable navigation-and-control flow. The benchmark harness builds and installs the fixture.
|
|
251
|
+
|
|
252
|
+
Before every run, the harness must force-stop the fixture, clear its data, install the known fixture APK, and verify the reset-state `saved-session` selector. The benchmark runs against one named Android emulator image, records its API level and fixture APK SHA-256, and performs no work in parallel. It executes this exact flow:
|
|
253
|
+
|
|
254
|
+
```text
|
|
255
|
+
launch app → open detail → activate control → return Home → reopen detail → change control state → stop
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
The fixture and benchmark harness MUST define and use their own stable selector map, and assert each resulting state. It performs 3 warm-up runs and 10 measured runs per mode.
|
|
259
|
+
|
|
260
|
+
The baseline invokes the current public tools over a spawned `dist/server.js` stdio MCP connection: `start_app`, `wait_for_ui`, `tap_element` with a handle returned by `wait_for_ui`, and the existing `expect_*` tools. The candidate invokes the new selector `tap_element` and `run_journey`. Both variants include the same waits/assertions and use `full`/`debug` for the compatibility comparison; the candidate additionally runs `light`/`compact`. End-to-end latency is measured by the benchmark client from immediately before its JSON-RPC request write until the corresponding response is fully read, excluding server process startup. The harness separately times explicit `capture_screenshot` and `capture_debug_snapshot` calls outside the journey.
|
|
261
|
+
|
|
262
|
+
A failed run is reported separately and excluded from percentile calculation; the report must state its failure count, first failed fixture selector, and failed tool/step. The harness emits the exact request sequence and fixture version in the report, with selectors but no UI text or serial number.
|
|
263
|
+
|
|
264
|
+
Acceptance gates:
|
|
265
|
+
|
|
266
|
+
- `full` has no regression greater than 10% at p95 for the representative journey and preserves all existing contract tests.
|
|
267
|
+
- `light` compact reduces p50 end-to-end MCP journey latency by at least 30% versus full/debug on the same benchmark environment.
|
|
268
|
+
- `none` compact has no post-dispatch accessibility read, proven by a focused unit test and its timing breakdown.
|
|
269
|
+
- A compact state delta never includes an unchanged node; a requested screenshot is the only path that captures screenshot bytes.
|
|
270
|
+
- The benchmark report is checked in for baseline and final runs. If environmental variance prevents the 30% threshold, implementation stops for diagnosis rather than relaxing verification semantics or claiming success.
|
|
271
|
+
|
|
272
|
+
## 12. Test plan
|
|
273
|
+
|
|
274
|
+
Add focused unit coverage for:
|
|
275
|
+
|
|
276
|
+
1. Every verification mode on `tap_element` and `start_app`, including the exact adapter/observation calls and lifecycle/result semantics.
|
|
277
|
+
2. Compact/debug compatibility: omitted fields produce the legacy debug envelope; compact retains actionable failure fields and omits verbose metadata.
|
|
278
|
+
3. Selector tap success, wait-until-present success, ambiguity, stale/non-actionable rejection, and timeout.
|
|
279
|
+
4. `run_journey` ordering, single platform/device resolution, first failed assertion, unexecuted remaining steps, and per-step timing.
|
|
280
|
+
5. Delta added/removed/changed identity behavior, revision miss, truncation, and no screenshot when disabled.
|
|
281
|
+
6. Action and verification timeout bounds and the absence of a fixed delay once a fresh expected state is observed.
|
|
282
|
+
7. Deterministic benchmark report aggregation from fixture timing data.
|
|
283
|
+
|
|
284
|
+
Existing unit, lint, build, and device checks remain required. Run the real benchmark only after the automated contract gates pass.
|
|
285
|
+
|
|
286
|
+
## 13. Implementation ownership
|
|
287
|
+
|
|
288
|
+
- `src/server/tool-definitions.ts`: additive schemas and documentation.
|
|
289
|
+
- `src/server/tool-handlers.ts` and `src/server/common.ts`: mode parsing, response shaping, timing envelope, and journey orchestration.
|
|
290
|
+
- `src/interact/index.ts`: selector resolve-and-tap, verification policy, deadline propagation, and step execution primitives.
|
|
291
|
+
- `src/observe/snapshot-metadata.ts` and `src/observe/index.ts`: changed-node deltas and explicit screenshot collection.
|
|
292
|
+
- `src/manage/*`: action-timeout propagation and truthful launch timing.
|
|
293
|
+
- `test/unit/{server,interact,observe}/`: contract coverage; `test/device/automated/`: benchmark journey harness.
|
|
294
|
+
|
|
295
|
+
## 14. Rollout
|
|
296
|
+
|
|
297
|
+
1. Land timing instrumentation and baseline benchmark without changing defaults.
|
|
298
|
+
2. Land verification/response modes with `full`/`debug` defaults and compatibility tests.
|
|
299
|
+
3. Land selector tap and state deltas.
|
|
300
|
+
4. Land `run_journey` and run before/after device benchmarks.
|
|
301
|
+
5. Publish the report and recommend `light`/`compact` for routine agent navigation only after the acceptance gates pass.
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Specification 019 — UI-Tree Observation Performance
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Implemented for Android with a paired emulator benchmark. iOS correctness has
|
|
6
|
+
unit and simulator smoke coverage; iOS latency remains unmeasured. See
|
|
7
|
+
`docs/benchmarks/ui-tree-performance/` for the initial baseline, paired reports,
|
|
8
|
+
and the retained failed first comparison attempt.
|
|
9
|
+
|
|
10
|
+
## 1. Summary
|
|
11
|
+
|
|
12
|
+
Reduce the elapsed time spent obtaining accessibility trees during interaction journeys while preserving fresh-state verification. Measure physical hierarchy reads and their cost first. Then remove demonstrably redundant work within a single observation or journey, and shorten platform acquisition where it can be done without returning an older tree as new evidence.
|
|
13
|
+
|
|
14
|
+
No public tool, request field, default verification mode, or response shape changes in this specification.
|
|
15
|
+
|
|
16
|
+
## 2. Current state and problem
|
|
17
|
+
|
|
18
|
+
Specification 018's checked-in Android benchmark reports a 51.8-second p50 `light`/`compact` journey on a Pixel 9 Pro Android 16 emulator. That is faster than its full/debug comparison, but still slow for an agent completing a short flow. The 018 report does not attribute the compact journey's time to individual hierarchy reads, so it does not establish how much of the remaining 51.8 seconds this work can save.
|
|
19
|
+
|
|
20
|
+
The present Android `getUITree` path obtains device metadata and display resolution, waits before an attempt, invokes `uiautomator dump`, reads the dumped XML, and parses it. The iOS path obtains device metadata, checks `idb`, waits, invokes `idb ui describe-all --json`, and parses the result. Both may retry. `wait_for_ui` and journey assertions can take multiple observations by design.
|
|
21
|
+
|
|
22
|
+
Specification 018 already passes the selector-resolution tree into `tapElementHandler` and, for a compact light tap immediately followed by an element-visible assertion, uses that assertion as the post-action observation. Spec 019 must retain those savings. It must not count a pre-action selector tree as proof of a post-action state, or collapse independent samples required by stabilization.
|
|
23
|
+
|
|
24
|
+
The roadmap's wait/synchronization work requires explicit freshness and reliable in-place UI updates. A network-backed screen can change without any MCP action. A time-based cross-request tree cache would therefore risk false assertions even if it improves a static fixture benchmark.
|
|
25
|
+
|
|
26
|
+
An agent report describes faster app start, tap dispatch, and screenshots, while reporting that `find_element` remains the slowest operation and that `verificationMode: "light"` often returns `VERIFICATION_UNAVAILABLE` after consuming its verification budget. The report provides no device, platform, sample count, request sequence, timeout, or raw measurements, so these figures are investigation leads and are not baseline evidence or acceptance results. In this specification, measure target resolution, dispatch, post-action observation, and assertion costs separately. A failed light observation remains the successful-delivery/incomplete-evidence outcome defined by Spec 018; this work must not relabel it as an action failure or silently remove the observation.
|
|
27
|
+
|
|
28
|
+
## 3. Goals
|
|
29
|
+
|
|
30
|
+
1. Attribute journey latency to physical hierarchy acquisition, retries, metadata/resolution work, parsing, waits, and assertions, without recording UI contents or device serials.
|
|
31
|
+
2. Reduce physical reads or their per-read cost on the measured hot path.
|
|
32
|
+
3. Preserve fresh observations after actions and during asynchronous, in-place UI changes.
|
|
33
|
+
4. Preserve selector actionability, stale-reference protection, snapshot revisions/deltas, timeout handling, and existing verification modes.
|
|
34
|
+
5. Demonstrate the resulting speed and correctness on both a static fixture flow and a controlled network-backed flow.
|
|
35
|
+
|
|
36
|
+
## 4. Non-goals
|
|
37
|
+
|
|
38
|
+
- A cross-request or fixed-TTL cache of UI trees.
|
|
39
|
+
- Treating elapsed time alone as evidence that a tree is current.
|
|
40
|
+
- Weakening `expect_*` stabilization, two-sample checks, or the meaning of `light` and `full` verification.
|
|
41
|
+
- Changing screenshot, log, network, or debugger tools except for test instrumentation needed to distinguish their cost.
|
|
42
|
+
- Claiming an iOS latency gain from Android measurements.
|
|
43
|
+
- Depending on a live external service for the acceptance benchmark.
|
|
44
|
+
|
|
45
|
+
## 5. Observation and reuse contract
|
|
46
|
+
|
|
47
|
+
A **physical tree read** is one invocation of Android `uiautomator dump` and retrieval of its XML, or one invocation of iOS `idb ui describe-all`. A retry is another physical read. Metadata queries, display-resolution queries, parsing, and selector evaluation are separate timing components. An **observation** is a successfully parsed tree from one physical read, with its platform, resolved device, capture completion time, and snapshot revision. Failed reads have an error but produce no reusable observation.
|
|
48
|
+
|
|
49
|
+
An **observation transaction** is an internal call that names all consumers before starting one physical acquisition. It ends when that acquisition returns or fails. A consumer added later, including a later journey step, starts a new transaction unless an existing Spec 018 path explicitly passes the same tree within the ongoing step. This boundary avoids silently making a later assertion depend on a tree captured before a network update. Consumers must be declared before acquisition starts; this spec does not authorize coalescing independent concurrent calls. Existing same-step explicit tree passing remains permitted.
|
|
50
|
+
|
|
51
|
+
The implementation may share one completed observation among consumers in the same observation transaction only if all of these hold:
|
|
52
|
+
|
|
53
|
+
- The consumers use the same resolved platform and device.
|
|
54
|
+
- No action dispatch, app lifecycle operation, scroll, or other operation capable of changing the visible hierarchy occurred between them.
|
|
55
|
+
- Neither consumer requests a later or independent sample. In particular, stabilization windows, `not_exists` confirmation, and screen-fingerprint convergence retain their required separate reads.
|
|
56
|
+
- A caller that requests post-action evidence receives a read started after that action's dispatch, never the tree used to find its target.
|
|
57
|
+
- An observation that failed, timed out, or was cancelled is never reused.
|
|
58
|
+
|
|
59
|
+
The implementation MUST NOT persist a reusable tree across MCP requests. It MUST NOT report a reused tree with a new `captured_at_ms` or `snapshot_revision`. This spec does not permit single-flight sharing across independently entered calls. If a future implementation proposes such sharing, it needs a separate contract for waiter cancellation, per-caller deadlines, acquisition deadline ownership, and process-exit confirmation before the sharing change is implemented.
|
|
60
|
+
|
|
61
|
+
If these conditions cannot be proven at a call site, perform a fresh read. The specification permits optimizing metadata queries, resolution queries, the acquisition command, or parsing instead of introducing observation reuse.
|
|
62
|
+
|
|
63
|
+
## 6. Platform and failure behavior
|
|
64
|
+
|
|
65
|
+
The public `get_ui_tree`, `wait_for_ui`, selector tap, `expect_*`, `run_journey`, and debug-snapshot contracts remain as defined by existing specs. Compact responses continue to omit debug timing components. Diagnostic measurements are collected by the benchmark harness or an internal, opt-in test hook; they are not added to compact tool responses.
|
|
66
|
+
|
|
67
|
+
An expired operation or verification budget stops polling and terminates the active device command under the existing `ACTION_TIMEOUT` or verification-unavailable rules. It does not leave a background tree read that can overlap the next action. For this contract, termination is complete only when the spawned hierarchy command has emitted its close/exit event; sending a kill signal alone does not satisfy it. The caller must not start a subsequent device action until termination is confirmed. A failed acquisition keeps the existing observable error and retry behavior. If an optimization cannot supply a qualifying fresh tree, it falls back to the existing acquisition path within the same remaining deadline. The fallback must not start after the deadline.
|
|
68
|
+
|
|
69
|
+
Tree revisions and deltas are derived only from actual observations. Sharing one observation cannot create a new revision or a second stability sample. Device identity is resolved before any sharing decision; observations from different emulators, simulators, or platforms are isolated.
|
|
70
|
+
|
|
71
|
+
## 7. Measurement contract
|
|
72
|
+
|
|
73
|
+
Add internal instrumentation at the adapter boundary and journey call sites. For each measured run, record counts and elapsed milliseconds for:
|
|
74
|
+
|
|
75
|
+
| Field | Meaning |
|
|
76
|
+
|---|---|
|
|
77
|
+
| `physical_tree_reads` | Number of platform hierarchy commands started, including retries. |
|
|
78
|
+
| `tree_read_ms` | Android dump plus XML retrieval, or the iOS hierarchy command, from start to exit. |
|
|
79
|
+
| `metadata_ms` | Device metadata and capability checks performed for tree acquisition. |
|
|
80
|
+
| `capability_check_ms` | Platform tooling availability checks, when measured separately from metadata. |
|
|
81
|
+
| `resolution_ms` | Display-size acquisition, where applicable. |
|
|
82
|
+
| `parse_ms` | Parsing and conversion to the public element model. |
|
|
83
|
+
| `observation_wait_ms` | Intentional pre-read waits and polling delays. |
|
|
84
|
+
| `target_resolution_ms` | Time spent finding, selecting, and validating an actionable target before dispatch. |
|
|
85
|
+
| `dispatch_ms` | Time from action dispatch start until the platform action command completes. |
|
|
86
|
+
| `verification_budget_ms` | Effective light-verification budget for the action. |
|
|
87
|
+
| `verification_elapsed_ms` | Time spent obtaining the light post-action observation, including waits and failed attempts. |
|
|
88
|
+
| `verification_outcome` | `observed`, `unavailable`, or `not_requested`, matching the public action outcome. |
|
|
89
|
+
| `reused_observations` | Consumers served by an eligible existing observation. |
|
|
90
|
+
| `failed_tree_reads` | Commands that failed, timed out, or were cancelled. |
|
|
91
|
+
|
|
92
|
+
The measurement record also includes the journey step ID, observation purpose (`target`, `verification`, `assertion`, or `debug`), platform, and whether the read started before or after the preceding dispatch. Each physical acquisition has one unique internal acquisition ID and is recorded exactly once; each consumer use has a separate consumer record referring to that ID. When one acquisition serves multiple purposes, record one acquisition and all consumer records; do not duplicate its duration or physical-read count in aggregate totals. Count `failed_tree_reads` once for each hierarchy command that fails to start, exits unsuccessfully, times out, or is cancelled before successful output retrieval. A successful hierarchy command followed by empty/invalid output is a failed acquisition and is counted once. For light verification, record whether the observation budget expired, how much of the effective budget elapsed, and the associated acquisition IDs, including failed attempts. This data is used to determine whether `VERIFICATION_UNAVAILABLE` is driven by command acquisition, metadata, intentional waits, parsing, or the configured budget. The report groups acquisition cost and consumer count by step and purpose; raw timestamps need not be checked in. It MUST NOT include tree nodes, UI text, app identifiers, screenshots, logs, network payloads, or device serials.
|
|
93
|
+
|
|
94
|
+
The harness measures the full MCP request from write to complete response as in Spec 018. Timing components need not sum exactly to wall time where work overlaps, but the report must identify overlap and use wall time for performance gates.
|
|
95
|
+
|
|
96
|
+
## 8. Reproducible benchmark flows
|
|
97
|
+
|
|
98
|
+
Create `test/device/automated/ui-tree-performance/` and `docs/benchmarks/ui-tree-performance/`. Build on Spec 018's spawned `dist/server.js` client and Android fixture rather than introducing a second transport or test framework. First land only instrumentation and the benchmark fixture; capture that clean commit as the baseline using `benchmark.ts --baseline-only`, before changing acquisition behavior. The baseline-only run writes `baseline.json`. After choosing and implementing an optimization, measure the candidate against the same instrumentation, fixture APK SHA-256, emulator image, display settings, and host configuration. Record both commit SHAs, server version, platform/model/OS/API, command timeout, sample counts, failures, and request sequences.
|
|
99
|
+
|
|
100
|
+
The harness accepts `UI_TREE_BASELINE_ROOT`, `UI_TREE_CANDIDATE_ROOT`, and `LATENCY_DEVICE_ID`. Each root must be a clean, built checkout containing `dist/server.js`; missing or dirty inputs fail before a device reset. It starts each server over stdio from the supplied root, uses the single candidate fixture APK for both modes, and writes `baseline.json`, `candidate.json`, and `comparison.md` under `docs/benchmarks/ui-tree-performance/`. A reviewer can reproduce the comparison with:
|
|
101
|
+
|
|
102
|
+
```sh
|
|
103
|
+
UI_TREE_BASELINE_ROOT=/path/to/instrumented-baseline \
|
|
104
|
+
UI_TREE_CANDIDATE_ROOT=/path/to/optimized-candidate \
|
|
105
|
+
LATENCY_DEVICE_ID=emulator-5554 \
|
|
106
|
+
npx tsx test/device/automated/ui-tree-performance/benchmark.ts
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Two flows are required:
|
|
110
|
+
|
|
111
|
+
1. **Static journey:** the Spec 018 navigation/control journey using `light`/`compact`, with the same actions and assertions in baseline and candidate. It isolates hierarchy overhead without network variance.
|
|
112
|
+
2. **Network-backed in-place update:** a deterministic local HTTP server drives an Android fixture screen from `loading` to `ready` after a configured delay, without navigation or a second MCP action. The fixture exposes stable accessibility IDs `loading`, `ready`, and `value`. For each attempt, the harness starts the fixture and server in a known reset state. The server holds the response until a completed MCP observation contains `loading` and contains neither `ready` nor `value`; it then starts the configured delay and sends `ready` with a run-unique value in the same response, recording a monotonic send timestamp and run ID. The harness then issues a fresh observation request after the response send event and asserts that one observation contains both `ready` and the matching run-unique value. A read that started before the response send event cannot satisfy this assertion even if it completes afterward. Run one response delay of 100 ms and one of 1,500 ms. Use emulator-to-host routing supplied by the harness; do not call a public service.
|
|
113
|
+
|
|
114
|
+
Both flows run sequentially, with three warm-ups followed by exactly ten measured attempts per flow, response-delay setting, and baseline/candidate mode. Failed runs are counted and excluded from percentiles, with the failed step and selector reported; any failed measured attempt fails acceptance. Screenshots and debug snapshots are outside the timed journeys and may be reported separately. Baseline and candidate order should alternate by run pair to limit emulator drift; the fixture and local server are reset before every run. The reports must state the timing boundary for every metric and include per-attempt acquisition/consumer records so the aggregation rules in Section 7 can be reproduced.
|
|
115
|
+
|
|
116
|
+
For iOS, automated unit tests with controlled `idb` output and the existing simulator smoke tests must cover correctness and cancellation. An iOS latency claim requires a reproducible iOS fixture and the same paired protocol; until then, report iOS performance as unmeasured.
|
|
117
|
+
|
|
118
|
+
## 9. Acceptance criteria
|
|
119
|
+
|
|
120
|
+
1. The baseline report identifies the physical-read count and p50/p95 time for each purpose and journey step, and distinguishes hierarchy command time from waits and parsing. It reports target-resolution, dispatch, and light-verification elapsed time and outcomes, including the rate and elapsed budget for `VERIFICATION_UNAVAILABLE`.
|
|
121
|
+
2. On the static Android journey, candidate p50 end-to-end time improves by at least 15% against its paired baseline, with no more than 5% p95 regression. At least one measured hot-path component must account for the gain; a response-size-only gain does not satisfy this spec.
|
|
122
|
+
3. On both network-backed delay settings, candidate p95 end-to-end time does not regress by more than 5%. The ready/value assertions never pass before the corresponding server response, and no run passes using a pre-update tree. The request start timestamp for the observation supplying the assertion must be later than that attempt's recorded response-send timestamp.
|
|
123
|
+
4. Baseline and candidate have zero assertion failures across the ten measured attempts per flow. Every failed warm-up or measured attempt remains visible in the report; no failed run contributes to a percentile.
|
|
124
|
+
5. Focused tests prove that a post-action assertion gets a post-dispatch read, two-sample stabilization uses two physical reads, device isolation holds, a failed/cancelled observation is not reused, and a budget expiry waits for hierarchy child-process exit before a subsequent device action can begin. Tests also prove one physical acquisition is counted once when explicitly passed to multiple same-step consumers, while all consumers receive records linked to that acquisition.
|
|
125
|
+
6. `npm run verify` and `npm run test:device` pass. Existing Spec 018 response-shape and journey tests continue to pass. The checked-in JSON and Markdown reports identify exactly which platforms and flows were measured.
|
|
126
|
+
|
|
127
|
+
If the 15% gate cannot be met without weakening freshness or verification, stop the optimization and report the measured bottleneck and failed gate. Do not lower the gate or mark this specification implemented on the strength of a synthetic unit benchmark.
|
|
128
|
+
|
|
129
|
+
## 10. Implementation plan and ownership
|
|
130
|
+
|
|
131
|
+
1. Add measurement hooks and reproduce the baseline before changing acquisition behavior. Validate the counters against a fake Android `adb` and fake iOS `idb` command so retries, invalid output, termination, and cancellations are counted once per actual acquisition and child-process exit is observable before the next action.
|
|
132
|
+
2. Use the baseline breakdown to select one or more changes in `src/observe/android.ts`, `src/observe/ios.ts`, `src/observe/index.ts`, or `src/interact/index.ts`. Document why each changed read is redundant or why its replacement is equivalently fresh.
|
|
133
|
+
3. Preserve the contracts in Section 5 with focused unit tests under `test/unit/observe/` and `test/unit/interact/`. Add the controlled network fixture and harness under `test/device/automated/`.
|
|
134
|
+
4. Run the paired benchmarks and required repository gates. Check in baseline and candidate reports, update tool documentation only if user-visible behavior changes, and record any unmeasured platform separately.
|
|
135
|
+
|
|
136
|
+
The benchmark and fixture belong to this repository. No backend or third-party service is required. Device testing requires the Android SDK/emulator and, for iOS smoke checks, a booted simulator with `idb`; inability to access either is an execution blocker for that platform's acceptance check, not evidence that it passed.
|
|
137
|
+
|
|
138
|
+
## 11. Baseline decision and result
|
|
139
|
+
|
|
140
|
+
The initial Android baseline found 20 physical tree reads in each measured
|
|
141
|
+
static journey, including independent assertion samples, and an approximately
|
|
142
|
+
400 ms intentional wait before every first attempt. No cross-request tree
|
|
143
|
+
reuse was added. The candidate starts the first Android read immediately and
|
|
144
|
+
retains the wait for retries. The clean repeat paired run improved static p50
|
|
145
|
+
from 52.40 s to 43.82 s (16.4%) with no network p95 regression or failed
|
|
146
|
+
attempts. A prior paired attempt had two network failures and is retained in
|
|
147
|
+
the benchmark directory; its cause remains unconfirmed. This result does not
|
|
148
|
+
claim that `VERIFICATION_UNAVAILABLE` is eliminated in other apps.
|
package/docs/tools/interact.md
CHANGED
|
@@ -616,3 +616,33 @@ Guidance:
|
|
|
616
616
|
- `uiChanged=true` or `expectedElementVisible=true` means the action outcome is already verified
|
|
617
617
|
- local-state actions should prefer refreshed snapshots, `expect_state`, or `expect_element_visible` over default network inspection
|
|
618
618
|
- network activity is auxiliary evidence, not mandatory proof
|
|
619
|
+
|
|
620
|
+
## Interaction execution controls (RFC 018)
|
|
621
|
+
|
|
622
|
+
`start_app`, `restart_app`, `tap`, `tap_element`, `swipe`, `scroll_to_element`,
|
|
623
|
+
`type_text`, and `press_back` accept `verificationMode` (`none`, `light`, `full`)
|
|
624
|
+
and `responseMode` (`compact`, `debug`). Omitted controls retain `full`/`debug`.
|
|
625
|
+
`none` reports dispatch without post-action accessibility reads. `light` requests
|
|
626
|
+
one fresh tree; an unavailable observation does not turn successful delivery into
|
|
627
|
+
a failed action. Use an explicit assertion to establish the intended outcome.
|
|
628
|
+
Compact responses retain delivery, retry, lifecycle, and timing fields.
|
|
629
|
+
|
|
630
|
+
`actionTimeoutMs` and `verificationTimeoutMs` are integer budgets, clamped to
|
|
631
|
+
100–30,000 and 100–60,000 ms respectively. An action timeout after dispatch starts
|
|
632
|
+
has unknown delivery and must be observed before retrying.
|
|
633
|
+
|
|
634
|
+
`tap_element` accepts exactly one `elementId` or `selector`. A selector supports
|
|
635
|
+
`text`, `resource_id`, `accessibility_id`, and `contains`. Optional `waitFor` waits
|
|
636
|
+
for the target before tapping; without it, resolution uses one observation.
|
|
637
|
+
Ambiguous targets require an explicit `waitFor.match.index`.
|
|
638
|
+
|
|
639
|
+
`run_journey` accepts 1–50 steps with unique IDs, on one platform/device. Supported
|
|
640
|
+
step types are `start_app`, `tap`, `wait`, and `assert`. Assertions support
|
|
641
|
+
`element_visible`, `element_absent`, `screen_fingerprint`, and `state_equals`.
|
|
642
|
+
The complete request is validated before device work. The first failed step stops
|
|
643
|
+
the journey; remaining steps report `not_run`. Journey responses default to compact.
|
|
644
|
+
`defaults` supplies execution controls; step verification overrides the default.
|
|
645
|
+
`captureOnFailure: true` includes a screenshot-free failure snapshot in debug mode.
|
|
646
|
+
|
|
647
|
+
See [RFC 018](../specs-deltas/018-interaction-latency-and-batched-journeys.md) for
|
|
648
|
+
contracts and the device acceptance criteria.
|
package/docs/tools/observe.md
CHANGED
|
@@ -92,6 +92,7 @@ Notes:
|
|
|
92
92
|
- Elements may also include platform-native identity hints such as `stable_id`, `role`, `test_tag`, `selector`, and `semantic`.
|
|
93
93
|
- The tree response may include `snapshot_revision`, `captured_at_ms`, and `loading_state` when a reliable signal is available.
|
|
94
94
|
- Prefer `wait_for_ui` for deterministic element resolution in interactive flows.
|
|
95
|
+
- Android can use the experimental persistent hierarchy reader by setting `MOBILE_DEBUG_MCP_ANDROID_TREE_BRIDGE_APK` to the absolute path of a locally built bridge APK before starting the server. Build it with `npx tsx test/fixtures/ui-tree-bridge/build.ts`. The server installs the helper on first use, reads a fresh accessibility root for every request, and falls back to `uiautomator dump` after stopping the helper if the bridge fails. The helper holds Android's UiAutomation connection while the server runs, so other `uiautomator dump` clients may fail during that time. Leave the variable unset for the default reader. To reproduce the device probe, set `LATENCY_DEVICE_ID` and run `npx tsx test/device/automated/ui-tree-performance/bridge-probe.ts`.
|
|
95
96
|
|
|
96
97
|
---
|
|
97
98
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mobile-debug-mcp
|
|
3
|
+
description: Verify Android app behaviour efficiently with mobile-debug MCP, especially session playback, background work, and notifications. Use for emulator/device QA; do not use for implementation-only checks.
|
|
4
|
+
metadata:
|
|
5
|
+
author: modul8-team
|
|
6
|
+
version: "1.0"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Mobile-debug MCP verification
|
|
10
|
+
|
|
11
|
+
Use mobile-debug MCP to make a product claim observable on a real device or emulator. A successful tap proves only that the input was delivered; verify the resulting UI, notification, or persisted state before reporting success.
|
|
12
|
+
|
|
13
|
+
## Choose the lightest evidence that proves the claim
|
|
14
|
+
|
|
15
|
+
- Use an accessibility assertion such as `expect_element_visible` or
|
|
16
|
+
`wait_for_ui` for an accessible label, enabled state, or changed control.
|
|
17
|
+
- Use a screenshot when visual layout, system UI, or notification content matters.
|
|
18
|
+
- Use a debug snapshot only when the screen does not explain a failed action or an accessibility-tree assertion is needed.
|
|
19
|
+
- Use Android shell commands only for device navigation or system surfaces that MCP cannot expose. Do not treat shell output as proof of user-visible behaviour.
|
|
20
|
+
|
|
21
|
+
Avoid repeated screenshots and snapshots when a targeted accessibility assertion is enough.
|
|
22
|
+
|
|
23
|
+
## Keep interaction fast
|
|
24
|
+
|
|
25
|
+
Treat each MCP call as a round trip and use the fastest path that still proves
|
|
26
|
+
the behaviour. Prefer the following sequence:
|
|
27
|
+
|
|
28
|
+
1. Wait for one stable, uniquely identifiable control.
|
|
29
|
+
2. Tap its returned element id.
|
|
30
|
+
3. Wait for the specific resulting state.
|
|
31
|
+
|
|
32
|
+
Do not wait before every tap when the current screen and element id are already known. Do not poll unchanged screens. Use short timeouts for ordinary controls; reserve longer waits for known asynchronous work such as model initialisation or network calls.
|
|
33
|
+
|
|
34
|
+
Use compact responses for routine actions. If action dispatch is reliable but
|
|
35
|
+
verification is slow or unavailable, dispatch with verification disabled and
|
|
36
|
+
verify at meaningful checkpoints with one focused accessibility assertion or
|
|
37
|
+
screenshot. Do not assume `light` verification is faster: use it only when it
|
|
38
|
+
has returned useful results reliably on the current MCP version and device.
|
|
39
|
+
|
|
40
|
+
Prefer a stable element selector when the accessibility tree exposes one. Use
|
|
41
|
+
coordinates only when needed, and take a fresh screenshot immediately before a
|
|
42
|
+
coordinate tap on system UI, notifications, or any screen where layout may have
|
|
43
|
+
changed. Never reuse coordinates after expanding/collapsing a notification,
|
|
44
|
+
changing orientation, or switching screens.
|
|
45
|
+
|
|
46
|
+
Element discovery can be the slowest part of a journey. Avoid repeated
|
|
47
|
+
`find_element` calls for the same screen: resolve a control once, use the
|
|
48
|
+
returned selector or coordinates while that screen is unchanged, and verify
|
|
49
|
+
the resulting state before continuing. If a query fails, inspect the current
|
|
50
|
+
screen once before retrying; controls represented only by icons may not have a
|
|
51
|
+
matching visible text label.
|
|
52
|
+
|
|
53
|
+
When the MCP offers a journey or batch action, use it only when its steps can
|
|
54
|
+
express the needed observable checks and it returns enough evidence to report
|
|
55
|
+
the outcome. Measure the whole journey and compare equivalent steps on the same
|
|
56
|
+
device; do not infer a performance improvement from a single fast action.
|
|
57
|
+
|
|
58
|
+
## Session playback checks
|
|
59
|
+
|
|
60
|
+
For a claimed playback start, observe a running timer or Pause state after Play. For a claimed pause, observe Resume and unchanged elapsed time. For a claimed stop, observe that playback ends and the notification disappears.
|
|
61
|
+
|
|
62
|
+
For background playback, press Home, open the notification shade, and verify
|
|
63
|
+
the notification title, progress/status, and available actions. Expand the
|
|
64
|
+
session notification if necessary, then capture its current layout before
|
|
65
|
+
tapping an action. Exercise each action needed for the change and observe its
|
|
66
|
+
result: Pause becomes Resume; Resume returns to active playback; Stop ends
|
|
67
|
+
playback and clears the notification. After each action, reacquire the current
|
|
68
|
+
screen state before using another coordinate.
|
|
69
|
+
|
|
70
|
+
Do not claim spoken guidance was verified unless audio output was actually inspected. A running timer or active notification verifies playback state, not audible wording.
|
|
71
|
+
|
|
72
|
+
## Report evidence precisely
|
|
73
|
+
|
|
74
|
+
State the device journey, observed result, and any untested scope. Mention whether evidence came from an accessibility assertion, screenshot, or system notification. Do not say a feature works solely because the build compiled or an MCP action returned success.
|