@capillarytech/cap-ui-utils 3.1.1 → 3.1.3

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.
Files changed (42) hide show
  1. package/e2e/README.md +1 -1
  2. package/e2e/constants/common.js +22 -0
  3. package/e2e/index.js +63 -0
  4. package/e2e/pages/common/base.page.js +13 -0
  5. package/e2e/pages/common/constant.js +5 -0
  6. package/e2e/pages/common/login.page.js +95 -0
  7. package/e2e/services/{lockService.ts → lockService.js} +63 -67
  8. package/e2e/utils/{antdVersionUtil.ts → antdVersionUtil.js} +24 -32
  9. package/e2e/utils/{automationBypassUtil.ts → automationBypassUtil.js} +11 -8
  10. package/e2e/utils/{debugModeUtil.ts → debugModeUtil.js} +9 -7
  11. package/e2e/utils/deletionRegistry.js +16 -0
  12. package/e2e/utils/{elementUtil.ts → elementUtil.js} +224 -296
  13. package/e2e/utils/expectUtil.js +40 -0
  14. package/e2e/utils/featureFlagUtil.js +25 -0
  15. package/e2e/utils/htmlEditorUtil.js +59 -0
  16. package/e2e/utils/logCollectorUtil.js +65 -0
  17. package/e2e/utils/{mockResponse.ts → mockResponse.js} +5 -2
  18. package/e2e/utils/{reportUploader.ts → reportUploader.js} +69 -66
  19. package/e2e/utils/{requestRecorderUtil.ts → requestRecorderUtil.js} +69 -97
  20. package/e2e/utils/{screenshotRecorderUtil.ts → screenshotRecorderUtil.js} +61 -82
  21. package/e2e/utils/setupMock.js +18 -0
  22. package/e2e/utils/unmatchedBracesUtil.js +87 -0
  23. package/e2e/utils/uploaders/fileServiceUploader.js +113 -0
  24. package/e2e/utils/uploaders/uploader.js +2 -0
  25. package/e2e/utils/virtualListUtil.js +74 -0
  26. package/package.json +4 -1
  27. package/e2e/constants/common.ts +0 -23
  28. package/e2e/index.ts +0 -30
  29. package/e2e/pages/common/base.page.ts +0 -11
  30. package/e2e/pages/common/constant.ts +0 -2
  31. package/e2e/pages/common/login.page.ts +0 -95
  32. package/e2e/utils/deletionRegistry.ts +0 -15
  33. package/e2e/utils/expectUtil.ts +0 -19
  34. package/e2e/utils/featureFlagUtil.ts +0 -30
  35. package/e2e/utils/garudaDropdownUtil.ts +0 -60
  36. package/e2e/utils/htmlEditorUtil.ts +0 -56
  37. package/e2e/utils/logCollectorUtil.ts +0 -59
  38. package/e2e/utils/setupMock.ts +0 -29
  39. package/e2e/utils/unmatchedBracesUtil.ts +0 -101
  40. package/e2e/utils/uploaders/fileServiceUploader.ts +0 -84
  41. package/e2e/utils/uploaders/uploader.ts +0 -20
  42. package/e2e/utils/virtualListUtil.ts +0 -115
