Skip to content

Commit 0d2c9fd

Browse files
feat: image-helper (#236)
1 parent ef270c9 commit 0d2c9fd

File tree

6 files changed

+162
-22
lines changed

6 files changed

+162
-22
lines changed

Diff for: index.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export { logInfo, logError, logWarn } from "./lib/utils";
2222
export { ITestReporter } from "./lib/interfaces/test-reporter";
2323
export { screencapture } from "./lib/helpers/screenshot-manager";
2424
export { LogImageType } from "./lib/enums/log-image-type";
25+
export { ImageHelper, IImageCompareOptions } from "./lib/image-helper";
2526
export declare const nsCapabilities: INsCapabilities;
2627
export declare function startServer(port?: number, deviceManager?: IDeviceManager): Promise<AppiumServer>;
2728
export declare function stopServer(): Promise<void>;

Diff for: index.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export { logInfo, logError, logWarn } from "./lib/utils";
3232
export { ITestReporter } from "./lib/interfaces/test-reporter";
3333
export { screencapture } from "./lib/helpers/screenshot-manager";
3434
export { LogImageType } from "./lib/enums/log-image-type";
35+
export { ImageHelper, IImageCompareOptions } from "./lib/image-helper";
3536

3637
export const nsCapabilities: INsCapabilities = new NsCapabilities(parser);
3738

@@ -139,7 +140,7 @@ const killProcesses = async (code) => {
139140
process.removeAllListeners();
140141
try {
141142
//if (isWin() && process) {
142-
// process.exit(0);
143+
// process.exit(0);
143144
//}
144145
} catch (error) { }
145146
}
@@ -152,9 +153,9 @@ const attachToExitProcessHookup = (processToAttach, processName) => {
152153
}
153154
signals.forEach(function (sig) {
154155
processToAttach.once(sig, async function () {
155-
await killProcesses(sig);
156-
console.log(`Exited from ${processName}`);
157-
processToAttach.removeListener(sig, killProcesses);
156+
await killProcesses(sig);
157+
console.log(`Exited from ${processName}`);
158+
processToAttach.removeListener(sig, killProcesses);
158159
});
159160
});
160161
}

