playwright-cucumber-ts-steps 0.1.7 → 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 (79) hide show
  1. package/README.md +21 -11
  2. package/lib/actions/clickSteps.d.ts +251 -1
  3. package/lib/actions/clickSteps.js +297 -47
  4. package/lib/actions/cookieSteps.d.ts +18 -1
  5. package/lib/actions/cookieSteps.js +65 -0
  6. package/lib/actions/debugSteps.d.ts +14 -1
  7. package/lib/actions/debugSteps.js +18 -3
  8. package/lib/actions/elementFindSteps.d.ts +668 -1
  9. package/lib/actions/elementFindSteps.js +808 -94
  10. package/lib/actions/fillFormSteps.d.ts +69 -1
  11. package/lib/actions/fillFormSteps.js +178 -71
  12. package/lib/actions/index.d.ts +11 -0
  13. package/lib/actions/index.js +28 -0
  14. package/lib/actions/inputSteps.d.ts +218 -1
  15. package/lib/actions/inputSteps.js +303 -57
  16. package/lib/actions/interceptionSteps.d.ts +169 -1
  17. package/lib/actions/interceptionSteps.js +258 -38
  18. package/lib/actions/miscSteps.d.ts +645 -1
  19. package/lib/actions/miscSteps.js +898 -157
  20. package/lib/actions/mouseSteps.d.ts +143 -1
  21. package/lib/actions/mouseSteps.js +200 -32
  22. package/lib/actions/scrollSteps.d.ts +82 -1
  23. package/lib/actions/scrollSteps.js +116 -16
  24. package/lib/actions/storageSteps.d.ts +174 -1
  25. package/lib/actions/storageSteps.js +253 -33
  26. package/lib/assertions/buttonAndTextVisibilitySteps.d.ts +245 -1
  27. package/lib/assertions/buttonAndTextVisibilitySteps.js +342 -91
  28. package/lib/assertions/cookieSteps.d.ts +75 -1
  29. package/lib/assertions/cookieSteps.js +97 -29
  30. package/lib/assertions/elementSteps.d.ts +264 -1
  31. package/lib/assertions/elementSteps.js +376 -78
  32. package/lib/assertions/formInputSteps.d.ts +248 -1
  33. package/lib/assertions/formInputSteps.js +342 -79
  34. package/lib/assertions/index.d.ts +10 -0
  35. package/lib/assertions/index.js +27 -0
  36. package/lib/assertions/interceptionRequestsSteps.d.ts +353 -1
  37. package/lib/assertions/interceptionRequestsSteps.js +569 -177
  38. package/lib/assertions/locationSteps.d.ts +217 -1
  39. package/lib/assertions/locationSteps.js +287 -64
  40. package/lib/assertions/roleTestIdSteps.d.ts +159 -1
  41. package/lib/assertions/roleTestIdSteps.js +217 -22
  42. package/lib/assertions/semanticSteps.d.ts +176 -1
  43. package/lib/assertions/semanticSteps.js +245 -60
  44. package/lib/assertions/storageSteps.d.ts +149 -1
  45. package/lib/assertions/storageSteps.js +201 -65
  46. package/lib/assertions/visualSteps.d.ts +74 -1
  47. package/lib/assertions/visualSteps.js +178 -45
  48. package/lib/custom_setups/loginHooks.js +19 -2
  49. package/lib/helpers/world.d.ts +3 -0
  50. package/lib/helpers/world.js +11 -5
  51. package/lib/index.d.ts +3 -21
  52. package/lib/index.js +3 -23
  53. package/package.json +9 -2
  54. package/src/actions/clickSteps.ts +364 -142
  55. package/src/actions/cookieSteps.ts +66 -0
  56. package/src/actions/debugSteps.ts +17 -3
  57. package/src/actions/elementFindSteps.ts +822 -117
  58. package/src/actions/fillFormSteps.ts +234 -177
  59. package/src/actions/index.ts +12 -0
  60. package/src/actions/inputSteps.ts +318 -82
  61. package/src/actions/interceptionSteps.ts +295 -57
  62. package/src/actions/miscSteps.ts +984 -254
  63. package/src/actions/mouseSteps.ts +212 -55
  64. package/src/actions/scrollSteps.ts +114 -16
  65. package/src/actions/storageSteps.ts +267 -42
  66. package/src/assertions/buttonAndTextVisibilitySteps.ts +353 -95
  67. package/src/assertions/cookieSteps.ts +115 -36
  68. package/src/assertions/elementSteps.ts +414 -85
  69. package/src/assertions/formInputSteps.ts +375 -108
  70. package/src/assertions/index.ts +11 -0
  71. package/src/assertions/interceptionRequestsSteps.ts +619 -195
  72. package/src/assertions/locationSteps.ts +280 -64
  73. package/src/assertions/roleTestIdSteps.ts +244 -26
  74. package/src/assertions/semanticSteps.ts +257 -69
  75. package/src/assertions/storageSteps.ts +234 -73
  76. package/src/assertions/visualSteps.ts +245 -68
  77. package/src/custom_setups/loginHooks.ts +21 -2
  78. package/src/helpers/world.ts +30 -4
  79. package/src/index.ts +4 -25
@@ -6,76 +6,339 @@ import { CustomWorld } from "../helpers/world";
6
6
  // WHEN I FIND ELEMENT(S)
7
7
  // =============================
8
8
 
