pw-element-interactions 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,6 +10,32 @@ A robust set of Playwright steps for readable interaction and assertions.
10
10
 
11
11
  With the introduction of the `Steps` class, you can now combine your element repository and interactions into a single, flattened Facade. This eliminates repetitive locator fetching and transforms your tests into clean, plain-English steps.
12
12
 
13
+ ### 🤖 AI-Friendly Test Development & Boilerplate Reduction
14
+
15
+ Stop writing the same three lines of code for every single interaction. This library handles the fetching, waiting, and acting automatically.
16
+
17
+ Because the API is highly semantic and completely decoupled from the DOM, it is an **ideal framework for AI coding assistants**. AI models can easily generate robust test flows using plain-English strings (`'CheckoutPage'`, `'submitButton'`) without hallucinating complex CSS selectors, writing flaky interactions, or forgetting critical `waitFor` states.
18
+
19
+ **Before (Raw Playwright):**
20
+
21
+ ```ts
22
+ // 1. Hardcode or manage raw locators inside your test
23
+ const submitBtn = page.locator('button[data-test="submit-order"]');
24
+
25
+ // 2. Explicitly wait for DOM stability and visibility
26
+ await submitBtn.waitFor({ state: 'visible', timeout: 30000 });
27
+
28
+ // 3. Perform the interaction
29
+ await submitBtn.click();
30
+ ```
31
+
32
+ **Now (with pw-element-interactions):**
33
+
34
+ ```ts
35
+ // 1. Locate, wait, and interact in a single, readable, AI-friendly line
36
+ await steps.click('CheckoutPage', 'submitButton');
37
+ ```
38
+
13
39
  ---
14
40
 
15
41
  ## 📦 Installation
@@ -30,8 +56,11 @@ This package requires `@playwright/test` to be installed in your project. If you
30
56
  * **Zero Locator Boilerplate:** The new `Steps` API fetches elements and interacts with them in a single method call.
31
57
  * **Separation of Concerns:** Keep your interaction logic entirely detached from how elements are found on the page.
32
58
  * **Readable Tests:** Abstract away Playwright boilerplate into semantic methods (`clickIfPresent`, `verifyPresence`, `selectDropdown`).
59
+ * **Standardized Waiting:** Easily wait for elements to reach specific DOM states (visible, hidden, attached, detached) with built-in utility methods.
33
60
  * **Advanced Visual Checks:** Includes a highly reliable `verifyImages` method that evaluates actual browser decoding and `naturalWidth` to ensure images aren't just in the DOM, but are properly rendered.
34
61
  * **Smart Dropdowns:** Easily select dropdown options by value, index, or completely randomly (skipping disabled or empty options automatically).
62
+ * **Flexible Verifications:** Easily verify exact text, non-empty text, or dynamic element counts (greater than, less than, or exact).
63
+ * **Advanced Drag & Drop:** Seamlessly drag elements to other elements, drop them at specific coordinate offsets, or combine both strategies natively.
35
64
 
36
65
  ---
37
66
 
@@ -44,7 +73,7 @@ Initialize the `Steps` class by passing the current Playwright `page` object and
44
73
  ```ts
45
74
  import { test } from '@playwright/test';
46
75
  import { ElementRepository } from 'pw-element-repository';
47
- import { Steps } from 'pw-element-interactions';
76
+ import { Steps, DropdownSelectType } from 'pw-element-interactions';
48
77
 
49
78
  test('Add random product and verify image gallery', async ({ page }) => {
50
79
  // 1. Initialize Repository & Steps
@@ -62,11 +91,20 @@ test('Add random product and verify image gallery', async ({ page }) => {
62
91
  await steps.verifyUrlContains('/product/');
63
92
 
64
93
  // 5. Smart Dropdown Interaction
65
- const selectedSize = await steps.selectDropdown('ProductDetailsPage', 'size-selector', { type: 'random' });
94
+ const selectedSize = await steps.selectDropdown('ProductDetailsPage', 'size-selector', {
95
+ type: DropdownSelectType.RANDOM
96
+ });
66
97
  console.log(`Selected size: ${selectedSize}`);
67
98
 
68
- // 6. Advanced Image Verification
99
+ // 6. Flexible Assertions & Data Extraction
100
+ await steps.verifyCount('ProductDetailsPage', 'gallery-images', { greaterThan: 0 });
101
+ await steps.verifyText('ProductDetailsPage', 'product-title', undefined, { notEmpty: true });
102
+
103
+ // 7. Advanced Image Verification
69
104
  await steps.verifyImages('ProductDetailsPage', 'gallery-images');
105
+
106
+ // 8. Explicit Waits
107
+ await steps.waitForState('CheckoutPage', 'confirmation-modal', 'visible');
70
108
  });
71
109
  ```
