@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.38.3",
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",
@@ -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.set(this.key, testInfo);
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 goldenFileName = `${join(newPath, PATHS.GOLDEN, browser, newName)}.png`;
178
- const passFileName = `${join(newPath, PATHS.PASS, browser, newName)}.png`;
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
- const screenshotFileName = `${screenshotFile}.png`;
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
- async function takeScreenshot() {
205
- const path = updateGoldens ? goldenFileName : screenshotFileName;
206
- const page = session.browser.getPage(session.id);
207
- return await page.screenshot({ path, ...screenshotOpts });
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 screenshotFileBuffer = await takeScreenshot();
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
- const rootLength = join(rootDir, PATHS.VDIFF_ROOT).length + 1;
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
- async function runCompareTest() {
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 && screenshotFileBuffer.equals(goldenFileBuffer)) {
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)}-diff.png`,
293
+ diff: getFileName(`${screenshotFile}-diff`, alt).substring(rootLength),
263
294
  pixelsDiff
264
- });
265
- await writeFile(`${screenshotFile}-diff.png`, PNG.sync.write(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
- await writeFile(`${screenshotFile}-resized-screenshot.png`, PNG.sync.write(screenshot.png));
283
- await writeFile(`${screenshotFile}-resized-golden.png`, PNG.sync.write(golden.png));
284
- await writeFile(`${screenshotFile}-diff.png`, PNG.sync.write(diff));
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: `${screenshotFile.substring(rootLength)}-diff.png`,
321
+ diff: diffPath.substring(rootLength),
287
322
  golden: {
288
- path: `${screenshotFile.substring(rootLength)}-resized-golden.png`
323
+ path: goldenResizedPath.substring(rootLength)
289
324
  },
290
325
  new: {
291
- path: `${screenshotFile.substring(rootLength)}-resized-screenshot.png`
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
- return await runCompareTest();
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
- const testName = `${prefix}${t.name}`;
88
+ let testName = `${prefix}${t.name}`;
89
89
  const testKey = testName.replaceAll(' > ', ' ');
90
- const info = getTestInfo(session, testKey);
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 (!info && !t.error) return;
94
-
95
- if (!fileData.tests.has(testName)) {
96
- fileData.tests.set(testName, {
97
- name: testName,
98
- numByteDiff: 0,
99
- numFailed: 0,
100
- results: []
101
- });
102
- }
103
- const testData = fileData.tests.get(testName);
104
- const bytediff = !t.passed && info?.diff === undefined && info?.pixelsDiff === 0;
105
- if (!t.passed) {
106
- if (bytediff) {
107
- browserData.numByteDiff++;
108
- testData.numByteDiff++;
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
- browserData.numFailed++;
111
- testData.numFailed++;
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