9
- When("I find element by selector {string}", async function (this: CustomWorld, selector: string) {
9
+ /**
10
+ * Finds an element by CSS selector and stores it as the current element.
11
+ *
12
+ * ```gherkin
13
+ * When I find element by selector {string}
14
+ * ```
15
+ *
16
+ * @example
17
+ * When I find element by selector ".my-class"
18
+ *
19
+ * @remarks
20
+ * This step sets the {@link CustomWorld.element | current element} in the test context.
21
+ * Subsequent steps can then interact with this single element. An `expect` assertion is included
22
+ * to ensure exactly one element is found.
23
+ */
24
+ export async function When_I_find_element_by_selector(this: CustomWorld, selector: string) {
10
25
  this.element = this.page.locator(selector);
11
26
  await expect(this.element).toHaveCount(1);
12
- });
13
- When("I find link by text {string}", async function (text: string) {
27
+ }
28
+ When("I find element by selector {string}", When_I_find_element_by_selector);
29
+
30
+ /**
31
+ * Finds a link by its text and stores it as the current locator.
32
+ *
33
+ * ```gherkin
34
+ * When I find link by text {string}
35
+ * ```
36
+ *
37
+ * @example
38
+ * When I find link by text "Home"
39
+ *
40
+ * @remarks
41
+ * This step sets the {@link CustomWorld.currentLocator | currentLocator} in the test context,
42
+ * allowing subsequent steps to operate on the found link. For example:
43
+ *
44
+ * ```gherkin
45
+ * When I find link by text "Products"
46
+ * And I click current element
47
+ * ```
48
+ */
49
+ export async function When_I_find_link_by_text(this: CustomWorld, text: string) {
14
50
  this.currentLocator = this.getScope().getByRole("link", { name: text });
15
- });
51
+ }
52
+ When("I find link by text {string}", When_I_find_link_by_text);
16
53
 
17
- When("I find heading by text {string}", async function (text: string) {
54
+ /**
55
+ * Finds a heading by its text and stores it as the current locator.
56
+ *
57
+ * ```gherkin
58
+ * When I find heading by text {string}
59
+ * ```
60
+ *
61
+ * @example
62
+ * When I find heading by text "Welcome"
63
+ *
64
+ * @remarks
65
+ * This step sets the {@link CustomWorld.currentLocator | currentLocator} in the test context,
66
+ * allowing subsequent steps to operate on the found heading.
67
+ */
68
+ export async function When_I_find_heading_by_text(this: CustomWorld, text: string) {
18
69
  this.currentLocator = this.getScope().getByRole("heading", { name: text });
19
- });
70
+ }
71
+ When("I find heading by text {string}", When_I_find_heading_by_text);
20
72
 
21
- When("I find headings by text {string}", async function (text: string) {
73
+ /**
74
+ * Finds all headings by text and stores them as the current locator.
75
+ *
76
+ * ```gherkin
77
+ * When I find headings by text {string}
78
+ * ```
79
+ *
80
+ * @example
81
+ * When I find headings by text "Section"
82
+ *
83
+ * @remarks
84
+ * This step sets the {@link CustomWorld.currentLocator | currentLocator} in the test context
85
+ * to a locator that matches *all* headings with the specified text. This is useful for
86
+ * verifying multiple instances or iterating.
87
+ */
88
+ export async function When_I_find_headings_by_text(this: CustomWorld, text: string) {
22
89
  this.currentLocator = this.getScope().getByRole("heading", { name: text });
23
- });
24
- When("I find elements by selector {string}", async function (this: CustomWorld, selector: string) {
90
+ }
91
+ When("I find headings by text {string}", When_I_find_headings_by_text);
92
+
93
+ /**
94
+ * Finds all elements by CSS selector and stores them.
95
+ *
96
+ * ```gherkin
97
+ * When I find elements by selector {string}
98
+ * ```
99
+ *
100
+ * @example
101
+ * When I find elements by selector ".item"
102
+ *
103
+ * @remarks
104
+ * This step sets the {@link CustomWorld.elements | elements} property in the test context
105
+ * to a Playwright `Locator` representing all matching elements. You can then use steps like
106
+ * "When I get first element" to pick a specific one.
107
+ */
108
+ export async function When_I_find_elements_by_selector(this: CustomWorld, selector: string) {
25
109
  this.elements = this.page.locator(selector);
26
110
  const count = await this.elements.count();
27
111
  this.log?.(`Found ${count} elements with selector ${selector}`);
28
- });
112
+ }
113
+ When("I find elements by selector {string}", When_I_find_elements_by_selector);
29
114
 
30
- When("I find element by text {string}", async function (this: CustomWorld, text: string) {
115
+ /**
116
+ * Finds an element by its exact text and stores it as the current element.
117
+ *
118
+ * ```gherkin
119
+ * When I find element by text {string}
120
+ * ```
121
+ *
122
+ * @example
123
+ * When I find element by text "Submit"
124
+ *
125
+ * @remarks
126
+ * This step uses `page.getByText` with `exact: true` to find an element
127
+ * that matches the text precisely. It stores the result in {@link CustomWorld.element | this.element}.
128
+ * An `expect` assertion is included to ensure exactly one element is found.
129
+ */
130
+ export async function When_I_find_element_by_text(this: CustomWorld, text: string) {
31
131
  this.element = this.page.getByText(text, { exact: true });
32
132
  await expect(this.element).toHaveCount(1);
33
- });
133
+ }
134
+ When("I find element by text {string}", When_I_find_element_by_text);
34
135
 
35
- When("I find element by title {string}", async function (this: CustomWorld, title: string) {
136
+ /**
137
+ * Finds an element by its title attribute and stores it as the current element.
138
+ *
139
+ * ```gherkin
140
+ * When I find element by title {string}
141
+ * ```
142
+ *
143
+ * @example
144
+ * When I find element by title "Tooltip"
145
+ *
146
+ * @remarks
147
+ * This step uses `page.getByTitle` to find an element with the specified `title` attribute.
148
+ * It stores the result in {@link CustomWorld.element | this.element}.
149
+ * An `expect` assertion is included to ensure exactly one element is found.
150
+ */
151
+ export async function When_I_find_element_by_title(this: CustomWorld, title: string) {
36
152
  this.element = this.page.getByTitle(title);
37
153
  await expect(this.element).toHaveCount(1);
38
- });
154
+ }
155
+ When("I find element by title {string}", When_I_find_element_by_title);
39
156
 
