@brightspace-ui/testing 1.38.0 → 1.38.2

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.0",
5
+ "version": "1.38.2",
6
6
  "repository": "https://github.com/BrightspaceUI/testing.git",
7
7
  "scripts": {
8
8
  "lint": "npm run lint:eslint && npm run lint:style",
@@ -0,0 +1,30 @@
1
+ const testInfoMap = new Map();
2
+
3
+ export class TestInfoManager {
4
+ constructor(session, fullTitle) {
5
+ this.key = `${session.browser.name.toLowerCase()}|${session.testFile}|${fullTitle}`;
6
+ }
7
+ get() {
8
+ return testInfoMap.get(this.key);
9
+ }
10
+
11
+ set(testInfo) {
12
+ if (testInfoMap.has(this.key)) {
13
+ const info = this.get();
14
+ testInfo.slowDuration = info.slowDuration;
15
+
16
+ if (info.golden || testInfo.golden) {
17
+ testInfo.golden = { ...info.golden, ...testInfo.golden };
18
+ }
19
+ if (info.new || testInfo.new) {
20
+ testInfo.new = { ...info.new, ...testInfo.new };
21
+ }
22
+ testInfo.diff = testInfo.diff || info.diff;
23
+ }
24
+ testInfoMap.set(this.key, testInfo);
25
+ }
26
+ }
27
+
28
+ export function getTestInfo(session, fullTitle) {
29
+ return new TestInfoManager(session, fullTitle).get();
30
+ }
@@ -4,6 +4,7 @@ import { env } from 'node:process';
4
4
  import { PATHS } from './paths.js';
5
5
  import pixelmatch from 'pixelmatch';
6
6
  import { PNG } from 'pngjs';
7
+ import { TestInfoManager } from './visual-diff-info.js';
7
8
 
8
9
  const isCI = !!env['CI'];
9
10
  const DEFAULT_TOLERANCE = 0; // TODO: Support tolerance override?
@@ -50,24 +51,31 @@ async function clearDiffPaths(dir) {
50
51
  }
51
52
  }
52
53
 
