ftmocks-utils 1.4.2 → 1.4.4

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.2",
3
+ "version": "1.4.4",
4
4
  "description": "Util functions for FtMocks",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -4,19 +4,35 @@ const { getMockDir, nameToFolder } = require("./common-utils");
4
4
 
5
5
  const getLocator = async (page, event) => {
6
6
  // Check if the event.target exists on the page before returning it.
7
+ console.log("➡ Getting locator for event", event);
7
8
  if (event && event.target && typeof page !== "undefined" && page.locator) {
8
- const count = await page.locator(event.target).count();
9
- if (count === 1) {
10
- return event.target;
11
- } else {
12
- await page.waitForTimeout(1000);
13
- for (let i = 0; i < event.selectors.length; i++) {
14
- const count = await page.locator(event.selectors[i].value).count();
9
+ let locator = null;
10
+ while (!locator) {
11
+ const selector = event.target.startsWith("/")
12
+ ? `xpath=${event.target}`
13
+ : event.target;
14
+ try {
15
+ const count = await page.locator(selector).count();
15
16
  if (count === 1) {
16
- return event.selectors[i].value;
17
+ locator = selector;
18
+ } else {
19
+ 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;
23
+ const count = await page.locator(selector).count();
24
+ if (count === 1) {
25
+ locator = selector;
26
+ }
27
+ }
17
28
  }
29
+ } catch (error) {
30
+ console.error("Error getting locator", error, selector);
18
31
  }
32
+ console.log("➡ Waiting for locator", event);
33
+ await page.waitForTimeout(500);
19
34
  }
35
+ return locator;
20
36
  }
21
37
  return event.target;
22
38
  };
@@ -83,8 +99,6 @@ const isValidEvent = (event) => {
83
99
  try {
84
100
  console.log("➡ Validating event", event);
85
101
  switch (event?.type) {
86
- case "url":
87
- return true;
88
102
  case "click":
89
103
  return true;
90
104
  case "input":
@@ -152,7 +166,7 @@ const runEventsInPresentationMode = async (page, ftmocksConifg, testName) => {
152
166
  console.log("➡ Next event triggered!");
153
167
  if (currentEventIndex === events.length) {
154
168
  console.log("➡ No more events to run!");
155
- return;
169
+ return false;
156
170
  }
157
171
  let result = await runEvent(page, events[currentEventIndex]);
158
172
  while (result === "Unsupported event type") {
@@ -160,12 +174,17 @@ const runEventsInPresentationMode = async (page, ftmocksConifg, testName) => {
160
174
  result = await runEvent(page, events[currentEventIndex]);
161
175
  }
162
176
  currentEventIndex = currentEventIndex + 1;
177
+ return true;
163
178
  });
164
179
 
165
180
  await page.exposeFunction("focusOnBodyForPresentationMode", async () => {
166
181
  await page.bringToFront();
167
182
  });
168
183
 
184
+ await page.exposeFunction("playwrightPageClose", async () => {
185
+ await page.close();
186
+ });
187
+
169
188
  // Inject keyboard listener into browser
170
189
  await page.addInitScript(() => {
171
190
  window.addEventListener("load", async () => {
@@ -174,10 +193,15 @@ const runEventsInPresentationMode = async (page, ftmocksConifg, testName) => {
174
193
  window.focus();
175
194
  document.body.focus();
176
195
  });
177
- window.addEventListener("keydown", (e) => {
178
- if (e.key === "ArrowRight") {
179
- console.log(" ArrowRight key pressed!");
180
- window.nextEvent();
196
+ window.addEventListener("keydown", async (e) => {
197
+ console.log("➡ keydown event triggered!", e);
198
+ if (e.key === "Shift" && !e.repeat) {
199
+ e.preventDefault();
200
+ const result = await window.nextEvent();
201
+ if (!result) {
202
+ console.log("➡ No more events to run!");
203
+ await window.playwrightPageClose();
204
+ }
181
205
  }
182
206
  });
183
207
  });
@@ -186,7 +210,7 @@ const runEventsInPresentationMode = async (page, ftmocksConifg, testName) => {
186
210
  };
187
211
 
188
212
  const runEventsInTrainingMode = async (page, ftmocksConifg, testName) => {
189
- let currentEventIndex = 0;
213
+ const executedEvents = [];
190
214
  const eventsFile = path.join(
191
215
  getMockDir(ftmocksConifg),
192
216
  `test_${nameToFolder(testName)}`,
@@ -197,20 +221,31 @@ const runEventsInTrainingMode = async (page, ftmocksConifg, testName) => {
197
221
  // Expose Node function
198
222
  await page.exposeFunction("getNextEvent", async () => {
199
223
  let result = false;
224
+ let nonExecutedEvents = events.filter(
225
+ (event) => !executedEvents.includes(event?.id)
226
+ );
227
+ let currentEventIndex = -1;
200
228
  while (!result) {
201
229
  currentEventIndex = currentEventIndex + 1;
202
- if (currentEventIndex === events.length) {
230
+ if (currentEventIndex === nonExecutedEvents.length) {
203
231
  console.log("➡ No more events to validate!");
204
232
  return;
205
233
  }
206
- result = isValidEvent(events[currentEventIndex]);
234
+ result = isValidEvent(nonExecutedEvents[currentEventIndex]);
207
235
  }
208
- if (events[currentEventIndex]) {
209
- const selector = await getLocator(page, events[currentEventIndex]);
236
+ if (nonExecutedEvents[currentEventIndex]) {
237
+ console.log(
238
+ "➡ Getting locator for event",
239
+ nonExecutedEvents[currentEventIndex]
240
+ );
241
+ const selector = await getLocator(
242
+ page,
243
+ nonExecutedEvents[currentEventIndex]
244
+ );
210
245
  const position = await getSelectorPosition(page, selector);
211
246
  const element = await page.locator(selector).elementHandle();
212
247
  return {
213
- event: events[currentEventIndex],
248
+ event: nonExecutedEvents[currentEventIndex],
214
249
  selector,
215
250
  position,
216
251
  element,
@@ -219,6 +254,14 @@ const runEventsInTrainingMode = async (page, ftmocksConifg, testName) => {
219
254
  return null;
220
255
  });
221
256
 
257
+ await page.exposeFunction("addExecutedEvent", (eventId) => {
258
+ executedEvents.push(eventId);
259
+ });
260
+
261
+ await page.exposeFunction("playwrightPageClose", async () => {
262
+ await page.close();
263
+ });
264
+
222
265
  // Inject keyboard listener into browser
223
266
  await page.addInitScript(async () => {
224
267
  let currentEventInfo = null;
@@ -242,6 +285,44 @@ const runEventsInTrainingMode = async (page, ftmocksConifg, testName) => {
242
285
  popover.style.borderRadius = "8px";
243
286
  popover.style.boxShadow = "0 2px 12px rgba(0,0,0,0.25)";
244
287
 
288
+ // Success Training Popover
289
+ const successPopover = document.createElement("div");
290
+ successPopover.id = "ftmocks-success-popover-training-mode";
291
+ successPopover.style.position = "fixed";
292
+ successPopover.style.top = "32px";
293
+ successPopover.style.left = "50%";
294
+ successPopover.style.transform = "translateX(-50%)";
295
+ successPopover.style.minWidth = "120px";
296
+ successPopover.style.height = "46px";
297
+ successPopover.style.background = "rgba(60,180,75,0.98)";
298
+ successPopover.style.color = "#fff";
299
+ successPopover.style.display = "none";
300
+ successPopover.style.zIndex = "100000";
301
+ successPopover.style.fontFamily = "sans-serif";
302
+ successPopover.style.fontSize = "16px";
303
+ successPopover.style.textAlign = "center";
304
+ successPopover.style.lineHeight = "1.4";
305
+ successPopover.style.padding = "12px 32px";
306
+ successPopover.style.borderRadius = "8px";
307
+ successPopover.style.boxShadow = "0 2px 12px rgba(0,0,0,0.20)";
308
+ successPopover.textContent = "✅ Training Success!";
309
+
310
+ // Utility to show the success popover for a short period
311
+ function showSuccessPopover(message = "✅ Training Success!") {
312
+ successPopover.textContent = message;
313
+ if (!document.getElementById("ftmocks-success-popover-training-mode")) {
314
+ document.body.appendChild(successPopover);
315
+ }
316
+ successPopover.style.display = "block";
317
+ setTimeout(() => {
318
+ successPopover.style.display = "none";
319
+ document
320
+ .getElementById("ftmocks-success-popover-training-mode")
321
+ ?.remove();
322
+ window.playwrightPageClose();
323
+ }, 3000);
324
+ }
325
+
245
326
  const highlighter = document.createElement("div");
246
327
  highlighter.id = "ftmocks-highlighter-training-mode";
247
328
  highlighter.style.position = "absolute";
@@ -253,8 +334,11 @@ const runEventsInTrainingMode = async (page, ftmocksConifg, testName) => {
253
334
  highlighter.style.border = "2px solid #3fa9f5";
254
335
  highlighter.style.display = "none";
255
336
  highlighter.style.pointerEvents = "none";
337
+ highlighter.style.zIndex = "99999";
256
338
 
257
339
  function showPopover(eventInfo) {
340
+ console.log("➡ Showing popover", eventInfo);
341
+ window.addExecutedEvent(eventInfo.event.id);
258
342
  if (!document.getElementById("ftmocks-popover-training-mode")) {
259
343
  document.body.appendChild(popover);
260
344
  }
@@ -278,6 +362,10 @@ const runEventsInTrainingMode = async (page, ftmocksConifg, testName) => {
278
362
 
279
363
  function hidePopover() {
280
364
  popover.style.display = "none";
365
+ highlighter.style.display = "none";
366
+ document.getElementById("ftmocks-popover-training-mode")?.remove();
367
+ document.getElementById("ftmocks-highlighter-training-mode")?.remove();
368
+ showSuccessPopover("✅ Training Success!");
281
369
  }
282
370
 
283
371
  const initialEventRun = async () => {
@@ -288,15 +376,18 @@ const runEventsInTrainingMode = async (page, ftmocksConifg, testName) => {
288
376
  };
289
377
 
290
378
  const matchElement = (event, currentEventInfo) => {
291
- console.log(
292
- "➡ Matching element!",
293
- event.target.isEqualNode(currentEventInfo?.element),
294
- currentEventInfo?.element?.contains(event.target)
295
- );
296
- return (
297
- event.target.isEqualNode(currentEventInfo?.element) ||
298
- currentEventInfo?.element?.contains(event.target)
299
- );
379
+ const inBoudingBox =
380
+ currentEventInfo?.position?.x <= event.clientX &&
381
+ currentEventInfo?.position?.x + currentEventInfo?.position?.width >=
382
+ event.clientX &&
383
+ currentEventInfo?.position?.y <= event.clientY &&
384
+ currentEventInfo?.position?.y + currentEventInfo?.position?.height >=
385
+ event.clientY;
386
+ console.log("➡ In bounding box?", inBoudingBox);
387
+ const matchingElement =
388
+ currentEventInfo?.element?.isEqualNode(event.target) ||
389
+ currentEventInfo?.element?.contains(event.target);
390
+ return inBoudingBox || matchingElement;
300
391
  };
301
392
 
302
393
  window.addEventListener("load", async () => {
@@ -304,16 +395,13 @@ const runEventsInTrainingMode = async (page, ftmocksConifg, testName) => {
304
395
  });
305
396
 
306
397
  window.addEventListener("click", async (event) => {
307
- console.log(
308
- "➡ Click event triggered!",
309
- event.target.isEqualNode(currentEventInfo?.element),
310
- currentEventInfo?.element?.contains(event.target)
311
- );
312
398
  if (
313
399
  currentEventInfo?.event?.type === "click" &&
314
400
  matchElement(event, currentEventInfo)
315
401
  ) {
402
+ console.log("➡ Click event triggered!", event);
316
403
  currentEventInfo = await window.getNextEvent();
404
+ console.log("➡ Next event", currentEventInfo);
317
405
  if (currentEventInfo) {
318
406
  showPopover(currentEventInfo);
319
407
  } else {