40
- When("I find element by testid {string}", async function (this: CustomWorld, testid: string) {
157
+ /**
158
+ * Finds an element by its test id and stores it as the current element.
159
+ *
160
+ * ```gherkin
161
+ * When I find element by testid {string}
162
+ * ```
163
+ *
164
+ * @example
165
+ * When I find element by testid "main-content"
166
+ *
167
+ * @remarks
168
+ * This step uses `page.getByTestId` to find an element based on its
169
+ * `data-testid` attribute (or configured test ID attribute).
170
+ * It stores the result in {@link CustomWorld.element | this.element}.
171
+ * An `expect` assertion is included to ensure exactly one element is found.
172
+ */
173
+ export async function When_I_find_element_by_testid(this: CustomWorld, testid: string) {
41
174
  this.element = this.page.getByTestId(testid);
42
175
  await expect(this.element).toHaveCount(1);
43
- });
176
+ }
177
+ When("I find element by testid {string}", When_I_find_element_by_testid);
44
178
 
45
- When("I find element by role {string}", async function (this: CustomWorld, role: string) {
179
+ /**
180
+ * Finds an element by its role and stores it as the current element.
181
+ *
182
+ * ```gherkin
183
+ * When I find element by role {string}
184
+ * ```
185
+ *
186
+ * @example
187
+ * When I find element by role "button"
188
+ *
189
+ * @remarks
190
+ * This step uses `page.getByRole` to find an element based on its
191
+ * ARIA role. It stores the result in {@link CustomWorld.element | this.element}.
192
+ * An `expect` assertion is included to ensure exactly one element is found.
193
+ */
194
+ export async function When_I_find_element_by_role(this: CustomWorld, role: string) {
46
195
  this.element = this.page.getByRole(role as any);
47
196
  await expect(this.element).toHaveCount(1);
48
- });
197
+ }
198
+ When("I find element by role {string}", When_I_find_element_by_role);
49
199
 
50
- When(
51
- "I find element by placeholder text {string}",
52
- async function (this: CustomWorld, text: string) {
53
- this.element = this.page.getByPlaceholder(text);
54
- await expect(this.element).toHaveCount(1);
55
- }
56
- );
200
+ /**
201
+ * Finds an element by its placeholder text and stores it as the current element.
202
+ *
203
+ * ```gherkin
204
+ * When I find element by placeholder text {string}
205
+ * ```
206
+ *
207
+ * @example
208
+ * When I find element by placeholder text "Enter your name"
209
+ *
210
+ * @remarks
211
+ * This step uses `page.getByPlaceholder` to find an input or textarea element
212
+ * based on its `placeholder` attribute. It stores the result in {@link CustomWorld.element | this.element}.
213
+ * An `expect` assertion is included to ensure exactly one element is found.
214
+ */
215
+ export async function When_I_find_element_by_placeholder_text(this: CustomWorld, text: string) {
216
+ this.element = this.page.getByPlaceholder(text);
217
+ await expect(this.element).toHaveCount(1);
218
+ }
219
+ When("I find element by placeholder text {string}", When_I_find_element_by_placeholder_text);
57
220
 
58
- When("I find element by label text {string}", async function (this: CustomWorld, label: string) {
221
+ /**
222
+ * Finds an element by its label text and stores it as the current element.
223
+ *
224
+ * ```gherkin
225
+ * When I find element by label text {string}
226
+ * ```
227
+ *
228
+ * @example
229
+ * When I find element by label text "Username"
230
+ *
231
+ * @remarks
232
+ * This step uses `page.getByLabel` to find an element associated with a given
233
+ * label text (e.g., using a `<label for="...">` or wrapping the input).
234
+ * It stores the result in {@link CustomWorld.element | this.element}.
235
+ * An `expect` assertion is included to ensure exactly one element is found.
236
+ */
237
+ export async function When_I_find_element_by_label_text(this: CustomWorld, label: string) {
59
238
  this.element = this.page.getByLabel(label);
60
239
  await expect(this.element).toHaveCount(1);
61
- });
62
- When("I find elements by label text {string}", async function (label: string) {
240
+ }
241
+ When("I find element by label text {string}", When_I_find_element_by_label_text);
242
+
243
+ /**
244
+ * Finds all elements by label text and stores them as the current locator.
245
+ *
246
+ * ```gherkin
247
+ * When I find elements by label text {string}
248
+ * ```
249
+ *
250
+ * @example
251
+ * When I find elements by label text "Username"
252
+ *
253
+ * @remarks
254
+ * This step sets the {@link CustomWorld.currentLocator | currentLocator} to a locator
255
+ * representing all elements associated with the given label text. This is useful
256
+ * for scenarios where multiple inputs might share a similar label.
257
+ */
258
+ export async function When_I_find_elements_by_label_text(this: CustomWorld, label: string) {
63
259
  this.currentLocator = this.getScope().getByLabel(label);
64
- });
65
- When("I find element by alt text {string}", async function (this: CustomWorld, alt: string) {
260
+ }
261
+ When("I find elements by label text {string}", When_I_find_elements_by_label_text);
262
+
263
+ /**
264
+ * Finds an element by its alt text and stores it as the current element.
265
+ *
266
+ * ```gherkin
267
+ * When I find element by alt text {string}
268
+ * ```
269
+ *
270
+ * @example
271
+ * When I find element by alt text "Logo"
272
+ *
273
+ * @remarks
274
+ * This step uses `page.getByAltText` to find elements, typically images,
275
+ * based on their `alt` attribute. It stores the result in {@link CustomWorld.element | this.element}.
276
+ * An `expect` assertion is included to ensure exactly one element is found.
277
+ */
278
+ export async function When_I_find_element_by_alt_text(this: CustomWorld, alt: string) {
66
279
  this.element = this.page.getByAltText(alt);
67
280
  await expect(this.element).toHaveCount(1);
68
- });
281
+ }
282
+ When("I find element by alt text {string}", When_I_find_element_by_alt_text);
69
283
 
