flexysnap 0.1.3-alpha.2 → 0.1.5-alpha.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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flexysnap",
3
- "version": "0.1.3-alpha.2",
3
+ "version": "0.1.5-alpha.1",
4
4
  "description": "Flexible visual snapshot testing for Playwright, built for e-commerce.",
5
5
  "keywords": [
6
6
  "playwright",
package/src/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export {
2
2
  waitForCompleteLoad,
3
+ gotoWithRetry,
3
4
  highlightedClick,
4
5
  click,
5
6
  hover,
@@ -23,4 +24,10 @@ export {
23
24
 
24
25
  export { areWireframesStable } from './wireframeStability.js';
25
26
 
26
- export { expectWireframe, getRGBHistogramFromBuffer } from './wireframeUtils.js';
27
+ export { expectWireframe, getRGBHistogramFromBuffer, getDeviceScaleFactor } from './wireframeUtils.js';
28
+
29
+ export {
30
+ setWireframeOutputRoot,
31
+ getWireframeOutputRoot,
32
+ resolveWireframeOutputDir
33
+ } from './wireframeOutput.js';
package/src/testUtils.js CHANGED
@@ -2,6 +2,18 @@ import { test } from '@playwright/test';
2
2
 
3
3
  let closePopups = async function(page) {}
4
4
 
5
+ async function gotoWithRetry(page, url) {
6
+ try {
7
+ logTimestamp('Loading ' + url);
8
+ await page.goto(url, { timeout: 30_000, waitUntil: "domcontentloaded" });
9
+ } catch (firstError) {
10
+ logTimestamp('Retry loading ' + url);
11
+ await page.goto(url, { timeout: 30_000, waitUntil: "domcontentloaded" });
12
+ }
13
+ await waitForCompleteLoad(page)
14
+ }
15
+
16
+
5
17
  function setClosePopups(f) {
6
18
  closePopups = f;
7
19
  }
@@ -188,6 +200,7 @@ async function selectOption(page, field, value) {
188
200
 
189
201
  export {
190
202
  waitForCompleteLoad,
203
+ gotoWithRetry,
191
204
  highlightedClick,
192
205
  click,
193
206
  hover,
@@ -0,0 +1,29 @@
1
+ import path from 'path';
2
+
3
+ let outputRoot = process.cwd();
4
+
5
+ function setWireframeOutputRoot(rootPath) {
6
+ outputRoot = rootPath || process.cwd();
7
+ }
8
+
9
+ function getWireframeOutputRoot() {
10
+ return outputRoot;
11
+ }
12
+
13
+ function resolveWireframeOutputDir(outputDir) {
14
+ if (!outputDir || typeof outputDir !== 'string') {
15
+ throw new Error('resolveWireframeOutputDir requires a non-empty string outputDir.');
16
+ }
17
+
18
+ if (path.isAbsolute(outputDir)) {
19
+ return outputDir;
20
+ }
21
+
22
+ return path.join(outputRoot, outputDir);
23
+ }
24
+
25
+ export {
26
+ setWireframeOutputRoot,
27
+ getWireframeOutputRoot,
28
+ resolveWireframeOutputDir
29
+ };
@@ -3,6 +3,7 @@ import path from 'path';
3
3
  import { logTimestamp } from './testUtils.js';
4
4
  import { loadImage, createCanvas } from 'canvas';
5
5
  import { areWireframesStable } from './wireframeStability.js';
6
+ import { resolveWireframeOutputDir } from './wireframeOutput.js';
6
7
 
7
8
  const FLEXYSNAP_ELEMENT_ID_ATTRIBUTE = 'data-flexysnap-id';
8
9
 
@@ -38,6 +39,58 @@ function delay(milliseconds) {
38
39
  return new Promise(resolve => setTimeout(resolve, milliseconds));
39
40
  }
40
41
 
42
+ async function getDeviceScaleFactor(page) {
43
+ const rawScaleFactor = await page.evaluate(() => window.devicePixelRatio);
44
+
45
+ if (typeof rawScaleFactor !== 'number' || !Number.isFinite(rawScaleFactor) || rawScaleFactor <= 0) {
46
+ return 1;
47
+ }
48
+
49
+ return rawScaleFactor;
50
+ }
51
+
52
+ function scaleBoundingRect(boundingRect, scaleFactor) {
53
+ if (!boundingRect) {
54
+ return boundingRect;
55
+ }
56
+
57
+ return {
58
+ top: Math.round(boundingRect.top * scaleFactor),
59
+ left: Math.round(boundingRect.left * scaleFactor),
60
+ bottom: Math.round(boundingRect.bottom * scaleFactor),
61
+ right: Math.round(boundingRect.right * scaleFactor)
62
+ };
63
+ }
64
+
65
+ function scalePoint(point, scaleFactor) {
66
+ if (!point) {
67
+ return point;
68
+ }
69
+
70
+ return {
71
+ x: Math.round(point.x * scaleFactor),
72
+ y: Math.round(point.y * scaleFactor)
73
+ };
74
+ }
75
+
76
+ function scaleWireframeData(wireframeData, scaleFactor) {
77
+ if (scaleFactor === 1) {
78
+ return wireframeData;
79
+ }
80
+
81
+ for (const elementGroup of wireframeData) {
82
+ for (const element of elementGroup.elements || []) {
83
+ element.boundingRect = scaleBoundingRect(element.boundingRect, scaleFactor);
84
+
85
+ for (const textEntry of element.texts || []) {
86
+ textEntry.boundingRect = scaleBoundingRect(textEntry.boundingRect, scaleFactor);
87
+ }
88
+ }
89
+ }
90
+
91
+ return wireframeData;
92
+ }
93
+
41
94
  async function extractWireframe(page, elementGroups) {
42
95
  const wireframeData = await page.evaluate(async ({elementGroups, elementIdAttribute}) => {
43
96
 
@@ -270,41 +323,50 @@ async function extractStableWireframe(page, elementGroups, retryDelay, maxRetryC
270
323
  return {wireframeData: currentWireframeData, scrollPosition: currentScrollPosition};
271
324
  }
272
325
 
273
- async function expectWireframe(page, elementGroups, configName, outputFile, outputName, retryDelay = 1000, maxRetryCount = 10) {
274
- logTimestamp(`Starting wireframe capture for: ${outputFile}`);
275
- const { wireframeData, scrollPosition } = await extractStableWireframe(page, elementGroups, retryDelay, maxRetryCount);
326
+ async function expectWireframe(page, elementGroups, outputDir, outputFile, outputName, options = {}) {
327
+ const { retryDelay = 1000, maxRetryCount = 10, metadata = {}, deviceScaleFactor } = options;
276
328
 
277
- const userType = process.env.USER_TYPE;
278
- const deviceType = process.env.DEVICE_TYPE;
279
- const testType = process.env.TEST_TYPE;
329
+ logTimestamp(`Starting wireframe capture for: ${outputFile}`);
330
+ const { wireframeData, scrollPosition } = await extractStableWireframe(page, elementGroups, retryDelay, maxRetryCount);
280
331
 
281
332
  const screenshotScrollPosition = await page.evaluate(() => ({
282
333
  x: window.scrollX,
283
334
  y: window.scrollY
284
335
  }));
285
336
 
337
+ const resolvedDeviceScaleFactor = deviceScaleFactor !== undefined
338
+ ? deviceScaleFactor
339
+ : await getDeviceScaleFactor(page);
340
+
341
+ logTimestamp(`Applying device scale factor: ${resolvedDeviceScaleFactor}`);
342
+
343
+ const scaledWireframeData = scaleWireframeData(wireframeData, resolvedDeviceScaleFactor);
344
+ const scaledScrollPosition = scalePoint(scrollPosition, resolvedDeviceScaleFactor);
345
+ const scaledScreenshotScrollPosition = scalePoint(screenshotScrollPosition, resolvedDeviceScaleFactor);
346
+
286
347
  const wireframeOutput = {
287
348
  name: outputName,
288
349
  timestamp: new Date().toISOString(),
289
- deviceType: process.env.DEVICE_TYPE,
290
- userType: process.env.USER_TYPE,
291
- scrollPosition,
292
- screenshotScrollPosition,
293
- elementGroups: wireframeData
350
+ scrollPosition: scaledScrollPosition,
351
+ screenshotScrollPosition: scaledScreenshotScrollPosition,
352
+ elementGroups: scaledWireframeData,
353
+ deviceScaleFactor: resolvedDeviceScaleFactor,
354
+ ...metadata
294
355
  };
295
356
 
357
+ const resolvedDir = resolveWireframeOutputDir(outputDir);
358
+ fs.mkdirSync(resolvedDir, {recursive: true});
359
+
296
360
  const fileName = `${outputFile}.json`;
297
- const filePath = path.join(process.cwd(), 'wireframes', 'test', testType, configName, deviceType, userType, fileName);
298
- const fileDir = path.dirname(filePath);
299
- fs.mkdirSync(fileDir, {recursive: true});
361
+ const filePath = path.join(resolvedDir, fileName);
300
362
  fs.writeFileSync(filePath, JSON.stringify(wireframeOutput, null, 2));
301
363
  logTimestamp(`Wireframe captured and saved to: ${fileName}`);
302
364
 
303
- const screenshotPath = path.join(fileDir, `${outputFile}.png`);
365
+ const screenshotPath = path.join(resolvedDir, `${outputFile}.png`);
304
366
  await page.screenshot({ path: screenshotPath });
305
367
  logTimestamp(`Wireframe screenshot saved to: ${screenshotPath}`);
306
368
 
307
- return {wireframeOutput, screenshotPath};
369
+ return {wireframeOutput, screenshotPath, outputDir: resolvedDir};
308
370
  }
309
371
 
310
- export { expectWireframe, getRGBHistogramFromBuffer };
372
+ export { expectWireframe, getRGBHistogramFromBuffer, getDeviceScaleFactor };