ftmocks-utils 1.5.3 → 1.5.5

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": "ftmocks-utils",
3
- "version": "1.5.3",
3
+ "version": "1.5.5",
4
4
  "description": "Util functions for FtMocks",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -20,7 +20,7 @@ const createDiffImage = async (img1, img2, diffPath) => {
20
20
  threshold: 0.1, // sensitivity
21
21
  diffColor: [255, 0, 0], // highlight color (red)
22
22
  diffMask: false,
23
- }
23
+ },
24
24
  );
25
25
 
26
26
  fs.writeFileSync(diffPath, PNG.sync.write(diff));
@@ -58,7 +58,7 @@ const matchAndReplaceScreenshot = async (page, event, screenshotsDir) => {
58
58
  await createDiffImage(
59
59
  img1,
60
60
  img2,
61
- path.join(screenshotsDir, `diff_${event.id}.png`)
61
+ path.join(screenshotsDir, `diff_${event.id}.png`),
62
62
  );
63
63
  return {
64
64
  replaced: true,
@@ -170,6 +170,8 @@ const runEvent = async ({
170
170
  screenshots = false,
171
171
  screenshotsDir = null,
172
172
  healSelectors = false,
173
+ eventsFile = null,
174
+ allEvents = null,
173
175
  }) => {
174
176
  try {
175
177
  const beforeEvent = async () => {
@@ -178,7 +180,7 @@ const runEvent = async ({
178
180
  const res = await matchAndReplaceScreenshot(
179
181
  page,
180
182
  event,
181
- screenshotsDir
183
+ screenshotsDir,
182
184
  );
183
185
  if (res.replaced) {
184
186
  const locator = await getLocator(page, event);
@@ -200,7 +202,7 @@ const runEvent = async ({
200
202
  page,
201
203
  event,
202
204
  event.target,
203
- position
205
+ position,
204
206
  );
205
207
  event.target = healedTarget.value;
206
208
  const selectors = [];
@@ -210,7 +212,7 @@ const runEvent = async ({
210
212
  page,
211
213
  event,
212
214
  event.selectors[i].value,
213
- position
215
+ position,
214
216
  );
215
217
  event.selectors[i].value = healedSelector.value;
216
218
  if (healedSelector.count !== 1) {
@@ -273,6 +275,10 @@ const runEvent = async ({
273
275
  default:
274
276
  return "Unsupported event type";
275
277
  }
278
+ if (eventsFile && allEvents) {
279
+ allEvents.find((e) => e.id === event.id).executed = true;
280
+ fs.writeFileSync(eventsFile, JSON.stringify(allEvents, null, 2));
281
+ }
276
282
  } catch (error) {
277
283
  console.error("Error running event", {
278
284
  error: error.message,
@@ -321,6 +327,8 @@ const runEvents = async ({
321
327
  screenshots = false,
322
328
  screenshotsDir = null,
323
329
  healSelectors = false,
330
+ eventsFile = null,
331
+ allEvents = null,
324
332
  }) => {
325
333
  for (const event of events) {
326
334
  await runEvent({
@@ -330,6 +338,8 @@ const runEvents = async ({
330
338
  screenshots,
331
339
  screenshotsDir,
332
340
  healSelectors,
341
+ eventsFile,
342
+ allEvents,
333
343
  });
334
344
  }
335
345
  };
@@ -338,14 +348,19 @@ const runEventsForTest = async (page, ftmocksConifg, testName) => {
338
348
  const eventsFile = path.join(
339
349
  getMockDir(ftmocksConifg),
340
350
  `test_${nameToFolder(testName)}`,
341
- `_events.json`
351
+ `_events.json`,
342
352
  );
343
353
  const events = JSON.parse(fs.readFileSync(eventsFile, "utf8"));
354
+ events.forEach((event) => {
355
+ event.executed = false;
356
+ });
344
357
  await runEvents({
345
358
  page,
346
359
  events,
347
360
  delay: ftmocksConifg.delay || 1000,
348
361
  screenshots: false,
362
+ eventsFile,
363
+ allEvents: events,
349
364
  });
350
365
  };
351
366
 
@@ -354,19 +369,32 @@ const runEventsInPresentationMode = async (page, ftmocksConifg, testName) => {
354
369
  const eventsFile = path.join(
355
370
  getMockDir(ftmocksConifg),
356
371
  `test_${nameToFolder(testName)}`,
357
- `_events.json`
372
+ `_events.json`,
358
373
  );
359
374
  const events = JSON.parse(fs.readFileSync(eventsFile, "utf8"));
375
+ events.forEach((event) => {
376
+ event.executed = false;
377
+ });
360
378
 
361
379
  // Expose Node function
362
380
  await page.exposeFunction("nextEvent", async () => {
363
381
  if (currentEventIndex === events.length) {
364
382
  return false;
365
383
  }
366
- let result = await runEvent({ page, event: events[currentEventIndex] });
384
+ let result = await runEvent({
385
+ page,
386
+ event: events[currentEventIndex],
387
+ eventsFile,
388
+ allEvents: events,
389
+ });
367
390
  while (result === "Unsupported event type") {
368
391
  currentEventIndex = currentEventIndex + 1;
369
- result = await runEvent({ page, event: events[currentEventIndex] });
392
+ result = await runEvent({
393
+ page,
394
+ event: events[currentEventIndex],
395
+ eventsFile,
396
+ allEvents: events,
397
+ });
370
398
  }
371
399
  currentEventIndex = currentEventIndex + 1;
372
400
  return true;
@@ -401,7 +429,7 @@ const runEventsInPresentationMode = async (page, ftmocksConifg, testName) => {
401
429
  });
402
430
  });
403
431
 
404
- await runEvent({ page, event: events[0] });
432
+ await runEvent({ page, event: events[0], eventsFile, allEvents: events });
405
433
  };
406
434
 
407
435
  const runEventsInTrainingMode = async (page, ftmocksConifg, testName) => {
@@ -409,15 +437,17 @@ const runEventsInTrainingMode = async (page, ftmocksConifg, testName) => {
409
437
  const eventsFile = path.join(
410
438
  getMockDir(ftmocksConifg),
411
439
  `test_${nameToFolder(testName)}`,
412
- `_events.json`
440
+ `_events.json`,
413
441
  );
414
442
  const events = JSON.parse(fs.readFileSync(eventsFile, "utf8"));
415
-
443
+ events.forEach((event) => {
444
+ event.executed = false;
445
+ });
416
446
  // Expose Node function
417
447
  await page.exposeFunction("getNextEvent", async () => {
418
448
  let result = false;
419
449
  let nonExecutedEvents = events.filter(
420
- (event) => !executedEvents.includes(event?.id)
450
+ (event) => !executedEvents.includes(event?.id),
421
451
  );
422
452
  let currentEventIndex = -1;
423
453
  while (!result) {
@@ -431,11 +461,11 @@ const runEventsInTrainingMode = async (page, ftmocksConifg, testName) => {
431
461
  if (nonExecutedEvents[currentEventIndex]) {
432
462
  console.log(
433
463
  "➡ Getting locator for event",
434
- nonExecutedEvents[currentEventIndex]
464
+ nonExecutedEvents[currentEventIndex],
435
465
  );
436
466
  const selector = await getLocator(
437
467
  page,
438
- nonExecutedEvents[currentEventIndex]
468
+ nonExecutedEvents[currentEventIndex],
439
469
  );
440
470
  const position = await getSelectorPosition(page, selector);
441
471
  const element = await page.locator(selector).elementHandle();
@@ -641,16 +671,19 @@ const runEventsInTrainingMode = async (page, ftmocksConifg, testName) => {
641
671
  });
642
672
  });
643
673
 
644
- await runEvent({ page, event: events[0] });
674
+ await runEvent({ page, event: events[0], eventsFile, allEvents: events });
645
675
  };
646
676
 
647
677
  const runEventsForScreenshots = async (page, ftmocksConifg, testName) => {
648
678
  const eventsFile = path.join(
649
679
  getMockDir(ftmocksConifg),
650
680
  `test_${nameToFolder(testName)}`,
651
- `_events.json`
681
+ `_events.json`,
652
682
  );
653
683
  const events = JSON.parse(fs.readFileSync(eventsFile, "utf8"));
684
+ events.forEach((event) => {
685
+ event.executed = false;
686
+ });
654
687
  await runEvents({
655
688
  page,
656
689
  events,
@@ -659,8 +692,10 @@ const runEventsForScreenshots = async (page, ftmocksConifg, testName) => {
659
692
  screenshotsDir: path.join(
660
693
  getMockDir(ftmocksConifg),
661
694
  `test_${nameToFolder(testName)}`,
662
- `screenshots`
695
+ `screenshots`,
663
696
  ),
697
+ eventsFile,
698
+ allEvents: events,
664
699
  });
665
700
  fs.writeFileSync(eventsFile, JSON.stringify(events, null, 2));
666
701
  await page.waitForTimeout(1000);
@@ -671,14 +706,19 @@ const runEventsForHealingSelectors = async (page, ftmocksConifg, testName) => {
671
706
  const eventsFile = path.join(
672
707
  getMockDir(ftmocksConifg),
673
708
  `test_${nameToFolder(testName)}`,
674
- `_events.json`
709
+ `_events.json`,
675
710
  );
676
711
  const events = JSON.parse(fs.readFileSync(eventsFile, "utf8"));
712
+ events.forEach((event) => {
713
+ event.executed = false;
714
+ });
677
715
  await runEvents({
678
716
  page,
679
717
  events,
680
718
  delay: ftmocksConifg.delay || 1000,
681
719
  healSelectors: true,
720
+ eventsFile,
721
+ allEvents: events,
682
722
  });
683
723
  fs.writeFileSync(eventsFile, JSON.stringify(events, null, 2));
684
724
  await page.waitForTimeout(1000);
@@ -6,14 +6,15 @@ const injectEventRecordingScript = async (
6
6
  page,
7
7
  url,
8
8
  ftmocksConifg,
9
- testName
9
+ testName,
10
+ continueRecordEvents = false,
10
11
  ) => {
11
12
  console.log("calling injectEventRecordingScript");
12
13
  try {
13
14
  const eventsFile = path.join(
14
15
  getMockDir(ftmocksConifg),
15
16
  `test_${nameToFolder(testName)}`,
16
- `_events.json`
17
+ `_events.json`,
17
18
  );
18
19
  if (!fs.existsSync(eventsFile)) {
19
20
  // Ensure the directory exists before writing the eventsFile
@@ -32,7 +33,7 @@ const injectEventRecordingScript = async (
32
33
  const screenshotsDir = path.join(
33
34
  getMockDir(ftmocksConifg),
34
35
  `test_${nameToFolder(testName)}`,
35
- "screenshots"
36
+ "screenshots",
36
37
  );
37
38
  if (!fs.existsSync(screenshotsDir)) {
38
39
  fs.mkdirSync(screenshotsDir, { recursive: true });
@@ -41,7 +42,7 @@ const injectEventRecordingScript = async (
41
42
  getMockDir(ftmocksConifg),
42
43
  `test_${nameToFolder(testName)}`,
43
44
  "screenshots",
44
- `screenshot_${imgOptions.name}.png`
45
+ `screenshot_${imgOptions.name}.png`,
45
46
  );
46
47
  fs.writeFileSync(screenshotFile, screenshot);
47
48
  return screenshotFile;
@@ -72,10 +73,23 @@ const injectEventRecordingScript = async (
72
73
  fs.writeFileSync(eventsFile, JSON.stringify(events, null, 2));
73
74
  });
74
75
 
76
+ let existingEvents = [];
77
+ // Read events from events file, if it exists
78
+ if (continueRecordEvents && fs.existsSync(eventsFile)) {
79
+ try {
80
+ const data = fs.readFileSync(eventsFile, "utf8");
81
+ existingEvents = JSON.parse(data);
82
+ } catch (err) {
83
+ console.error("Failed to read events file:", err);
84
+ existingEvents = [];
85
+ }
86
+ }
87
+
75
88
  fs.writeFileSync(
76
89
  eventsFile,
77
90
  JSON.stringify(
78
91
  [
92
+ ...existingEvents,
79
93
  {
80
94
  id: crypto.randomUUID(),
81
95
  type: "url",
@@ -85,8 +99,8 @@ const injectEventRecordingScript = async (
85
99
  },
86
100
  ],
87
101
  null,
88
- 2
89
- )
102
+ 2,
103
+ ),
90
104
  );
91
105
  await page.addInitScript(() => {
92
106
  console.log("calling addInitScript");
@@ -149,7 +163,7 @@ const injectEventRecordingScript = async (
149
163
  doc,
150
164
  null,
151
165
  XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
152
- null
166
+ null,
153
167
  );
154
168
  const elements = [];
155
169
  for (let i = 0; i < snapshot.snapshotLength; i++) {
@@ -224,7 +238,7 @@ const injectEventRecordingScript = async (
224
238
  document,
225
239
  null,
226
240
  XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
227
- null
241
+ null,
228
242
  );
229
243
  return elements.snapshotLength === 1;
230
244
  } catch (error) {
@@ -238,7 +252,7 @@ const injectEventRecordingScript = async (
238
252
  const getUniqueXpath = (xpath, mainElement) => {
239
253
  const prevElements = filterXpathElementsFromHtml(
240
254
  prevEventSnapshot,
241
- xpath
255
+ xpath,
242
256
  );
243
257
  if (prevElements.snapshotLength > 1 && mainElement) {
244
258
  return `(${xpath})[${
@@ -251,7 +265,7 @@ const injectEventRecordingScript = async (
251
265
  const getUniqueElementSelectorNth = (selector, mainElement) => {
252
266
  const prevElements = filterElementsFromHtml(
253
267
  prevEventSnapshot,
254
- selector
268
+ selector,
255
269
  );
256
270
  if (prevElements.length > 1) {
257
271
  return getElementsByRank(prevElements, mainElement)[0].index + 1;
@@ -264,11 +278,11 @@ const injectEventRecordingScript = async (
264
278
  if (selector.value.startsWith("/")) {
265
279
  const prevElements = filterXpathElementsFromHtml(
266
280
  prevEventSnapshot,
267
- selector.value
281
+ selector.value,
268
282
  );
269
283
  const nextElements = filterXpathElementsFromHtml(
270
284
  currentEventSnapshot,
271
- selector.value
285
+ selector.value,
272
286
  );
273
287
  return {
274
288
  selector: selector.value,
@@ -278,11 +292,11 @@ const injectEventRecordingScript = async (
278
292
  } else {
279
293
  const prevElements = filterElementsFromHtml(
280
294
  prevEventSnapshot,
281
- selector.value
295
+ selector.value,
282
296
  );
283
297
  const nextElements = filterElementsFromHtml(
284
298
  currentEventSnapshot,
285
- selector.value
299
+ selector.value,
286
300
  );
287
301
  return {
288
302
  selector: selector.value,
@@ -313,13 +327,13 @@ const injectEventRecordingScript = async (
313
327
  selectors.push({
314
328
  type: "locator",
315
329
  value: `${tagName}[data-testid='${element.getAttribute(
316
- "data-testid"
330
+ "data-testid",
317
331
  )}']`,
318
332
  nth: getUniqueElementSelectorNth(
319
333
  `${tagName}[data-testid='${element.getAttribute(
320
- "data-testid"
334
+ "data-testid",
321
335
  )}']`,
322
- element
336
+ element,
323
337
  ),
324
338
  });
325
339
  }
@@ -329,7 +343,7 @@ const injectEventRecordingScript = async (
329
343
  value: `${tagName}[data-id='${element.getAttribute("data-id")}']`,
330
344
  nth: getUniqueElementSelectorNth(
331
345
  `${tagName}[data-id='${element.getAttribute("data-id")}']`,
332
- element
346
+ element,
333
347
  ),
334
348
  });
335
349
  }
@@ -337,13 +351,13 @@ const injectEventRecordingScript = async (
337
351
  selectors.push({
338
352
  type: "locator",
339
353
  value: `${tagName}[data-action='${element.getAttribute(
340
- "data-action"
354
+ "data-action",
341
355
  )}']`,
342
356
  nth: getUniqueElementSelectorNth(
343
357
  `${tagName}[data-action='${element.getAttribute(
344
- "data-action"
358
+ "data-action",
345
359
  )}']`,
346
- element
360
+ element,
347
361
  ),
348
362
  });
349
363
  }
@@ -353,7 +367,7 @@ const injectEventRecordingScript = async (
353
367
  value: `${tagName}[data-cy='${element.getAttribute("data-cy")}']`,
354
368
  nth: getUniqueElementSelectorNth(
355
369
  `${tagName}[data-cy='${element.getAttribute("data-cy")}']`,
356
- element
370
+ element,
357
371
  ),
358
372
  });
359
373
  }
@@ -367,7 +381,7 @@ const injectEventRecordingScript = async (
367
381
  value: `${tagName}[name='${element.name}']`,
368
382
  nth: getUniqueElementSelectorNth(
369
383
  `${tagName}[name='${element.name}']`,
370
- element
384
+ element,
371
385
  ),
372
386
  });
373
387
  } else if (
@@ -380,7 +394,7 @@ const injectEventRecordingScript = async (
380
394
  value: `${tagName}[name='${element.name}'][value='${element.value}']`,
381
395
  nth: getUniqueElementSelectorNth(
382
396
  `${tagName}[name='${element.name}'][value='${element.value}']`,
383
- element
397
+ element,
384
398
  ),
385
399
  });
386
400
  }
@@ -390,7 +404,7 @@ const injectEventRecordingScript = async (
390
404
  value: `${tagName}[aria-label='${element.ariaLabel}']`,
391
405
  nth: getUniqueElementSelectorNth(
392
406
  `${tagName}[aria-label='${element.ariaLabel}']`,
393
- element
407
+ element,
394
408
  ),
395
409
  });
396
410
  }
@@ -400,7 +414,7 @@ const injectEventRecordingScript = async (
400
414
  value: `${tagName}[role='${element.role}'][name='${element.name}']`,
401
415
  nth: getUniqueElementSelectorNth(
402
416
  `${tagName}[role='${element.role}'][name='${element.name}']`,
403
- element
417
+ element,
404
418
  ),
405
419
  });
406
420
  }
@@ -410,7 +424,7 @@ const injectEventRecordingScript = async (
410
424
  value: `${tagName}[src='${element.getAttribute("src")}']`,
411
425
  nth: getUniqueElementSelectorNth(
412
426
  `${tagName}[src='${element.getAttribute("src")}']`,
413
- element
427
+ element,
414
428
  ),
415
429
  });
416
430
  }
@@ -420,7 +434,7 @@ const injectEventRecordingScript = async (
420
434
  value: `${tagName}[href='${element.getAttribute("href")}']`,
421
435
  nth: getUniqueElementSelectorNth(
422
436
  `${tagName}[href='${element.getAttribute("href")}']`,
423
- element
437
+ element,
424
438
  ),
425
439
  });
426
440
  }
@@ -433,7 +447,7 @@ const injectEventRecordingScript = async (
433
447
  type: "locator",
434
448
  value: getUniqueXpath(
435
449
  `//${tagName}[@role='${element.role}' and normalize-space(.) = '${escapedText}']`,
436
- element
450
+ element,
437
451
  ),
438
452
  });
439
453
  }
@@ -448,7 +462,7 @@ const injectEventRecordingScript = async (
448
462
  .replace(/'/g, "\\'")
449
463
  .replace(/\s+/g, " ")
450
464
  .trim()}']`,
451
- event.target
465
+ event.target,
452
466
  ),
453
467
  });
454
468
  }
@@ -604,7 +618,7 @@ const injectEventRecordingScript = async (
604
618
  target.getAttribute(`on${eventType}`) ||
605
619
  target.getAttribute(`${eventType}`) ||
606
620
  target.getAttribute(
607
- `${eventType.charAt(0).toUpperCase() + eventType.slice(1)}`
621
+ `${eventType.charAt(0).toUpperCase() + eventType.slice(1)}`,
608
622
  )
609
623
  ) {
610
624
  return target;
@@ -677,7 +691,7 @@ const injectEventRecordingScript = async (
677
691
  currentEventSnapshot = document.documentElement.innerHTML;
678
692
  const currentTarget = getParentElementWithEventOrId(
679
693
  event,
680
- "ondblclick"
694
+ "ondblclick",
681
695
  );
682
696
  const selectors = getBestSelectors(currentTarget, event);
683
697
  getXpathsIncluded(selectors, currentTarget, event);
@@ -699,7 +713,7 @@ const injectEventRecordingScript = async (
699
713
  currentEventSnapshot = document.documentElement.innerHTML;
700
714
  const currentTarget = getParentElementWithEventOrId(
701
715
  event,
702
- "oncontextmenu"
716
+ "oncontextmenu",
703
717
  );
704
718
  const selectors = getBestSelectors(currentTarget, event);
705
719
  getXpathsIncluded(selectors, currentTarget, event);