70
- When("I find element by name {string}", async function (this: CustomWorld, name: string) {
284
+ /**
285
+ * Finds an input element by its name attribute and stores it as the current element.
286
+ *
287
+ * ```gherkin
288
+ * When I find element by name {string}
289
+ * ```
290
+ *
291
+ * @example
292
+ * When I find element by name "email"
293
+ *
294
+ * @remarks
295
+ * This step uses `page.getByRole("textbox", { name })` to specifically find an input
296
+ * element (or similar interactive element) with the given `name` attribute.
297
+ * It stores the result in {@link CustomWorld.element | this.element}.
298
+ * An `expect` assertion is included to ensure exactly one element is found.
299
+ */
300
+ export async function When_I_find_element_by_name(this: CustomWorld, name: string) {
71
301
  this.element = this.page.getByRole("textbox", { name });
72
302
  await expect(this.element).toHaveCount(1);
73
- });
74
- When("I find elements by name {string}", async function (name: string) {
303
+ }
304
+ When("I find element by name {string}", When_I_find_element_by_name);
305
+
306
+ /**
307
+ * Finds all elements by name attribute and stores them as the current locator.
308
+ *
309
+ * ```gherkin
310
+ * When I find elements by name {string}
311
+ * ```
312
+ *
313
+ * @example
314
+ * When I find elements by name "email"
315
+ *
316
+ * @remarks
317
+ * This step sets the {@link CustomWorld.currentLocator | currentLocator} to a CSS locator
318
+ * targeting all elements with the specified `name` attribute.
319
+ */
320
+ export async function When_I_find_elements_by_name(this: CustomWorld, name: string) {
75
321
  this.currentLocator = this.getScope().locator(`[name="${name}"]`);
76
- });
322
+ }
323
+ When("I find elements by name {string}", When_I_find_elements_by_name);
77
324
 