72
110
 
@@ -83,26 +121,36 @@ The `Steps` class automatically handles fetching the Playwright `Locator` using
83
121
 
84
122
  ### 🖱️ Interaction
85
123
 
86
- * **`click(pageName: string, elementName: string)`**: Retrieves an element from the repository and performs a standard click. Automatically waits for actionability.
124
+ * **`click(pageName: string, elementName: string)`**: Retrieves an element from the repository and performs a standard Playwright click. Automatically waits for the element to be attached, visible, stable, and actionable.
125
+ * **`clickWithoutScrolling(pageName: string, elementName: string)`**: Dispatches a native `click` event directly to the element, bypassing Playwright's default scrolling and intersection observer checks. Highly useful for clicking elements obscured by sticky headers or transparent overlays.
126
+ * **`clickIfPresent(pageName: string, elementName: string)`**: Checks if an element is visible before attempting to click it. Safely skips the action without failing the test if the element is hidden. Great for optional elements like cookie banners.
87
127
  * **`clickRandom(pageName: string, elementName: string)`**: Retrieves a random element from a resolved list of locators and clicks it. Useful for clicking random items in a list or grid.
88
- * **`clickIfPresent(pageName: string, elementName: string)`**: Retrieves an element and clicks it only if it is visible. Prevents test failures on optional UI elements like cookie banners or promotional pop-ups.
89
- * **`fill(pageName: string, elementName: string, text: string)`**: Retrieves an input field and fills it with the provided text, replacing any existing value.
90
- * **`uploadFile(pageName: string, elementName: string, filePath: string)`**: Retrieves an `<input type="file">` and uploads the file from the provided local `filePath`.
91
- * **`selectDropdown(pageName: string, elementName: string, options?: DropdownSelectOptions)`**: Interacts with `<select>` dropdown elements. Returns the exact `value` attribute of the selected option. Accepts:
92
- * `{ type: 'random' }` *(Default)* - Selects a random, non-disabled option with a valid value.
93
- * `{ type: 'value', value: 'string' }` - Selects by exact value.
94
- * `{ type: 'index', index: 1 }` - Selects by index.
128
+ * **`hover(pageName: string, elementName: string)`**: Retrieves an element and hovers over it. Useful for triggering dropdowns or tooltips.
129
+ * **`scrollIntoView(pageName: string, elementName: string)`**: Retrieves an element and smoothly scrolls it into the viewport if it is not already visible.
130
+ * **`dragAndDrop(pageName: string, elementName: string, options: DragAndDropOptions)`**: Drags an element to a specified destination. Supports dropping onto another element (`{ target: Locator }`), dragging by coordinates (`{ xOffset: number, yOffset: number }`), or dropping onto a target at a specific offset.
131
+ * **`dragAndDropListedElement(pageName: string, elementName: string, elementText: string, options: DragAndDropOptions)`**: Finds a specific element by its text from a list of elements and drags it to a specified destination based on the provided options.
132
+ * **`fill(pageName: string, elementName: string, text: string)`**: Clears any existing value in the target input field and types the provided text.
133
+ * **`uploadFile(pageName: string, elementName: string, filePath: string)`**: Uploads a local file from the provided `filePath` to an `<input type="file">` element.
134
+ * **`selectDropdown(pageName: string, elementName: string, options?: DropdownSelectOptions)`**: Selects an option from a `<select>` element and returns its `value`. Defaults to a random, non-disabled option (`{ type: DropdownSelectType.RANDOM }`). Alternatively, select by exact value (`{ type: DropdownSelectType.VALUE, value: '...' }`) or zero-based index (`{ type: DropdownSelectType.INDEX, index: 1 }`).
95
135
 
136
+ ### 📊 Data Extraction
96
137
 
138
+ * **`getText(pageName: string, elementName: string)`**: Safely retrieves and trims the text content of a specified element. Returns an empty string if null.
139
+ * **`getAttribute(pageName: string, elementName: string, attributeName: string)`**: Retrieves the value of a specified HTML attribute (e.g., `href`, `aria-pressed`) from an element. Returns `null` if the attribute doesn't exist.
97
140
 
98
141
  ### ✅ Verification
99
142
 
100
143
  * **`verifyPresence(pageName: string, elementName: string)`**: Asserts that a specified element is attached to the DOM and is visible.
