mobile-debug-mcp 0.21.5 → 0.23.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.md +74 -0
- package/README.md +24 -5
- package/dist/interact/classify.js +35 -0
- package/dist/interact/index.js +220 -13
- package/dist/network/index.js +232 -0
- package/dist/observe/ios.js +10 -3
- package/dist/server-core.js +822 -0
- package/dist/server.js +6 -693
- package/dist/utils/resolve-device.js +15 -3
- package/docs/CHANGELOG.md +10 -1
- package/docs/tools/interact.md +69 -30
- package/package.json +3 -3
- package/skills/README.md +35 -0
- package/skills/test-authoring/SKILL.md +57 -0
- package/skills/test-authoring/references/repo-test-layout.md +47 -0
- package/skills/test-authoring/references/test-authoring-workflow.md +73 -0
- package/skills/test-authoring/references/test-quality-checklist.md +39 -0
- package/src/interact/classify.ts +64 -0
- package/src/interact/index.ts +250 -13
- package/src/network/index.ts +268 -0
- package/src/observe/ios.ts +12 -3
- package/src/server-core.ts +879 -0
- package/src/server.ts +8 -754
- package/src/types.ts +10 -1
- package/src/utils/resolve-device.ts +19 -3
- package/test/device/automated/observe/capture_screenshot.android.smoke.ts +30 -0
- package/test/device/automated/observe/capture_screenshot.ios.smoke.ts +30 -0
- package/test/{observe/device → device/automated/observe}/get_logs.android.smoke.ts +1 -1
- package/test/{observe/device → device/automated/observe}/get_logs.ios.smoke.ts +1 -1
- package/test/device/automated/observe/get_ui_tree.android.smoke.ts +31 -0
- package/test/device/automated/observe/get_ui_tree.ios.smoke.ts +31 -0
- package/test/device/index.ts +52 -0
- package/test/{interact/device/smoke-test.ts → device/manual/interact/app_lifecycle.manual.ts} +5 -5
- package/test/{manage/device/run-build-install-ios.ts → device/manual/manage/build_install_ios.manual.ts} +1 -1
- package/test/{manage/device → device/manual/manage}/install.integration.ts +6 -6
- package/test/{manage/device/run-install-android.ts → device/manual/manage/install_android.manual.ts} +1 -1
- package/test/{manage/device/run-install-ios.ts → device/manual/manage/install_ios.manual.ts} +1 -1
- package/test/device/manual/observe/capture_screenshot.manual.ts +29 -0
- package/test/{helpers/run-get-logs.ts → device/manual/observe/get_logs.manual.ts} +1 -1
- package/test/device/manual/observe/get_ui_tree.manual.ts +29 -0
- package/test/{observe/device/logstream-real.ts → device/manual/observe/logstream.manual.ts} +1 -1
- package/test/{observe/device/run-screen-fingerprint.ts → device/manual/observe/screen_fingerprint.manual.ts} +1 -1
- package/test/{observe/device/run-scroll-test-android.ts → device/manual/observe/scroll_to_element_android.manual.ts} +1 -1
- package/test/{observe/device/test-ui-tree.ts → device/manual/observe/ui_tree.manual.ts} +6 -6
- package/test/unit/index.ts +47 -27
- package/test/unit/interact/classify_action_outcome.test.ts +110 -0
- package/test/unit/interact/handler_shapes.test.ts +55 -0
- package/test/unit/interact/tap_element.test.ts +170 -0
- package/test/unit/interact/wait_for_screen_change.test.ts +34 -0
- package/test/{interact/unit → unit/interact}/wait_for_ui_contract.test.ts +11 -10
- package/test/unit/interact/wait_for_ui_selector_matching.test.ts +76 -0
- package/test/unit/manage/handler_shapes.test.ts +43 -0
- package/test/unit/network/get_network_activity.test.ts +181 -0
- package/test/{observe/unit → unit/observe}/capture_debug_snapshot.test.ts +5 -1
- package/test/{observe/unit → unit/observe}/find_element.test.ts +12 -6
- package/test/unit/observe/get_screen_fingerprint.test.ts +71 -0
- package/test/unit/observe/ios-getlogs.test.ts +53 -0
- package/test/unit/observe/scroll_to_element.test.ts +127 -0
- package/test/unit/server/contract.test.ts +45 -0
- package/test/unit/server/response_shapes.test.ts +93 -0
- package/test/unit/system/adb_version.test.ts +35 -0
- package/test/unit/system/get_system_status.test.ts +20 -0
- package/test/unit/system/system_status.test.ts +141 -0
- package/test/{utils → unit/utils}/detect_java.test.ts +1 -1
- package/test/unit/utils/exec.test.ts +51 -0
- package/test/unit/utils/resolve_device.test.ts +63 -0
- package/tsconfig.json +2 -2
- package/test/interact/device/run-real-test.ts +0 -3
- package/test/interact/unit/wait_for_screen_change.test.ts +0 -32
- package/test/interact/unit/wait_for_ui.test.ts +0 -76
- package/test/interact/unit/wait_for_ui_new.test.ts +0 -57
- package/test/observe/device/wait_for_element_real.ts +0 -3
- package/test/observe/unit/get_screen_fingerprint.test.ts +0 -69
- package/test/observe/unit/ios-getlogs.test.ts +0 -67
- package/test/observe/unit/scroll_to_element.test.ts +0 -129
- package/test/observe/unit/wait_for_element_mock.ts +0 -2
- package/test/observe/unit/wait_for_ui_edge_cases.test.ts +0 -41
- package/test/observe/unit/wait_for_ui_stability.test.ts +0 -30
- package/test/system/adb_version.test.ts +0 -25
- package/test/system/get_system_status.test.ts +0 -52
- package/test/system/system_status.test.ts +0 -109
- /package/test/{manage/unit → unit/manage}/build.test.ts +0 -0
- /package/test/{manage/unit → unit/manage}/build_and_install.test.ts +0 -0
- /package/test/{manage/unit → unit/manage}/detection.test.ts +0 -0
- /package/test/{manage/unit → unit/manage}/diagnostics.test.ts +0 -0
- /package/test/{manage/unit → unit/manage}/install.test.ts +0 -0
- /package/test/{manage/unit → unit/manage}/mcp_disable_autodetect.test.ts +0 -0
- /package/test/{observe/unit → unit/observe}/get_logs.test.ts +0 -0
- /package/test/{observe/unit → unit/observe}/logparse.test.ts +0 -0
- /package/test/{observe/unit → unit/observe}/logstream.test.ts +0 -0
package/src/types.ts
CHANGED
|
@@ -132,6 +132,16 @@ export interface TapResponse {
|
|
|
132
132
|
error?: string;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
export interface TapElementResponse {
|
|
136
|
+
success: boolean;
|
|
137
|
+
elementId: string;
|
|
138
|
+
action: 'tap';
|
|
139
|
+
error?: {
|
|
140
|
+
code: 'element_not_found' | 'element_not_visible' | 'element_not_enabled' | 'tap_failed';
|
|
141
|
+
message: string;
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
135
145
|
export interface SwipeResponse {
|
|
136
146
|
device: DeviceInfo;
|
|
137
147
|
success: boolean;
|
|
@@ -161,4 +171,3 @@ export interface InstallAppResponse {
|
|
|
161
171
|
error?: string;
|
|
162
172
|
diagnostics?: any;
|
|
163
173
|
}
|
|
164
|
-
|
|
@@ -18,14 +18,30 @@ function parseNumericVersion(v: string): number {
|
|
|
18
18
|
return major + minor / 100
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
let androidDeviceLister = listAndroidDevices
|
|
22
|
+
let iosDeviceLister = listIOSDevices
|
|
23
|
+
|
|
24
|
+
export function _setDeviceListersForTests(overrides: {
|
|
25
|
+
listAndroidDevices?: typeof listAndroidDevices
|
|
26
|
+
listIOSDevices?: typeof listIOSDevices
|
|
27
|
+
}) {
|
|
28
|
+
if (overrides.listAndroidDevices) androidDeviceLister = overrides.listAndroidDevices
|
|
29
|
+
if (overrides.listIOSDevices) iosDeviceLister = overrides.listIOSDevices
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function _resetDeviceListersForTests() {
|
|
33
|
+
androidDeviceLister = listAndroidDevices
|
|
34
|
+
iosDeviceLister = listIOSDevices
|
|
35
|
+
}
|
|
36
|
+
|
|
21
37
|
export async function listDevices(platform?: "android" | "ios", appId?: string): Promise<DeviceInfo[]> {
|
|
22
38
|
if (!platform || platform === "android") {
|
|
23
|
-
const android = await
|
|
39
|
+
const android = await androidDeviceLister(appId)
|
|
24
40
|
if (platform === "android") return android
|
|
25
|
-
const ios = await
|
|
41
|
+
const ios = await iosDeviceLister(appId)
|
|
26
42
|
return [...android, ...ios]
|
|
27
43
|
}
|
|
28
|
-
return
|
|
44
|
+
return iosDeviceLister(appId)
|
|
29
45
|
}
|
|
30
46
|
|
|
31
47
|
export async function resolveTargetDevice(opts: ResolveOptions): Promise<DeviceInfo> {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import { execSync } from 'child_process'
|
|
3
|
+
|
|
4
|
+
function log(msg: string) { console.log(msg) }
|
|
5
|
+
|
|
6
|
+
if (process.env.SKIP_DEVICE_TESTS === '1') {
|
|
7
|
+
log('SKIP_DEVICE_TESTS=1 detected - skipping android screenshot smoke test')
|
|
8
|
+
process.exit(0)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const helperScript = 'test/device/manual/observe/capture_screenshot.manual.ts'
|
|
12
|
+
if (!fs.existsSync(helperScript)) {
|
|
13
|
+
console.error(`Missing ${helperScript}. Run 'npm run build' first or ensure the helper exists.`)
|
|
14
|
+
process.exit(1)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const out = execSync(`tsx ${helperScript} --platform android --id default`, { encoding: 'utf8', maxBuffer: 10 * 1024 * 1024, timeout: 30000 })
|
|
19
|
+
const parsed = JSON.parse(out)
|
|
20
|
+
|
|
21
|
+
if (!parsed?.resolution || parsed.resolution.width <= 0 || parsed.resolution.height <= 0) throw new Error('Invalid screenshot resolution')
|
|
22
|
+
if (typeof parsed.screenshotBytes !== 'number' || parsed.screenshotBytes <= 0) throw new Error('Screenshot payload missing')
|
|
23
|
+
|
|
24
|
+
log('Android capture_screenshot smoke test: PASS')
|
|
25
|
+
process.exit(0)
|
|
26
|
+
} catch (err: any) {
|
|
27
|
+
console.error('Android capture_screenshot smoke test: FAIL')
|
|
28
|
+
console.error(err && err.message ? err.message : err)
|
|
29
|
+
process.exit(2)
|
|
30
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import { execSync } from 'child_process'
|
|
3
|
+
|
|
4
|
+
function log(msg: string) { console.log(msg) }
|
|
5
|
+
|
|
6
|
+
if (process.env.SKIP_DEVICE_TESTS === '1') {
|
|
7
|
+
log('SKIP_DEVICE_TESTS=1 detected - skipping ios screenshot smoke test')
|
|
8
|
+
process.exit(0)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const helperScript = 'test/device/manual/observe/capture_screenshot.manual.ts'
|
|
12
|
+
if (!fs.existsSync(helperScript)) {
|
|
13
|
+
console.error(`Missing ${helperScript}. Run 'npm run build' first or ensure the helper exists.`)
|
|
14
|
+
process.exit(1)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const out = execSync(`tsx ${helperScript} --platform ios --id booted`, { encoding: 'utf8', maxBuffer: 10 * 1024 * 1024, timeout: 30000 })
|
|
19
|
+
const parsed = JSON.parse(out)
|
|
20
|
+
|
|
21
|
+
if (!parsed?.resolution || parsed.resolution.width <= 0 || parsed.resolution.height <= 0) throw new Error('Invalid screenshot resolution')
|
|
22
|
+
if (typeof parsed.screenshotBytes !== 'number' || parsed.screenshotBytes <= 0) throw new Error('Screenshot payload missing')
|
|
23
|
+
|
|
24
|
+
log('iOS capture_screenshot smoke test: PASS')
|
|
25
|
+
process.exit(0)
|
|
26
|
+
} catch (err: any) {
|
|
27
|
+
console.error('iOS capture_screenshot smoke test: FAIL')
|
|
28
|
+
console.error(err && err.message ? err.message : err)
|
|
29
|
+
process.exit(2)
|
|
30
|
+
}
|
|
@@ -9,7 +9,7 @@ if (process.env.SKIP_DEVICE_TESTS === '1') {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
// Ensure helper script exists
|
|
12
|
-
const helperScript = 'test/
|
|
12
|
+
const helperScript = 'test/device/manual/observe/get_logs.manual.ts'
|
|
13
13
|
if (!fs.existsSync(helperScript)) {
|
|
14
14
|
console.error(`Missing ${helperScript}. Run 'npm run build' first or ensure the helper exists.`)
|
|
15
15
|
process.exit(1)
|
|
@@ -8,7 +8,7 @@ if (process.env.SKIP_DEVICE_TESTS === '1') {
|
|
|
8
8
|
process.exit(0)
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
const helperScript = 'test/
|
|
11
|
+
const helperScript = 'test/device/manual/observe/get_logs.manual.ts'
|
|
12
12
|
if (!fs.existsSync(helperScript)) {
|
|
13
13
|
console.error(`Missing ${helperScript}. Run 'npm run build' first or ensure the helper exists.`)
|
|
14
14
|
process.exit(1)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import { execSync } from 'child_process'
|
|
3
|
+
|
|
4
|
+
function log(msg: string) { console.log(msg) }
|
|
5
|
+
|
|
6
|
+
if (process.env.SKIP_DEVICE_TESTS === '1') {
|
|
7
|
+
log('SKIP_DEVICE_TESTS=1 detected - skipping android ui tree smoke test')
|
|
8
|
+
process.exit(0)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const helperScript = 'test/device/manual/observe/get_ui_tree.manual.ts'
|
|
12
|
+
if (!fs.existsSync(helperScript)) {
|
|
13
|
+
console.error(`Missing ${helperScript}. Run 'npm run build' first or ensure the helper exists.`)
|
|
14
|
+
process.exit(1)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const out = execSync(`tsx ${helperScript} --platform android --id default`, { encoding: 'utf8', maxBuffer: 10 * 1024 * 1024, timeout: 30000 })
|
|
19
|
+
const parsed = JSON.parse(out)
|
|
20
|
+
|
|
21
|
+
if (!parsed?.resolution || parsed.resolution.width <= 0 || parsed.resolution.height <= 0) throw new Error('Invalid UI tree resolution')
|
|
22
|
+
if (typeof parsed.elementCount !== 'number') throw new Error('elementCount missing')
|
|
23
|
+
if (parsed.elementCount > 0 && parsed.hasCenterAndDepth !== true) throw new Error('UI element metadata missing center/depth')
|
|
24
|
+
|
|
25
|
+
log('Android get_ui_tree smoke test: PASS')
|
|
26
|
+
process.exit(0)
|
|
27
|
+
} catch (err: any) {
|
|
28
|
+
console.error('Android get_ui_tree smoke test: FAIL')
|
|
29
|
+
console.error(err && err.message ? err.message : err)
|
|
30
|
+
process.exit(2)
|
|
31
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import { execSync } from 'child_process'
|
|
3
|
+
|
|
4
|
+
function log(msg: string) { console.log(msg) }
|
|
5
|
+
|
|
6
|
+
if (process.env.SKIP_DEVICE_TESTS === '1') {
|
|
7
|
+
log('SKIP_DEVICE_TESTS=1 detected - skipping ios ui tree smoke test')
|
|
8
|
+
process.exit(0)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const helperScript = 'test/device/manual/observe/get_ui_tree.manual.ts'
|
|
12
|
+
if (!fs.existsSync(helperScript)) {
|
|
13
|
+
console.error(`Missing ${helperScript}. Run 'npm run build' first or ensure the helper exists.`)
|
|
14
|
+
process.exit(1)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const out = execSync(`tsx ${helperScript} --platform ios --id booted`, { encoding: 'utf8', maxBuffer: 10 * 1024 * 1024, timeout: 30000 })
|
|
19
|
+
const parsed = JSON.parse(out)
|
|
20
|
+
|
|
21
|
+
if (!parsed?.resolution || parsed.resolution.width <= 0 || parsed.resolution.height <= 0) throw new Error('Invalid UI tree resolution')
|
|
22
|
+
if (typeof parsed.elementCount !== 'number') throw new Error('elementCount missing')
|
|
23
|
+
if (parsed.elementCount > 0 && parsed.hasCenterAndDepth !== true) throw new Error('UI element metadata missing center/depth')
|
|
24
|
+
|
|
25
|
+
log('iOS get_ui_tree smoke test: PASS')
|
|
26
|
+
process.exit(0)
|
|
27
|
+
} catch (err: any) {
|
|
28
|
+
console.error('iOS get_ui_tree smoke test: FAIL')
|
|
29
|
+
console.error(err && err.message ? err.message : err)
|
|
30
|
+
process.exit(2)
|
|
31
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { readdir } from 'fs/promises'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import { spawn } from 'child_process'
|
|
4
|
+
import { fileURLToPath } from 'url'
|
|
5
|
+
|
|
6
|
+
const deviceRoot = fileURLToPath(new URL('.', import.meta.url))
|
|
7
|
+
const automatedRoot = fileURLToPath(new URL('./automated', import.meta.url))
|
|
8
|
+
const runnableSuffixes = ['.test.ts', '.smoke.ts', '.integration.ts']
|
|
9
|
+
|
|
10
|
+
async function collectFiles(dir: string): Promise<string[]> {
|
|
11
|
+
const entries = await readdir(dir, { withFileTypes: true })
|
|
12
|
+
const files = await Promise.all(entries.map(async (entry) => {
|
|
13
|
+
const fullPath = path.join(dir, entry.name)
|
|
14
|
+
if (entry.isDirectory()) return collectFiles(fullPath)
|
|
15
|
+
return fullPath
|
|
16
|
+
}))
|
|
17
|
+
|
|
18
|
+
return files.flat()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function runFile(file: string): Promise<void> {
|
|
22
|
+
const relativePath = path.relative(deviceRoot, file).replaceAll(path.sep, '/')
|
|
23
|
+
console.log(`Running device test: ${relativePath}`)
|
|
24
|
+
|
|
25
|
+
await new Promise<void>((resolve, reject) => {
|
|
26
|
+
const child = spawn('tsx', [file], { stdio: 'inherit' })
|
|
27
|
+
child.on('error', reject)
|
|
28
|
+
child.on('close', (code) => {
|
|
29
|
+
if (code === 0) {
|
|
30
|
+
resolve()
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
reject(new Error(`Device test failed: ${relativePath} (exit code ${code ?? 'unknown'})`))
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
(async function () {
|
|
40
|
+
const allFiles = (await collectFiles(automatedRoot))
|
|
41
|
+
.filter((file) => runnableSuffixes.some((suffix) => file.endsWith(suffix)))
|
|
42
|
+
.sort()
|
|
43
|
+
|
|
44
|
+
for (const file of allFiles) {
|
|
45
|
+
await runFile(file)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
console.log('Device tests loaded.')
|
|
49
|
+
})().catch((error) => {
|
|
50
|
+
console.error(error instanceof Error ? error.message : String(error))
|
|
51
|
+
process.exit(1)
|
|
52
|
+
})
|
package/test/{interact/device/smoke-test.ts → device/manual/interact/app_lifecycle.manual.ts}
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { AndroidObserve, iOSObserve } from "
|
|
2
|
-
import { AndroidInteract } from "
|
|
3
|
-
import { iOSInteract } from "
|
|
1
|
+
import { AndroidObserve, iOSObserve } from "../../../src/observe/index.js";
|
|
2
|
+
import { AndroidInteract } from "../../../src/interact/index.js";
|
|
3
|
+
import { iOSInteract } from "../../../src/interact/index.js";
|
|
4
4
|
import fs from "fs/promises";
|
|
5
5
|
|
|
6
6
|
const androidObserve = new AndroidObserve();
|
|
@@ -14,7 +14,7 @@ async function main() {
|
|
|
14
14
|
const appId = args[1];
|
|
15
15
|
|
|
16
16
|
if ((platform !== "android" && platform !== "ios") || !appId) {
|
|
17
|
-
console.error("Usage: npx tsx test/
|
|
17
|
+
console.error("Usage: npx tsx test/device/manual/interact/app_lifecycle.manual.ts <android|ios> <appId>");
|
|
18
18
|
process.exit(1);
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -104,7 +104,7 @@ async function main() {
|
|
|
104
104
|
|
|
105
105
|
console.log(`\n✨ Smoke test COMPLETED SUCCESSFULLY! ✨\n`);
|
|
106
106
|
|
|
107
|
-
} catch {
|
|
107
|
+
} catch (error) {
|
|
108
108
|
console.error(`\n❌ Smoke test FAILED:`, error);
|
|
109
109
|
process.exit(1);
|
|
110
110
|
}
|
|
@@ -35,7 +35,7 @@ function spawnStream(cmd: string, args: string[], opts: any = {}): Promise<numbe
|
|
|
35
35
|
async function main() {
|
|
36
36
|
const [, , projectPath, deviceId = 'booted'] = process.argv
|
|
37
37
|
if (!projectPath) {
|
|
38
|
-
console.error('Usage: tsx test/
|
|
38
|
+
console.error('Usage: tsx test/device/manual/manage/build_install_ios.manual.ts <project-dir> [deviceId]')
|
|
39
39
|
process.exit(1)
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Integration runner: calls existing
|
|
3
|
-
// Usage: npx tsx test/
|
|
2
|
+
// Integration runner: calls existing manual install helpers.
|
|
3
|
+
// Usage: npx tsx test/device/manual/manage/install.integration.ts /path/to/project [deviceId]
|
|
4
4
|
import { spawn } from 'child_process'
|
|
5
5
|
import fs from 'fs'
|
|
6
6
|
import path from 'path'
|
|
7
7
|
|
|
8
8
|
const args = process.argv.slice(2)
|
|
9
9
|
if (args.length < 1) {
|
|
10
|
-
console.error('Usage: npx tsx test/
|
|
10
|
+
console.error('Usage: npx tsx test/device/manual/manage/install.integration.ts /path/to/project [deviceId]')
|
|
11
11
|
process.exit(2)
|
|
12
12
|
}
|
|
13
13
|
const project = args[0]
|
|
@@ -29,9 +29,9 @@ function isIosDir(p: string) {
|
|
|
29
29
|
|
|
30
30
|
let runner: string | undefined
|
|
31
31
|
if (isAndroidDir(project)) {
|
|
32
|
-
runner = path.join(process.cwd(), 'test', '
|
|
32
|
+
runner = path.join(process.cwd(), 'test', 'device', 'manual', 'manage', 'install_android.manual.ts')
|
|
33
33
|
} else if (isIosDir(project)) {
|
|
34
|
-
runner = path.join(process.cwd(), 'test', '
|
|
34
|
+
runner = path.join(process.cwd(), 'test', 'device', 'manual', 'manage', 'install_ios.manual.ts')
|
|
35
35
|
} else {
|
|
36
36
|
console.error('Cannot determine platform for project:', project)
|
|
37
37
|
process.exit(3)
|
|
@@ -39,7 +39,7 @@ if (isAndroidDir(project)) {
|
|
|
39
39
|
|
|
40
40
|
if (!runner) process.exit(4)
|
|
41
41
|
|
|
42
|
-
const proc = spawn('
|
|
42
|
+
const proc = spawn('tsx', [runner, project, ...(deviceId ? [deviceId] : [])], { stdio: ['ignore', 'pipe', 'inherit'] })
|
|
43
43
|
let stdout = ''
|
|
44
44
|
proc.stdout?.on('data', (c) => { stdout += c.toString() })
|
|
45
45
|
proc.on('close', (code) => {
|
package/test/{manage/device/run-install-android.ts → device/manual/manage/install_android.manual.ts}
RENAMED
|
@@ -4,7 +4,7 @@ import { AndroidManage } from '../../../dist/utils/android/manage.js'
|
|
|
4
4
|
async function main() {
|
|
5
5
|
const [, , appPath, deviceId] = process.argv
|
|
6
6
|
if (!appPath) {
|
|
7
|
-
console.error('Usage: node test/
|
|
7
|
+
console.error('Usage: node test/device/manual/manage/install_android.manual.ts <apk-or-project-dir> [deviceId]')
|
|
8
8
|
process.exit(1)
|
|
9
9
|
}
|
|
10
10
|
|
package/test/{manage/device/run-install-ios.ts → device/manual/manage/install_ios.manual.ts}
RENAMED
|
@@ -4,7 +4,7 @@ import { iOSManage } from '../../../dist/utils/ios/manage.js'
|
|
|
4
4
|
async function main() {
|
|
5
5
|
const [, , appPath, deviceId] = process.argv
|
|
6
6
|
if (!appPath) {
|
|
7
|
-
console.error('Usage: node test/
|
|
7
|
+
console.error('Usage: node test/device/manual/manage/install_ios.manual.ts <.app-or-project-dir> [deviceId]')
|
|
8
8
|
process.exit(1)
|
|
9
9
|
}
|
|
10
10
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ToolsObserve } from '../../../src/observe/index.js'
|
|
2
|
+
|
|
3
|
+
function readArg(flag: string): string | undefined {
|
|
4
|
+
const index = process.argv.indexOf(flag)
|
|
5
|
+
if (index === -1) return undefined
|
|
6
|
+
return process.argv[index + 1]
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async function main() {
|
|
10
|
+
const platform = (readArg('--platform') || process.argv[2] || 'android') as 'android' | 'ios'
|
|
11
|
+
const deviceId = readArg('--id') || readArg('--deviceId') || process.argv[3]
|
|
12
|
+
|
|
13
|
+
const result = await ToolsObserve.captureScreenshotHandler({ platform, deviceId })
|
|
14
|
+
const screenshot = (result as any).screenshot || ''
|
|
15
|
+
const fallback = (result as any).screenshot_fallback || ''
|
|
16
|
+
|
|
17
|
+
console.log(JSON.stringify({
|
|
18
|
+
device: result.device,
|
|
19
|
+
resolution: result.resolution,
|
|
20
|
+
mimeType: (result as any).screenshot_mime || 'image/png',
|
|
21
|
+
screenshotBytes: screenshot ? Buffer.from(screenshot, 'base64').length : 0,
|
|
22
|
+
fallbackBytes: fallback ? Buffer.from(fallback, 'base64').length : 0
|
|
23
|
+
}))
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
main().catch((error) => {
|
|
27
|
+
console.error(error instanceof Error ? error.message : String(error))
|
|
28
|
+
process.exit(1)
|
|
29
|
+
})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ToolsObserve } from '../../../src/observe/index.js'
|
|
2
|
+
|
|
3
|
+
function readArg(flag: string): string | undefined {
|
|
4
|
+
const index = process.argv.indexOf(flag)
|
|
5
|
+
if (index === -1) return undefined
|
|
6
|
+
return process.argv[index + 1]
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async function main() {
|
|
10
|
+
const platform = (readArg('--platform') || process.argv[2] || 'android') as 'android' | 'ios'
|
|
11
|
+
const deviceId = readArg('--id') || readArg('--deviceId') || process.argv[3]
|
|
12
|
+
|
|
13
|
+
const result = await ToolsObserve.getUITreeHandler({ platform, deviceId })
|
|
14
|
+
if ((result as any).error) throw new Error((result as any).error)
|
|
15
|
+
|
|
16
|
+
const firstElement = Array.isArray((result as any).elements) ? (result as any).elements[0] : null
|
|
17
|
+
|
|
18
|
+
console.log(JSON.stringify({
|
|
19
|
+
device: (result as any).device,
|
|
20
|
+
resolution: (result as any).resolution,
|
|
21
|
+
elementCount: Array.isArray((result as any).elements) ? (result as any).elements.length : 0,
|
|
22
|
+
hasCenterAndDepth: firstElement ? ('center' in firstElement && 'depth' in firstElement) : true
|
|
23
|
+
}))
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
main().catch((error) => {
|
|
27
|
+
console.error(error instanceof Error ? error.message : String(error))
|
|
28
|
+
process.exit(1)
|
|
29
|
+
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* Device E2E: get_screen_fingerprint
|
|
4
|
-
* Usage: RUN_DEVICE_TESTS=true npx tsx
|
|
4
|
+
* Usage: RUN_DEVICE_TESTS=true npx tsx test/device/manual/observe/screen_fingerprint.manual.ts [android|ios] [deviceId]
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { AndroidObserve } from '../../../src/observe/index.js'
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { AndroidInteract } from '../../../dist/interact/index.js'
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
// Usage: tsx test/device/observe/
|
|
5
|
+
// Usage: tsx test/device/manual/observe/scroll_to_element_android.manual.ts <deviceId> <appId> <selectorText>
|
|
6
6
|
const args = process.argv.slice(2)
|
|
7
7
|
const DEVICE_ID = args[0] || process.env.DEVICE_ID || 'emulator-5554'
|
|
8
8
|
const SELECTOR = args[2] || process.env.SELECTOR || 'Generate Session'
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
* Test script for verify UI Tree functionality.
|
|
3
3
|
*
|
|
4
4
|
* Usage:
|
|
5
|
-
* npx tsx test
|
|
5
|
+
* npx tsx test/device/manual/observe/ui_tree.manual.ts [android|ios] [deviceId]
|
|
6
6
|
*
|
|
7
7
|
* Examples:
|
|
8
|
-
* npx tsx test
|
|
9
|
-
* npx tsx test
|
|
8
|
+
* npx tsx test/device/manual/observe/ui_tree.manual.ts android
|
|
9
|
+
* npx tsx test/device/manual/observe/ui_tree.manual.ts ios booted
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { AndroidObserve } from '
|
|
13
|
-
import { iOSObserve } from '
|
|
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);
|
|
@@ -67,7 +67,7 @@ async function main() {
|
|
|
67
67
|
console.log(`- Elements with text: ${withText}`);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
} catch {
|
|
70
|
+
} catch (error) {
|
|
71
71
|
console.error("\n❌ Test Failed:", error);
|
|
72
72
|
process.exit(1);
|
|
73
73
|
}
|
package/test/unit/index.ts
CHANGED
|
@@ -1,30 +1,50 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
await
|
|
10
|
-
await
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
1
|
+
import { readdir } from 'fs/promises'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import { spawn } from 'child_process'
|
|
4
|
+
import { fileURLToPath } from 'url'
|
|
5
|
+
|
|
6
|
+
const unitRoot = fileURLToPath(new URL('.', import.meta.url))
|
|
7
|
+
|
|
8
|
+
async function collectFiles(dir: string): Promise<string[]> {
|
|
9
|
+
const entries = await readdir(dir, { withFileTypes: true })
|
|
10
|
+
const files = await Promise.all(entries.map(async (entry) => {
|
|
11
|
+
const fullPath = path.join(dir, entry.name)
|
|
12
|
+
if (entry.isDirectory()) return collectFiles(fullPath)
|
|
13
|
+
return fullPath
|
|
14
|
+
}))
|
|
15
|
+
|
|
16
|
+
return files.flat()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function runFile(file: string): Promise<void> {
|
|
20
|
+
const relativePath = path.relative(unitRoot, file).replaceAll(path.sep, '/')
|
|
21
|
+
console.log(`Running unit test: ${relativePath}`)
|
|
22
|
+
|
|
23
|
+
await new Promise<void>((resolve, reject) => {
|
|
24
|
+
const child = spawn('tsx', [file], { stdio: 'inherit' })
|
|
25
|
+
child.on('error', reject)
|
|
26
|
+
child.on('close', (code) => {
|
|
27
|
+
if (code === 0) {
|
|
28
|
+
resolve()
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
reject(new Error(`Unit test failed: ${relativePath} (exit code ${code ?? 'unknown'})`))
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
(async function () {
|
|
38
|
+
const allFiles = (await collectFiles(unitRoot))
|
|
39
|
+
.filter((file) => file.endsWith('.test.ts') && path.basename(file) !== 'index.ts')
|
|
40
|
+
.sort()
|
|
41
|
+
|
|
42
|
+
for (const file of allFiles) {
|
|
43
|
+
await runFile(file)
|
|
27
44
|
}
|
|
28
45
|
|
|
29
46
|
console.log('Unit tests loaded.')
|
|
30
|
-
})().catch(
|
|
47
|
+
})().catch((error) => {
|
|
48
|
+
console.error(error instanceof Error ? error.message : String(error))
|
|
49
|
+
process.exit(1)
|
|
50
|
+
})
|