mobile-debug-mcp 0.36.0 → 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/agent-plugin/.antigravity-plugin/plugin.json +1 -1
- 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/cursor/.cursor-plugin/plugin.json +2 -2
- package/agent-plugin/cursor/skills/mobile-debug-mcp/SKILL.md +10 -2
- package/agent-plugin/mcp.json +1 -1
- package/agent-plugin/mcp_config.json +1 -1
- package/agent-plugin/plugin.json +1 -1
- package/agent-plugin/skills/mobile-debug-mcp/SKILL.md +10 -2
- package/dist/contracts/device-readiness.js +1 -0
- package/dist/interact/waits/ui-change-wait.js +9 -2
- package/dist/interact/waits/ui-wait.js +7 -3
- package/dist/manage/index.js +3 -0
- package/dist/observe/device-readiness.js +116 -0
- package/dist/observe/evidence.js +31 -0
- package/dist/observe/ios-trace/sessions.js +5 -3
- package/dist/observe/readiness-service.js +21 -9
- package/dist/observe/readiness.js +30 -0
- package/dist/observe/screenshot-encoding.js +4 -0
- package/dist/observe/snapshot-collection.js +7 -2
- package/dist/server/definitions/manage.js +3 -2
- package/dist/server/handlers/action-execution.js +23 -14
- package/dist/server/handlers/compact-interaction.js +106 -0
- package/dist/server/handlers/compact-state.js +47 -0
- package/dist/server/handlers/input.js +2 -0
- package/dist/server/handlers/interact.js +2 -0
- package/dist/server/handlers/interaction-diagnostics.js +62 -0
- package/dist/server/handlers/journey.js +18 -20
- package/dist/server/handlers/observe.js +2 -1
- package/dist/server/handlers/tool-handlers.js +9 -5
- package/dist/server/schemas/interaction-diagnostics.js +9 -0
- package/dist/server/tool-definitions.js +9 -4
- package/dist/server-core.js +5 -2
- package/dist/server.js +1 -1
- package/dist/system/android.js +19 -15
- package/dist/system/index.js +2 -1
- package/dist/utils/android/utils.js +7 -16
- package/dist/utils/app-installation.js +77 -0
- package/dist/utils/benchmark-metrics.js +43 -0
- package/dist/utils/exec.js +4 -0
- package/dist/utils/ios/utils.js +4 -11
- package/dist/utils/resolve-device.js +3 -2
- package/dist/utils/timing.js +3 -3
- package/docs/CHANGELOG.md +25 -0
- package/docs/benchmarks/README.md +1 -0
- package/docs/benchmarks/end-to-end-latency/README.md +98 -0
- package/docs/benchmarks/end-to-end-latency/android-snapshot-pair.json +6532 -0
- package/docs/benchmarks/end-to-end-latency/android-snapshot-pair.md +30 -0
- package/docs/benchmarks/end-to-end-latency/baseline-attempt-1.json +15901 -0
- package/docs/benchmarks/end-to-end-latency/baseline-attempt-1.md +14 -0
- package/docs/benchmarks/end-to-end-latency/baseline.json +233843 -0
- package/docs/benchmarks/end-to-end-latency/baseline.md +21 -0
- package/docs/benchmarks/end-to-end-latency/investigation.md +128 -0
- package/docs/benchmarks/end-to-end-latency/ios-smoke.json +84 -0
- package/docs/benchmarks/end-to-end-latency/ios-smoke.md +9 -0
- package/docs/benchmarks/end-to-end-latency/smoke.json +19601 -0
- package/docs/benchmarks/end-to-end-latency/smoke.md +21 -0
- package/docs/specs/031-ios-system-traces/plan.md +28 -0
- package/docs/specs/033-device-readiness-blockers/android-evidence.json +437 -0
- package/docs/specs/033-device-readiness-blockers/android-physical-evidence.json +28 -0
- package/docs/specs/033-device-readiness-blockers/android-physical-power-evidence.json +29 -0
- package/docs/specs/033-device-readiness-blockers/intent.md +41 -0
- package/docs/specs/033-device-readiness-blockers/ios-investigation.md +55 -0
- package/docs/specs/033-device-readiness-blockers/ios-probe-evidence.json +24 -0
- package/docs/specs/033-device-readiness-blockers/ios-tool-evidence.json +73 -0
- package/docs/specs/033-device-readiness-blockers/plan.md +249 -0
- package/docs/specs/033-device-readiness-blockers/spec.md +134 -0
- package/docs/specs/034-app-installation-status/intent.md +41 -0
- package/docs/specs/034-app-installation-status/native-evidence.json +654 -0
- package/docs/specs/034-app-installation-status/plan.md +73 -0
- package/docs/specs/034-app-installation-status/spec.md +114 -0
- package/docs/specs/035-compact-interaction-results/baseline.md +45 -0
- package/docs/specs/035-compact-interaction-results/intent.md +38 -0
- package/docs/specs/035-compact-interaction-results/plan.md +43 -0
- package/docs/specs/035-compact-interaction-results/spec.md +118 -0
- package/docs/specs/035-compact-interaction-results/verification.md +111 -0
- package/docs/specs/036-end-to-end-interaction-latency/android-snapshot-amendment.md +60 -0
- package/docs/specs/036-end-to-end-interaction-latency/android-snapshot-plan.md +32 -0
- package/docs/specs/036-end-to-end-interaction-latency/intent.md +37 -0
- package/docs/specs/036-end-to-end-interaction-latency/plan.md +59 -0
- package/docs/specs/036-end-to-end-interaction-latency/spec.md +126 -0
- package/docs/specs/036-end-to-end-interaction-latency/verification.md +87 -0
- package/docs/tools/TOOLS.md +1 -0
- package/docs/tools/compact-interactions.md +56 -0
- package/docs/tools/correlated-evidence.md +6 -0
- package/docs/tools/interact.md +17 -8
- package/docs/tools/manage.md +10 -2
- package/docs/tools/observe.md +42 -2
- package/docs/tools/system.md +6 -0
- package/package.json +1 -1
- package/src/contracts/device-readiness.ts +8 -0
- package/src/interact/waits/ui-change-wait.ts +7 -2
- package/src/interact/waits/ui-wait.ts +6 -3
- package/src/manage/index.ts +2 -0
- package/src/observe/device-readiness.ts +96 -0
- package/src/observe/evidence.ts +30 -1
- package/src/observe/ios-trace/sessions.ts +4 -2
- package/src/observe/readiness-service.ts +21 -9
- package/src/observe/readiness.ts +33 -0
- package/src/observe/screenshot-encoding.ts +4 -0
- package/src/observe/snapshot-collection.ts +7 -2
- package/src/server/common.ts +2 -2
- package/src/server/definitions/manage.ts +3 -2
- package/src/server/handlers/action-execution.ts +22 -14
- package/src/server/handlers/compact-interaction.ts +91 -0
- package/src/server/handlers/compact-state.ts +43 -0
- package/src/server/handlers/input.ts +2 -0
- package/src/server/handlers/interact.ts +2 -0
- package/src/server/handlers/interaction-diagnostics.ts +50 -0
- package/src/server/handlers/journey.ts +15 -17
- package/src/server/handlers/observe.ts +2 -1
- package/src/server/handlers/payload.ts +2 -1
- package/src/server/handlers/tool-handlers.ts +9 -5
- package/src/server/schemas/interaction-diagnostics.ts +10 -0
- package/src/server/tool-definitions.ts +8 -4
- package/src/server-core.ts +5 -2
- package/src/server.ts +1 -1
- package/src/system/android.ts +18 -11
- package/src/system/index.ts +2 -1
- package/src/types.ts +2 -0
- package/src/utils/android/utils.ts +8 -10
- package/src/utils/app-installation.ts +73 -0
- package/src/utils/benchmark-metrics.ts +42 -0
- package/src/utils/exec.ts +5 -0
- package/src/utils/ios/utils.ts +4 -10
- package/src/utils/resolve-device.ts +4 -2
- package/src/utils/timing.ts +3 -2
- package/test/device/automated/debugger/runtime.android.test.ts +5 -0
- package/test/device/automated/debugger/runtime.ios.test.ts +7 -1
- package/test/device/automated/interact/compact-results.smoke.ts +58 -0
- package/test/device/automated/latency/end-to-end-environment.ts +61 -0
- package/test/device/automated/latency/end-to-end-report.ts +90 -0
- package/test/device/automated/latency/end-to-end-session.ts +98 -0
- package/test/device/automated/latency/end-to-end.ts +149 -0
- package/test/device/automated/latency/owned-transport.ts +81 -0
- package/test/device/automated/latency/snapshot-pair.ts +143 -0
- package/test/device/automated/latency-ios/benchmark.ts +126 -0
- package/test/device/automated/observe/correlated-evidence.smoke.ts +7 -5
- package/test/device/automated/observe/device-readiness-cleanup.ts +14 -0
- package/test/device/automated/observe/device-readiness.android.smoke.ts +78 -0
- package/test/device/automated/observe/ios-system-trace.smoke.ts +2 -2
- package/test/device/automated/observe/system-trace.smoke.ts +2 -2
- package/test/fixtures/contracts/tool-definitions.json +51 -15
- package/test/fixtures/latency-journey-app/build.ts +3 -3
- package/test/unit/debugger/handle-guards.test.ts +2 -1
- package/test/unit/interact/ui-change-readiness-interruption.test.ts +37 -0
- package/test/unit/observe/capture_debug_snapshot.test.ts +66 -12
- package/test/unit/observe/device-readiness-cleanup.test.ts +24 -0
- package/test/unit/observe/device-readiness.test.ts +97 -0
- package/test/unit/observe/ios-trace-sessions.test.ts +23 -0
- package/test/unit/observe/readiness-retained-blockers.test.ts +40 -0
- package/test/unit/server/compact-interaction.test.ts +87 -0
- package/test/unit/server/compact-journey-evidence.test.ts +40 -0
- package/test/unit/server/compact-review-regressions.test.ts +78 -0
- package/test/unit/server/conditional_actions.test.ts +7 -3
- package/test/unit/server/correlated-evidence.test.ts +18 -6
- package/test/unit/server/end-to-end-report.test.ts +46 -0
- package/test/unit/server/input-device-readiness.test.ts +55 -0
- package/test/unit/server/interaction-diagnostics.test.ts +45 -0
- package/test/unit/server/owned-benchmark-transport.test.ts +32 -0
- package/test/unit/server/readiness-retained-deadline.test.ts +32 -0
- package/test/unit/server/readiness.test.ts +24 -1
- package/test/unit/server/run_journey.test.ts +2 -1
- package/test/unit/server/selector_tap_target.test.ts +2 -1
- package/test/unit/server/system-trace.test.ts +6 -4
- package/test/unit/utils/app-installation.test.ts +78 -0
- package/test/unit/utils/benchmark-metrics.test.ts +36 -0
- package/test/unit/utils/resolve_device.test.ts +11 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withDeviceReadinessProbe } from '../../../src/observe/device-readiness.js'
|
|
1
2
|
import assert from 'node:assert/strict'
|
|
2
3
|
import { AjvJsonSchemaValidator } from '@modelcontextprotocol/sdk/validation/ajv'
|
|
3
4
|
import { handleToolCall, toolDefinitions } from '../../../src/server-core.js'
|
|
@@ -10,6 +11,7 @@ async function run() {
|
|
|
10
11
|
const originalTree = ToolsObserve.getUITreeHandler
|
|
11
12
|
const originalNative = AndroidObserve.prototype.getUITree
|
|
12
13
|
const device = { platform: 'android', id: 'fixture', model: 'Fixture', osVersion: '16', simulator: true }
|
|
14
|
+
let deviceState = 'unknown'
|
|
13
15
|
let reads = 0
|
|
14
16
|
let mode = 'stable'
|
|
15
17
|
let delayed = 0
|
|
@@ -43,12 +45,33 @@ async function run() {
|
|
|
43
45
|
queueMicrotask(() => { elapsed += Math.max(0, Number(delay) || 0); callback(...args) })
|
|
44
46
|
return {} as ReturnType<typeof setTimeout>
|
|
45
47
|
}) as typeof setTimeout
|
|
46
|
-
const call = async (name: string, args: any = {}) =>
|
|
48
|
+
const call = async (name: string, args: any = {}) => withDeviceReadinessProbe(async (_cmd, nativeArgs) => {
|
|
49
|
+
const power = 'mWakefulness=Awake\nmWakefulnessChanging=false'
|
|
50
|
+
const keyguard = `KeyguardServiceDelegate\n showing=${deviceState === 'locked'}\n mIsShowing=${deviceState === 'locked'}`
|
|
51
|
+
return { exitCode: 0, stdout: deviceState === 'unknown' ? '' : nativeArgs.includes('power') ? power : keyguard, stderr: '' }
|
|
52
|
+
}, async () => JSON.parse((await handleToolCall(name, { platform: 'android', deviceId: 'fixture', ...args })).content[0].text!))
|
|
47
53
|
try {
|
|
48
54
|
let result = await call('get_app_readiness', { stabilityMs: 100 })
|
|
49
55
|
assert.equal(result.readiness.state, 'settled')
|
|
50
56
|
assert.equal(result.success, true)
|
|
51
57
|
assert.equal(reads, 2)
|
|
58
|
+
deviceState = 'locked'; reads = 0
|
|
59
|
+
result = await call('get_app_readiness')
|
|
60
|
+
assert.deepEqual(result.device_readiness.blockers, ['DEVICE_LOCKED'])
|
|
61
|
+
assert.equal(result.success, true, 'an observed blocker is a successful observation')
|
|
62
|
+
assert.equal(reads, 0, 'known blocker avoids a slow UI read')
|
|
63
|
+
result = await call('wait_for_app_ready', { timeoutMs: 300, pollIntervalMs: 100 })
|
|
64
|
+
assert.equal(result.success, false)
|
|
65
|
+
assert.equal(result.failure_code, 'READINESS_TIMEOUT')
|
|
66
|
+
assert.equal(result.device_readiness.input_ready, false)
|
|
67
|
+
const lockedPredicate = await call('wait_for_ui', { selector: { text: 'Ready' }, requireReady: true, timeout_ms: 300, poll_interval_ms: 100 })
|
|
68
|
+
assert.equal(lockedPredicate.status, 'timeout')
|
|
69
|
+
assert.equal(lockedPredicate.device_readiness.lock_state, 'locked')
|
|
70
|
+
deviceState = 'unlocked'; reads = 0
|
|
71
|
+
result = await call('wait_for_app_ready', { stabilityMs: 100, timeoutMs: 1500, pollIntervalMs: 100 })
|
|
72
|
+
assert.equal(result.success, true, 'fresh unlocked evidence permits recovery')
|
|
73
|
+
assert.equal(result.device_readiness.input_ready, null, 'stable UI and unlocked flags do not prove input readiness')
|
|
74
|
+
deviceState = 'unknown'
|
|
52
75
|
mode = 'loading'; reads = 0
|
|
53
76
|
result = await call('get_app_readiness')
|
|
54
77
|
assert.equal(result.readiness.state, 'loading')
|
|
@@ -42,7 +42,7 @@ async function run() {
|
|
|
42
42
|
assert.strictEqual(result.steps[1].status, 'failed')
|
|
43
43
|
assert.deepStrictEqual(result.steps[2], {
|
|
44
44
|
id: 'not-run', type: 'wait', status: 'not_run', success: false,
|
|
45
|
-
reason: 'PREVIOUS_STEP_FAILED', result: null
|
|
45
|
+
reason: 'PREVIOUS_STEP_FAILED', result: null
|
|
46
46
|
})
|
|
47
47
|
|
|
48
48
|
let postActionReads = 0
|
|
@@ -62,6 +62,7 @@ async function run() {
|
|
|
62
62
|
})
|
|
63
63
|
const light = JSON.parse(lightResponse.content[0].text)
|
|
64
64
|
assert.strictEqual(light.success, true)
|
|
65
|
+
assert.ok(light.steps[0].result.device_readiness, 'journey action retains device evidence in compact mode')
|
|
65
66
|
assert.deepStrictEqual(Object.keys(light.timing), ['total_ms'])
|
|
66
67
|
assert.deepStrictEqual(light.steps[0].result.verification, { mode: 'light', status: 'observed' })
|
|
67
68
|
assert.strictEqual(light.steps[0].result.verification_diagnostic, undefined)
|
|
@@ -43,7 +43,8 @@ async function run() {
|
|
|
43
43
|
error = 'Failed to get UI tree: ACTION_TIMEOUT'
|
|
44
44
|
const timeout = await call()
|
|
45
45
|
assert.equal(timeout.failure_code, 'ACTION_TIMEOUT', 'Observation timeout must not claim that the label is missing')
|
|
46
|
-
|
|
46
|
+
const retained = JSON.parse((await handleToolCall('get_interaction_diagnostics', { sessionId: timeout.diagnostics.session_id, resultId: timeout.diagnostics.result_id })).content[0].text!)
|
|
47
|
+
assert.equal(retained.result.evidence.dispatch_started_at_ms, null)
|
|
47
48
|
assert.equal(taps.length, tapCount)
|
|
48
49
|
error = undefined
|
|
49
50
|
|
|
@@ -50,14 +50,16 @@ async function run() {
|
|
|
50
50
|
}
|
|
51
51
|
const result = parse(await handleToolCall('run_journey', journey))
|
|
52
52
|
assert.equal(result.success, true, JSON.stringify(result)); assert.equal(dispatched, 1)
|
|
53
|
-
assert.equal(result.evidence.
|
|
54
|
-
|
|
53
|
+
assert.equal(result.evidence.trace_id, id)
|
|
54
|
+
const retained = parse(await handleToolCall('get_interaction_diagnostics', { sessionId: result.diagnostics.session_id, resultId: result.diagnostics.result_id }))
|
|
55
|
+
assert.equal(retained.result.evidence.trace_context.supported, true)
|
|
55
56
|
assert.equal(result.evidence.correlation_id, correlation)
|
|
56
|
-
assert.equal(result.steps[0].result.evidence.
|
|
57
|
+
assert.equal(result.steps[0].result.evidence.trace_id, id)
|
|
57
58
|
assert.equal(result.steps[0].result.evidence.correlation_id, correlation)
|
|
58
59
|
assert.equal(result.system_trace.coverage, 'covered')
|
|
59
60
|
const plain = parse(await handleToolCall('run_journey', { ...journey, systemTraceId: undefined }))
|
|
60
|
-
|
|
61
|
+
const retainedPlain = parse(await handleToolCall('get_interaction_diagnostics', { sessionId: plain.diagnostics.session_id, resultId: plain.diagnostics.result_id }))
|
|
62
|
+
assert.deepEqual(retainedPlain.result.evidence.trace_context, { supported: false, trace_id: null, markers: [] })
|
|
61
63
|
const [linked, independent] = await Promise.all([
|
|
62
64
|
withEvidence({ journey: true }, async () => { bindSystemTrace(id); bindEvidenceDevice({ platform: 'android', id: 'trace-device' }); return withEvidence({ action: true }, async () => {}) }),
|
|
63
65
|
withEvidence({ journey: true }, async () => {})
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { mkdtemp, writeFile, rm } from 'node:fs/promises'
|
|
3
|
+
import os from 'node:os'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import { probeInstallation, installationFields } from '../../../src/utils/app-installation.js'
|
|
6
|
+
import { listAndroidDevices } from '../../../src/utils/android/utils.js'
|
|
7
|
+
import { checkAndroid } from '../../../src/system/android.js'
|
|
8
|
+
import { ToolsManage } from '../../../src/manage/index.js'
|
|
9
|
+
import { runWithBudget } from '../../../src/utils/operation-budget.js'
|
|
10
|
+
|
|
11
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), 'installation-test-'))
|
|
12
|
+
const binary = path.join(dir, 'adb')
|
|
13
|
+
const saved = { ...process.env }
|
|
14
|
+
try {
|
|
15
|
+
await writeFile(binary, `#!/usr/bin/env node
|
|
16
|
+
const args = process.argv.slice(2)
|
|
17
|
+
if (args[0] === '--version') console.log('Android Debug Bridge version 1.0.41')
|
|
18
|
+
else if (args[0] === 'devices') console.log(process.env.ONE_INSTALL_DEVICE ? 'List of devices attached\\na device' : 'List of devices attached\\na device\\nb device')
|
|
19
|
+
else if (args.includes('getprop')) console.log('14')
|
|
20
|
+
else if (args.includes('logcat')) console.log('log')
|
|
21
|
+
else if (args.includes('listapps')) {
|
|
22
|
+
if (args.at(-1) === 'failed') process.exit(1)
|
|
23
|
+
if (args.at(-1) === 'malformed') {console.log('not a plist'); process.exit(0)}
|
|
24
|
+
console.log('<?xml version="1.0"?><plist version="1.0"><dict><key>com.example.app</key><dict/></dict></plist>')
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
const id = args[1], app = args.at(-1)
|
|
28
|
+
if (id === 'b' && args.includes('path')) process.exit(1)
|
|
29
|
+
if (id === 'failed') process.exit(1)
|
|
30
|
+
if (id === 'malformed') console.log('Error: query failed')
|
|
31
|
+
else if (id === 'slow') setTimeout(() => console.log('package:/late.apk'), 1000)
|
|
32
|
+
else if (app === 'com.example.app' && id !== 'b') console.log('package:/data/app/base.apk')
|
|
33
|
+
}
|
|
34
|
+
`, { mode: 0o755 })
|
|
35
|
+
process.env.ADB_PATH = binary
|
|
36
|
+
delete process.env.MCP_TARGET_PACKAGE
|
|
37
|
+
delete process.env.MCP_TARGET_APP_ID
|
|
38
|
+
const check = (id: string, app: string | undefined = 'com.example.app') => probeInstallation(binary, id, app, 'android-current-user')
|
|
39
|
+
assert.equal((await check('a')).status, 'installed')
|
|
40
|
+
assert.equal((await check('b')).status, 'absent')
|
|
41
|
+
for (const id of ['failed', 'malformed']) {
|
|
42
|
+
const result = await check(id)
|
|
43
|
+
assert.equal(result.status, 'unknown')
|
|
44
|
+
assert(!JSON.stringify(installationFields(result)).includes('appInstalled'))
|
|
45
|
+
}
|
|
46
|
+
assert.equal((await probeInstallation(binary, 'a', undefined, 'android-current-user')).reason, 'APP_ID_NOT_PROVIDED')
|
|
47
|
+
assert.equal((await check('a', 'bad;input')).checkedAt, null)
|
|
48
|
+
assert.equal((await probeInstallation(binary, 'a', 'com.example.app', 'android-current-user', false)).reason, 'DEVICE_UNAVAILABLE')
|
|
49
|
+
assert.equal((await runWithBudget(50, () => check('slow'))).status, 'unknown')
|
|
50
|
+
if (process.platform === 'darwin') {
|
|
51
|
+
assert.equal((await probeInstallation(binary, 'a', 'com.example.app', 'ios-simulator')).status, 'installed')
|
|
52
|
+
assert.equal((await probeInstallation(binary, 'a', 'com.missing.app', 'ios-simulator')).status, 'absent')
|
|
53
|
+
assert.equal((await probeInstallation(binary, 'failed', 'com.example.app', 'ios-simulator')).status, 'unknown')
|
|
54
|
+
assert.equal((await probeInstallation(binary, 'malformed', 'com.example.app', 'ios-simulator')).status, 'unknown')
|
|
55
|
+
}
|
|
56
|
+
const inventory = await listAndroidDevices()
|
|
57
|
+
assert.equal(inventory.length, 2)
|
|
58
|
+
assert(inventory.every(device => device.appInstallation?.reason === 'APP_ID_NOT_PROVIDED' && !Object.hasOwn(device, 'appInstalled')))
|
|
59
|
+
const scoped = await listAndroidDevices('com.example.app')
|
|
60
|
+
assert.deepEqual(scoped.map(device => device.appInstalled), [true, false])
|
|
61
|
+
assert.deepEqual(scoped.map(device => device.appInstallation?.deviceId), ['a', 'b'])
|
|
62
|
+
process.env.MCP_TARGET_PACKAGE = 'com.example.app'
|
|
63
|
+
const system = await checkAndroid()
|
|
64
|
+
assert.equal(system.appInstalled, undefined)
|
|
65
|
+
assert.deepEqual(system.appInstallations.map(result => result.status), ['installed', 'absent'])
|
|
66
|
+
assert(system.issues.some(issue => issue.includes('device b')))
|
|
67
|
+
process.env.ONE_INSTALL_DEVICE = '1'
|
|
68
|
+
assert.equal((await checkAndroid()).appInstalled, true)
|
|
69
|
+
process.env.MCP_TARGET_PACKAGE = 'bad;input'
|
|
70
|
+
const invalid = await checkAndroid()
|
|
71
|
+
assert.equal(invalid.appInstalled, undefined)
|
|
72
|
+
assert.equal(invalid.appInstallations[0].checkedAt, null)
|
|
73
|
+
await assert.rejects(() => ToolsManage.listDevicesHandler({ appId: 'bad;input' }), /Invalid appId/)
|
|
74
|
+
console.log('app installation tests passed')
|
|
75
|
+
} finally {
|
|
76
|
+
process.env = saved
|
|
77
|
+
await rm(dir, { recursive: true, force: true })
|
|
78
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { spawnSync } from 'node:child_process'
|
|
3
|
+
|
|
4
|
+
const program = `
|
|
5
|
+
import assert from 'node:assert/strict';
|
|
6
|
+
import { withBenchmarkRequest, benchmarkStage } from './src/utils/benchmark-metrics.ts';
|
|
7
|
+
import { measure, withTiming } from './src/utils/timing.ts';
|
|
8
|
+
const values = {};
|
|
9
|
+
const result = await withBenchmarkRequest('outer', () => withTiming(values, async () => {
|
|
10
|
+
await measure('dispatch_ms', () => benchmarkStage('subprocess_ms', async () => 1));
|
|
11
|
+
return { unchanged: true };
|
|
12
|
+
}));
|
|
13
|
+
assert.deepEqual(result, { unchanged: true });
|
|
14
|
+
assert.ok(values.dispatch_ms >= 0);
|
|
15
|
+
await Promise.all(['first', 'second'].map(tool => withBenchmarkRequest(tool, () => benchmarkStage(tool, async () => tool))));
|
|
16
|
+
try { await withBenchmarkRequest('failure', async () => { throw Error('secret'); }); } catch {}
|
|
17
|
+
`
|
|
18
|
+
for (const enabled of ['0', '1']) {
|
|
19
|
+
const result = spawnSync(process.execPath, ['--import', 'tsx', '--input-type=module', '-e', program], {
|
|
20
|
+
cwd: process.cwd(), encoding: 'utf8', timeout: 15000,
|
|
21
|
+
env: { ...process.env, MOBILE_DEBUG_MCP_LATENCY_METRICS: enabled }
|
|
22
|
+
})
|
|
23
|
+
assert.equal(result.status, 0, result.stderr)
|
|
24
|
+
assert.equal(result.stdout, '')
|
|
25
|
+
if (enabled === '0') assert.equal(result.stderr, '')
|
|
26
|
+
else {
|
|
27
|
+
const events = result.stderr.trim().split('\n').map(line => JSON.parse(line))
|
|
28
|
+
assert.equal(events.length, 4)
|
|
29
|
+
assert.equal(new Set(events.map(event => event.request_id)).size, 4)
|
|
30
|
+
assert.deepEqual(events.find(event => event.tool === 'outer').spans.map((span: { stage: string }) => span.stage), ['subprocess_ms', 'dispatch_ms'])
|
|
31
|
+
for (const tool of ['first', 'second']) assert.deepEqual(events.find(event => event.tool === tool).spans.map((span: { stage: string }) => span.stage), [tool])
|
|
32
|
+
assert.equal(events.find(event => event.tool === 'failure').handler_completed, false)
|
|
33
|
+
assert.ok(!result.stderr.includes('secret'))
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
console.log('Opt-in latency metrics, failure and request isolation: PASS')
|
|
@@ -51,6 +51,17 @@ async function run() {
|
|
|
51
51
|
}
|
|
52
52
|
)
|
|
53
53
|
|
|
54
|
+
const base = { platform: 'android', model: 'Fixture', simulator: true }
|
|
55
|
+
_setDeviceListersForTests({ listAndroidDevices: async () => [
|
|
56
|
+
{ ...base, id: 'unknown', osVersion: '17', appInstalled: true, appInstallation: {
|
|
57
|
+
status: 'unknown', appId: 'com.example.app', deviceId: 'unknown', scope: 'android-current-user', checkedAt: null, reason: 'CHECK_FAILED'
|
|
58
|
+
} },
|
|
59
|
+
{ ...base, id: 'installed', osVersion: '16', appInstallation: {
|
|
60
|
+
status: 'installed', appId: 'com.example.app', deviceId: 'installed', scope: 'android-current-user', checkedAt: new Date(0).toISOString()
|
|
61
|
+
} }
|
|
62
|
+
] })
|
|
63
|
+
assert.equal((await resolveTargetDevice({ platform: 'android', appId: 'com.example.app' })).id, 'installed')
|
|
64
|
+
assert.equal((await resolveTargetDevice({ platform: 'android', appId: 'com.other.app' })).id, 'unknown')
|
|
54
65
|
console.log('resolve-device tests passed')
|
|
55
66
|
} finally {
|
|
56
67
|
_resetDeviceListersForTests()
|