ftmocks-utils 1.4.3 → 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.3",
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,11 +193,15 @@ const runEventsInPresentationMode = async (page, ftmocksConifg, testName) => {
174
193
  window.focus();
175
194
  document.body.focus();
176
195
  });
177
- window.addEventListener("keydown", (e) => {
196
+ window.addEventListener("keydown", async (e) => {
178
197
  console.log("➡ keydown event triggered!", e);
179
198
  if (e.key === "Shift" && !e.repeat) {
180
199
  e.preventDefault();
181
- window.nextEvent();
200
+ const result = await window.nextEvent();
201
+ if (!result) {
202
+ console.log("➡ No more events to run!");
203
+ await window.playwrightPageClose();
204
+ }
182
205
  }
183
206
  });
184
207
  });
@@ -187,7 +210,7 @@ const runEventsInPresentationMode = async (page, ftmocksConifg, testName) => {
187
210
  };
188
211
 
189
212
  const runEventsInTrainingMode = async (page, ftmocksConifg, testName) => {
190
- let currentEventIndex = 0;
213
+ const executedEvents = [];
191
214
  const eventsFile = path.join(
192
215
  getMockDir(ftmocksConifg),
193
216
  `test_${nameToFolder(testName)}`,
@@ -198,20 +221,31 @@ const runEventsInTrainingMode = async (page, ftmocksConifg, testName) => {
198
221
  // Expose Node function
199
222
  await page.exposeFunction("getNextEvent", async () => {
200
223
  let result = false;
224
+ let nonExecutedEvents = events.filter(
225
+ (event) => !executedEvents.includes(event?.id)
226
+ );
227
+ let currentEventIndex = -1;
201
228
  while (!result) {
202
229
  currentEventIndex = currentEventIndex + 1;
203
- if (currentEventIndex === events.length) {
230
+ if (currentEventIndex === nonExecutedEvents.length) {
204
231
  console.log("➡ No more events to validate!");
205
232
  return;
206
233
  }
207
- result = isValidEvent(events[currentEventIndex]);
234
+ result = isValidEvent(nonExecutedEvents[currentEventIndex]);
208
235
  }
209
- if (events[currentEventIndex]) {
210
- 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
+ );
211
245
  const position = await getSelectorPosition(page, selector);
212
246
  const element = await page.locator(selector).elementHandle();
213
247
  return {
214
- event: events[currentEventIndex],
248
+ event: nonExecutedEvents[currentEventIndex],
215
249
  selector,
216
250
  position,
217
251
  element,
@@ -220,6 +254,14 @@ const runEventsInTrainingMode = async (page, ftmocksConifg, testName) => {
220
254
  return null;
221
255
  });
222
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
+
223
265
  // Inject keyboard listener into browser
224
266
  await page.addInitScript(async () => {
225
267
  let currentEventInfo = null;
@@ -243,6 +285,44 @@ const runEventsInTrainingMode = async (page, ftmocksConifg, testName) => {
243
285
  popover.style.borderRadius = "8px";
244
286
  popover.style.boxShadow = "0 2px 12px rgba(0,0,0,0.25)";
245
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
+
246
326
  const highlighter = document.createElement("div");
247
327
  highlighter.id = "ftmocks-highlighter-training-mode";
248
328
  highlighter.style.position = "absolute";
@@ -254,8 +334,11 @@ const runEventsInTrainingMode = async (page, ftmocksConifg, testName) => {
254
334
  highlighter.style.border = "2px solid #3fa9f5";
255
335
  highlighter.style.display = "none";
256
336
  highlighter.style.pointerEvents = "none";
337
+ highlighter.style.zIndex = "99999";
257
338
 
258
339
  function showPopover(eventInfo) {
340
+ console.log("➡ Showing popover", eventInfo);
341
+ window.addExecutedEvent(eventInfo.event.id);
259
342
  if (!document.getElementById("ftmocks-popover-training-mode")) {
260
343
  document.body.appendChild(popover);
261
344
  }
@@ -279,6 +362,10 @@ const runEventsInTrainingMode = async (page, ftmocksConifg, testName) => {
279
362
 
280
363
  function hidePopover() {
281
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!");
282
369
  }
283
370
 
284
371
  const initialEventRun = async () => {
@@ -289,15 +376,18 @@ const runEventsInTrainingMode = async (page, ftmocksConifg, testName) => {
289
376
  };
290
377
 
291
378
  const matchElement = (event, currentEventInfo) => {
292
- console.log(
293
- "➡ Matching element!",
294
- event.target.isEqualNode(currentEventInfo?.element),
295
- currentEventInfo?.element?.contains(event.target)
296
- );
297
- return (
298
- event.target.isEqualNode(currentEventInfo?.element) ||
299
- currentEventInfo?.element?.contains(event.target)
300
- );
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;
301
391
  };
302
392
 
303
393
  window.addEventListener("load", async () => {
@@ -305,16 +395,13 @@ const runEventsInTrainingMode = async (page, ftmocksConifg, testName) => {
305
395
  });
306
396
 
307
397
  window.addEventListener("click", async (event) => {
308
- console.log(
309
- "➡ Click event triggered!",
310
- event.target.isEqualNode(currentEventInfo?.element),
311
- currentEventInfo?.element?.contains(event.target)
312
- );
313
398
  if (
314
399
  currentEventInfo?.event?.type === "click" &&
315
400
  matchElement(event, currentEventInfo)
316
401
  ) {
402
+ console.log("➡ Click event triggered!", event);
317
403
  currentEventInfo = await window.getNextEvent();
404
+ console.log("➡ Next event", currentEventInfo);
318
405
  if (currentEventInfo) {
319
406
  showPopover(currentEventInfo);
320
407
  } else {