ftmocks-utils 1.4.4 → 1.4.6

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.4.4",
3
+ "version": "1.4.6",
4
4
  "description": "Util functions for FtMocks",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -8,18 +8,21 @@ const getLocator = async (page, event) => {
8
8
  if (event && event.target && typeof page !== "undefined" && page.locator) {
9
9
  let locator = null;
10
10
  while (!locator) {
11
- const selector = event.target.startsWith("/")
12
- ? `xpath=${event.target}`
13
- : event.target;
11
+ const selector =
12
+ event.target.startsWith("/") || event.target.startsWith("(/")
13
+ ? `xpath=${event.target}`
14
+ : event.target;
14
15
  try {
15
16
  const count = await page.locator(selector).count();
16
17
  if (count === 1) {
17
18
  locator = selector;
18
19
  } else {
19
20
  for (let i = 0; i < event.selectors.length; i++) {
20
- const selector = event.selectors[i].value.startsWith("/")
21
- ? `xpath=${event.selectors[i].value}`
22
- : event.selectors[i].value;
21
+ const selector =
22
+ event.selectors[i].value.startsWith("/") ||
23
+ event.selectors[i].value.startsWith("(/")
24
+ ? `xpath=${event.selectors[i].value}`
25
+ : event.selectors[i].value;
23
26
  const count = await page.locator(selector).count();
24
27
  if (count === 1) {
25
28
  locator = selector;
@@ -37,51 +40,157 @@ const getLocator = async (page, event) => {
37
40
  return event.target;
38
41
  };
39
42
 
40
- const runEvent = async (page, event, delay = 0) => {
43
+ const healSelector = async (page, event, selector, position) => {
44
+ console.log("➡ Healing selector for event", event);
45
+ let locator = selector;
46
+ if (locator.startsWith("/") || locator.startsWith("(/")) {
47
+ locator = `xpath=${locator}`;
48
+ try {
49
+ // INSERT_YOUR_CODE
50
+ // Get all elements that match the given locator
51
+ const elements = await page.locator(locator).elementHandles();
52
+ if (elements.length > 1) {
53
+ let index = 0;
54
+ for (let i = 0; i < elements.length; i++) {
55
+ const element = elements[i];
56
+ const currPosition = await element.boundingBox();
57
+ if (
58
+ currPosition.x >= position.x &&
59
+ currPosition.y >= position.y &&
60
+ currPosition.x + currPosition.width <=
61
+ position.x + position.width &&
62
+ currPosition.y + currPosition.height <= position.y + position.height
63
+ ) {
64
+ index = i;
65
+ break;
66
+ }
67
+ }
68
+ return {
69
+ value: `(${selector})[${index + 1}]`,
70
+ count: 1,
71
+ };
72
+ } else {
73
+ return {
74
+ value: selector,
75
+ count: elements.length,
76
+ };
77
+ }
78
+ } catch (error) {
79
+ console.error("Error getting locator", error, selector);
80
+ }
81
+ }
82
+ return {
83
+ value: selector,
84
+ count: 1,
85
+ };
86
+ };
87
+
88
+ const getSelectorPosition = async (page, selector) => {
89
+ const element = await page.locator(selector).elementHandle();
90
+ const position = await element.boundingBox();
91
+ console.log("position", position);
92
+ return position;
93
+ };
94
+
95
+ const runEvent = async ({
96
+ page,
97
+ event,
98
+ delay = 0,
99
+ screenshots = false,
100
+ screenshotsDir = null,
101
+ healSelectors = false,
102
+ }) => {
41
103
  try {
42
104
  console.log("➡ Running event", event);
105
+ const beforeEvent = async () => {
106
+ await page.waitForTimeout(delay);
107
+ if (screenshots) {
108
+ const locator = await getLocator(page, event);
109
+ const position = await getSelectorPosition(page, locator);
110
+ event.screenshotInfo = {
111
+ name: `${event.id}.png`,
112
+ position,
113
+ };
114
+ await page.screenshot({
115
+ path: path.join(screenshotsDir, `${event.id}.png`),
116
+ fullPage: false,
117
+ });
118
+ }
119
+ if (healSelectors) {
120
+ const locator = await getLocator(page, event);
121
+ const position = await getSelectorPosition(page, locator);
122
+ const healedTarget = await healSelector(
123
+ page,
124
+ event,
125
+ event.target,
126
+ position
127
+ );
128
+ event.target = healedTarget.value;
129
+ const selectors = [];
130
+ const laterToBeAdded = [];
131
+ for (let i = 0; i < event.selectors.length; i++) {
132
+ const healedSelector = await healSelector(
133
+ page,
134
+ event,
135
+ event.selectors[i].value,
136
+ position
137
+ );
138
+ event.selectors[i].value = healedSelector.value;
139
+ if (healedSelector.count !== 1) {
140
+ laterToBeAdded.push(event.selectors[i]);
141
+ } else {
142
+ selectors.push(event.selectors[i]);
143
+ }
144
+ }
145
+ selectors.push(...laterToBeAdded);
146
+ if (healedTarget.count !== 1) {
147
+ event.target = selectors[0].value;
148
+ }
149
+ event.selectors = selectors;
150
+ }
151
+ };
43
152
  switch (event.type) {
44
153
  case "url":
45
154
  await page.goto(event.value);
46
155
  break;
47
156
  case "click":
48
- await page.waitForTimeout(delay);
157
+ await beforeEvent();
49
158
  await page.click(await getLocator(page, event));
50
159
  break;
51
160
  case "input":
52
- await page.waitForTimeout(delay);
161
+ await beforeEvent();
53
162
  await page.fill(await getLocator(page, event), event.value);
54
163
  break;
55
164
  case "keypress":
56
- await page.waitForTimeout(delay);
165
+ await beforeEvent();
57
166
  await page.keyboard.press(await getLocator(page, event), event.key);
58
167
  break;
59
168
  case "change":
60
- await page.waitForTimeout(delay);
169
+ await beforeEvent();
61
170
  await page.select(await getLocator(page, event), event.value);
62
171
  break;
63
172
  case "url":
64
- await page.waitForTimeout(delay);
173
+ await beforeEvent();
65
174
  await page.goto(await getLocator(page, event), event.value);
66
175
  break;
67
176
  case "dblclick":
68
- await page.waitForTimeout(delay);
177
+ await beforeEvent();
69
178
  await page.dblclick(await getLocator(page, event));
70
179
  break;
71
180
  case "contextmenu":
72
- await page.waitForTimeout(delay);
181
+ await beforeEvent();
73
182
  await page.contextmenu(await getLocator(page, event));
74
183
  break;
75
184
  case "hover":
76
- await page.waitForTimeout(delay);
185
+ await beforeEvent();
77
186
  await page.hover(await getLocator(page, event));
78
187
  break;
79
188
  case "keydown":
80
- await page.waitForTimeout(delay);
189
+ await beforeEvent();
81
190
  await page.keyboard.down(await getLocator(page, event), event.key);
82
191
  break;
83
192
  case "keyup":
84
- await page.waitForTimeout(delay);
193
+ await beforeEvent();
85
194
  await page.keyboard.up(await getLocator(page, event), event.key);
86
195
  break;
87
196
  default:
@@ -129,9 +238,23 @@ const isValidEvent = (event) => {
129
238
  return false;
130
239
  };
131
240
 
132
- const runEvents = async (page, events, delay = 1000) => {
241
+ const runEvents = async ({
242
+ page,
243
+ events,
244
+ delay = 1000,
245
+ screenshots = false,
246
+ screenshotsDir = null,
247
+ healSelectors = false,
248
+ }) => {
133
249
  for (const event of events) {
134
- await runEvent(page, event, delay);
250
+ await runEvent({
251
+ page,
252
+ event,
253
+ delay,
254
+ screenshots,
255
+ screenshotsDir,
256
+ healSelectors,
257
+ });
135
258
  }
136
259
  };
137
260
 
@@ -142,14 +265,12 @@ const runEventsForTest = async (page, ftmocksConifg, testName) => {
142
265
  `_events.json`
143
266
  );
144
267
  const events = JSON.parse(fs.readFileSync(eventsFile, "utf8"));
145
- await runEvents(page, events, ftmocksConifg.delay || 1000);
146
- };
147
-
148
- const getSelectorPosition = async (page, selector) => {
149
- const element = await page.locator(selector).elementHandle();
150
- const position = await element.boundingBox();
151
- console.log("position", position);
152
- return position;
268
+ await runEvents({
269
+ page,
270
+ events,
271
+ delay: ftmocksConifg.delay || 1000,
272
+ screenshots: false,
273
+ });
153
274
  };
154
275
 
155
276
  const runEventsInPresentationMode = async (page, ftmocksConifg, testName) => {
@@ -168,10 +289,10 @@ const runEventsInPresentationMode = async (page, ftmocksConifg, testName) => {
168
289
  console.log("➡ No more events to run!");
169
290
  return false;
170
291
  }
171
- let result = await runEvent(page, events[currentEventIndex]);
292
+ let result = await runEvent({ page, event: events[currentEventIndex] });
172
293
  while (result === "Unsupported event type") {
173
294
  currentEventIndex = currentEventIndex + 1;
174
- result = await runEvent(page, events[currentEventIndex]);
295
+ result = await runEvent({ page, event: events[currentEventIndex] });
175
296
  }
176
297
  currentEventIndex = currentEventIndex + 1;
177
298
  return true;
@@ -206,7 +327,7 @@ const runEventsInPresentationMode = async (page, ftmocksConifg, testName) => {
206
327
  });
207
328
  });
208
329
 
209
- await runEvent(page, events[0]);
330
+ await runEvent({ page, event: events[0] });
210
331
  };
211
332
 
212
333
  const runEventsInTrainingMode = async (page, ftmocksConifg, testName) => {
@@ -375,6 +496,15 @@ const runEventsInTrainingMode = async (page, ftmocksConifg, testName) => {
375
496
  }
376
497
  };
377
498
 
499
+ const showNextEvent = async () => {
500
+ currentEventInfo = await window.getNextEvent();
501
+ if (currentEventInfo) {
502
+ showPopover(currentEventInfo);
503
+ } else {
504
+ hidePopover();
505
+ }
506
+ };
507
+
378
508
  const matchElement = (event, currentEventInfo) => {
379
509
  const inBoudingBox =
380
510
  currentEventInfo?.position?.x <= event.clientX &&
@@ -400,13 +530,7 @@ const runEventsInTrainingMode = async (page, ftmocksConifg, testName) => {
400
530
  matchElement(event, currentEventInfo)
401
531
  ) {
402
532
  console.log("➡ Click event triggered!", event);
403
- currentEventInfo = await window.getNextEvent();
404
- console.log("➡ Next event", currentEventInfo);
405
- if (currentEventInfo) {
406
- showPopover(currentEventInfo);
407
- } else {
408
- hidePopover();
409
- }
533
+ showNextEvent();
410
534
  }
411
535
  });
412
536
  window.addEventListener("dblclick", async (event) => {
@@ -414,12 +538,7 @@ const runEventsInTrainingMode = async (page, ftmocksConifg, testName) => {
414
538
  currentEventInfo?.event?.type === "dblclick" &&
415
539
  matchElement(event, currentEventInfo)
416
540
  ) {
417
- currentEventInfo = await window.getNextEvent();
418
- if (currentEventInfo) {
419
- showPopover(currentEventInfo);
420
- } else {
421
- hidePopover();
422
- }
541
+ showNextEvent();
423
542
  }
424
543
  });
425
544
  window.addEventListener("contextmenu", async (event) => {
@@ -427,12 +546,7 @@ const runEventsInTrainingMode = async (page, ftmocksConifg, testName) => {
427
546
  currentEventInfo?.event?.type === "contextmenu" &&
428
547
  matchElement(event, currentEventInfo)
429
548
  ) {
430
- currentEventInfo = await window.getNextEvent();
431
- if (currentEventInfo) {
432
- showPopover(currentEventInfo);
433
- } else {
434
- hidePopover();
435
- }
549
+ showNextEvent();
436
550
  }
437
551
  });
438
552
  window.addEventListener("input", async (event) => {
@@ -440,10 +554,7 @@ const runEventsInTrainingMode = async (page, ftmocksConifg, testName) => {
440
554
  currentEventInfo?.event?.type === "input" &&
441
555
  matchElement(event, currentEventInfo)
442
556
  ) {
443
- currentEventInfo = await window.getNextEvent();
444
- if (currentEventInfo) {
445
- showPopover(currentEventInfo);
446
- }
557
+ showNextEvent();
447
558
  }
448
559
  });
449
560
  window.addEventListener("keypress", async (event) => {
@@ -451,15 +562,53 @@ const runEventsInTrainingMode = async (page, ftmocksConifg, testName) => {
451
562
  currentEventInfo?.event?.type === "keypress" &&
452
563
  matchElement(event, currentEventInfo)
453
564
  ) {
454
- currentEventInfo = await window.getNextEvent();
455
- if (currentEventInfo) {
456
- showPopover(currentEventInfo);
457
- }
565
+ showNextEvent();
458
566
  }
459
567
  });
460
568
  });
461
569
 
462
- await runEvent(page, events[0]);
570
+ await runEvent({ page, event: events[0] });
571
+ };
572
+
573
+ const runEventsForScreenshots = async (page, ftmocksConifg, testName) => {
574
+ const eventsFile = path.join(
575
+ getMockDir(ftmocksConifg),
576
+ `test_${nameToFolder(testName)}`,
577
+ `_events.json`
578
+ );
579
+ const events = JSON.parse(fs.readFileSync(eventsFile, "utf8"));
580
+ await runEvents({
581
+ page,
582
+ events,
583
+ delay: ftmocksConifg.delay || 1000,
584
+ screenshots: true,
585
+ screenshotsDir: path.join(
586
+ getMockDir(ftmocksConifg),
587
+ `test_${nameToFolder(testName)}`,
588
+ `screenshots`
589
+ ),
590
+ });
591
+ fs.writeFileSync(eventsFile, JSON.stringify(events, null, 2));
592
+ await page.waitForTimeout(1000);
593
+ await page.close();
594
+ };
595
+
596
+ const runEventsForHealingSelectors = async (page, ftmocksConifg, testName) => {
597
+ const eventsFile = path.join(
598
+ getMockDir(ftmocksConifg),
599
+ `test_${nameToFolder(testName)}`,
600
+ `_events.json`
601
+ );
602
+ const events = JSON.parse(fs.readFileSync(eventsFile, "utf8"));
603
+ await runEvents({
604
+ page,
605
+ events,
606
+ delay: ftmocksConifg.delay || 1000,
607
+ healSelectors: true,
608
+ });
609
+ fs.writeFileSync(eventsFile, JSON.stringify(events, null, 2));
610
+ await page.waitForTimeout(1000);
611
+ await page.close();
463
612
  };
464
613
 
465
614
  module.exports = {
@@ -467,5 +616,7 @@ module.exports = {
467
616
  runEventsForTest,
468
617
  runEventsInPresentationMode,
469
618
  runEventsInTrainingMode,
619
+ runEventsForScreenshots,
620
+ runEventsForHealingSelectors,
470
621
  runEvent,
471
622
  };
package/src/index.js CHANGED
@@ -27,6 +27,8 @@ const {
27
27
  runEvent,
28
28
  runEventsInPresentationMode,
29
29
  runEventsInTrainingMode,
30
+ runEventsForScreenshots,
31
+ runEventsForHealingSelectors,
30
32
  } = require("./event-run-utils");
31
33
 
32
34
  // Export functions as a module
@@ -54,4 +56,6 @@ module.exports = {
54
56
  runEvent,
55
57
  runEventsInPresentationMode,
56
58
  runEventsInTrainingMode,
59
+ runEventsForScreenshots,
60
+ runEventsForHealingSelectors,
57
61
  };