flexysnap 0.1.3-alpha.1 → 0.1.3-alpha.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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flexysnap",
3
- "version": "0.1.3-alpha.1",
3
+ "version": "0.1.3-alpha.2",
4
4
  "description": "Flexible visual snapshot testing for Playwright, built for e-commerce.",
5
5
  "keywords": [
6
6
  "playwright",
@@ -16,6 +16,16 @@ function applyGrayscale(ctx, width, height) {
16
16
  ctx.putImageData(imageData, 0, 0);
17
17
  }
18
18
 
19
+ function getScrollOffset(wireframe) {
20
+ const extractionScrollPosition = wireframe.scrollPosition || { x: 0, y: 0 };
21
+ const screenshotScrollPosition = wireframe.screenshotScrollPosition || extractionScrollPosition;
22
+
23
+ return {
24
+ offsetX: extractionScrollPosition.x - screenshotScrollPosition.x,
25
+ offsetY: extractionScrollPosition.y - screenshotScrollPosition.y
26
+ };
27
+ }
28
+
19
29
  async function annotateWireframeFile(wireframeFile, screenshotFile) {
20
30
  if (!fs.existsSync(wireframeFile)) {
21
31
  console.error(`Wireframe file not found: ${wireframeFile}`);
@@ -40,6 +50,12 @@ async function annotateWireframeFile(wireframeFile, screenshotFile) {
40
50
  ctx.drawImage(screenshot, 0, 0);
41
51
  applyGrayscale(ctx, width, height);
42
52
 
53
+ const { offsetX, offsetY } = getScrollOffset(wireframe);
54
+
55
+ if (offsetX !== 0 || offsetY !== 0) {
56
+ console.log(`Compensating for scroll drift between extraction and screenshot: offsetX=${offsetX}, offsetY=${offsetY}`);
57
+ }
58
+
43
59
  const colors = {
44
60
  box: { fill: 'rgba(220, 160, 40, 0.4)', stroke: '#E6A500' },
45
61
  image: { fill: 'rgba(70, 180, 120, 0.4)', stroke: '#2EBC6F' },
@@ -52,16 +68,23 @@ async function annotateWireframeFile(wireframeFile, screenshotFile) {
52
68
  }
53
69
 
54
70
  function drawBoundingBox(rect, colorKey, dashed = false) {
55
- const boxWidth = rect.right - rect.left;
56
- const boxHeight = rect.bottom - rect.top;
71
+ const adjustedRect = {
72
+ left: rect.left + offsetX,
73
+ right: rect.right + offsetX,
74
+ top: rect.top + offsetY,
75
+ bottom: rect.bottom + offsetY
76
+ };
77
+
78
+ const boxWidth = adjustedRect.right - adjustedRect.left;
79
+ const boxHeight = adjustedRect.bottom - adjustedRect.top;
57
80
  const color = colors[colorKey];
58
81
 
59
82
  ctx.fillStyle = color.fill;
60
- ctx.fillRect(rect.left, rect.top, boxWidth, boxHeight);
83
+ ctx.fillRect(adjustedRect.left, adjustedRect.top, boxWidth, boxHeight);
61
84
  ctx.strokeStyle = color.stroke;
62
85
  ctx.lineWidth = 2;
63
86
  ctx.setLineDash(dashed ? [6, 4] : []);
64
- ctx.strokeRect(rect.left, rect.top, boxWidth, boxHeight);
87
+ ctx.strokeRect(adjustedRect.left, adjustedRect.top, boxWidth, boxHeight);
65
88
  ctx.setLineDash([]);
66
89
  }
67
90
 
@@ -4,6 +4,8 @@ import { logTimestamp } from './testUtils.js';
4
4
  import { loadImage, createCanvas } from 'canvas';
5
5
  import { areWireframesStable } from './wireframeStability.js';
6
6
 
7
+ const FLEXYSNAP_ELEMENT_ID_ATTRIBUTE = 'data-flexysnap-id';
8
+
7
9
  async function getRGBHistogramFromBuffer(buffer) {
8
10
  const image = await loadImage(buffer);
9
11
  const canvas = createCanvas(image.width, image.height);
@@ -37,7 +39,7 @@ function delay(milliseconds) {
37
39
  }
38
40
 
39
41
  async function extractWireframe(page, elementGroups) {
40
- const wireframeData = await page.evaluate(async ({elementGroups}) => {
42
+ const wireframeData = await page.evaluate(async ({elementGroups, elementIdAttribute}) => {
41
43
 
42
44
  function createBoundingRect(element) {
43
45
  const rect = element.getBoundingClientRect();
@@ -80,7 +82,8 @@ async function extractWireframe(page, elementGroups) {
80
82
  }
81
83
 
82
84
 
83
- for (const elementGroup of elementGroups) {
85
+ for (let groupIndex = 0; groupIndex < elementGroups.length; groupIndex++) {
86
+ const elementGroup = elementGroups[groupIndex];
84
87
  elementGroup.strictPosition = elementGroup.strictPosition !== false;
85
88
  elementGroup.elements = [];
86
89
 
@@ -159,18 +162,26 @@ async function extractWireframe(page, elementGroups) {
159
162
  }
160
163
  }
161
164
 
162
- elementGroup.elements.push({
165
+ const elementData = {
163
166
  index: index,
164
167
  boundingRect: createBoundingRect(element),
165
168
  texts: texts,
166
169
  type: elementGroup.type
167
- });
170
+ };
171
+
172
+ if (elementGroup.type === 'image') {
173
+ const elementId = `fsnap-g${groupIndex}-e${index}`;
174
+ element.setAttribute(elementIdAttribute, elementId);
175
+ elementData.elementId = elementId;
176
+ }
177
+
178
+ elementGroup.elements.push(elementData);
168
179
  index += 1;
169
180
  }
170
181
  }
171
182
 
172
183
  return elementGroups;
173
- }, {elementGroups});
184
+ }, {elementGroups, elementIdAttribute: FLEXYSNAP_ELEMENT_ID_ATTRIBUTE});
174
185
 
175
186
  const scrollPosition = await page.evaluate(() => ({
176
187
  x: window.scrollX,
@@ -179,8 +190,8 @@ async function extractWireframe(page, elementGroups) {
179
190
 
180
191
  for (const elementGroup of wireframeData) {
181
192
  for (const element of elementGroup.elements) {
182
- if (element.type === 'image') {
183
- const locator = page.locator(elementGroup.selector).nth(element.index);
193
+ if (element.type === 'image' && element.elementId) {
194
+ const locator = page.locator(`[${FLEXYSNAP_ELEMENT_ID_ATTRIBUTE}="${element.elementId}"]`);
184
195
  await locator.waitFor({state: 'visible'});
185
196
 
186
197
  try {
@@ -199,14 +210,28 @@ async function extractWireframe(page, elementGroups) {
199
210
  } catch {
200
211
  element.type = 'box'
201
212
  }
213
+
214
+ delete element.elementId;
202
215
  }
203
216
  }
204
217
  }
205
218
 
206
- await page.evaluate(({scrollPosition}) => {
207
- window.scrollTo(scrollPosition.x, scrollPosition.y);
208
- }, {scrollPosition});
209
- return wireframeData;
219
+ await page.evaluate(({scrollPosition, elementIdAttribute}) => {
220
+ for (const el of document.querySelectorAll(`[${elementIdAttribute}]`)) {
221
+ el.removeAttribute(elementIdAttribute);
222
+ }
223
+ window.scrollTo({top: scrollPosition.y, left: scrollPosition.x, behavior: 'instant'});
224
+ }, {scrollPosition, elementIdAttribute: FLEXYSNAP_ELEMENT_ID_ATTRIBUTE});
225
+
226
+ try {
227
+ await page.waitForFunction((expectedScrollPosition) => {
228
+ return window.scrollX === expectedScrollPosition.x && window.scrollY === expectedScrollPosition.y;
229
+ }, scrollPosition, {timeout: 2000});
230
+ } catch {
231
+ logTimestamp('Scroll position did not settle back to the extraction position.');
232
+ }
233
+
234
+ return {wireframeData, scrollPosition};
210
235
  }
211
236
 
212
237
  function cloneElementGroups(elementGroups) {
@@ -216,17 +241,20 @@ function cloneElementGroups(elementGroups) {
216
241
  async function extractStableWireframe(page, elementGroups, retryDelay, maxRetryCount) {
217
242
  let previousWireframeData = null;
218
243
  let currentWireframeData = null;
244
+ let currentScrollPosition = null;
219
245
 
220
246
  for (let attempt = 0; attempt < maxRetryCount; attempt++) {
221
247
  const extractionStartTime = Date.now();
222
- currentWireframeData = await extractWireframe(page, cloneElementGroups(elementGroups));
248
+ const extractionResult = await extractWireframe(page, cloneElementGroups(elementGroups));
249
+ currentWireframeData = extractionResult.wireframeData;
250
+ currentScrollPosition = extractionResult.scrollPosition;
223
251
  const extractionDuration = Date.now() - extractionStartTime;
224
252
  logTimestamp(`Wireframe candidate captured after ${extractionDuration/1000} seconds.`)
225
253
 
226
254
  if (previousWireframeData !== null &&
227
255
  areWireframesStable(previousWireframeData, currentWireframeData)) {
228
256
  logTimestamp(`Wireframe stabilized after ${attempt + 1} extraction(s)`);
229
- return currentWireframeData;
257
+ return {wireframeData: currentWireframeData, scrollPosition: currentScrollPosition};
230
258
  }
231
259
 
232
260
  previousWireframeData = currentWireframeData;
@@ -239,22 +267,29 @@ async function extractStableWireframe(page, elementGroups, retryDelay, maxRetryC
239
267
  }
240
268
 
241
269
  logTimestamp(`Wireframe did not stabilize within ${maxRetryCount} extraction(s)`);
242
- return currentWireframeData;
270
+ return {wireframeData: currentWireframeData, scrollPosition: currentScrollPosition};
243
271
  }
244
272
 
245
273
  async function expectWireframe(page, elementGroups, configName, outputFile, outputName, retryDelay = 1000, maxRetryCount = 10) {
246
274
  logTimestamp(`Starting wireframe capture for: ${outputFile}`);
247
- const wireframeData = await extractStableWireframe(page, elementGroups, retryDelay, maxRetryCount);
275
+ const { wireframeData, scrollPosition } = await extractStableWireframe(page, elementGroups, retryDelay, maxRetryCount);
248
276
 
249
277
  const userType = process.env.USER_TYPE;
250
278
  const deviceType = process.env.DEVICE_TYPE;
251
279
  const testType = process.env.TEST_TYPE;
252
280
 
281
+ const screenshotScrollPosition = await page.evaluate(() => ({
282
+ x: window.scrollX,
283
+ y: window.scrollY
284
+ }));
285
+
253
286
  const wireframeOutput = {
254
287
  name: outputName,
255
288
  timestamp: new Date().toISOString(),
256
289
  deviceType: process.env.DEVICE_TYPE,
257
290
  userType: process.env.USER_TYPE,
291
+ scrollPosition,
292
+ screenshotScrollPosition,
258
293
  elementGroups: wireframeData
259
294
  };
260
295