diff --git a/index.ts b/index.ts index 9e783a7..c63b3d8 100644 --- a/index.ts +++ b/index.ts @@ -7,6 +7,7 @@ import { shutdown, findFreePort } from "./lib/utils"; import * as frameComparerHelper from "./lib/frame-comparer"; import { FrameComparer } from "./lib/frame-comparer"; import { DeviceManager } from "./lib/device-manager"; +import { DeviceController } from "mobile-devices-controller"; export { AppiumDriver } from "./lib/appium-driver"; export { AppiumServer } from "./lib/appium-server"; @@ -49,6 +50,10 @@ export async function stopServer() { if (appiumServer && appiumServer.server && !appiumServer.server.killed) { await appiumServer.stop(); } + + if (nsCapabilities.cleanApp) { + await DeviceController.uninstallApp(nsCapabilities.device, nsCapabilities.appPath); + } }; export async function createDriver() { diff --git a/lib/appium-driver.ts b/lib/appium-driver.ts index 7846a26..de5935e 100644 --- a/lib/appium-driver.ts +++ b/lib/appium-driver.ts @@ -31,7 +31,8 @@ import { calculateOffset, scroll, findFreePort, - wait + wait, + copy } from "./utils"; import { INsCapabilities } from "./interfaces/ns-capabilities"; @@ -42,6 +43,7 @@ import { ImageOptions } from "./image-options" import { unlinkSync, writeFileSync } from "fs"; import * as webdriverio from "webdriverio"; import { DeviceManager } from "../lib/device-manager"; +import { extname, basename } from "path"; export class AppiumDriver { private static pngFileExt = '.png'; @@ -451,6 +453,9 @@ export class AppiumDriver { await this._imageHelper.clipRectangleImage(rect, pathActualImage); } + const pathActualImageToReportsFolder = resolve(this._logPath, basename(pathActualImage)); + copy(pathActualImage, pathActualImageToReportsFolder, false); + console.log("Remove the 'actual' suffix to continue using the image as expected one ", pathExpectedImage); return false; } diff --git a/lib/device-manager.d.ts b/lib/device-manager.d.ts index 858ee0f..df6c9d0 100644 --- a/lib/device-manager.d.ts +++ b/lib/device-manager.d.ts @@ -11,7 +11,7 @@ export declare class DeviceManager implements IDeviceManager { static kill(device: IDevice): Promise; private static getDefaultDevice; static setDontKeepActivities(args: INsCapabilities, driver: any, value: any): Promise; - static executeShellCommand(driver: IDevice, commandAndargs: { + static executeShellCommand(driver: any, commandAndargs: { command: string; "args": Array; }): Promise; diff --git a/lib/device-manager.ts b/lib/device-manager.ts index cf44ef8..fb2d8d9 100644 --- a/lib/device-manager.ts +++ b/lib/device-manager.ts @@ -6,7 +6,6 @@ import { shutdown, executeCommand } from "./utils"; -import * as child_process from "child_process"; import { INsCapabilities } from "./interfaces/ns-capabilities"; import { IDeviceManager } from "./interfaces/device-manager"; @@ -80,8 +79,8 @@ export class DeviceManager implements IDeviceManager { await DeviceController.startDevice(device); console.log("Started device: ", device); } else { - console.log("Device is already started", device); - if (!args.reuseDevice && device.type !== DeviceType.EMULATOR && device.type !== DeviceType.SIMULATOR) { + device.type === DeviceType.DEVICE ? console.log("Device is connected:", device) : console.log("Device is already started", device) + if (!args.reuseDevice && device.type !== DeviceType.DEVICE) { console.log("Since is it specified without reusing, the device would be shut down and restart!"); DeviceController.kill(device); await DeviceController.startDevice(device); @@ -91,6 +90,10 @@ export class DeviceManager implements IDeviceManager { DeviceManager._emulators.set(args.runType, device); + if (!device || !device.token) { + console.error("Check appium capabilites and provide correct device options!"); + process.exit(1); + } return device; } @@ -149,7 +152,7 @@ export class DeviceManager implements IDeviceManager { } } - public static async executeShellCommand(driver: IDevice, commandAndargs: { command: string, "args": Array }) { + public static async executeShellCommand(driver, commandAndargs: { command: string, "args": Array }) { if (driver.platform.toLowerCase() === Platform.ANDROID) { const output = await driver.execute("mobile: shell", commandAndargs); return output; diff --git a/lib/frame-comparer.d.ts b/lib/frame-comparer.d.ts index 4d457fd..c787700 100644 --- a/lib/frame-comparer.d.ts +++ b/lib/frame-comparer.d.ts @@ -1,15 +1,13 @@ import { INsCapabilities } from "./interfaces/ns-capabilities"; -import * as frComparer from "frame-comparer"; export declare function loadFrameComparer(nsCapabilities: INsCapabilities): FrameComparer; export declare class FrameComparer { private _nsCapabilities; private _storage; private _logPath; - private _frameComparer; private _framesGeneralName; private _cropImageRect; - constructor(_nsCapabilities: INsCapabilities, _storage: string, _logPath: string, _frameComparer: frComparer.FrameComparer); - processVideo(videoFullName: any, framesGeneralName?: string, videoTempStorage?: string): Promise; - compareFrameRanges(imageFrameCount: number, startRange: any, endRange: any, logImageComparisonResults?: boolean, tollerancePixels?: number, verbose?: boolean): Promise; - compareFrames(imageFrameCount: number, tolleranceRange?: number, tollerancePixels?: number, logImageComparisonResults?: boolean, verbose?: boolean): Promise; + constructor(_nsCapabilities: INsCapabilities, _storage: string, _logPath: string); + processVideo(videoFullName: any, framesGeneralName?: string, videoTempStorage?: string): Promise; + compareFrameRanges(frames: Array, imageFrameCount: number, startRange: any, endRange: any, logImageComparisonResults?: boolean, tollerancePixels?: number, verbose?: boolean): Promise; + compareFrames(frames: Array, imageFrameCount: number, tolleranceRange?: number, tollerancePixels?: number, logImageComparisonResults?: boolean, verbose?: boolean): Promise; } diff --git a/lib/frame-comparer.ts b/lib/frame-comparer.ts index e8ef3f7..133e1a8 100644 --- a/lib/frame-comparer.ts +++ b/lib/frame-comparer.ts @@ -7,10 +7,9 @@ import { ImageHelper } from "./image-helper"; export function loadFrameComparer(nsCapabilities: INsCapabilities) { try { - const frameComparer = frComparer.createFrameComparer(); const storage = getStorageByDeviceName(nsCapabilities); const logPath = getReportPath(nsCapabilities); - return new FrameComparer(nsCapabilities, storage, logPath, frameComparer); + return new FrameComparer(nsCapabilities, storage, logPath); } catch (error) { console.error("In order to use frame comaprer, please read carefully https://github.com/SvetoslavTsenov/frame-comparer/blob/master/README.md for dependecies that are required!"); } @@ -20,25 +19,25 @@ export class FrameComparer { private _framesGeneralName: string = "frame"; private _cropImageRect: IRectangle; - constructor(private _nsCapabilities: INsCapabilities, private _storage: string, private _logPath: string, private _frameComparer: frComparer.FrameComparer) { + constructor(private _nsCapabilities: INsCapabilities, private _storage: string, private _logPath: string) { this._cropImageRect = ImageHelper.cropImageDefault(this._nsCapabilities); } async processVideo(videoFullName, framesGeneralName?: string, videoTempStorage = "tempFramesFolder") { this._framesGeneralName = framesGeneralName || this._framesGeneralName; this._framesGeneralName = this._framesGeneralName.replace(/\s/gi, ""); - await this._frameComparer.processVideo(videoFullName, videoTempStorage, this._framesGeneralName); + return await frComparer.FrameComparer.processVideo(videoFullName, videoTempStorage, this._framesGeneralName); } - async compareFrameRanges(imageFrameCount: number, startRange, endRange, logImageComparisonResults: boolean = false, tollerancePixels = 0.1, verbose = false): Promise { - const result = await this._frameComparer.compareImageFromVideo(resolve(this._storage, `${this._framesGeneralName}${imageFrameCount}.png`), this._logPath, startRange, endRange, tollerancePixels, this._cropImageRect, true, logImageComparisonResults, verbose); + async compareFrameRanges(frames: Array, imageFrameCount: number, startRange, endRange, logImageComparisonResults: boolean = false, tollerancePixels = 0.1, verbose = false): Promise { + const result = await frComparer.FrameComparer.compareImageFromVideo(frames, resolve(this._storage, `${this._framesGeneralName}${imageFrameCount}.png`), this._logPath, startRange, endRange, tollerancePixels, true, logImageComparisonResults, this._cropImageRect, verbose); return result; } - async compareFrames(imageFrameCount: number, tolleranceRange = 3, tollerancePixels = 0.1, logImageComparisonResults: boolean = false, verbose = false): Promise { + async compareFrames(frames: Array, imageFrameCount: number, tolleranceRange = 3, tollerancePixels = 0.1, logImageComparisonResults: boolean = false, verbose = false): Promise { const start = imageFrameCount - tolleranceRange > 0 ? imageFrameCount - tolleranceRange : 0; const end = imageFrameCount + tolleranceRange; - const result = await this.compareFrameRanges(imageFrameCount, start, end, logImageComparisonResults, tollerancePixels) + const result = await this.compareFrameRanges(frames, imageFrameCount, start, end, logImageComparisonResults, tollerancePixels) return result; } } \ No newline at end of file diff --git a/lib/image-helper.d.ts b/lib/image-helper.d.ts index 5b2d5df..8466480 100644 --- a/lib/image-helper.d.ts +++ b/lib/image-helper.d.ts @@ -10,7 +10,7 @@ export declare class ImageHelper { blockOutAreas: IRectangle[]; imageOutputLimit(): ImageOptions; thresholdType(): ImageOptions; - threshold(thresholdType: any): 10 | 0.01; + threshold(thresholdType: any): 0.01 | 10; delta(): number; static cropImageDefault(_args: INsCapabilities): { x: number; diff --git a/lib/interfaces/ns-capabilities.d.ts b/lib/interfaces/ns-capabilities.d.ts index 58fd135..7595107 100644 --- a/lib/interfaces/ns-capabilities.d.ts +++ b/lib/interfaces/ns-capabilities.d.ts @@ -31,4 +31,5 @@ export interface INsCapabilities { path: string; automationName: AutomationName; relaxedSecurity: boolean; + cleanApp: boolean; } diff --git a/lib/interfaces/ns-capabilities.ts b/lib/interfaces/ns-capabilities.ts index 6b4e446..76ca8da 100644 --- a/lib/interfaces/ns-capabilities.ts +++ b/lib/interfaces/ns-capabilities.ts @@ -32,5 +32,6 @@ export interface INsCapabilities { wdaLocalPort: number; path: string; automationName: AutomationName; - relaxedSecurity: boolean + relaxedSecurity: boolean, + cleanApp: boolean, } \ No newline at end of file diff --git a/lib/ns-capabilities.d.ts b/lib/ns-capabilities.d.ts index 1ecd64f..4f6c592 100644 --- a/lib/ns-capabilities.d.ts +++ b/lib/ns-capabilities.d.ts @@ -27,6 +27,7 @@ export declare class NsCapabilities implements INsCapabilities { private _ignoreDeviceController; private _wdaLocalPort; private _relaxedSecurity; + private _cleanApp; private exceptions; constructor(); readonly path: string; @@ -55,6 +56,7 @@ export declare class NsCapabilities implements INsCapabilities { device: IDevice; readonly emulatorOptions: string; readonly relaxedSecurity: boolean; + readonly cleanApp: boolean; private isAndroidPlatform; private setAutomationName; tryGetAndroidApiLevel(): number; diff --git a/lib/ns-capabilities.ts b/lib/ns-capabilities.ts index 6c833b5..ad44b6e 100644 --- a/lib/ns-capabilities.ts +++ b/lib/ns-capabilities.ts @@ -31,6 +31,7 @@ export class NsCapabilities implements INsCapabilities { private _ignoreDeviceController: boolean; private _wdaLocalPort: number; private _relaxedSecurity: boolean; + private _cleanApp: boolean; private exceptions: Array = new Array(); constructor() { @@ -56,6 +57,7 @@ export class NsCapabilities implements INsCapabilities { this._wdaLocalPort = parser.wdaLocalPort; this._path = parser.path; this._relaxedSecurity = parser.relaxedSecurity; + this._cleanApp = parser.cleanApp; this.setAutomationName(); this.resolveApplication(); this.checkMandatoryCapabiliies(); @@ -91,6 +93,7 @@ export class NsCapabilities implements INsCapabilities { set device(device: IDevice) { this._device = device; } get emulatorOptions() { return (this._emulatorOptions || "-wipe-data -gpu on") } get relaxedSecurity() { return this._relaxedSecurity } + get cleanApp() { return this._cleanApp; } private isAndroidPlatform() { return this._appiumCaps.platformName.toLowerCase().includes("android"); } diff --git a/lib/parser.d.ts b/lib/parser.d.ts index 4d4efe0..29aee4e 100644 --- a/lib/parser.d.ts +++ b/lib/parser.d.ts @@ -1,2 +1,2 @@ export declare const capabilitiesName = "appium.capabilities.json"; -export declare const projectDir: any, projectBinary: any, pluginRoot: any, pluginBinary: any, port: any, verbose: any, appiumCapsLocation: any, testFolder: any, runType: any, isSauceLab: any, appPath: any, storage: any, testReports: any, reuseDevice: any, devMode: any, ignoreDeviceController: any, wdaLocalPort: any, path: any, relaxedSecurity: any; +export declare const projectDir: any, projectBinary: any, pluginRoot: any, pluginBinary: any, port: any, verbose: any, appiumCapsLocation: any, testFolder: any, runType: any, isSauceLab: any, appPath: any, storage: any, testReports: any, reuseDevice: any, devMode: any, ignoreDeviceController: any, wdaLocalPort: any, path: any, relaxedSecurity: any, cleanApp: boolean; diff --git a/lib/parser.ts b/lib/parser.ts index aeeb647..9b5412f 100644 --- a/lib/parser.ts +++ b/lib/parser.ts @@ -21,6 +21,7 @@ const config = (() => { .option("reuseDevice", { describe: "Reusing device if available.", type: "boolean", default: false }) .option("devMode", { alias: "dev-mode", describe: "Will skipp app instalation and will reuse the one installed on device!", type: "boolean", default: false }) .option("ignoreDeviceController", { alias: "i-ns-device-controller", describe: "Use default appium options for running emulatos/ simulators.", type: "boolean", default: false }) + .option("cleanApp", { alias: "c", describe: "Uninstall app after test are finished", type: "boolean", default: false }) .help() .argv; @@ -55,6 +56,7 @@ const config = (() => { testReports: options.testReports || process.env.npm_config_TEST_REPORTS || process.env.TEST_REPORTS, reuseDevice: options.devMode ? true : options.reuseDevice || process.env.npm_config_REUSE_DEVICE || process.env.REUSE_DEVICE, devMode: options.devMode || process.env.npm_config_REUSE_APP, + cleanApp: !options.devMode && options.cleanApp && !options.sauceLab && !options.ignoreDeviceController, ignoreDeviceController: options.ignoreDeviceController, path: options.path, relaxedSecurity: options.relaxedSecurity @@ -82,5 +84,6 @@ export const { ignoreDeviceController, wdaLocalPort, path, - relaxedSecurity + relaxedSecurity, + cleanApp } = config;