pw-element-interactions 0.0.8 → 0.1.0
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 +154 -79
- package/dist/fixture/BaseFixture.js +10 -0
- package/dist/interactions/Interaction.js +5 -4
- package/dist/interactions/Navigation.d.ts +8 -4
- package/dist/interactions/Navigation.js +17 -5
- package/dist/interactions/Verification.js +6 -6
- package/dist/logger/Logger.d.ts +25 -0
- package/dist/logger/Logger.js +66 -0
- package/dist/steps/CommonSteps.d.ts +0 -145
- package/dist/steps/CommonSteps.js +38 -172
- package/dist/utils/ElementUtilities.js +3 -3
- package/package.json +12 -4
- package/scripts/postinstall.js +25 -0
- package/skills/pw-element-interactions.md +235 -0
|
@@ -22,174 +22,29 @@ export declare class Steps {
|
|
|
22
22
|
* @param timeout - Optional global timeout override (in milliseconds).
|
|
23
23
|
*/
|
|
24
24
|
constructor(page: Page, repo: ElementRepository, timeout?: number);
|
|
25
|
-
/**
|
|
26
|
-
* Navigates the browser to the specified URL.
|
|
27
|
-
* @param url - The absolute or relative URL to navigate to.
|
|
28
|
-
*/
|
|
29
25
|
navigateTo(url: string): Promise<void>;
|
|
30
|
-
/**
|
|
31
|
-
* Reloads the current page.
|
|
32
|
-
*/
|
|
33
26
|
refresh(): Promise<void>;
|
|
34
|
-
/**
|
|
35
|
-
* Navigates the browser history stack either backwards or forwards.
|
|
36
|
-
* Mirrors the behavior of the browser's native Back and Forward buttons.
|
|
37
|
-
* @param direction - The direction to move in history: either 'BACKWARDS' or 'FORWARDS'.
|
|
38
|
-
*/
|
|
39
27
|
backOrForward(direction: 'BACKWARDS' | 'FORWARDS'): Promise<void>;
|
|
40
|
-
/**
|
|
41
|
-
* Resizes the browser viewport to the specified dimensions.
|
|
42
|
-
* Useful for simulating different device screen sizes or responsive breakpoints.
|
|
43
|
-
* @param width - The desired width of the viewport in pixels.
|
|
44
|
-
* @param height - The desired height of the viewport in pixels.
|
|
45
|
-
*/
|
|
46
28
|
setViewport(width: number, height: number): Promise<void>;
|
|
47
|
-
/**
|
|
48
|
-
* Retrieves an element from the repository and performs a standard click.
|
|
49
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
50
|
-
* @param elementName - The specific element name in your repository.
|
|
51
|
-
*/
|
|
52
29
|
click(pageName: string, elementName: string): Promise<void>;
|
|
53
|
-
/**
|
|
54
|
-
* Retrieves an element and dispatches a native 'click' event directly to it.
|
|
55
|
-
* Bypasses default scrolling and intersection checks. Useful for obscured elements.
|
|
56
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
57
|
-
* @param elementName - The specific element name in your repository.
|
|
58
|
-
*/
|
|
59
30
|
clickWithoutScrolling(pageName: string, elementName: string): Promise<void>;
|
|
60
|
-
/**
|
|
61
|
-
* Retrieves a random element from a resolved list of locators and clicks it.
|
|
62
|
-
* Useful for clicking random items in a list or grid (e.g., product cards).
|
|
63
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
64
|
-
* @param elementName - The specific element name in your repository representing multiple elements.
|
|
65
|
-
*/
|
|
66
31
|
clickRandom(pageName: string, elementName: string): Promise<void>;
|
|
67
|
-
/**
|
|
68
|
-
* Retrieves an element and clicks it only if it is visible.
|
|
69
|
-
* Prevents test failures on optional elements like cookie banners or promotional pop-ups.
|
|
70
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
71
|
-
* @param elementName - The specific element name in your repository.
|
|
72
|
-
*/
|
|
73
32
|
clickIfPresent(pageName: string, elementName: string): Promise<void>;
|
|
74
|
-
/**
|
|
75
|
-
* Retrieves an element and hovers over it. Useful for triggering dropdowns or tooltips.
|
|
76
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
77
|
-
* @param elementName - The specific element name in your repository.
|
|
78
|
-
*/
|
|
79
33
|
hover(pageName: string, elementName: string): Promise<void>;
|
|
80
|
-
/**
|
|
81
|
-
* Retrieves an element and scrolls it into view if it is not already visible.
|
|
82
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
83
|
-
* @param elementName - The specific element name in your repository.
|
|
84
|
-
*/
|
|
85
34
|
scrollIntoView(pageName: string, elementName: string): Promise<void>;
|
|
86
|
-
/**
|
|
87
|
-
* Retrieves an input field and fills it with the provided text, replacing any existing value.
|
|
88
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
89
|
-
* @param elementName - The specific element name in your repository.
|
|
90
|
-
* @param text - The text to type into the input field.
|
|
91
|
-
*/
|
|
92
35
|
fill(pageName: string, elementName: string, text: string): Promise<void>;
|
|
93
|
-
/**
|
|
94
|
-
* Retrieves an input element of type `file` and sets its files.
|
|
95
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
96
|
-
* @param elementName - The specific element name in your repository.
|
|
97
|
-
* @param filePath - The local file path of the file to be uploaded.
|
|
98
|
-
*/
|
|
99
36
|
uploadFile(pageName: string, elementName: string, filePath: string): Promise<void>;
|
|
100
|
-
/**
|
|
101
|
-
* Retrieves a `<select>` dropdown element and selects an option based on the provided strategy.
|
|
102
|
-
* Defaults to selecting a random, non-disabled option if no strategy is specified.
|
|
103
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
104
|
-
* @param elementName - The specific element name in your repository.
|
|
105
|
-
* @param options - Configuration specifying whether to select by 'random', 'index', or 'value'.
|
|
106
|
-
* @returns The exact value attribute of the selected option.
|
|
107
|
-
*/
|
|
108
37
|
selectDropdown(pageName: string, elementName: string, options?: DropdownSelectOptions): Promise<string>;
|
|
109
|
-
/**
|
|
110
|
-
* Drags an element either to a specified target element, a target element with an offset, or by a coordinate offset.
|
|
111
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
112
|
-
* @param elementName - The specific element name in your repository (the element to drag).
|
|
113
|
-
* @param options - Configuration specifying a 'targetLocator', offsets, or both.
|
|
114
|
-
*/
|
|
115
38
|
dragAndDrop(pageName: string, elementName: string, options: DragAndDropOptions): Promise<void>;
|
|
116
|
-
/**
|
|
117
|
-
* Drags an element either to a specified target element, a target element with an offset, or by a coordinate offset.
|
|
118
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
119
|
-
* @param elementName - The specific element name in your repository (the element to drag).
|
|
120
|
-
* @param options - Configuration specifying a 'targetLocator', offsets, or both.
|
|
121
|
-
*/
|
|
122
39
|
dragAndDropListedElement(pageName: string, elementName: string, elementText: string, options: DragAndDropOptions): Promise<void>;
|
|
123
|
-
/**
|
|
124
|
-
* Safely retrieves and trims the text content of a specified element.
|
|
125
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
126
|
-
* @param elementName - The specific element name in your repository.
|
|
127
|
-
* @returns The trimmed string, or an empty string if null.
|
|
128
|
-
*/
|
|
129
40
|
getText(pageName: string, elementName: string): Promise<string | null>;
|
|
130
|
-
/**
|
|
131
|
-
* Retrieves the value of a specified attribute (e.g., 'href', 'aria-pressed') from an element.
|
|
132
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
133
|
-
* @param elementName - The specific element name in your repository.
|
|
134
|
-
* @param attributeName - The name of the attribute to retrieve.
|
|
135
|
-
* @returns The attribute value as a string, or null if it doesn't exist.
|
|
136
|
-
*/
|
|
137
41
|
getAttribute(pageName: string, elementName: string, attributeName: string): Promise<string | null>;
|
|
138
|
-
/**
|
|
139
|
-
* Asserts that a specified element is attached to the DOM and is visible.
|
|
140
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
141
|
-
* @param elementName - The specific element name in your repository.
|
|
142
|
-
*/
|
|
143
42
|
verifyPresence(pageName: string, elementName: string): Promise<void>;
|
|
144
|
-
/**
|
|
145
|
-
* Asserts that a specified element is hidden or completely detached from the DOM.
|
|
146
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
147
|
-
* @param elementName - The specific element name in your repository.
|
|
148
|
-
*/
|
|
149
43
|
verifyAbsence(pageName: string, elementName: string): Promise<void>;
|
|
150
|
-
/**
|
|
151
|
-
* Asserts that the specified element exactly matches the expected text, or optionally checks if it is not empty.
|
|
152
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
153
|
-
* @param elementName - The specific element name in your repository.
|
|
154
|
-
* @param expectedText - The exact string expected inside the element (optional if checking 'notEmpty').
|
|
155
|
-
* @param options - Configuration to alter the verification behavior.
|
|
156
|
-
*/
|
|
157
44
|
verifyText(pageName: string, elementName: string, expectedText?: string, options?: TextVerifyOptions): Promise<void>;
|
|
158
|
-
/**
|
|
159
|
-
* Asserts the number of elements matching the locator based on the provided conditions.
|
|
160
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
161
|
-
* @param elementName - The specific element name in your repository.
|
|
162
|
-
* @param options - Configuration specifying 'exact', 'greaterThan', or 'lessThan' logic.
|
|
163
|
-
*/
|
|
164
45
|
verifyCount(pageName: string, elementName: string, options: CountVerifyOptions): Promise<void>;
|
|
165
|
-
/**
|
|
166
|
-
* Performs a rigorous verification of one or more images.
|
|
167
|
-
* Asserts visibility, checks for a valid 'src' attribute, ensures a positive 'naturalWidth',
|
|
168
|
-
* and evaluates the native browser `decode()` promise to ensure the image isn't broken.
|
|
169
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
170
|
-
* @param elementName - The specific element name in your repository.
|
|
171
|
-
* @param scroll - Whether to smoothly scroll the image(s) into view before verifying (default: true).
|
|
172
|
-
*/
|
|
173
46
|
verifyImages(pageName: string, elementName: string, scroll?: boolean): Promise<void>;
|
|
174
|
-
/**
|
|
175
|
-
* Asserts that the current browser URL contains the expected substring.
|
|
176
|
-
* @param text - The substring expected to be present within the active URL.
|
|
177
|
-
*/
|
|
178
47
|
verifyUrlContains(text: string): Promise<void>;
|
|
179
|
-
/**
|
|
180
|
-
* Waits for an element to reach a specific state in the DOM.
|
|
181
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
182
|
-
* @param elementName - The specific element name in your repository.
|
|
183
|
-
* @param state - The state to wait for: 'visible' | 'attached' | 'hidden' | 'detached'. Defaults to 'visible'.
|
|
184
|
-
*/
|
|
185
48
|
waitForState(pageName: string, elementName: string, state?: 'visible' | 'attached' | 'hidden' | 'detached'): Promise<void>;
|
|
186
|
-
/**
|
|
187
|
-
* Types text into a specific element character by character with a delay.
|
|
188
|
-
* Ideal for OTP inputs, search bars, or fields with sensitive 'keyup' listeners.
|
|
189
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
190
|
-
* @param elementName - The specific element name in your repository.
|
|
191
|
-
* @param text - The string to type sequentially.
|
|
192
|
-
* @param delay - Optional delay between key presses in milliseconds (defaults to 100).
|
|
193
|
-
*/
|
|
194
49
|
typeSequentially(pageName: string, elementName: string, text: string, delay?: number): Promise<void>;
|
|
195
50
|
}
|
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Steps = void 0;
|
|
4
4
|
const ElementInteractions_1 = require("../interactions/facade/ElementInteractions");
|
|
5
|
+
const Logger_1 = require("../logger/Logger");
|
|
6
|
+
const logger = (type) => (0, Logger_1.createLogger)(`${type}`);
|
|
7
|
+
const log = {
|
|
8
|
+
navigate: logger('navigate'),
|
|
9
|
+
interact: logger('interact'),
|
|
10
|
+
extract: logger('extract'),
|
|
11
|
+
verify: logger('verify'),
|
|
12
|
+
wait: logger('wait'),
|
|
13
|
+
};
|
|
5
14
|
/**
|
|
6
15
|
* The `Steps` class serves as a unified Facade for test orchestration.
|
|
7
16
|
* It combines element acquisition (via `pw-element-repository`) with
|
|
@@ -35,279 +44,136 @@ class Steps {
|
|
|
35
44
|
// ==========================================
|
|
36
45
|
// 🧭 NAVIGATION STEPS
|
|
37
46
|
// ==========================================
|
|
38
|
-
/**
|
|
39
|
-
* Navigates the browser to the specified URL.
|
|
40
|
-
* @param url - The absolute or relative URL to navigate to.
|
|
41
|
-
*/
|
|
42
47
|
async navigateTo(url) {
|
|
43
|
-
|
|
48
|
+
log.navigate('Navigating to URL: "%s"', url);
|
|
44
49
|
await this.navigate.toUrl(url);
|
|
45
50
|
}
|
|
46
|
-
/**
|
|
47
|
-
* Reloads the current page.
|
|
48
|
-
*/
|
|
49
51
|
async refresh() {
|
|
50
|
-
|
|
52
|
+
log.navigate('Refreshing the current page');
|
|
51
53
|
await this.navigate.reload();
|
|
52
54
|
}
|
|
53
|
-
/**
|
|
54
|
-
* Navigates the browser history stack either backwards or forwards.
|
|
55
|
-
* Mirrors the behavior of the browser's native Back and Forward buttons.
|
|
56
|
-
* @param direction - The direction to move in history: either 'BACKWARDS' or 'FORWARDS'.
|
|
57
|
-
*/
|
|
58
55
|
async backOrForward(direction) {
|
|
59
|
-
|
|
56
|
+
log.navigate('Navigating browser: "%s"', direction);
|
|
60
57
|
await this.navigate.backOrForward(direction);
|
|
61
58
|
}
|
|
62
|
-
/**
|
|
63
|
-
* Resizes the browser viewport to the specified dimensions.
|
|
64
|
-
* Useful for simulating different device screen sizes or responsive breakpoints.
|
|
65
|
-
* @param width - The desired width of the viewport in pixels.
|
|
66
|
-
* @param height - The desired height of the viewport in pixels.
|
|
67
|
-
*/
|
|
68
59
|
async setViewport(width, height) {
|
|
69
|
-
|
|
60
|
+
log.navigate('Setting viewport to %dx%d', width, height);
|
|
70
61
|
await this.navigate.setViewport(width, height);
|
|
71
62
|
}
|
|
72
63
|
// ==========================================
|
|
73
64
|
// 🖱️ INTERACTION STEPS
|
|
74
65
|
// ==========================================
|
|
75
|
-
/**
|
|
76
|
-
* Retrieves an element from the repository and performs a standard click.
|
|
77
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
78
|
-
* @param elementName - The specific element name in your repository.
|
|
79
|
-
*/
|
|
80
66
|
async click(pageName, elementName) {
|
|
81
|
-
|
|
67
|
+
log.interact('Clicking on "%s" in "%s"', elementName, pageName);
|
|
82
68
|
const locator = await this.repo.get(this.page, pageName, elementName);
|
|
83
69
|
await this.interact.click(locator);
|
|
84
70
|
}
|
|
85
|
-
/**
|
|
86
|
-
* Retrieves an element and dispatches a native 'click' event directly to it.
|
|
87
|
-
* Bypasses default scrolling and intersection checks. Useful for obscured elements.
|
|
88
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
89
|
-
* @param elementName - The specific element name in your repository.
|
|
90
|
-
*/
|
|
91
71
|
async clickWithoutScrolling(pageName, elementName) {
|
|
92
|
-
|
|
72
|
+
log.interact('Clicking (no scroll) on "%s" in "%s"', elementName, pageName);
|
|
93
73
|
const locator = await this.repo.get(this.page, pageName, elementName);
|
|
94
74
|
await this.interact.clickWithoutScrolling(locator);
|
|
95
75
|
}
|
|
96
|
-
/**
|
|
97
|
-
* Retrieves a random element from a resolved list of locators and clicks it.
|
|
98
|
-
* Useful for clicking random items in a list or grid (e.g., product cards).
|
|
99
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
100
|
-
* @param elementName - The specific element name in your repository representing multiple elements.
|
|
101
|
-
*/
|
|
102
76
|
async clickRandom(pageName, elementName) {
|
|
103
|
-
|
|
77
|
+
log.interact('Clicking a random element from "%s" in "%s"', elementName, pageName);
|
|
104
78
|
const locator = await this.repo.getRandom(this.page, pageName, elementName);
|
|
105
79
|
await this.interact.click(locator);
|
|
106
80
|
}
|
|
107
|
-
/**
|
|
108
|
-
* Retrieves an element and clicks it only if it is visible.
|
|
109
|
-
* Prevents test failures on optional elements like cookie banners or promotional pop-ups.
|
|
110
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
111
|
-
* @param elementName - The specific element name in your repository.
|
|
112
|
-
*/
|
|
113
81
|
async clickIfPresent(pageName, elementName) {
|
|
114
|
-
|
|
82
|
+
log.interact('Clicking on "%s" in "%s" (if present)', elementName, pageName);
|
|
115
83
|
const locator = await this.repo.get(this.page, pageName, elementName);
|
|
116
84
|
await this.interact.clickIfPresent(locator);
|
|
117
85
|
}
|
|
118
|
-
/**
|
|
119
|
-
* Retrieves an element and hovers over it. Useful for triggering dropdowns or tooltips.
|
|
120
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
121
|
-
* @param elementName - The specific element name in your repository.
|
|
122
|
-
*/
|
|
123
86
|
async hover(pageName, elementName) {
|
|
124
|
-
|
|
87
|
+
log.interact('Hovering over "%s" in "%s"', elementName, pageName);
|
|
125
88
|
const locator = await this.repo.get(this.page, pageName, elementName);
|
|
126
89
|
await this.interact.hover(locator);
|
|
127
90
|
}
|
|
128
|
-
/**
|
|
129
|
-
* Retrieves an element and scrolls it into view if it is not already visible.
|
|
130
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
131
|
-
* @param elementName - The specific element name in your repository.
|
|
132
|
-
*/
|
|
133
91
|
async scrollIntoView(pageName, elementName) {
|
|
134
|
-
|
|
92
|
+
log.interact('Scrolling "%s" in "%s" into view', elementName, pageName);
|
|
135
93
|
const locator = await this.repo.get(this.page, pageName, elementName);
|
|
136
94
|
await this.interact.scrollIntoView(locator);
|
|
137
95
|
}
|
|
138
|
-
/**
|
|
139
|
-
* Retrieves an input field and fills it with the provided text, replacing any existing value.
|
|
140
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
141
|
-
* @param elementName - The specific element name in your repository.
|
|
142
|
-
* @param text - The text to type into the input field.
|
|
143
|
-
*/
|
|
144
96
|
async fill(pageName, elementName, text) {
|
|
145
|
-
|
|
97
|
+
log.interact('Filling "%s" in "%s" with text: "%s"', elementName, pageName, text);
|
|
146
98
|
const locator = await this.repo.get(this.page, pageName, elementName);
|
|
147
99
|
await this.interact.fill(locator, text);
|
|
148
100
|
}
|
|
149
|
-
/**
|
|
150
|
-
* Retrieves an input element of type `file` and sets its files.
|
|
151
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
152
|
-
* @param elementName - The specific element name in your repository.
|
|
153
|
-
* @param filePath - The local file path of the file to be uploaded.
|
|
154
|
-
*/
|
|
155
101
|
async uploadFile(pageName, elementName, filePath) {
|
|
156
|
-
|
|
102
|
+
log.interact('Uploading file "%s" to "%s" in "%s"', filePath, elementName, pageName);
|
|
157
103
|
const locator = await this.repo.get(this.page, pageName, elementName);
|
|
158
104
|
await this.interact.uploadFile(locator, filePath);
|
|
159
105
|
}
|
|
160
|
-
/**
|
|
161
|
-
* Retrieves a `<select>` dropdown element and selects an option based on the provided strategy.
|
|
162
|
-
* Defaults to selecting a random, non-disabled option if no strategy is specified.
|
|
163
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
164
|
-
* @param elementName - The specific element name in your repository.
|
|
165
|
-
* @param options - Configuration specifying whether to select by 'random', 'index', or 'value'.
|
|
166
|
-
* @returns The exact value attribute of the selected option.
|
|
167
|
-
*/
|
|
168
106
|
async selectDropdown(pageName, elementName, options) {
|
|
169
|
-
|
|
170
|
-
console.log(`[Step] -> Selecting dropdown option for '${elementName}' in '${pageName}' using options: ${optLog}`);
|
|
107
|
+
log.interact('Selecting dropdown option for "%s" in "%s" using options: %O', elementName, pageName, options ?? 'default (random)');
|
|
171
108
|
const locator = await this.repo.get(this.page, pageName, elementName);
|
|
172
109
|
return await this.interact.selectDropdown(locator, options);
|
|
173
110
|
}
|
|
174
|
-
/**
|
|
175
|
-
* Drags an element either to a specified target element, a target element with an offset, or by a coordinate offset.
|
|
176
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
177
|
-
* @param elementName - The specific element name in your repository (the element to drag).
|
|
178
|
-
* @param options - Configuration specifying a 'targetLocator', offsets, or both.
|
|
179
|
-
*/
|
|
180
111
|
async dragAndDrop(pageName, elementName, options) {
|
|
181
|
-
|
|
112
|
+
log.interact('Dragging and dropping "%s" in "%s"', elementName, pageName);
|
|
182
113
|
const locator = await this.repo.get(this.page, pageName, elementName);
|
|
183
114
|
await this.interact.dragAndDrop(locator, options);
|
|
184
115
|
}
|
|
185
|
-
/**
|
|
186
|
-
* Drags an element either to a specified target element, a target element with an offset, or by a coordinate offset.
|
|
187
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
188
|
-
* @param elementName - The specific element name in your repository (the element to drag).
|
|
189
|
-
* @param options - Configuration specifying a 'targetLocator', offsets, or both.
|
|
190
|
-
*/
|
|
191
116
|
async dragAndDropListedElement(pageName, elementName, elementText, options) {
|
|
192
|
-
|
|
117
|
+
log.interact('Dragging and dropping "%s" in "%s"', elementText, pageName);
|
|
193
118
|
const locator = await this.repo.getByText(this.page, pageName, elementName, elementText);
|
|
194
119
|
await this.interact.dragAndDrop(locator, options);
|
|
195
120
|
}
|
|
196
121
|
// ==========================================
|
|
197
122
|
// 📊 DATA EXTRACTION STEPS
|
|
198
123
|
// ==========================================
|
|
199
|
-
/**
|
|
200
|
-
* Safely retrieves and trims the text content of a specified element.
|
|
201
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
202
|
-
* @param elementName - The specific element name in your repository.
|
|
203
|
-
* @returns The trimmed string, or an empty string if null.
|
|
204
|
-
*/
|
|
205
124
|
async getText(pageName, elementName) {
|
|
206
|
-
|
|
125
|
+
log.extract('Getting text from "%s" in "%s"', elementName, pageName);
|
|
207
126
|
const locator = await this.repo.get(this.page, pageName, elementName);
|
|
208
127
|
return await this.extract.getText(locator);
|
|
209
128
|
}
|
|
210
|
-
/**
|
|
211
|
-
* Retrieves the value of a specified attribute (e.g., 'href', 'aria-pressed') from an element.
|
|
212
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
213
|
-
* @param elementName - The specific element name in your repository.
|
|
214
|
-
* @param attributeName - The name of the attribute to retrieve.
|
|
215
|
-
* @returns The attribute value as a string, or null if it doesn't exist.
|
|
216
|
-
*/
|
|
217
129
|
async getAttribute(pageName, elementName, attributeName) {
|
|
218
|
-
|
|
130
|
+
log.extract('Getting attribute "%s" from "%s" in "%s"', attributeName, elementName, pageName);
|
|
219
131
|
const locator = await this.repo.get(this.page, pageName, elementName);
|
|
220
132
|
return await this.extract.getAttribute(locator, attributeName);
|
|
221
133
|
}
|
|
222
134
|
// ==========================================
|
|
223
135
|
// ✅ VERIFICATION STEPS
|
|
224
136
|
// ==========================================
|
|
225
|
-
/**
|
|
226
|
-
* Asserts that a specified element is attached to the DOM and is visible.
|
|
227
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
228
|
-
* @param elementName - The specific element name in your repository.
|
|
229
|
-
*/
|
|
230
137
|
async verifyPresence(pageName, elementName) {
|
|
231
|
-
|
|
138
|
+
log.verify('Verifying presence of "%s" in "%s"', elementName, pageName);
|
|
232
139
|
const locator = await this.repo.get(this.page, pageName, elementName);
|
|
233
140
|
await this.verify.presence(locator);
|
|
234
141
|
}
|
|
235
|
-
/**
|
|
236
|
-
* Asserts that a specified element is hidden or completely detached from the DOM.
|
|
237
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
238
|
-
* @param elementName - The specific element name in your repository.
|
|
239
|
-
*/
|
|
240
142
|
async verifyAbsence(pageName, elementName) {
|
|
241
|
-
|
|
143
|
+
log.verify('Verifying absence of "%s" in "%s"', elementName, pageName);
|
|
242
144
|
const selector = await this.repo.getSelector(pageName, elementName);
|
|
243
145
|
await this.verify.absence(selector);
|
|
244
146
|
}
|
|
245
|
-
/**
|
|
246
|
-
* Asserts that the specified element exactly matches the expected text, or optionally checks if it is not empty.
|
|
247
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
248
|
-
* @param elementName - The specific element name in your repository.
|
|
249
|
-
* @param expectedText - The exact string expected inside the element (optional if checking 'notEmpty').
|
|
250
|
-
* @param options - Configuration to alter the verification behavior.
|
|
251
|
-
*/
|
|
252
147
|
async verifyText(pageName, elementName, expectedText, options) {
|
|
253
|
-
const logDetail = options?.notEmpty ?
|
|
254
|
-
|
|
148
|
+
const logDetail = options?.notEmpty ? 'is not empty' : `matches: "${expectedText}"`;
|
|
149
|
+
log.verify('Verifying text of "%s" in "%s" %s', elementName, pageName, logDetail);
|
|
255
150
|
const locator = await this.repo.get(this.page, pageName, elementName);
|
|
256
151
|
await this.verify.text(locator, expectedText, options);
|
|
257
152
|
}
|
|
258
|
-
/**
|
|
259
|
-
* Asserts the number of elements matching the locator based on the provided conditions.
|
|
260
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
261
|
-
* @param elementName - The specific element name in your repository.
|
|
262
|
-
* @param options - Configuration specifying 'exact', 'greaterThan', or 'lessThan' logic.
|
|
263
|
-
*/
|
|
264
153
|
async verifyCount(pageName, elementName, options) {
|
|
265
|
-
|
|
154
|
+
log.verify('Verifying count for "%s" in "%s" with options: %O', elementName, pageName, options);
|
|
266
155
|
const locator = await this.repo.get(this.page, pageName, elementName);
|
|
267
156
|
await this.verify.count(locator, options);
|
|
268
157
|
}
|
|
269
|
-
/**
|
|
270
|
-
* Performs a rigorous verification of one or more images.
|
|
271
|
-
* Asserts visibility, checks for a valid 'src' attribute, ensures a positive 'naturalWidth',
|
|
272
|
-
* and evaluates the native browser `decode()` promise to ensure the image isn't broken.
|
|
273
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
274
|
-
* @param elementName - The specific element name in your repository.
|
|
275
|
-
* @param scroll - Whether to smoothly scroll the image(s) into view before verifying (default: true).
|
|
276
|
-
*/
|
|
277
158
|
async verifyImages(pageName, elementName, scroll = true) {
|
|
278
|
-
|
|
159
|
+
log.verify('Verifying images for "%s" in "%s" (scroll: %s)', elementName, pageName, scroll);
|
|
279
160
|
const locator = await this.repo.get(this.page, pageName, elementName);
|
|
280
161
|
await this.verify.images(locator, scroll);
|
|
281
162
|
}
|
|
282
|
-
/**
|
|
283
|
-
* Asserts that the current browser URL contains the expected substring.
|
|
284
|
-
* @param text - The substring expected to be present within the active URL.
|
|
285
|
-
*/
|
|
286
163
|
async verifyUrlContains(text) {
|
|
287
|
-
|
|
164
|
+
log.verify('Verifying current URL contains: "%s"', text);
|
|
288
165
|
await this.verify.urlContains(text);
|
|
289
166
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
* @param elementName - The specific element name in your repository.
|
|
294
|
-
* @param state - The state to wait for: 'visible' | 'attached' | 'hidden' | 'detached'. Defaults to 'visible'.
|
|
295
|
-
*/
|
|
167
|
+
// ==========================================
|
|
168
|
+
// ⏳ WAIT STEPS
|
|
169
|
+
// ==========================================
|
|
296
170
|
async waitForState(pageName, elementName, state = 'visible') {
|
|
297
|
-
|
|
171
|
+
log.wait('Waiting for "%s" in "%s" to be "%s"', elementName, pageName, state);
|
|
298
172
|
const locator = await this.repo.get(this.page, pageName, elementName);
|
|
299
173
|
await this.utils.waitForState(locator, state);
|
|
300
174
|
}
|
|
301
|
-
/**
|
|
302
|
-
* Types text into a specific element character by character with a delay.
|
|
303
|
-
* Ideal for OTP inputs, search bars, or fields with sensitive 'keyup' listeners.
|
|
304
|
-
* @param pageName - The page or component grouping name in your repository.
|
|
305
|
-
* @param elementName - The specific element name in your repository.
|
|
306
|
-
* @param text - The string to type sequentially.
|
|
307
|
-
* @param delay - Optional delay between key presses in milliseconds (defaults to 100).
|
|
308
|
-
*/
|
|
309
175
|
async typeSequentially(pageName, elementName, text, delay = 100) {
|
|
310
|
-
|
|
176
|
+
log.interact('Typing "%s" sequentially into "%s" in "%s" (delay: %dms)', text, elementName, pageName, delay);
|
|
311
177
|
const locator = await this.repo.get(this.page, pageName, elementName);
|
|
312
178
|
await this.interact.typeSequentially(locator, text, delay);
|
|
313
179
|
}
|
|
@@ -35,16 +35,16 @@ class Utils {
|
|
|
35
35
|
catch (error) {
|
|
36
36
|
const message = error instanceof Error ? error.message : String(error);
|
|
37
37
|
if (message.includes('strict mode violation')) {
|
|
38
|
-
console.warn(`
|
|
38
|
+
console.warn(`Locator resolved to multiple elements. Waiting on first element instead.`);
|
|
39
39
|
try {
|
|
40
40
|
await locator.first().waitFor({ state, timeout: this.timeout });
|
|
41
41
|
}
|
|
42
42
|
catch {
|
|
43
|
-
console.warn(`
|
|
43
|
+
console.warn(`First element failed to reach state '${state}' within ${this.timeout}ms. Proceeding...`);
|
|
44
44
|
}
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
47
|
-
console.warn(`
|
|
47
|
+
console.warn(`Element failed to reach state '${state}' within ${this.timeout}ms. Proceeding...`);
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pw-element-interactions",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "A robust, readable interaction and assertion Facade for Playwright. Abstract away boilerplate into semantic, English-like methods, making your test automation framework cleaner, easier to maintain, and accessible to non-developers.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -8,10 +8,14 @@
|
|
|
8
8
|
"clean": "rm -rf dist",
|
|
9
9
|
"build": "npm run clean && tsc",
|
|
10
10
|
"test": "npx playwright test",
|
|
11
|
-
"prepublishOnly": "npm run build"
|
|
11
|
+
"prepublishOnly": "npm run build",
|
|
12
|
+
"postinstall": "node scripts/postinstall.js",
|
|
13
|
+
"test:quiet": "TESTER_DEBUG=false npx playwright test"
|
|
12
14
|
},
|
|
13
15
|
"files": [
|
|
14
|
-
"dist"
|
|
16
|
+
"dist",
|
|
17
|
+
"skills",
|
|
18
|
+
"scripts/postinstall.js"
|
|
15
19
|
],
|
|
16
20
|
"peerDependencies": {
|
|
17
21
|
"@civitas-cerebrum/context-store": ">=0.0.2",
|
|
@@ -21,6 +25,7 @@
|
|
|
21
25
|
"devDependencies": {
|
|
22
26
|
"@civitas-cerebrum/context-store": "^0.0.2",
|
|
23
27
|
"@playwright/test": "^1.58.2",
|
|
28
|
+
"@types/debug": "^4.1.13",
|
|
24
29
|
"@types/node": "^20.0.0",
|
|
25
30
|
"pw-element-repository": "^0.0.3",
|
|
26
31
|
"typescript": "^5.0.0"
|
|
@@ -44,5 +49,8 @@
|
|
|
44
49
|
"url": "git+https://github.com/Umutayb/pw-element-interactions.git"
|
|
45
50
|
},
|
|
46
51
|
"author": "Umut Ay Bora",
|
|
47
|
-
"license": "MIT"
|
|
52
|
+
"license": "MIT",
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"debug": "^4.4.3"
|
|
55
|
+
}
|
|
48
56
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
// Resolve the root of the consuming project (three levels up from
|
|
7
|
+
// node_modules/pw-element-interactions/scripts/postinstall.js)
|
|
8
|
+
const projectRoot = path.resolve(__dirname, '..', '..', '..');
|
|
9
|
+
|
|
10
|
+
const src = path.join(__dirname, '..', 'skills', 'pw-element-interactions.md');
|
|
11
|
+
const dest = path.join(projectRoot, '.claude', 'skills', 'pw-element-interactions', 'SKILL.md');
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
if (!fs.existsSync(src)) {
|
|
15
|
+
console.warn('[pw-element-interactions] Skill file not found, skipping.');
|
|
16
|
+
process.exit(0);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
20
|
+
fs.copyFileSync(src, dest);
|
|
21
|
+
console.log('[pw-element-interactions] ✔ Claude Code skill installed — restart Claude Code to pick it up.');
|
|
22
|
+
} catch (err) {
|
|
23
|
+
// Never fail the install — skill copy is best-effort
|
|
24
|
+
console.warn(`[pw-element-interactions] Could not install Claude Code skill: ${err.message}`);
|
|
25
|
+
}
|