mobile-debug-mcp 0.4.0 ā 0.5.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/.github/copilot-instructions.md +33 -0
- package/README.md +154 -23
- package/dist/android.js +174 -32
- package/dist/ios.js +203 -14
- package/dist/server.js +263 -20
- package/docs/CHANGELOG.md +23 -0
- package/package.json +2 -2
- package/smoke-test.js +102 -0
- package/smoke-test.ts +115 -0
- package/src/android.ts +205 -31
- package/src/ios.ts +234 -16
- package/src/server.ts +305 -24
- package/src/types.ts +58 -0
- package/tsconfig.json +2 -1
package/smoke-test.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { startAndroidApp, terminateAndroidApp, captureAndroidScreen, getAndroidLogs } from "./src/android.js";
|
|
2
|
+
import { startIOSApp, terminateIOSApp, captureIOSScreenshot, getIOSLogs } from "./src/ios.js";
|
|
3
|
+
import fs from "fs/promises";
|
|
4
|
+
async function main() {
|
|
5
|
+
const args = process.argv.slice(2);
|
|
6
|
+
const platform = args[0]; // Cast to string first
|
|
7
|
+
const appId = args[1];
|
|
8
|
+
if ((platform !== "android" && platform !== "ios") || !appId) {
|
|
9
|
+
console.error("Usage: npx tsx smoke-test.ts <android|ios> <appId>");
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
console.log(`\nš Starting smoke test for ${platform} app: ${appId}`);
|
|
13
|
+
try {
|
|
14
|
+
// 1. Start App
|
|
15
|
+
console.log(`[1/4] Starting app...`);
|
|
16
|
+
let startResult;
|
|
17
|
+
let launchTimeMs;
|
|
18
|
+
if (platform === "android") {
|
|
19
|
+
const result = await startAndroidApp(appId);
|
|
20
|
+
startResult = result.appStarted;
|
|
21
|
+
launchTimeMs = result.launchTimeMs;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
const result = await startIOSApp(appId);
|
|
25
|
+
startResult = result.appStarted;
|
|
26
|
+
launchTimeMs = result.launchTimeMs;
|
|
27
|
+
}
|
|
28
|
+
if (startResult) {
|
|
29
|
+
console.log(`ā
App started successfully (Launch time: ${launchTimeMs}ms)`);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
throw new Error("Failed to start app");
|
|
33
|
+
}
|
|
34
|
+
// Wait for app to settle
|
|
35
|
+
console.log(`ā³ Waiting 3s for app to load...`);
|
|
36
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
37
|
+
// 2. Capture Screenshot
|
|
38
|
+
console.log(`[2/4] Capturing screenshot...`);
|
|
39
|
+
let screenshotBase64;
|
|
40
|
+
let resolution;
|
|
41
|
+
if (platform === "android") {
|
|
42
|
+
const result = await captureAndroidScreen(appId);
|
|
43
|
+
screenshotBase64 = result.screenshot;
|
|
44
|
+
resolution = result.resolution;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
const result = await captureIOSScreenshot();
|
|
48
|
+
screenshotBase64 = result.screenshot;
|
|
49
|
+
resolution = result.resolution;
|
|
50
|
+
}
|
|
51
|
+
if (screenshotBase64) {
|
|
52
|
+
const fileName = `smoke-test-${platform}.png`;
|
|
53
|
+
await fs.writeFile(fileName, Buffer.from(screenshotBase64, 'base64'));
|
|
54
|
+
console.log(`ā
Screenshot saved to ./${fileName} (${resolution.width}x${resolution.height})`);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
throw new Error("Failed to capture screenshot");
|
|
58
|
+
}
|
|
59
|
+
// 3. Get Logs
|
|
60
|
+
console.log(`[3/4] Fetching logs...`);
|
|
61
|
+
let logsCount = 0;
|
|
62
|
+
let logs = [];
|
|
63
|
+
if (platform === "android") {
|
|
64
|
+
const result = await getAndroidLogs(appId, 50);
|
|
65
|
+
logsCount = result.logCount;
|
|
66
|
+
logs = result.logs;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
const result = await getIOSLogs();
|
|
70
|
+
logsCount = result.logCount;
|
|
71
|
+
logs = result.logs;
|
|
72
|
+
}
|
|
73
|
+
console.log(`ā
Retrieved ${logsCount} log lines`);
|
|
74
|
+
// Print last log line as sample
|
|
75
|
+
if (logs.length > 0) {
|
|
76
|
+
console.log(` Sample: "${logs[logs.length - 1].substring(0, 80)}..."`);
|
|
77
|
+
}
|
|
78
|
+
// 4. Terminate App
|
|
79
|
+
console.log(`[4/4] Terminating app...`);
|
|
80
|
+
let termResult;
|
|
81
|
+
if (platform === "android") {
|
|
82
|
+
const result = await terminateAndroidApp(appId);
|
|
83
|
+
termResult = result.appTerminated;
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
const result = await terminateIOSApp(appId);
|
|
87
|
+
termResult = result.appTerminated;
|
|
88
|
+
}
|
|
89
|
+
if (termResult) {
|
|
90
|
+
console.log(`ā
App terminated successfully`);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
throw new Error("Failed to terminate app");
|
|
94
|
+
}
|
|
95
|
+
console.log(`\n⨠Smoke test COMPLETED SUCCESSFULLY! āØ\n`);
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
console.error(`\nā Smoke test FAILED:`, error);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
main();
|
package/smoke-test.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { startAndroidApp, terminateAndroidApp, captureAndroidScreen, getAndroidLogs } from "./src/android.js";
|
|
2
|
+
import { startIOSApp, terminateIOSApp, captureIOSScreenshot, getIOSLogs } from "./src/ios.js";
|
|
3
|
+
import fs from "fs/promises";
|
|
4
|
+
import path from "path";
|
|
5
|
+
|
|
6
|
+
async function main() {
|
|
7
|
+
const args = process.argv.slice(2);
|
|
8
|
+
const platform = args[0] as string; // Cast to string first
|
|
9
|
+
const appId = args[1];
|
|
10
|
+
|
|
11
|
+
if ((platform !== "android" && platform !== "ios") || !appId) {
|
|
12
|
+
console.error("Usage: npx tsx smoke-test.ts <android|ios> <appId>");
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
console.log(`\nš Starting smoke test for ${platform} app: ${appId}`);
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
// 1. Start App
|
|
20
|
+
console.log(`[1/4] Starting app...`);
|
|
21
|
+
let startResult: boolean;
|
|
22
|
+
let launchTimeMs: number;
|
|
23
|
+
|
|
24
|
+
if (platform === "android") {
|
|
25
|
+
const result = await startAndroidApp(appId);
|
|
26
|
+
startResult = result.appStarted;
|
|
27
|
+
launchTimeMs = result.launchTimeMs;
|
|
28
|
+
} else {
|
|
29
|
+
const result = await startIOSApp(appId);
|
|
30
|
+
startResult = result.appStarted;
|
|
31
|
+
launchTimeMs = result.launchTimeMs;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (startResult) {
|
|
35
|
+
console.log(`ā
App started successfully (Launch time: ${launchTimeMs}ms)`);
|
|
36
|
+
} else {
|
|
37
|
+
throw new Error("Failed to start app");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Wait for app to settle
|
|
41
|
+
console.log(`ā³ Waiting 3s for app to load...`);
|
|
42
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
43
|
+
|
|
44
|
+
// 2. Capture Screenshot
|
|
45
|
+
console.log(`[2/4] Capturing screenshot...`);
|
|
46
|
+
let screenshotBase64: string;
|
|
47
|
+
let resolution: { width: number; height: number };
|
|
48
|
+
|
|
49
|
+
if (platform === "android") {
|
|
50
|
+
const result = await captureAndroidScreen();
|
|
51
|
+
screenshotBase64 = result.screenshot;
|
|
52
|
+
resolution = result.resolution;
|
|
53
|
+
} else {
|
|
54
|
+
const result = await captureIOSScreenshot();
|
|
55
|
+
screenshotBase64 = result.screenshot;
|
|
56
|
+
resolution = result.resolution;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (screenshotBase64) {
|
|
60
|
+
const fileName = `smoke-test-${platform}.png`;
|
|
61
|
+
await fs.writeFile(fileName, Buffer.from(screenshotBase64, 'base64'));
|
|
62
|
+
console.log(`ā
Screenshot saved to ./${fileName} (${resolution.width}x${resolution.height})`);
|
|
63
|
+
} else {
|
|
64
|
+
throw new Error("Failed to capture screenshot");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 3. Get Logs
|
|
68
|
+
console.log(`[3/4] Fetching logs...`);
|
|
69
|
+
let logsCount = 0;
|
|
70
|
+
let logs: string[] = [];
|
|
71
|
+
|
|
72
|
+
if (platform === "android") {
|
|
73
|
+
const result = await getAndroidLogs(appId, 50);
|
|
74
|
+
logsCount = result.logCount;
|
|
75
|
+
logs = result.logs;
|
|
76
|
+
} else {
|
|
77
|
+
const result = await getIOSLogs();
|
|
78
|
+
logsCount = result.logCount;
|
|
79
|
+
logs = result.logs;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
console.log(`ā
Retrieved ${logsCount} log lines`);
|
|
83
|
+
// Print last log line as sample
|
|
84
|
+
if (logs.length > 0) {
|
|
85
|
+
console.log(` Sample: "${logs[logs.length - 1].substring(0, 80)}..."`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 4. Terminate App
|
|
89
|
+
console.log(`[4/4] Terminating app...`);
|
|
90
|
+
let termResult: boolean;
|
|
91
|
+
|
|
92
|
+
if (platform === "android") {
|
|
93
|
+
const result = await terminateAndroidApp(appId);
|
|
94
|
+
termResult = result.appTerminated;
|
|
95
|
+
} else {
|
|
96
|
+
const result = await terminateIOSApp(appId);
|
|
97
|
+
termResult = result.appTerminated;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (termResult) {
|
|
101
|
+
console.log(`ā
App terminated successfully`);
|
|
102
|
+
} else {
|
|
103
|
+
throw new Error("Failed to terminate app");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
console.log(`\n⨠Smoke test COMPLETED SUCCESSFULLY! āØ\n`);
|
|
107
|
+
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.error(`\nā Smoke test FAILED:`, error);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
main();
|
|
115
|
+
|
package/src/android.ts
CHANGED
|
@@ -1,48 +1,222 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFile, spawn } from "child_process"
|
|
2
|
+
import { StartAppResponse, GetLogsResponse, CaptureAndroidScreenResponse, TerminateAppResponse, RestartAppResponse, ResetAppDataResponse, DeviceInfo } from "./types.js"
|
|
2
3
|
|
|
3
4
|
const ADB = process.env.ADB_PATH || "adb"
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
else resolve(stdout)
|
|
12
|
-
}
|
|
13
|
-
)
|
|
14
|
-
})
|
|
6
|
+
// Helper to construct ADB args with optional device ID
|
|
7
|
+
function getAdbArgs(args: string[], deviceId?: string): string[] {
|
|
8
|
+
if (deviceId) {
|
|
9
|
+
return ['-s', deviceId, ...args]
|
|
10
|
+
}
|
|
11
|
+
return args
|
|
15
12
|
}
|
|
16
13
|
|
|
17
|
-
|
|
14
|
+
function execAdb(args: string[], deviceId?: string, options: any = {}): Promise<string> {
|
|
15
|
+
const adbArgs = getAdbArgs(args, deviceId)
|
|
18
16
|
return new Promise((resolve, reject) => {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
// Use spawn instead of execFile for better stream control and to avoid potential buffering hangs
|
|
18
|
+
const child = spawn(ADB, adbArgs, options)
|
|
19
|
+
|
|
20
|
+
let stdout = ''
|
|
21
|
+
let stderr = ''
|
|
24
22
|
|
|
25
|
-
|
|
23
|
+
if (child.stdout) {
|
|
24
|
+
child.stdout.on('data', (data) => {
|
|
25
|
+
stdout += data.toString()
|
|
26
|
+
})
|
|
27
|
+
}
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
if (child.stderr) {
|
|
30
|
+
child.stderr.on('data', (data) => {
|
|
31
|
+
stderr += data.toString()
|
|
30
32
|
})
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const timeoutMs = args.includes('logcat') ? 10000 : 2000 // Shorter timeout for metadata queries
|
|
36
|
+
const timeout = setTimeout(() => {
|
|
37
|
+
child.kill()
|
|
38
|
+
reject(new Error(`ADB command timed out after ${timeoutMs}ms: ${args.join(' ')}`))
|
|
39
|
+
}, timeoutMs)
|
|
40
|
+
|
|
41
|
+
child.on('close', (code) => {
|
|
42
|
+
clearTimeout(timeout)
|
|
43
|
+
if (code !== 0) {
|
|
44
|
+
// If there's an actual error (non-zero exit code), reject
|
|
45
|
+
reject(new Error(stderr.trim() || `Command failed with code ${code}`))
|
|
46
|
+
} else {
|
|
47
|
+
// If exit code is 0, resolve with stdout
|
|
48
|
+
resolve(stdout.trim())
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
child.on('error', (err) => {
|
|
53
|
+
clearTimeout(timeout)
|
|
54
|
+
reject(err)
|
|
31
55
|
})
|
|
32
56
|
})
|
|
33
57
|
}
|
|
34
58
|
|
|
35
|
-
|
|
59
|
+
function getDeviceInfo(deviceId: string, metadata: Partial<DeviceInfo> = {}): DeviceInfo {
|
|
60
|
+
return {
|
|
61
|
+
platform: 'android',
|
|
62
|
+
id: deviceId || 'default',
|
|
63
|
+
osVersion: metadata.osVersion || '',
|
|
64
|
+
model: metadata.model || '',
|
|
65
|
+
simulator: metadata.simulator || false
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function getAndroidDeviceMetadata(appId: string, deviceId?: string): Promise<DeviceInfo> {
|
|
36
70
|
try {
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
71
|
+
// Run these in parallel to avoid sequential timeouts
|
|
72
|
+
const [osVersion, model, simOutput] = await Promise.all([
|
|
73
|
+
execAdb(['shell', 'getprop', 'ro.build.version.release'], deviceId).catch(() => ''),
|
|
74
|
+
execAdb(['shell', 'getprop', 'ro.product.model'], deviceId).catch(() => ''),
|
|
75
|
+
execAdb(['shell', 'getprop', 'ro.kernel.qemu'], deviceId).catch(() => '0')
|
|
76
|
+
])
|
|
77
|
+
|
|
78
|
+
const simulator = simOutput === '1'
|
|
79
|
+
return { platform: 'android', id: deviceId || 'default', osVersion, model, simulator }
|
|
80
|
+
} catch (e) {
|
|
81
|
+
return { platform: 'android', id: deviceId || 'default', osVersion: '', model: '', simulator: false }
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function startAndroidApp(appId: string, deviceId?: string): Promise<StartAppResponse> {
|
|
86
|
+
const metadata = await getAndroidDeviceMetadata(appId, deviceId)
|
|
87
|
+
const deviceInfo = getDeviceInfo(deviceId || 'default', metadata)
|
|
88
|
+
|
|
89
|
+
await execAdb(['shell', 'monkey', '-p', appId, '-c', 'android.intent.category.LAUNCHER', '1'], deviceId)
|
|
90
|
+
|
|
91
|
+
return { device: deviceInfo, appStarted: true, launchTimeMs: 1000 }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function terminateAndroidApp(appId: string, deviceId?: string): Promise<TerminateAppResponse> {
|
|
95
|
+
const metadata = await getAndroidDeviceMetadata(appId, deviceId)
|
|
96
|
+
const deviceInfo = getDeviceInfo(deviceId || 'default', metadata)
|
|
97
|
+
|
|
98
|
+
await execAdb(['shell', 'am', 'force-stop', appId], deviceId)
|
|
99
|
+
|
|
100
|
+
return { device: deviceInfo, appTerminated: true }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function restartAndroidApp(appId: string, deviceId?: string): Promise<RestartAppResponse> {
|
|
104
|
+
await terminateAndroidApp(appId, deviceId)
|
|
105
|
+
const startResult = await startAndroidApp(appId, deviceId)
|
|
106
|
+
return {
|
|
107
|
+
device: startResult.device,
|
|
108
|
+
appRestarted: startResult.appStarted,
|
|
109
|
+
launchTimeMs: startResult.launchTimeMs
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function resetAndroidAppData(appId: string, deviceId?: string): Promise<ResetAppDataResponse> {
|
|
114
|
+
const metadata = await getAndroidDeviceMetadata(appId, deviceId)
|
|
115
|
+
const deviceInfo = getDeviceInfo(deviceId || 'default', metadata)
|
|
116
|
+
|
|
117
|
+
const output = await execAdb(['shell', 'pm', 'clear', appId], deviceId)
|
|
118
|
+
|
|
119
|
+
return { device: deviceInfo, dataCleared: output === 'Success' }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export async function getAndroidLogs(appId?: string, lines = 200, deviceId?: string): Promise<GetLogsResponse> {
|
|
123
|
+
const metadata = await getAndroidDeviceMetadata(appId || "", deviceId)
|
|
124
|
+
const deviceInfo = getDeviceInfo(deviceId || 'default', metadata)
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
// We'll skip PID lookup for now to avoid potential hangs with 'pidof' on some emulators
|
|
128
|
+
// and rely on robust string matching against the log line.
|
|
129
|
+
|
|
130
|
+
// Get logs
|
|
131
|
+
const stdout = await execAdb(['logcat', '-d', '-t', lines.toString(), '-v', 'threadtime'], deviceId)
|
|
132
|
+
const allLogs = stdout.split('\n')
|
|
133
|
+
|
|
134
|
+
let filteredLogs = allLogs
|
|
135
|
+
if (appId) {
|
|
136
|
+
// Filter by checking if the line contains the appId string.
|
|
137
|
+
const matchingLogs = allLogs.filter(line => line.includes(appId))
|
|
138
|
+
|
|
139
|
+
if (matchingLogs.length > 0) {
|
|
140
|
+
filteredLogs = matchingLogs
|
|
141
|
+
} else {
|
|
142
|
+
// Fallback: if no logs match the appId, return the raw logs (last N lines)
|
|
143
|
+
// This matches the behavior of the "working" version provided by the user,
|
|
144
|
+
// ensuring they at least see system activity if the app is silent or crashing early.
|
|
145
|
+
filteredLogs = allLogs
|
|
146
|
+
}
|
|
43
147
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
148
|
+
|
|
149
|
+
return { device: deviceInfo, logs: filteredLogs, logCount: filteredLogs.length }
|
|
150
|
+
} catch (e) {
|
|
151
|
+
console.error("Error fetching logs:", e)
|
|
152
|
+
return { device: deviceInfo, logs: [], logCount: 0 }
|
|
47
153
|
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export async function captureAndroidScreen(deviceId?: string): Promise<CaptureAndroidScreenResponse> {
|
|
157
|
+
const metadata = await getAndroidDeviceMetadata("", deviceId)
|
|
158
|
+
const deviceInfo: DeviceInfo = getDeviceInfo(deviceId || 'default', metadata)
|
|
159
|
+
|
|
160
|
+
return new Promise((resolve, reject) => {
|
|
161
|
+
const adbArgs = getAdbArgs(['exec-out', 'screencap', '-p'], deviceId)
|
|
162
|
+
|
|
163
|
+
// Using spawn for screencap as well to ensure consistent process handling
|
|
164
|
+
const child = spawn(ADB, adbArgs)
|
|
165
|
+
|
|
166
|
+
const chunks: Buffer[] = []
|
|
167
|
+
let stderr = ''
|
|
168
|
+
|
|
169
|
+
child.stdout.on('data', (chunk) => {
|
|
170
|
+
chunks.push(Buffer.from(chunk))
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
child.stderr.on('data', (data) => {
|
|
174
|
+
stderr += data.toString()
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
const timeout = setTimeout(() => {
|
|
178
|
+
child.kill()
|
|
179
|
+
reject(new Error(`ADB screencap timed out after 10s`))
|
|
180
|
+
}, 10000)
|
|
181
|
+
|
|
182
|
+
child.on('close', (code) => {
|
|
183
|
+
clearTimeout(timeout)
|
|
184
|
+
if (code !== 0) {
|
|
185
|
+
reject(new Error(stderr.trim() || `Screencap failed with code ${code}`))
|
|
186
|
+
return
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const screenshotBuffer = Buffer.concat(chunks)
|
|
190
|
+
const screenshotBase64 = screenshotBuffer.toString('base64')
|
|
191
|
+
|
|
192
|
+
// Get resolution
|
|
193
|
+
execAdb(['shell', 'wm', 'size'], deviceId)
|
|
194
|
+
.then(sizeStdout => {
|
|
195
|
+
let width = 0
|
|
196
|
+
let height = 0
|
|
197
|
+
const match = sizeStdout.match(/Physical size: (\d+)x(\d+)/)
|
|
198
|
+
if (match) {
|
|
199
|
+
width = parseInt(match[1], 10)
|
|
200
|
+
height = parseInt(match[2], 10)
|
|
201
|
+
}
|
|
202
|
+
resolve({
|
|
203
|
+
device: deviceInfo,
|
|
204
|
+
screenshot: screenshotBase64,
|
|
205
|
+
resolution: { width, height }
|
|
206
|
+
})
|
|
207
|
+
})
|
|
208
|
+
.catch(() => {
|
|
209
|
+
resolve({
|
|
210
|
+
device: deviceInfo,
|
|
211
|
+
screenshot: screenshotBase64,
|
|
212
|
+
resolution: { width: 0, height: 0 }
|
|
213
|
+
})
|
|
214
|
+
})
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
child.on('error', (err) => {
|
|
218
|
+
clearTimeout(timeout)
|
|
219
|
+
reject(err)
|
|
220
|
+
})
|
|
221
|
+
})
|
|
48
222
|
}
|