101
144
  * **`verifyAbsence(pageName: string, elementName: string)`**: Asserts that a specified element is hidden or completely detached from the DOM.
102
- * **`verifyText(pageName: string, elementName: string, expectedText: string)`**: Asserts that the specified element exactly matches the expected text.
145
+ * **`verifyText(pageName: string, elementName: string, expectedText?: string, options?: TextVerifyOptions)`**: Asserts the text of an element. Provide `expectedText` for an exact match, or pass `{ notEmpty: true }` in the options to simply assert that the dynamically generated text is not blank.
146
+ * **`verifyCount(pageName: string, elementName: string, options: CountVerifyOptions)`**: Asserts the number of elements matching the locator. Accepts a configuration object to evaluate: `{ exact: number }`, `{ greaterThan: number }`, or `{ lessThan: number }`.
103
147
  * **`verifyImages(pageName: string, elementName: string, scroll?: boolean)`**: Performs a rigorous verification of one or more images. Asserts visibility, checks for a valid `src` attribute, ensures `naturalWidth > 0`, and evaluates the native browser `decode()` promise. Smoothly scrolls into view by default (`scroll: true`).
104
148
  * **`verifyUrlContains(text: string)`**: Asserts that the active browser URL contains the expected substring.
105
149
 
150
+ ### ⏳ Wait
151
+
152
+ * **`waitForState(pageName: string, elementName: string, state?: 'visible' | 'attached' | 'hidden' | 'detached')`**: Waits for an element to reach a specific state in the DOM. Defaults to `'visible'`.
153
+
106
154
  ---
107
155
 
108
156
  ## 🧱 Advanced Usage: Raw Interactions API
@@ -118,7 +166,7 @@ const interactions = new ElementInteractions(page);
118
166
  // Pass Playwright Locators directly
119
167
  const customLocator = page.locator('button.dynamic-class');
120
168
  await interactions.interact.clickWithoutScrolling(customLocator);
