Skip to content

Commit 7781e47

Browse files
feat: implemet video and screenshot recordig
1 parent 8bad93c commit 7781e47

File tree

6 files changed

+104
-39
lines changed

6 files changed

+104
-39
lines changed

lib/android-controller.d.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ export declare class AndroidController {
99
private static LIST_AVDS;
1010
private static _emulatorIds;
1111
static getAllDevices(verbose?: boolean): Promise<Map<string, Array<IDevice>>>;
12-
static getPhysicalDensity(token: string): number;
13-
static getPixelsOffset(token: string): number;
12+
static getPhysicalDensity(device: IDevice): number;
13+
static getPixelsOffset(device: IDevice): number;
1414
static startEmulator(emulator: IDevice, options?: string, logPath?: any): Promise<IDevice>;
1515
static unlock(token: any, password?: any): void;
1616
/**
@@ -30,9 +30,11 @@ export declare class AndroidController {
3030
static installApp(device: IDevice, testAppName: any): string;
3131
static uninstallApp(device: any, appId: any): void;
3232
static stopApp(device: IDevice, appId: any): void;
33+
static getScreenshot(device: IDevice, dir: any, fileName: any): Promise<string>;
34+
static recordVideo(device: IDevice, dir: any, fileName: any, callback: () => Promise<any>): Promise<void>;
3335
static getPackageId(appFullName: any): string;
34-
static pullFile(device: IDevice, remotePath: any, destinationFolder: any): any;
35-
static pushFile(device: IDevice, localPath: any, remotePath: any): any;
36+
static pullFile(device: IDevice, remotePath: any, destinationFile: any): any;
37+
static pushFile(device: IDevice, fileName: any, deviceParh: any): any;
3638
private static getAaptPath();
3739
private static runAaptCommand(appFullName, grep);
3840
private static startEmulatorProcess(emulator, options);

lib/android-controller.ts

Lines changed: 52 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { spawn, ChildProcess } from "child_process";
2-
import { resolve, delimiter, sep } from "path";
3-
import { existsSync } from "fs";
2+
import { resolve, delimiter, sep, dirname } from "path";
3+
import { existsSync, copyFile, copyFileSync } from "fs";
44
import { Platform, DeviceType, Status } from "./enums";
55
import { IDevice, Device } from "./device";
66
import {
@@ -36,12 +36,12 @@ export class AndroidController {
3636
return devices;
3737
}
3838

39-
public static getPhysicalDensity(token: string) {
40-
return parseInt(executeCommand(AndroidController.ADB + " -s emulator-" + token + " shell wm density").split(":")[1]) * 0.01;
39+
public static getPhysicalDensity(device: IDevice) {
40+
return parseInt(AndroidController.executeAdbCommand(device, "shell wm density").split(":")[1]) * 0.01;
4141
}
4242

43-
public static getPixelsOffset(token: string) {
44-
return Math.floor(OFFSET_DI_PIXELS * AndroidController.getPhysicalDensity(token));
43+
public static getPixelsOffset(device: IDevice) {
44+
return Math.floor(OFFSET_DI_PIXELS * AndroidController.getPhysicalDensity(device));
4545
}
4646

4747
public static async startEmulator(emulator: IDevice, options = "", logPath = undefined): Promise<IDevice> {
@@ -61,8 +61,8 @@ export class AndroidController {
6161
emulator.startedAt = Date.now();
6262
}
6363

64-
const density = AndroidController.getPhysicalDensity(emulator.token);
65-
const offsetPixels = AndroidController.getPixelsOffset(emulator.token);
64+
const density = AndroidController.getPhysicalDensity(emulator);
65+
const offsetPixels = AndroidController.getPixelsOffset(emulator);
6666
emulator.config = {
6767
density: density,
6868
offsetPixels: offsetPixels,
@@ -90,9 +90,8 @@ export class AndroidController {
9090
public static kill(emulator: IDevice) {
9191
let isAlive: boolean = true;
9292
if (emulator.type === DeviceType.EMULATOR) {
93-
9493
if (emulator.token) {
95-
executeCommand(AndroidController.ADB + " -s " + DeviceType.EMULATOR + "-" + emulator.token + " emu kill");
94+
AndroidController.executeAdbCommand(emulator, " emu kill");
9695
isAlive = false;
9796
}
9897

@@ -208,11 +207,39 @@ export class AndroidController {
208207
AndroidController.executeAdbCommand(device, `shell am force-stop ${appId}`);
209208
}
210209

210+
public static async getScreenshot(device: IDevice, dir, fileName) {
211+
fileName = fileName.endsWith(".pne") ? fileName : `${fileName}.png`;
212+
const pathToScreenshotPng = `/sdcard/${fileName}`;
213+
AndroidController.executeAdbCommand(device, `shell screencap ${pathToScreenshotPng}`);
214+
const fullFileName = resolve(dir, fileName);
215+
AndroidController.pullFile(device, pathToScreenshotPng, fullFileName);
216+
return fullFileName;
217+
}
218+
219+
public static async recordVideo(device: IDevice, dir, fileName, callback: () => Promise<any>) {
220+
new Promise(async (res, reject) => {
221+
const videoFileName = `${fileName}.mp4`;
222+
const pathToVideo = resolve(dir, res);
223+
const devicePath = `/sdcard/${videoFileName}`;
224+
const prefix = AndroidController.gettokenPrefix(device.type);
225+
const videoRecoringProcess = spawn(AndroidController.ADB, ['-s', prefix + device.token, 'screenrecord', devicePath]);
226+
callback().then((result) => {
227+
videoRecoringProcess.kill("SIGINT");
228+
AndroidController.pullFile(device, devicePath, pathToVideo);
229+
console.log(result);
230+
res(pathToVideo);
231+
}).catch((error) => {
232+
reject(error);
233+
});
234+
});
235+
}
236+
211237
public static getPackageId(appFullName) {
212238
return AndroidController.runAaptCommand(appFullName, "package:");
213239
}
214-
215-
public static pullFile(device: IDevice, remotePath, destinationFolder) {
240+
241+
public static pullFile(device: IDevice, remotePath, destinationFile) {
242+
const destinationFolder = dirname(destinationFile);
216243
// Verify remotePath
217244
const remoteBasePath = remotePath.substring(0, remotePath.lastIndexOf("/"));
218245
const sdcardFiles = AndroidController.executeAdbCommand(device, " shell ls -la " + remoteBasePath);
@@ -228,7 +255,7 @@ export class AndroidController {
228255
}
229256

230257
// Pull files
231-
const output = AndroidController.executeAdbCommand(device, "pull " + remotePath + " " + destinationFolder);
258+
const output = AndroidController.executeAdbCommand(device, "pull " + remotePath + " " + destinationFile);
232259
console.log(output);
233260
const o = output.toLowerCase();
234261
if ((o.includes("error")) || (o.includes("failed")) || (o.includes("does not exist"))) {
@@ -237,18 +264,18 @@ export class AndroidController {
237264
console.log("Error: " + output);
238265
return undefined;
239266
} else {
240-
console.log(remotePath + " transferred to " + destinationFolder);
267+
console.log(remotePath + " transferred to " + destinationFile);
241268
}
242269

243-
return destinationFolder;
270+
return destinationFile;
244271
}
245272

246-
public static pushFile(device: IDevice, localPath, remotePath) {
273+
public static pushFile(device: IDevice, fileName, deviceParh) {
247274

248275
let output = AndroidController.executeAdbCommand(device, "shell mount -o rw,remount -t rootfs /");
249276

250277
// Verify remotePath
251-
const remoteBasePath = remotePath.substring(0, remotePath.lastIndexOf("/"));
278+
const remoteBasePath = deviceParh.substring(0, deviceParh.lastIndexOf("/"));
252279
const sdcardFiles = AndroidController.executeAdbCommand(device, "shell ls -la " + remoteBasePath);
253280
if (sdcardFiles.includes("No such file or directory")) {
254281
const error = remoteBasePath + " does not exist.";
@@ -257,28 +284,25 @@ export class AndroidController {
257284
}
258285

259286
// Verify localPath
260-
localPath = localPath.replace("/", sep);
261-
localPath = localPath.replace("\\", sep);
262-
const localFilePath = localPath;
263-
if (!existsSync(localFilePath)) {
264-
const error = localPath + " does not exist.";
287+
fileName = fileName.replace("/", sep).replace("\\", sep);
288+
if (!existsSync(fileName)) {
289+
const error = fileName + " does not exist.";
265290
console.log(error);
266291
return undefined;
267292
}
268293

269294
// Push files
270-
output = AndroidController.executeAdbCommand(device, "push " + localFilePath + " " + remotePath);
295+
output = AndroidController.executeAdbCommand(device, "push " + fileName + " " + deviceParh);
271296
console.log(output);
272297
if ((output.toLowerCase().includes("error")) || (output.toLowerCase().includes("failed"))) {
273-
const error = "Failed to transfer " + localPath + " to " + remotePath;
274-
console.log(error);
275-
console.log("Error: " + output);
298+
console.log("Failed to transfer " + fileName + " to " + deviceParh);
299+
console.log("Error: ", output);
276300
return undefined;
277301
} else {
278-
console.log(localPath + " transferred to " + remotePath);
302+
console.log(fileName + " transferred to " + deviceParh);
279303
}
280304

281-
return localFilePath;
305+
return fileName;
282306
}
283307

284308
private static getAaptPath() {

lib/device-controller.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ export declare class DeviceController {
1212
static kill(device: IDevice): Promise<void>;
1313
static killAll(type: DeviceType): void;
1414
static filter(devices: Array<IDevice>, searchQuery: any): IDevice[];
15+
static getScreenshot(device: IDevice, dir: any, fileName: any): Promise<string>;
16+
static recordVideo(device: IDevice, dir: any, fileName: any, callback: () => Promise<any>): Promise<any>;
1517
private static copyProperties(from);
1618
private static getAllDevicesByPlatform(platform, verbose?);
1719
private static getDevicesByPlatformAndName(platform, name?, verbose?);

lib/device-controller.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,22 @@ export class DeviceController {
9494
})
9595
}
9696

97+
public static async getScreenshot(device: IDevice, dir, fileName) {
98+
if (device.type === DeviceType.EMULATOR || device.platform === Platform.ANDROID) {
99+
return AndroidController.getScreenshot(device, dir, fileName);
100+
} else {
101+
return IOSController.getScreenshot(device, dir, fileName);
102+
}
103+
}
104+
105+
public static async recordVideo(device: IDevice, dir, fileName, callback: () => Promise<any>): Promise<any> {
106+
if (device.type === DeviceType.EMULATOR || device.platform === Platform.ANDROID) {
107+
return AndroidController.recordVideo(device, dir, fileName, callback);
108+
} else {
109+
return IOSController.recordVideo(device, dir, fileName, callback);
110+
}
111+
}
112+
97113
private static copyProperties(from: IDevice) {
98114
const to: IDevice = { platform: undefined, token: undefined, name: undefined, type: undefined }
99115
if (!from || from === null || from === {} || Object.getOwnPropertyNames(from).length <= 0) {

lib/ios-controller.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ export declare class IOSController {
2626
static parseRealDevices(devices?: Map<string, IDevice[]>): Map<string, IDevice[]>;
2727
static getSimLocation(token: any): string;
2828
static filterDeviceBy(...args: any[]): IDevice[];
29-
getScreenshot(dir: any, token: any): Promise<string>;
29+
static getScreenshot(device: IDevice, dir: any, fileName: any): Promise<string>;
30+
static recordVideo(device: IDevice, dir: any, fileName: any, callback: () => Promise<any>): Promise<any>;
3031
private static checkIfSimulatorIsBooted(udid, timeout);
3132
private static getIOSPackageId(device, fullAppName);
3233
/**

lib/ios-controller.ts

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -275,12 +275,32 @@ export class IOSController {
275275
return result;
276276
}
277277

278-
public async getScreenshot(dir, token) {
279-
let pathToScreenshotPng = resolve(dir, `screenshot-${token}.png`);
280-
executeCommand(`${IOSController.SIMCTL} io ${token} 'screenshot' ${pathToScreenshotPng}`);
281-
let screenshotImg = readFileSync(pathToScreenshotPng);
282-
//await fs.rimraf(pathToScreenshotPng);
283-
return screenshotImg.toString('base64');
278+
public static async getScreenshot(device: IDevice, dir, fileName) {
279+
const pathToScreenshotPng = resolve(dir, `${fileName}.png`);
280+
if (device.type === DeviceType.DEVICE) {
281+
executeCommand(`idevicescreenshot -u ${device.token} ${pathToScreenshotPng}`);
282+
} else {
283+
executeCommand(`${IOSController.SIMCTL} io ${device.token} screenshot ${pathToScreenshotPng}`);
284+
}
285+
286+
return pathToScreenshotPng;
287+
}
288+
289+
public static async recordVideo(device: IDevice, dir, fileName, callback: () => Promise<any>): Promise<any> {
290+
if (device.type === DeviceType.DEVICE) {
291+
return Promise.resolve("");
292+
}
293+
return new Promise(async (res, reject) => {
294+
const pathToVideo = resolve(dir, `${fileName}.mp4`);
295+
const videoRecoringProcess = spawn(IOSController.SIMCTL, ['io', device.token, 'recordVideo', pathToVideo]);
296+
callback().then((result) => {
297+
videoRecoringProcess.kill("SIGINT");
298+
console.log(result);
299+
res(pathToVideo);
300+
}).catch((error) => {
301+
reject(error);
302+
});
303+
});
284304
}
285305

286306
// Should find a better way

0 commit comments

Comments
 (0)