@anddone/coretestautomation 1.0.1

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 (72) hide show
  1. package/.github/workflows/npm-release.yml +102 -0
  2. package/dist/api/base.api.d.ts +32 -0
  3. package/dist/api/base.api.d.ts.map +1 -0
  4. package/dist/api/base.api.js +7 -0
  5. package/dist/api/base.api.js.map +1 -0
  6. package/dist/api/headers.d.ts +6 -0
  7. package/dist/api/headers.d.ts.map +1 -0
  8. package/dist/api/headers.js +23 -0
  9. package/dist/api/headers.js.map +1 -0
  10. package/dist/index.d.ts +13 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +29 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/pages/basepage.d.ts +6 -0
  15. package/dist/pages/basepage.d.ts.map +1 -0
  16. package/dist/pages/basepage.js +10 -0
  17. package/dist/pages/basepage.js.map +1 -0
  18. package/dist/testData/api.data.json +6 -0
  19. package/dist/utils/apiUtils.d.ts +123 -0
  20. package/dist/utils/apiUtils.d.ts.map +1 -0
  21. package/dist/utils/apiUtils.js +264 -0
  22. package/dist/utils/apiUtils.js.map +1 -0
  23. package/dist/utils/assertionUtils.d.ts +223 -0
  24. package/dist/utils/assertionUtils.d.ts.map +1 -0
  25. package/dist/utils/assertionUtils.js +400 -0
  26. package/dist/utils/assertionUtils.js.map +1 -0
  27. package/dist/utils/commonUtils.d.ts +590 -0
  28. package/dist/utils/commonUtils.d.ts.map +1 -0
  29. package/dist/utils/commonUtils.js +1292 -0
  30. package/dist/utils/commonUtils.js.map +1 -0
  31. package/dist/utils/fakerStaticData.d.ts +16 -0
  32. package/dist/utils/fakerStaticData.d.ts.map +1 -0
  33. package/dist/utils/fakerStaticData.js +88 -0
  34. package/dist/utils/fakerStaticData.js.map +1 -0
  35. package/dist/utils/fileCommonUtils.d.ts +22 -0
  36. package/dist/utils/fileCommonUtils.d.ts.map +1 -0
  37. package/dist/utils/fileCommonUtils.js +243 -0
  38. package/dist/utils/fileCommonUtils.js.map +1 -0
  39. package/dist/utils/generationUtils.d.ts +424 -0
  40. package/dist/utils/generationUtils.d.ts.map +1 -0
  41. package/dist/utils/generationUtils.js +869 -0
  42. package/dist/utils/generationUtils.js.map +1 -0
  43. package/dist/utils/pageUtils.d.ts +90 -0
  44. package/dist/utils/pageUtils.d.ts.map +1 -0
  45. package/dist/utils/pageUtils.js +214 -0
  46. package/dist/utils/pageUtils.js.map +1 -0
  47. package/dist/utils/tableUtils.d.ts +304 -0
  48. package/dist/utils/tableUtils.d.ts.map +1 -0
  49. package/dist/utils/tableUtils.js +555 -0
  50. package/dist/utils/tableUtils.js.map +1 -0
  51. package/dist/utils/validationUtils.d.ts +80 -0
  52. package/dist/utils/validationUtils.d.ts.map +1 -0
  53. package/dist/utils/validationUtils.js +172 -0
  54. package/dist/utils/validationUtils.js.map +1 -0
  55. package/package.json +23 -0
  56. package/playwright.config.ts +79 -0
  57. package/src/api/base.api.ts +39 -0
  58. package/src/api/headers.ts +17 -0
  59. package/src/index.ts +12 -0
  60. package/src/pages/basepage.ts +11 -0
  61. package/src/testData/api.data.json +6 -0
  62. package/src/types/pdf-parse.d.ts +6 -0
  63. package/src/utils/apiUtils.ts +307 -0
  64. package/src/utils/assertionUtils.ts +455 -0
  65. package/src/utils/commonUtils.ts +1544 -0
  66. package/src/utils/fakerStaticData.ts +91 -0
  67. package/src/utils/fileCommonUtils.ts +239 -0
  68. package/src/utils/generationUtils.ts +929 -0
  69. package/src/utils/pageUtils.ts +224 -0
  70. package/src/utils/tableUtils.ts +715 -0
  71. package/src/utils/validationUtils.ts +179 -0
  72. package/tsconfig.json +19 -0