@@ -1,28 +1,26 @@
1
- import { strict as assert } from "assert";
2
-
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const assert_1 = require("assert");
3
4
  class ElementUtils {
4
- async hoverToReveal(element:WebdriverIO.Element){
5
+ async hoverToReveal(element) {
5
6
  await element.waitForDisplayed({ timeout: 60000 });
6
7
  await element.moveTo();
7
8
  await browser.execute((el) => {
8
- ['mouseenter','mouseover','mousemove'].forEach((t) =>
9
- el.dispatchEvent(new MouseEvent(t, { bubbles: true })));
9
+ ['mouseenter', 'mouseover', 'mousemove'].forEach((t) => el.dispatchEvent(new MouseEvent(t, { bubbles: true })));
10
10
  }, element);
11
11
  await browser.pause(400);
12
12
  }
13
-
14
- async elementClick(element:WebdriverIO.Element){
15
- await browser.pause(2000)
16
- await element.waitForDisplayed({ timeout: 90000})
17
- try{
18
- await element.waitForClickable({ timeout: 60000, interval:1000, timeoutMsg:"element is still not clickable" })
19
- await element.click()
13
+ async elementClick(element) {
14
+ await browser.pause(2000);
15
+ await element.waitForDisplayed({ timeout: 90000 });
16
+ try {
17
+ await element.waitForClickable({ timeout: 60000, interval: 1000, timeoutMsg: "element is still not clickable" });
18
+ await element.click();
20
19
  }
21
- catch(error){
22
- await this.forceClick(element)
20
+ catch (error) {
21
+ await this.forceClick(element);
23
22
  }
24
23
  }
25
-
26
24
  /**
27
25
  * Scrolls the element to viewport center and clicks it via a dispatched
28
26
  * pointer/mouse-event sequence — bypasses WDIO's isClickable coverage check.
@@ -30,31 +28,28 @@ class ElementUtils {
30
28
  * overlay (info banner, lingering popover) sits over its centre point, or
31
29
  * antd selects that open on mousedown rather than click.
32
30
  */
33
- async forceClick(element:WebdriverIO.Element){
31
+ async forceClick(element) {
34
32
  const fresh = await this.waitForDisplayedStaleSafe(element, 90000);
35
- await browser.execute((el: any) => {
33
+ await browser.execute((el) => {
36
34
  el.scrollIntoView({ block: "center", inline: "center" });
37
- ['pointerdown','mousedown','pointerup','mouseup','click'].forEach((t) =>
38
- el.dispatchEvent(new MouseEvent(t, { bubbles: true, cancelable: true, view: window })));
35
+ ['pointerdown', 'mousedown', 'pointerup', 'mouseup', 'click'].forEach((t) => el.dispatchEvent(new MouseEvent(t, { bubbles: true, cancelable: true, view: window })));
39
36
  }, fresh);
40
37
  await browser.pause(500);
41
38
  }
42
-
43
39
  /**
44
40
  * Re-fetches a fresh element from its original selector. A detached ("stale")
45
41
  * handle — left behind when React/antd re-renders the node — is replaced by a
46
42
  * live one. Falls back to the original reference for elements without a plain
47
43
  * string selector (e.g. chained/derived elements).
48
44
  */
49
- private async refetch(element: WebdriverIO.Element): Promise<WebdriverIO.Element> {
45
+ async refetch(element) {
50
46
  // `.selector` on an un-awaited ChainablePromiseElement (e.g. a page getter
51
47
  // passed straight through, as every current caller does) resolves lazily —
52
48
  // reading it as a promise, not a sync property, prevents this from always
53
49
  // seeing a Promise object and silently skipping the re-query.
54
- const selector = await (element as any)?.selector;
50
+ const selector = await (element === null || element === void 0 ? void 0 : element.selector);
55
51
  return typeof selector === "string" ? await $(selector) : element;
56
52
  }
57
-
58
53
  /**
59
54
  * waitForDisplayed that survives the target re-rendering mid-wait, returning
60
55
  * the live handle it last confirmed. WDIO's built-in waitForDisplayed polls
@@ -66,7 +61,7 @@ class ElementUtils {
66
61
  * get a fresh, currently-attached element instead of one that already went
67
62
  * stale during the wait.
68
63
  */
69
- private async waitForDisplayedStaleSafe(element: WebdriverIO.Element, timeout: number = 90000): Promise<WebdriverIO.Element> {
64
+ async waitForDisplayedStaleSafe(element, timeout = 90000) {
70
65
  const start = Date.now();
71
66
  while (Date.now() - start < timeout) {
72
67
  const el = await this.refetch(element);
@@ -74,18 +69,20 @@ class ElementUtils {
74
69
  const outcome = await el
75
70
  .waitForDisplayed({ timeout: remaining, interval: 500 })
76
71
  .then(() => "displayed")
77
- .catch((error) =>
78
- String((error as any)?.message ?? error).includes("stale element reference")
79
- ? "stale"
80
- : "timeout"
81
- );
82
- if (outcome === "displayed") return el;
83
- if (outcome === "timeout") throw new Error("Element not displayed within timeout");
72
+ .catch((error) => {
73
+ var _a;
74
+ return String((_a = error === null || error === void 0 ? void 0 : error.message) !== null && _a !== void 0 ? _a : error).includes("stale element reference")
75
+ ? "stale"
76
+ : "timeout";
77
+ });
78
+ if (outcome === "displayed")
79
+ return el;
80
+ if (outcome === "timeout")
81
+ throw new Error("Element not displayed within timeout");
84
82
  await browser.pause(300); // stale → re-query and keep waiting
85
83
  }
86
84
  throw new Error("Element kept going stale while waiting for it to be displayed");
87
85
  }
88
-
89
86
  /**
90
87
  * Clicks an element, retrying against a freshly re-queried node whenever the
91
88
  * DOM re-renders mid-interaction and throws "stale element reference" (antd
@@ -93,7 +90,7 @@ class ElementUtils {
93
90
  * failure it falls back to a forced click. Throws only if nothing succeeds
94
91
  * within `timeout`.
95
92
  */
96
- private async clickWithStaleRetry(element: WebdriverIO.Element, timeout: number = 40000): Promise<void> {
93
+ async clickWithStaleRetry(element, timeout = 40000) {
97
94
  const start = Date.now();
98
95
  // Uses promise .catch() rather than nested try/catch blocks: a genuine
99
96
  // failure still propagates cleanly to WDIO so the failure screenshot is
@@ -104,14 +101,17 @@ class ElementUtils {
104
101
  .waitForClickable({ timeout: 10000, interval: 500 })
105
102
  .then(() => el.click())
106
103
  .then(() => "clicked")
107
- .catch((error) =>
108
- String((error as any)?.message ?? error).includes("stale element reference")
109
- ? "stale"
110
- : "unclickable"
111
- );
112
- if (outcome === "clicked") return;
113
- if (outcome === "unclickable") break; // genuine (non-stale) failure → forced click
114
- await browser.pause(500); // stale → re-query and retry
104
+ .catch((error) => {
105
+ var _a;
106
+ return String((_a = error === null || error === void 0 ? void 0 : error.message) !== null && _a !== void 0 ? _a : error).includes("stale element reference")
107
+ ? "stale"
108
+ : "unclickable";
109
+ });
110
+ if (outcome === "clicked")
111
+ return;
112
+ if (outcome === "unclickable")
113
+ break; // genuine (non-stale) failure → forced click
114
+ await browser.pause(500); // stale → re-query and retry
115
115
  }
116
116
  // Single fallback: forceClick re-fetches and stale-waits internally, so
117
117
  // it survives the target still churning after the loop above gave up.
@@ -119,7 +119,6 @@ class ElementUtils {
119
119
  // real error to the test (and its screenshot).
120
120
  await this.forceClick(element);
121
121
  }
122
-
123
122
  /**
124
123
  * Expands an antd Collapse (accordion) left-nav section by its header label,
125
124
  * then confirms `confirmChild` became visible.
@@ -137,18 +136,16 @@ class ElementUtils {
137
136
  * User segments, …). `label` is the visible header text; `confirmChild` is any
138
137
  * element that only becomes visible once the section is expanded.
139
138
  */
140
- async expandSideNavSection(label: string, confirmChild: WebdriverIO.Element, attempts = 3): Promise<void> {
139
+ async expandSideNavSection(label, confirmChild, attempts = 3) {
141
140
  // The header label may be a <span> (top-level sections) or a <div> (nested
142
141
  // sub-accordions like "Status"), so match any element by its text.
143
- const header = await $(
144
- `//div[@role="tab" and contains(@class,"ant-collapse-header")][.//*[normalize-space(text())="${label}"]]`
145
- );
142
+ const header = await $(`//div[@role="tab" and contains(@class,"ant-collapse-header")][.//*[normalize-space(text())="${label}"]]`);
146
143
  await header.waitForDisplayed({ timeout: 60000 });
147
144
  for (let attempt = 1; attempt <= attempts; attempt++) {
148
145
  const expanded = (await header.getAttribute("aria-expanded")) === "true";
149
146
  if (!expanded) {
150
147
  await header.waitForClickable({ timeout: 40000 });
151
- await browser.execute((el: any) => el.click(), header);
148
+ await browser.execute((el) => el.click(), header);
152
149
  console.log(`Expanded side-nav "${label}" (attempt ${attempt})`);
153
150
  }
154
151
  const visible = await confirmChild
@@ -162,7 +159,6 @@ class ElementUtils {
162
159
  }
163
160
  throw new Error(`Side-nav section "${label}" did not expand to reveal its content`);
164
161
  }
165
-
166
162
  /**
167
163
  * Opens a leaf item that lives inside a left-nav antd Collapse section:
168
164
  * expands the section (idempotent) via expandSideNavSection, then clicks the
@@ -172,8 +168,8 @@ class ElementUtils {
172
168
  * attempt. Use for the "expand section -> click child" pattern
173
169
  * (Library -> Charts, Data connectors -> Export schedules, User segments -> Status).
174
170
  */
175
- async openSideNavItem(sectionLabel: string, leaf: WebdriverIO.Element, attempts = 3): Promise<void> {
176
- let lastError: unknown;
171
+ async openSideNavItem(sectionLabel, leaf, attempts = 3) {
172
+ let lastError;
177
173
  for (let attempt = 1; attempt <= attempts; attempt++) {
178
174
  try {
179
175
  await this.expandSideNavSection(sectionLabel, leaf);
@@ -181,7 +177,8 @@ class ElementUtils {
181
177
  await leaf.click();
182
178
  console.log(`Opened "${sectionLabel}" item on attempt ${attempt}`);
183
179
  return;
184
- } catch (error) {
180
+ }
181
+ catch (error) {
185
182
  lastError = error;
186
183
  console.log(`openSideNavItem "${sectionLabel}": attempt ${attempt}/${attempts} failed (${error}) — re-expanding`);
187
184
  await browser.pause(2000);
@@ -189,28 +186,25 @@ class ElementUtils {
189
186
  }
190
187
  throw new Error(`openSideNavItem: could not open item under "${sectionLabel}" after ${attempts} attempts. Last error: ${lastError}`);
191
188
  }
192
-
193
- async enterText(element:WebdriverIO.Element, textToEnter){
194
- await browser.pause(1000)
195
- // browser.execute('arguments[0].style.outline = "#f00 solid 4px";', element);
196
- await element.waitForEnabled({ timeout: 40000, interval:1000, timeoutMsg:"element is still not editable" })
197
- await element.setValue(textToEnter)
189
+ async enterText(element, textToEnter) {
190
+ await browser.pause(1000);
191
+ // browser.execute('arguments[0].style.outline = "#f00 solid 4px";', element);
192
+ await element.waitForEnabled({ timeout: 40000, interval: 1000, timeoutMsg: "element is still not editable" });
193
+ await element.setValue(textToEnter);
198
194
  }
199
-
200
195
  /**
201
196
  * Types text using addValue (key events). Use for React controlled inputs
202
197
  * where setValue does not update state.
203
198
  */
204
- async typeText(element: WebdriverIO.Element, textToEnter: string) {
205
- await browser.pause(1000);
206
- await element.waitForEnabled({
207
- timeout: 40000,
208
- interval: 1000,
209
- timeoutMsg: "element is still not editable"
210
- });
211
- await element.addValue(textToEnter);
212
- }
213
-
199
+ async typeText(element, textToEnter) {
200
+ await browser.pause(1000);
201
+ await element.waitForEnabled({
202
+ timeout: 40000,
203
+ interval: 1000,
204
+ timeoutMsg: "element is still not editable"
205
+ });
206
+ await element.addValue(textToEnter);
207
+ }
214
208
  /**
215
209
  * Selects a date by clicking through the antd calendar UI instead of
216
210
  * typing into the input. Some DatePicker instances only accept a value
@@ -228,33 +222,29 @@ class ElementUtils {
228
222
  * (via the next/prev-month buttons) until the target month is showing,
229
223
  * then clicks the day cell there.
230
224
  */
231
- async selectCalendarDate(dateInput: WebdriverIO.Element, targetDate: Date, label = "date field") {
225
+ async selectCalendarDate(dateInput, targetDate, label = "date field") {
232
226
  const isoDate = `${targetDate.getFullYear()}-${String(targetDate.getMonth() + 1).padStart(2, "0")}-${String(targetDate.getDate()).padStart(2, "0")}`;
233
227
  const expectedDisplay = targetDate.toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric" });
234
-
235
228
  await this.retryStep(async () => {
236
229
  const input = await this.refetch(dateInput);
237
230
  await input.waitForClickable({ timeout: 40000 });
238
231
  await input.click();
239
-
240
232
  const panel = await $('//div[contains(@class,"ant-picker-dropdown") and not(contains(@class,"ant-picker-dropdown-hidden"))]');
241
233
  await panel.waitForDisplayed({ timeout: 10000 });
242
-
243
234
  let dayCell = await panel.$(`.//td[@title="${isoDate}"]`);
244
235
  const alreadyVisible = await dayCell.isDisplayed().catch(() => false);
245
-
246
236
  if (!alreadyVisible) {
247
237
  const headerView = await panel.$(".ant-picker-header-view");
248
238
  const nextBtn = await panel.$(".ant-picker-header-next-btn");
249
239
  const prevBtn = await panel.$(".ant-picker-header-prev-btn");
250
240
  const targetMonths = targetDate.getFullYear() * 12 + targetDate.getMonth();
251
-
252
241
  for (let attempt = 0; attempt < 24; attempt++) {
253
242
  const shownText = (await headerView.getText()).trim(); // e.g. "Jul 2026"
254
243
  const shownDate = new Date(`1 ${shownText}`);
255
244
  const shownMonths = shownDate.getFullYear() * 12 + shownDate.getMonth();
256
245
  const diff = targetMonths - shownMonths;
257
- if (diff === 0) break;
246
+ if (diff === 0)
247
+ break;
258
248
  if (attempt === 23) {
259
249
  throw new Error(`${label}: could not navigate calendar to target month (stuck on "${shownText}")`);
260
250
  }
@@ -263,12 +253,9 @@ class ElementUtils {
263
253
  }
264
254
  dayCell = await panel.$(`.//td[@title="${isoDate}"]`);
265
255
  }
266
-
267
256
  await dayCell.waitForClickable({ timeout: 10000 });
268
257
  await dayCell.click();
269
-
270
258
  await panel.waitForDisplayed({ timeout: 5000, reverse: true });
271
-
272
259
  const committed = await input.getValue();
273
260
  if (committed !== expectedDisplay) {
274
261
  throw new Error(`${label} did not commit: expected "${expectedDisplay}", got "${committed}"`);
@@ -282,7 +269,6 @@ class ElementUtils {
282
269
  },
283
270
  });
284
271
  }
285
-
286
272
  /**
287
273
  * Writes via the native HTMLInputElement value setter, then dispatches an
288
274
  * `input` event. Bypasses React's `_valueTracker`, so React commits the
@@ -298,91 +284,79 @@ class ElementUtils {
298
284
  * (validation, commit-on-blur, etc). With an element, it first waits for the
299
285
  * field to be editable, then dispatches `change` + blur after `input`.
300
286
  */
301
- async setReactInputValue(
302
- target: string | WebdriverIO.Element,
303
- value: string,
304
- alsoDispatch: Array<"change" | "blur"> = [],
305
- ) {
306
- if (typeof target === "string") {
307
- await browser.execute(
308
- (sel: string, val: string, extras: string[]) => {
309
- const input = document.querySelector(sel) as HTMLInputElement | null;
310
- if (!input) {
311
- throw new Error(`setReactInputValue: no element matches selector "${sel}"`);
287
+ async setReactInputValue(target, value, alsoDispatch = []) {
288
+ if (typeof target === "string") {
289
+ await browser.execute((sel, val, extras) => {
290
+ const input = document.querySelector(sel);
291
+ if (!input) {
292
+ throw new Error(`setReactInputValue: no element matches selector "${sel}"`);
293
+ }
294
+ const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
295
+ setter.call(input, val);
296
+ input.dispatchEvent(new Event('input', { bubbles: true }));
297
+ for (const name of extras) {
298
+ input.dispatchEvent(new Event(name, { bubbles: true }));
299
+ }
300
+ }, target, value, alsoDispatch);
301
+ return;
302
+ }
303
+ await browser.pause(1000);
304
+ await target.waitForEnabled({
305
+ timeout: 40000,
306
+ interval: 1000,
307
+ timeoutMsg: "element is still not editable"
308
+ });
309
+ await browser.execute((el, val) => {
310
+ const desc = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value");
311
+ if (desc && desc.set) {
312
+ desc.set.call(el, val);
312
313
  }
313
- const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')!.set!;
314
- setter.call(input, val);
315
- input.dispatchEvent(new Event('input', { bubbles: true }));
316
- for (const name of extras) {
317
- input.dispatchEvent(new Event(name, { bubbles: true }));
314
+ else {
315
+ el.value = val;
318
316
  }
319
- },
320
- target,
321
- value,
322
- alsoDispatch,
323
- );
324
- return;
325
- }
326
- await browser.pause(1000);
327
- await target.waitForEnabled({
328
- timeout: 40000,
329
- interval: 1000,
330
- timeoutMsg: "element is still not editable"
331
- });
332
- await browser.execute((el: any, val: string) => {
333
- const desc = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value");
334
- if (desc && desc.set) {
335
- desc.set.call(el, val);
336
- } else {
337
- el.value = val;
317
+ el.dispatchEvent(new Event("input", { bubbles: true }));
318
+ el.dispatchEvent(new Event("change", { bubbles: true }));
319
+ el.blur();
320
+ }, target, value);
321
+ }
322
+ async switchToUrl(url) {
323
+ await browser.switchWindow(url);
324
+ }
325
+ async waitUntilDisplayed(element, awaitedText, timeout = 30000) {
326
+ try {
327
+ // First wait for element to be displayed
328
+ await element.waitForDisplayed({ timeout: timeout, timeoutMsg: `Element not displayed within ${timeout} ms` });
329
+ // Then wait for the correct text
330
+ await element.waitUntil(async function () {
331
+ const currentText = await this.getText();
332
+ console.log(`Waiting for text: '${awaitedText}', Current text: '${currentText}'`);
333
+ return currentText.includes(awaitedText) || currentText === awaitedText;
334
+ }, {
335
+ timeout,
336
+ timeoutMsg: `Unable to locate the awaited text: '${awaitedText}' within ${timeout} ms`
337
+ });
338
+ }
339
+ catch (error) {
340
+ console.log(`🔴 Error in waitUntilDisplayed: ${error}`);
341
+ throw error;
338
342
  }
339
- el.dispatchEvent(new Event("input", { bubbles: true }));
340
- el.dispatchEvent(new Event("change", { bubbles: true }));
341
- el.blur();
342
- }, target, value);
343
- }
344
-
345
- async switchToUrl(url: string){
346
- await browser.switchWindow(url)
347
- }
348
-
349
- async waitUntilDisplayed(element:WebdriverIO.Element, awaitedText, timeout=30000){
350
- try {
351
- // First wait for element to be displayed
352
- await element.waitForDisplayed({ timeout: timeout, timeoutMsg: `Element not displayed within ${timeout} ms` });
353
-
354
- // Then wait for the correct text
343
+ }
344
+ async waitUntilEnabled(element) {
355
345
  await element.waitUntil(async function () {
356
- const currentText = await this.getText();
357
- console.log(`Waiting for text: '${awaitedText}', Current text: '${currentText}'`);
358
- return currentText.includes(awaitedText) || currentText === awaitedText;
346
+ return await element.isEnabled();
359
347
  }, {
360
- timeout,
361
- timeoutMsg: `Unable to locate the awaited text: '${awaitedText}' within ${timeout} ms`
348
+ timeout: 90000,
349
+ timeoutMsg: 'Element not enabled'
362
350
  });
363
- } catch (error) {
364
- console.log(`🔴 Error in waitUntilDisplayed: ${error}`);
365
- throw error;
366
- }
367
- }
368
-
369
- async waitUntilEnabled(element:WebdriverIO.Element){
370
- await element.waitUntil(async function () {
371
- return await element.isEnabled();
372
- }, {
373
- timeout: 90000,
374
- timeoutMsg: 'Element not enabled'
375
- });
376
- }
377
- async checkElemetExistsAndElementClick(element:WebdriverIO.Element){
351
+ }
352
+ async checkElemetExistsAndElementClick(element) {
378
353
  await browser.pause(1000);
379
354
  let isDisplayed = await element.isDisplayed();
380
355
  if (isDisplayed) {
381
- await element.waitForClickable({ timeout: 40000, interval:1000, timeoutMsg:"element is still not clickable" })
382
- await element.click()
356
+ await element.waitForClickable({ timeout: 40000, interval: 1000, timeoutMsg: "element is still not clickable" });
357
+ await element.click();
383
358
  }
384
359
  }
385
-
386
360
  /**
387
361
  * Stale-safe variant of checkElemetExistsAndElementClick. Opt-in helper for
388
362
  * optional buttons that live inside a re-rendering container — e.g. the
@@ -391,7 +365,7 @@ class ElementUtils {
391
365
  * element reference". Kept as a separate method so the widely-used shared
392
366
  * helpers stay untouched and other modules are unaffected.
393
367
  */
394
- async checkElementExistsAndClickStaleSafe(element:WebdriverIO.Element, appearTimeout: number = 3000){
368
+ async checkElementExistsAndClickStaleSafe(element, appearTimeout = 3000) {
395
369
  // Presence must be *waited* for, not sampled. A fixed pause + one
396
370
  // isDisplayed() snapshot loses the race against an antd modal's
397
371
  // mount + zoom transition: the helper returns "not there", the modal
@@ -405,10 +379,9 @@ class ElementUtils {
405
379
  .waitForDisplayed({ timeout: appearTimeout, interval: 250 })
406
380
  .then(() => true)
407
381
  .catch(() => false);
408
- if (!appeared) return false;
409
-
382
+ if (!appeared)
383
+ return false;
410
384
  await this.clickWithStaleRetry(element, 40000);
411
-
412
385
  // Confirm the modal actually went away: if it is still up, everything
413
386
  // downstream is about to fail confusingly, so fail here instead.
414
387
  const dismissed = await (await this.refetch(element))
@@ -420,12 +393,11 @@ class ElementUtils {
420
393
  }
421
394
  return true;
422
395
  }
423
-
424
396
  /**
425
397
  * Use this when the element/input is optional (not mandatory).
426
398
  * Does not throw if the element is not displayed; only logs and skips entering text.
427
399
  */
428
- async checkElemetExistsAndEnterText(element: WebdriverIO.Element, textToEnter: string) {
400
+ async checkElemetExistsAndEnterText(element, textToEnter) {
429
401
  const isDisplayed = await element.isDisplayed();
430
402
  if (isDisplayed) {
431
403
  await element.waitForEnabled({
@@ -436,102 +408,89 @@ class ElementUtils {
436
408
  await element.setValue(textToEnter);
437
409
  }
438
410
  }
439
-
440
- async isElementDisplayed(element:WebdriverIO.Element){
441
- await browser.pause(1000)
411
+ async isElementDisplayed(element) {
412
+ await browser.pause(1000);
442
413
  let isDisplayed = await element.isDisplayed();
443
414
  return isDisplayed;
444
- }
445
-
446
- async elementHover(element: WebdriverIO.Element) {
415
+ }
416
+ async elementHover(element) {
447
417
  try {
448
418
  // Pause for 2 seconds before starting (optional)
449
419
  await browser.pause(2000);
450
-
451
420
  // Wait for the element to be displayed
452
421
  await element.waitForDisplayed({ timeout: 90000 });
453
-
454
422
  // Wait for the element to be interactable/clickable (often ensures it's not obscured)
455
- await element.waitForClickable({
456
- timeout: 40000,
457
- interval: 1000,
423
+ await element.waitForClickable({
424
+ timeout: 40000,
425
+ interval: 1000,
458
426
  timeoutMsg: "element is still not interactable for hover"
459
427
  });
460
-
461
428
  // Perform the hover action
462
429
  await element.moveTo();
463
430
  console.log('Move to action performed');
464
- } catch (error) {
431
+ }
432
+ catch (error) {
465
433
  throw new Error(error);
466
434
  }
467
435
  }
468
-
469
436
  /**
470
437
  * Click the button only if it becomes clickable within the given timeout.
471
438
  * Uses a short timeout (default 1s) to avoid waiting when the button stays disabled (e.g. after validation failure).
472
439
  * Use during unmatched-braces checks: if Done/Save becomes clickable we click it; otherwise we skip and assert the error.
473
440
  */
474
- async clickIfClickable(button: WebdriverIO.Element, timeoutMs = 1000) {
475
- try {
476
- await button.waitForClickable({ timeout: timeoutMs });
477
- } catch (error) {
478
- const message = error instanceof Error ? error.message : String(error);
479
- if (/timeout|not clickable/i.test(message)) {
480
- // Button stayed disabled within the timeout; caller will assert validation error.
481
- return;
441
+ async clickIfClickable(button, timeoutMs = 1000) {
442
+ try {
443
+ await button.waitForClickable({ timeout: timeoutMs });
444
+ }
445
+ catch (error) {
446
+ const message = error instanceof Error ? error.message : String(error);
447
+ if (/timeout|not clickable/i.test(message)) {
448
+ // Button stayed disabled within the timeout; caller will assert validation error.
449
+ return;
450
+ }
451
+ throw error;
482
452
  }
483
- throw error;
484
- }
485
-
486
- // waitForClickable succeeded — click errors propagate normally.
487
- await button.click();
453
+ // waitForClickable succeeded — click errors propagate normally.
454
+ await button.click();
488
455
  }
489
-
490
456
  /**
491
457
  * Clear the input's current value then type new text.
492
458
  * Delegates the clear step to selectAllAndClear (select-all + Backspace) so
493
459
  * React-controlled inputs see the onChange event. Only calls setValue when
494
460
  * textToEnter is non-empty.
495
461
  */
496
- async clearAndEnterText(element: WebdriverIO.Element, textToEnter: string) {
462
+ async clearAndEnterText(element, textToEnter) {
497
463
  await this.selectAllAndClear(element);
498
464
  if (textToEnter !== '') {
499
465
  await element.setValue(textToEnter);
500
466
  }
501
467
  }
502
-
503
468
  /**
504
469
  * Clear text by sending Backspace key N times (N = textToRemove.length).
505
470
  * If element is provided, focus it first (click) so backspaces apply to that field.
506
471
  * Use for clearing content in inputs/editors (e.g. unmatched-braces recovery in Viber, Email CodeMirror).
507
472
  */
508
- async clearByBackspace(textToRemove: string, element?: WebdriverIO.Element) {
509
- if (element) {
510
- await element.waitForDisplayed({ timeout: 10000 });
511
- await element.click();
512
- }
513
- const count = textToRemove.length;
514
- for (let i = 0; i < count; i++) {
515
- await browser.keys(["Backspace"]);
516
- }
473
+ async clearByBackspace(textToRemove, element) {
474
+ if (element) {
475
+ await element.waitForDisplayed({ timeout: 10000 });
476
+ await element.click();
477
+ }
478
+ const count = textToRemove.length;
479
+ for (let i = 0; i < count; i++) {
480
+ await browser.keys(["Backspace"]);
481
+ }
517
482
  }
518
-
519
483
  /**
520
484
  * Wait for a spinner to appear (optional) then disappear.
521
485
  * Mirrors the pattern in deleteTemplate: catches if spinner never appears (loaded too fast),
522
486
  * then waits for it to vanish before proceeding.
523
487
  */
524
- async waitForSpinnerCycle(
525
- spinner: WebdriverIO.Element,
526
- appearTimeoutMs = 5000,
527
- disappearTimeoutMs = 180000
528
- ) {
529
- await spinner.waitForDisplayed({ timeout: appearTimeoutMs }).catch(() => {
530
- console.log("Spinner did not appear — may have loaded too fast, proceeding");
531
- });
532
- await spinner.waitForDisplayed({ timeout: disappearTimeoutMs, reverse: true });
533
- }
534
-
488
+ async waitForSpinnerCycle(spinner, appearTimeoutMs = 5000, disappearTimeoutMs = 180000) {
489
+ await spinner.waitForDisplayed({ timeout: appearTimeoutMs }).catch(() => {
490
+ console.log("Spinner did not appear — may have loaded too fast, proceeding");
491
+ });
492
+ await spinner.waitForDisplayed({ timeout: disappearTimeoutMs, reverse: true });
493
+ }
535
494
  /**
536
495
  * Best-effort loader "settle" for the blank-screen -> loader -> content
537
496
  * lifecycle. Waits (advisory) for a loader to APPEAR, then to DISAPPEAR, both
@@ -542,18 +501,14 @@ class ElementUtils {
542
501
  * hard guarantee should follow this with a positive element check (e.g.
543
502
  * waitForStableVisible on real content).
544
503
  */
545
- async waitForLoaderSettle(
546
- loader: WebdriverIO.Element,
547
- { appearTimeoutMs = 20000, disappearTimeoutMs = 180000 } = {}
548
- ) {
549
- await loader
550
- .waitForDisplayed({ timeout: appearTimeoutMs })
551
- .catch(() => console.log(`Loader did not appear within ${appearTimeoutMs}ms — proceeding`));
552
- await loader
553
- .waitForDisplayed({ timeout: disappearTimeoutMs, reverse: true })
554
- .catch(() => console.log(`Loader still present after ${disappearTimeoutMs}ms — proceeding (positive check gates)`));
555
- }
556
-
504
+ async waitForLoaderSettle(loader, { appearTimeoutMs = 20000, disappearTimeoutMs = 180000 } = {}) {
505
+ await loader
506
+ .waitForDisplayed({ timeout: appearTimeoutMs })
507
+ .catch(() => console.log(`Loader did not appear within ${appearTimeoutMs}ms — proceeding`));
508
+ await loader
509
+ .waitForDisplayed({ timeout: disappearTimeoutMs, reverse: true })
510
+ .catch(() => console.log(`Loader still present after ${disappearTimeoutMs}ms — proceeding (positive check gates)`));
511
+ }
557
512
  /**
558
513
  * Reusable try-catch handler function for waiting and verifying elements
559
514
  */
@@ -561,66 +516,50 @@ class ElementUtils {
561
516
  try {
562
517
  await element.waitForDisplayed({ timeout });
563
518
  console.log(`${label} found in search results:`, name);
564
- } catch (error) {
519
+ }
520
+ catch (error) {
565
521
  console.error(`${label} not found in search results:`, name);
566
522
  throw new Error(`${label} "${name}" not found in search results`);
567
523
  }
568
524
  }
569
-
570
525
  /**
571
526
  * Counts visible elements in a `$$` collection and asserts it equals `expected`.
572
527
  * antd v6 keeps duplicate hidden DOM nodes around, so plain `.length` isn't reliable.
573
528
  */
574
- async assertVisibleCount(
575
- elements: WebdriverIO.ElementArray,
576
- expected: number,
577
- label = "elements"
578
- ) {
579
- let visibleCount = 0;
580
- for (const el of elements) {
581
- if (await el.isDisplayed().catch(() => false)) visibleCount++;
582
- }
583
- assert.equal(
584
- visibleCount,
585
- expected,
586
- `Expected ${expected} visible ${label} but found ${visibleCount}`
587
- );
588
- console.log(`🟢 Verified visible ${label} count = ${visibleCount}`);
589
- }
590
-
529
+ async assertVisibleCount(elements, expected, label = "elements") {
530
+ let visibleCount = 0;
531
+ for (const el of elements) {
532
+ if (await el.isDisplayed().catch(() => false))
533
+ visibleCount++;
534
+ }
535
+ assert_1.strict.equal(visibleCount, expected, `Expected ${expected} visible ${label} but found ${visibleCount}`);
536
+ console.log(`🟢 Verified visible ${label} count = ${visibleCount}`);
537
+ }
591
538
  /**
592
539
  * Polls `$$(selector)` until one match is displayed, skipping hidden duplicates
593
540
  * (e.g. antd keeps inactive tab panes mounted in the DOM).
594
541
  */
595
- async firstVisibleElement(selector: string, timeout = 90000): Promise<WebdriverIO.Element> {
596
- let found: WebdriverIO.Element | undefined;
597
- await browser.waitUntil(
598
- async () => {
599
- const elements = await $$(selector);
600
- for (const element of elements) {
601
- if (await element.isDisplayed()) {
602
- found = element;
603
- return true;
604
- }
542
+ async firstVisibleElement(selector, timeout = 90000) {
543
+ let found;
544
+ await browser.waitUntil(async () => {
545
+ const elements = await $$(selector);
546
+ for (const element of elements) {
547
+ if (await element.isDisplayed()) {
548
+ found = element;
549
+ return true;
605
550
  }
606
- return false;
607
- },
608
- { timeout, timeoutMsg: `No visible element matched selector: ${selector}` }
609
- );
610
- return found as WebdriverIO.Element;
551
+ }
552
+ return false;
553
+ }, { timeout, timeoutMsg: `No visible element matched selector: ${selector}` });
554
+ return found;
611
555
  }
612
-
613
556
  /**
614
557
  * Races candidates and confirms the winner is stably displayed.
615
558
  * After a candidate wins the race, waits stableMs then re-checks.
616
559
  * If the winner disappeared, retries the full race with remaining timeout.
617
560
  * Only throws when the total timeout is exhausted.
618
561
  */
619
- async waitForStableVisible(
620
- candidates: WebdriverIO.Element[],
621
- timeout: number,
622
- stableMs: number = 5000,
623
- ): Promise<WebdriverIO.Element> {
562
+ async waitForStableVisible(candidates, timeout, stableMs = 5000) {
624
563
  const deadline = Date.now() + timeout;
625
564
  while (true) {
626
565
  const remaining = deadline - Date.now();
@@ -631,14 +570,14 @@ class ElementUtils {
631
570
  for (const el of candidates) {
632
571
  if (await el.isDisplayed()) {
633
572
  await browser.pause(stableMs);
634
- if (await el.isDisplayed()) return el;
573
+ if (await el.isDisplayed())
574
+ return el;
635
575
  console.log(`waitForStableVisible: candidate "${el.selector}" disappeared — retrying race`);
636
576
  break;
637
577
  }
638
578
  }
639
579
  }
640
580
  }
641
-
642
581
  /**
643
582
  * Retries an async step until it completes without throwing, up to `maxAttempts`
644
583
  * times (default 3). The flow only moves ahead once the step is acknowledged
@@ -649,24 +588,17 @@ class ElementUtils {
649
588
  * (e.g. refresh / re-trigger a search) before the next attempt. The last error is
650
589
  * only re-thrown after every attempt is exhausted.
651
590
  */
652
- async retryStep<T>(
653
- action: () => Promise<T>,
654
- options: {
655
- maxAttempts?: number;
656
- delayMs?: number;
657
- label?: string;
658
- beforeRetry?: (attempt: number) => Promise<void>;
659
- } = {}
660
- ): Promise<T> {
591
+ async retryStep(action, options = {}) {
661
592
  const { maxAttempts = 3, delayMs = 3000, label = "step", beforeRetry } = options;
662
- let lastError: unknown;
593
+ let lastError;
663
594
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
664
595
  try {
665
596
  console.log(`[retry] Attempt ${attempt}/${maxAttempts} for: '${label}'`);
666
597
  const result = await action();
667
598
  console.log(`[retry] '${label}' acknowledged on attempt ${attempt}/${maxAttempts}`);
668
599
  return result;
669
- } catch (error) {
600
+ }
601
+ catch (error) {
670
602
  lastError = error;
671
603
  console.log(`[retry] '${label}' failed on attempt ${attempt}/${maxAttempts}: ${error}`);
672
604
  if (attempt < maxAttempts) {
@@ -679,7 +611,6 @@ class ElementUtils {
679
611
  }
680
612
  throw new Error(`[retry] '${label}' failed after ${maxAttempts} attempts. Last error: ${lastError}`);
681
613
  }
682
-
683
614
  /**
684
615
  * Guards a step's precondition: the element that the PREVIOUS step is expected to
685
616
  * have produced must be present before the current step runs. If it is not displayed
@@ -688,36 +619,34 @@ class ElementUtils {
688
619
  * its result. Returns true when the precondition holds (use the return value inside
689
620
  * a retry's beforeRetry hook to decide whether re-confirmation is needed).
690
621
  */
691
- async ensurePrecondition(element: WebdriverIO.Element, label: string, timeout = 15000): Promise<boolean> {
622
+ async ensurePrecondition(element, label, timeout = 15000) {
692
623
  try {
693
624
  await element.waitForDisplayed({ timeout, interval: 500 });
694
625
  console.log(`[precondition] OK — previous step output present: '${label}'`);
695
626
  return true;
696
- } catch (error) {
697
- throw new Error(
698
- `[precondition] FAILED — expected output from the previous step is not present: '${label}'. ` +
699
- `The current step will not run. Underlying: ${error}`
700
- );
627
+ }
628
+ catch (error) {
629
+ throw new Error(`[precondition] FAILED — expected output from the previous step is not present: '${label}'. ` +
630
+ `The current step will not run. Underlying: ${error}`);
701
631
  }
702
632
  }
703
-
704
633
  /**
705
634
  * Selects all text in a React controlled input and clears it via Backspace,
706
635
  * triggering React's onChange. Use instead of clearValue() for React inputs.
707
636
  */
708
- async selectAllAndClear(element: WebdriverIO.Element) {
637
+ async selectAllAndClear(element) {
709
638
  await element.waitForEnabled({ timeout: 40000, interval: 1000 });
710
- await browser.execute((el: any) => el.select(), element);
639
+ await browser.execute((el) => el.select(), element);
711
640
  await browser.keys("Backspace");
712
641
  }
713
-
714
- async clearByKeysAndType(element: WebdriverIO.Element, textToEnter: string) {
642
+ async clearByKeysAndType(element, textToEnter) {
715
643
  await element.waitForDisplayed({ timeout: 20000 });
716
644
  await element.waitForEnabled({ timeout: 40000, interval: 1000 });
717
645
  await element.click();
718
646
  for (let attempt = 0; attempt < 10; attempt++) {
719
647
  const current = await element.getValue();
720
- if (!current) break;
648
+ if (!current)
649
+ break;
721
650
  await browser.keys("End");
722
651
  await browser.keys(new Array(current.length + 2).fill("Backspace"));
723
652
  }
@@ -725,7 +654,6 @@ class ElementUtils {
725
654
  await element.addValue(textToEnter);
726
655
  }
727
656
  }
728
-
729
657
  /**
730
658
  * Moves the cursor to the absolute end of the currently focused contenteditable element
731
659
  * (e.g. TinyMCE inline editor) using the DOM Selection API.
@@ -738,20 +666,20 @@ class ElementUtils {
738
666
  * Runs in the current WebDriver frame context — call from inside the BeFree iframe
739
667
  * after switchToIframe() so document refers to the iframe document.
740
668
  */
741
- async moveCursorToEndOfContentEditable(): Promise<void> {
669
+ async moveCursorToEndOfContentEditable() {
742
670
  await browser.execute(() => {
743
- const el = document.activeElement as HTMLElement;
744
- if (!el || !el.isContentEditable) return;
671
+ const el = document.activeElement;
672
+ if (!el || !el.isContentEditable)
673
+ return;
745
674
  const range = document.createRange();
746
675
  const sel = window.getSelection();
747
- if (!sel) return;
676
+ if (!sel)
677
+ return;
748
678
  range.selectNodeContents(el);
749
679
  range.collapse(false); // collapse to end
750
680
  sel.removeAllRanges();
751
681
  sel.addRange(range);
752
682
  });
753
683
  }
754
-
755
684
  }
756
-
757
- export default new ElementUtils()
685
+ exports.default = new ElementUtils();