@o3r/testing 12.5.0-prerelease.4 → 12.5.0-prerelease.41
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/core/angular/angular-materials/autocomplete-material.js +9 -20
- package/core/angular/angular-materials/autocomplete-material.js.map +1 -1
- package/core/angular/angular-materials/index.js +1 -1
- package/core/angular/angular-materials/select-material.js +42 -91
- package/core/angular/angular-materials/select-material.js.map +1 -1
- package/core/angular/angular-materials/slider-material.js +8 -11
- package/core/angular/angular-materials/slider-material.js.map +1 -1
- package/core/angular/component-fixture.js +68 -148
- package/core/angular/component-fixture.js.map +1 -1
- package/core/angular/element.js +44 -50
- package/core/angular/element.js.map +1 -1
- package/core/angular/elements/checkbox-element.js +12 -16
- package/core/angular/elements/checkbox-element.js.map +1 -1
- package/core/angular/elements/index.js +1 -1
- package/core/angular/elements/radio-element.js +12 -16
- package/core/angular/elements/radio-element.js.map +1 -1
- package/core/angular/elements/select-element.js +28 -49
- package/core/angular/elements/select-element.js.map +1 -1
- package/core/angular/elements/slider-element.js +38 -42
- package/core/angular/elements/slider-element.js.map +1 -1
- package/core/angular/group.js +9 -14
- package/core/angular/group.js.map +1 -1
- package/core/angular/groups/index.js +1 -1
- package/core/angular/groups/radio-group.js +25 -62
- package/core/angular/groups/radio-group.js.map +1 -1
- package/core/angular/index.js +1 -1
- package/core/angular/page-fixture.js +8 -11
- package/core/angular/page-fixture.js.map +1 -1
- package/core/angular-materials/autocomplete-material.js +8 -11
- package/core/angular-materials/autocomplete-material.js.map +1 -1
- package/core/angular-materials/index.js +1 -1
- package/core/angular-materials/select-material.js +12 -15
- package/core/angular-materials/select-material.js.map +1 -1
- package/core/angular-materials/slider-material.js +5 -8
- package/core/angular-materials/slider-material.js.map +1 -1
- package/core/component-fixture.js +28 -33
- package/core/component-fixture.js.map +1 -1
- package/core/element.js +24 -26
- package/core/element.js.map +1 -1
- package/core/elements/checkbox-element.js +12 -16
- package/core/elements/checkbox-element.js.map +1 -1
- package/core/elements/index.js +1 -1
- package/core/elements/radio-element.js +12 -16
- package/core/elements/radio-element.js.map +1 -1
- package/core/elements/select-element.js +10 -13
- package/core/elements/select-element.js.map +1 -1
- package/core/elements/slider-element.js +5 -8
- package/core/elements/slider-element.js.map +1 -1
- package/core/group.js +9 -15
- package/core/group.js.map +1 -1
- package/core/groups/index.js +1 -1
- package/core/groups/radio-group.js +8 -11
- package/core/groups/radio-group.js.map +1 -1
- package/core/helpers.js +11 -16
- package/core/helpers.js.map +1 -1
- package/core/index.js +1 -1
- package/core/page-fixture.js +8 -11
- package/core/page-fixture.js.map +1 -1
- package/core/playwright/angular-materials/autocomplete-material.js +26 -56
- package/core/playwright/angular-materials/autocomplete-material.js.map +1 -1
- package/core/playwright/angular-materials/index.js +1 -1
- package/core/playwright/angular-materials/select-material.js +48 -110
- package/core/playwright/angular-materials/select-material.js.map +1 -1
- package/core/playwright/angular-materials/slider-material.js +7 -10
- package/core/playwright/angular-materials/slider-material.js.map +1 -1
- package/core/playwright/component-fixture.js +88 -173
- package/core/playwright/component-fixture.js.map +1 -1
- package/core/playwright/element.js +38 -90
- package/core/playwright/element.js.map +1 -1
- package/core/playwright/elements/checkbox-element.js +49 -94
- package/core/playwright/elements/checkbox-element.js.map +1 -1
- package/core/playwright/elements/index.js +1 -1
- package/core/playwright/elements/radio-element.js +18 -34
- package/core/playwright/elements/radio-element.js.map +1 -1
- package/core/playwright/elements/select-element.js +14 -21
- package/core/playwright/elements/select-element.js.map +1 -1
- package/core/playwright/elements/slider-element.js +78 -157
- package/core/playwright/elements/slider-element.js.map +1 -1
- package/core/playwright/group.js +9 -14
- package/core/playwright/group.js.map +1 -1
- package/core/playwright/groups/index.js +1 -1
- package/core/playwright/groups/radio-group.js +27 -64
- package/core/playwright/groups/radio-group.js.map +1 -1
- package/core/playwright/index.js +1 -1
- package/core/playwright/page-fixture.js +8 -18
- package/core/playwright/page-fixture.js.map +1 -1
- package/core/protractor/angular-materials/autocomplete-material.js +12 -26
- package/core/protractor/angular-materials/autocomplete-material.js.map +1 -1
- package/core/protractor/angular-materials/index.js +1 -1
- package/core/protractor/angular-materials/select-material.js +45 -97
- package/core/protractor/angular-materials/select-material.js.map +1 -1
- package/core/protractor/component-fixture.js +89 -200
- package/core/protractor/component-fixture.js.map +1 -1
- package/core/protractor/element.js +46 -118
- package/core/protractor/element.js.map +1 -1
- package/core/protractor/elements/checkbox-element.js +18 -40
- package/core/protractor/elements/checkbox-element.js.map +1 -1
- package/core/protractor/elements/index.js +1 -1
- package/core/protractor/elements/radio-element.js +20 -42
- package/core/protractor/elements/radio-element.js.map +1 -1
- package/core/protractor/elements/select-element.js +16 -37
- package/core/protractor/elements/select-element.js.map +1 -1
- package/core/protractor/group.js +9 -14
- package/core/protractor/group.js.map +1 -1
- package/core/protractor/groups/index.js +1 -1
- package/core/protractor/groups/radio-group.js +29 -66
- package/core/protractor/groups/radio-group.js.map +1 -1
- package/core/protractor/index.js +1 -1
- package/core/protractor/page-fixture.js +9 -12
- package/core/protractor/page-fixture.js.map +1 -1
- package/core/protractor/utils.js +1 -1
- package/core/protractor/utils.js.map +1 -1
- package/errors/fixture-usage-error.js +5 -10
- package/errors/fixture-usage-error.js.map +1 -1
- package/errors/index.js +1 -1
- package/errors/transpilation-purpose-only.js +5 -10
- package/errors/transpilation-purpose-only.js.map +1 -1
- package/kassette/index.js +1 -1
- package/kassette/update-dates-in-mocks.js +66 -85
- package/kassette/update-dates-in-mocks.js.map +1 -1
- package/localization/index.js +1 -1
- package/localization/localization-mock.js +38 -60
- package/localization/localization-mock.js.map +1 -1
- package/mocks/index.js +1 -1
- package/mocks/mock-component-generator.js +54 -53
- package/mocks/mock-component-generator.js.map +1 -1
- package/package.json +25 -19
- package/public_api.js +1 -1
- package/schematics/ng-add/index.d.ts.map +1 -1
- package/schematics/ng-add/index.js +34 -27
- package/schematics/ng-add/index.js.map +1 -1
- package/schematics/ng-add/playwright/index.d.ts.map +1 -1
- package/schematics/ng-add/playwright/index.js +18 -15
- package/schematics/ng-add/playwright/index.js.map +1 -1
- package/store/index.js +1 -1
- package/store/testable-select.js +13 -13
- package/store/testable-select.js.map +1 -1
- package/tools/index.js +1 -1
- package/tools/path-replacement/index.js +1 -1
- package/tools/path-replacement/path-replacement.js +24 -19
- package/tools/path-replacement/path-replacement.js.map +1 -1
- package/tools/protractor/fetch-manager/index.js +1 -1
- package/tools/protractor/fetch-manager/utils.js +15 -51
- package/tools/protractor/fetch-manager/utils.js.map +1 -1
- package/tools/protractor/index.js +1 -1
- package/tools/protractor/post-message-interceptor/index.js +1 -1
- package/tools/protractor/post-message-interceptor/post-message-interceptor.js +30 -103
- package/tools/protractor/post-message-interceptor/post-message-interceptor.js.map +1 -1
- package/visual-test/index.js +1 -1
- package/visual-test/utils.js +18 -7
- package/visual-test/utils.js.map +1 -1
- package/visual-test/visual-test.js +29 -34
- package/visual-test/visual-test.js.map +1 -1
- package/visual-testing-reporter/index.d.ts.map +1 -1
- package/visual-testing-reporter/index.js +37 -40
- package/visual-testing-reporter/index.js.map +1 -1
@@ -1,16 +1,15 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
3
|
exports.PostMessageInterceptor = void 0;
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
var protractor_1 = require("protractor");
|
4
|
+
const fs = require("node:fs");
|
5
|
+
const path = require("node:path");
|
6
|
+
const protractor_1 = require("protractor");
|
8
7
|
/**
|
9
8
|
* Controls the postMessage interceptor
|
10
9
|
* @deprecated Will be removed in v13, please use Playwright instead
|
11
10
|
*/
|
12
|
-
|
13
|
-
|
11
|
+
class PostMessageInterceptor {
|
12
|
+
constructor() {
|
14
13
|
this.GET_INSTANCE = 'window.postMessageInterceptor.getInstance()';
|
15
14
|
}
|
16
15
|
/**
|
@@ -18,127 +17,55 @@ var PostMessageInterceptor = /** @class */ (function () {
|
|
18
17
|
* @param methodName
|
19
18
|
* @param {...any} args
|
20
19
|
*/
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
args[_i - 1] = arguments[_i];
|
25
|
-
}
|
26
|
-
return tslib_1.__awaiter(this, void 0, void 0, function () {
|
27
|
-
return tslib_1.__generator(this, function (_a) {
|
28
|
-
switch (_a.label) {
|
29
|
-
case 0: return [4 /*yield*/, protractor_1.browser.executeScript.apply(protractor_1.browser, tslib_1.__spreadArray(["".concat(this.GET_INSTANCE, ".").concat(methodName, "(...arguments);")], args, false))];
|
30
|
-
case 1: return [2 /*return*/, _a.sent()];
|
31
|
-
}
|
32
|
-
});
|
33
|
-
});
|
34
|
-
};
|
20
|
+
async execInstanceMethod(methodName, ...args) {
|
21
|
+
return await protractor_1.browser.executeScript(`${this.GET_INSTANCE}.${methodName}(...arguments);`, ...args);
|
22
|
+
}
|
35
23
|
/**
|
36
24
|
* Executes an async script in the app
|
37
25
|
* @param methodName
|
38
26
|
* @param {...any} args
|
39
27
|
*/
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
args[_i - 1] = arguments[_i];
|
44
|
-
}
|
45
|
-
return tslib_1.__awaiter(this, void 0, void 0, function () {
|
46
|
-
return tslib_1.__generator(this, function (_a) {
|
47
|
-
switch (_a.label) {
|
48
|
-
case 0: return [4 /*yield*/, protractor_1.browser.executeAsyncScript.apply(protractor_1.browser, tslib_1.__spreadArray(["".concat(this.GET_INSTANCE, ".").concat(methodName, "(...arguments);")], args, false))];
|
49
|
-
case 1: return [2 /*return*/, _a.sent()];
|
50
|
-
}
|
51
|
-
});
|
52
|
-
});
|
53
|
-
};
|
28
|
+
async execInstanceAsyncMethod(methodName, ...args) {
|
29
|
+
return await protractor_1.browser.executeAsyncScript(`${this.GET_INSTANCE}.${methodName}(...arguments);`, ...args);
|
30
|
+
}
|
54
31
|
/**
|
55
32
|
* Initializes the PostMessageInterceptor in the app
|
56
33
|
* NOTE: It does not start listening right away. You need to call listen()
|
57
34
|
*/
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
return tslib_1.__generator(this, function (_a) {
|
62
|
-
switch (_a.label) {
|
63
|
-
case 0:
|
64
|
-
fetchManager = fs.readFileSync(path.resolve(process.cwd(), 'node_modules', '@o3r', 'testing', 'tools', 'protractor', 'post-message-interceptor', '_post-message-interceptor.js'), {
|
65
|
-
encoding: 'utf8'
|
66
|
-
});
|
67
|
-
return [4 /*yield*/, protractor_1.browser.executeScript(fetchManager)];
|
68
|
-
case 1:
|
69
|
-
_a.sent();
|
70
|
-
return [4 /*yield*/, this.execInstanceMethod('init')];
|
71
|
-
case 2:
|
72
|
-
_a.sent();
|
73
|
-
return [2 /*return*/];
|
74
|
-
}
|
75
|
-
});
|
35
|
+
async initialize() {
|
36
|
+
const fetchManager = fs.readFileSync(path.resolve(process.cwd(), 'node_modules', '@o3r', 'testing', 'tools', 'protractor', 'post-message-interceptor', '_post-message-interceptor.js'), {
|
37
|
+
encoding: 'utf8'
|
76
38
|
});
|
77
|
-
|
39
|
+
await protractor_1.browser.executeScript(fetchManager);
|
40
|
+
await this.execInstanceMethod('init');
|
41
|
+
}
|
78
42
|
/**
|
79
43
|
* Stops the PostMessageInterceptor.
|
80
44
|
*/
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
switch (_a.label) {
|
85
|
-
case 0: return [4 /*yield*/, this.execInstanceMethod('stop')];
|
86
|
-
case 1:
|
87
|
-
_a.sent();
|
88
|
-
return [2 /*return*/];
|
89
|
-
}
|
90
|
-
});
|
91
|
-
});
|
92
|
-
};
|
45
|
+
async stop() {
|
46
|
+
await this.execInstanceMethod('stop');
|
47
|
+
}
|
93
48
|
/**
|
94
49
|
* Listens for post messages
|
95
50
|
* @param conditionFn an optional boolean function that will evaluate if the message should be saved or not in the stack
|
96
51
|
*/
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
switch (_a.label) {
|
101
|
-
case 0: return [4 /*yield*/, this.execInstanceMethod('listen', conditionFn)];
|
102
|
-
case 1:
|
103
|
-
_a.sent();
|
104
|
-
return [2 /*return*/];
|
105
|
-
}
|
106
|
-
});
|
107
|
-
});
|
108
|
-
};
|
52
|
+
async listen(conditionFn) {
|
53
|
+
await this.execInstanceMethod('listen', conditionFn);
|
54
|
+
}
|
109
55
|
/**
|
110
56
|
* Stops listening for post messages, resets the stack and clears the condition
|
111
57
|
*/
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
switch (_a.label) {
|
116
|
-
case 0: return [4 /*yield*/, this.execInstanceMethod('stopListening')];
|
117
|
-
case 1:
|
118
|
-
_a.sent();
|
119
|
-
return [2 /*return*/];
|
120
|
-
}
|
121
|
-
});
|
122
|
-
});
|
123
|
-
};
|
58
|
+
async stopListening() {
|
59
|
+
await this.execInstanceMethod('stopListening');
|
60
|
+
}
|
124
61
|
/**
|
125
62
|
* Get all the post messages
|
126
63
|
* @param timeoutInterval Interval in milliseconds between two checks
|
127
64
|
* @param retries How many retries to be executed (-1 for infinite retries)
|
128
65
|
*/
|
129
|
-
|
130
|
-
return
|
131
|
-
|
132
|
-
|
133
|
-
return tslib_1.__generator(this, function (_a) {
|
134
|
-
switch (_a.label) {
|
135
|
-
case 0: return [4 /*yield*/, this.execInstanceAsyncMethod('getMessages', timeoutInterval, retries)];
|
136
|
-
case 1: return [2 /*return*/, (_a.sent())];
|
137
|
-
}
|
138
|
-
});
|
139
|
-
});
|
140
|
-
};
|
141
|
-
return PostMessageInterceptor;
|
142
|
-
}());
|
66
|
+
async getMessages(timeoutInterval = 100, retries = -1) {
|
67
|
+
return (await this.execInstanceAsyncMethod('getMessages', timeoutInterval, retries));
|
68
|
+
}
|
69
|
+
}
|
143
70
|
exports.PostMessageInterceptor = PostMessageInterceptor;
|
144
71
|
//# sourceMappingURL=post-message-interceptor.js.map
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"post-message-interceptor.js","sourceRoot":"","sources":["../../../../src/tools/protractor/post-message-interceptor/post-message-interceptor.ts"],"names":[],"mappings":"
|
1
|
+
{"version":3,"file":"post-message-interceptor.js","sourceRoot":"","sources":["../../../../src/tools/protractor/post-message-interceptor/post-message-interceptor.ts"],"names":[],"mappings":";;;AAAA,8BAA8B;AAC9B,kCAAkC;AAClC,2CAEoB;AAMpB;;;GAGG;AACH,MAAa,sBAAsB;IAAnC;QACmB,iBAAY,GAAG,6CAA6C,CAAC;IAiEhF,CAAC;IA/DC;;;;OAIG;IACK,KAAK,CAAC,kBAAkB,CAAC,UAAkB,EAAE,GAAG,IAAW;QACjE,OAAO,MAAM,oBAAO,CAAC,aAAa,CAAC,GAAG,IAAI,CAAC,YAAY,IAAI,UAAU,iBAAiB,EAAE,GAAG,IAAI,CAAC,CAAC;IACnG,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,uBAAuB,CAAC,UAAkB,EAAE,GAAG,IAAW;QACtE,OAAO,MAAM,oBAAO,CAAC,kBAAkB,CAAC,GAAG,IAAI,CAAC,YAAY,IAAI,UAAU,iBAAiB,EAAE,GAAG,IAAI,CAAC,CAAC;IACxG,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,UAAU;QACrB,MAAM,YAAY,GAAG,EAAE,CAAC,YAAY,CAClC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,YAAY,EAAE,0BAA0B,EAAE,8BAA8B,CAAC,EACjJ;YACE,QAAQ,EAAE,MAAM;SACjB,CACF,CAAC;QACF,MAAM,oBAAO,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC;QAC1C,MAAM,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC;IACxC,CAAC;IAED;;OAEG;IACI,KAAK,CAAC,IAAI;QACf,MAAM,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC;IACxC,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,MAAM,CAAC,WAAyB;QAC3C,MAAM,IAAI,CAAC,kBAAkB,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IACvD,CAAC;IAED;;OAEG;IACI,KAAK,CAAC,aAAa;QACxB,MAAM,IAAI,CAAC,kBAAkB,CAAC,eAAe,CAAC,CAAC;IACjD,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,WAAW,CAAC,eAAe,GAAG,GAAG,EAAE,OAAO,GAAG,CAAC,CAAC;QAC1D,OAAO,CAAC,MAAM,IAAI,CAAC,uBAAuB,CAAC,aAAa,EAAE,eAAe,EAAE,OAAO,CAAC,CAAsB,CAAC;IAC5G,CAAC;CACF;AAlED,wDAkEC","sourcesContent":["import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport {\n browser,\n} from 'protractor';\nimport {\n ConditionFn,\n PostMessageCall,\n} from './core';\n\n/**\n * Controls the postMessage interceptor\n * @deprecated Will be removed in v13, please use Playwright instead\n */\nexport class PostMessageInterceptor {\n private readonly GET_INSTANCE = 'window.postMessageInterceptor.getInstance()';\n\n /**\n * Executes a script in the app\n * @param methodName\n * @param {...any} args\n */\n private async execInstanceMethod(methodName: string, ...args: any[]) {\n return await browser.executeScript(`${this.GET_INSTANCE}.${methodName}(...arguments);`, ...args);\n }\n\n /**\n * Executes an async script in the app\n * @param methodName\n * @param {...any} args\n */\n private async execInstanceAsyncMethod(methodName: string, ...args: any[]) {\n return await browser.executeAsyncScript(`${this.GET_INSTANCE}.${methodName}(...arguments);`, ...args);\n }\n\n /**\n * Initializes the PostMessageInterceptor in the app\n * NOTE: It does not start listening right away. You need to call listen()\n */\n public async initialize() {\n const fetchManager = fs.readFileSync(\n path.resolve(process.cwd(), 'node_modules', '@o3r', 'testing', 'tools', 'protractor', 'post-message-interceptor', '_post-message-interceptor.js'),\n {\n encoding: 'utf8'\n }\n );\n await browser.executeScript(fetchManager);\n await this.execInstanceMethod('init');\n }\n\n /**\n * Stops the PostMessageInterceptor.\n */\n public async stop() {\n await this.execInstanceMethod('stop');\n }\n\n /**\n * Listens for post messages\n * @param conditionFn an optional boolean function that will evaluate if the message should be saved or not in the stack\n */\n public async listen(conditionFn?: ConditionFn) {\n await this.execInstanceMethod('listen', conditionFn);\n }\n\n /**\n * Stops listening for post messages, resets the stack and clears the condition\n */\n public async stopListening() {\n await this.execInstanceMethod('stopListening');\n }\n\n /**\n * Get all the post messages\n * @param timeoutInterval Interval in milliseconds between two checks\n * @param retries How many retries to be executed (-1 for infinite retries)\n */\n public async getMessages(timeoutInterval = 100, retries = -1): Promise<PostMessageCall[]> {\n return (await this.execInstanceAsyncMethod('getMessages', timeoutInterval, retries)) as PostMessageCall[];\n }\n}\n"]}
|
package/visual-test/index.js
CHANGED
package/visual-test/utils.js
CHANGED
@@ -10,18 +10,29 @@ exports.toggleVisualTestingRender = toggleVisualTestingRender;
|
|
10
10
|
* @note this function is evaluated in the context of the page and should not use external variables
|
11
11
|
* @param ignoreClass
|
12
12
|
*/
|
13
|
-
function prepareVisualTesting(ignoreClass) {
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
13
|
+
function prepareVisualTesting(ignoreClass = 'e2e-ignore') {
|
14
|
+
const visualTestingCss = document.createElement('style');
|
15
|
+
const visualTestingClass = 'visual-testing-render';
|
16
|
+
visualTestingCss.textContent = `
|
17
|
+
.${visualTestingClass} .${ignoreClass} {position: relative;}
|
18
|
+
|
19
|
+
.${visualTestingClass} .${ignoreClass}:before {
|
20
|
+
z-index: 999;
|
21
|
+
content: '';
|
22
|
+
width: 100%;
|
23
|
+
height: 100%;
|
24
|
+
background: grey;
|
25
|
+
position: absolute;
|
26
|
+
left: 0;
|
27
|
+
top: 0;
|
28
|
+
}`;
|
18
29
|
document.head.append(visualTestingCss);
|
19
30
|
}
|
20
31
|
/**
|
21
32
|
* Determine if the visual testing is enabled
|
22
33
|
*/
|
23
34
|
function isVisualTestingEnabled() {
|
24
|
-
|
35
|
+
const visualTestingClass = 'visual-testing-render';
|
25
36
|
return document.body.classList.contains(visualTestingClass);
|
26
37
|
}
|
27
38
|
/**
|
@@ -30,7 +41,7 @@ function isVisualTestingEnabled() {
|
|
30
41
|
* @param enabled
|
31
42
|
*/
|
32
43
|
function toggleVisualTestingRender(enabled) {
|
33
|
-
|
44
|
+
const visualTestingClass = 'visual-testing-render';
|
34
45
|
if (enabled) {
|
35
46
|
document.body.classList.add(visualTestingClass);
|
36
47
|
}
|
package/visual-test/utils.js.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/visual-test/utils.ts"],"names":[],"mappings":";;AAOA,oDAkBC;AAKD,wDAGC;AAOD,8DAOC;AA/CD;;;;;;GAMG;AACH,SAAgB,oBAAoB,CAAC,
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/visual-test/utils.ts"],"names":[],"mappings":";;AAOA,oDAkBC;AAKD,wDAGC;AAOD,8DAOC;AA/CD;;;;;;GAMG;AACH,SAAgB,oBAAoB,CAAC,WAAW,GAAG,YAAY;IAC7D,MAAM,gBAAgB,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IACzD,MAAM,kBAAkB,GAAG,uBAAuB,CAAC;IACnD,gBAAgB,CAAC,WAAW,GAAG;OAC1B,kBAAkB,KAAK,WAAW;;OAElC,kBAAkB,KAAK,WAAW;;;;;;;;;IASrC,CAAC;IAEH,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;AACzC,CAAC;AAED;;GAEG;AACH,SAAgB,sBAAsB;IACpC,MAAM,kBAAkB,GAAG,uBAAuB,CAAC;IACnD,OAAO,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC;AAC9D,CAAC;AAED;;;;GAIG;AACH,SAAgB,yBAAyB,CAAC,OAAgB;IACxD,MAAM,kBAAkB,GAAG,uBAAuB,CAAC;IACnD,IAAI,OAAO,EAAE,CAAC;QACZ,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IAClD,CAAC;SAAM,CAAC;QACN,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC;IACrD,CAAC;AACH,CAAC","sourcesContent":["/**\n * Prepare css rule to hide specific blocks\n *\n * Should be called only once during the visual test.\n * @note this function is evaluated in the context of the page and should not use external variables\n * @param ignoreClass\n */\nexport function prepareVisualTesting(ignoreClass = 'e2e-ignore') {\n const visualTestingCss = document.createElement('style');\n const visualTestingClass = 'visual-testing-render';\n visualTestingCss.textContent = `\n .${visualTestingClass} .${ignoreClass} {position: relative;}\n\n .${visualTestingClass} .${ignoreClass}:before {\n z-index: 999;\n content: '';\n width: 100%;\n height: 100%;\n background: grey;\n position: absolute;\n left: 0;\n top: 0;\n }`;\n\n document.head.append(visualTestingCss);\n}\n\n/**\n * Determine if the visual testing is enabled\n */\nexport function isVisualTestingEnabled() {\n const visualTestingClass = 'visual-testing-render';\n return document.body.classList.contains(visualTestingClass);\n}\n\n/**\n * Toggle the visual testing view : if it is active, will hide tagged components as grey blocks.\n * @note this function is evaluated in the context of the page and cannot use external code\n * @param enabled\n */\nexport function toggleVisualTestingRender(enabled: boolean) {\n const visualTestingClass = 'visual-testing-render';\n if (enabled) {\n document.body.classList.add(visualTestingClass);\n } else {\n document.body.classList.remove(visualTestingClass);\n }\n}\n"]}
|
@@ -6,10 +6,10 @@ exports.saveScreenshot = saveScreenshot;
|
|
6
6
|
exports.writeScreenshotsDiff = writeScreenshotsDiff;
|
7
7
|
exports.compareScreenshot = compareScreenshot;
|
8
8
|
exports.o3rVisualTest = o3rVisualTest;
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
const fs = require("node:fs");
|
10
|
+
const path = require("node:path");
|
11
|
+
const pixelmatch = require("pixelmatch");
|
12
|
+
const pngjs_1 = require("pngjs");
|
13
13
|
var utils_1 = require("./utils");
|
14
14
|
Object.defineProperty(exports, "prepareVisualTesting", { enumerable: true, get: function () { return utils_1.prepareVisualTesting; } });
|
15
15
|
Object.defineProperty(exports, "toggleVisualTestingRender", { enumerable: true, get: function () { return utils_1.toggleVisualTestingRender; } });
|
@@ -27,7 +27,7 @@ exports.visualTestMessages = {
|
|
27
27
|
*/
|
28
28
|
function toBeVisuallySimilar() {
|
29
29
|
return {
|
30
|
-
compare:
|
30
|
+
compare: (actual, _expected) => {
|
31
31
|
if (actual.generateMode) {
|
32
32
|
return {
|
33
33
|
pass: true,
|
@@ -37,19 +37,19 @@ function toBeVisuallySimilar() {
|
|
37
37
|
if (actual.baseScreenshotNotFound) {
|
38
38
|
return {
|
39
39
|
pass: false,
|
40
|
-
message:
|
40
|
+
message: `${exports.visualTestMessages.baseImgNotFound} ${actual.baseScreenshotNotFound.baseScreenshotPath}`
|
41
41
|
};
|
42
42
|
}
|
43
43
|
if (actual.imagesSize) {
|
44
44
|
return {
|
45
45
|
pass: false,
|
46
|
-
message:
|
46
|
+
message: `${exports.visualTestMessages.imagesSize} ${actual.imagesSize.screenshotName}`
|
47
47
|
};
|
48
48
|
}
|
49
49
|
if (actual.diff && actual.diff.actualDiff > actual.diff.threshold) {
|
50
50
|
return {
|
51
51
|
pass: false,
|
52
|
-
message:
|
52
|
+
message: `${actual.diff.actualDiff} > ${actual.diff.threshold} : ${exports.visualTestMessages.diffMessage} ${actual.diff.screenshotName}`
|
53
53
|
};
|
54
54
|
}
|
55
55
|
return {
|
@@ -70,14 +70,13 @@ function toBeVisuallySimilar() {
|
|
70
70
|
* @param filenameWithoutExtension file name to save the screenshot - .png will be added at the end
|
71
71
|
* @param distScreenshotsDir Name of the directory to save the screenshots
|
72
72
|
*/
|
73
|
-
function saveScreenshot(screenshot, scenarioName, device, filenameWithoutExtension, distScreenshotsDir) {
|
74
|
-
|
75
|
-
var screenshotsDir = path.resolve(distScreenshotsDir, scenarioName, "".concat(device.replace(/ +/g, '_')));
|
73
|
+
function saveScreenshot(screenshot, scenarioName, device, filenameWithoutExtension, distScreenshotsDir = 'dist-screenshots') {
|
74
|
+
const screenshotsDir = path.resolve(distScreenshotsDir, scenarioName, `${device.replace(/ +/g, '_')}`);
|
76
75
|
if (!fs.existsSync(screenshotsDir)) {
|
77
76
|
fs.mkdirSync(screenshotsDir, { recursive: true });
|
78
77
|
}
|
79
|
-
|
80
|
-
|
78
|
+
const fullFileName = `${filenameWithoutExtension}.png`;
|
79
|
+
const stream = fs.createWriteStream(path.resolve(screenshotsDir, fullFileName));
|
81
80
|
stream.write(Buffer.from(screenshot, 'base64'));
|
82
81
|
stream.end();
|
83
82
|
}
|
@@ -91,13 +90,13 @@ function saveScreenshot(screenshot, scenarioName, device, filenameWithoutExtensi
|
|
91
90
|
* @param currentImg the actual taken screenshot image
|
92
91
|
*/
|
93
92
|
function writeScreenshotsDiff(pathToScenarioReport, screenshotsDirName, diff, baseImage, currentImg) {
|
94
|
-
|
93
|
+
const destScreenshotsDiffDir = path.join(pathToScenarioReport, 'screenshots-diff', screenshotsDirName);
|
95
94
|
if (!fs.existsSync(destScreenshotsDiffDir)) {
|
96
95
|
fs.mkdirSync(destScreenshotsDiffDir, { recursive: true });
|
97
96
|
}
|
98
|
-
|
99
|
-
|
100
|
-
|
97
|
+
const diffPath = path.resolve(destScreenshotsDiffDir, 'diff.png');
|
98
|
+
const oldPath = path.resolve(destScreenshotsDiffDir, 'old.png');
|
99
|
+
const newPath = path.resolve(destScreenshotsDiffDir, 'new.png');
|
101
100
|
fs.writeFileSync(diffPath, pngjs_1.PNG.sync.write(diff));
|
102
101
|
fs.writeFileSync(oldPath, pngjs_1.PNG.sync.write(baseImage));
|
103
102
|
fs.writeFileSync(newPath, pngjs_1.PNG.sync.write(currentImg));
|
@@ -111,15 +110,15 @@ function writeScreenshotsDiff(pathToScenarioReport, screenshotsDirName, diff, ba
|
|
111
110
|
* @returns An object of visual test result type
|
112
111
|
*/
|
113
112
|
function compareScreenshot(screenshot, baseImagePath, threshold, pathToScenarioReport) {
|
114
|
-
|
113
|
+
const baseImageExists = fs.existsSync(baseImagePath);
|
115
114
|
if (baseImageExists) {
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
115
|
+
const baseImage = pngjs_1.PNG.sync.read(fs.readFileSync(baseImagePath));
|
116
|
+
const { width, height } = baseImage;
|
117
|
+
const diff = new pngjs_1.PNG({ width, height });
|
118
|
+
const screenshotBuffer = Buffer.from(screenshot, 'base64');
|
119
|
+
const currentImg = pngjs_1.PNG.sync.read(screenshotBuffer);
|
120
|
+
const diffDirName = path.basename(baseImagePath, '.png');
|
121
|
+
let result;
|
123
122
|
try {
|
124
123
|
result = pixelmatch(baseImage.data, currentImg.data, diff.data, width, height, { threshold: 0.1 });
|
125
124
|
}
|
@@ -130,11 +129,11 @@ function compareScreenshot(screenshot, baseImagePath, threshold, pathToScenarioR
|
|
130
129
|
writeScreenshotsDiff(pathToScenarioReport, diffDirName, diff, baseImage, currentImg);
|
131
130
|
return { imagesSize: { screenshotName: diffDirName } };
|
132
131
|
}
|
133
|
-
|
132
|
+
const pr = Math.round(100 * 100 * result / (width * height)) / 100;
|
134
133
|
if (pr > threshold) {
|
135
134
|
writeScreenshotsDiff(pathToScenarioReport, diffDirName, diff, baseImage, currentImg);
|
136
135
|
}
|
137
|
-
return { diff: { actualDiff: pr, threshold
|
136
|
+
return { diff: { actualDiff: pr, threshold, screenshotName: diffDirName } };
|
138
137
|
}
|
139
138
|
else {
|
140
139
|
return { baseScreenshotNotFound: { baseScreenshotPath: baseImagePath } };
|
@@ -152,18 +151,14 @@ function compareScreenshot(screenshot, baseImagePath, threshold, pathToScenarioR
|
|
152
151
|
* @param generateMode If true it will generate the screenshot file without screenshots comparison
|
153
152
|
* @param baseScreenshotsDirPath The folder path to search base screenshots; used only in compare mode
|
154
153
|
*/
|
155
|
-
function o3rVisualTest(screenshotObj, filenameWithoutExtension, device, scenarioName, pathToScenarioReport, threshold, generateMode, baseScreenshotsDirPath) {
|
156
|
-
if (pathToScenarioReport === void 0) { pathToScenarioReport = 'reports'; }
|
157
|
-
if (threshold === void 0) { threshold = 0; }
|
158
|
-
if (generateMode === void 0) { generateMode = false; }
|
159
|
-
if (baseScreenshotsDirPath === void 0) { baseScreenshotsDirPath = 'dist-screenshots-base'; }
|
154
|
+
function o3rVisualTest(screenshotObj, filenameWithoutExtension, device, scenarioName, pathToScenarioReport = 'reports', threshold = 0, generateMode = false, baseScreenshotsDirPath = 'dist-screenshots-base') {
|
160
155
|
if (generateMode) {
|
161
156
|
saveScreenshot(screenshotObj, scenarioName, device, filenameWithoutExtension);
|
162
157
|
return { generateMode: true };
|
163
158
|
}
|
164
159
|
else {
|
165
|
-
|
166
|
-
|
160
|
+
const baseImagePath = path.resolve(baseScreenshotsDirPath, scenarioName, device, `${filenameWithoutExtension}.png`);
|
161
|
+
const visualTestResult = compareScreenshot(screenshotObj, baseImagePath, threshold, pathToScenarioReport);
|
167
162
|
return visualTestResult;
|
168
163
|
}
|
169
164
|
}
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"visual-test.js","sourceRoot":"","sources":["../../src/visual-test/visual-test.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport * as pixelmatch from 'pixelmatch';\nimport {\n PNG,\n} from 'pngjs';\n\nexport {\n prepareVisualTesting,\n toggleVisualTestingRender,\n} from './utils';\n\n/** Error types returned by visual testing comparison */\nexport interface VisualTestMessage {\n /** Error message when base image and actual image have different sizes */\n imagesSize: string;\n /** Error message when base screensot not found */\n baseImgNotFound: string;\n /** Error message when the threshold is passed */\n diffMessage: string;\n /** The message in case of success */\n success: string;\n /** The message in case of generate mode run */\n generateMode: string;\n}\n\n/** Error messages in case of visual testing failure */\nexport const visualTestMessages: Readonly<VisualTestMessage> = {\n imagesSize: 'Image sizes do not match for:',\n diffMessage: 'Diff between images is greater than threshold for:',\n baseImgNotFound: 'Base screenshot file not found:',\n success: 'Visual test successful',\n generateMode: 'Run in generate screenshot mode'\n} as const;\n\n/**\n * Object returned by a visual test operation\n */\nexport interface VisualTestResult {\n /** Error object when base image and actual image have different sizes; Contains the screenshot ffile name */\n imagesSize?: { screenshotName: string };\n /** Error object when base screensot not found. Contains the not found path as a string */\n baseScreenshotNotFound?: { baseScreenshotPath: string };\n /** Object containing the actual diff between images as percentage, the threshold and screenshot file name */\n diff?: { actualDiff: number; threshold: number; screenshotName: string };\n /** Run only generation of screenshots */\n generateMode?: boolean;\n}\n\n/**\n * Visual test matcher\n * Based on the VisualTestResult object return by compareScreenshots function, this matcher will compute the error messages\n */\nexport function toBeVisuallySimilar() {\n return {\n compare: (actual: VisualTestResult, _expected: VisualTestResult) => {\n if (actual.generateMode) {\n return {\n pass: true,\n message: visualTestMessages.generateMode\n };\n }\n if (actual.baseScreenshotNotFound) {\n return {\n pass: false,\n message: `${visualTestMessages.baseImgNotFound} ${actual.baseScreenshotNotFound.baseScreenshotPath}` };\n }\n if (actual.imagesSize) {\n return {\n pass: false,\n message: `${visualTestMessages.imagesSize} ${actual.imagesSize.screenshotName}`\n };\n }\n if (actual.diff && actual.diff.actualDiff > actual.diff.threshold) {\n return {\n pass: false,\n message: `${actual.diff.actualDiff} > ${actual.diff.threshold} : ${visualTestMessages.diffMessage} ${actual.diff.screenshotName}`\n };\n }\n return {\n pass: true,\n message: visualTestMessages.success\n };\n }\n };\n}\n\n/**\n * It will create a file for the passed screenshot object.\n * The path of the new file will be calculated using the parameters\n * Ex: ./dist-screenshots\\OWBooking\\windows_chrome_91\\fare-page-after-click-on-continue-0.png\n * distScreenshotsDir/scenarionName/device/filenameWithoutExtension.png\n * @param screenshot The screenshot object captured. Ex: for protractor - browser.takeScreenshot()\n * @param scenarioName E2e Scenario class name\n * @param device Details of the platform on which the test is run. If there are spaces the helper will do the concatenation. Ex: `Windows 10 chrome 89`\n * @param filenameWithoutExtension file name to save the screenshot - .png will be added at the end\n * @param distScreenshotsDir Name of the directory to save the screenshots\n */\nexport function saveScreenshot(screenshot: string, scenarioName: string, device: string, filenameWithoutExtension: string, distScreenshotsDir = 'dist-screenshots') {\n const screenshotsDir = path.resolve(distScreenshotsDir, scenarioName, `${device.replace(/ +/g, '_')}`);\n if (!fs.existsSync(screenshotsDir)) {\n fs.mkdirSync(screenshotsDir, { recursive: true });\n }\n const fullFileName = `${filenameWithoutExtension}.png`;\n const stream = fs.createWriteStream(path.resolve(screenshotsDir, fullFileName));\n stream.write(Buffer.from(screenshot, 'base64'));\n stream.end();\n}\n\n/**\n * Write the 3 images (base/new/diff) on the reports folder\n * The path inside the reports forlder will be calculated using the parameters\n * @param pathToScenarioReport Path where the scenario report is saved inside reports folder\n * @param screenshotsDirName Name of the directory which will contain the 3 images\n * @param diff diff image\n * @param baseImage the base image\n * @param currentImg the actual taken screenshot image\n */\nexport function writeScreenshotsDiff(pathToScenarioReport: string, screenshotsDirName: string, diff: PNG, baseImage: PNG, currentImg: PNG) {\n const destScreenshotsDiffDir = path.join(pathToScenarioReport, 'screenshots-diff', screenshotsDirName);\n if (!fs.existsSync(destScreenshotsDiffDir)) {\n fs.mkdirSync(destScreenshotsDiffDir, { recursive: true });\n }\n\n const diffPath = path.resolve(destScreenshotsDiffDir, 'diff.png');\n const oldPath = path.resolve(destScreenshotsDiffDir, 'old.png');\n const newPath = path.resolve(destScreenshotsDiffDir, 'new.png');\n fs.writeFileSync(diffPath, PNG.sync.write(diff));\n fs.writeFileSync(oldPath, PNG.sync.write(baseImage));\n fs.writeFileSync(newPath, PNG.sync.write(currentImg));\n}\n\n/**\n * Compare images helper function. If the comparison fails the 3 images (base/new/diff) will be written inside the reports folder of the actual scenario\n * @param screenshot Actual captured screenshot object\n * @param baseImagePath The path to the base screenshot\n * @param threshold The diff between base screenshot and the current one should not be bigger than this value.\n * @param pathToScenarioReport Path where the scenario report is saved inside reports folder. Used to compute the path to write diff images in case there is a diff at comparison\n * @returns An object of visual test result type\n */\nexport function compareScreenshot(screenshot: string, baseImagePath: string, threshold: number, pathToScenarioReport: string): VisualTestResult {\n const baseImageExists = fs.existsSync(baseImagePath);\n if (baseImageExists) {\n const baseImage = PNG.sync.read(fs.readFileSync(baseImagePath));\n const { width, height } = baseImage;\n const diff = new PNG({ width, height });\n\n const screenshotBuffer = Buffer.from(screenshot, 'base64');\n const currentImg = PNG.sync.read(screenshotBuffer);\n const diffDirName = path.basename(baseImagePath, '.png');\n let result;\n try {\n result = pixelmatch(baseImage.data, currentImg.data, diff.data, width, height, { threshold: 0.1 });\n } catch (err: any) {\n if (!err.toString().includes('Image sizes do not match.')) {\n throw err;\n }\n writeScreenshotsDiff(pathToScenarioReport, diffDirName, diff, baseImage, currentImg);\n return { imagesSize: { screenshotName: diffDirName } };\n }\n const pr = Math.round(100 * 100 * result / (width * height)) / 100;\n if (pr > threshold) {\n writeScreenshotsDiff(pathToScenarioReport, diffDirName, diff, baseImage, currentImg);\n }\n return { diff: { actualDiff: pr, threshold, screenshotName: diffDirName } };\n } else {\n return { baseScreenshotNotFound: { baseScreenshotPath: baseImagePath } };\n }\n}\n\n/**\n * Helper function to perform a visual test operation\n * @param screenshotObj Ex: for protractor browser.takeScreenshot()\n * @param filenameWithoutExtension file name to save the screenshot - .png will be added at the end\n * @param device os followed by browser version - ex: `Windows 10 chrome 89`\n * @param scenarioName E2e Scenario class name\n * @param pathToScenarioReport Path used in compare mode for saving the base,new,diff images in reports in case there is a diff\n * @param threshold The diff between base screenshot and the current one should not be bigger than this value.\n * If the diff is bigger, 3 png files will be created: base screenshot, new screenshot and diff image\n * @param generateMode If true it will generate the screenshot file without screenshots comparison\n * @param baseScreenshotsDirPath The folder path to search base screenshots; used only in compare mode\n */\nexport function o3rVisualTest(\n screenshotObj: string,\n filenameWithoutExtension: string,\n device: string,\n scenarioName: string,\n pathToScenarioReport = 'reports',\n threshold = 0,\n generateMode = false,\n baseScreenshotsDirPath = 'dist-screenshots-base'\n) {\n if (generateMode) {\n saveScreenshot(screenshotObj, scenarioName, device, filenameWithoutExtension);\n return { generateMode: true };\n } else {\n const baseImagePath = path.resolve(baseScreenshotsDirPath, scenarioName, device, `${filenameWithoutExtension}.png`);\n const visualTestResult = compareScreenshot(screenshotObj, baseImagePath, threshold, pathToScenarioReport);\n return visualTestResult;\n }\n}\n"]}
|
1
|
+
{"version":3,"file":"visual-test.js","sourceRoot":"","sources":["../../src/visual-test/visual-test.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport * as pixelmatch from 'pixelmatch';\nimport {\n PNG,\n} from 'pngjs';\n\nexport {\n prepareVisualTesting,\n toggleVisualTestingRender,\n} from './utils';\n\n/** Error types returned by visual testing comparison */\nexport interface VisualTestMessage {\n /** Error message when base image and actual image have different sizes */\n imagesSize: string;\n /** Error message when base screensot not found */\n baseImgNotFound: string;\n /** Error message when the threshold is passed */\n diffMessage: string;\n /** The message in case of success */\n success: string;\n /** The message in case of generate mode run */\n generateMode: string;\n}\n\n/** Error messages in case of visual testing failure */\nexport const visualTestMessages: Readonly<VisualTestMessage> = {\n imagesSize: 'Image sizes do not match for:',\n diffMessage: 'Diff between images is greater than threshold for:',\n baseImgNotFound: 'Base screenshot file not found:',\n success: 'Visual test successful',\n generateMode: 'Run in generate screenshot mode'\n} as const;\n\n/**\n * Object returned by a visual test operation\n */\nexport interface VisualTestResult {\n /** Error object when base image and actual image have different sizes; Contains the screenshot ffile name */\n imagesSize?: { screenshotName: string };\n /** Error object when base screensot not found. Contains the not found path as a string */\n baseScreenshotNotFound?: { baseScreenshotPath: string };\n /** Object containing the actual diff between images as percentage, the threshold and screenshot file name */\n diff?: { actualDiff: number; threshold: number; screenshotName: string };\n /** Run only generation of screenshots */\n generateMode?: boolean;\n}\n\n/**\n * Visual test matcher\n * Based on the VisualTestResult object return by compareScreenshots function, this matcher will compute the error messages\n */\nexport function toBeVisuallySimilar() {\n return {\n compare: (actual: VisualTestResult, _expected: VisualTestResult) => {\n if (actual.generateMode) {\n return {\n pass: true,\n message: visualTestMessages.generateMode\n };\n }\n if (actual.baseScreenshotNotFound) {\n return {\n pass: false,\n message: `${visualTestMessages.baseImgNotFound} ${actual.baseScreenshotNotFound.baseScreenshotPath}` };\n }\n if (actual.imagesSize) {\n return {\n pass: false,\n message: `${visualTestMessages.imagesSize} ${actual.imagesSize.screenshotName}`\n };\n }\n if (actual.diff && actual.diff.actualDiff > actual.diff.threshold) {\n return {\n pass: false,\n message: `${actual.diff.actualDiff} > ${actual.diff.threshold} : ${visualTestMessages.diffMessage} ${actual.diff.screenshotName}`\n };\n }\n return {\n pass: true,\n message: visualTestMessages.success\n };\n }\n };\n}\n\n/**\n * It will create a file for the passed screenshot object.\n * The path of the new file will be calculated using the parameters\n * Ex: ./dist-screenshots\\OWBooking\\windows_chrome_91\\fare-page-after-click-on-continue-0.png\n * distScreenshotsDir/scenarionName/device/filenameWithoutExtension.png\n * @param screenshot The screenshot object captured. Ex: for protractor - browser.takeScreenshot()\n * @param scenarioName E2e Scenario class name\n * @param device Details of the platform on which the test is run. If there are spaces the helper will do the concatenation. Ex: `Windows 10 chrome 89`\n * @param filenameWithoutExtension file name to save the screenshot - .png will be added at the end\n * @param distScreenshotsDir Name of the directory to save the screenshots\n */\nexport function saveScreenshot(screenshot: string, scenarioName: string, device: string, filenameWithoutExtension: string, distScreenshotsDir = 'dist-screenshots') {\n const screenshotsDir = path.resolve(distScreenshotsDir, scenarioName, `${device.replace(/ +/g, '_')}`);\n if (!fs.existsSync(screenshotsDir)) {\n fs.mkdirSync(screenshotsDir, { recursive: true });\n }\n const fullFileName = `${filenameWithoutExtension}.png`;\n const stream = fs.createWriteStream(path.resolve(screenshotsDir, fullFileName));\n stream.write(Buffer.from(screenshot, 'base64'));\n stream.end();\n}\n\n/**\n * Write the 3 images (base/new/diff) on the reports folder\n * The path inside the reports forlder will be calculated using the parameters\n * @param pathToScenarioReport Path where the scenario report is saved inside reports folder\n * @param screenshotsDirName Name of the directory which will contain the 3 images\n * @param diff diff image\n * @param baseImage the base image\n * @param currentImg the actual taken screenshot image\n */\nexport function writeScreenshotsDiff(pathToScenarioReport: string, screenshotsDirName: string, diff: PNG, baseImage: PNG, currentImg: PNG) {\n const destScreenshotsDiffDir = path.join(pathToScenarioReport, 'screenshots-diff', screenshotsDirName);\n if (!fs.existsSync(destScreenshotsDiffDir)) {\n fs.mkdirSync(destScreenshotsDiffDir, { recursive: true });\n }\n\n const diffPath = path.resolve(destScreenshotsDiffDir, 'diff.png');\n const oldPath = path.resolve(destScreenshotsDiffDir, 'old.png');\n const newPath = path.resolve(destScreenshotsDiffDir, 'new.png');\n fs.writeFileSync(diffPath, PNG.sync.write(diff));\n fs.writeFileSync(oldPath, PNG.sync.write(baseImage));\n fs.writeFileSync(newPath, PNG.sync.write(currentImg));\n}\n\n/**\n * Compare images helper function. If the comparison fails the 3 images (base/new/diff) will be written inside the reports folder of the actual scenario\n * @param screenshot Actual captured screenshot object\n * @param baseImagePath The path to the base screenshot\n * @param threshold The diff between base screenshot and the current one should not be bigger than this value.\n * @param pathToScenarioReport Path where the scenario report is saved inside reports folder. Used to compute the path to write diff images in case there is a diff at comparison\n * @returns An object of visual test result type\n */\nexport function compareScreenshot(screenshot: string, baseImagePath: string, threshold: number, pathToScenarioReport: string): VisualTestResult {\n const baseImageExists = fs.existsSync(baseImagePath);\n if (baseImageExists) {\n const baseImage = PNG.sync.read(fs.readFileSync(baseImagePath));\n const { width, height } = baseImage;\n const diff = new PNG({ width, height });\n\n const screenshotBuffer = Buffer.from(screenshot, 'base64');\n const currentImg = PNG.sync.read(screenshotBuffer);\n const diffDirName = path.basename(baseImagePath, '.png');\n let result;\n try {\n result = pixelmatch(baseImage.data, currentImg.data, diff.data, width, height, { threshold: 0.1 });\n } catch (err: any) {\n if (!err.toString().includes('Image sizes do not match.')) {\n throw err;\n }\n writeScreenshotsDiff(pathToScenarioReport, diffDirName, diff, baseImage, currentImg);\n return { imagesSize: { screenshotName: diffDirName } };\n }\n const pr = Math.round(100 * 100 * result / (width * height)) / 100;\n if (pr > threshold) {\n writeScreenshotsDiff(pathToScenarioReport, diffDirName, diff, baseImage, currentImg);\n }\n return { diff: { actualDiff: pr, threshold, screenshotName: diffDirName } };\n } else {\n return { baseScreenshotNotFound: { baseScreenshotPath: baseImagePath } };\n }\n}\n\n/**\n * Helper function to perform a visual test operation\n * @param screenshotObj Ex: for protractor browser.takeScreenshot()\n * @param filenameWithoutExtension file name to save the screenshot - .png will be added at the end\n * @param device os followed by browser version - ex: `Windows 10 chrome 89`\n * @param scenarioName E2e Scenario class name\n * @param pathToScenarioReport Path used in compare mode for saving the base,new,diff images in reports in case there is a diff\n * @param threshold The diff between base screenshot and the current one should not be bigger than this value.\n * If the diff is bigger, 3 png files will be created: base screenshot, new screenshot and diff image\n * @param generateMode If true it will generate the screenshot file without screenshots comparison\n * @param baseScreenshotsDirPath The folder path to search base screenshots; used only in compare mode\n */\nexport function o3rVisualTest(\n screenshotObj: string,\n filenameWithoutExtension: string,\n device: string,\n scenarioName: string,\n pathToScenarioReport = 'reports',\n threshold = 0,\n generateMode = false,\n baseScreenshotsDirPath = 'dist-screenshots-base'\n) {\n if (generateMode) {\n saveScreenshot(screenshotObj, scenarioName, device, filenameWithoutExtension);\n return { generateMode: true };\n } else {\n const baseImagePath = path.resolve(baseScreenshotsDirPath, scenarioName, device, `${filenameWithoutExtension}.png`);\n const visualTestResult = compareScreenshot(screenshotObj, baseImagePath, threshold, pathToScenarioReport);\n return visualTestResult;\n }\n}\n"]}
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/visual-testing-reporter/index.ts"],"names":[],"mappings":"
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/visual-testing-reporter/index.ts"],"names":[],"mappings":"AAQA,OAAO,EACL,KAAK,UAAU,EACf,KAAK,QAAQ,EACb,KAAK,KAAK,EACX,MAAM,2BAA2B,CAAC;AAEnC;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,MAAM,2BAA2B,GAAG,qBAAqB,EAAE,CAAC;AAElE;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC3C,qCAAqC;IACrC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,qBAAa,+BAAgC,YAAW,QAAQ;IAC9D,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAExB,OAAO,EAAE,4BAA4B;IAIjD;;OAEG;IACI,aAAa;IAIpB;;OAEG;IACI,OAAO,CAAC,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK;IAI1C;;OAEG;IACU,KAAK;CAgCnB;AAED,eAAe,+BAA+B,CAAC"}
|
@@ -1,63 +1,60 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
3
|
exports.VisualTestingPlaywrightReporter = void 0;
|
4
|
-
|
5
|
-
|
6
|
-
var node_path_1 = require("node:path");
|
4
|
+
const promises_1 = require("node:fs/promises");
|
5
|
+
const node_path_1 = require("node:path");
|
7
6
|
/**
|
8
7
|
* Playwright reporter for visual testing
|
9
8
|
*/
|
10
|
-
|
11
|
-
|
9
|
+
class VisualTestingPlaywrightReporter {
|
10
|
+
constructor(options) {
|
12
11
|
this.outputFile = options.outputFile || (0, node_path_1.join)(process.cwd(), 'playwright-reports/visual-testing/report.json');
|
13
12
|
}
|
14
13
|
/**
|
15
14
|
* @inheritdoc
|
16
15
|
*/
|
17
|
-
|
16
|
+
printsToStdio() {
|
18
17
|
return false;
|
19
|
-
}
|
18
|
+
}
|
20
19
|
/**
|
21
20
|
* @inheritdoc
|
22
21
|
*/
|
23
|
-
|
22
|
+
onBegin(_, suite) {
|
24
23
|
this.suite = suite;
|
25
|
-
}
|
24
|
+
}
|
26
25
|
/**
|
27
26
|
* @inheritdoc
|
28
27
|
*/
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
return [4 /*yield*/, (0, promises_1.mkdir)((0, node_path_1.dirname)(this.outputFile), { recursive: true })];
|
52
|
-
case 1:
|
53
|
-
_a.sent();
|
54
|
-
return [2 /*return*/, (0, promises_1.writeFile)(this.outputFile, JSON.stringify(screenshotsToUpdate, null, 2))];
|
28
|
+
async onEnd() {
|
29
|
+
const fileNameExtractor = /snapshot:\s*(?<browser>[\w-]+)[\\/]+(?<filename>[\w-]+)\.(?<extension>\w+)/i;
|
30
|
+
const screenshotsToUpdate = this.suite.allTests().flatMap((test) => test.results.flatMap((result) => result.errors
|
31
|
+
.reduce((acc, error) => {
|
32
|
+
if (error.matcherResult?.name === 'toHaveScreenshot'
|
33
|
+
&& typeof error.matcherResult.actual === 'string'
|
34
|
+
&& typeof error.matcherResult.expected === 'string'
|
35
|
+
&& !error.matcherResult.pass) {
|
36
|
+
// Playwright <= 1.51
|
37
|
+
acc.push({ actual: error.matcherResult.actual, expected: error.matcherResult.expected });
|
38
|
+
}
|
39
|
+
else if (fileNameExtractor.test(error.message)) {
|
40
|
+
// Playwright > 1.51
|
41
|
+
const { filename, extension } = fileNameExtractor.exec(error.message).groups;
|
42
|
+
if (filename && extension) {
|
43
|
+
const attachmentMatcherActual = new RegExp(`[\\\\/]${filename}-actual.${extension}`);
|
44
|
+
const attachmentMatcherExpected = new RegExp(`[\\\\/]${filename}-expected.${extension}`);
|
45
|
+
const actual = result.attachments.find((attachment) => attachmentMatcherActual.test(attachment.name));
|
46
|
+
const expected = result.attachments.find((attachment) => attachmentMatcherExpected.test(attachment.name));
|
47
|
+
if (actual?.path && expected?.path) {
|
48
|
+
acc.push({ actual: actual.path, expected: expected.path });
|
49
|
+
}
|
55
50
|
}
|
56
|
-
}
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
51
|
+
}
|
52
|
+
return acc;
|
53
|
+
}, [])));
|
54
|
+
await (0, promises_1.mkdir)((0, node_path_1.dirname)(this.outputFile), { recursive: true });
|
55
|
+
return (0, promises_1.writeFile)(this.outputFile, JSON.stringify(screenshotsToUpdate, null, 2));
|
56
|
+
}
|
57
|
+
}
|
61
58
|
exports.VisualTestingPlaywrightReporter = VisualTestingPlaywrightReporter;
|
62
59
|
exports.default = VisualTestingPlaywrightReporter;
|
63
60
|
//# sourceMappingURL=index.js.map
|