121
- await interactions.verify.state(customLocator, 'enabled');
169
+ await interactions.verify.count(customLocator, { greaterThan: 2 });
122
170
  ```
123
171
 
124
- *Note: All core interaction (`interact`), verification (`verify`), and navigation (`Maps`) methods are also available when using `ElementInteractions` directly.*
172
+ *Note: All core interaction (`interact`), verification (`verify`), and navigation (`Maps`) methods are also available when using `ElementInteractions` directly.*
@@ -0,0 +1,53 @@
1
+ import { Locator } from '@playwright/test';
2
+ /**
3
+ * Defines the strategy for selecting an option from a dropdown element.
4
+ */
5
+ export declare enum DropdownSelectType {
6
+ /** Selects a completely random, non-disabled option with a valid value. */
7
+ RANDOM = "random",
8
+ /** Selects an option based on its zero-based index in the dropdown. */
9
+ INDEX = "index",
10
+ /** Selects an option based on its exact 'value' attribute. */
11
+ VALUE = "value"
12
+ }
13
+ /**
14
+ * Configuration options for the `selectDropdown` method.
15
+ */
16
+ export interface DropdownSelectOptions {
17
+ /** The selection strategy to use. Defaults to RANDOM. */
18
+ type?: DropdownSelectType;
19
+ /** The specific value attribute to select (Required if type is VALUE). */
20
+ value?: string;
21
+ /** The index of the option to select (Required if type is INDEX). */
22
+ index?: number;
23
+ }
24
+ /**
25
+ * Configuration options for the `text` verification method.
26
+ */
27
+ export interface TextVerifyOptions {
28
+ /** If true, asserts that the element has text content, ignoring 'expectedText' */
29
+ notEmpty?: boolean;
30
+ }
31
+ /**
32
+ * Configuration options for the `count` verification method.
33
+ */
34
+ export interface CountVerifyOptions {
35
+ /** Asserts that the element count exactly matches this value */
36
+ exactly?: number;
37
+ /** Asserts that the element count is strictly greater than this value */
38
+ greaterThan?: number;
39
+ /** Asserts that the element count is strictly less than this value */
40
+ lessThan?: number;
41
+ }
42
+ /**
43
+ * Configuration options for the `dragAndDrop` method.
44
+ * You must provide either a `targetLocator` OR both `xOffset` and `yOffset`.
45
+ */
46
+ export interface DragAndDropOptions {
47
+ /** The destination element to drop the dragged element onto. */
48
+ target?: Locator;
49
+ /** The horizontal offset from the center of the element (positive moves right). */
50
+ xOffset?: number;
51
+ /** The vertical offset from the center of the element (positive moves down). */
52
+ yOffset?: number;
53
+ }
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DropdownSelectType = void 0;
4
+ /**
5
+ * Defines the strategy for selecting an option from a dropdown element.
6
+ */
7
+ var DropdownSelectType;
8
+ (function (DropdownSelectType) {
9
+ /** Selects a completely random, non-disabled option with a valid value. */
10
+ DropdownSelectType["RANDOM"] = "random";
11
+ /** Selects an option based on its zero-based index in the dropdown. */
12
+ DropdownSelectType["INDEX"] = "index";
13
+ /** Selects an option based on its exact 'value' attribute. */
14
+ DropdownSelectType["VALUE"] = "value";
15
+ })(DropdownSelectType || (exports.DropdownSelectType = DropdownSelectType = {}));
package/dist/index.d.ts CHANGED
@@ -1,2 +1,9 @@
1
- export { ElementInteractions } from './ElementInteractions';
1
+ export * from './enum/Options';
2
+ export { Navigation } from './interactions/Navigation';
3
+ export { Verifications } from './interactions/Verification';
4
+ export { Interactions } from './interactions/Interaction';
5
+ export { Extractions } from './interactions/Extraction';
6
+ export { DateUtilities } from './utils/DateUtilities';
7
+ export { Utils } from './utils/ElementUtilities';
8
+ export { ElementInteractions } from './interactions/facade/ElementInteractions';
2
9
  export { Steps } from './steps/CommonSteps';
package/dist/index.js CHANGED
@@ -1,7 +1,39 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
2
16
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Steps = exports.ElementInteractions = void 0;
4
- var ElementInteractions_1 = require("./ElementInteractions");
17
+ exports.Steps = exports.ElementInteractions = exports.Utils = exports.DateUtilities = exports.Extractions = exports.Interactions = exports.Verifications = exports.Navigation = void 0;
18
+ // Enums
19
+ __exportStar(require("./enum/Options"), exports);
20
+ // Supporting Action Classes
21
+ var Navigation_1 = require("./interactions/Navigation");
22
+ Object.defineProperty(exports, "Navigation", { enumerable: true, get: function () { return Navigation_1.Navigation; } });
23
+ var Verification_1 = require("./interactions/Verification");
24
+ Object.defineProperty(exports, "Verifications", { enumerable: true, get: function () { return Verification_1.Verifications; } });
25
+ var Interaction_1 = require("./interactions/Interaction");
26
+ Object.defineProperty(exports, "Interactions", { enumerable: true, get: function () { return Interaction_1.Interactions; } });
27
+ var Extraction_1 = require("./interactions/Extraction");
28
+ Object.defineProperty(exports, "Extractions", { enumerable: true, get: function () { return Extraction_1.Extractions; } });
29
+ // Utilities
30
+ var DateUtilities_1 = require("./utils/DateUtilities");
31
+ Object.defineProperty(exports, "DateUtilities", { enumerable: true, get: function () { return DateUtilities_1.DateUtilities; } });
32
+ var ElementUtilities_1 = require("./utils/ElementUtilities");
33
+ Object.defineProperty(exports, "Utils", { enumerable: true, get: function () { return ElementUtilities_1.Utils; } });
34
+ // Element Interactions Facade
35
+ var ElementInteractions_1 = require("./interactions/facade/ElementInteractions");
5
36
  Object.defineProperty(exports, "ElementInteractions", { enumerable: true, get: function () { return ElementInteractions_1.ElementInteractions; } });
37
+ // Test Steps Facade
6
38
  var CommonSteps_1 = require("./steps/CommonSteps");
7
39
  Object.defineProperty(exports, "Steps", { enumerable: true, get: function () { return CommonSteps_1.Steps; } });
@@ -0,0 +1,25 @@
1
+ import { Page, Locator } from '@playwright/test';
2
+ export declare class Extractions {
3
+ private page;
4
+ private ELEMENT_TIMEOUT;
5
+ private utils;
6
+ /**
7
+ * Initializes the Extractions class.
8
+ * @param page - The current Playwright Page object.
9
+ * @param timeout - Optional override for the default element timeout.
10
+ */
11
+ constructor(page: Page, timeout?: number);
12
+ /**
13
+ * Safely retrieves and trims the text content of an element.
14
+ * @param locator - The Playwright Locator pointing to the target element.
15
+ * @returns The trimmed string, or an empty string if null.
16
+ */
17
+ getText(locator: Locator): Promise<string | null>;
18
+ /**
19
+ * Retrieves the value of a specified attribute (e.g., 'href', 'aria-pressed').
20
+ * @param locator - The Playwright Locator pointing to the target element.
21
+ * @param attributeName - The name of the attribute to retrieve.
22
+ * @returns The attribute value as a string, or null if it doesn't exist.
23
+ */
24
+ getAttribute(locator: Locator, attributeName: string): Promise<string | null>;
25
+ }
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Extractions = void 0;
4
+ const ElementUtilities_1 = require("../utils/ElementUtilities");
5
+ class Extractions {
6
+ page;
7
+ ELEMENT_TIMEOUT;
8
+ utils;
9
+ /**
10
+ * Initializes the Extractions class.
11
+ * @param page - The current Playwright Page object.
12
+ * @param timeout - Optional override for the default element timeout.
13
+ */
14
+ constructor(page, timeout = 30000) {
15
+ this.page = page;
16
+ this.ELEMENT_TIMEOUT = timeout;
17
+ this.utils = new ElementUtilities_1.Utils(this.ELEMENT_TIMEOUT);
18
+ }
19
+ /**
20
+ * Safely retrieves and trims the text content of an element.
21
+ * @param locator - The Playwright Locator pointing to the target element.
22
+ * @returns The trimmed string, or an empty string if null.
23
+ */
24
+ async getText(locator) {
25
+ await this.utils.waitForState(locator, 'attached');
26
+ const text = await locator.textContent({ timeout: this.ELEMENT_TIMEOUT });
27
+ return text?.trim() ?? null;
28
+ }
29
+ /**
30
+ * Retrieves the value of a specified attribute (e.g., 'href', 'aria-pressed').
31
+ * @param locator - The Playwright Locator pointing to the target element.
32
+ * @param attributeName - The name of the attribute to retrieve.
33
+ * @returns The attribute value as a string, or null if it doesn't exist.
34
+ */
35
+ async getAttribute(locator, attributeName) {
36
+ await this.utils.waitForState(locator, 'attached');
37
+ return await locator.getAttribute(attributeName, { timeout: this.ELEMENT_TIMEOUT });
38
+ }
39
+ }
40
+ exports.Extractions = Extractions;
@@ -1,26 +1,5 @@
1
1
  import { Page, Locator } from '@playwright/test';
2
- /**
3
- * Defines the strategy for selecting an option from a dropdown element.
4
- */
5
- export declare enum DropdownSelectType {
6
- /** Selects a completely random, non-disabled option with a valid value. */
7
- RANDOM = "random",
8
- /** Selects an option based on its zero-based index in the dropdown. */
9
- INDEX = "index",
10
- /** Selects an option based on its exact 'value' attribute. */
11
- VALUE = "value"
12
- }
13
- /**
14
- * Configuration options for the `selectDropdown` method.
15
- */
16
- export interface DropdownSelectOptions {
17
- /** The selection strategy to use. Defaults to RANDOM. */
18
- type?: DropdownSelectType;
19
- /** The specific value attribute to select (Required if type is VALUE). */
20
- value?: string;
21
- /** The index of the option to select (Required if type is INDEX). */
22
- index?: number;
23
- }
2
+ import { DropdownSelectOptions, DragAndDropOptions } from '../enum/Options';
24
3
  /**
25
4
  * The `Interactions` class provides a robust set of methods for interacting
26
5
  * with DOM elements via Playwright Locators. It abstracts away common boilerplate
@@ -28,11 +7,14 @@ export interface DropdownSelectOptions {
28
7
  */
29
8
  export declare class Interactions {
30
9
  private page;
10
+ private ELEMENT_TIMEOUT;
11
+ private utils;
31
12
  /**
32
13
  * Initializes the Interactions class.
33
14
  * @param page - The current Playwright Page object.
15
+ * @param timeout - Optional override for the default element timeout.
34
16
  */
35
- constructor(page: Page);
17
+ constructor(page: Page, timeout?: number);
36
18
  /**
37
19
  * Performs a standard Playwright click on the given locator.
38
20
  * Automatically waits for the element to be attached, visible, stable, and actionable.
@@ -68,10 +50,37 @@ export declare class Interactions {
68
50
  /**
69
51
  * Unified method to interact with `<select>` dropdown elements based on the specified `DropdownSelectType`.
70
52
  * If no options are provided, it safely defaults to randomly selecting an enabled, non-empty option.
71
- * * @param locator - The Playwright Locator pointing to the `<select>` element.
53
+ * @param locator - The Playwright Locator pointing to the `<select>` element.
72
54
  * @param options - Configuration specifying whether to select by 'random', 'index', or 'value'.
73
55
  * @returns A promise that resolves to the exact 'value' attribute of the newly selected option.
74
56
  * @throws Error if 'value' or 'index' is missing when their respective types are chosen, or if no enabled options exist.
75
57
  */
76
58
  selectDropdown(locator: Locator, options?: DropdownSelectOptions): Promise<string>;
59
+ /**
60
+ * Hovers over the specified element. Useful for triggering dropdowns or tooltips.
61
+ * @param locator - The Playwright Locator pointing to the target element.
62
+ */
63
+ hover(locator: Locator): Promise<void>;
64
+ /**
65
+ * Scrolls the element into view if it is not already visible in the viewport.
66
+ * @param locator - The Playwright Locator pointing to the target element.
67
+ */
68
+ scrollIntoView(locator: Locator): Promise<void>;
69
+ /**
70
+ * Drags an element either to a specified target element, a target element with an offset, or by a coordinate offset.
71
+ * @param locator - The Playwright Locator pointing to the element to drag.
72
+ * @param options - Configuration specifying a 'targetLocator', offsets, or both.
73
+ */
74
+ dragAndDrop(locator: Locator, options: DragAndDropOptions): Promise<void>;
75
+ /**
76
+ * Filters a locator list and returns the first element that contains the specified text.
77
+ * If the element is not found, it prints the available text contents of the base locator for debugging.
78
+ * @param baseLocator The base Playwright Locator.
79
+ * @param pageName The name of the page block in the JSON repository.
80
+ * @param elementName The specific element name to look up.
81
+ * @param desiredText The string of text to search for within the elements.
82
+ * @param strict If true, throws an error if the element is not found. Defaults to false.
83
+ * @returns A promise that resolves to the matched Playwright Locator, or null if not found.
84
+ */
85
+ getByText(baseLocator: Locator, pageName: string, elementName: string, desiredText: string, strict?: boolean): Promise<ReturnType<Page['locator']> | null>;
77
86
  }
@@ -1,18 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Interactions = exports.DropdownSelectType = void 0;
4
- /**
5
- * Defines the strategy for selecting an option from a dropdown element.
6
- */
7
- var DropdownSelectType;
8
- (function (DropdownSelectType) {
9
- /** Selects a completely random, non-disabled option with a valid value. */
10
- DropdownSelectType["RANDOM"] = "random";
11
- /** Selects an option based on its zero-based index in the dropdown. */
12
- DropdownSelectType["INDEX"] = "index";
13
- /** Selects an option based on its exact 'value' attribute. */
14
- DropdownSelectType["VALUE"] = "value";
15
- })(DropdownSelectType || (exports.DropdownSelectType = DropdownSelectType = {}));
3
+ exports.Interactions = void 0;
4
+ const Options_1 = require("../enum/Options");
5
+ const ElementUtilities_1 = require("../utils/ElementUtilities");
16
6
  /**
17
7
  * The `Interactions` class provides a robust set of methods for interacting
18
8
  * with DOM elements via Playwright Locators. It abstracts away common boilerplate
@@ -20,12 +10,17 @@ var DropdownSelectType;
20
10
  */
21
11
  class Interactions {
22
12
  page;
13
+ ELEMENT_TIMEOUT;
14
+ utils;
23
15
  /**
24
16
  * Initializes the Interactions class.
25
17
  * @param page - The current Playwright Page object.
18
+ * @param timeout - Optional override for the default element timeout.
26
19
  */
27
- constructor(page) {
20
+ constructor(page, timeout = 30000) {
28
21
  this.page = page;
22
+ this.ELEMENT_TIMEOUT = timeout;
23
+ this.utils = new ElementUtilities_1.Utils(this.ELEMENT_TIMEOUT);
29
24
  }
30
25
  /**
31
26
  * Performs a standard Playwright click on the given locator.
@@ -33,7 +28,8 @@ class Interactions {
33
28
  * @param locator - The Playwright Locator pointing to the target element.
34
29
  */
35
30
  async click(locator) {
36
- await locator.click();
31
+ await this.utils.waitForState(locator, 'visible');
32
+ await locator.click({ timeout: this.ELEMENT_TIMEOUT });
37
33
  }
38
34
  /**
39
35
  * Dispatches a native 'click' event directly to the element.
@@ -42,6 +38,7 @@ class Interactions {
42
38
  * @param locator - The Playwright Locator pointing to the target element.
43
39
  */
44
40
  async clickWithoutScrolling(locator) {
41
+ await this.utils.waitForState(locator, 'attached');
45
42
  await locator.dispatchEvent('click');
46
43
  }
47
44
  /**
@@ -52,7 +49,7 @@ class Interactions {
52
49
  */
53
50
  async clickIfPresent(locator) {
54
51
  if (await locator.isVisible()) {
55
- await locator.click();
52
+ await locator.click({ timeout: this.ELEMENT_TIMEOUT });
56
53
  }
57
54
  else {
58
55
  console.log(`[Action] -> Locator was not visible. Skipping click.`);
@@ -64,7 +61,8 @@ class Interactions {
64
61
  * @param text - The string to type into the input field.
65
62
  */
66
63
  async fill(locator, text) {
67
- await locator.fill(text);
64
+ await this.utils.waitForState(locator, 'visible');
65
+ await locator.fill(text, { timeout: this.ELEMENT_TIMEOUT });
68
66
  }
69
67
  /**
70
68
  * Uploads a local file to an `<input type="file">` element.
@@ -72,45 +70,131 @@ class Interactions {
72
70
  * @param filePath - The local file system path to the file you want to upload.
73
71
  */
74
72
  async uploadFile(locator, filePath) {
73
+ await this.utils.waitForState(locator, 'attached');
75
74
  console.log(`[Action] -> Uploading file from path "${filePath}"`);
76
- await locator.setInputFiles(filePath);
75
+ await locator.setInputFiles(filePath, { timeout: this.ELEMENT_TIMEOUT });
77
76
  }
78
77
  /**
79
78
  * Unified method to interact with `<select>` dropdown elements based on the specified `DropdownSelectType`.
80
79
  * If no options are provided, it safely defaults to randomly selecting an enabled, non-empty option.
81
- * * @param locator - The Playwright Locator pointing to the `<select>` element.
80
+ * @param locator - The Playwright Locator pointing to the `<select>` element.
82
81
  * @param options - Configuration specifying whether to select by 'random', 'index', or 'value'.
83
82
  * @returns A promise that resolves to the exact 'value' attribute of the newly selected option.
84
83
  * @throws Error if 'value' or 'index' is missing when their respective types are chosen, or if no enabled options exist.
85
84
  */
86
- async selectDropdown(locator, options = { type: DropdownSelectType.RANDOM }) {
87
- const type = options.type ?? DropdownSelectType.RANDOM;
88
- if (type === DropdownSelectType.VALUE) {
85
+ async selectDropdown(locator, options = { type: Options_1.DropdownSelectType.RANDOM }) {
86
+ await this.utils.waitForState(locator, 'visible');
87
+ const type = options.type ?? Options_1.DropdownSelectType.RANDOM;
88
+ if (type === Options_1.DropdownSelectType.VALUE) {
89
89
  if (options.value === undefined) {
90
90
  throw new Error('[Action] Error -> "value" must be provided when using DropdownSelectType.VALUE.');
91
91
  }
92
- const selected = await locator.selectOption({ value: options.value });
92
+ const selected = await locator.selectOption({ value: options.value }, { timeout: this.ELEMENT_TIMEOUT });
93
93
  return selected[0];
94
94
  }
95
- if (type === DropdownSelectType.INDEX) {
95
+ if (type === Options_1.DropdownSelectType.INDEX) {
96
96
  if (options.index === undefined) {
97
97
  throw new Error('[Action] Error -> "index" must be provided when using DropdownSelectType.INDEX.');
98
98
  }
99
- const selected = await locator.selectOption({ index: options.index });
99
+ const selected = await locator.selectOption({ index: options.index }, { timeout: this.ELEMENT_TIMEOUT });
100
100
  return selected[0];
101
101
  }
102
102
  const enabledOptions = locator.locator('option:not([disabled]):not([value=""])');
103
+ await this.utils.waitForState(enabledOptions.first(), 'attached').catch(() => { });
103
104
  const count = await enabledOptions.count();
104
105
  if (count === 0) {
105
106
  throw new Error('[Action] Error -> No enabled options found to select!');
106
107
  }
107
108
  const randomIndex = Math.floor(Math.random() * count);
108
- const valueToSelect = await enabledOptions.nth(randomIndex).getAttribute('value');
109
+ const valueToSelect = await enabledOptions.nth(randomIndex).getAttribute('value', { timeout: this.ELEMENT_TIMEOUT });
109
110
  if (valueToSelect === null) {
110
111
  throw new Error(`[Action] Error -> Option at index ${randomIndex} is missing a "value" attribute.`);
111
112
  }
112
- const selected = await locator.selectOption({ value: valueToSelect });
113
+ const selected = await locator.selectOption({ value: valueToSelect }, { timeout: this.ELEMENT_TIMEOUT });
113
114
  return selected[0];
114
115
  }
116
+ /**
117
+ * Hovers over the specified element. Useful for triggering dropdowns or tooltips.
118
+ * @param locator - The Playwright Locator pointing to the target element.
119
+ */
120
+ async hover(locator) {
121
+ await this.utils.waitForState(locator, 'visible');
122
+ await locator.hover({ timeout: this.ELEMENT_TIMEOUT });
123
+ }
124
+ /**
125
+ * Scrolls the element into view if it is not already visible in the viewport.
126
+ * @param locator - The Playwright Locator pointing to the target element.
127
+ */
128
+ async scrollIntoView(locator) {
129
+ await this.utils.waitForState(locator, 'attached');
130
+ await locator.scrollIntoViewIfNeeded({ timeout: this.ELEMENT_TIMEOUT });
131
+ }
132
+ /**
133
+ * Drags an element either to a specified target element, a target element with an offset, or by a coordinate offset.
134
+ * @param locator - The Playwright Locator pointing to the element to drag.
135
+ * @param options - Configuration specifying a 'targetLocator', offsets, or both.
136
+ */
137
+ async dragAndDrop(locator, options) {
138
+ await this.utils.waitForState(locator, 'visible');
139
+ if (options.target) {
140
+ await this.utils.waitForState(options.target, 'visible');
141
+ if (options.xOffset !== undefined && options.yOffset !== undefined) {
142
+ const targetBox = await options.target.boundingBox();
143
+ if (!targetBox) {
144
+ throw new Error(`[Action] Error -> Unable to get bounding box for target element.`);
145
+ }
146
+ const targetPosition = {
147
+ x: (targetBox.width / 2) + options.xOffset,
148
+ y: (targetBox.height / 2) + options.yOffset
149
+ };
150
+ await locator.dragTo(options.target, {
151
+ targetPosition,
152
+ timeout: this.ELEMENT_TIMEOUT
153
+ });
154
+ return;
155
+ }
156
+ await locator.dragTo(options.target, { timeout: this.ELEMENT_TIMEOUT });
157
+ return;
158
+ }
159
+ if (options.xOffset !== undefined && options.yOffset !== undefined) {
160
+ const box = await locator.boundingBox();
161
+ if (!box) {
162
+ throw new Error(`[Action] Error -> Unable to get bounding box for element to perform drag action.`);
163
+ }
164
+ const startX = box.x + box.width / 2;
165
+ const startY = box.y + box.height / 2;
166
+ await this.page.mouse.move(startX, startY);
167
+ await this.page.mouse.down();
168
+ await this.page.mouse.move(startX + options.xOffset, startY + options.yOffset, { steps: 10 });
169
+ await this.page.mouse.up();
170
+ return;
171
+ }
172
+ throw new Error(`[Action] Error -> You must provide either 'targetLocator', or both 'xOffset' and 'yOffset' in DragAndDropOptions.`);
173
+ }
174
+ /**
175
+ * Filters a locator list and returns the first element that contains the specified text.
176
+ * If the element is not found, it prints the available text contents of the base locator for debugging.
177
+ * @param baseLocator The base Playwright Locator.
178
+ * @param pageName The name of the page block in the JSON repository.
179
+ * @param elementName The specific element name to look up.
180
+ * @param desiredText The string of text to search for within the elements.
181
+ * @param strict If true, throws an error if the element is not found. Defaults to false.
182
+ * @returns A promise that resolves to the matched Playwright Locator, or null if not found.
183
+ */
184
+ async getByText(baseLocator, pageName, elementName, desiredText, strict = false) {
185
+ const locator = baseLocator.filter({ hasText: desiredText }).first();
186
+ if ((await locator.count()) === 0) {
187
+ const rawTexts = await baseLocator.allInnerTexts();
188
+ const availableTexts = rawTexts
189
+ .map((text) => text.trim())
190
+ .filter((text) => text.length > 0);
191
+ const msg = `Element '${elementName}' on '${pageName}' with text "${desiredText}" not found.\nAvailable texts found in locator: ${availableTexts.length > 0 ? `\n- ${availableTexts.join('\n- ')}` : 'None (Base locator found no elements or elements had no text)'}`;
192
+ if (strict)
193
+ throw new Error(msg);
194
+ console.warn(msg);
195
+ return null;
196
+ }
197
+ return locator;
198
+ }
115
199
  }
116
200
  exports.Interactions = Interactions;