54
+ function getResizedPng(xName, yName, original, newSize) {
55
+ const xOffsets = {
56
+ left: 0,
57
+ center: Math.floor((newSize.width - original.width) / 2),
58
+ right: newSize.width - original.width
59
+ };
60
+ const yOffsets = {
61
+ top: 0,
62
+ center: Math.floor((newSize.height - original.height) / 2),
63
+ bottom: newSize.height - original.height
64
+ };
65
+ const resized = new PNG(newSize);
66
+ PNG.bitblt(original, resized, 0, 0, original.width, original.height, xOffsets[xName], yOffsets[yName]);
67
+ return resized;
68
+ }
69
+
53
70
  async function createComparisonPNGs(original, newSize) {
54
71
  const resizedPNGs = [];
55
- [
56
- { name: 'top', coord: 0 },
57
- { name: 'center', coord: Math.floor((newSize.height - original.height) / 2) },
58
- { name: 'bottom', coord: newSize.height - original.height }
59
- ].forEach(y => {
60
- [
61
- { name: 'left', coord: 0 },
62
- { name: 'center', coord: Math.floor((newSize.width - original.width) / 2) },
63
- { name: 'right', coord: newSize.width - original.width }
64
- ].forEach(x => { // TODO: position added for reports, remove/adjust as needed
72
+ [ 'top', 'center', 'bottom' ].forEach(y => {
73
+ ['left', 'center', 'right'].forEach(x => {
65
74
  if (original.width === newSize.width && original.height === newSize.height) {
66
- resizedPNGs.push({ png: original, position: `${y.name}-${x.name}` });
75
+ resizedPNGs.push({ png: original, position: `${y}-${x}` });
67
76
  } else {
68
- const resized = new PNG(newSize);
69
- PNG.bitblt(original, resized, 0, 0, original.width, original.height, x.coord, y.coord);
70
- resizedPNGs.push({ png: resized, position: `${y.name}-${x.name}` });
77
+ const resized = getResizedPng(x, y, original, newSize);
78
+ resizedPNGs.push({ png: resized, position: `${y}-${x}` });
71
79
  }
72
80
  });
73
81
  });
@@ -75,6 +83,14 @@ async function createComparisonPNGs(original, newSize) {
75
83
  return resizedPNGs;
76
84
  }
77
85
 
86
+ function getPixelsDiff(screenshotImage, goldenImage) {
87
+ const diff = new PNG({ width: screenshotImage.width, height: screenshotImage.height });
88
+ const pixelsDiff = pixelmatch(
89
+ screenshotImage.data, goldenImage.data, diff.data, screenshotImage.width, screenshotImage.height, { diffMask: true, threshold: DEFAULT_TOLERANCE }
90
+ );
91
+ return { diff, pixelsDiff };
92
+ }
93
+
78
94
  async function tryMoveFile(srcFileName, destFileName) {
79
95
  await mkdir(dirname(destFileName), { recursive: true });
80
96
  try {
@@ -105,29 +121,6 @@ function extractTestPartsFromName(name) {
105
121
  };
106
122
  }
107
123
 
108
- const testInfoMap = new Map();
109
- export function getTestInfo(session, fullTitle) {
110
- return testInfoMap.get(getTestInfoKey(session, fullTitle));
111
- }
112
- function getTestInfoKey(session, fullTitle) {
113
- return `${session.browser.name.toLowerCase()}|${session.testFile}|${fullTitle}`;
114
- }
115
- function setTestInfo(session, fullTitle, testInfo) {
116
- const key = getTestInfoKey(session, fullTitle);
117
- if (testInfoMap.has(key)) {
118
- const info = testInfoMap.get(key);
119
- testInfo.slowDuration = info.slowDuration;
120
- if (info.golden || testInfo.golden) {
121
- testInfo.golden = { ...info.golden, ...testInfo.golden };
122
- }
123
- if (info.new || testInfo.new) {
124
- testInfo.new = { ...info.new, ...testInfo.new };
125
- }
126
- testInfo.diff = testInfo.diff || info.diff;
127
- }
128
- testInfoMap.set(key, testInfo);
129
- }
130
-
131
124
  export function visualDiff({ updateGoldens = false, runSubset = false } = {}) {
132
125
  let currentRun = 0,
133
126
  rootDir;
@@ -142,6 +135,7 @@ export function visualDiff({ updateGoldens = false, runSubset = false } = {}) {
142
135
  await clearAllDirs(updateGoldens, join(rootDir, PATHS.VDIFF_ROOT));
143
136
  },
144
137
  async executeCommand({ command, payload, session }) {
138
+ const infoManager = new TestInfoManager(session, payload.name);
145
139
 
146
140
  if (session.browser.type !== 'playwright') {
147
141
  throw new Error('Visual-diff is only supported for browser type Playwright.');
@@ -172,15 +166,19 @@ export function visualDiff({ updateGoldens = false, runSubset = false } = {}) {
172
166
  }
173
167
 
174
168
  const screenshotOpts = {
175
- animations: 'disabled',
176
- path: updateGoldens ? goldenFileName : screenshotFileName
169
+ animations: 'disabled'
177
170
  };
178
171
 
179
172
  if (payload.fullPage) screenshotOpts.fullPage = true;
180
173
  if (payload.rect) screenshotOpts.clip = payload.rect;
181
174
 
182
- const page = session.browser.getPage(session.id);
183
- await page.screenshot(screenshotOpts);
175
+ async function takeScreenshot() {
176
+ const path = updateGoldens ? goldenFileName : screenshotFileName;
177
+ const page = session.browser.getPage(session.id);
178
+ return await page.screenshot({ path, ...screenshotOpts });
179
+ }
180
+
181
+ const screenshotFileBuffer = await takeScreenshot();
184
182
 
185
183
  if (updateGoldens) {
186
184
  return { pass: true };
@@ -188,77 +186,72 @@ export function visualDiff({ updateGoldens = false, runSubset = false } = {}) {
188
186
 
189
187
  const rootLength = join(rootDir, PATHS.VDIFF_ROOT).length + 1;
190
188
 
191
- const screenshotFileBuffer = await readFile(screenshotFileName);
192
- const screenshotImage = PNG.sync.read(screenshotFileBuffer);
193
- setTestInfo(session, payload.name, {
194
- slowDuration: payload.slowDuration,
195
- new: {
196
- height: screenshotImage.height,
197
- path: passFileName.substring(rootLength),
198
- width: screenshotImage.width
199
- }
200
- });
201
-
202
- const goldenExists = await checkFileExists(goldenFileName);
203
- if (!goldenExists) {
204
- setTestInfo(session, payload.name, {
189
+ async function runCompareTest() {
190
+ const screenshotImage = PNG.sync.read(screenshotFileBuffer);
191
+ infoManager.set({
192
+ slowDuration: payload.slowDuration,
205
193
  new: {
206
- path: `${screenshotFile.substring(rootLength)}.png`
194
+ height: screenshotImage.height,
195
+ path: screenshotFileName.substring(rootLength),
196
+ width: screenshotImage.width
207
197
  }
208
198
  });
209
- return { pass: false, message: 'No golden exists. Use the "--golden" CLI flag to re-run and re-generate goldens.' };
210
- }
211
199
 
212
- const goldenFileBuffer = await readFile(goldenFileName);
213
- const goldenImage = PNG.sync.read(goldenFileBuffer);
214
- setTestInfo(session, payload.name, {
215
- golden: {
216
- height: goldenImage.height,
217
- path: goldenFileName.substring(rootLength),
218
- width: goldenImage.width
200
+ const goldenExists = await checkFileExists(goldenFileName);
201
+ if (!goldenExists) {
202
+ return { pass: false, message: 'No golden exists. Use the "--golden" CLI flag to re-run and re-generate goldens.' };
219
203
  }
220
- });
221
204
 
222
- if (screenshotImage.width === goldenImage.width && screenshotImage.height === goldenImage.height) {
223
- const goldenSize = (await stat(goldenFileName)).size;
224
- const screenshotSize = (await stat(screenshotFileName)).size;
225
- if (goldenSize === screenshotSize && screenshotFileBuffer.equals(goldenFileBuffer)) {
226
- const success = await tryMoveFile(screenshotFileName, passFileName);
227
- if (!success) return { pass: false, message: 'Problem moving file to "pass" directory.' };
228
- return { pass: true };
229
- }
205
+ const goldenFileBuffer = await readFile(goldenFileName);
206
+ const goldenImage = PNG.sync.read(goldenFileBuffer);
207
+ infoManager.set({
208
+ golden: {
209
+ height: goldenImage.height,
210
+ path: goldenFileName.substring(rootLength),
211
+ width: goldenImage.width
212
+ }
213
+ });
230
214
 
231
- const diff = new PNG({ width: screenshotImage.width, height: screenshotImage.height });
232
- const pixelsDiff = pixelmatch(
233
- screenshotImage.data, goldenImage.data, diff.data, screenshotImage.width, screenshotImage.height, { diffMask: true, threshold: DEFAULT_TOLERANCE }
234
- );
235
-
236
- if (pixelsDiff !== 0) {
237
- setTestInfo(session, payload.name, {
238
- diff: `${screenshotFile.substring(rootLength)}-diff.png`,
239
- new: {
240
- path: `${screenshotFile.substring(rootLength)}.png`
241
- },
242
- pixelsDiff
243
- });
244
- await writeFile(`${screenshotFile}-diff.png`, PNG.sync.write(diff));
245
- return { pass: false, message: `Image does not match golden. ${pixelsDiff} pixels are different.` };
215
+ if (screenshotImage.width === goldenImage.width && screenshotImage.height === goldenImage.height) {
216
+ const goldenSize = (await stat(goldenFileName)).size;
217
+ const screenshotSize = (await stat(screenshotFileName)).size;
218
+ if (goldenSize === screenshotSize && screenshotFileBuffer.equals(goldenFileBuffer)) {
219
+ const success = await tryMoveFile(screenshotFileName, passFileName);
220
+ if (!success) return { pass: false, message: 'Problem moving file to "pass" directory.' };
221
+ infoManager.set({
222
+ new: {
223
+ path: passFileName.substring(rootLength)
224
+ }
225
+ });
226
+ return { pass: true };
227
+ }
228
+
229
+ const { diff, pixelsDiff } = getPixelsDiff(screenshotImage, goldenImage);
230
+
231
+ if (pixelsDiff !== 0) {
232
+ infoManager.set({
233
+ diff: `${screenshotFile.substring(rootLength)}-diff.png`,
234
+ pixelsDiff
235
+ });
236
+ await writeFile(`${screenshotFile}-diff.png`, PNG.sync.write(diff));
237
+ return { pass: false, message: `Image does not match golden. ${pixelsDiff} pixels are different.` };
238
+ } else {
239
+ infoManager.set({
240
+ golden: {
241
+ byteSize: goldenSize
242
+ },
243
+ new: {
244
+ byteSize: screenshotSize
245
+ },
246
+ pixelsDiff
247
+ });
248
+ return { pass: false, message: 'Image diff is clean but the images do not have the same bytes.' };
249
+ }
246
250
  } else {
247
- setTestInfo(session, payload.name, {
248
- golden: {
249
- byteSize: goldenSize
250
- },
251
- new: {
252
- path: `${screenshotFile.substring(rootLength)}.png`,
253
- byteSize: screenshotSize
254
- },
255
- pixelsDiff
256
- });
257
- return { pass: false, message: 'Image diff is clean but the images do not have the same bytes.' };
251
+ return { resizeRequired: true };
258
252
  }
259
- } else {
260
- return { resizeRequired: true };
261
253
  }
254
+ return await runCompareTest();
262
255
  } else if (command === 'brightspace-visual-diff-compare-resize') {
263
256
  const screenshotImage = PNG.sync.read(await readFile(screenshotFileName));
264
257
  const goldenImage = PNG.sync.read(await readFile(goldenFileName));
@@ -272,17 +265,14 @@ export function visualDiff({ updateGoldens = false, runSubset = false } = {}) {
272
265
 
273
266
  let bestIndex = -1;
274
267
  let bestDiffImage = null;
275
- let pixelsDiff = Number.MAX_SAFE_INTEGER;
268
+ let bestPixelsDiff = Number.MAX_SAFE_INTEGER;
276
269
  for (let i = 0; i < newScreenshots.length; i++) {
277
- const currentDiff = new PNG(newSize);
278
- const currentPixelsDiff = pixelmatch(
279
- newScreenshots[i].png.data, newGoldens[i].png.data, currentDiff.data, currentDiff.width, currentDiff.height, { diffMask: true, threshold: DEFAULT_TOLERANCE }
280
- );
270
+ const { diff, pixelsDiff } = getPixelsDiff(newScreenshots[i].png, newGoldens[i].png);
281
271
 
282
- if (currentPixelsDiff < pixelsDiff) {
272
+ if (pixelsDiff < bestPixelsDiff) {
283
273
  bestIndex = i;
284
- bestDiffImage = currentDiff;
285
- pixelsDiff = currentPixelsDiff;
274
+ bestDiffImage = diff;
275
+ bestPixelsDiff = pixelsDiff;
286
276
  }
287
277
  }
288
278
 
@@ -291,7 +281,7 @@ export function visualDiff({ updateGoldens = false, runSubset = false } = {}) {
291
281
  await writeFile(`${screenshotFile}-diff.png`, PNG.sync.write(bestDiffImage));
292
282
 
293
283
  const rootLength = join(rootDir, PATHS.VDIFF_ROOT).length + 1;
294
- setTestInfo(session, payload.name, {
284
+ infoManager.set({
295
285
  diff: `${screenshotFile.substring(rootLength)}-diff.png`,
296
286
  golden: {
297
287
  path: `${screenshotFile.substring(rootLength)}-resized-golden.png`
@@ -299,10 +289,10 @@ export function visualDiff({ updateGoldens = false, runSubset = false } = {}) {
299
289
  new: {
300
290
  path: `${screenshotFile.substring(rootLength)}-resized-screenshot.png`
301
291
  },
302
- pixelsDiff
292
+ pixelsDiff: bestPixelsDiff
303
293
  });
304
294
 
305
- return { pass: false, message: `Images are not the same size. When resized, ${pixelsDiff} pixels are different.` };
295
+ return { pass: false, message: `Images are not the same size. When resized, ${bestPixelsDiff} pixels are different.` };
306
296
  }
307
297
 
308
298
  }
@@ -4,7 +4,7 @@ import { dirname, join } from 'node:path';
4
4
  import { env } from 'node:process';
5
5
  import { execSync } from 'node:child_process';
6
6
  import { fileURLToPath } from 'node:url';
7
- import { getTestInfo } from './visual-diff-plugin.js';
7
+ import { getTestInfo } from './visual-diff-info.js';
8
8
  import { PATHS } from './paths.js';
9
9
 
10
10
  const __dirname = dirname(fileURLToPath(import.meta.url));