@brightspace-ui/testing 1.38.3 → 1.39.1
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/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@brightspace-ui/testing",
|
|
3
3
|
"description": "Utilities for testing front-end components and applications",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.39.1",
|
|
6
6
|
"repository": "https://github.com/BrightspaceUI/testing.git",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"lint": "npm run lint:eslint && npm run lint:style",
|
package/src/browser/vdiff.js
CHANGED
|
@@ -89,9 +89,11 @@ async function ScreenshotAndCompare(opts = {}) {
|
|
|
89
89
|
({ rect, fullPage } = findLargestRect(elemsToInclude, margin));
|
|
90
90
|
}
|
|
91
91
|
const slowDuration = this.test.slow();
|
|
92
|
+
const altTests = [];
|
|
93
|
+
if (opts.allColorModes) altTests.push('dark');
|
|
92
94
|
|
|
93
95
|
this.test.timeout(0);
|
|
94
|
-
const result = await executeServerCommand('brightspace-visual-diff-compare', { name, fullPage, rect, slowDuration });
|
|
96
|
+
const result = await executeServerCommand('brightspace-visual-diff-compare', { altTests, name, fullPage, rect, slowDuration });
|
|
95
97
|
|
|
96
98
|
if (window.d2lTest) document.documentElement.classList.remove('screenshot');
|
|
97
99
|
|
|
@@ -8,9 +8,9 @@ export class TestInfoManager {
|
|
|
8
8
|
return testInfoMap.get(this.key);
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
set(testInfo) {
|
|
11
|
+
set(testInfo, alt = 'default') {
|
|
12
12
|
if (testInfoMap.has(this.key)) {
|
|
13
|
-
const info = this.get();
|
|
13
|
+
const info = this.get()?.[alt] || {};
|
|
14
14
|
testInfo.slowDuration = info.slowDuration;
|
|
15
15
|
|
|
16
16
|
if (info.golden || testInfo.golden) {
|
|
@@ -20,8 +20,10 @@ export class TestInfoManager {
|
|
|
20
20
|
testInfo.new = { ...info.new, ...testInfo.new };
|
|
21
21
|
}
|
|
22
22
|
testInfo.diff = testInfo.diff || info.diff;
|
|
23
|
+
testInfo.byteSizeDiff = testInfo.byteSizeDiff || info.byteSizeDiff;
|
|
23
24
|
}
|
|
24
|
-
testInfoMap.
|
|
25
|
+
const tests = testInfoMap.get(this.key) || {};
|
|
26
|
+
testInfoMap.set(this.key, { ...tests, [alt]: testInfo });
|
|
25
27
|
}
|
|
26
28
|
}
|
|
27
29
|
|
|
@@ -9,6 +9,17 @@ import { TestInfoManager } from './visual-diff-info.js';
|
|
|
9
9
|
const isCI = !!env['CI'];
|
|
10
10
|
const DEFAULT_TOLERANCE = 0; // TODO: Support tolerance override?
|
|
11
11
|
|
|
12
|
+
const ALT_TESTS = {
|
|
13
|
+
dark: {
|
|
14
|
+
set() {
|
|
15
|
+
document.documentElement.setAttribute('data-color-mode', 'dark');
|
|
16
|
+
},
|
|
17
|
+
reset() {
|
|
18
|
+
document.documentElement.removeAttribute('data-color-mode');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
12
23
|
async function checkFileExists(fileName) {
|
|
13
24
|
try {
|
|
14
25
|
await access(fileName, constants.F_OK);
|
|
@@ -67,11 +78,20 @@ function getResizedPng(xName, yName, original, newSize) {
|
|
|
67
78
|
return resized;
|
|
68
79
|
}
|
|
69
80
|
|
|
81
|
+
let bestPosition = null;
|
|
70
82
|
async function getBestResizePng(screenshotImage, goldenImage) {
|
|
71
83
|
const newWidth = Math.max(screenshotImage.width, goldenImage.width);
|
|
72
84
|
const newHeight = Math.max(screenshotImage.height, goldenImage.height);
|
|
73
85
|
const newSize = { width: newWidth, height: newHeight };
|
|
74
86
|
|
|
87
|
+
if (bestPosition !== null) {
|
|
88
|
+
const [y, x] = bestPosition.split('-');
|
|
89
|
+
const screenshot = getResizedPng(x, y, screenshotImage, newSize);
|
|
90
|
+
const golden = getResizedPng(x, y, goldenImage, newSize);
|
|
91
|
+
const { diff, pixelsDiff } = getPixelsDiff(screenshot, golden);
|
|
92
|
+
return { screenshot, golden, diff, pixelsDiff };
|
|
93
|
+
}
|
|
94
|
+
|
|
75
95
|
const newScreenshots = await createComparisonPNGs(screenshotImage, newSize);
|
|
76
96
|
const newGoldens = await createComparisonPNGs(goldenImage, newSize);
|
|
77
97
|
|
|
@@ -88,9 +108,10 @@ async function getBestResizePng(screenshotImage, goldenImage) {
|
|
|
88
108
|
}
|
|
89
109
|
}
|
|
90
110
|
|
|
111
|
+
bestPosition = newScreenshots[bestIndex].position;
|
|
91
112
|
return {
|
|
92
|
-
screenshot: newScreenshots[bestIndex],
|
|
93
|
-
golden: newGoldens[bestIndex],
|
|
113
|
+
screenshot: newScreenshots[bestIndex].png,
|
|
114
|
+
golden: newGoldens[bestIndex].png,
|
|
94
115
|
diff: bestDiffImage,
|
|
95
116
|
pixelsDiff: bestPixelsDiff
|
|
96
117
|
};
|
|
@@ -174,10 +195,14 @@ export function visualDiff({ updateGoldens = false, runSubset = false } = {}) {
|
|
|
174
195
|
const { dir, newName } = extractTestPartsFromName(payload.name);
|
|
175
196
|
const testPath = dirname(session.testFile).replace(rootDir, '');
|
|
176
197
|
const newPath = join(rootDir, PATHS.VDIFF_ROOT, testPath, dir);
|
|
177
|
-
const
|
|
178
|
-
const
|
|
198
|
+
const goldenFile = join(newPath, PATHS.GOLDEN, browser, newName);
|
|
199
|
+
const passFile = join(newPath, PATHS.PASS, browser, newName);
|
|
179
200
|
const screenshotFile = join(newPath, PATHS.FAIL, browser, newName);
|
|
180
|
-
|
|
201
|
+
function getFileName(base, alt = 'default') {
|
|
202
|
+
return `${base}${alt === 'default' ? '' : `.${alt}`}.png`;
|
|
203
|
+
}
|
|
204
|
+
const tests = ['default', ...(payload.altTests || [])];
|
|
205
|
+
const rootLength = join(rootDir, PATHS.VDIFF_ROOT).length + 1;
|
|
181
206
|
|
|
182
207
|
if (command !== 'brightspace-visual-diff-compare') return;
|
|
183
208
|
if (!isCI) { // CI will be a fresh .vdiff folder each time and only one run
|
|
@@ -201,22 +226,28 @@ export function visualDiff({ updateGoldens = false, runSubset = false } = {}) {
|
|
|
201
226
|
if (payload.fullPage) screenshotOpts.fullPage = true;
|
|
202
227
|
if (payload.rect) screenshotOpts.clip = payload.rect;
|
|
203
228
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const
|
|
207
|
-
|
|
229
|
+
const page = session.browser.getPage(session.id);
|
|
230
|
+
async function takeScreenshot(alt = 'default') {
|
|
231
|
+
const path = getFileName(updateGoldens ? goldenFile : screenshotFile, alt);
|
|
232
|
+
if (ALT_TESTS[alt]?.set) page.evaluate(ALT_TESTS[alt].set);
|
|
233
|
+
const screenshot = await page.screenshot({ path, ...screenshotOpts });
|
|
234
|
+
if (ALT_TESTS[alt]?.reset) page.evaluate(ALT_TESTS[alt].reset);
|
|
235
|
+
return screenshot;
|
|
208
236
|
}
|
|
209
237
|
|
|
210
|
-
const
|
|
238
|
+
const screenshotFileBuffers = {};
|
|
239
|
+
for (const test of tests) screenshotFileBuffers[test] = await takeScreenshot(test);
|
|
211
240
|
|
|
212
241
|
if (updateGoldens) {
|
|
213
242
|
return { pass: true };
|
|
214
243
|
}
|
|
215
244
|
|
|
216
|
-
|
|
245
|
+
async function runCompareTest(alt = 'default') {
|
|
246
|
+
const screenshotFileName = getFileName(screenshotFile, alt);
|
|
247
|
+
const goldenFileName = getFileName(goldenFile, alt);
|
|
248
|
+
const passFileName = getFileName(passFile, alt);
|
|
217
249
|
|
|
218
|
-
|
|
219
|
-
const screenshotImage = PNG.sync.read(screenshotFileBuffer);
|
|
250
|
+
const screenshotImage = PNG.sync.read(screenshotFileBuffers[alt]);
|
|
220
251
|
infoManager.set({
|
|
221
252
|
slowDuration: payload.slowDuration,
|
|
222
253
|
new: {
|
|
@@ -224,7 +255,7 @@ export function visualDiff({ updateGoldens = false, runSubset = false } = {}) {
|
|
|
224
255
|
path: screenshotFileName.substring(rootLength),
|
|
225
256
|
width: screenshotImage.width
|
|
226
257
|
}
|
|
227
|
-
});
|
|
258
|
+
}, alt);
|
|
228
259
|
|
|
229
260
|
const goldenExists = await checkFileExists(goldenFileName);
|
|
230
261
|
if (!goldenExists) {
|
|
@@ -239,19 +270,19 @@ export function visualDiff({ updateGoldens = false, runSubset = false } = {}) {
|
|
|
239
270
|
path: goldenFileName.substring(rootLength),
|
|
240
271
|
width: goldenImage.width
|
|
241
272
|
}
|
|
242
|
-
});
|
|
273
|
+
}, alt);
|
|
243
274
|
|
|
244
275
|
if (screenshotImage.width === goldenImage.width && screenshotImage.height === goldenImage.height) {
|
|
245
276
|
const goldenSize = (await stat(goldenFileName)).size;
|
|
246
277
|
const screenshotSize = (await stat(screenshotFileName)).size;
|
|
247
|
-
if (goldenSize === screenshotSize &&
|
|
278
|
+
if (goldenSize === screenshotSize && screenshotFileBuffers[alt].equals(goldenFileBuffer)) {
|
|
248
279
|
const success = await tryMoveFile(screenshotFileName, passFileName);
|
|
249
280
|
if (!success) return { pass: false, message: 'Problem moving file to "pass" directory.' };
|
|
250
281
|
infoManager.set({
|
|
251
282
|
new: {
|
|
252
283
|
path: passFileName.substring(rootLength)
|
|
253
284
|
}
|
|
254
|
-
});
|
|
285
|
+
}, alt);
|
|
255
286
|
return { pass: true };
|
|
256
287
|
}
|
|
257
288
|
|
|
@@ -259,10 +290,10 @@ export function visualDiff({ updateGoldens = false, runSubset = false } = {}) {
|
|
|
259
290
|
|
|
260
291
|
if (pixelsDiff !== 0) {
|
|
261
292
|
infoManager.set({
|
|
262
|
-
diff: `${screenshotFile.substring(rootLength)
|
|
293
|
+
diff: getFileName(`${screenshotFile}-diff`, alt).substring(rootLength),
|
|
263
294
|
pixelsDiff
|
|
264
|
-
});
|
|
265
|
-
await writeFile(`${screenshotFile}-diff
|
|
295
|
+
}, alt);
|
|
296
|
+
await writeFile(getFileName(`${screenshotFile}-diff`, alt), PNG.sync.write(diff));
|
|
266
297
|
return { pass: false, message: `Image does not match golden. ${pixelsDiff} pixels are different.` };
|
|
267
298
|
} else {
|
|
268
299
|
infoManager.set({
|
|
@@ -272,31 +303,49 @@ export function visualDiff({ updateGoldens = false, runSubset = false } = {}) {
|
|
|
272
303
|
new: {
|
|
273
304
|
byteSize: screenshotSize
|
|
274
305
|
},
|
|
275
|
-
pixelsDiff
|
|
276
|
-
|
|
306
|
+
pixelsDiff,
|
|
307
|
+
byteSizeDiff: true
|
|
308
|
+
}, alt);
|
|
277
309
|
return { pass: false, message: 'Image diff is clean but the images do not have the same bytes.' };
|
|
278
310
|
}
|
|
279
311
|
} else {
|
|
280
312
|
const { screenshot, golden, diff, pixelsDiff } = await getBestResizePng(screenshotImage, goldenImage);
|
|
281
313
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
314
|
+
const screenshotResizedPath = getFileName(`${screenshotFile}-resized-screenshot`, alt);
|
|
315
|
+
const goldenResizedPath = getFileName(`${screenshotFile}-resized-golden`, alt);
|
|
316
|
+
const diffPath = getFileName(`${screenshotFile}-diff`, alt);
|
|
317
|
+
await writeFile(screenshotResizedPath, PNG.sync.write(screenshot));
|
|
318
|
+
await writeFile(goldenResizedPath, PNG.sync.write(golden));
|
|
319
|
+
await writeFile(diffPath, PNG.sync.write(diff));
|
|
285
320
|
infoManager.set({
|
|
286
|
-
diff:
|
|
321
|
+
diff: diffPath.substring(rootLength),
|
|
287
322
|
golden: {
|
|
288
|
-
path:
|
|
323
|
+
path: goldenResizedPath.substring(rootLength)
|
|
289
324
|
},
|
|
290
325
|
new: {
|
|
291
|
-
path:
|
|
326
|
+
path: screenshotResizedPath.substring(rootLength)
|
|
292
327
|
},
|
|
293
328
|
pixelsDiff
|
|
294
|
-
});
|
|
329
|
+
}, alt);
|
|
295
330
|
|
|
296
331
|
return { pass: false, message: `Images are not the same size. When resized, ${pixelsDiff} pixels are different.` };
|
|
297
332
|
}
|
|
298
333
|
}
|
|
299
|
-
|
|
334
|
+
|
|
335
|
+
bestPosition = null; // reset best position
|
|
336
|
+
let pass = true;
|
|
337
|
+
let errors = '';
|
|
338
|
+
const testResults = {};
|
|
339
|
+
for (const test of tests) {
|
|
340
|
+
testResults[test] = await runCompareTest(test);
|
|
341
|
+
if (!testResults[test].pass) {
|
|
342
|
+
pass = false;
|
|
343
|
+
errors += `\n(${test}) ${testResults[test].message}`;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
if (pass) return { pass: true };
|
|
347
|
+
else if (tests.length === 1) return testResults[tests[0]];
|
|
348
|
+
else return { pass: false, message: `One or more tests failed:${errors}` };
|
|
300
349
|
}
|
|
301
350
|
};
|
|
302
351
|
}
|
|
@@ -85,39 +85,43 @@ function flattenResults(session, browserData, fileData) {
|
|
|
85
85
|
|
|
86
86
|
function collectTests(prefix, tests) {
|
|
87
87
|
tests.forEach(t => {
|
|
88
|
-
|
|
88
|
+
let testName = `${prefix}${t.name}`;
|
|
89
89
|
const testKey = testName.replaceAll(' > ', ' ');
|
|
90
|
-
const
|
|
90
|
+
const allTestInfo = getTestInfo(session, testKey);
|
|
91
91
|
|
|
92
92
|
// tests missing info but with no error were skipped via grep, so exclude them
|
|
93
|
-
if (!
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
93
|
+
if (!allTestInfo && !t.error) return;
|
|
94
|
+
|
|
95
|
+
for (const test in allTestInfo) {
|
|
96
|
+
const info = allTestInfo[test];
|
|
97
|
+
if (test !== 'default') testName = `${prefix}${t.name} (${test})`;
|
|
98
|
+
if (!fileData.tests.has(testName)) {
|
|
99
|
+
fileData.tests.set(testName, {
|
|
100
|
+
name: testName,
|
|
101
|
+
numByteDiff: 0,
|
|
102
|
+
numFailed: 0,
|
|
103
|
+
results: []
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
const testData = fileData.tests.get(testName);
|
|
107
|
+
const passed = !!(info.golden) && !(info.pixelsDiff) && !(info.byteSizeDiff);
|
|
108
|
+
if (!passed) {
|
|
109
|
+
if (info.byteSizeDiff) {
|
|
110
|
+
browserData.numByteDiff++;
|
|
111
|
+
testData.numByteDiff++;
|
|
112
|
+
}
|
|
113
|
+
browserData.numFailed++;
|
|
114
|
+
testData.numFailed++;
|
|
109
115
|
}
|
|
110
|
-
|
|
111
|
-
|
|
116
|
+
testData.results.push({
|
|
117
|
+
name: browserData.name,
|
|
118
|
+
duration: t.duration,
|
|
119
|
+
error: t.error?.message,
|
|
120
|
+
passed,
|
|
121
|
+
bytediff: info.byteSizeDiff,
|
|
122
|
+
info: info
|
|
123
|
+
});
|
|
112
124
|
}
|
|
113
|
-
testData.results.push({
|
|
114
|
-
name: browserData.name,
|
|
115
|
-
duration: t.duration,
|
|
116
|
-
error: t.error?.message,
|
|
117
|
-
passed: t.passed,
|
|
118
|
-
bytediff,
|
|
119
|
-
info: info
|
|
120
|
-
});
|
|
121
125
|
});
|
|
122
126
|
}
|
|
123
127
|
|