mobile-debug-mcp 0.34.0 → 0.36.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/.agents/skills/mcp-builder/SKILL.md +44 -64
- package/.agents/skills/mcp-builder/references/build-flags.md +44 -43
- package/.agents/skills/mcp-builder/references/diagnostics-schema.md +4 -1
- package/.agents/skills/mcp-builder/references/toolchain-details.md +49 -62
- package/.agents/skills/spec-review/references/spec-review-checklist.md +7 -0
- package/.agents/skills/test-authoring/SKILL.md +7 -6
- package/.agents/skills/test-authoring/references/repo-test-layout.md +3 -1
- package/.agents/skills/test-authoring/references/test-authoring-workflow.md +16 -7
- package/.agents/skills/test-authoring/references/test-quality-checklist.md +2 -1
- package/.cursor-plugin/marketplace.json +1 -1
- package/.github/copilot-instructions.md +14 -36
- package/.github/workflows/ci.yml +4 -4
- package/AGENTS.md +12 -3
- package/CONTRIBUTING.md +79 -0
- package/README.md +218 -129
- package/agent-plugin/.antigravity-plugin/README.md +48 -0
- package/agent-plugin/.antigravity-plugin/plugin.json +18 -0
- package/agent-plugin/.claude-plugin/plugin.json +2 -2
- package/agent-plugin/.codex-plugin/plugin.json +1 -1
- package/agent-plugin/.cursor-plugin/plugin.json +2 -2
- package/agent-plugin/.mcp.json +1 -1
- package/agent-plugin/README.md +50 -11
- package/agent-plugin/cursor/.cursor-plugin/plugin.json +23 -0
- package/agent-plugin/cursor/LICENSE +201 -0
- package/agent-plugin/cursor/README.md +9 -0
- package/agent-plugin/cursor/skills/mobile-debug-mcp/SKILL.md +253 -0
- package/agent-plugin/mcp.json +1 -1
- package/agent-plugin/mcp_config.json +13 -0
- package/agent-plugin/plugin.json +1 -1
- package/agent-plugin/skills/mobile-debug-mcp/SKILL.md +62 -17
- package/dist/contracts/execution-controls.js +24 -0
- package/dist/contracts/selector.js +8 -0
- package/dist/contracts/target.js +15 -0
- package/dist/contracts/ui.js +1 -0
- package/dist/debugger/android.js +2 -1
- package/dist/debugger/diagnostics.js +54 -0
- package/dist/debugger/guard.js +4 -4
- package/dist/debugger/ios-worker.js +62 -0
- package/dist/debugger/ios.js +57 -0
- package/dist/debugger/protocol.js +33 -0
- package/dist/debugger/sessions.js +133 -74
- package/dist/debugger/worker.js +59 -46
- package/dist/interact/adjustment/adjustment-context.js +41 -0
- package/dist/interact/adjustment/adjustment-gestures.js +55 -0
- package/dist/interact/adjustment/adjustment-plan.js +18 -0
- package/dist/interact/adjustment/adjustment-resolution.js +127 -0
- package/dist/interact/adjustment/adjustment-result.js +19 -0
- package/dist/interact/adjustment/adjustment-semantic.js +64 -0
- package/dist/interact/adjustment/adjustment-verification.js +35 -0
- package/dist/interact/adjustment/adjustment.js +52 -0
- package/dist/interact/adjustment/control-geometry.js +87 -0
- package/dist/interact/adjustment/control-values.js +136 -0
- package/dist/interact/adjustment/slider-labels.js +84 -0
- package/dist/interact/assertions/screen-assertion.js +37 -0
- package/dist/interact/assertions/state-assertion-context.js +35 -0
- package/dist/interact/assertions/state-assertion.js +76 -0
- package/dist/interact/assertions/state-comparison.js +141 -0
- package/dist/interact/assertions/visibility-assertion.js +34 -0
- package/dist/interact/conditional-action.js +12 -7
- package/dist/interact/index.js +77 -3092
- package/dist/interact/input-service.js +1 -0
- package/dist/interact/interaction-trace.js +21 -0
- package/dist/interact/model.js +1 -0
- package/dist/interact/observation.js +1 -0
- package/dist/interact/targeting/candidate-ancestor.js +58 -0
- package/dist/interact/targeting/candidate-result.js +23 -0
- package/dist/interact/targeting/candidate-scoring.js +50 -0
- package/dist/interact/targeting/element-helpers.js +216 -0
- package/dist/interact/targeting/find-element.js +65 -0
- package/dist/interact/targeting/nearby-control.js +56 -0
- package/dist/interact/targeting/resolved-elements.js +21 -0
- package/dist/interact/targeting/tap-target.js +17 -0
- package/dist/interact/targeting/target-scope.js +104 -0
- package/dist/interact/waits/legacy-log-wait.js +38 -0
- package/dist/interact/waits/legacy-wait-observations.js +103 -0
- package/dist/interact/waits/legacy-wait-state.js +34 -0
- package/dist/interact/waits/legacy-wait.js +48 -0
- package/dist/interact/waits/screen-wait.js +49 -0
- package/dist/interact/waits/ui-change-helpers.js +157 -0
- package/dist/interact/waits/ui-change-state.js +18 -0
- package/dist/interact/waits/ui-change-wait.js +77 -0
- package/dist/interact/waits/ui-wait-condition.js +56 -0
- package/dist/interact/waits/ui-wait-state.js +38 -0
- package/dist/interact/waits/ui-wait.js +106 -0
- package/dist/manage/android.js +55 -51
- package/dist/manage/index.js +38 -75
- package/dist/manage/ios/build-types.js +1 -0
- package/dist/manage/ios/build.js +210 -0
- package/dist/manage/ios/project-discovery.js +29 -0
- package/dist/manage/ios/scheme.js +18 -0
- package/dist/manage/ios.js +44 -256
- package/dist/observe/android/context.js +1 -0
- package/dist/observe/android/current-screen.js +55 -0
- package/dist/observe/android/fingerprint.js +13 -0
- package/dist/observe/android/log-stream.js +132 -0
- package/dist/observe/android/logs.js +107 -0
- package/dist/observe/android/screenshot.js +70 -0
- package/dist/observe/android/ui-tree.js +107 -0
- package/dist/observe/android.js +16 -520
- package/dist/observe/evidence.js +114 -75
- package/dist/observe/index.js +16 -263
- package/dist/observe/ios/context.js +16 -0
- package/dist/observe/ios/fingerprint.js +12 -0
- package/dist/observe/ios/log-stream.js +105 -0
- package/dist/observe/ios/logs.js +168 -0
- package/dist/observe/ios/screenshot.js +22 -0
- package/dist/observe/ios/ui-elements.js +241 -0
- package/dist/observe/ios/ui-tree.js +137 -0
- package/dist/observe/ios-trace/artifact.js +63 -0
- package/dist/observe/ios-trace/capabilities.js +86 -0
- package/dist/observe/ios-trace/native.js +93 -0
- package/dist/observe/ios-trace/notification.js +35 -0
- package/dist/observe/ios-trace/process.js +98 -0
- package/dist/observe/ios-trace/sessions.js +244 -0
- package/dist/observe/ios.js +17 -715
- package/dist/observe/logs/model.js +1 -0
- package/dist/{network → observe/network}/index.js +2 -2
- package/dist/observe/readiness-service.js +5 -3
- package/dist/observe/readiness.js +33 -14
- package/dist/observe/screenshot-encoding.js +41 -0
- package/dist/observe/snapshot-collection.js +79 -0
- package/dist/observe/snapshot-logs.js +30 -0
- package/dist/observe/snapshot-metadata.js +43 -31
- package/dist/observe/snapshot-semantic.js +115 -0
- package/dist/observe/system-trace-error.js +7 -0
- package/dist/observe/system-trace-router.js +81 -0
- package/dist/observe/system-trace.js +26 -21
- package/dist/runtime/action-results.js +270 -0
- package/dist/runtime/clock.js +4 -0
- package/dist/server/common.js +1 -256
- package/dist/server/definitions/assert.js +162 -0
- package/dist/server/definitions/diagnostic.js +118 -0
- package/dist/server/definitions/input.js +274 -0
- package/dist/server/definitions/manage.js +194 -0
- package/dist/server/definitions/observe.js +149 -0
- package/dist/server/definitions/target.js +138 -0
- package/dist/server/definitions/wait.js +105 -0
- package/dist/server/handlers/action-execution.js +163 -0
- package/dist/server/handlers/correlation.js +23 -0
- package/dist/server/handlers/debugger-handlers.js +67 -0
- package/dist/server/handlers/debugger-response.js +9 -0
- package/dist/server/handlers/diagnostic.js +77 -0
- package/dist/server/handlers/handler-types.js +1 -0
- package/dist/server/handlers/input.js +154 -0
- package/dist/server/handlers/interact.js +113 -0
- package/dist/server/handlers/journey-assertions.js +35 -0
- package/dist/server/handlers/journey.js +147 -0
- package/dist/server/handlers/manage.js +119 -0
- package/dist/server/handlers/observe.js +95 -0
- package/dist/server/handlers/payload.js +4 -0
- package/dist/server/handlers/tool-handlers.js +170 -0
- package/dist/server/schemas/condition-schema.js +2 -0
- package/dist/server/{debugger-schema.js → schemas/debugger-schema.js} +20 -12
- package/dist/server/{journey-schema.js → schemas/journey-schema.js} +7 -22
- package/dist/server/schemas/system-trace-schema.js +22 -0
- package/dist/server/tool-definitions.js +58 -1135
- package/dist/server-core.js +2 -2
- package/dist/server.js +4 -4
- package/dist/system/android-status.js +101 -0
- package/dist/system/android.js +37 -27
- package/dist/system/index.js +13 -439
- package/dist/system/ios-status.js +132 -0
- package/dist/system/ios.js +11 -8
- package/dist/system/model.js +1 -0
- package/dist/system/probes.js +105 -0
- package/dist/system/status-summary.js +43 -0
- package/dist/system/unsupported-status.js +52 -0
- package/dist/utils/android/commands.js +89 -0
- package/dist/utils/android/gradle.js +139 -0
- package/dist/utils/android/logs.js +103 -0
- package/dist/utils/android/ui-tree.js +233 -0
- package/dist/utils/android/utils.js +6 -564
- package/dist/utils/cli/idb/idb-helper.js +4 -6
- package/dist/utils/cli/idb/install-idb.js +13 -9
- package/dist/utils/cli/ios/preflight-ios.js +42 -38
- package/dist/utils/ios/utils.js +48 -46
- package/dist/utils/java.js +65 -49
- package/dist/utils/resolve-device.js +4 -2
- package/dist/utils/ui/index.js +26 -26
- package/docs/CHANGELOG.md +67 -1
- package/docs/README.md +38 -0
- package/docs/agent-plugin-installation-verification.md +39 -0
- package/docs/benchmarks/README.md +18 -0
- package/docs/benchmarks/interaction-latency/README.md +14 -0
- package/docs/benchmarks/interaction-latency/final.md +4 -1
- package/docs/benchmarks/interaction-latency/ios-validation.md +2 -0
- package/docs/benchmarks/ui-tree-performance/README.md +24 -16
- package/docs/benchmarks/ui-tree-performance/comparison.md +11 -10
- package/docs/code-structure.md +120 -0
- package/docs/specs/001-state-verification/spec.md +16 -14
- package/docs/specs/002-richer-element-identity/spec.md +12 -10
- package/docs/specs/003-wait-and-synchronization-reliability/spec.md +12 -10
- package/docs/specs/004-action-verification-routing/spec.md +3 -3
- package/docs/specs/005-unified-action-execution-and-verification-model/spec.md +2 -0
- package/docs/specs/006-runtime-action-instrumentation-and-binding-layer/spec.md +2 -0
- package/docs/specs/007-actionability-resolution-and-executable-target-selection/spec.md +3 -1
- package/docs/specs/008-adjustable-control-support-and-semantic-value-manipulation/spec.md +3 -4
- package/docs/specs/009-semantic-control-modeling-for-custom-and-composite-controls/spec.md +2 -0
- package/docs/specs/010-verification-stabilization-and-temporal-convergence/spec.md +2 -2
- package/docs/specs/011-recovery-and-replanning-for-failed-or-ambiguous-interaction-flows/spec.md +2 -2
- package/docs/specs/011.1-recovery-contract-types-and-runtime-wiring-spec/spec.md +2 -0
- package/docs/specs/012-action-trace-and-xecution-observability/spec.md +2 -3
- package/docs/specs/013-wait-and-synchronization-reliability/spec.md +19 -19
- package/docs/specs/014-actionability-resolution/spec.md +20 -18
- package/docs/specs/015-adjustable-control-support/spec.md +17 -15
- package/docs/specs/016-environment-tools/spec.md +20 -20
- package/docs/specs/018-interaction-latency-and-batched-journeys/spec.md +2 -0
- package/docs/specs/019-ui-tree-observation-performance/spec.md +3 -1
- package/docs/specs/020-conditional-actions/intent.md +5 -3
- package/docs/specs/020-conditional-actions/plan.md +8 -9
- package/docs/specs/020-conditional-actions/spec.md +4 -2
- package/docs/specs/021-app-readiness-signals/intent.md +4 -2
- package/docs/specs/021-app-readiness-signals/plan.md +7 -6
- package/docs/specs/021-app-readiness-signals/spec.md +3 -1
- package/docs/specs/022-correlated-evidence/intent.md +4 -4
- package/docs/specs/022-correlated-evidence/plan.md +10 -11
- package/docs/specs/022-correlated-evidence/spec.md +3 -1
- package/docs/specs/023-android-system-traces/intent.md +3 -2
- package/docs/specs/023-android-system-traces/plan.md +5 -7
- package/docs/specs/023-android-system-traces/spec.md +3 -1
- package/docs/specs/024-android-heap-dump-capture/intent.md +3 -1
- package/docs/specs/025-android-runtime-debugger/intent.md +4 -3
- package/docs/specs/025-android-runtime-debugger/plan.md +3 -1
- package/docs/specs/025-android-runtime-debugger/spec.md +4 -3
- package/docs/specs/026-code-structure-and-quality-gates/health-plan.md +88 -0
- package/docs/specs/026-code-structure-and-quality-gates/intent.md +47 -0
- package/docs/specs/026-code-structure-and-quality-gates/plan.md +191 -0
- package/docs/specs/026-code-structure-and-quality-gates/spec.md +138 -0
- package/docs/specs/026-code-structure-and-quality-gates/verification-final.json +200 -0
- package/docs/specs/026-code-structure-and-quality-gates/verification-thin-facade.json +154 -0
- package/docs/specs/026-code-structure-and-quality-gates/verification.json +136 -0
- package/docs/specs/027-debugger-failure-diagnostics/intent.md +43 -0
- package/docs/specs/027-debugger-failure-diagnostics/plan.md +86 -0
- package/docs/specs/027-debugger-failure-diagnostics/spec.md +133 -0
- package/docs/specs/028-compact-debugger-responses/intent.md +41 -0
- package/docs/specs/028-compact-debugger-responses/plan.md +112 -0
- package/docs/specs/028-compact-debugger-responses/spec.md +108 -0
- package/docs/specs/029-kotlin-locals-validation/report.md +119 -0
- package/docs/specs/030-android-coroutine-validation/evidence.json +475 -0
- package/docs/specs/030-android-coroutine-validation/intent.md +47 -0
- package/docs/specs/030-android-coroutine-validation/plan.md +147 -0
- package/docs/specs/030-android-coroutine-validation/report.md +240 -0
- package/docs/specs/030-android-coroutine-validation/spec.md +107 -0
- package/docs/specs/031-ios-system-traces/intent.md +38 -0
- package/docs/specs/031-ios-system-traces/investigation.md +92 -0
- package/docs/specs/031-ios-system-traces/late-run-failure-evidence.json +32 -0
- package/docs/specs/031-ios-system-traces/plan.md +138 -0
- package/docs/specs/031-ios-system-traces/pr102-monitor-evidence.json +22 -0
- package/docs/specs/031-ios-system-traces/simulator-evidence.json +47 -0
- package/docs/specs/031-ios-system-traces/spec.md +135 -0
- package/docs/specs/032-ios-runtime-debugger/evidence.json +176 -0
- package/docs/specs/032-ios-runtime-debugger/intent.md +38 -0
- package/docs/specs/032-ios-runtime-debugger/plan.md +95 -0
- package/docs/specs/032-ios-runtime-debugger/report.md +205 -0
- package/docs/specs/032-ios-runtime-debugger/spec.md +153 -0
- package/docs/tools/TOOLS.md +31 -18
- package/docs/tools/{evidence.md → correlated-evidence.md} +9 -1
- package/docs/tools/debugger.md +120 -4
- package/docs/tools/interact.md +42 -20
- package/docs/tools/manage.md +25 -44
- package/docs/tools/observe.md +55 -23
- package/docs/tools/system-traces.md +89 -14
- package/docs/tools/system.md +12 -5
- package/package.json +12 -8
- package/scripts/agent-plugin.mjs +55 -5
- package/scripts/architecture-graph.mjs +69 -0
- package/scripts/check-architecture.mjs +47 -0
- package/scripts/code-quality-baseline.json +1 -0
- package/scripts/code-quality.mjs +161 -0
- package/src/contracts/execution-controls.ts +26 -0
- package/src/contracts/selector.ts +17 -0
- package/src/contracts/target.ts +19 -0
- package/src/contracts/ui.ts +94 -0
- package/src/debugger/android.ts +3 -2
- package/src/debugger/diagnostics.ts +43 -0
- package/src/debugger/guard.ts +10 -5
- package/src/debugger/ios-worker.ts +49 -0
- package/src/debugger/ios.ts +45 -0
- package/{worker → src/debugger/java}/DebuggerWorker.java +9 -3
- package/src/debugger/lldb/breakpoints.py +51 -0
- package/src/debugger/lldb/inspection.py +109 -0
- package/src/debugger/lldb/worker.py +203 -0
- package/src/debugger/protocol.ts +37 -0
- package/src/debugger/sessions.ts +326 -155
- package/src/debugger/worker.ts +57 -41
- package/src/interact/adjustment/adjustment-context.ts +83 -0
- package/src/interact/adjustment/adjustment-gestures.ts +48 -0
- package/src/interact/adjustment/adjustment-plan.ts +23 -0
- package/src/interact/adjustment/adjustment-resolution.ts +104 -0
- package/src/interact/adjustment/adjustment-result.ts +34 -0
- package/src/interact/adjustment/adjustment-semantic.ts +53 -0
- package/src/interact/adjustment/adjustment-verification.ts +60 -0
- package/src/interact/adjustment/adjustment.ts +49 -0
- package/src/interact/adjustment/control-geometry.ts +120 -0
- package/src/interact/adjustment/control-values.ts +141 -0
- package/src/interact/adjustment/slider-labels.ts +84 -0
- package/src/interact/assertions/screen-assertion.ts +39 -0
- package/src/interact/assertions/state-assertion-context.ts +48 -0
- package/src/interact/assertions/state-assertion.ts +64 -0
- package/src/interact/assertions/state-comparison.ts +168 -0
- package/src/interact/assertions/visibility-assertion.ts +41 -0
- package/src/interact/conditional-action.ts +18 -12
- package/src/interact/index.ts +89 -3494
- package/src/interact/input-service.ts +11 -0
- package/src/interact/interaction-trace.ts +36 -0
- package/src/interact/model.ts +109 -0
- package/src/interact/observation.ts +7 -0
- package/src/interact/targeting/candidate-ancestor.ts +49 -0
- package/src/interact/targeting/candidate-result.ts +24 -0
- package/src/interact/targeting/candidate-scoring.ts +42 -0
- package/src/interact/targeting/element-helpers.ts +243 -0
- package/src/interact/targeting/find-element.ts +47 -0
- package/src/interact/targeting/nearby-control.ts +46 -0
- package/src/interact/targeting/resolved-elements.ts +20 -0
- package/src/interact/targeting/tap-target.ts +22 -0
- package/src/interact/targeting/target-scope.ts +121 -0
- package/src/interact/waits/legacy-log-wait.ts +30 -0
- package/src/interact/waits/legacy-wait-observations.ts +55 -0
- package/src/interact/waits/legacy-wait-state.ts +48 -0
- package/src/interact/waits/legacy-wait.ts +34 -0
- package/src/interact/waits/screen-wait.ts +37 -0
- package/src/interact/waits/ui-change-helpers.ts +184 -0
- package/src/interact/waits/ui-change-state.ts +26 -0
- package/src/interact/waits/ui-change-wait.ts +59 -0
- package/src/interact/waits/ui-wait-condition.ts +52 -0
- package/src/interact/waits/ui-wait-state.ts +55 -0
- package/src/interact/waits/ui-wait.ts +84 -0
- package/src/manage/android.ts +56 -50
- package/src/manage/index.ts +63 -93
- package/src/manage/ios/build-types.ts +14 -0
- package/src/manage/ios/build.ts +230 -0
- package/src/manage/ios/project-discovery.ts +26 -0
- package/src/manage/ios/scheme.ts +16 -0
- package/src/manage/ios.ts +67 -298
- package/src/observe/android/context.ts +1 -0
- package/src/observe/android/current-screen.ts +63 -0
- package/src/observe/android/fingerprint.ts +13 -0
- package/src/observe/android/log-stream.ts +127 -0
- package/src/observe/android/logs.ts +110 -0
- package/src/observe/android/screenshot.ts +79 -0
- package/src/observe/android/ui-tree.ts +113 -0
- package/src/observe/android.ts +17 -531
- package/src/observe/evidence.ts +122 -70
- package/src/observe/index.ts +24 -286
- package/src/observe/ios/context.ts +22 -0
- package/src/observe/ios/fingerprint.ts +12 -0
- package/src/observe/ios/log-stream.ts +103 -0
- package/src/observe/ios/logs.ts +179 -0
- package/src/observe/ios/screenshot.ts +26 -0
- package/src/observe/ios/ui-elements.ts +266 -0
- package/src/observe/ios/ui-tree.ts +143 -0
- package/src/observe/ios-trace/artifact.ts +55 -0
- package/src/observe/ios-trace/capabilities.ts +74 -0
- package/src/observe/ios-trace/native.ts +72 -0
- package/src/observe/ios-trace/notification.ts +30 -0
- package/src/observe/ios-trace/process.ts +82 -0
- package/src/observe/ios-trace/sessions.ts +204 -0
- package/src/observe/ios.ts +18 -749
- package/src/observe/logs/model.ts +11 -0
- package/src/{network → observe/network}/index.ts +2 -2
- package/src/observe/readiness-service.ts +6 -4
- package/src/observe/readiness.ts +42 -21
- package/src/observe/screenshot-encoding.ts +32 -0
- package/src/observe/snapshot-collection.ts +69 -0
- package/src/observe/snapshot-logs.ts +26 -0
- package/src/observe/snapshot-metadata.ts +51 -31
- package/src/observe/snapshot-semantic.ts +153 -0
- package/src/observe/system-trace-error.ts +3 -0
- package/src/observe/system-trace-router.ts +61 -0
- package/src/observe/system-trace.ts +28 -19
- package/src/runtime/action-results.ts +376 -0
- package/src/runtime/clock.ts +9 -0
- package/src/server/common.ts +9 -359
- package/src/server/definitions/assert.ts +164 -0
- package/src/server/definitions/diagnostic.ts +120 -0
- package/src/server/definitions/input.ts +276 -0
- package/src/server/definitions/manage.ts +196 -0
- package/src/server/definitions/observe.ts +151 -0
- package/src/server/definitions/target.ts +140 -0
- package/src/server/definitions/wait.ts +107 -0
- package/src/server/handlers/action-execution.ts +164 -0
- package/src/server/handlers/correlation.ts +20 -0
- package/src/server/handlers/debugger-handlers.ts +58 -0
- package/src/server/handlers/debugger-response.ts +7 -0
- package/src/server/handlers/diagnostic.ts +86 -0
- package/src/server/handlers/handler-types.ts +7 -0
- package/src/server/handlers/input.ts +173 -0
- package/src/server/handlers/interact.ts +135 -0
- package/src/server/handlers/journey-assertions.ts +39 -0
- package/src/server/handlers/journey.ts +132 -0
- package/src/server/handlers/manage.ts +141 -0
- package/src/server/handlers/observe.ts +116 -0
- package/src/server/handlers/payload.ts +36 -0
- package/src/server/handlers/tool-handlers.ts +170 -0
- package/src/server/schemas/condition-schema.ts +2 -0
- package/src/server/schemas/debugger-schema.ts +46 -0
- package/src/server/{journey-schema.ts → schemas/journey-schema.ts} +7 -22
- package/src/server/schemas/system-trace-schema.ts +23 -0
- package/src/server/tool-definitions.ts +67 -1143
- package/src/server-core.ts +2 -2
- package/src/server.ts +4 -4
- package/src/system/android-status.ts +124 -0
- package/src/system/android.ts +33 -21
- package/src/system/index.ts +21 -525
- package/src/system/ios-status.ts +147 -0
- package/src/system/ios.ts +14 -10
- package/src/system/model.ts +41 -0
- package/src/system/probes.ts +114 -0
- package/src/system/status-summary.ts +47 -0
- package/src/system/unsupported-status.ts +55 -0
- package/src/types.ts +16 -106
- package/src/utils/android/commands.ts +81 -0
- package/src/utils/android/gradle.ts +132 -0
- package/src/utils/android/logs.ts +103 -0
- package/src/utils/android/ui-tree.ts +239 -0
- package/src/utils/android/utils.ts +16 -556
- package/src/utils/cli/idb/check-idb.ts +4 -4
- package/src/utils/cli/idb/idb-helper.ts +6 -8
- package/src/utils/cli/idb/install-idb.ts +15 -9
- package/src/utils/cli/ios/preflight-ios.ts +39 -31
- package/src/utils/cli/ios/run-ios-ui-tree-tap.ts +4 -4
- package/src/utils/ios/utils.ts +60 -61
- package/src/utils/java.ts +62 -43
- package/src/utils/resolve-device.ts +9 -6
- package/src/utils/ui/index.ts +28 -28
- package/test/device/automated/debugger/coroutine-lifecycle.ts +27 -0
- package/test/device/automated/debugger/coroutine-support.ts +140 -0
- package/test/device/automated/debugger/coroutine.android.test.ts +169 -0
- package/test/device/automated/debugger/kotlin.android.test.ts +127 -0
- package/test/device/automated/debugger/runtime.android.test.ts +19 -3
- package/test/device/automated/debugger/runtime.ios.test.ts +221 -0
- package/test/device/automated/interact/interaction.android.test.ts +56 -0
- package/test/device/automated/observe/ios-system-trace.smoke.ts +119 -0
- package/test/device/automated/observe/system-trace.smoke.ts +1 -1
- package/test/device/manual/interact/modul8_selector_tap.manual.ts +1 -1
- package/test/fixtures/contracts/tool-definitions.json +3696 -0
- package/test/fixtures/coroutine-app/MainActivity.kt +100 -0
- package/test/fixtures/coroutine-app/README.md +48 -0
- package/test/fixtures/coroutine-app/build.ts +35 -0
- package/test/fixtures/debugger-app/MainActivity.java +21 -0
- package/test/fixtures/debugger-app/MainActivity.kt +32 -0
- package/test/fixtures/debugger-app/README.md +52 -0
- package/test/fixtures/debugger-app/build.ts +25 -15
- package/test/fixtures/ios-debugger-app/Main.swift +95 -0
- package/test/fixtures/ios-debugger-app/README.md +27 -0
- package/test/fixtures/ios-debugger-app/build.ts +21 -0
- package/test/fixtures/latency-journey-app/README.md +2 -0
- package/test/fixtures/readiness-app/README.md +2 -0
- package/test/helpers/ios-trace-shutdown.ts +29 -0
- package/test/unit/debugger/coroutine-lifecycle.test.ts +43 -0
- package/test/unit/debugger/diagnostic-sessions.test.ts +67 -0
- package/test/unit/debugger/diagnostics.test.ts +80 -0
- package/test/unit/debugger/guards.test.ts +4 -0
- package/test/unit/debugger/handle-guards.test.ts +88 -0
- package/test/unit/debugger/ios-preparation.test.ts +57 -0
- package/test/unit/debugger/ios.test.ts +104 -0
- package/test/unit/debugger/lldb-worker.py +166 -0
- package/test/unit/debugger/lldb-worker.test.ts +10 -0
- package/test/unit/debugger/preparation.test.ts +32 -0
- package/test/unit/debugger/protocol.test.ts +15 -0
- package/test/unit/debugger/sessions.test.ts +1 -1
- package/test/unit/interact/adjustment_service.test.ts +42 -0
- package/test/unit/interact/adjustment_verification.test.ts +35 -0
- package/test/unit/interact/clock_dependencies.test.ts +63 -0
- package/test/unit/interact/element_helpers.test.ts +19 -0
- package/test/unit/interact/legacy_wait_service.test.ts +17 -0
- package/test/unit/interact/state_comparison.test.ts +20 -0
- package/test/unit/interact/target_scope.test.ts +35 -0
- package/test/unit/network/get_network_activity.test.ts +1 -1
- package/test/unit/observe/ios-trace-native.test.ts +90 -0
- package/test/unit/observe/ios-trace-sessions.test.ts +131 -0
- package/test/unit/observe/readiness.test.ts +1 -1
- package/test/unit/observe/screenshot-encoding.test.ts +30 -0
- package/test/unit/observe/snapshot-logs.test.ts +14 -0
- package/test/unit/observe/system-trace-router.test.ts +49 -0
- package/test/unit/quality/agent-plugin-export.test.ts +38 -0
- package/test/unit/quality/architecture.test.ts +60 -0
- package/test/unit/quality/code-quality.test.ts +58 -0
- package/test/unit/quality/interaction_boundaries.test.ts +24 -0
- package/test/unit/server/conditional_actions.test.ts +1 -1
- package/test/unit/server/conditional_schema.test.ts +2 -2
- package/test/unit/server/debugger-response.test.ts +63 -0
- package/test/unit/server/system-trace.test.ts +12 -3
- package/test/unit/server/tool_definitions_parity.test.ts +8 -0
- package/dist/server/debugger-handlers.js +0 -38
- package/dist/server/system-trace-schema.js +0 -17
- package/dist/server/tool-handlers.js +0 -1000
- package/docs/ROADMAP.md +0 -646
- package/docs/benchmarks/ui-tree-performance/bridge-probe.json +0 -29
- package/docs/benchmarks/ui-tree-performance/initial-baseline.json +0 -59560
- package/docs/specs/baseline-spec-v0.md +0 -312
- package/docs/specs/mcp-tooling-spec-v1.md +0 -446
- package/src/server/debugger-handlers.ts +0 -29
- package/src/server/debugger-schema.ts +0 -37
- package/src/server/system-trace-schema.ts +0 -17
- package/src/server/tool-handlers.ts +0 -1008
- /package/dist/{server/condition-schema.js → contracts/condition.js} +0 -0
- /package/dist/server/{readiness-schema.js → schemas/readiness-schema.js} +0 -0
- /package/src/{server/condition-schema.ts → contracts/condition.ts} +0 -0
- /package/src/server/{readiness-schema.ts → schemas/readiness-schema.ts} +0 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { execFileSync } from 'node:child_process'
|
|
3
|
+
import { readFile, rm, readdir, writeFile, rename } from 'node:fs/promises'
|
|
4
|
+
import { createHash } from 'node:crypto'
|
|
5
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
|
|
6
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
|
|
7
|
+
import { buildIOSDebuggerFixture, iosDebuggerAppId, iosDebuggerSource } from '../../../fixtures/ios-debugger-app/build.js'
|
|
8
|
+
|
|
9
|
+
if (!process.env.MCP_IOS_DEBUGGER_DEVICE) {
|
|
10
|
+
console.log('UNRUN iOS debugger: select a booted simulator with MCP_IOS_DEBUGGER_DEVICE')
|
|
11
|
+
} else {
|
|
12
|
+
const deviceId = process.env.MCP_IOS_DEBUGGER_DEVICE
|
|
13
|
+
const sim = (...args: string[]) => execFileSync('xcrun', ['simctl', ...args], { encoding: 'utf8', timeout: 30000 }).trim()
|
|
14
|
+
console.log('Building disposable iOS fixture')
|
|
15
|
+
const fixture = buildIOSDebuggerFixture()
|
|
16
|
+
console.log('Fixture built')
|
|
17
|
+
const source = await readFile(iosDebuggerSource, 'utf8')
|
|
18
|
+
const line = (marker: string) => source.split('\n').findIndex(row => row.includes('// ' + marker)) + 1
|
|
19
|
+
const client = new Client({ name: 'ios-debugger-smoke', version: '1.0.0' })
|
|
20
|
+
const transport = new StdioClientTransport({ command: process.execPath, args: ['dist/server.js'], stderr: 'pipe' })
|
|
21
|
+
transport.stderr?.on('data', data => process.stderr.write(data))
|
|
22
|
+
const call = async (name: string, args: Record<string, unknown> = {}, signal?: AbortSignal) => {
|
|
23
|
+
const response = await client.callTool({ name, arguments: args }, undefined, { timeout: 45000, signal })
|
|
24
|
+
return JSON.parse((response.content as { type: string; text: string }[]).find(item => item.type === 'text')!.text)
|
|
25
|
+
}
|
|
26
|
+
const hash = createHash('sha256')
|
|
27
|
+
const fingerprint = async (directory: string): Promise<void> => {
|
|
28
|
+
for (const entry of (await readdir(directory, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
29
|
+
const file = directory + '/' + entry.name
|
|
30
|
+
if (entry.isDirectory()) await fingerprint(file)
|
|
31
|
+
else if (!file.includes('__pycache__')) hash.update(file).update(await readFile(file))
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
await fingerprint('dist'); await fingerprint('src/debugger/lldb')
|
|
35
|
+
const serverHash = hash.digest('hex')
|
|
36
|
+
const target = { platform: 'ios', deviceId, appId: iosDebuggerAppId }
|
|
37
|
+
let debugSessionId = '', cursor = 0
|
|
38
|
+
const session = () => ({ debugSessionId })
|
|
39
|
+
const attach = async () => {
|
|
40
|
+
const response = await call('attach_debugger', { ...target, timeoutMs: 30000 })
|
|
41
|
+
assert.equal(response.success, true, JSON.stringify(response))
|
|
42
|
+
assert.equal(response.device.platform, 'ios')
|
|
43
|
+
debugSessionId = response.debug_session_id; cursor = 0
|
|
44
|
+
}
|
|
45
|
+
const breakpoint = async (marker: string) => {
|
|
46
|
+
const response = await call('set_debugger_breakpoint', { ...session(), sourceFile: iosDebuggerSource, line: line(marker) })
|
|
47
|
+
assert.equal(response.success, true, JSON.stringify(response))
|
|
48
|
+
assert.equal(response.result.status, 'resolved', JSON.stringify(response))
|
|
49
|
+
assert.ok(response.result.locations.every((loc: any) => loc.line === line(marker)))
|
|
50
|
+
return response.result.breakpoint_id
|
|
51
|
+
}
|
|
52
|
+
const remove = (breakpointId: string) => call('remove_debugger_breakpoint', { ...session(), breakpointId })
|
|
53
|
+
const stop = async () => {
|
|
54
|
+
for (let i = 0; i < 15; i++) {
|
|
55
|
+
const response = await call('wait_for_debugger_event', { ...session(), afterSequence: cursor, timeoutMs: 1000 })
|
|
56
|
+
assert.equal(response.success, true, JSON.stringify(response)); cursor = response.next_cursor
|
|
57
|
+
const found = response.events.find((event: any) => event.event === 'stopped')
|
|
58
|
+
if (found) { console.log('STOP', JSON.stringify(found)); return found }
|
|
59
|
+
}
|
|
60
|
+
throw new Error('No iOS debugger stop observed')
|
|
61
|
+
}
|
|
62
|
+
const resume = (event: any) => call('resume_debugger', { ...session(), stopId: event.stop_id })
|
|
63
|
+
const locals = async (event: any) => {
|
|
64
|
+
const stack = await call('get_debugger_stack', { ...session(), stopId: event.stop_id, threadId: event.thread_id })
|
|
65
|
+
assert.equal(stack.success, true, JSON.stringify(stack))
|
|
66
|
+
const frame = stack.result.frames.find((f: any) => f.source_file === iosDebuggerSource)
|
|
67
|
+
assert.ok(frame, JSON.stringify(stack))
|
|
68
|
+
const result = await call('get_debugger_locals', { ...session(), stopId: event.stop_id, frameId: frame.frame_id })
|
|
69
|
+
assert.equal(result.success, true, JSON.stringify(result)); console.log('LOCALS', JSON.stringify(result.result))
|
|
70
|
+
return { result: result.result, frame }
|
|
71
|
+
}
|
|
72
|
+
const expectLocal = (result: any, name: string, value: string) => assert.equal(result.locals.find((v: any) => v.name === name)?.value, value, JSON.stringify(result))
|
|
73
|
+
let commandFile = ''
|
|
74
|
+
const trigger = async (command: string) => {
|
|
75
|
+
console.log('TRIGGER', command)
|
|
76
|
+
await writeFile(commandFile + '.next', command)
|
|
77
|
+
await rename(commandFile + '.next', commandFile)
|
|
78
|
+
}
|
|
79
|
+
const tap = async (text: string) => {
|
|
80
|
+
const tree = await call('get_ui_tree', { platform: 'ios', deviceId })
|
|
81
|
+
assert.ok(tree.elements.some((e: any) => e.text === text), JSON.stringify(tree))
|
|
82
|
+
const commands: Record<string, string> = { Calculate: 'calculate', 'Run async': 'async', 'Start cancellation': 'start-cancellation', 'Cancel task': 'cancel' }
|
|
83
|
+
// The installed idb HID backend is unavailable. A fixture-only main-loop
|
|
84
|
+
// command file invokes the same button actions without debugger evaluation.
|
|
85
|
+
await trigger(commands[text])
|
|
86
|
+
}
|
|
87
|
+
const expectUI = async (text: string) => {
|
|
88
|
+
for (let i = 0; i < 15; i++) {
|
|
89
|
+
const tree = await call('get_ui_tree', { platform: 'ios', deviceId })
|
|
90
|
+
if (tree.elements.some((e: any) => e.text === text)) return
|
|
91
|
+
await new Promise(resolve => setTimeout(resolve, 200))
|
|
92
|
+
}
|
|
93
|
+
throw new Error('Missing independent UI state: ' + text)
|
|
94
|
+
}
|
|
95
|
+
const detach = async () => {
|
|
96
|
+
const response = await call('detach_debugger', session())
|
|
97
|
+
assert.equal(response.cleanup.status, 'complete', JSON.stringify(response))
|
|
98
|
+
assert.deepEqual(response.cleanup.remaining_resources, [])
|
|
99
|
+
assert.equal((await call('detach_debugger', session())).cleanup.status, 'complete')
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
sim('install', deviceId, fixture.app)
|
|
103
|
+
sim('launch', deviceId, iosDebuggerAppId)
|
|
104
|
+
commandFile = sim('get_app_container', deviceId, iosDebuggerAppId, 'data') + '/Documents/debugger-command'
|
|
105
|
+
await client.connect(transport)
|
|
106
|
+
const shortProbe = await call('get_debugger_capabilities', { ...target, timeoutMs: 100 })
|
|
107
|
+
assert.equal(shortProbe.error?.code, 'DEBUGGER_TIMEOUT')
|
|
108
|
+
const absent = await call('get_debugger_capabilities', { ...target, appId: 'dev.mobiledebug.absentfixture' })
|
|
109
|
+
assert.equal(absent.supported, false); assert.ok(absent.reasons.includes('PROCESS_NOT_FOUND'))
|
|
110
|
+
const cap = await call('get_debugger_capabilities', target)
|
|
111
|
+
assert.equal(cap.supported, true, JSON.stringify(cap))
|
|
112
|
+
assert.equal(cap.capabilities.async_execution_inspection, true)
|
|
113
|
+
assert.equal(cap.capabilities.async_stepping, true)
|
|
114
|
+
const foreign = await call('attach_debugger', { ...target, processId: process.pid })
|
|
115
|
+
assert.equal(foreign.success, false); assert.equal(foreign.error.code, 'PROCESS_NOT_FOUND')
|
|
116
|
+
await attach()
|
|
117
|
+
const bp = await breakpoint('BREAK_CALCULATE')
|
|
118
|
+
const pending = await call('set_debugger_breakpoint', { ...session(), sourceFile: '/tmp/nonexistent-ios-debugger-source.swift', line: 1 })
|
|
119
|
+
assert.equal(pending.result.status, 'pending'); await remove(pending.result.breakpoint_id)
|
|
120
|
+
const noncode = await call('set_debugger_breakpoint', { ...session(), sourceFile: iosDebuggerSource, line: 9999 })
|
|
121
|
+
assert.equal(noncode.result.status, 'rejected'); await remove(noncode.result.breakpoint_id)
|
|
122
|
+
const storedHandle = await call('wait_for_ui', { platform: 'ios', deviceId, selector: { text: 'Calculate' }, condition: 'exists', timeout_ms: 10000 })
|
|
123
|
+
assert.equal(storedHandle.status, 'success', JSON.stringify(storedHandle))
|
|
124
|
+
assert.ok(storedHandle.element.elementId)
|
|
125
|
+
await tap('Calculate')
|
|
126
|
+
let event = await stop()
|
|
127
|
+
const originalStop = event
|
|
128
|
+
assert.equal(event.location.line, line('BREAK_CALCULATE'))
|
|
129
|
+
const inspected = await locals(event); expectLocal(inspected.result, 'known', '41')
|
|
130
|
+
const guarded = await call('tap', { platform: 'ios', deviceId, x: 10, y: 10 })
|
|
131
|
+
assert.equal(guarded.failure_code, 'DEBUGGER_PAUSED'); assert.equal(guarded.delivery_status, 'not_delivered')
|
|
132
|
+
const storedTap = await call('tap_element', { elementId: storedHandle.element.elementId, verificationMode: 'none', responseMode: 'compact' })
|
|
133
|
+
assert.equal(storedTap.failure_code, 'DEBUGGER_PAUSED', JSON.stringify(storedTap))
|
|
134
|
+
assert.equal(storedTap.delivery_status, 'not_delivered'); assert.equal(storedTap.dispatch_started, false)
|
|
135
|
+
assert.deepEqual(storedTap.debugger.device, { platform: 'ios', id: deviceId })
|
|
136
|
+
console.log('PASS paused stored iOS handle without platform/device hints rejected before dispatch')
|
|
137
|
+
assert.equal((await call('get_app_readiness', { platform: 'ios', deviceId })).readiness.state, 'debugger_paused')
|
|
138
|
+
await remove(bp)
|
|
139
|
+
for (const kind of ['into', 'over', 'out']) {
|
|
140
|
+
const response = await call('step_debugger', { ...session(), stopId: event.stop_id, threadId: event.thread_id, kind })
|
|
141
|
+
assert.equal(response.success, true, JSON.stringify(response)); event = await stop()
|
|
142
|
+
}
|
|
143
|
+
const stale = await call('get_debugger_locals', { ...session(), stopId: originalStop.stop_id, frameId: inspected.frame.frame_id })
|
|
144
|
+
assert.equal(stale.error.code, 'DEBUGGER_STALE_REFERENCE')
|
|
145
|
+
await resume(event)
|
|
146
|
+
await expectUI('Debugger result 42')
|
|
147
|
+
const controller = new AbortController()
|
|
148
|
+
const waiting = call('wait_for_debugger_event', { ...session(), afterSequence: cursor, timeoutMs: 30000 }, controller.signal)
|
|
149
|
+
setTimeout(() => controller.abort(), 100)
|
|
150
|
+
await assert.rejects(waiting)
|
|
151
|
+
assert.equal((await call('get_debugger_state', session())).state, 'running')
|
|
152
|
+
await detach()
|
|
153
|
+
|
|
154
|
+
await attach()
|
|
155
|
+
const before = await breakpoint('BEFORE_AWAIT'), after = await breakpoint('AFTER_AWAIT')
|
|
156
|
+
await tap('Run async')
|
|
157
|
+
event = await stop(); assert.equal(event.location.line, line('BEFORE_AWAIT'))
|
|
158
|
+
expectLocal((await locals(event)).result, 'known', '42')
|
|
159
|
+
await remove(before)
|
|
160
|
+
await resume(event)
|
|
161
|
+
event = await stop(); assert.equal(event.location.line, line('AFTER_AWAIT'))
|
|
162
|
+
expectLocal((await locals(event)).result, 'after', '43')
|
|
163
|
+
await remove(after); await resume(event)
|
|
164
|
+
await expectUI('Async result 44')
|
|
165
|
+
await detach()
|
|
166
|
+
|
|
167
|
+
// Boundary stepping has a distinct evidence result, independent of breakpoint coverage.
|
|
168
|
+
await attach()
|
|
169
|
+
const boundary = await breakpoint('BEFORE_AWAIT')
|
|
170
|
+
await tap('Run async'); event = await stop(); await remove(boundary)
|
|
171
|
+
assert.equal((await call('step_debugger', { ...session(), stopId: event.stop_id, threadId: event.thread_id, kind: 'over' })).success, true)
|
|
172
|
+
event = await stop()
|
|
173
|
+
assert.equal(event.location.source_file, iosDebuggerSource)
|
|
174
|
+
assert.equal(event.location.line, line('AFTER_AWAIT'), 'Advertised async stepping must follow the continuation')
|
|
175
|
+
console.log('ASYNC_BOUNDARY_STEP', JSON.stringify({ expected_line: line('AFTER_AWAIT'), observed: event.location }))
|
|
176
|
+
await detach(); await expectUI('Async result 44')
|
|
177
|
+
|
|
178
|
+
await attach()
|
|
179
|
+
const cancel = await breakpoint('CANCELLED')
|
|
180
|
+
await tap('Start cancellation'); await expectUI('Task running'); await tap('Cancel task')
|
|
181
|
+
event = await stop(); expectLocal((await locals(event)).result, 'cancelled', 'true')
|
|
182
|
+
await remove(cancel); await resume(event); await detach(); await expectUI('Task cancelled')
|
|
183
|
+
|
|
184
|
+
if (process.env.MCP_IOS_DEBUGGER_EXTENDED === '1') {
|
|
185
|
+
await attach(); await breakpoint('BREAK_CALCULATE'); await tap('Calculate'); await stop()
|
|
186
|
+
const deadline = Date.now() + 75000
|
|
187
|
+
let state: any
|
|
188
|
+
do { await new Promise(resolve => setTimeout(resolve, 1000)); state = await call('get_debugger_state', session()) } while (state.state === 'paused' && Date.now() < deadline)
|
|
189
|
+
assert.equal(state.reason, 'pause_limit'); assert.equal(state.cleanup.status, 'complete')
|
|
190
|
+
await expectUI('Debugger result 42')
|
|
191
|
+
}
|
|
192
|
+
await attach()
|
|
193
|
+
await trigger('exit')
|
|
194
|
+
let exited: any
|
|
195
|
+
for (let i = 0; i < 100; i++) {
|
|
196
|
+
exited = await call('get_debugger_state', session())
|
|
197
|
+
if (exited.state === 'disconnected' && exited.cleanup.status === 'complete') break
|
|
198
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
199
|
+
}
|
|
200
|
+
assert.equal(exited.state, 'disconnected'); assert.equal(exited.cleanup.status, 'complete')
|
|
201
|
+
sim('launch', deviceId, iosDebuggerAppId); await expectUI('Debugger ready')
|
|
202
|
+
// Clear the earlier calculate result before proving shutdown recovery.
|
|
203
|
+
await tap('Start cancellation'); await expectUI('Task running'); await tap('Cancel task'); await expectUI('Task cancelled')
|
|
204
|
+
// Closing the server while paused must release its owned suspension.
|
|
205
|
+
await attach(); await breakpoint('BREAK_CALCULATE'); await tap('Calculate'); await stop()
|
|
206
|
+
await client.close()
|
|
207
|
+
const verifier = new Client({ name: 'ios-cleanup-verifier', version: '1.0.0' })
|
|
208
|
+
await verifier.connect(new StdioClientTransport({ command: process.execPath, args: ['dist/server.js'], stderr: 'pipe' }))
|
|
209
|
+
try {
|
|
210
|
+
const response = await verifier.callTool({ name: 'get_ui_tree', arguments: { platform: 'ios', deviceId } })
|
|
211
|
+
const tree = JSON.parse((response.content as any[])[0].text)
|
|
212
|
+
assert.ok(tree.elements.some((e: any) => e.text === 'Debugger result 42'), JSON.stringify(tree))
|
|
213
|
+
} finally { await verifier.close() }
|
|
214
|
+
console.log('PASS iOS MCP debugger core/async/cancellation/cleanup', JSON.stringify({ deviceId, server_sha256: serverHash, source_sha256: createHash('sha256').update(source).digest('hex'), xcode: execFileSync('xcodebuild', ['-version'], { encoding: 'utf8' }).trim(), lldb: execFileSync('xcrun', ['lldb', '--version'], { encoding: 'utf8' }).trim() }))
|
|
215
|
+
} finally {
|
|
216
|
+
if (debugSessionId) { try { await call('detach_debugger', session()) } catch {} }
|
|
217
|
+
await client.close().catch(() => {})
|
|
218
|
+
sim('uninstall', deviceId, iosDebuggerAppId)
|
|
219
|
+
await rm(fixture.directory, { recursive: true, force: true })
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { execFileSync } from 'node:child_process'
|
|
3
|
+
import { rm } from 'node:fs/promises'
|
|
4
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
|
|
5
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
|
|
6
|
+
import { buildDebuggerFixture, debuggerAppId } from '../../../fixtures/debugger-app/build.js'
|
|
7
|
+
|
|
8
|
+
// Reuse the isolated fixture; the device runner executes fixture journeys sequentially.
|
|
9
|
+
const deviceId = process.env.MCP_INTERACTION_DEVICE ?? process.env.MCP_DEBUGGER_DEVICE
|
|
10
|
+
if (!deviceId) {
|
|
11
|
+
console.log('UNRUN Android interaction fixture: select an emulator with MCP_INTERACTION_DEVICE')
|
|
12
|
+
} else {
|
|
13
|
+
const fixture = buildDebuggerFixture()
|
|
14
|
+
const adb = (...args: string[]) => execFileSync(process.env.ADB_PATH || 'adb', ['-s', deviceId, ...args], { encoding: 'utf8', timeout: 30000 })
|
|
15
|
+
const client = new Client({ name: 'interaction-service-smoke', version: '1.0.0' })
|
|
16
|
+
const target = { platform: 'android', deviceId }
|
|
17
|
+
const call = async (name: string, args: Record<string, unknown>) => {
|
|
18
|
+
const response = await client.callTool({ name, arguments: args }, undefined, { timeout: 120000 })
|
|
19
|
+
const text = response.content.find(item => item.type === 'text')
|
|
20
|
+
assert(text?.type === 'text')
|
|
21
|
+
return JSON.parse(text.text)
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
await client.connect(new StdioClientTransport({ command: process.execPath, args: ['dist/server.js'], stderr: 'pipe' }))
|
|
25
|
+
adb('install', '-r', fixture.artifact)
|
|
26
|
+
const started = await call('start_app', { ...target, appId: debuggerAppId, verificationMode: 'none', responseMode: 'compact' })
|
|
27
|
+
assert.equal(started.success, true)
|
|
28
|
+
const waited = await call('wait_for_ui', { ...target, selector: { text: 'Calculate' }, condition: 'clickable', timeout_ms: 20000 })
|
|
29
|
+
assert.equal(waited.status, 'success', JSON.stringify(waited))
|
|
30
|
+
assert.ok(waited.element?.elementId)
|
|
31
|
+
const tapped = await call('tap_element', { ...target, elementId: waited.element.elementId, verificationMode: 'none', actionTimeoutMs: 30000 })
|
|
32
|
+
assert.equal(tapped.success, true, JSON.stringify(tapped))
|
|
33
|
+
const visible = await call('expect_element_visible', { ...target, selector: { text: 'Result 43' }, timeout_ms: 20000 })
|
|
34
|
+
assert.equal(visible.success, true, JSON.stringify(visible))
|
|
35
|
+
const journey = await call('run_journey', { ...target, steps: [
|
|
36
|
+
{ id: 'calculate-again', type: 'tap', selector: { text: 'Calculate' }, verificationMode: 'none' },
|
|
37
|
+
{ id: 'enabled-result', type: 'assert', timeoutMs: 30000, assertion: { kind: 'state_equals', selector: { text: 'Result 43' }, property: 'enabled', expected: true } },
|
|
38
|
+
{ id: 'visible-result', type: 'assert', timeoutMs: 20000, assertion: { kind: 'element_visible', selector: { text: 'Result 43' } } }
|
|
39
|
+
], defaults: { actionTimeoutMs: 30000 } })
|
|
40
|
+
assert.equal(journey.success, true, JSON.stringify(journey))
|
|
41
|
+
const adjusted = await call('adjust_control', { ...target, selector: { accessibility_id: 'Fixture level' },
|
|
42
|
+
property: 'value', targetValue: 75, tolerance: 5, maxAttempts: 5 })
|
|
43
|
+
assert.equal(adjusted.success, true, JSON.stringify(adjusted))
|
|
44
|
+
assert.equal(adjusted.converged, true, JSON.stringify(adjusted))
|
|
45
|
+
assert(Math.abs(adjusted.actual_state.value - 75) <= 5, JSON.stringify(adjusted))
|
|
46
|
+
const level = await call('expect_element_visible', { ...target, selector: { text: `${adjusted.actual_state.value} %` }, timeout_ms: 20000 })
|
|
47
|
+
assert.equal(level.success, true, JSON.stringify(level))
|
|
48
|
+
console.log(JSON.stringify({ status: 'PASS', server: client.getServerVersion(), device: deviceId,
|
|
49
|
+
api: adb('shell', 'getprop', 'ro.build.version.sdk').trim(), runtime: process.version,
|
|
50
|
+
journey: 'clickable wait, resolved-handle tap, visible assertion, selector journey and stable enabled-state assertion and labelled SeekBar convergence',
|
|
51
|
+
evidence: 'Result 43, enabled=true and adjusted slider label established by fresh hierarchy assertions', cleanup: 'Owned fixture removed in finally; no other app data or device settings changed' }))
|
|
52
|
+
} finally {
|
|
53
|
+
await client.close()
|
|
54
|
+
try { adb('uninstall', debuggerAppId) } finally { await rm(fixture.directory, { recursive: true, force: true }) }
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { verifyIosTraceShutdown } from '../../../helpers/ios-trace-shutdown.js'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { execFileSync } from 'node:child_process'
|
|
4
|
+
import { promises as fs } from 'node:fs'
|
|
5
|
+
import path from 'node:path'
|
|
6
|
+
import { createHash } from 'node:crypto'
|
|
7
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
|
|
8
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
|
|
9
|
+
import { buildReadinessFixture, readinessAppId } from '../../../fixtures/readiness-app/build.js'
|
|
10
|
+
|
|
11
|
+
const inputEnvironment = () => Object.fromEntries(['IDB_COMPANION','MCP_IDB_PATH','IDB_PATH','XCRUN_PATH'].flatMap(key=>process.env[key]?[[key,process.env[key]!]]:[]))
|
|
12
|
+
async function run() {
|
|
13
|
+
const deviceId=process.env.IOS_SYSTEM_TRACE_DEVICE
|
|
14
|
+
if(process.env.SKIP_DEVICE_TESTS==='1'||!deviceId){console.log('iOS system trace smoke: UNRUN (set IOS_SYSTEM_TRACE_DEVICE to an explicit simulator UDID)');return}
|
|
15
|
+
assert.equal(process.platform,'darwin')
|
|
16
|
+
const command=(args:string[])=>execFileSync('xcrun',args,{encoding:'utf8',timeout:120000,stdio:'pipe'})
|
|
17
|
+
const sims=JSON.parse(command(['simctl','list','devices','--json']))
|
|
18
|
+
const device=Object.values(sims.devices).flat().find((d:any)=>d.udid===deviceId) as any
|
|
19
|
+
assert.ok(device?.isAvailable,'Explicit available simulator required')
|
|
20
|
+
let booted=false,installed=false,fixture:ReturnType<typeof buildReadinessFixture>|undefined
|
|
21
|
+
const client=new Client({name:'ios-system-trace-smoke',version:'1.0.0'})
|
|
22
|
+
const transport=new StdioClientTransport({command:process.execPath,args:['dist/server.js'],stderr:'pipe',env:inputEnvironment()})
|
|
23
|
+
const owned=new Set<string>(),artifacts:string[]=[],checks:Record<string,unknown>={}
|
|
24
|
+
let journeyFailure: Error | undefined
|
|
25
|
+
const target={platform:'ios',deviceId},traceTarget={...target,appId:readinessAppId}
|
|
26
|
+
const call=async(name:string,args:Record<string,unknown>)=>{
|
|
27
|
+
const result=await client.callTool({name,arguments:args},undefined,{timeout:180000})
|
|
28
|
+
return JSON.parse((result.content as any[]).find(c=>c.type==='text').text)
|
|
29
|
+
}
|
|
30
|
+
const start=async(durationMs=90000)=>{
|
|
31
|
+
const r=await call('start_system_trace',{...traceTarget,durationMs})
|
|
32
|
+
if(r.trace_id&&r.cleanup?.status!=='complete')owned.add(r.trace_id)
|
|
33
|
+
assert.equal(r.state,'recording',JSON.stringify(r));assert.ok(r.recording_ready_at_ms>=r.capture_started_at_ms)
|
|
34
|
+
return r
|
|
35
|
+
}
|
|
36
|
+
const stop=async(id:string)=>{
|
|
37
|
+
const r=await call('stop_system_trace',{traceId:id})
|
|
38
|
+
if(r.artifact)artifacts.push(r.artifact.path)
|
|
39
|
+
assert.equal(r.state,'completed',JSON.stringify(r));assert.equal(r.cleanup.status,'complete')
|
|
40
|
+
assert.equal(r.artifact.validation.readable,true);owned.delete(id);return r
|
|
41
|
+
}
|
|
42
|
+
try{
|
|
43
|
+
if(device.state!=='Booted'){command(['simctl','boot',deviceId]);booted=true;command(['simctl','bootstatus',deviceId,'-b'])}
|
|
44
|
+
let preexisting=false
|
|
45
|
+
try{const existing=command(['simctl','get_app_container',deviceId,readinessAppId,'app']).trim();await fs.stat(existing);preexisting=true}catch{/* absent or stale simulator registration */}
|
|
46
|
+
assert.equal(preexisting,false,'Refuse to replace a pre-existing fixture')
|
|
47
|
+
fixture=buildReadinessFixture('ios');command(['simctl','install',deviceId,fixture.artifact]);installed=true
|
|
48
|
+
command(['simctl','launch',deviceId,readinessAppId])
|
|
49
|
+
await client.connect(transport)
|
|
50
|
+
const cap=await call('get_system_trace_capabilities',traceTarget)
|
|
51
|
+
assert.equal(cap.supported,true,JSON.stringify(cap));assert.equal(cap.prerequisites.permissions,'unknown')
|
|
52
|
+
const tree=await call('get_ui_tree',target)
|
|
53
|
+
assert.ok(tree.elements.some((e:any)=>e.text==='Ready'),'Fixture must be semantically visible')
|
|
54
|
+
assert.ok(tree.elements.some((e:any)=>e.text==='Show loader'),'Fixture button must be semantically visible')
|
|
55
|
+
const recording=await start()
|
|
56
|
+
const baselineJourney=await call('run_journey',{...target,systemTraceId:recording.trace_id,steps:[{id:'ready',type:'assert',timeoutMs:10000,assertion:{kind:'element_visible',selector:{text:'Ready'}}}]})
|
|
57
|
+
assert.equal(baselineJourney.success,true,JSON.stringify(baselineJourney));checks.linked_baseline='PASS; Ready asserted via built-server journey'
|
|
58
|
+
const journeySubmitted=Date.now()
|
|
59
|
+
const journey=await call('run_journey',{...target,systemTraceId:recording.trace_id,steps:[
|
|
60
|
+
{id:'show-loader',type:'tap',selector:{text:'Show loader'},verificationMode:'none'},
|
|
61
|
+
{id:'done',type:'assert',timeoutMs:30000,assertion:{kind:'element_visible',selector:{text:'Done'}}}
|
|
62
|
+
]})
|
|
63
|
+
if (!journey.success) journeyFailure = new Error(`Traced fixture input journey failed at ${journey.stopped_at_step_id}; ${journey.steps[0].result?.failure_code}`)
|
|
64
|
+
else assert.equal(journey.steps[1].status,'passed')
|
|
65
|
+
assert.equal(journey.evidence.trace_context.trace_id,recording.trace_id)
|
|
66
|
+
assert.equal(journey.steps[0].result.evidence.trace_context.trace_id,recording.trace_id)
|
|
67
|
+
assert.equal(journey.system_trace.coverage,'covered');assert.ok(recording.recording_ready_at_ms<=journeySubmitted)
|
|
68
|
+
const completed=await stop(recording.trace_id)
|
|
69
|
+
assert.equal(completed.device.id,deviceId);assert.equal(completed.app_id,readinessAppId)
|
|
70
|
+
assert.deepEqual(completed.correlation_ids,[baselineJourney.evidence.correlation_id,journey.evidence.correlation_id]);assert.ok(completed.artifact.validation.sample_rows>0)
|
|
71
|
+
checks.journey={status:journey.success?'PASS':'FAIL',trace_id:recording.trace_id,correlation_id:journey.evidence.correlation_id,
|
|
72
|
+
ready_at_ms:recording.recording_ready_at_ms,journey_submitted_at_ms:journeySubmitted,outcome:journey.success?'Done visible via journey UI-tree assertion':journeyFailure?.message,
|
|
73
|
+
sample_rows:completed.artifact.validation.sample_rows,artifact_bytes:completed.artifact.size_bytes,warnings:completed.warnings}
|
|
74
|
+
const stale=await call('run_journey',{...target,systemTraceId:recording.trace_id,steps:[{id:'must-not-tap',type:'tap',selector:{text:'Show loader'},verificationMode:'none'}]})
|
|
75
|
+
assert.equal(stale.error.code,'TRACE_NOT_FOUND')
|
|
76
|
+
const unchanged=await call('expect_element_visible',{...target,selector:{text:journey.success?'Done':'Ready'},timeoutMs:10000})
|
|
77
|
+
assert.ok(unchanged.success||unchanged.visible||unchanged.status==='success',JSON.stringify(unchanged))
|
|
78
|
+
checks.stale_id='PASS; rejected tap left prior fixture label visible'
|
|
79
|
+
const cancelled=await start(30000),directory=cancelled.cleanup.remaining_resources[0]
|
|
80
|
+
const cancel=await call('cancel_system_trace',{traceId:cancelled.trace_id})
|
|
81
|
+
assert.equal(cancel.state,'cancelled');assert.equal(cancel.cleanup.status,'complete');assert.equal(cancel.artifact,null);owned.delete(cancelled.trace_id)
|
|
82
|
+
await assert.rejects(()=>fs.stat(directory),{code:'ENOENT'});checks.cancel='PASS'
|
|
83
|
+
const short=await start(5000)
|
|
84
|
+
let expired=short
|
|
85
|
+
for(let i=0;i<90&&expired.state==='recording';i++){
|
|
86
|
+
await new Promise(r=>setTimeout(r,500));expired=await call('get_system_trace_status',{traceId:short.trace_id})
|
|
87
|
+
}
|
|
88
|
+
if(expired.artifact)artifacts.push(expired.artifact.path)
|
|
89
|
+
assert.equal(expired.state,'completed',JSON.stringify(expired));assert.equal(expired.completion_reason,'duration_limit');assert.equal(expired.cleanup.status,'complete');owned.delete(short.trace_id);checks.expiry='PASS'
|
|
90
|
+
const failedRecording=await start(60000)
|
|
91
|
+
const failedJourney=await call('run_journey',{...target,systemTraceId:failedRecording.trace_id,steps:[{id:'missing',type:'assert',timeoutMs:1000,assertion:{kind:'element_visible',selector:{text:'impossible-trace-fixture-label'}}}]})
|
|
92
|
+
assert.equal(failedJourney.success,false)
|
|
93
|
+
assert.equal((await call('get_system_trace_status',{traceId:failedRecording.trace_id})).state,'recording')
|
|
94
|
+
await stop(failedRecording.trace_id);checks.failed_journey='PASS; failure retained capture for explicit stop'
|
|
95
|
+
await verifyIosTraceShutdown(traceTarget,inputEnvironment());checks.shutdown='PASS; owned recording and partial directory removed'
|
|
96
|
+
const live=command(['simctl','spawn',deviceId,'launchctl','list'])
|
|
97
|
+
assert.ok(live.includes(`UIKitApplication:${readinessAppId}[`),'Recorder cleanup must leave the fixture app running')
|
|
98
|
+
checks.app_survived_cleanup='PASS'
|
|
99
|
+
const hash=createHash('sha256')
|
|
100
|
+
for(const file of ['dist/server.js','dist/observe/system-trace.js','dist/observe/system-trace-router.js','dist/observe/ios-trace/native.js','dist/observe/ios-trace/sessions.js','dist/observe/ios-trace/process.js'])hash.update(await fs.readFile(file))
|
|
101
|
+
checks.status=journeyFailure?'FAIL':'PASS';checks.build_sha256=hash.digest('hex');checks.runtime=process.version;checks.device=deviceId;checks.server=client.getServerVersion()
|
|
102
|
+
checks.physical_device='UNRUN; unsupported initial matrix';checks.native_failure_injection='UNRUN on real target; deterministic unit tests separate'
|
|
103
|
+
checks.input_toolchain=process.env.IDB_COMPANION?'Explicit test companion via IDB_COMPANION; see investigation.md':'Repository default idb companion'
|
|
104
|
+
if(journeyFailure) throw journeyFailure
|
|
105
|
+
}catch(error){checks.status='FAIL';checks.error=String(error);throw error}finally{
|
|
106
|
+
const errors:string[]=[]
|
|
107
|
+
for(const id of owned)try{const r=await call('cancel_system_trace',{traceId:id});if(r.artifact&&!artifacts.includes(r.artifact.path))artifacts.push(r.artifact.path);if(r.cleanup?.status!=='complete')errors.push(id)}catch{errors.push(id)}
|
|
108
|
+
await client.close()
|
|
109
|
+
for(const file of artifacts)await fs.rm(path.dirname(file),{recursive:true,force:true})
|
|
110
|
+
if(installed)command(['simctl','uninstall',deviceId,readinessAppId])
|
|
111
|
+
if(fixture)await fs.rm(fixture.directory,{recursive:true,force:true})
|
|
112
|
+
if(booted)command(['simctl','shutdown',deviceId])
|
|
113
|
+
checks.cleanup={trace_errors:errors,fixture_removed:installed,simulator_shutdown:booted,host_artifacts_removed:true}
|
|
114
|
+
console.log(JSON.stringify(checks))
|
|
115
|
+
if(process.env.IOS_SYSTEM_TRACE_EVIDENCE_PATH)await fs.writeFile(process.env.IOS_SYSTEM_TRACE_EVIDENCE_PATH,JSON.stringify(checks,null,2)+'\n')
|
|
116
|
+
assert.deepEqual(errors,[],'Trace cleanup failed')
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
run().catch(error=>{console.error(error);process.exitCode=1})
|
|
@@ -82,7 +82,7 @@ async function run() {
|
|
|
82
82
|
const absent = await execCmd(getAdbCmd(), ['-s', device.id, 'shell', 'test', '!', '-e', remote], { timeout: 10000 })
|
|
83
83
|
assert.equal(absent.exitCode, 0, `Shutdown must stop and remove its owned device capture: ${shutdownTrace.trace_id}; ${shutdownDiagnostics}; ${absent.stderr}`)
|
|
84
84
|
const buildHash = createHash('sha256')
|
|
85
|
-
for (const file of ['dist/server.js', 'dist/observe/system-trace.js', 'dist/observe/system-trace-android.js', 'dist/server/tool-handlers.js', 'dist/observe/evidence.js']) buildHash.update(await fs.readFile(file))
|
|
85
|
+
for (const file of ['dist/server.js', 'dist/observe/system-trace.js', 'dist/observe/system-trace-android.js', 'dist/server/handlers/tool-handlers.js', 'dist/observe/evidence.js']) buildHash.update(await fs.readFile(file))
|
|
86
86
|
const report = { status: 'PASS', build_sha256: buildHash.digest('hex'), runtime: process.version, server: client.getServerVersion(), device: device.id, osVersion: device.osVersion,
|
|
87
87
|
starting_state: 'System Settings fixture', journey: 'start Settings, assert baseline Settings-owned resource visible in UI tree; explicit trace association',
|
|
88
88
|
trace_id: start.trace_id, correlation_id: journey.evidence.correlation_id, phases: {
|
|
@@ -11,7 +11,7 @@ async function run() {
|
|
|
11
11
|
const transport = new StdioClientTransport({ command: process.execPath, args: ['dist/server.js'], stderr: 'pipe' })
|
|
12
12
|
const target = { platform: 'android', deviceId: 'emulator-5554' }
|
|
13
13
|
const report: any = { verified_on: new Date().toISOString(), device: target, package: 'com.ideamechanics.modul8.dev',
|
|
14
|
-
source: 'rebuilt local MCP stdio server', build_sha256: createHash('sha256').update(readFileSync('dist/server/tool-handlers.js')).digest('hex'),
|
|
14
|
+
source: 'rebuilt local MCP stdio server', build_sha256: createHash('sha256').update(readFileSync('dist/server/handlers/tool-handlers.js')).digest('hex'),
|
|
15
15
|
steps: [], status: 'UNRUN', cleanup: 'UNRUN' }
|
|
16
16
|
const call = async (name: string, args: Record<string, unknown>) => {
|
|
17
17
|
const response: any = await client.callTool({ name, arguments: { ...target, ...args } }, undefined, { timeout: 120000 })
|