mobile-debug-mcp 0.14.0 → 0.16.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.
Files changed (103) hide show
  1. package/dist/android/interact.js +2 -2
  2. package/dist/android/observe.js +13 -0
  3. package/dist/cli/ios/run-ios-smoke.js +2 -2
  4. package/dist/cli/ios/run-ios-ui-tree-tap.js +2 -2
  5. package/dist/interact/android.js +91 -0
  6. package/dist/interact/index.js +76 -0
  7. package/dist/interact/ios.js +120 -0
  8. package/dist/interact/shared/fingerprint.js +1 -0
  9. package/dist/interact/shared/scroll_to_element.js +1 -0
  10. package/dist/ios/interact.js +2 -2
  11. package/dist/ios/observe.js +12 -0
  12. package/dist/manage/android.js +162 -0
  13. package/dist/manage/index.js +364 -0
  14. package/dist/manage/ios.js +353 -0
  15. package/dist/observe/android.js +351 -0
  16. package/dist/observe/fingerprint.js +1 -0
  17. package/dist/observe/index.js +85 -0
  18. package/dist/observe/ios.js +320 -0
  19. package/dist/observe/test/device/logstream-real.js +34 -0
  20. package/dist/observe/test/device/run-screen-fingerprint.js +29 -0
  21. package/dist/observe/test/device/run-scroll-test-android.js +22 -0
  22. package/dist/observe/test/device/test-ui-tree.js +67 -0
  23. package/dist/observe/test/device/wait_for_element_real.js +69 -0
  24. package/dist/observe/test/unit/get_screen_fingerprint.test.js +54 -0
  25. package/dist/observe/test/unit/logparse.test.js +39 -0
  26. package/dist/observe/test/unit/logstream.test.js +41 -0
  27. package/dist/observe/test/unit/scroll_to_element.test.js +113 -0
  28. package/dist/observe/test/unit/wait_for_element_mock.js +92 -0
  29. package/dist/server.js +41 -5
  30. package/dist/shared/fingerprint.js +72 -0
  31. package/dist/shared/scroll_to_element.js +98 -0
  32. package/dist/tools/interact.js +2 -2
  33. package/dist/tools/manage.js +2 -2
  34. package/dist/tools/observe.js +45 -43
  35. package/dist/utils/android/utils.js +373 -0
  36. package/dist/utils/cli/idb/check-idb.js +84 -0
  37. package/dist/utils/cli/idb/idb-helper.js +91 -0
  38. package/dist/utils/cli/idb/install-idb.js +82 -0
  39. package/dist/utils/cli/ios/preflight-ios.js +155 -0
  40. package/dist/utils/cli/ios/run-ios-smoke.js +28 -0
  41. package/dist/utils/cli/ios/run-ios-ui-tree-tap.js +29 -0
  42. package/dist/utils/diagnostics.js +1 -1
  43. package/dist/utils/exec.js +34 -0
  44. package/dist/utils/ios/utils.js +301 -0
  45. package/dist/utils/resolve-device.js +2 -2
  46. package/dist/utils/ui/index.js +169 -0
  47. package/docs/CHANGELOG.md +8 -0
  48. package/docs/tools/interact.md +29 -0
  49. package/docs/tools/observe.md +24 -0
  50. package/package.json +1 -1
  51. package/src/{android/interact.ts → interact/android.ts} +3 -3
  52. package/src/{tools/interact.ts → interact/index.ts} +47 -3
  53. package/src/{ios/interact.ts → interact/ios.ts} +3 -3
  54. package/src/{android/manage.ts → manage/android.ts} +2 -2
  55. package/src/{tools/manage.ts → manage/index.ts} +7 -4
  56. package/src/{ios/manage.ts → manage/ios.ts} +1 -1
  57. package/src/{android/observe.ts → observe/android.ts} +14 -26
  58. package/src/observe/index.ts +92 -0
  59. package/src/{ios/observe.ts → observe/ios.ts} +17 -35
  60. package/src/server.ts +45 -6
  61. package/src/types.ts +1 -0
  62. package/src/{android → utils/android}/utils.ts +12 -79
  63. package/src/{cli → utils/cli}/ios/run-ios-smoke.ts +2 -2
  64. package/src/{cli → utils/cli}/ios/run-ios-ui-tree-tap.ts +3 -3
  65. package/src/utils/diagnostics.ts +1 -1
  66. package/src/utils/exec.ts +33 -0
  67. package/src/{ios → utils/ios}/utils.ts +2 -2
  68. package/src/utils/resolve-device.ts +2 -2
  69. package/src/{tools/scroll_to_element.ts → utils/ui/index.ts} +73 -2
  70. package/test/{device/interact → interact/device}/smoke-test.ts +3 -4
  71. package/test/interact/unit/wait_for_screen_change.test.ts +32 -0
  72. package/test/{device/manage → manage/device}/run-install-android.ts +1 -1
  73. package/test/{device/manage → manage/device}/run-install-ios.ts +1 -1
  74. package/test/{device/manage → manage/device}/run-install-kmp.ts +1 -1
  75. package/test/{unit/manage → manage/unit}/build.test.ts +1 -1
  76. package/test/{unit/manage → manage/unit}/build_and_install.test.ts +1 -1
  77. package/test/{unit/manage → manage/unit}/detection.test.ts +1 -1
  78. package/test/{unit/manage → manage/unit}/diagnostics.test.ts +2 -2
  79. package/test/{unit/manage → manage/unit}/install.test.ts +1 -1
  80. package/test/{unit/manage → manage/unit}/mcp_disable_autodetect.test.ts +1 -1
  81. package/test/{device/observe → observe/device}/logstream-real.ts +1 -1
  82. package/test/observe/device/run-screen-fingerprint.ts +36 -0
  83. package/test/{device/observe → observe/device}/run-scroll-test-android.ts +2 -2
  84. package/test/{device/observe → observe/device}/test-ui-tree.ts +2 -2
  85. package/test/{device/observe → observe/device}/wait_for_element_real.ts +2 -2
  86. package/test/observe/unit/get_screen_fingerprint.test.ts +69 -0
  87. package/test/{unit/observe → observe/unit}/logparse.test.ts +1 -1
  88. package/test/{unit/observe → observe/unit}/logstream.test.ts +1 -1
  89. package/test/{unit/observe → observe/unit}/scroll_to_element.test.ts +3 -3
  90. package/test/{unit/observe → observe/unit}/wait_for_element_mock.ts +2 -2
  91. package/test/unit/index.ts +13 -11
  92. package/src/tools/observe.ts +0 -82
  93. package/test/device/README.md +0 -49
  94. package/test/device/index.ts +0 -27
  95. package/test/device/utils/test-dist.ts +0 -41
  96. package/test/unit/utils/detect-java.test.ts +0 -22
  97. /package/src/{cli → utils/cli}/idb/check-idb.ts +0 -0
  98. /package/src/{cli → utils/cli}/idb/idb-helper.ts +0 -0
  99. /package/src/{cli → utils/cli}/idb/install-idb.ts +0 -0
  100. /package/src/{cli → utils/cli}/ios/preflight-ios.ts +0 -0
  101. /package/test/{device/interact → interact/device}/run-real-test.ts +0 -0
  102. /package/test/{device/manage → manage/device}/install.integration.ts +0 -0
  103. /package/test/{device/manage → manage/device}/run-build-install-ios.ts +0 -0