78
- When("I find buttons by text {string}", async function (this: CustomWorld, buttonText: string) {
325
+ /**
326
+ * Finds all buttons by text (supports alias) and stores them as elements.
327
+ *
328
+ * ```gherkin
329
+ * When I find buttons by text {string}
330
+ * ```
331
+ *
332
+ * @example
333
+ * When I find buttons by text "Save"
334
+ * When I find buttons by text "@buttonAlias"
335
+ *
336
+ * @remarks
337
+ * This step uses `page.getByRole("button", { name })` to find all buttons
338
+ * matching the provided text. It supports resolving an alias from `this.data`.
339
+ * The result is stored in {@link CustomWorld.elements | this.elements}.
340
+ */
341
+ export async function When_I_find_buttons_by_text(this: CustomWorld, buttonText: string) {
79
342
  // 🧠 Resolve alias
80
343
  if (buttonText.startsWith("@")) {
81
344
  const alias = buttonText.slice(1);
@@ -92,143 +355,565 @@ When("I find buttons by text {string}", async function (this: CustomWorld, butto
92
355
  });
93
356
 
94
357
  this.log?.(`🔘 Stored all buttons matching text "${buttonText}"`);
95
- });
358
+ }
359
+ When("I find buttons by text {string}", When_I_find_buttons_by_text);
360
+
96
361
  // =============================
97
362
  // WHEN I GET ELEMENT(S)
98
363
  // =============================
99
364
 
100
- When("I get element by selector {string}", async function (this: CustomWorld, selector: string) {
365
+ /**
366
+ * Gets the first element matching the selector and stores it as the current element.
367
+ *
368
+ * ```gherkin
369
+ * When I get element by selector {string}
370
+ * ```
371
+ *
372
+ * @example
373
+ * When I get element by selector ".item"
374
+ *
375
+ * @remarks
376
+ * This step targets a single element using a CSS selector and sets it as the
377
+ * {@link CustomWorld.element | current element}. It's useful when you expect only
378
+ * one element to match or you only need the first one.
379
+ */
380
+ export async function When_I_get_element_by_selector(this: CustomWorld, selector: string) {
101
381
  this.element = this.page.locator(selector).first();
102
- });
382
+ }
383
+ When("I get element by selector {string}", When_I_get_element_by_selector);
103
384
 
104
- When("I get elements by selector {string}", async function (this: CustomWorld, selector: string) {
385
+ /**
386
+ * Gets all elements matching the selector and stores them.
387
+ *
388
+ * ```gherkin
389
+ * When I get elements by selector {string}
390
+ * ```
391
+ *
392
+ * @example
393
+ * When I get elements by selector ".item"
394
+ *
395
+ * @remarks
396
+ * This step sets the {@link CustomWorld.elements | elements} property to a
397
+ * Playwright `Locator` representing all elements that match the given CSS selector.
398
+ * You can then use other steps like "When I get first element" to work with specific items.
399
+ */
400
+ export async function When_I_get_elements_by_selector(this: CustomWorld, selector: string) {
105
401
  this.elements = this.page.locator(selector);
106
- });
402
+ }
403
+ When("I get elements by selector {string}", When_I_get_elements_by_selector);
107
404
 
108
- When("I get first element", async function (this: CustomWorld) {
109
- if (!this.elements) throw new Error("No element collection found");
405
+ /**
406
+ * Gets the first element from the stored elements collection.
407
+ *
408
+ * ```gherkin
409
+ * When I get first element
410
+ * ```
411
+ *
412
+ * @example
413
+ * When I get first element
414
+ *
415
+ * @remarks
416
+ * This step requires a preceding step that populates {@link CustomWorld.elements | this.elements}
417
+ * (e.g., "When I find elements by selector"). It then selects the very first
418
+ * element from that collection and sets it as the {@link CustomWorld.element | current element}.
419
+ */
420
+ export async function When_I_get_first_element(this: CustomWorld) {
421
+ if (!this.elements)
422
+ throw new Error("No element collection found. Use a 'find elements' step first.");
110
423
  this.element = this.elements.first();
111
- });
424
+ }
425
+ When("I get first element", When_I_get_first_element);
112
426
 
113
- When("I get last element", async function (this: CustomWorld) {
114
- if (!this.elements) throw new Error("No element collection found");
427
+ /**
428
+ * Gets the last element from the stored elements collection.
429
+ *
430
+ * ```gherkin
431
+ * When I get last element
432
+ * ```
433
+ *
434
+ * @example
435
+ * When I get last element
436
+ *
437
+ * @remarks
438
+ * This step requires a preceding step that populates {@link CustomWorld.elements | this.elements}
439
+ * (e.g., "When I find elements by selector"). It then selects the very last
440
+ * element from that collection and sets it as the {@link CustomWorld.element | current element}.
441
+ */
442
+ export async function When_I_get_last_element(this: CustomWorld) {
443
+ if (!this.elements)
444
+ throw new Error("No element collection found. Use a 'find elements' step first.");
115
445
  this.element = this.elements.last();
116
- });
446
+ }
447
+ When("I get last element", When_I_get_last_element);
117
448
 
118
- When(/^I get (\d+)(?:st|nd|rd|th) element$/, async function (this: CustomWorld, index: number) {
119
- if (!this.elements) throw new Error("No elements stored to pick from");
449
+ /**
450
+ * Gets the nth element (1-based index) from the stored elements.
451
+ *
452
+ * ```gherkin
453
+ * When I get {int}st element
454
+ * When I get {int}nd element
455
+ * When I get {int}rd element
456
+ * When I get {int}th element
457
+ * ```
458
+ *
459
+ * @example
460
+ * When I get 2nd element
461
+ *
462
+ * @remarks
463
+ * This step requires a preceding step that populates {@link CustomWorld.elements | this.elements}
464
+ * (e.g., "When I find elements by selector"). It then selects the element at the
465
+ * specified 1-based index from that collection and sets it as the {@link CustomWorld.element | current element}.
466
+ * Error handling is included for out-of-bounds indices.
467
+ */
468
+ export async function When_I_get_nth_element(this: CustomWorld, index: number) {
469
+ if (!this.elements)
470
+ throw new Error("No elements stored to pick from. Use a 'find elements' step first.");
120
471
  const count = await this.elements.count();
121
472
  if (index < 1 || index > count) {
122
- throw new Error(`Cannot get element ${index} — only ${count} found`);
473
+ throw new Error(`Cannot get element ${index} — only ${count} found.`);
123
474
  }
124
- this.element = this.elements.nth(index - 1);
475
+ this.element = this.elements.nth(index - 1); // Playwright is 0-based
125
476
  this.log?.(`Selected ${index} element from stored elements`);
126
- });
477
+ }
478
+ When(/^I get (\d+)(?:st|nd|rd|th) element$/, When_I_get_nth_element);
127
479
 
128
- When("I find elements by role {string}", async function (this: CustomWorld, role: string) {
480
+ /**
481
+ * Finds all elements by role and stores them in the elements collection.
482
+ *
483
+ * ```gherkin
484
+ * When I find elements by role {string}
485
+ * ```
486
+ *
487
+ * @example
488
+ * When I find elements by role "button"
489
+ *
490
+ * @remarks
491
+ * This step uses `page.getByRole` to find all elements with a specific ARIA role
492
+ * and stores them in {@link CustomWorld.elements | this.elements}.
493
+ * It includes a check to ensure at least one element is found.
494
+ */
495
+ export async function When_I_find_elements_by_role(this: CustomWorld, role: string) {
129
496
  const locator = this.page.getByRole(role as any);
130
497
  const count = await locator.count();
131
498
 
132
499
  if (count === 0) {
133
- throw new Error(`No elements found with role "${role}"`);
500
+ throw new Error(`No elements found with role "${role}".`);
134
501
  }
135
502
 
136
503
  this.elements = locator;
137
504
  this.log?.(`Stored ${count} elements with role "${role}"`);
138
- });
505
+ }
506
+ When("I find elements by role {string}", When_I_find_elements_by_role);
139
507
 
140
- When("I get {int}rd element", async function (this: CustomWorld, index: number) {
141
- if (!this.elements) throw new Error("No element collection found");
508
+ /**
509
+ * Gets the nth element (0-based index) from the stored elements.
510
+ *
511
+ * ```gherkin
512
+ * When I get {int}rd element
513
+ * ```
514
+ *
515
+ * @example
516
+ * When I get 3rd element
517
+ *
518
+ * @remarks
519
+ * This step requires a preceding step that populates {@link CustomWorld.elements | this.elements}.
520
+ * It selects the element at the specified **0-based index** from the collection
521
+ * and sets it as the {@link CustomWorld.element | current element}.
522
+ *
523
+ * **Note:** Consider using `When I get {int}(?:st|nd|rd|th) element` for a 1-based index
524
+ * which is often more user-friendly in Gherkin.
525
+ */
526
+ export async function When_I_get_int_rd_element(this: CustomWorld, index: number) {
527
+ if (!this.elements)
528
+ throw new Error("No element collection found. Use a 'find elements' step first.");
142
529
  this.element = this.elements.nth(index);
143
- });
530
+ }
531
+ When("I get {int}rd element", When_I_get_int_rd_element); // This step pattern is a bit specific; the regex-based one is more general.
144
532
 
145
- When("I get focused element", async function (this: CustomWorld) {
533
+ /**
534
+ * Gets the currently focused element and stores it as the current element.
535
+ *
536
+ * ```gherkin
537
+ * When I get focused element
538
+ * ```
539
+ *
540
+ * @example
541
+ * When I get focused element
542
+ *
543
+ * @remarks
544
+ * This step uses `page.evaluateHandle` to find the `document.activeElement`
545
+ * (the currently focused element in the browser DOM) and sets it as the
546
+ * {@link CustomWorld.element | current element}. This is useful for testing focus management.
547
+ */
548
+ export async function When_I_get_focused_element(this: CustomWorld) {
146
549
  this.element = (await this.page.evaluateHandle(
147
550
  () => document.activeElement
148
551
  )) as unknown as Locator;
149
- });
150
- When("I store element text as {string}", async function (this: CustomWorld, alias: string) {
552
+ }
553
+ When("I get focused element", When_I_get_focused_element);
554
+
555
+ /**
556
+ * Stores the text content of the current element as an alias in the test data.
557
+ *
558
+ * ```gherkin
559
+ * When I store element text as {string}
560
+ * ```
561
+ *
562
+ * @example
563
+ * When I store element text as "greeting"
564
+ *
565
+ * @remarks
566
+ * This step requires a preceding step that sets the {@link CustomWorld.element | current element}.
567
+ * It retrieves the `textContent` of that element and stores it in
568
+ * {@link CustomWorld.data | this.data} under the provided alias, allowing it to be
569
+ * reused in subsequent steps (e.g., in assertions or input fields).
570
+ */
571
+ export async function When_I_store_element_text_as(this: CustomWorld, alias: string) {
151
572
  const element = this.element;
152
- if (!element) throw new Error("No element selected");
573
+ if (!element) throw new Error("No element selected. Use a 'find element' step first.");
153
574
  const text = await element.textContent();
154
575
  this.data[alias] = text?.trim();
155
576
  this.log?.(`Stored text "${text}" as "${alias}"`);
156
- });
157
- When("I find textarea by label text {string}", async function (this: CustomWorld, label: string) {
577
+ }
578
+ When("I store element text as {string}", When_I_store_element_text_as);
579
+
580
+ // =============================
581
+ // WHEN I FIND TEXTAREA(S)
582
+ // =============================
583
+
584
+ /**
585
+ * Finds a textarea by its label text and stores it as the current element.
586
+ *
587
+ * ```gherkin
588
+ * When I find textarea by label text {string}
589
+ * ```
590
+ *
591
+ * @example
592
+ * When I find textarea by label text "Description"
593
+ *
594
+ * @remarks
595
+ * This step uses Playwright's `getByLabel` to locate a textarea associated
596
+ * with the given label text. It sets the found textarea as the
597
+ * {@link CustomWorld.element | current element}.
598
+ */
599
+ export async function When_I_find_textarea_by_label_text(this: CustomWorld, label: string) {
158
600
  this.element = this.page.getByLabel(label);
159
601
  this.log?.(`Stored textarea with label "${label}"`);
160
- });
161
- When(
162
- "I find textarea by placeholder text {string}",
163
- async function (this: CustomWorld, placeholder: string) {
164
- this.element = this.page.getByPlaceholder(placeholder);
165
- this.log?.(`Stored textarea with placeholder "${placeholder}"`);
166
- }
167
- );
168
- When("I find textareas by label text {string}", async function (this: CustomWorld, label: string) {
602
+ }
603
+ When("I find textarea by label text {string}", When_I_find_textarea_by_label_text);
604
+
605
+ /**
606
+ * Finds a textarea by its placeholder text and stores it as the current element.
607
+ *
608
+ * ```gherkin
609
+ * When I find textarea by placeholder text {string}
610
+ * ```
611
+ *
612
+ * @example
613
+ * When I find textarea by placeholder text "Type here"
614
+ *
615
+ * @remarks
616
+ * This step uses Playwright's `getByPlaceholder` to locate a textarea
617
+ * based on its `placeholder` attribute. It sets the found textarea as the
618
+ * {@link CustomWorld.element | current element}.
619
+ */
620
+ export async function When_I_find_textarea_by_placeholder_text(
621
+ this: CustomWorld,
622
+ placeholder: string
623
+ ) {
624
+ this.element = this.page.getByPlaceholder(placeholder);
625
+ this.log?.(`Stored textarea with placeholder "${placeholder}"`);
626
+ }
627
+ When("I find textarea by placeholder text {string}", When_I_find_textarea_by_placeholder_text);
628
+
629
+ /**
630
+ * Finds all textareas by label text and stores them as elements.
631
+ *
632
+ * ```gherkin
633
+ * When I find textareas by label text {string}
634
+ * ```
635
+ *
636
+ * @example
637
+ * When I find textareas by label text "Comment"
638
+ *
639
+ * @remarks
640
+ * This step uses a CSS selector to find all textareas associated with the
641
+ * given label text and stores them in the {@link CustomWorld.elements | elements}
642
+ * collection.
643
+ */
644
+ export async function When_I_find_textareas_by_label_text(this: CustomWorld, label: string) {
169
645
  this.elements = this.page.locator(`label:has-text("${label}") + textarea`);
170
646
  this.log?.(`Stored multiple textareas with label "${label}"`);
171
- });
172
- When("I find textarea by name {string}", async function (this: CustomWorld, name: string) {
647
+ }
648
+ When("I find textareas by label text {string}", When_I_find_textareas_by_label_text);
649
+
650
+ /**
651
+ * Finds a textarea by its name attribute and stores it as the current element.
652
+ *
653
+ * ```gherkin
654
+ * When I find textarea by name {string}
655
+ * ```
656
+ *
657
+ * @example
658
+ * When I find textarea by name "bio"
659
+ *
660
+ * @remarks
661
+ * This step uses a CSS selector to locate a textarea based on its `name` attribute.
662
+ * It sets the found textarea as the {@link CustomWorld.element | current element}.
663
+ */
664
+ export async function When_I_find_textarea_by_name(this: CustomWorld, name: string) {
173
665
  this.element = this.page.locator(`textarea[name="${name}"]`);
174
666
  this.log?.(`Stored textarea with name "${name}"`);
175
- });
176
- When("I find textareas by ID {string}", async function (this: CustomWorld, id: string) {
667
+ }
668
+ When("I find textarea by name {string}", When_I_find_textarea_by_name);
669
+
670
+ /**
671
+ * Finds all textareas by ID and stores them as elements.
672
+ *
673
+ * ```gherkin
674
+ * When I find textareas by ID {string}
675
+ * ```
676
+ *
677
+ * @example
678
+ * When I find textareas by ID "my-textarea"
679
+ *
680
+ * @remarks
681
+ * This step uses a CSS selector to find all textareas with the specified ID
682
+ * and stores them in the {@link CustomWorld.elements | elements} collection.
683
+ */
684
+ export async function When_I_find_textareas_by_ID(this: CustomWorld, id: string) {
177
685
  this.elements = this.page.locator(`textarea#${id}`);
178
686
  this.log?.(`Stored multiple textareas with ID "${id}"`);
179
- });
180
- When("I find input by ID {string}", async function (this: CustomWorld, id: string) {
687
+ }
688
+ When("I find textareas by ID {string}", When_I_find_textareas_by_ID);
689
+
690
+ /**
691
+ * Finds all textareas by placeholder text and stores them as elements.
692
+ *
693
+ * ```gherkin
694
+ * When I find textareas by placeholder text {string}
695
+ * ```
696
+ *
697
+ * @example
698
+ * When I find textareas by placeholder text "Type here"
699
+ *
700
+ * @remarks
701
+ * This step uses a CSS selector to find all textareas with the specified
702
+ * `placeholder` attribute and stores them in the {@link CustomWorld.elements | elements}
703
+ * collection.
704
+ */
705
+ export async function When_I_find_textareas_by_placeholder_text_multiple(
706
+ this: CustomWorld,
707
+ placeholder: string
708
+ ) {
709
+ this.elements = this.page.locator(`textarea[placeholder="${placeholder}"]`);
710
+ this.log?.(`Stored multiple textareas with placeholder "${placeholder}"`);
711
+ }
712
+ When(
713
+ "I find textareas by placeholder text {string}",
714
+ When_I_find_textareas_by_placeholder_text_multiple
715
+ );
716
+
717
+ // =============================
718
+ // WHEN I FIND INPUT(S)
719
+ // =============================
720
+
721
+ /**
722
+ * Finds an input by its ID and stores it as the current element.
723
+ *
724
+ * ```gherkin
725
+ * When I find input by ID {string}
726
+ * ```
727
+ *
728
+ * @example
729
+ * When I find input by ID "email"
730
+ *
731
+ * @remarks
732
+ * This step uses a CSS selector to locate an input element by its ID.
733
+ * It sets the found input as the {@link CustomWorld.element | current element}.
734
+ */
735
+ export async function When_I_find_input_by_ID(this: CustomWorld, id: string) {
181
736
  this.element = this.page.locator(`input#${id}`);
182
737
  this.log?.(`Stored input with ID "${id}"`);
183
- });
184
- When("I find inputs by ID {string}", async function (this: CustomWorld, id: string) {
738
+ }
739
+ When("I find input by ID {string}", When_I_find_input_by_ID);
740
+
741
+ /**
742
+ * Finds all inputs by ID and stores them as elements.
743
+ *
744
+ * ```gherkin
745
+ * When I find inputs by ID {string}
746
+ * ```
747
+ *
748
+ * @example
749
+ * When I find inputs by ID "email"
750
+ *
751
+ * @remarks
752
+ * This step uses a CSS selector to find all input elements with the specified ID
753
+ * and stores them in the {@link CustomWorld.elements | elements} collection.
754
+ */
755
+ export async function When_I_find_inputs_by_ID(this: CustomWorld, id: string) {
185
756
  this.elements = this.page.locator(`input#${id}`);
186
757
  this.log?.(`Stored multiple inputs with ID "${id}"`);
187
- });
188
- When(
189
- "I find textareas by placeholder text {string}",
190
- async function (this: CustomWorld, placeholder: string) {
191
- this.elements = this.page.locator(`textarea[placeholder="${placeholder}"]`);
192
- this.log?.(`Stored multiple textareas with placeholder "${placeholder}"`);
193
- }
194
- );
195
- When("I find input by label text {string}", async function (this: CustomWorld, label: string) {
758
+ }
759
+ When("I find inputs by ID {string}", When_I_find_inputs_by_ID);
760
+
761
+ /**
762
+ * Finds an input by its label text and stores it as the current element.
763
+ *
764
+ * ```gherkin
765
+ * When I find input by label text {string}
766
+ * ```
767
+ *
768
+ * @example
769
+ * When I find input by label text "Email"
770
+ *
771
+ * @remarks
772
+ * This step uses Playwright's `getByLabel` to locate an input associated
773
+ * with the given label text. It sets the found input as the
774
+ * {@link CustomWorld.element | current element}.
775
+ */
776
+ export async function When_I_find_input_by_label_text(this: CustomWorld, label: string) {
196
777
  this.element = this.page.getByLabel(label);
197
778
  this.log?.(`Stored input with label "${label}"`);
198
- });
199
- When("I find input by name {string}", async function (this: CustomWorld, name: string) {
779
+ }
780
+ When("I find input by label text {string}", When_I_find_input_by_label_text);
781
+
782
+ /**
783
+ * Finds an input by its name attribute and stores it as the current element.
784
+ *
785
+ * ```gherkin
786
+ * When I find input by name {string}
787
+ * ```
788
+ * * @example
789
+ * When I find input by name "username"
790
+ *
791
+ * @remarks
792
+ * This step uses a CSS selector to locate an input element based on its `name` attribute.
793
+ * It sets the found input as the {@link CustomWorld.element | current element}.
794
+ */
795
+ export async function When_I_find_input_by_name(this: CustomWorld, name: string) {
200
796
  this.element = this.page.locator(`input[name="${name}"]`);
201
797
  this.log?.(`Stored input with name "${name}"`);
202
- });
203
- When(
204
- "I find input by placeholder text {string}",
205
- async function (this: CustomWorld, placeholder: string) {
206
- this.element = this.page.getByPlaceholder(placeholder);
207
- this.log?.(`Stored input with placeholder "${placeholder}"`);
208
- }
209
- );
210
- When("I find inputs by name {string}", async function (this: CustomWorld, name: string) {
798
+ }
799
+ When("I find input by name {string}", When_I_find_input_by_name);
800
+
801
+ /**
802
+ * Finds an input by its placeholder text and stores it as the current element.
803
+ *
804
+ * ```gherkin
805
+ * When I find input by placeholder text {string}
806
+ * ```
807
+ *
808
+ * @example
809
+ * When I find input by placeholder text "Enter your email"
810
+ *
811
+ * @remarks
812
+ * This step uses Playwright's `getByPlaceholder` to locate an input
813
+ * based on its `placeholder` attribute. It sets the found input as the
814
+ * {@link CustomWorld.element | current element}.
815
+ */
816
+ export async function When_I_find_input_by_placeholder_text(
817
+ this: CustomWorld,
818
+ placeholder: string
819
+ ) {
820
+ this.element = this.page.getByPlaceholder(placeholder);
821
+ this.log?.(`Stored input with placeholder "${placeholder}"`);
822
+ }
823
+ When("I find input by placeholder text {string}", When_I_find_input_by_placeholder_text);
824
+
825
+ /**
826
+ * Finds all inputs by name attribute and stores them as elements.
827
+ *
828
+ * ```gherkin
829
+ * When I find inputs by name {string}
830
+ * ```
831
+ *
832
+ * @example
833
+ * When I find inputs by name "username"
834
+ *
835
+ * @remarks
836
+ * This step uses a CSS selector to find all input elements with the specified
837
+ * `name` attribute and stores them in the {@link CustomWorld.elements | elements}
838
+ * collection.
839
+ */
840
+ export async function When_I_find_inputs_by_name_multiple(this: CustomWorld, name: string) {
211
841
  this.elements = this.page.locator(`input[name="${name}"]`);
212
842
  this.log?.(`Stored multiple inputs with name "${name}"`);
213
- });
214
- When(
215
- "I find inputs by placeholder text {string}",
216
- async function (this: CustomWorld, placeholder: string) {
217
- this.elements = this.page.locator(`input[placeholder="${placeholder}"]`);
218
- this.log?.(`Stored multiple inputs with placeholder "${placeholder}"`);
219
- }
220
- );
221
- When("I find inputs by label text {string}", async function (this: CustomWorld, label: string) {
843
+ }
844
+ When("I find inputs by name {string}", When_I_find_inputs_by_name_multiple);
845
+
846
+ /**
847
+ * Finds all inputs by placeholder text and stores them as elements.
848
+ *
849
+ * ```gherkin
850
+ * When I find inputs by placeholder text {string}
851
+ * ```
852
+ *
853
+ * @example
854
+ * When I find inputs by placeholder text "Search"
855
+ *
856
+ * @remarks
857
+ * This step uses a CSS selector to find all input elements with the specified
858
+ * `placeholder` attribute and stores them in the {@link CustomWorld.elements | elements}
859
+ * collection.
860
+ */
861
+ export async function When_I_find_inputs_by_placeholder_text_multiple(
862
+ this: CustomWorld,
863
+ placeholder: string
864
+ ) {
865
+ this.elements = this.page.locator(`input[placeholder="${placeholder}"]`);
866
+ this.log?.(`Stored multiple inputs with placeholder "${placeholder}"`);
867
+ }
868
+ When("I find inputs by placeholder text {string}", When_I_find_inputs_by_placeholder_text_multiple);
869
+
870
+ /**
871
+ * Finds all inputs by label text and stores them as elements.
872
+ *
873
+ * ```gherkin
874
+ * When I find inputs by label text {string}
875
+ * ```
876
+ *
877
+ * @example
878
+ * When I find inputs by label text "Email"
879
+ *
880
+ * @remarks
881
+ * This step uses a CSS selector to find all input elements associated with the
882
+ * given label text and stores them in the {@link CustomWorld.elements | elements}
883
+ * collection.
884
+ */
885
+ export async function When_I_find_inputs_by_label_text_multiple(this: CustomWorld, label: string) {
222
886
  this.elements = this.page.locator(`label:has-text("${label}") + input`);
223
887
  this.log?.(`Stored multiple inputs with label "${label}"`);
224
- });
225
- When("I find inputs by display value {string}", async function (this: CustomWorld, value: string) {
888
+ }
889
+ When("I find inputs by label text {string}", When_I_find_inputs_by_label_text_multiple);
890
+
891
+ /**
892
+ * Finds all inputs by display value (supports alias) and stores them as elements.
893
+ *
894
+ * ```gherkin
895
+ * When I find inputs by display value {string}
896
+ * ```
897
+ *
898
+ * @example
899
+ * When I find inputs by display value "John"
900
+ * When I find inputs by display value "@userName"
901
+ *
902
+ * @remarks
903
+ * This step searches for all input elements whose `value` attribute matches
904
+ * the provided text or resolved alias. The matching inputs are stored in the
905
+ * {@link CustomWorld.elements | elements} collection.
906
+ */
907
+ export async function When_I_find_inputs_by_display_value_multiple(
908
+ this: CustomWorld,
909
+ value: string
910
+ ) {
226
911
  // 🧠 Handle alias
227
912
  if (value.startsWith("@")) {
228
913
  const alias = value.slice(1);
229
914
  value = this.data?.[alias];
230
915
  if (!value) {
231
- throw new Error(`No value found for alias "@${alias}"`);
916
+ throw new Error(`No value found for alias "@${alias}".`);
232
917
  }
233
918
  }
234
919
 
@@ -236,14 +921,33 @@ When("I find inputs by display value {string}", async function (this: CustomWorl
236
921
  this.elements = this.page.locator(`input[value="${value}"]`);
237
922
 
238
923
  this.log?.(`📦 Stored multiple inputs with display value "${value}"`);
239
- });
240
- When("I find input by display value {string}", async function (this: CustomWorld, value: string) {
924
+ }
925
+ When("I find inputs by display value {string}", When_I_find_inputs_by_display_value_multiple);
926
+
927
+ /**
928
+ * Finds an input by display value (supports alias) and stores it as the current element.
929
+ *
930
+ * ```gherkin
931
+ * When I find input by display value {string}
932
+ * ```
933
+ *
934
+ * @example
935
+ * When I find input by display value "John"
936
+ * When I find input by display value "@userName"
937
+ *
938
+ * @remarks
939
+ * This step searches for a single input element whose `value` attribute matches
940
+ * the provided text or resolved alias. It sets the found input as the
941
+ * {@link CustomWorld.element | current element}. An `expect` assertion
942
+ * is included to ensure the element is visible.
943
+ */
944
+ export async function When_I_find_input_by_display_value(this: CustomWorld, value: string) {
241
945
  // 🧠 Handle alias
242
946
  if (value.startsWith("@")) {
243
947
  const alias = value.slice(1);
244
948
  value = this.data?.[alias];
245
949
  if (!value) {
246
- throw new Error(`No value found for alias "@${alias}"`);
950
+ throw new Error(`No value found for alias "@${alias}".`);
247
951
  }
248
952
  }
249
953
 
@@ -253,4 +957,5 @@ When("I find input by display value {string}", async function (this: CustomWorld
253
957
  await expect(locator).toBeVisible({ timeout: 5000 });
254
958
  this.element = locator;
255
959
  this.log?.(`🔍 Found input with value: "${value}"`);
256
- });
960
+ }
961
+ When("I find input by display value {string}", When_I_find_input_by_display_value);