@@ -0,0 +1,1544 @@
1
+ import type { Locator } from '@playwright/test';
2
+
3
+ export type ActionOptions = {
4
+ timeout?: number;
5
+ description?: string;
6
+ lastButtonLocator?: string;
7
+ activePageLocator?: string;
8
+ nextButtonLocator?: string;
9
+ prevButtonLocator?: string;
10
+ firstButtonLocator?: string;
11
+ pageButtonLocator?: string;
12
+ };
13
+
14
+ export class CommonUtils {
15
+ private static isHighlight = false;
16
+
17
+ private static resolveOptions(options?: ActionOptions) {
18
+ return {
19
+ timeout: options?.timeout ?? 15000,
20
+ description: options?.description ?? 'Element',
21
+ };
22
+ }
23
+ /**
24
+ * Highlights a UI element on the page for debugging and visual validation.
25
+ */
26
+ public static async highlight(locator: Locator) {
27
+ if (!this.isHighlight) return;
28
+ try {
29
+ await locator.highlight();
30
+ } catch {
31
+ console.warn('⚠ highlight skipped: page/locator not ready');
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Waits for a Playwright locator to become visible on the page.
37
+ *
38
+ * @param locator - Playwright Locator to wait for
39
+ * @param options - Optional configuration object
40
+ * @param options.timeout - Maximum wait time in milliseconds (default: 15000)
41
+ * @param options.description - Logical name of the element for logging purposes
42
+ *
43
+ * @example
44
+ * await CommonUtils.waitForVisible(loginButton);
45
+ *
46
+ * @example
47
+ * await CommonUtils.waitForVisible(loginButton, {timeout: 5000,description: 'Login Button'});
48
+ */
49
+ static async waitForVisible(locator: Locator, options: ActionOptions = {}
50
+ ): Promise<void> {
51
+ const { timeout, description } = this.resolveOptions(options);
52
+ try {
53
+ await locator.waitFor({ state: 'visible', timeout });
54
+ } catch {
55
+ console.error(`❌ ${description} not visible after ${timeout}ms`);
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Waits for a Playwright locator to become hidden or removed from the DOM.
61
+ *
62
+ * @param locator - Playwright Locator to wait for
63
+ * @param options - Optional configuration object
64
+ * @param options.timeout - Maximum wait time in milliseconds (default: 15000)
65
+ * @param options.description - Logical name of the element for logging purposes
66
+ *
67
+ * @example
68
+ * await CommonUtils.waitForInvisible(loginButton);
69
+ *
70
+ * @example
71
+ * await CommonUtils.waitForInvisible(loginButton, {timeout: 5000,description: 'Login Button'});
72
+ */
73
+ static async waitForInvisible(locator: Locator, options: ActionOptions = {}
74
+ ): Promise<void> {
75
+ const { timeout, description } = this.resolveOptions(options);
76
+ try {
77
+ await locator.waitFor({ state: 'hidden', timeout });
78
+ } catch {
79
+ console.error(`❌ ${description} Element still visible after ${timeout}ms`);
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Pauses execution for a specified number of seconds.
85
+ *
86
+ * @param seconds - Number of seconds to wait
87
+ *
88
+ * @example
89
+ * await CommonUtils.waitSeconds(3);
90
+ *
91
+ * @returns Promise<void>
92
+ */
93
+ static async waitSeconds(seconds: number): Promise<void> {
94
+ try {
95
+ await new Promise(resolve => setTimeout(resolve, seconds * 1000));
96
+ } catch (error) {
97
+ console.warn(`⚠ Error during wait:`, (error as Error).message);
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Clicks on a web element after waiting for it to become visible.
103
+ *
104
+ * If the element is not found or the click fails, the error is logged
105
+ *
106
+ * @param locator - Playwright Locator to be clicked
107
+ * @param options - Optional configuration object
108
+ * @param options.timeout - Maximum wait time for visibility and click (default: 15000 ms)
109
+ * @param options.description - Logical name of the element for logging purposes
110
+ *
111
+ * @example
112
+ * await CommonUtils.click(loginButton);
113
+ *
114
+ * @example
115
+ * await CommonUtils.click(loginButton, {timeout: 5000,description: 'Login Button'});
116
+ */
117
+ static async click(locator: Locator, options: ActionOptions = {}
118
+ ): Promise<void> {
119
+ const { timeout, description } = this.resolveOptions(options);
120
+ try {
121
+ await this.waitForVisible(locator, options);
122
+ await this.highlight(locator);
123
+ await locator.click({ timeout });
124
+ } catch (error) {
125
+ console.warn(`⚠ Click skipped on ${description}: ${(error as Error).message}`);
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Fills text into an input field after ensuring it is visible.
131
+ *
132
+ * @param locator - Playwright Locator of the input element
133
+ * @param text - Text value to be entered into the field
134
+ * @param options - Optional configuration object
135
+ * @param options.timeout - Maximum wait time for visibility (default: 15000 ms)
136
+ * @param options.description - Logical name of the element for logging purposes
137
+ *
138
+ * @example
139
+ * await CommonUtils.fill(usernameInput, 'admin');
140
+ *
141
+ * @example
142
+ * await CommonUtils.fill(usernameInput, 'admin', {timeout: 5000,description: 'Username Input'
143
+ * });
144
+ */
145
+ static async fill(locator: Locator, text: string, options: ActionOptions = {}
146
+ ): Promise<void> {
147
+ const { timeout, description } = this.resolveOptions(options);
148
+ try {
149
+ await this.waitForVisible(locator, options);
150
+ await this.highlight(locator);
151
+ await locator.fill(text, { timeout });
152
+ } catch (error) {
153
+ console.warn(
154
+ `⚠ Fill skipped on ${description}. Reason: ${(error as Error).message}`
155
+ );
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Types text into an input or editable element using keyboard events.
161
+ *
162
+ * @param locator - Playwright Locator of the input or editable element
163
+ * @param text - Text to be typed into the element
164
+ * @param options - Optional configuration object
165
+ * @param options.timeout - Maximum wait time for visibility (default: 15000 ms)
166
+ * @param options.description - Logical name of the element for logging purposes
167
+ *
168
+ * @example
169
+ * await CommonUtils.typeText(usernameInput, 'admin');
170
+ *
171
+ * @example
172
+ * await CommonUtils.typeText(passwordInput, 'password123', {
173
+ * timeout: 5000,
174
+ * description: 'Password Input'
175
+ * });
176
+ */
177
+ static async typeText(locator: Locator, text: string, options: ActionOptions = {}
178
+ ): Promise<void> {
179
+ const { timeout, description } = this.resolveOptions(options);
180
+ try {
181
+ await this.waitForVisible(locator, options);
182
+ await this.highlight(locator);
183
+ await locator.type(text, { delay: 50, timeout });
184
+ } catch (error) {
185
+ console.warn(
186
+ `⚠ Type skipped on ${description}. Reason: ${(error as Error).message}`
187
+ );
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Checks whether a locator is visible within the given timeout.
193
+ *
194
+ * @param locator - Locator of the element to check
195
+ * @param options - Optional configuration (timeout in ms, description)
196
+ * @returns True if the element becomes visible, otherwise false
197
+ *
198
+ * @example
199
+ * const visible = await CommonUtils.isVisible(page.locator('#loginButton'));
200
+ * const visibleWithDesc = await CommonUtils.isVisible(page.locator('#loginButton'), {timeout:5000, description:'Login Button'});
201
+ */
202
+ static async isVisible(locator: Locator, options: ActionOptions = {}): Promise<boolean> {
203
+ const { timeout, description } = this.resolveOptions(options);
204
+ try {
205
+ await this.waitForVisible(locator, options);
206
+ await this.highlight(locator);
207
+ return await locator.isVisible();
208
+ } catch {
209
+ console.warn(`⚠ ${description} is not visible`);
210
+ return false;
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Checks whether a locator is enabled (interactable).
216
+ *
217
+ * @param locator - Locator of the element to check
218
+ * @param options - Optional configuration (timeout in ms, description)
219
+ * @returns True if the element is enabled, otherwise false
220
+ *
221
+ * @example
222
+ * const enabled = await CommonUtils.isEnabled(page.locator('#submitButton'));
223
+ */
224
+ static async isEnabled(locator: Locator, options: ActionOptions = {}): Promise<boolean> {
225
+ const { timeout, description } = this.resolveOptions(options);
226
+ try {
227
+ await this.waitForVisible(locator, options);
228
+ await this.highlight(locator);
229
+ return await locator.isEnabled();
230
+ } catch {
231
+ console.warn(`⚠ ${description} is not enabled`);
232
+ return false;
233
+ }
234
+ }
235
+
236
+ /**
237
+ * Checks whether a locator is disabled.
238
+ *
239
+ * @param locator - Locator of the element to check
240
+ * @param options - Optional configuration (timeout in ms, description)
241
+ * @returns True if the element is disabled or not interactable, otherwise false
242
+ *
243
+ * @example
244
+ * const disabled = await CommonUtils.isDisabled(page.locator('#cancelButton'));
245
+ */
246
+ static async isDisabled(locator: Locator, options: ActionOptions = {}): Promise<boolean> {
247
+ const { timeout, description } = this.resolveOptions(options);
248
+ try {
249
+ await this.waitForVisible(locator, options);
250
+ await this.highlight(locator);
251
+ return await locator.isDisabled();
252
+ } catch {
253
+ console.warn(`⚠ ${description} is not disabled`);
254
+ return true;
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Checks whether a locator is editable.
260
+ *
261
+ * @param locator - Locator of the element to check
262
+ * @param options - Optional configuration (timeout in ms, description)
263
+ * @returns True if the element is editable, otherwise false
264
+ *
265
+ * @example
266
+ * const editable = await CommonUtils.isEditable(page.locator('#nameInput'));
267
+ */
268
+ static async isEditable(locator: Locator, options: ActionOptions = {}): Promise<boolean> {
269
+ const { timeout, description } = this.resolveOptions(options);
270
+ try {
271
+ await this.waitForVisible(locator, options);
272
+ await this.highlight(locator);
273
+ return await locator.isEditable();
274
+ } catch {
275
+ console.warn(`⚠ ${description} is not editable`);
276
+ return false;
277
+ }
278
+ }
279
+
280
+ /**
281
+ * Checks whether a checkbox or radio button is checked.
282
+ *
283
+ * @param locator - Locator of the checkbox or radio element
284
+ * @param options - Optional configuration (timeout in ms, description)
285
+ * @returns True if the element is checked, otherwise false
286
+ *
287
+ * @example
288
+ * const checked = await CommonUtils.isChecked(page.locator('#acceptTerms'));
289
+ */
290
+ static async isChecked(locator: Locator, options: ActionOptions = {}): Promise<boolean> {
291
+ const { timeout, description } = this.resolveOptions(options);
292
+ try {
293
+ await this.waitForVisible(locator, options);
294
+ await this.highlight(locator);
295
+ return await locator.isChecked();
296
+ } catch {
297
+ console.warn(`⚠ ${description} is not checked`);
298
+ return false;
299
+ }
300
+ }
301
+
302
+ /**
303
+ * Checks whether a locator is hidden within the given timeout.
304
+ *
305
+ * @param locator - Locator of the element to check
306
+ * @param options - Optional configuration (timeout in ms, description)
307
+ * @returns True if the element is hidden or not visible, otherwise false
308
+ *
309
+ * @example
310
+ * const hidden = await CommonUtils.isHidden(page.locator('#loadingSpinner'));
311
+ */
312
+ static async isHidden(locator: Locator, options: ActionOptions = {}): Promise<boolean> {
313
+ const { description } = this.resolveOptions(options);
314
+ try {
315
+ await this.waitForInvisible(locator, options);
316
+ return true;
317
+ } catch {
318
+ console.warn(`⚠ ${description} is not hidden`);
319
+ return false;
320
+ }
321
+ }
322
+
323
+ /**
324
+ * Clears the value of an input or editable element.
325
+ *
326
+ * @param locator - Locator of the element to clear
327
+ * @param options - Optional configuration (timeout in ms, description)
328
+ *
329
+ * @example
330
+ * await CommonUtils.clear(page.locator('#searchInput'));
331
+ * await CommonUtils.clear(page.locator('#searchInput'), {timeout: 5000, description: 'Search Input'});
332
+ */
333
+ static async clear(locator: Locator, options: ActionOptions = {}): Promise<void> {
334
+ const { description } = this.resolveOptions(options);
335
+ try {
336
+ await this.waitForVisible(locator, options);
337
+ await this.highlight(locator);
338
+ await locator.clear();
339
+ } catch (error) {
340
+ console.warn(`⚠ Unable to clear ${description}: ${(error as Error).message}`);
341
+ }
342
+ }
343
+
344
+ /**
345
+ * Retrieves the value of a specified attribute from an element.
346
+ *
347
+ * @param locator - Locator of the element
348
+ * @param attributeName - Name of the attribute to retrieve
349
+ * @param options - Optional configuration (timeout in ms, description)
350
+ * @returns Attribute value or null if not found
351
+ */
352
+ static async getAttributeValue(locator: Locator, attributeName: string,
353
+ options: ActionOptions = {}): Promise<string | null> {
354
+ const { timeout, description } = this.resolveOptions(options);
355
+ try {
356
+ await locator.waitFor({ state: 'attached', timeout });
357
+ await this.highlight(locator);
358
+ return await locator.getAttribute(attributeName);
359
+ } catch (error) {
360
+ console.warn(
361
+ `⚠ Unable to get attribute "${attributeName}" from ${description}`,
362
+ (error as Error).message
363
+ );
364
+ return null;
365
+ }
366
+ }
367
+
368
+ /**
369
+ * Checks whether an element's attribute value contains the expected text.
370
+ *
371
+ * @param locator - Locator of the element
372
+ * @param attributeName - Attribute to inspect
373
+ * @param expectedValue - Text expected to be contained in the attribute
374
+ * @param options - Optional configuration (timeout in ms, description)
375
+ * @returns True if attribute contains the expected value
376
+ */
377
+ static async isAttributeValueContains(locator: Locator, attributeName: string,
378
+ expectedValue: string, options: ActionOptions = {}
379
+ ): Promise<boolean> {
380
+ const { timeout, description } = this.resolveOptions(options);
381
+ try {
382
+ await locator.waitFor({ state: 'attached', timeout });
383
+ await this.highlight(locator);
384
+
385
+ const attributeValue = await this.getAttributeValue(locator, attributeName,
386
+ options);
387
+
388
+ return attributeValue?.includes(expectedValue) ?? false;
389
+ } catch {
390
+ console.warn(
391
+ `⚠ Attribute "${attributeName}" of ${description} does not contain "${expectedValue}"`
392
+ );
393
+ return false;
394
+ }
395
+ }
396
+
397
+ /**
398
+ * Retrieves visible text from a Playwright locator.
399
+ *
400
+ * @param locator - Playwright Locator from which text is retrieved
401
+ * @param options - Optional configuration object
402
+ * @param options.timeout - Maximum wait time for visibility (default: 15000 ms)
403
+ * @param options.description - Logical name of the element for logging
404
+ *
405
+ * @returns The extracted text, or an empty string if unavailable
406
+ *
407
+ * @example
408
+ * const header = await CommonUtils.getText(pageHeader);
409
+ *
410
+ * @example
411
+ * const errorMsg = await CommonUtils.getText(errorLabel, {
412
+ * timeout: 5000,
413
+ * description: 'Error Message'
414
+ * });
415
+ */
416
+ static async getText(locator: Locator, options: ActionOptions = {}
417
+ ): Promise<string> {
418
+ const { description } = this.resolveOptions(options);
419
+ try {
420
+ await this.waitForVisible(locator, options);
421
+ await this.highlight(locator);
422
+ const text = await locator.innerText();
423
+ return text.trim();
424
+ } catch (error) {
425
+ console.warn(
426
+ `⚠ Unable to get text from ${description}. Reason: ${(error as Error).message}`
427
+ );
428
+ return '';
429
+ }
430
+ }
431
+
432
+
433
+ /**
434
+ * Retrieves the visible innerText of an element.
435
+ *
436
+ * @param locator - Locator of the target element
437
+ * @param options - Optional action settings
438
+ * @param options.timeout - Maximum time to wait for the element to become visible (default: 1500)
439
+ * @param options.description - Description of the element used for logging
440
+ *
441
+ * @example
442
+ * const text = await getInnerText(title);
443
+ *
444
+ * @example
445
+ * const text = await getInnerText(title, {description: 'Page title',timeout: 3000
446
+ * });
447
+ *
448
+ * @returns Trimmed innerText, or empty string if unavailable
449
+ */
450
+ static async getInnerText(locator: Locator, options: ActionOptions = {}
451
+ ): Promise<string> {
452
+ const { description } = this.resolveOptions(options);
453
+ try {
454
+ const target = locator.first();
455
+ this.waitForVisible(target, options);
456
+ await this.highlight(target);
457
+
458
+ return (await target.innerText()).trim();
459
+ } catch (error) {
460
+ console.warn(`⚠ Unable to get innerText from ${description}`,
461
+ (error as Error).message
462
+ );
463
+ return '';
464
+ }
465
+ }
466
+
467
+ /**
468
+ * Gets trimmed innerText values from a list of elements.
469
+ *
470
+ * @param locatorList - Locator representing multiple elements
471
+ * @param options - Optional action settings
472
+ * @param options.timeout - Maximum wait time for at least one element to become visible (default: 1500)
473
+ * @param options.description - Description of the element list used for logging
474
+ *
475
+ * @example
476
+ * const texts = await CommonUtils.getAllInnerText(listItems);
477
+ *
478
+ * @example
479
+ * const texts = await CommonUtils.getAllInnerText(listItems, {
480
+ * description: 'Fruit list items',
481
+ * timeout: 5000
482
+ * });
483
+ *
484
+ * @returns Array of trimmed innerText values, or empty array if none found
485
+ */
486
+ static async getAllInnerText(locatorList: Locator, options: ActionOptions = {}
487
+ ): Promise<string[]> {
488
+ const { description = 'Element list' } = options;
489
+ try {
490
+ const first = locatorList.first();
491
+ this.waitForVisible(first, options);
492
+ await this.highlight(first);
493
+
494
+ const texts = await locatorList.allInnerTexts();
495
+ return texts.map(t => t.trim());
496
+ } catch (error) {
497
+ console.warn(`⚠ Unable to get innerText values from ${description}:`, (error as Error).message);
498
+ return [];
499
+ }
500
+ }
501
+
502
+ /**
503
+ * Gets trimmed textContent values from a list of elements.
504
+ *
505
+ * @param locatorList - Locator representing multiple elements
506
+ * @param options - Optional action settings
507
+ * @param options.timeout - Maximum wait time for at least one element to be attached (default: 1500)
508
+ * @param options.description - Description of the element list used for logging
509
+ *
510
+ * @example
511
+ * const texts = await CommonUtils.getAllText(listItems);
512
+ *
513
+ * @example
514
+ * const texts = await CommonUtils.getAllText(listItems, {
515
+ * description: 'Fruit list items',
516
+ * timeout: 5000
517
+ * });
518
+ *
519
+ * @returns Array of trimmed textContent values, or empty array if none found
520
+ */
521
+ static async getAllText(locatorList: Locator, options: ActionOptions = {}
522
+ ): Promise<string[]> {
523
+ const { description = 'Element list' } = options;
524
+
525
+ try {
526
+ const first = locatorList.first();
527
+ this.waitForVisible(first, options);
528
+ await this.highlight(first);
529
+
530
+ const texts = await locatorList.allTextContents();
531
+ return texts.map(t => t.trim());
532
+ } catch (error) {
533
+ console.warn(`⚠ Unable to get textContent values from ${description}:`, (error as Error).message);
534
+ return [];
535
+ }
536
+ }
537
+
538
+ /**
539
+ * Fetches the trimmed text of a specific child tag inside a locator.
540
+ *
541
+ * @param locator - Locator of the parent element
542
+ * @param tagName - Tag name of the child element to fetch text from (e.g., 'span', 'p')
543
+ * @param options - Optional action settings
544
+ * @param options.timeout - Maximum time to wait for the element (default: 15000)
545
+ * @param options.description - Description of the element used for logging
546
+ *
547
+ * @example
548
+ * const spanText = await CommonUtils.getTextByTag(parentDiv, 'span');
549
+ *
550
+ * @example
551
+ * const spanText = await CommonUtils.getTextByTag(parentDiv, 'span', {
552
+ * description: 'Amount label',
553
+ * timeout: 5000
554
+ * });
555
+ *
556
+ * @returns Trimmed text of the child element, or empty string if not found
557
+ */
558
+ static async getTextByTag(locator: Locator, tagName: string,
559
+ options: ActionOptions = {}
560
+ ): Promise<string> {
561
+ const { timeout, description } = this.resolveOptions(options);
562
+
563
+ try {
564
+ await this.waitForVisible(locator, options);
565
+ await this.highlight(locator);
566
+
567
+ const child = locator.locator(tagName).first();
568
+ await child.waitFor({ state: 'visible', timeout });
569
+
570
+ const text = await child.innerText();
571
+ return text.trim();
572
+ } catch (error) {
573
+ console.warn(`⚠ Unable to get text from <${tagName}> of ${description}: ${(error as Error).message}`);
574
+ return '';
575
+ }
576
+ }
577
+
578
+ /**
579
+ * Finds the index of the first element whose innerText exactly matches the expected value.
580
+ *
581
+ * @param locatorList - Locator representing a list of elements
582
+ * @param expected - Exact text to match against each element's innerText
583
+ * @param options - Optional action settings
584
+ * @param options.timeout - Maximum time to wait for at least one element to become visible (default: 1500)
585
+ * @param options.description - Description of the element list used for logging
586
+ *
587
+ * @example
588
+ * const index = await getIndexOfExpected(listItems, 'Apple');
589
+ *
590
+ * @example
591
+ * const index = await getIndexOfExpected(listItems,'Apple',
592
+ * { description: 'Fruit list items', timeout: 5000 }
593
+ * );
594
+ *
595
+ * @returns Zero-based index of the matched element, or -1 if not found
596
+ */
597
+ static async getIndexOfExpected(locatorList: Locator, expected: string,
598
+ options: ActionOptions = {}
599
+ ): Promise<number> {
600
+
601
+ const { timeout, description } = this.resolveOptions(options);
602
+
603
+ try {
604
+ await locatorList.first().waitFor({ state: 'visible', timeout });
605
+
606
+ const count = await locatorList.count();
607
+
608
+ for (let i = 0; i < count; i++) {
609
+ const item = locatorList.nth(i);
610
+ await this.highlight(item);
611
+
612
+ const text = await this.getInnerText(item);
613
+ if (text === expected) return i;
614
+ }
615
+
616
+ console.warn(`⚠ "${expected}" not found in ${description}`);
617
+ return -1;
618
+ } catch (error) {
619
+ console.warn(
620
+ `⚠ Unable to get index for "${expected}" in ${description}`,
621
+ (error as Error).message
622
+ );
623
+ return -1;
624
+ }
625
+ }
626
+
627
+ /**
628
+ * Checks if an element's innerText exactly matches the expected text.
629
+ *
630
+ * @param locator - Locator of the target element
631
+ * @param expected - Expected text to match exactly against the element's innerText
632
+ * @param options - Optional action settings
633
+ * @param options.timeout - Maximum time to wait for the element to become visible (default: 1500)
634
+ * @param options.description - Description of the element used for logging
635
+ *
636
+ * @example
637
+ * const isMatch = await isElementTextMatchInnerText(title, 'Welcome');
638
+ *
639
+ * @example
640
+ * const isMatch = await isElementTextMatchInnerText(title, 'Welcome', {
641
+ * description: 'Page title',
642
+ * timeout: 3000
643
+ * });
644
+ *
645
+ * @returns True if the element's innerText matches the expected text exactly, otherwise false
646
+ */
647
+ static async isElementTextMatchInnerText(locator: Locator, expected: string,
648
+ options: ActionOptions = {}
649
+ ): Promise<boolean> {
650
+ const { description } = this.resolveOptions(options);
651
+ try {
652
+ await this.waitForVisible(locator, options);
653
+ await this.highlight(locator);
654
+
655
+ const actualText = await this.getInnerText(locator);
656
+ return actualText === expected;
657
+ } catch {
658
+ console.warn(`⚠ innerText of ${description} does not match "${expected}"`);
659
+ return false;
660
+ }
661
+ }
662
+
663
+ /**
664
+ * Checks if an element's innerText contains the expected text.
665
+ *
666
+ * @param locator - Locator of the target element
667
+ * @param expected - Text expected to be contained within the element's innerText
668
+ * @param options - Optional action settings
669
+ * @param options.timeout - Maximum time to wait for the element to become visible (default: 1500)
670
+ * @param options.description - Description of the element used for logging
671
+ *
672
+ * @example
673
+ * const contains = await isElementTextContainsInnerText(message, 'Success');
674
+ *
675
+ * @example
676
+ * const contains = await isElementTextContainsInnerText(message, 'Success', {
677
+ * description: 'Toast message'
678
+ * });
679
+ *
680
+ * @returns True if the element's innerText contains the expected text, otherwise false
681
+ */
682
+ static async isElementTextContainsInnerText(locator: Locator, expected: string,
683
+ options: ActionOptions = {}
684
+ ): Promise<boolean> {
685
+ const { description } = this.resolveOptions(options);
686
+ try {
687
+ await this.waitForVisible(locator, options);
688
+ await this.highlight(locator);
689
+
690
+ const actualText = await this.getInnerText(locator);
691
+ return actualText.includes(expected);
692
+ } catch {
693
+ console.warn(`⚠ innerText of ${description} does not contain "${expected}"`);
694
+ return false;
695
+ }
696
+ }
697
+
698
+ /**
699
+ * Checks if an element's textContent exactly matches the expected text.
700
+ *
701
+ * @param locator - Locator of the target element
702
+ * @param expected - Expected text to match exactly against the element's textContent
703
+ * @param options - Optional action settings
704
+ * @param options.timeout - Maximum time to wait for the element to become visible (default: 1500)
705
+ * @param options.description - Description of the element used for logging
706
+ *
707
+ * @example
708
+ * const isExact = await isElementTextMatchText(label, 'Username');
709
+ *
710
+ * @example
711
+ * const isExact = await isElementTextMatchText(label, 'Username', {
712
+ * description: 'Username label'
713
+ * });
714
+ *
715
+ * @returns True if the element's textContent matches the expected text exactly, otherwise false
716
+ */
717
+ static async isElementTextMatchText(locator: Locator, expected: string,
718
+ options: ActionOptions = {}
719
+ ): Promise<boolean> {
720
+ const { description } = this.resolveOptions(options);
721
+ try {
722
+ await this.waitForVisible(locator, options);
723
+ await this.highlight(locator);
724
+
725
+ const actualText = await this.getText(locator);
726
+ return actualText === expected;
727
+ } catch {
728
+ console.warn(`⚠ textContent of ${description} does not match "${expected}"`);
729
+ return false;
730
+ }
731
+ }
732
+
733
+ /**
734
+ * Checks if an element's textContent contains the expected text.
735
+ *
736
+ * @param locator - Locator of the target element
737
+ * @param expected - Text expected to be contained within the element's textContent
738
+ * @param options - Optional action settings
739
+ * @param options.timeout - Maximum time to wait for the element to become visible (default: 1500)
740
+ * @param options.description - Description of the element used for logging
741
+ *
742
+ * @example
743
+ * const contains = await isElementTextContainsText(button, 'Submit');
744
+ *
745
+ * @example
746
+ * const contains = await isElementTextContainsText(button, 'Submit', {description: 'Submit button',
747
+ * timeout: 4000
748
+ * });
749
+ *
750
+ * @returns True if the element's textContent contains the expected text, otherwise false
751
+ */
752
+ static async isElementTextContainsText(locator: Locator, expected: string,
753
+ options: ActionOptions = {}
754
+ ): Promise<boolean> {
755
+
756
+ const { description } = this.resolveOptions(options);
757
+
758
+ try {
759
+ await this.waitForVisible(locator, options);
760
+ await this.highlight(locator);
761
+
762
+ const actualText = await this.getText(locator);
763
+ return typeof actualText === 'string' && actualText.includes(expected);
764
+ } catch {
765
+ console.warn(`⚠ textContent of ${description} does not contain "${expected}"`);
766
+ return false;
767
+ }
768
+ }
769
+
770
+ /**
771
+ * Checks if the given list is sorted in ascending order.
772
+ * @param list List of comparable values
773
+ * @returns true if sorted ascending, else false
774
+ */
775
+ static isAscending<T>(list: T[]): boolean {
776
+ try {
777
+ if (!list || list.length <= 1) return true;
778
+
779
+ for (let i = 0; i < list.length - 1; i++) {
780
+ if (list[i] > list[i + 1]) return false;
781
+ }
782
+ return true;
783
+ } catch (error) {
784
+ return false;
785
+ }
786
+ }
787
+
788
+ /**
789
+ * Checks if the given list is sorted in descending order.
790
+ * @param list List of comparable values
791
+ * @returns true if sorted descending, else false
792
+ */
793
+ static isDescending<T>(list: T[]): boolean {
794
+ try {
795
+ if (!list || list.length <= 1) return true;
796
+
797
+ for (let i = 0; i < list.length - 1; i++) {
798
+ if (list[i] < list[i + 1]) return false;
799
+ }
800
+ return true;
801
+ } catch (error) {
802
+ return false;
803
+ }
804
+ }
805
+
806
+
807
+ /**
808
+ * Sorts a list of values in ascending or descending order.
809
+ * @param values Values to sort
810
+ * @param order Sorting order ('asc' or 'desc')
811
+ * @returns Sorted array
812
+ */
813
+ static sortValues(values: any[], order: 'asc' | 'desc'): any[] {
814
+
815
+ const sorted = [...values].sort((a, b) => {
816
+ if (a === b) return 0;
817
+ if (a > b) return 1;
818
+ return -1;
819
+ });
820
+
821
+ return order === 'asc' ? sorted : sorted.reverse();
822
+ }
823
+ /**
824
+ * Sort date strings from Newest to Oldest using given format.
825
+ * @param dateStrings Date strings from UI
826
+ * @param format Date format (e.g. 'MMM dd, yyyy HH:mm:s')
827
+ * @returns Sorted date strings (Newest first)
828
+ */
829
+ static sortDateNewestToOldest(dateStrings: string[], format: string): string[] {
830
+ if (!dateStrings || dateStrings.length === 0) return [];
831
+
832
+ const dates = dateStrings.map(d =>
833
+ this.parseDateGeneric(d.trim(), format)
834
+ );
835
+
836
+ dates.sort((a, b) => b.getTime() - a.getTime());
837
+
838
+ return dates.map(d => this.formatDateGeneric(d, format));
839
+ }
840
+
841
+ /**
842
+ * Sort date strings from Oldest to Newest using given format.
843
+ * @param dateStrings Date strings from UI
844
+ * @param format Date format (e.g. 'MMM dd, yyyy HH:mm:s')
845
+ * @returns Sorted date strings (Oldest first)
846
+ */
847
+ static sortDateOldestToNewest(dateStrings: string[], format: string): string[] {
848
+ if (!dateStrings || dateStrings.length === 0) return [];
849
+
850
+ const dates = dateStrings.map(d =>
851
+ this.parseDateGeneric(d.trim(), format)
852
+ );
853
+
854
+ dates.sort((a, b) => a.getTime() - b.getTime());
855
+
856
+ return dates.map(d => this.formatDateGeneric(d, format));
857
+ }
858
+
859
+ /**
860
+ * Month name to month index mapping for parsing 'MMM' format.
861
+ */
862
+ static readonly MONTH_MAP: Record<string, number> = {
863
+ Jan: 0, Feb: 1, Mar: 2, Apr: 3,
864
+ May: 4, Jun: 5, Jul: 6, Aug: 7,
865
+ Sep: 8, Oct: 9, Nov: 10, Dec: 11
866
+ }
867
+
868
+ /**
869
+ * Parses a date string into a Date object using a generic format.
870
+ * Supports multiple date tokens like yyyy, MM, MMM, dd, HH, mm, ss.
871
+ * @param dateStr Date string from UI
872
+ * @param format Format of the date string
873
+ * @returns Parsed Date object
874
+ */
875
+ static parseDateGeneric(dateStr: string, format: string): Date {
876
+ const tokenRegex = /(yyyy|MMM|MM|dd|HH|mm|ss|s)/g;
877
+ const formatTokens = format.match(tokenRegex);
878
+ const dateParts = dateStr.match(/\w+/g);
879
+
880
+ if (!formatTokens || !dateParts || formatTokens.length !== dateParts.length) {
881
+ throw new Error(`Invalid date or format: ${dateStr}`);
882
+ }
883
+
884
+ let year = 1970, month = 0, day = 1;
885
+ let hour = 0, minute = 0, second = 0;
886
+
887
+ formatTokens.forEach((token, i) => {
888
+ const value = dateParts[i];
889
+ switch (token) {
890
+ case "yyyy": year = Number(value); break;
891
+ case "MM": month = Number(value) - 1; break;
892
+ case "MMM": month = this.MONTH_MAP[value]; break;
893
+ case "dd": day = Number(value); break;
894
+ case "HH": hour = Number(value); break;
895
+ case "mm": minute = Number(value); break;
896
+ case "s":
897
+ case "ss": second = Number(value); break;
898
+ }
899
+ });
900
+
901
+ return new Date(year, month, day, hour, minute, second);
902
+ }
903
+
904
+ /**
905
+ * Formats a Date object into a string using the provided format.
906
+ * @param date Date object
907
+ * @param format Desired output format
908
+ * @returns Formatted date string
909
+ */
910
+ static formatDateGeneric(date: Date, format: string): string {
911
+ const pad = (n: number) => n.toString().padStart(2, "0");
912
+
913
+ return format
914
+ .replace("yyyy", date.getFullYear().toString())
915
+ .replace("MMM", Object.keys(this.MONTH_MAP)
916
+ .find(k => this.MONTH_MAP[k] === date.getMonth())!)
917
+ .replace("MM", pad(date.getMonth() + 1))
918
+ .replace("dd", pad(date.getDate()))
919
+ .replace("HH", pad(date.getHours()))
920
+ .replace("mm", pad(date.getMinutes()))
921
+ .replace("ss", pad(date.getSeconds()))
922
+ .replace("s", date.getSeconds().toString());
923
+ }
924
+
925
+ /**
926
+ * Selects an option from a list of locators based on visible text.
927
+ *
928
+ * @param optionsList - Locator representing list of selectable options
929
+ * @param optionText - Visible text of the option to select
930
+ * @param options - Optional action settings
931
+ * @param options.timeout - Maximum wait time (default: 15000)
932
+ *
933
+ * @returns true if option is clicked successfully, otherwise false
934
+ */
935
+ static async clickOnFilterOptionFromList(optionsList: Locator, optionText: string,
936
+ options: ActionOptions = {}
937
+ ): Promise<boolean> {
938
+
939
+ const { timeout = 15000 } = this.resolveOptions(options);
940
+
941
+ try {
942
+ if (!optionText || optionText.trim() === '') {
943
+ console.error('⚠ Option text must be a non-empty string');
944
+ return false;
945
+ }
946
+
947
+ const count = await optionsList.count();
948
+
949
+ for (let i = 0; i < count; i++) {
950
+ const option = optionsList.nth(i);
951
+ const text = (await option.innerText())
952
+ .replace(/\s+/g, ' ')
953
+ .trim();
954
+ console.log("fileter options:", text);
955
+
956
+ if (text.toLowerCase() === (optionText.toLowerCase())) {
957
+ await option.scrollIntoViewIfNeeded();
958
+ await this.click(option, { timeout });
959
+ return true;
960
+ }
961
+ }
962
+
963
+ console.error(`⚠ Option "${optionText}" not found in list`);
964
+ return false;
965
+
966
+ } catch (error) {
967
+ console.error(
968
+ `⚠ Failed to select option "${optionText}": ${(error as Error).message}`
969
+ );
970
+ return false;
971
+ }
972
+ }
973
+
974
+ /**
975
+ * Selects an option from a native <select> dropdown using visible text.
976
+ *
977
+ * @param dropdown - Locator pointing to the <select> element
978
+ * @param optionText - Visible text of the option to select
979
+ * @param options - Optional configuration (timeout, description)
980
+ */
981
+ static async selectByText(dropdown: Locator, optionText: string, options: ActionOptions = {}
982
+ ): Promise<void> {
983
+ const { timeout, description } = this.resolveOptions(options);
984
+
985
+ try {
986
+ await this.waitForVisible(dropdown, options);
987
+ await this.highlight(dropdown);
988
+ await dropdown.scrollIntoViewIfNeeded();
989
+
990
+ await dropdown.selectOption(
991
+ { label: optionText },
992
+ { timeout }
993
+ );
994
+
995
+ const selected = await dropdown.inputValue();
996
+ if (!selected) {
997
+ console.warn(`⚠ Option "${optionText}" may not be selected in ${description}`);
998
+ }
999
+ } catch (error) {
1000
+ console.warn(
1001
+ `⚠ ${description}: Unable to select option "${optionText}". ${(error as Error).message}`
1002
+ );
1003
+ }
1004
+ }
1005
+
1006
+ /**
1007
+ * Selects an option from a native <select> dropdown using the option's value attribute.
1008
+ *
1009
+ * @param dropdown - Locator pointing to the <select> element
1010
+ * @param value - Value attribute of the option to select
1011
+ * @param options - Optional configuration (timeout, description)
1012
+ */
1013
+ static async selectByValue(dropdown: Locator, value: string, options: ActionOptions = {}
1014
+ ): Promise<void> {
1015
+ const { timeout, description } = this.resolveOptions(options);
1016
+
1017
+ try {
1018
+ await this.waitForVisible(dropdown, options);
1019
+ await this.highlight(dropdown);
1020
+ await dropdown.scrollIntoViewIfNeeded();
1021
+
1022
+ await dropdown.selectOption(
1023
+ { value },
1024
+ { timeout }
1025
+ );
1026
+
1027
+ const selected = await dropdown.inputValue();
1028
+ if (selected !== value) {
1029
+ console.warn(`⚠ Value "${value}" may not be selected in ${description}`);
1030
+ }
1031
+ } catch (error) {
1032
+ console.warn(
1033
+ `⚠ ${description}: Unable to select value "${value}". ${(error as Error).message}`
1034
+ );
1035
+ }
1036
+ }
1037
+ /**
1038
+ * Selects an option from a native <select> dropdown using the option index (0-based).
1039
+ *
1040
+ * @param dropdown - Locator pointing to the <select> element
1041
+ * @param index - Index of the option (0-based)
1042
+ * @param options - Optional configuration (timeout, description)
1043
+ */
1044
+ static async selectByIndex(dropdown: Locator, index: number,
1045
+ options: ActionOptions = {}
1046
+ ): Promise<void> {
1047
+ const { timeout, description } = this.resolveOptions(options);
1048
+
1049
+ try {
1050
+ await this.waitForVisible(dropdown, options);
1051
+ await this.highlight(dropdown);
1052
+ await dropdown.scrollIntoViewIfNeeded();
1053
+
1054
+ await dropdown.selectOption({ index }, { timeout }
1055
+ );
1056
+
1057
+ const selected = await dropdown.inputValue();
1058
+ if (!selected) {
1059
+ console.warn(`⚠ Option at index ${index} may not be selected in ${description}`);
1060
+ }
1061
+ } catch (error) {
1062
+ console.warn(
1063
+ `⚠ ${description}: Unable to select index ${index}. ${(error as Error).message}`
1064
+ );
1065
+ }
1066
+ }
1067
+
1068
+ /**
1069
+ * Selects an option from a custom (non-<select>) dropdown using visible text.
1070
+ *
1071
+ * @param dropdown - Locator for the dropdown trigger element
1072
+ * @param optionsList - Locator representing all dropdown option elements
1073
+ * @param value - Visible text of the option to select
1074
+ * @param options - Optional configuration (timeout, description)
1075
+ */
1076
+ static async selectFromCustomDropdown(dropdown: Locator, optionsList: Locator, value: string,
1077
+ options: ActionOptions = {}
1078
+ ): Promise<void> {
1079
+ const { timeout, description } = this.resolveOptions(options);
1080
+
1081
+ try {
1082
+ await this.waitForVisible(dropdown, options);
1083
+ await this.highlight(dropdown);
1084
+ await dropdown.click({ timeout });
1085
+
1086
+ const firstOption = optionsList.first();
1087
+ await firstOption.waitFor({ state: 'visible', timeout });
1088
+
1089
+ const count = await optionsList.count();
1090
+
1091
+ for (let i = 0; i < count; i++) {
1092
+ const option = optionsList.nth(i);
1093
+ await this.highlight(option);
1094
+
1095
+ const text = (await option.innerText()).trim();
1096
+ if (text === value) {
1097
+ await option.click({ timeout });
1098
+ return;
1099
+ }
1100
+ }
1101
+
1102
+ console.warn(`⚠ ${description}: Option "${value}" not found`);
1103
+ } catch (error) {
1104
+ console.warn(
1105
+ `⚠ ${description}: Failed selecting "${value}". ${(error as Error).message}`
1106
+ );
1107
+ }
1108
+ }
1109
+ /**
1110
+ * Retrieves all visible option texts from a dropdown or options list.
1111
+ *
1112
+ * @param optionsList - Locator pointing to dropdown option elements
1113
+ * @param options - Optional configuration (timeout, description)
1114
+ * @returns Array of trimmed option texts or empty array
1115
+ */
1116
+ static async getAllOptions(optionsList: Locator, options: ActionOptions = {}
1117
+ ): Promise<string[]> {
1118
+ const { timeout, description } = this.resolveOptions(options);
1119
+
1120
+ try {
1121
+ const first = optionsList.first();
1122
+ await first.waitFor({ state: 'visible', timeout });
1123
+ await this.highlight(first);
1124
+
1125
+ const count = await optionsList.count();
1126
+ const values: string[] = [];
1127
+
1128
+ for (let i = 0; i < count; i++) {
1129
+ const option = optionsList.nth(i);
1130
+ const text = (await option.innerText()).trim();
1131
+ values.push(text);
1132
+ }
1133
+
1134
+ return values;
1135
+ } catch (error) {
1136
+ console.warn(
1137
+ `⚠ ${description}: Unable to fetch dropdown options. ${(error as Error).message}`
1138
+ );
1139
+ return [];
1140
+ }
1141
+ }
1142
+ /**
1143
+ * Checks whether a dropdown/options list contains a specific visible option.
1144
+ *
1145
+ * @param optionsList - Locator representing dropdown option elements
1146
+ * @param value - Option text to verify
1147
+ * @param options - Optional configuration (timeout, description)
1148
+ * @returns true if option exists, otherwise false
1149
+ */
1150
+ static async hasOption(optionsList: Locator, value: string,
1151
+ options: ActionOptions = {}
1152
+ ): Promise<boolean> {
1153
+ const { timeout, description } = this.resolveOptions(options);
1154
+
1155
+ try {
1156
+ const first = optionsList.first();
1157
+ await first.waitFor({ state: 'visible', timeout });
1158
+
1159
+ const count = await optionsList.count();
1160
+
1161
+ for (let i = 0; i < count; i++) {
1162
+ const option = optionsList.nth(i);
1163
+ const text = (await option.innerText()).trim();
1164
+
1165
+ if (text === value) {
1166
+ return true;
1167
+ }
1168
+ }
1169
+
1170
+ return false;
1171
+ } catch (error) {
1172
+ console.warn(
1173
+ `⚠ ${description}: Error while checking option "${value}". ${(error as Error).message}`
1174
+ );
1175
+ return false;
1176
+ }
1177
+ }
1178
+ /**
1179
+ * Retrieves the currently selected option text from a native <select> dropdown.
1180
+ *
1181
+ * @param dropdown - Locator for the <select> element
1182
+ * @param options - Optional configuration (timeout, description)
1183
+ * @returns The text of the selected option, or null if none is selected
1184
+ */
1185
+ static async getSelectedOption(dropdown: Locator, options: ActionOptions = {}
1186
+ ): Promise<string | null> {
1187
+ const { description } = this.resolveOptions(options);
1188
+
1189
+ try {
1190
+ await this.waitForVisible(dropdown, options);
1191
+ await this.highlight(dropdown);
1192
+
1193
+ const selectedOptions = await dropdown.evaluate((select: HTMLSelectElement) => {
1194
+ const selected = Array.from(select.options).filter(option => option.selected);
1195
+ return selected.map(option => option.text.trim());
1196
+ });
1197
+
1198
+ if (!selectedOptions || selectedOptions.length === 0) {
1199
+ console.warn(`⚠ ${description}: No option is currently selected`);
1200
+ return null;
1201
+ }
1202
+
1203
+ return selectedOptions[0];
1204
+ } catch (error) {
1205
+ console.warn(
1206
+ `⚠ ${description}: Unable to get selected option. ${(error as Error).message}`
1207
+ );
1208
+ return null;
1209
+ }
1210
+ }
1211
+
1212
+
1213
+ //**Pagination Methods */
1214
+
1215
+ /**
1216
+ * Determines the total number of pages by navigating to the last page
1217
+ * and reading the active page number.
1218
+ *
1219
+ * @param paginationContainer - Locator for the pagination container
1220
+ * @param options - Optional configuration (timeout, description, locators)
1221
+ * @returns Total number of pages, or 0 if it cannot be determined
1222
+ */
1223
+ static async getTotalPaginationCount(paginationContainer: Locator, options: ActionOptions = {}
1224
+ ): Promise<number> {
1225
+
1226
+ const {
1227
+ timeout = 15000,
1228
+ description = 'Pagination',
1229
+ lastButtonLocator = 'button.pagination-btn-last',
1230
+ activePageLocator = 'button.pagination-btn.active'
1231
+ } = options;
1232
+
1233
+ try {
1234
+ const page = paginationContainer.page();
1235
+ await this.waitForVisible(paginationContainer, { timeout, description });
1236
+
1237
+ const activePage = paginationContainer.locator(activePageLocator).first();
1238
+ await this.waitForVisible(activePage, { timeout, description: 'Active Page Button' });
1239
+
1240
+ const beforeText = (await activePage.innerText()).trim();
1241
+
1242
+ const lastButton = paginationContainer.locator(lastButtonLocator).first();
1243
+
1244
+ if ((await lastButton.count()) === 0) {
1245
+ console.warn(`⚠ ${description}: Last page button not found`);
1246
+ return 0;
1247
+ }
1248
+
1249
+ await this.waitForVisible(lastButton, { timeout, description: 'Last Page Button' });
1250
+ await this.highlight(lastButton);
1251
+ await lastButton.click({ timeout });
1252
+
1253
+ // Wait until active page number changes
1254
+ const handle = await activePage.elementHandle();
1255
+ if (handle) {
1256
+ await page.waitForFunction(
1257
+ ([el, previous]) => (el as HTMLElement).innerText.trim() !== previous,
1258
+ [handle, beforeText],
1259
+ { timeout }
1260
+ );
1261
+ }
1262
+
1263
+ const finalText = (await activePage.innerText()).trim();
1264
+ const total = Number(finalText);
1265
+
1266
+ if (Number.isNaN(total)) {
1267
+ console.warn(`⚠ ${description}: Invalid total page number "${finalText}"`);
1268
+ return 0;
1269
+ }
1270
+
1271
+ return total;
1272
+
1273
+ } catch (error) {
1274
+ console.warn(
1275
+ `⚠ ${description}: Unable to determine total pagination count. ${(error as Error).message}`
1276
+ );
1277
+ return 0;
1278
+ }
1279
+ }
1280
+
1281
+ /**
1282
+ * Clicks the "Next" button in pagination, if available.
1283
+ *
1284
+ * @param paginationContainer - Locator for the pagination container
1285
+ * @param options - Optional configuration (timeout, description, locators)
1286
+ * @returns True if the next button was clicked, otherwise false
1287
+ */
1288
+ static async clickNextPagination( paginationContainer: Locator, options: ActionOptions = {}
1289
+ ): Promise<boolean> {
1290
+
1291
+ const {
1292
+ timeout = 15000,
1293
+ description = 'Pagination Next Button',
1294
+ nextButtonLocator = 'button[data-page="next"]'
1295
+ } = options;
1296
+
1297
+ try {
1298
+ await this.waitForVisible(paginationContainer, { timeout, description });
1299
+
1300
+ const nextBtn = paginationContainer.locator(nextButtonLocator).first();
1301
+
1302
+ if ((await nextBtn.count()) === 0) {
1303
+ console.warn(`⚠ ${description}: Next button not found`);
1304
+ return false;
1305
+ }
1306
+
1307
+ await this.waitForVisible(nextBtn, { timeout });
1308
+ await this.highlight(nextBtn);
1309
+ await nextBtn.scrollIntoViewIfNeeded();
1310
+ await nextBtn.click({ timeout });
1311
+
1312
+ return true;
1313
+
1314
+ } catch (error) {
1315
+ console.warn(
1316
+ `⚠ ${description}: Failed to click next pagination button. ${(error as Error).message}`
1317
+ );
1318
+ return false;
1319
+ }
1320
+ }
1321
+
1322
+ /**
1323
+ * Clicks the "Previous" button in pagination, if available.
1324
+ *
1325
+ * @param paginationContainer - Locator for the pagination container
1326
+ * @param options - Optional configuration (timeout, description, locators)
1327
+ * @returns True if the previous button was clicked, otherwise false
1328
+ */
1329
+ static async clickPrevPagination(paginationContainer: Locator,options: ActionOptions = {}
1330
+ ): Promise<boolean> {
1331
+
1332
+ const {
1333
+ timeout = 15000,
1334
+ description = 'Pagination Previous Button',
1335
+ prevButtonLocator = 'button[data-page="prev"]'
1336
+ } = options;
1337
+
1338
+ try {
1339
+ await this.waitForVisible(paginationContainer, { timeout, description });
1340
+
1341
+ const prevBtn = paginationContainer.locator(prevButtonLocator).first();
1342
+
1343
+ if ((await prevBtn.count()) === 0) {
1344
+ console.warn(`⚠ ${description}: Previous button not found`);
1345
+ return false;
1346
+ }
1347
+
1348
+ await this.waitForVisible(prevBtn, { timeout });
1349
+ await this.highlight(prevBtn);
1350
+ await prevBtn.scrollIntoViewIfNeeded();
1351
+ await prevBtn.click({ timeout });
1352
+
1353
+ return true;
1354
+
1355
+ } catch (error) {
1356
+ console.warn(
1357
+ `⚠ ${description}: Failed to click previous pagination button. ${(error as Error).message}`
1358
+ );
1359
+ return false;
1360
+ }
1361
+ }
1362
+
1363
+ /**
1364
+ * Clicks the "Last" page button in pagination.
1365
+ *
1366
+ * @param paginationContainer - Locator for the pagination container
1367
+ * @param options - Optional configuration (timeout, description, locators)
1368
+ * @returns True if the last button was clicked successfully, otherwise false
1369
+ */
1370
+ static async clickLastPagination(paginationContainer: Locator, options: ActionOptions = {}
1371
+ ): Promise<boolean> {
1372
+
1373
+ const {
1374
+ timeout = 15000,
1375
+ description = 'Pagination Last Button',
1376
+ lastButtonLocator = 'button[data-page="last"]'
1377
+ } = options;
1378
+
1379
+ try {
1380
+ await this.waitForVisible(paginationContainer, { timeout, description });
1381
+
1382
+ const lastBtn = paginationContainer.locator(lastButtonLocator).first();
1383
+
1384
+ if ((await lastBtn.count()) === 0) {
1385
+ console.warn(`⚠ ${description}: Last button not found`);
1386
+ return false;
1387
+ }
1388
+
1389
+ await this.waitForVisible(lastBtn, { timeout });
1390
+ await this.highlight(lastBtn);
1391
+ await lastBtn.scrollIntoViewIfNeeded();
1392
+ await lastBtn.click({ timeout });
1393
+
1394
+ return true;
1395
+
1396
+ } catch (error) {
1397
+ console.warn(
1398
+ `⚠ ${description}: Failed to click last pagination button. ${(error as Error).message}`
1399
+ );
1400
+ return false;
1401
+ }
1402
+ }
1403
+
1404
+ /**
1405
+ * Retrieves the currently active page number from pagination.
1406
+ *
1407
+ * @param paginationContainer - Locator for the pagination container
1408
+ * @param options - Optional configuration (timeout, description, locators)
1409
+ * @returns Active page number, or 0 if not found or invalid
1410
+ */
1411
+ static async getActivePaginationPage(paginationContainer: Locator, options: ActionOptions = {}
1412
+ ): Promise<number> {
1413
+
1414
+ const {
1415
+ timeout = 15000,
1416
+ description = 'Active Pagination Page',
1417
+ activePageLocator = 'button.pagination-btn.active'
1418
+ } = options;
1419
+
1420
+ try {
1421
+ await this.waitForVisible(paginationContainer, { timeout, description });
1422
+
1423
+ const activeBtn = paginationContainer.locator(activePageLocator).first();
1424
+
1425
+ if ((await activeBtn.count()) === 0) {
1426
+ console.warn(`⚠ ${description}: Active page button not found`);
1427
+ return 0;
1428
+ }
1429
+
1430
+ await this.waitForVisible(activeBtn, { timeout });
1431
+ await this.highlight(activeBtn);
1432
+
1433
+ const text = (await activeBtn.innerText()).trim();
1434
+ const pageNumber = Number(text);
1435
+
1436
+ if (Number.isNaN(pageNumber)) {
1437
+ console.warn(`⚠ ${description}: Invalid page number text "${text}"`);
1438
+ return 0;
1439
+ }
1440
+
1441
+ return pageNumber;
1442
+
1443
+ } catch (error) {
1444
+ console.warn(
1445
+ `⚠ ${description}: Failed to get active pagination page. ${(error as Error).message}`
1446
+ );
1447
+ return 0;
1448
+ }
1449
+ }
1450
+
1451
+ /**
1452
+ * Clicks the "Start" (first page) button in pagination, if available.
1453
+ *
1454
+ * @param paginationContainer - Locator for the pagination container
1455
+ * @param options - Optional configuration (timeout, description, locators)
1456
+ * @returns True if the start button was clicked, otherwise false
1457
+ */
1458
+ static async clickStartPagination(paginationContainer: Locator,options: ActionOptions = {}
1459
+ ): Promise<boolean> {
1460
+
1461
+ const {
1462
+ timeout = 15000,
1463
+ description = 'Pagination Start Button',
1464
+ firstButtonLocator = 'button.pagination-btn-start'
1465
+ } = options;
1466
+
1467
+ try {
1468
+ await this.waitForVisible(paginationContainer, { timeout, description });
1469
+
1470
+ const startBtn = paginationContainer.locator(firstButtonLocator).first();
1471
+
1472
+ if ((await startBtn.count()) === 0) {
1473
+ console.warn(`⚠ ${description}: Start button not found`);
1474
+ return false;
1475
+ }
1476
+
1477
+ await this.waitForVisible(startBtn, { timeout });
1478
+ await this.highlight(startBtn);
1479
+ await startBtn.scrollIntoViewIfNeeded();
1480
+ await startBtn.click({ timeout });
1481
+
1482
+ return true;
1483
+
1484
+ } catch (error) {
1485
+ console.warn(
1486
+ `⚠ ${description}: Failed to click start pagination button. ${(error as Error).message}`
1487
+ );
1488
+ return false;
1489
+ }
1490
+ }
1491
+
1492
+ /**
1493
+ * Clicks a specific page number button in pagination.
1494
+ *
1495
+ * @param paginationContainer - Locator for the pagination container
1496
+ * @param pageNumber - Page number to navigate to (1-based)
1497
+ * @param options - Optional configuration (timeout, description, locators)
1498
+ * @returns True if the page number button was clicked, otherwise false
1499
+ */
1500
+ static async clickPaginationPageNumber( paginationContainer: Locator,pageNumber: number,
1501
+ options: ActionOptions = {}
1502
+ ): Promise<boolean> {
1503
+
1504
+ const {
1505
+ timeout = 15000,
1506
+ description = `Pagination Page ${pageNumber} Button`,
1507
+ pageButtonLocator = 'button.pagination-btn'
1508
+ } = options;
1509
+
1510
+ try {
1511
+ await this.waitForVisible(paginationContainer, { timeout, description });
1512
+
1513
+ const pageButtons = paginationContainer.locator(pageButtonLocator);
1514
+ const count = await pageButtons.count();
1515
+
1516
+ if (count === 0) {
1517
+ console.warn(`⚠ ${description}: No pagination buttons found`);
1518
+ return false;
1519
+ }
1520
+
1521
+ for (let i = 0; i < count; i++) {
1522
+ const btn = pageButtons.nth(i);
1523
+ const text = (await btn.innerText()).trim();
1524
+
1525
+ if (text === String(pageNumber)) {
1526
+ await this.waitForVisible(btn, { timeout });
1527
+ await this.highlight(btn);
1528
+ await btn.scrollIntoViewIfNeeded();
1529
+ await btn.click({ timeout });
1530
+ return true;
1531
+ }
1532
+ }
1533
+
1534
+ console.warn(`⚠ ${description}: Page button "${pageNumber}" not found`);
1535
+ return false;
1536
+
1537
+ } catch (error) {
1538
+ console.warn(
1539
+ `⚠ ${description}: Failed to click page number "${pageNumber}". ${(error as Error).message}`
1540
+ );
1541
+ return false;
1542
+ }
1543
+ }
1544
+ }