@@ -2,7 +2,7 @@ import assert from 'assert'
2
2
  import fs from 'fs/promises'
3
3
  import os from 'os'
4
4
  import path from 'path'
5
- import { detectProjectPlatform } from '../../../src/tools/manage.js'
5
+ import { detectProjectPlatform } from '../../../src/manage/index.js'
6
6
 
7
7
  export async function run() {
8
8
  const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'mcp-detect-'))
@@ -26,7 +26,7 @@ process.exit(1)
26
26
  // Prefix PATH so our fake adb is preferred but keep original PATH to allow /usr/bin/env node to work
27
27
  process.env.PATH = `${binDir}:${origPath}`
28
28
 
29
- const { AndroidManage } = await import('../../../src/android/manage.js')
29
+ const { AndroidManage } = await import('../../../src/manage/index.js')
30
30
 
31
31
  try {
32
32
  const { dir, file: apk } = await makeTempFile('.apk')
@@ -70,7 +70,7 @@ process.exit(1)
70
70
  process.env.PATH = `${binDir2}:${origPath2}`
71
71
 
72
72
  try {
73
- const { iOSManage } = await import('../../../src/ios/manage.js')
73
+ const { iOSManage } = await import('../../../src/manage/index.js')
74
74
  const im = new iOSManage()
75
75
  const res2 = await im.startApp('com.example.myapp')
76
76
  console.log('ios diag res', res2)
@@ -36,7 +36,7 @@ exit 0
36
36
 
37
37
  // Import the module under test after PATH/ADB_PATH is adjusted
38
38
  console.log('DEBUG install.test ADB_PATH=', process.env.ADB_PATH, 'PATH starts with=', process.env.PATH?.split(':')[0])
39
- const { AndroidManage } = await import('../../../src/android/manage.js?test=install')
39
+ const { AndroidManage } = await import('../../../src/manage/index.js?test=install')
40
40
 
41
41
  try {
42
42
  // Test: install with .apk file should call adb install
@@ -15,7 +15,7 @@ export async function run() {
15
15
  const orig = process.env.MCP_DISABLE_AUTODETECT
16
16
  process.env.MCP_DISABLE_AUTODETECT = '1'
17
17
 
18
- const { ToolsManage } = await import('../../../src/tools/manage.js')
18
+ const { ToolsManage } = await import('../../../src/manage/index.js')
19
19
 
20
20
  try {
21
21
  // platform and projectType are now mandatory; calling without them should return a missing-params error
@@ -1,4 +1,4 @@
1
- import { AndroidObserve } from '../../src/android/observe.js'
1
+ import { AndroidObserve } from '../../src/observe/index.js'
2
2
 
3
3
  async function sleep(ms: number) { return new Promise(r => setTimeout(r, ms)) }
4
4
 
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Device E2E: get_screen_fingerprint
4
+ * Usage: RUN_DEVICE_TESTS=true npx tsx run-screen-fingerprint.ts [android|ios] [deviceId]
5
+ */
6
+
7
+ import { AndroidObserve } from '../../../src/observe/index.js'
8
+ import { iOSObserve } from '../../../src/observe/index.js'
9
+
10
+ async function main() {
11
+ const args = process.argv.slice(2)
12
+ const platform = (args[0] || 'android').toLowerCase()
13
+ const deviceId = args[1]
14
+
15
+ console.log(`Running screen fingerprint test for ${platform}${deviceId ? ` on ${deviceId}` : ''}`)
16
+
17
+ try {
18
+ const obs = platform === 'ios' ? new iOSObserve() : new AndroidObserve()
19
+ const id = platform === 'ios' ? (deviceId || 'booted') : deviceId
20
+ const res = await (obs as any).getScreenFingerprint(id)
21
+
22
+ if (res.error || !res.fingerprint) {
23
+ console.error('❌ Failed to compute fingerprint:', res.error)
24
+ process.exit(1)
25
+ }
26
+
27
+ console.log('Fingerprint:', res.fingerprint)
28
+ console.log('Activity:', res.activity || '<n/a>')
29
+ process.exit(0)
30
+ } catch (err) {
31
+ console.error('❌ Test failed:', err instanceof Error ? err.message : String(err))
32
+ process.exit(1)
33
+ }
34
+ }
35
+
36
+ main()
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { AndroidInteract } from '../../../dist/android/interact.js'
2
+ import { AndroidInteract } from '../../../dist/interact/index.js'
3
3
 
4
4
 
5
5
  // Usage: tsx test/device/observe/run-scroll-test-android.ts <deviceId> <appId> <selectorText>
@@ -15,7 +15,7 @@ async function main() {
15
15
 
16
16
  console.log('Running scroll_to_element for selector:', SELECTOR)
17
17
  // Use ToolsInteract from dist to call the handler
18
- const ToolsInteract = (await import('../../../dist/tools/interact.js')).ToolsInteract
18
+ const ToolsInteract = (await import('../../../dist/interact/index.js')).ToolsInteract
19
19
 
20
20
  const res = await (ToolsInteract as any).scrollToElementHandler({ platform: 'android', selector: { text: SELECTOR }, direction: 'down', maxScrolls: 10, scrollAmount: 0.7, deviceId: DEVICE_ID })
21
21
  console.log('Result:', JSON.stringify(res, null, 2))
@@ -9,8 +9,8 @@
9
9
  * npx tsx test-ui-tree.ts ios booted
10
10
  */
11
11
 
12
- import { AndroidObserve } from '../../src/android/observe.js';
13
- import { iOSObserve } from '../../src/ios/observe.js';
12
+ import { AndroidObserve } from '../../src/observe/index.js';
13
+ import { iOSObserve } from '../../src/observe/index.js';
14
14
 
15
15
  async function main() {
16
16
  const args = process.argv.slice(2);
@@ -1,5 +1,5 @@
1
- import { AndroidInteract } from "../../src/android/interact.js";
2
- import { AndroidObserve } from "../../src/android/observe.js";
1
+ import { AndroidInteract } from "../../src/interact/index.js";
2
+ import { AndroidObserve } from "../../src/observe/index.js";
3
3
 
4
4
  // Usage: npx tsx test/wait_for_element_real.ts <deviceId> <appId>
5
5
  const args = process.argv.slice(2);
@@ -0,0 +1,69 @@
1
+ import { AndroidObserve } from '../../../src/observe/index.js'
2
+
3
+ async function run() {
4
+ console.log('Starting get_screen_fingerprint unit tests...')
5
+
6
+ const origGet = (AndroidObserve as any).prototype.getUITree
7
+ const origCurrent = (AndroidObserve as any).prototype.getCurrentScreen
8
+
9
+ // Test 1: stable identical screens produce same fingerprint
10
+ ;(AndroidObserve as any).prototype.getUITree = async function() {
11
+ return {
12
+ device: { platform: 'android', id: 'mock', osVersion: '12', model: 'Pixel', simulator: true },
13
+ screen: '',
14
+ resolution: { width: 1080, height: 1920 },
15
+ elements: [
16
+ { text: 'Title', type: 'TextView', contentDescription: null, clickable: false, enabled: true, visible: true, bounds: [0,0,1080,100], resourceId: 'id/title' },
17
+ { text: 'Sign in', type: 'Button', contentDescription: null, clickable: true, enabled: true, visible: true, bounds: [0,200,200,260], resourceId: 'id/signin' }
18
+ ]
19
+ }
20
+ }
21
+
22
+ ;(AndroidObserve as any).prototype.getCurrentScreen = async function() {
23
+ return { device: { platform: 'android', id: 'mock', osVersion: '12', model: 'Pixel', simulator: true }, package: 'com.example', activity: 'com.example.MainActivity', shortActivity: 'MainActivity' }
24
+ }
25
+
26
+ const ai = new AndroidObserve()
27
+ const a = await ai.getScreenFingerprint('mock')
28
+ const b = await ai.getScreenFingerprint('mock')
29
+ console.log('Test 1:', a.fingerprint === b.fingerprint ? 'PASS' : 'FAIL')
30
+
31
+ // Test 2: change in UI text changes fingerprint
32
+ ;(AndroidObserve as any).prototype.getUITree = async function() {
33
+ return {
34
+ device: { platform: 'android', id: 'mock', osVersion: '12', model: 'Pixel', simulator: true },
35
+ screen: '',
36
+ resolution: { width: 1080, height: 1920 },
37
+ elements: [
38
+ { text: 'Title', type: 'TextView', contentDescription: null, clickable: false, enabled: true, visible: true, bounds: [0,0,1080,100], resourceId: 'id/title' },
39
+ { text: 'Profile', type: 'Button', contentDescription: null, clickable: true, enabled: true, visible: true, bounds: [0,200,200,260], resourceId: 'id/signin' }
40
+ ]
41
+ }
42
+ }
43
+
44
+ const c = await ai.getScreenFingerprint('mock')
45
+ console.log('Test 2:', a.fingerprint !== c.fingerprint ? 'PASS' : 'FAIL')
46
+
47
+ // Test 3: dynamic text ignored (timestamp) should not change fingerprint
48
+ ;(AndroidObserve as any).prototype.getUITree = async function() {
49
+ return {
50
+ device: { platform: 'android', id: 'mock', osVersion: '12', model: 'Pixel', simulator: true },
51
+ screen: '',
52
+ resolution: { width: 1080, height: 1920 },
53
+ elements: [
54
+ { text: 'Title', type: 'TextView', contentDescription: null, clickable: false, enabled: true, visible: true, bounds: [0,0,1080,100], resourceId: 'id/title' },
55
+ { text: 'Sign in', type: 'Button', contentDescription: null, clickable: true, enabled: true, visible: true, bounds: [0,200,200,260], resourceId: 'id/signin' },
56
+ { text: '12:34', type: 'TextView', contentDescription: null, clickable: false, enabled: true, visible: true, bounds: [900,10,1080,40], resourceId: null }
57
+ ]
58
+ }
59
+ }
60
+
61
+ const d = await ai.getScreenFingerprint('mock')
62
+ console.log('Test 3:', a.fingerprint === d.fingerprint ? 'PASS' : 'FAIL')
63
+
64
+ // Restore
65
+ ;(AndroidObserve as any).prototype.getUITree = origGet
66
+ ;(AndroidObserve as any).prototype.getCurrentScreen = origCurrent
67
+ }
68
+
69
+ run().catch(console.error)
@@ -1,4 +1,4 @@
1
- import { parseLogLine } from '../../../src/android/utils.js'
1
+ import { parseLogLine } from '../../../src/utils/android/utils.js'
2
2
 
3
3
  function assert(cond: boolean, msg?: string) { if (!cond) throw new Error(msg || 'Assertion failed') }
4
4
 
@@ -1,7 +1,7 @@
1
1
  import { promises as fs } from 'fs'
2
2
  import os from 'os'
3
3
  import path from 'path'
4
- import { AndroidObserve } from '../../../src/android/observe.js'
4
+ import { AndroidObserve } from '../../../src/observe/index.js'
5
5
 
6
6
  async function run() {
7
7
  const tmp = os.tmpdir()
@@ -1,5 +1,5 @@
1
- import { ToolsInteract } from '../../../src/tools/interact.js'
2
- import { ToolsObserve } from '../../../src/tools/observe.js'
1
+ import { ToolsInteract } from '../../../src/interact/index.js'
2
+ import { ToolsObserve } from '../../../src/observe/index.js'
3
3
 
4
4
  const origGet = (ToolsObserve as any).getUITreeHandler
5
5
  const origSwipe = (ToolsInteract as any).swipeHandler
@@ -85,7 +85,7 @@ async function runTests() {
85
85
 
86
86
  // Test 4: Offscreen element scrolls into view
87
87
  console.log('\nTest 4: Offscreen element scrolls into view')
88
- const ai = new (await import('../../../src/android/interact.js')).AndroidInteract()
88
+ const ai = new (await import('../../../src/interact/index.js')).AndroidInteract()
89
89
  const origObserveGet = ai['observe'].getUITree
90
90
  const origAiSwipe = ai.swipe
91
91
  let swiped = false
@@ -1,5 +1,5 @@
1
- import { AndroidInteract } from '../../../src/android/interact.js';
2
- import { AndroidObserve } from '../../../src/android/observe.js';
1
+ import { AndroidInteract } from '../../../src/interact/index.js';
2
+ import { AndroidObserve } from '../../../src/observe/index.js';
3
3
 
4
4
  const originalGetUITree = (AndroidObserve as any).prototype.getUITree;
5
5
 
@@ -1,13 +1,15 @@
1
- // Aggregator entrypoint for unit tests
2
- import './utils/detect-java.test.ts'
3
- import './observe/logparse.test.ts'
4
- import './observe/logstream.test.ts'
5
- import './observe/wait_for_element_mock.ts'
6
- import './manage/install.test.ts'
7
- import './manage/build.test.ts'
8
- import './manage/build_and_install.test.ts'
9
- import './manage/diagnostics.test.ts'
10
- import './manage/detection.test.ts'
11
- import './manage/mcp_disable_autodetect.test.ts'
1
+ // Aggregator entrypoint for unit tests (updated to new test layout)
2
+ import '../observe/unit/logparse.test.ts'
3
+ import '../observe/unit/logstream.test.ts'
4
+ import '../observe/unit/wait_for_element_mock.ts'
5
+ import '../observe/unit/get_screen_fingerprint.test.ts'
6
+
7
+ import '../manage/unit/install.test.ts'
8
+ import '../manage/unit/build.test.ts'
9
+ import '../manage/unit/build_and_install.test.ts'
10
+ import '../manage/unit/diagnostics.test.ts'
11
+ import '../manage/unit/detection.test.ts'
12
+ import '../manage/unit/mcp_disable_autodetect.test.ts'
13
+ import '../interact/unit/wait_for_screen_change.test.ts'
12
14
 
13
15
  console.log('Unit tests loaded.')
@@ -1,82 +0,0 @@
1
- import { resolveTargetDevice } from '../utils/resolve-device.js'
2
- import { AndroidObserve } from '../android/observe.js'
3
- import { iOSObserve } from '../ios/observe.js'
4
-
5
- export class ToolsObserve {
6
- static async getUITreeHandler({ platform, deviceId }: { platform: 'android' | 'ios', deviceId?: string }) {
7
- if (platform === 'android') {
8
- const resolved = await resolveTargetDevice({ platform: 'android', deviceId })
9
- return await new AndroidObserve().getUITree(resolved.id)
10
- } else {
11
- const resolved = await resolveTargetDevice({ platform: 'ios', deviceId })
12
- return await new iOSObserve().getUITree(resolved.id)
13
- }
14
- }
15
-
16
- static async getCurrentScreenHandler({ deviceId }: { deviceId?: string }) {
17
- const resolved = await resolveTargetDevice({ platform: 'android', deviceId })
18
- return await new AndroidObserve().getCurrentScreen(resolved.id)
19
- }
20
-
21
- static async getLogsHandler({ platform, appId, deviceId, lines }: { platform: 'android' | 'ios', appId?: string, deviceId?: string, lines?: number }) {
22
- if (platform === 'android') {
23
- const resolved = await resolveTargetDevice({ platform: 'android', appId, deviceId })
24
- const response = await new AndroidObserve().getLogs(appId, lines ?? 200, resolved.id)
25
- const logs = Array.isArray(response.logs) ? response.logs : []
26
- const crashLines = logs.filter(line => line.includes('FATAL EXCEPTION'))
27
- return { device: response.device, logs, crashLines }
28
- } else {
29
- const resolved = await resolveTargetDevice({ platform: 'ios', appId, deviceId })
30
- const resp = await new iOSObserve().getLogs(appId, resolved.id)
31
- const logs = Array.isArray(resp.logs) ? resp.logs : []
32
- const crashLines = logs.filter(l => l.includes('FATAL EXCEPTION'))
33
- return { device: resp.device, logs, crashLines }
34
- }
35
- }
36
-
37
- static async startLogStreamHandler({ platform, packageName, level, sessionId, deviceId }: { platform?: 'android' | 'ios', packageName: string, level?: 'error' | 'warn' | 'info' | 'debug', sessionId?: string, deviceId?: string }) {
38
- const effectivePlatform = platform || 'android'
39
- const sid = sessionId || 'default'
40
- if (effectivePlatform === 'android') {
41
- const resolved = await resolveTargetDevice({ platform: 'android', appId: packageName, deviceId })
42
- // Delegate to AndroidObserve's log stream methods
43
- return await new AndroidObserve().startLogStream(packageName, level || 'error', resolved.id, sid)
44
- } else {
45
- const resolved = await resolveTargetDevice({ platform: 'ios', appId: packageName, deviceId })
46
- // Delegate to iOSObserve for starting log streams
47
- return await new iOSObserve().startLogStream(packageName, resolved.id, sid)
48
- }
49
- }
50
-
51
- static async readLogStreamHandler({ platform, sessionId, limit, since }: { platform?: 'android' | 'ios', sessionId?: string, limit?: number, since?: string }) {
52
- const effectivePlatform = platform || 'android'
53
- const sid = sessionId || 'default'
54
- if (effectivePlatform === 'android') {
55
- return await new AndroidObserve().readLogStream(sid, limit ?? 100, since)
56
- } else {
57
- return await new iOSObserve().readLogStream(sid, limit ?? 100, since)
58
- }
59
- }
60
-
61
- static async stopLogStreamHandler({ platform, sessionId }: { platform?: 'android' | 'ios', sessionId?: string }) {
62
- const effectivePlatform = platform || 'android'
63
- const sid = sessionId || 'default'
64
- if (effectivePlatform === 'android') {
65
- return await new AndroidObserve().stopLogStream(sid)
66
- } else {
67
- return await new iOSObserve().stopLogStream(sid)
68
- }
69
- }
70
-
71
- static async captureScreenshotHandler({ platform, deviceId }: { platform?: 'android' | 'ios', deviceId?: string }) {
72
- const effectivePlatform = platform || 'android'
73
- if (effectivePlatform === 'android') {
74
- const resolved = await resolveTargetDevice({ platform: 'android', deviceId })
75
- return await new AndroidObserve().captureScreen(resolved.id)
76
- } else {
77
- const resolved = await resolveTargetDevice({ platform: 'ios', deviceId })
78
- return await new iOSObserve().captureScreenshot(resolved.id)
79
- }
80
- }
81
- }
82
-
@@ -1,49 +0,0 @@
1
- Device-dependent integration tests
2
-
3
- Overview
4
-
5
- This folder contains integration tests that require a simulator or a real device (Android/iOS). These tests exercise real device flows (install, start, UI-tree, logs, interaction) and are intentionally gated from the default CI to avoid failures on runners without devices.
6
-
7
- Prerequisites
8
-
9
- - Build the project: npm run build
10
- - Android: adb available (set ADB_PATH if custom) and a connected device or emulator
11
- - iOS: idb (fb-idb) and idb_companion installed and available. Prefer setting MCP_IDB_PATH or IDB_PATH, or add `idb` to PATH. For simulator tests ensure a simulator is booted.
12
-
13
- Environment variables
14
-
15
- - RUN_DEVICE_TESTS=true — enable device tests when running the integration runner
16
- - ADB_PATH — custom path to adb (optional)
17
- - MCP_IDB_PATH / IDB_PATH — path to idb CLI (optional)
18
- - DEVICE_ID — when a test requires a device id
19
- - APP_ID — when a test requires an app package/bundle id (used by some scripts)
20
-
21
- How to run
22
-
23
- - Run all non-device integration tests (default):
24
- npm run test:integration
25
-
26
- - Run device tests (all):
27
- RUN_DEVICE_TESTS=true npm run test:integration
28
-
29
- - Run a single device test (example: iOS UI tree):
30
- npx tsx test/device/observe/test-ui-tree.ts ios booted
31
-
32
- - Run install integration for a project:
33
- npx tsx test/device/manage/install.integration.ts /path/to/project [deviceId]
34
-
35
- iOS notes
36
-
37
- - If using idb_companion, prefer starting it bound to a UDID to reduce flakiness:
38
- idb_companion --udid <UDID> &
39
- - Boot a simulator if needed:
40
- xcrun simctl boot <UDID>
41
-
42
- Android notes
43
-
44
- - Ensure an emulator or device is connected (adb devices)
45
- - Some tests read DEVICE_ID or accept it as an argument
46
-
47
- CI recommendation
48
-
49
- Keep these tests gated behind RUN_DEVICE_TESTS and run them only on macOS runners (for iOS) or self-hosted runners with attached devices.
@@ -1,27 +0,0 @@
1
- #!/usr/bin/env node
2
- // Device tests runner
3
- // This runner loads minimal utilities, and only imports the device tests when
4
- // RUN_DEVICE_TESTS=true to avoid running them in CI by default.
5
-
6
- import './utils/test-dist';
7
-
8
- (async () => {
9
- if (process.env.RUN_DEVICE_TESTS === 'true') {
10
- console.log('RUN_DEVICE_TESTS=true: running device integration tests');
11
- await Promise.all([
12
- import('./manage/install.integration'),
13
- import('./manage/run-install-android'),
14
- import('./manage/run-install-ios'),
15
- import('./observe/logstream-real'),
16
- import('./observe/test-ui-tree'),
17
- import('./observe/wait_for_element_real'),
18
- import('./interact/run-real-test'),
19
- import('./interact/smoke-test')
20
- ]);
21
- console.log('Device integration imports complete');
22
- } else {
23
- console.log('Skipping device-dependent integration tests. Set RUN_DEVICE_TESTS=true to enable them.');
24
- }
25
- })();
26
-
27
- console.log('Device tests runner ready');
@@ -1,41 +0,0 @@
1
- import fs from 'fs/promises'
2
- import { listAndroidDevices } from '../../../dist/android/utils.js'
3
- import { getAndroidLogs, captureAndroidScreen } from '../../../dist/android.js'
4
-
5
- async function main() {
6
- try {
7
- console.log('Listing Android devices...')
8
- const devices = await listAndroidDevices()
9
- console.log('Devices:', JSON.stringify(devices, null, 2))
10
-
11
- if (devices.length === 0) {
12
- console.log('No Android devices found; aborting Android smoke test.')
13
- return
14
- }
15
-
16
- const target = devices[0]
17
- console.log('Using target device:', target.id)
18
-
19
- console.log('Fetching logs (last 50 lines)...')
20
- const logsRes = await getAndroidLogs(undefined, 50, target.id)
21
- console.log(`Retrieved ${logsRes.logCount} log lines`)
22
- if (logsRes.logs && logsRes.logs.length > 0) {
23
- console.log('Sample log:', logsRes.logs[Math.max(0, logsRes.logs.length - 1)].substring(0, 200))
24
- }
25
-
26
- console.log('Capturing screenshot...')
27
- const shot = await captureAndroidScreen(target.id)
28
- if (shot && shot.screenshot) {
29
- const file = `smoke-test-android-${target.id}.png`
30
- await fs.writeFile(file, Buffer.from(shot.screenshot, 'base64'))
31
- console.log('Screenshot saved to', file)
32
- } else {
33
- console.log('No screenshot returned')
34
- }
35
- } catch {
36
- console.error('Smoke test script failed:', err)
37
- process.exit(1)
38
- }
39
- }
40
-
41
- main()
@@ -1,22 +0,0 @@
1
- import assert from 'assert'
2
- import { detectJavaHome } from '../../../src/utils/java.js'
3
-
4
- // These tests are lightweight smoke tests; they don't rely on actual JDK17 installs,
5
- // but exercise the failure modes and ensure the function returns undefined or a string.
6
-
7
- export async function run() {
8
- const res = await detectJavaHome()
9
- // It's acceptable for local dev env to not have JDK17; just ensure call returns (string|undefined)
10
- assert.ok(typeof res === 'string' || typeof res === 'undefined')
11
-
12
- // Basic mocking: set JAVA_HOME to a fake path and ensure detectJavaHome still runs without throwing.
13
- const orig = process.env.JAVA_HOME
14
- process.env.JAVA_HOME = '/non/existent/java/home'
15
- await detectJavaHome()
16
- // accept either undefined or string results depending on environment; do not fail deterministically
17
- process.env.JAVA_HOME = orig
18
-
19
- console.log('detectJavaHome tests passed')
20
- }
21
-
22
- run().catch((e) => { console.error(e); process.exit(1) })
File without changes
File without changes
File without changes
File without changes