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.
- package/dist/android/interact.js +2 -2
- package/dist/android/observe.js +13 -0
- package/dist/cli/ios/run-ios-smoke.js +2 -2
- package/dist/cli/ios/run-ios-ui-tree-tap.js +2 -2
- package/dist/interact/android.js +91 -0
- package/dist/interact/index.js +76 -0
- package/dist/interact/ios.js +120 -0
- package/dist/interact/shared/fingerprint.js +1 -0
- package/dist/interact/shared/scroll_to_element.js +1 -0
- package/dist/ios/interact.js +2 -2
- package/dist/ios/observe.js +12 -0
- package/dist/manage/android.js +162 -0
- package/dist/manage/index.js +364 -0
- package/dist/manage/ios.js +353 -0
- package/dist/observe/android.js +351 -0
- package/dist/observe/fingerprint.js +1 -0
- package/dist/observe/index.js +85 -0
- package/dist/observe/ios.js +320 -0
- package/dist/observe/test/device/logstream-real.js +34 -0
- package/dist/observe/test/device/run-screen-fingerprint.js +29 -0
- package/dist/observe/test/device/run-scroll-test-android.js +22 -0
- package/dist/observe/test/device/test-ui-tree.js +67 -0
- package/dist/observe/test/device/wait_for_element_real.js +69 -0
- package/dist/observe/test/unit/get_screen_fingerprint.test.js +54 -0
- package/dist/observe/test/unit/logparse.test.js +39 -0
- package/dist/observe/test/unit/logstream.test.js +41 -0
- package/dist/observe/test/unit/scroll_to_element.test.js +113 -0
- package/dist/observe/test/unit/wait_for_element_mock.js +92 -0
- package/dist/server.js +41 -5
- package/dist/shared/fingerprint.js +72 -0
- package/dist/shared/scroll_to_element.js +98 -0
- package/dist/tools/interact.js +2 -2
- package/dist/tools/manage.js +2 -2
- package/dist/tools/observe.js +45 -43
- package/dist/utils/android/utils.js +373 -0
- package/dist/utils/cli/idb/check-idb.js +84 -0
- package/dist/utils/cli/idb/idb-helper.js +91 -0
- package/dist/utils/cli/idb/install-idb.js +82 -0
- package/dist/utils/cli/ios/preflight-ios.js +155 -0
- package/dist/utils/cli/ios/run-ios-smoke.js +28 -0
- package/dist/utils/cli/ios/run-ios-ui-tree-tap.js +29 -0
- package/dist/utils/diagnostics.js +1 -1
- package/dist/utils/exec.js +34 -0
- package/dist/utils/ios/utils.js +301 -0
- package/dist/utils/resolve-device.js +2 -2
- package/dist/utils/ui/index.js +169 -0
- package/docs/CHANGELOG.md +8 -0
- package/docs/tools/interact.md +29 -0
- package/docs/tools/observe.md +24 -0
- package/package.json +1 -1
- package/src/{android/interact.ts → interact/android.ts} +3 -3
- package/src/{tools/interact.ts → interact/index.ts} +47 -3
- package/src/{ios/interact.ts → interact/ios.ts} +3 -3
- package/src/{android/manage.ts → manage/android.ts} +2 -2
- package/src/{tools/manage.ts → manage/index.ts} +7 -4
- package/src/{ios/manage.ts → manage/ios.ts} +1 -1
- package/src/{android/observe.ts → observe/android.ts} +14 -26
- package/src/observe/index.ts +92 -0
- package/src/{ios/observe.ts → observe/ios.ts} +17 -35
- package/src/server.ts +45 -6
- package/src/types.ts +1 -0
- package/src/{android → utils/android}/utils.ts +12 -79
- package/src/{cli → utils/cli}/ios/run-ios-smoke.ts +2 -2
- package/src/{cli → utils/cli}/ios/run-ios-ui-tree-tap.ts +3 -3
- package/src/utils/diagnostics.ts +1 -1
- package/src/utils/exec.ts +33 -0
- package/src/{ios → utils/ios}/utils.ts +2 -2
- package/src/utils/resolve-device.ts +2 -2
- package/src/{tools/scroll_to_element.ts → utils/ui/index.ts} +73 -2
- package/test/{device/interact → interact/device}/smoke-test.ts +3 -4
- package/test/interact/unit/wait_for_screen_change.test.ts +32 -0
- package/test/{device/manage → manage/device}/run-install-android.ts +1 -1
- package/test/{device/manage → manage/device}/run-install-ios.ts +1 -1
- package/test/{device/manage → manage/device}/run-install-kmp.ts +1 -1
- package/test/{unit/manage → manage/unit}/build.test.ts +1 -1
- package/test/{unit/manage → manage/unit}/build_and_install.test.ts +1 -1
- package/test/{unit/manage → manage/unit}/detection.test.ts +1 -1
- package/test/{unit/manage → manage/unit}/diagnostics.test.ts +2 -2
- package/test/{unit/manage → manage/unit}/install.test.ts +1 -1
- package/test/{unit/manage → manage/unit}/mcp_disable_autodetect.test.ts +1 -1
- package/test/{device/observe → observe/device}/logstream-real.ts +1 -1
- package/test/observe/device/run-screen-fingerprint.ts +36 -0
- package/test/{device/observe → observe/device}/run-scroll-test-android.ts +2 -2
- package/test/{device/observe → observe/device}/test-ui-tree.ts +2 -2
- package/test/{device/observe → observe/device}/wait_for_element_real.ts +2 -2
- package/test/observe/unit/get_screen_fingerprint.test.ts +69 -0
- package/test/{unit/observe → observe/unit}/logparse.test.ts +1 -1
- package/test/{unit/observe → observe/unit}/logstream.test.ts +1 -1
- package/test/{unit/observe → observe/unit}/scroll_to_element.test.ts +3 -3
- package/test/{unit/observe → observe/unit}/wait_for_element_mock.ts +2 -2
- package/test/unit/index.ts +13 -11
- package/src/tools/observe.ts +0 -82
- package/test/device/README.md +0 -49
- package/test/device/index.ts +0 -27
- package/test/device/utils/test-dist.ts +0 -41
- package/test/unit/utils/detect-java.test.ts +0 -22
- /package/src/{cli → utils/cli}/idb/check-idb.ts +0 -0
- /package/src/{cli → utils/cli}/idb/idb-helper.ts +0 -0
- /package/src/{cli → utils/cli}/idb/install-idb.ts +0 -0
- /package/src/{cli → utils/cli}/ios/preflight-ios.ts +0 -0
- /package/test/{device/interact → interact/device}/run-real-test.ts +0 -0
- /package/test/{device/manage → manage/device}/install.integration.ts +0 -0
- /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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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
|
|
@@ -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/
|
|
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/
|
|
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/
|
|
13
|
-
import { iOSObserve } from '../../src/
|
|
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/
|
|
2
|
-
import { AndroidObserve } from "../../src/
|
|
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,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/
|
|
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/
|
|
2
|
-
import { ToolsObserve } from '../../../src/
|
|
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/
|
|
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/
|
|
2
|
-
import { AndroidObserve } from '../../../src/
|
|
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
|
|
package/test/unit/index.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
// Aggregator entrypoint for unit tests
|
|
2
|
-
import '
|
|
3
|
-
import '
|
|
4
|
-
import '
|
|
5
|
-
import '
|
|
6
|
-
|
|
7
|
-
import '
|
|
8
|
-
import '
|
|
9
|
-
import '
|
|
10
|
-
import '
|
|
11
|
-
import '
|
|
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.')
|
package/src/tools/observe.ts
DELETED
|
@@ -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
|
-
|
package/test/device/README.md
DELETED
|
@@ -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.
|
package/test/device/index.ts
DELETED
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|