Diff for: lib/appium-driver.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export declare class AppiumDriver {
4040
readonly isIOS: boolean;
4141
readonly driver: any;
4242
/**
43-
* Get the storage where test results from image comparisson is logged It will be reports/app nam/device name
43+
* Get the storage where test results from image comparison is logged It will be reports/app nam/device name
4444
*/
4545
readonly reportsPath: string;
4646
/**

Diff for: lib/appium-driver.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export class AppiumDriver {
6767

6868
private constructor(private _driver: any, private _wd, private _webio: any, private _driverConfig, private _args: INsCapabilities) {
6969
this._elementHelper = new ElementHelper(this._args);
70-
this._imageHelper = new ImageHelper(this._args);
70+
this._imageHelper = new ImageHelper(this._args, this);
7171
this._isAlive = true;
7272
this._locators = new Locator(this._args);
7373
this._webio.requestHandler.sessionID = this._driver.sessionID;
@@ -126,7 +126,7 @@ export class AppiumDriver {
126126
}
127127

128128
/**
129-
* Get the storage where test results from image comparisson is logged It will be reports/app nam/device name
129+
* Get the storage where test results from image comparison is logged It will be reports/app nam/device name
130130
*/
131131
get reportsPath() {
132132
return this._logPath;
@@ -239,7 +239,7 @@ export class AppiumDriver {
239239
prepareApp(args);
240240
if (!args.device) {
241241
if (args.isAndroid) {
242-
args.device = DeviceManager.getDefaultDevice(args, sessionInfo.capabilities.deviceName, sessionInfo.capabilities.deviceUDID.replace("emulator-", ""), sessionInfo.capabilities.deviceUDID.includes("emulator") ? DeviceType.EMULATOR : DeviceType.SIMULATOR, sessionInfo.capabilities.desired.platformVersion || sessionInfo.capabilities.platformVersion);
242+
args.device = DeviceManager.getDefaultDevice(args, sessionInfo.capabilities.desired.deviceName, sessionInfo.capabilities.deviceUDID.replace("emulator-", ""), sessionInfo.capabilities.deviceUDID.includes("emulator") ? DeviceType.EMULATOR : DeviceType.SIMULATOR, sessionInfo.capabilities.desired.platformVersion || sessionInfo.capabilities.platformVersion);
243243
} else {
244244
args.device = DeviceManager.getDefaultDevice(args);
245245
}
@@ -589,9 +589,9 @@ export class AppiumDriver {
589589

590590
// First time capture
591591
if (!existsSync(pathExpectedImage)) {
592-
const pathActualImage = resolvePath(this._storageByDeviceName, imageName.replace(".", "_actual."));
593-
if (this.imageHelper.waitOnCreatingInitialSnapshot > 0) {
594-
await this.wait(this.imageHelper.waitOnCreatingInitialSnapshot);
592+
const pathActualImage = resolvePath(this._storageByDeviceName, this.imageHelper.options.preserveImageName ? imageName : imageName.replace(".", "_actual."));
593+
if (this.imageHelper.options.waitOnCreatingInitialSnapshot > 0) {
594+
await this.wait(this.imageHelper.options.waitOnCreatingInitialSnapshot);
595595
}
596596
await this.takeScreenshot(pathActualImage);
597597

Diff for: lib/image-helper.d.ts

+32-4
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,46 @@
11
import { ImageOptions } from "./image-options";
22
import { INsCapabilities } from "./interfaces/ns-capabilities";
33
import { IRectangle } from "./interfaces/rectangle";
4+
import { UIElement } from "./ui-element";
5+
import { AppiumDriver } from "./appium-driver";
6+
export interface IImageCompareOptions {
7+
imageName?: string;
8+
timeOutSeconds?: number;
9+
tolerance?: number;
10+
toleranceType?: ImageOptions;
11+
/**
12+
* wait miliseconds before capture creating image
13+
*/
14+
waitOnCreatingInitialSnapshot?: number;
15+
/**
16+
* This property will keep image name as it is and will not add _actual postfix on initial capture
17+
*/
18+
preserveImageName?: boolean;
19+
}
420
export declare class ImageHelper {
521
private _args;
22+
private _driver;
623
private _imageCropRect;
724
private _blockOutAreas;
8-
private _waitOnCreatingInitialSnapshot;
9-
constructor(_args: INsCapabilities);
10-
waitOnCreatingInitialSnapshot: number;
25+
private _imagesResults;
26+
private _testName;
27+
private _options;
28+
constructor(_args: INsCapabilities, _driver: AppiumDriver);
29+
options: IImageCompareOptions;
30+
testName: string;
31+
imageComppareOptions: IImageCompareOptions;
32+
compareScreen(options?: IImageCompareOptions): Promise<boolean>;
33+
compareElement(element: UIElement, options?: IImageCompareOptions): Promise<boolean>;
34+
compareRectangle(element: IRectangle, options?: IImageCompareOptions): Promise<boolean>;
35+
hasImageComparisonPassed(): boolean;
36+
reset(): void;
37+
private increaseImageName;
38+
private extendOptions;
1139
imageCropRect: IRectangle;
1240
blockOutAreas: IRectangle[];
1341
imageOutputLimit(): ImageOptions;
1442
thresholdType(): ImageOptions;
15-
threshold(thresholdType: any): 10 | 0.01;
43+
threshold(thresholdType: any): 0.01 | 10;
1644
delta(): number;
1745
static cropImageDefault(_args: INsCapabilities): {
1846
x: number;

Diff for: lib/image-helper.ts

+117-7
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,136 @@
1+
import { basename } from "path";
12
import * as BlinkDiff from "blink-diff";
23
import * as PngJsImage from "pngjs-image";
34
import { ImageOptions } from "./image-options";
45
import { INsCapabilities } from "./interfaces/ns-capabilities";
56
import { IRectangle } from "./interfaces/rectangle";
67
import { LogImageType } from "./enums/log-image-type";
7-
import { basename } from "path";
8+
import { UIElement } from "./ui-element";
9+
import { AppiumDriver } from "./appium-driver";
10+
import { logError } from "./utils";
11+
12+
export interface IImageCompareOptions {
13+
imageName?: string;
14+
timeOutSeconds?: number;
15+
tolerance?: number;
16+
toleranceType?: ImageOptions;
17+
/**
18+
* wait miliseconds before capture creating image
19+
*/
20+
waitOnCreatingInitialSnapshot?: number;
21+
/**
22+
* This property will keep image name as it is and will not add _actual postfix on initial capture
23+
*/
24+
preserveImageName?: boolean;
25+
}
826

927
export class ImageHelper {
1028

1129
private _imageCropRect: IRectangle;
1230
private _blockOutAreas: IRectangle[];
13-
private _waitOnCreatingInitialSnapshot: number;
31+
private _imagesResults = new Map<string, boolean>();
32+
private _testName: string;
33+
private _options: IImageCompareOptions = {
34+
timeOutSeconds: 2,
35+
tolerance: 0,
36+
toleranceType: ImageOptions.pixel,
37+
waitOnCreatingInitialSnapshot: 2000,
38+
preserveImageName: false,
39+
};
40+
41+
constructor(private _args: INsCapabilities, private _driver: AppiumDriver) {
42+
}
43+
44+
get options() {
45+
return this._options;
46+
}
47+
48+
set options(options: IImageCompareOptions) {
49+
this._options = this.extendOptions(options);
50+
}
1451

15-
constructor(private _args: INsCapabilities) {
52+
set testName(testName: string) {
53+
this._testName = testName;
1654
}
1755

18-
get waitOnCreatingInitialSnapshot() {
19-
return this._waitOnCreatingInitialSnapshot;
56+
get testName() {
57+
return this._testName;
2058
}
2159

22-
set waitOnCreatingInitialSnapshot(waitOnCreatingInitialSnapshot: number) {
23-
this._waitOnCreatingInitialSnapshot = waitOnCreatingInitialSnapshot;
60+
get imageComppareOptions() {
61+
this.extendOptions(this._options);
62+
63+
return this._options;
64+
}
65+
66+
set imageComppareOptions(imageComppareOptions: IImageCompareOptions) {
67+
this._options = this.extendOptions(imageComppareOptions);
68+
}
69+
70+
public async compareScreen(options?: IImageCompareOptions) {
71+
options = this.extendOptions(options);
72+
const imageName = this.increaseImageName(options.imageName || this._testName);
73+
const result = await this._driver.compareScreen(imageName, options.timeOutSeconds, options.tolerance, options.toleranceType);
74+
this._imagesResults.set(imageName, result);
75+
76+
return result;
77+
}
78+
79+
public async compareElement(element: UIElement, options?: IImageCompareOptions) {
80+
options = this.extendOptions(options);
81+
const imageName = this.increaseImageName(options.imageName || this._testName);
82+
const result = await this._driver.compareElement(element, imageName, options.tolerance, options.timeOutSeconds, options.toleranceType);
83+
this._imagesResults.set(imageName, result);
84+
85+
return result;
86+
}
87+
88+
public async compareRectangle(element: IRectangle, options?: IImageCompareOptions) {
89+
options = this.extendOptions(options);
90+
const imageName = this.increaseImageName(options.imageName || this._testName);
91+
const result = await this._driver.compareRectangle(element, imageName, options.timeOutSeconds, options.tolerance, options.toleranceType);
92+
this._imagesResults.set(imageName, result);
93+
94+
return result;
95+
}
96+
97+
public hasImageComparisonPassed() {
98+
let shouldFailTest = true;
99+
console.log();
100+
this._imagesResults.forEach((v, k, map) => {
101+
if (!this._imagesResults.get(k)) {
102+
shouldFailTest = false;
103+
this._driver.testReporterLog(`Image comparison for image ${k} has failed!`);
104+
logError(`Image comparison for image ${k} has failed`);
105+
}
106+
});
107+
108+
this.reset();
109+
return shouldFailTest;
110+
}
111+
112+
public reset() {
113+
this._imagesResults.clear();
114+
}
115+
116+
private increaseImageName(imageName: string) {
117+
if (this._imagesResults.size > 1) {
118+
const number = /\d+$/.test(imageName) ? +`${/\d+$/.exec(imageName)}` + 1 : `2`;
119+
imageName = `${imageName}_${number}`;
120+
}
121+
122+
return imageName;
123+
}
124+
125+
private extendOptions(options: IImageCompareOptions) {
126+
options = options || {};
127+
Object.getOwnPropertyNames(this.options).forEach(prop => {
128+
if (!options[prop]) {
129+
options[prop] = this.options[prop];
130+
}
131+
});
132+
133+
return options;
24134
}
25135

26136
get imageCropRect(): IRectangle {

0 commit comments

Comments
 (0)