playwright-cucumber-ts-steps 1.0.1 โ†’ 1.0.2

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 (132) hide show
  1. package/README.md +195 -256
  2. package/dist/backend/actions/index.js +4 -0
  3. package/dist/backend/actions/interactions.js +23 -0
  4. package/dist/backend/actions/navigation.js +19 -0
  5. package/dist/backend/api/assertions.js +26 -0
  6. package/dist/backend/api/index.js +4 -0
  7. package/dist/backend/api/requests.js +24 -0
  8. package/dist/backend/api/state.js +15 -0
  9. package/dist/backend/assertions/expectVisible.js +8 -0
  10. package/dist/backend/assertions/index.js +5 -0
  11. package/dist/backend/assertions/pageState.js +25 -0
  12. package/dist/backend/assertions/text.js +20 -0
  13. package/dist/backend/assertions/visibility.js +20 -0
  14. package/dist/backend/auth/index.js +71 -0
  15. package/dist/backend/elements/alerts.js +21 -0
  16. package/dist/backend/elements/forms.js +59 -0
  17. package/dist/backend/elements/frames.js +25 -0
  18. package/dist/backend/elements/index.js +5 -0
  19. package/dist/core/registry.js +20 -0
  20. package/dist/core/runner.js +136 -0
  21. package/dist/index.js +10 -0
  22. package/dist/reporting/index.js +43 -0
  23. package/package.json +19 -101
  24. package/LICENSE +0 -21
  25. package/lib/actions/clickSteps.d.ts +0 -251
  26. package/lib/actions/clickSteps.js +0 -415
  27. package/lib/actions/cookieSteps.d.ts +0 -18
  28. package/lib/actions/cookieSteps.js +0 -93
  29. package/lib/actions/debugSteps.d.ts +0 -14
  30. package/lib/actions/debugSteps.js +0 -23
  31. package/lib/actions/elementFindSteps.d.ts +0 -668
  32. package/lib/actions/elementFindSteps.js +0 -931
  33. package/lib/actions/fillFormSteps.d.ts +0 -69
  34. package/lib/actions/fillFormSteps.js +0 -237
  35. package/lib/actions/index.d.ts +0 -11
  36. package/lib/actions/index.js +0 -28
  37. package/lib/actions/inputSteps.d.ts +0 -218
  38. package/lib/actions/inputSteps.js +0 -343
  39. package/lib/actions/interceptionSteps.d.ts +0 -169
  40. package/lib/actions/interceptionSteps.js +0 -291
  41. package/lib/actions/miscSteps.d.ts +0 -645
  42. package/lib/actions/miscSteps.js +0 -1061
  43. package/lib/actions/mouseSteps.d.ts +0 -143
  44. package/lib/actions/mouseSteps.js +0 -234
  45. package/lib/actions/scrollSteps.d.ts +0 -82
  46. package/lib/actions/scrollSteps.js +0 -123
  47. package/lib/actions/storageSteps.d.ts +0 -174
  48. package/lib/actions/storageSteps.js +0 -292
  49. package/lib/assertions/buttonAndTextVisibilitySteps.d.ts +0 -245
  50. package/lib/assertions/buttonAndTextVisibilitySteps.js +0 -401
  51. package/lib/assertions/cookieSteps.d.ts +0 -75
  52. package/lib/assertions/cookieSteps.js +0 -113
  53. package/lib/assertions/elementSteps.d.ts +0 -264
  54. package/lib/assertions/elementSteps.js +0 -388
  55. package/lib/assertions/formInputSteps.d.ts +0 -248
  56. package/lib/assertions/formInputSteps.js +0 -350
  57. package/lib/assertions/index.d.ts +0 -10
  58. package/lib/assertions/index.js +0 -27
  59. package/lib/assertions/interceptionRequestsSteps.d.ts +0 -353
  60. package/lib/assertions/interceptionRequestsSteps.js +0 -593
  61. package/lib/assertions/locationSteps.d.ts +0 -217
  62. package/lib/assertions/locationSteps.js +0 -310
  63. package/lib/assertions/roleTestIdSteps.d.ts +0 -159
  64. package/lib/assertions/roleTestIdSteps.js +0 -221
  65. package/lib/assertions/semanticSteps.d.ts +0 -176
  66. package/lib/assertions/semanticSteps.js +0 -252
  67. package/lib/assertions/storageSteps.d.ts +0 -149
  68. package/lib/assertions/storageSteps.js +0 -210
  69. package/lib/assertions/visualSteps.d.ts +0 -74
  70. package/lib/assertions/visualSteps.js +0 -209
  71. package/lib/custom_setups/loginHooks.d.ts +0 -1
  72. package/lib/custom_setups/loginHooks.js +0 -130
  73. package/lib/helpers/checkPeerDeps.d.ts +0 -1
  74. package/lib/helpers/checkPeerDeps.js +0 -19
  75. package/lib/helpers/compareSnapshots.d.ts +0 -6
  76. package/lib/helpers/compareSnapshots.js +0 -20
  77. package/lib/helpers/hooks.d.ts +0 -1
  78. package/lib/helpers/hooks.js +0 -210
  79. package/lib/helpers/utils/fakerUtils.d.ts +0 -1
  80. package/lib/helpers/utils/fakerUtils.js +0 -60
  81. package/lib/helpers/utils/index.d.ts +0 -4
  82. package/lib/helpers/utils/index.js +0 -20
  83. package/lib/helpers/utils/optionsUtils.d.ts +0 -24
  84. package/lib/helpers/utils/optionsUtils.js +0 -88
  85. package/lib/helpers/utils/resolveUtils.d.ts +0 -6
  86. package/lib/helpers/utils/resolveUtils.js +0 -72
  87. package/lib/helpers/utils/sessionUtils.d.ts +0 -3
  88. package/lib/helpers/utils/sessionUtils.js +0 -40
  89. package/lib/helpers/world.d.ts +0 -34
  90. package/lib/helpers/world.js +0 -110
  91. package/lib/iframes/frames.d.ts +0 -1
  92. package/lib/iframes/frames.js +0 -11
  93. package/lib/index.d.ts +0 -10
  94. package/lib/index.js +0 -28
  95. package/lib/register.d.ts +0 -1
  96. package/lib/register.js +0 -6
  97. package/src/actions/clickSteps.ts +0 -429
  98. package/src/actions/cookieSteps.ts +0 -95
  99. package/src/actions/debugSteps.ts +0 -21
  100. package/src/actions/elementFindSteps.ts +0 -961
  101. package/src/actions/fillFormSteps.ts +0 -270
  102. package/src/actions/index.ts +0 -12
  103. package/src/actions/inputSteps.ts +0 -354
  104. package/src/actions/interceptionSteps.ts +0 -325
  105. package/src/actions/miscSteps.ts +0 -1144
  106. package/src/actions/mouseSteps.ts +0 -256
  107. package/src/actions/scrollSteps.ts +0 -122
  108. package/src/actions/storageSteps.ts +0 -308
  109. package/src/assertions/buttonAndTextVisibilitySteps.ts +0 -436
  110. package/src/assertions/cookieSteps.ts +0 -131
  111. package/src/assertions/elementSteps.ts +0 -432
  112. package/src/assertions/formInputSteps.ts +0 -377
  113. package/src/assertions/index.ts +0 -11
  114. package/src/assertions/interceptionRequestsSteps.ts +0 -640
  115. package/src/assertions/locationSteps.ts +0 -315
  116. package/src/assertions/roleTestIdSteps.ts +0 -254
  117. package/src/assertions/semanticSteps.ts +0 -267
  118. package/src/assertions/storageSteps.ts +0 -250
  119. package/src/assertions/visualSteps.ts +0 -275
  120. package/src/custom_setups/loginHooks.ts +0 -154
  121. package/src/helpers/checkPeerDeps.ts +0 -19
  122. package/src/helpers/compareSnapshots.ts +0 -35
  123. package/src/helpers/hooks.ts +0 -212
  124. package/src/helpers/utils/fakerUtils.ts +0 -64
  125. package/src/helpers/utils/index.ts +0 -4
  126. package/src/helpers/utils/optionsUtils.ts +0 -104
  127. package/src/helpers/utils/resolveUtils.ts +0 -74
  128. package/src/helpers/utils/sessionUtils.ts +0 -36
  129. package/src/helpers/world.ts +0 -119
  130. package/src/iframes/frames.ts +0 -15
  131. package/src/index.ts +0 -18
  132. package/src/register.ts +0 -4
@@ -1,270 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import { When } from "@cucumber/cucumber";
4
- import { expect } from "@playwright/test";
5
- import { resolveLoginValue } from "../helpers/utils/resolveUtils"; // Assuming this path is correct
6
- import type { CustomWorld } from "../helpers/world"; // Assuming this path is correct
7
-
8
- // Explicitly type each row for better readability and type safety
9
- type ActionRow = {
10
- Target: string;
11
- Value: string;
12
- PayloadDir?: string; // Optional: for API requests, specifies directory for payload files
13
- SaveAs?: string; // Optional: for API requests, saves response body to this alias
14
- };
15
-
16
- /**
17
- * Fills a form using a data table, supporting various interactions and assertions.
18
- * This is a highly versatile step designed to encapsulate multiple form-related
19
- * actions, including filling inputs, clicking elements, checking checkboxes,
20
- * uploading files, performing drag-and-drop, managing browser storage,
21
- * initiating API requests, and making UI assertions.
22
- *
23
- * ```gherkin
24
- * When I fill the following {string} form data:
25
- * | Target | Value |
26
- * | input[placeholder='email'] | test@email.com |
27
- * | input[placeholder='password'] | @adminPassword |
28
- * | input[type='checkbox'] | check |
29
- * | select[name='role'] | select |
30
- * | button:has-text("Sign In") | click |
31
- * | .dashboard-header | assert:visible |
32
- * | .user-role | assert:text:Admin |
33
- * | input[type='file'] | upload:fixtures/profile-pic.jpg |
34
- * | div.upload-target | drag:.upload-preview |
35
- * | request:POST:/api/auth/login | payload:adminLogin.json | SaveAs:loginResponse |
36
- * | set:localStorage:auth_token | @lastApiResponse.token |
37
- * | wait | wait:1000 |
38
- * | reload | |
39
- * ```
40
- *
41
- * @param formName - A descriptive name for the form (e.g., "Login", "Profile").
42
- * This is currently for documentation purposes only.
43
- * @param dataTable - A Cucumber data table containing 'Target' and 'Value' columns.
44
- * Optionally, 'PayloadDir' and 'SaveAs' columns can be used for API requests.
45
- *
46
- * @remarks
47
- * Each row in the data table represents an action to perform. The `Target` column
48
- * typically specifies a CSS selector for an element, but can also define special
49
- * actions like `request:`, `set:localStorage:`, `set:sessionStorage:`, `wait`, and `reload`.
50
- * The `Value` column defines the action or input for the target.
51
- *
52
- * **Supported `Value` actions:**
53
- * - **`fill_value` (default):** Fills the target input with the given value. Supports aliases
54
- * resolved via `resolveLoginValue`.
55
- * - **`click`:** Clicks the target element.
56
- * - **`check`:** Checks a checkbox or radio button.
57
- * - **`uncheck`:** Unchecks a checkbox.
58
- * - **`select`:** Selects the first option in a dropdown (e.g., `<select>`).
59
- * - **`upload:filepath`:** Uploads a file from the `filepath` (relative to project root).
60
- * Example: `upload:fixtures/my-image.png`
61
- * - **`drag:target_selector`:** Drags the `Target` element to the element specified by `target_selector`.
62
- * - **`assert:type:expected_value`:** Performs an assertion on the `Target` element.
63
- * - `assert:visible`: Asserts the element is visible.
64
- * - `assert:text:expected_text`: Asserts the element has the exact text.
65
- * - `assert:value:expected_value`: Asserts an input/textarea has the exact value.
66
- * - **`request:METHOD:url`:** Makes an API request.
67
- * - `METHOD` can be `POST`, `GET`, `PUT`, `PATCH`.
68
- * - `url` is the API endpoint.
69
- * - **Requires additional columns:** `PayloadDir` (optional, default 'payload')
70
- * and `Payload` (filename, e.g., `adminLogin.json`).
71
- * - **Optional `SaveAs` column:** Saves the API response body to `this.data[SaveAs]`.
72
- * - The response body is also stored in `this.data.lastApiResponse`.
73
- * - **`set:localStorage:key`:** Sets a value in `localStorage`.
74
- * The `Value` column provides the value to set, supporting aliases.
75
- * - **`set:sessionStorage:key`:** Sets a value in `sessionStorage`.
76
- * The `Value` column provides the value to set, supporting aliases.
77
- * - **`wait:milliseconds`:** Pauses execution for the specified milliseconds.
78
- * - **`reload`:** Reloads the current page.
79
- *
80
- * @category Form Interaction Steps
81
- */
82
- export async function When_I_fill_the_following_form_data(
83
- this: CustomWorld,
84
- _formName: string, // Renamed to _formName as it's not used in logic, just for readability in Gherkin
85
- dataTable: any // Use `any` for dataTable as its structure is defined by `hashes()`
86
- ) {
87
- const rows = dataTable.hashes() as ActionRow[];
88
-
89
- for (const row of rows) {
90
- const target = row.Target.trim();
91
- const rawValue = row.Value.trim();
92
- // Resolve value early if it's not an action that prevents it (like `click`)
93
- resolveLoginValue(rawValue, this);
94
-
95
- // --- Special Actions (not directly on a locator) ---
96
-
97
- // โœ… Request handling (e.g., `request:POST:/api/auth/login`)
98
- if (target.startsWith("request:")) {
99
- const [, method, url] = target.replace("request:", "").split(":");
100
- const payloadFile = rawValue.trim(); // Value column now holds the payload filename
101
-
102
- const payloadDir = row.PayloadDir || this.parameters?.payloadDir || "payload";
103
- const filePath = path.resolve(payloadDir, payloadFile);
104
-
105
- if (!fs.existsSync(filePath)) {
106
- throw new Error(`Payload file not found for API request: ${filePath}`);
107
- }
108
-
109
- const payload = JSON.parse(fs.readFileSync(filePath, "utf-8"));
110
- this.log?.(`๐Ÿ“ž Making API ${method} request to ${url} with payload from ${payloadFile}`);
111
-
112
- const response = await this.page.request[
113
- method.toLowerCase() as "post" | "put" | "patch" | "get" | "delete" // Added 'delete' for completeness if needed
114
- ](url, {
115
- data: payload,
116
- });
117
-
118
- const responseBody = await response.json();
119
- this.data.lastApiResponse = responseBody;
120
- this.data.lastStatusCode = response.status();
121
- this.log?.(
122
- `โœ… API response status: ${response.status()}, body: ${JSON.stringify(responseBody).slice(0, 100)}...`
123
- );
124
-
125
- if (row.SaveAs) {
126
- // Using the optional 'SaveAs' column
127
- this.data[row.SaveAs] = responseBody;
128
- this.log?.(`๐Ÿ’พ API response saved as alias "${row.SaveAs}"`);
129
- }
130
-
131
- continue; // Move to next row
132
- }
133
-
134
- // โœ… Local Storage (e.g., `set:localStorage:auth_token`)
135
- if (target.startsWith("set:localStorage:")) {
136
- const [, , key] = target.split(":"); // target has "set:localStorage:key"
137
- if (typeof key !== "string" || !key) {
138
- throw new Error("Local storage key must be a non-empty string for 'set:localStorage:'.");
139
- }
140
- const resolvedValue = resolveLoginValue(rawValue, this) ?? ""; // RawValue holds the actual data or alias
141
- await this.page.evaluate(([k, v]) => localStorage.setItem(k, v), [key, resolvedValue]);
142
- this.log?.(`๐Ÿ“ฆ Set localStorage item "${key}" to "${resolvedValue.slice(0, 50)}..."`);
143
- continue;
144
- }
145
-
146
- // โœ… Session Storage (e.g., `set:sessionStorage:user_id`)
147
- if (target.startsWith("set:sessionStorage:")) {
148
- const [, , key] = target.split(":"); // target has "set:sessionStorage:key"
149
- if (typeof key !== "string" || !key) {
150
- throw new Error(
151
- "Session storage key must be a non-empty string for 'set:sessionStorage:'."
152
- );
153
- }
154
- const resolvedValue = resolveLoginValue(rawValue, this) ?? ""; // RawValue holds the actual data or alias
155
- await this.page.evaluate(([k, v]) => sessionStorage.setItem(k, v), [key, resolvedValue]);
156
- this.log?.(`๐Ÿ“ฆ Set sessionStorage item "${key}" to "${resolvedValue.slice(0, 50)}..."`);
157
- continue;
158
- }
159
-
160
- // โœ… Wait (e.g., `wait` in Target, `wait:1000` in Value)
161
- if (target === "wait") {
162
- const [, timeMs] = rawValue.split(":");
163
- const waitTime = Number(timeMs);
164
- if (!isNaN(waitTime)) {
165
- this.log?.(`โณ Waiting for ${waitTime}ms...`);
166
- await this.page.waitForTimeout(waitTime);
167
- } else {
168
- throw new Error(
169
- `Invalid wait time specified: "${rawValue}". Expected format "wait:milliseconds".`
170
- );
171
- }
172
- continue;
173
- }
174
-
175
- // โœ… Reload (e.g., `reload` in Target)
176
- if (target === "reload") {
177
- this.log?.("๐Ÿ”„ Reloading page...");
178
- await this.page.reload();
179
- continue;
180
- }
181
-
182
- // --- Actions on a specific Locator ---
183
- const locator = this.getLocator(target); // Get locator for UI interactions/assertions
184
-
185
- // โœ… Assertions (e.g., `.dashboard-header` | `assert:visible`)
186
- if (rawValue.startsWith("assert:")) {
187
- const [, type, expected] = rawValue.split(":");
188
-
189
- if (type === "visible") {
190
- this.log?.(`๐Ÿ”Ž Asserting "${target}" is visible.`);
191
- await expect(locator).toBeVisible();
192
- } else if (type === "text") {
193
- this.log?.(`๐Ÿ”Ž Asserting "${target}" has text "${expected}"`);
194
- await expect(locator).toHaveText(expected ?? "", {
195
- useInnerText: true, // Prefer innerText for visible text content
196
- });
197
- } else if (type === "value") {
198
- this.log?.(`๐Ÿ”Ž Asserting "${target}" has value "${expected}"`);
199
- await expect(locator).toHaveValue(expected ?? "");
200
- } else {
201
- throw new Error(`โŒ Unknown assertion type: "${type}" for target "${target}".`);
202
- }
203
- continue;
204
- }
205
-
206
- // โœ… Click (e.g., `button:has-text("Sign In")` | `click`)
207
- if (rawValue === "click") {
208
- this.log?.(`๐Ÿ‘† Clicking on "${target}"`);
209
- await locator.click();
210
- continue;
211
- }
212
-
213
- // โœ… Check (e.g., `input[type='checkbox']` | `check`)
214
- if (rawValue === "check") {
215
- this.log?.(`โ˜‘๏ธ Checking "${target}"`);
216
- await locator.check();
217
- continue;
218
- }
219
-
220
- // โœ… Uncheck (e.g., `input[type='checkbox']` | `uncheck`)
221
- if (rawValue === "uncheck") {
222
- this.log?.(`๐Ÿ”ฒ Unchecking "${target}"`);
223
- await locator.uncheck();
224
- continue;
225
- }
226
-
227
- // โœ… Select (e.g., `select[name='role']` | `select`)
228
- if (rawValue === "select") {
229
- this.log?.(`๐Ÿ”ฝ Selecting first option for "${target}"`);
230
- await locator.selectOption({ index: 0 }); // Typically used for dropdowns
231
- continue;
232
- }
233
-
234
- // โœ… File upload (e.g., `input[type='file']` | `upload:fixtures/profile-pic.jpg`)
235
- if (rawValue.startsWith("upload:")) {
236
- const filePath = rawValue.split("upload:")[1].trim();
237
- const resolvedPath = path.resolve(filePath);
238
- if (!fs.existsSync(resolvedPath)) {
239
- throw new Error(`File not found for upload: ${filePath} (resolved to: ${resolvedPath})`);
240
- }
241
- this.log?.(`๐Ÿ“ค Uploading file "${resolvedPath}" to "${target}"`);
242
- await locator.setInputFiles(resolvedPath);
243
- continue;
244
- }
245
-
246
- // โœ… Drag and drop (e.g., `div.upload-target` | `drag:.upload-preview`)
247
- if (rawValue.startsWith("drag:")) {
248
- const targetSelector = rawValue.split("drag:")[1].trim();
249
- const targetLocator = this.getLocator(targetSelector);
250
- this.log?.(`๐Ÿ–๏ธ Dragging "${target}" to "${targetSelector}"`);
251
- await locator.dragTo(targetLocator);
252
- continue;
253
- }
254
-
255
- // โœ… Default: fill (if no other action matches, assume fill)
256
- // Ensure `value` is resolved for normal fill actions
257
- const fillValue = resolveLoginValue(rawValue, this);
258
- if (fillValue !== undefined) {
259
- this.log?.(`โœ๏ธ Filling "${target}" with "${String(fillValue).slice(0, 50)}..."`);
260
- await locator.fill(String(fillValue));
261
- } else {
262
- // This case should ideally not be hit if rawValue is always handled,
263
- // but good for debugging if an unhandled action slips through.
264
- this.log?.(`โš ๏ธ No action performed for target "${target}" with value "${rawValue}"`);
265
- }
266
- }
267
- }
268
-
269
- // Register the Cucumber step
270
- When("I fill the following {string} form data:", When_I_fill_the_following_form_data);
@@ -1,12 +0,0 @@
1
- // Actions
2
- export * from "./clickSteps";
3
- export * from "./cookieSteps";
4
- export * from "./debugSteps";
5
- export * from "./elementFindSteps";
6
- export * from "./fillFormSteps";
7
- export * from "./inputSteps";
8
- export * from "./interceptionSteps";
9
- export * from "./miscSteps";
10
- export * from "./mouseSteps";
11
- export * from "./scrollSteps";
12
- export * from "./storageSteps";
@@ -1,354 +0,0 @@
1
- // e2e/step_definitions/common/actions/inputSteps.ts
2
- import { When } from "@cucumber/cucumber";
3
- import { evaluateFaker } from "../helpers/utils/fakerUtils"; // Assuming this path is correct
4
- import {
5
- parseCheckOptions,
6
- parseFillOptions,
7
- parseSelectOptions,
8
- } from "../helpers/utils/optionsUtils"; // Assuming this path is correct
9
- import { CustomWorld } from "../helpers/world"; // Assuming this path is correct
10
-
11
- // =============================
12
- // WHEN I CHECK / UNCHECK
13
- // =============================
14
-
15
- /**
16
- * Checks the previously selected element (e.g., a checkbox or radio button).
17
- *
18
- * ```gherkin
19
- * When I check
20
- * ```
21
- *
22
- * @example
23
- * When I find element by label text "Remember me"
24
- * And I check
25
- *
26
- * @remarks
27
- * This step requires a preceding step that sets the {@link CustomWorld.element | current element}
28
- * to a checkbox or radio button. It will mark the element as checked. Optional Playwright
29
- * `CheckOptions` can be provided via a data table.
30
- */
31
- export async function When_I_check(this: CustomWorld, ...rest: any[]) {
32
- if (!this.element) throw new Error("No element selected to check.");
33
- const maybeTable = rest[0];
34
- const options = maybeTable?.rowsHash ? parseCheckOptions(maybeTable) : {};
35
- await this.element.check(options);
36
- this.log?.("โœ… Checked stored element.");
37
- }
38
- When("I check", When_I_check);
39
-
40
- /**
41
- * Unchecks the previously selected element (e.g., a checkbox).
42
- *
43
- * ```gherkin
44
- * When I uncheck
45
- * ```
46
- *
47
- * @example
48
- * When I find element by label text "Agree to terms"
49
- * And I uncheck
50
- *
51
- * @remarks
52
- * This step requires a preceding step that sets the {@link CustomWorld.element | current element}
53
- * to a checkbox. It will mark the element as unchecked. Optional Playwright
54
- * `CheckOptions` can be provided via a data table.
55
- */
56
- export async function When_I_uncheck(this: CustomWorld, ...rest: any[]) {
57
- if (!this.element) throw new Error("No element selected to uncheck.");
58
- const maybeTable = rest[0];
59
- const options = maybeTable?.rowsHash ? parseCheckOptions(maybeTable) : {};
60
- await this.element.uncheck(options);
61
- this.log?.("โœ… Unchecked stored element.");
62
- }
63
- When("I uncheck", When_I_uncheck);
64
-
65
- /**
66
- * Checks a specific input element, requiring a preceding step to select it.
67
- *
68
- * ```gherkin
69
- * When I check input
70
- * ```
71
- *
72
- * @example
73
- * When I find element by selector "input#myCheckbox"
74
- * And I check input
75
- *
76
- * @remarks
77
- * This is an alias for "I check". It requires a preceding step that sets the
78
- * {@link CustomWorld.element | current element} to an input element (like a checkbox or radio).
79
- * Optional Playwright `CheckOptions` can be provided via a data table.
80
- */
81
- export async function When_I_check_input(this: CustomWorld, ...rest: any[]) {
82
- if (!this.element) throw new Error("No input selected to check.");
83
- const maybeTable = rest[0];
84
- const options = maybeTable?.rowsHash ? parseCheckOptions(maybeTable) : {};
85
- await this.element.check(options);
86
- this.log?.("โœ… Checked stored input.");
87
- }
88
- When("I check input", When_I_check_input);
89
-
90
- /**
91
- * Unchecks a specific input element, requiring a preceding step to select it.
92
- *
93
- * ```gherkin
94
- * When I uncheck input
95
- * ```
96
- *
97
- * @example
98
- * When I find element by selector "input#newsletter"
99
- * And I uncheck input
100
- *
101
- * @remarks
102
- * This is an alias for "I uncheck". It requires a preceding step that sets the
103
- * {@link CustomWorld.element | current element} to an input element (like a checkbox).
104
- * Optional Playwright `CheckOptions` can be provided via a data table.
105
- */
106
- export async function When_I_uncheck_input(this: CustomWorld, ...rest: any[]) {
107
- if (!this.element) throw new Error("No input selected to uncheck.");
108
- const maybeTable = rest[0];
109
- const options = maybeTable?.rowsHash ? parseCheckOptions(maybeTable) : {};
110
- await this.element.uncheck(options);
111
- this.log?.("โœ… Unchecked stored input.");
112
- }
113
- When("I uncheck input", When_I_uncheck_input);
114
-
115
- // =============================
116
- // WHEN I TYPE / SET VALUE
117
- // =============================
118
-
119
- /**
120
- * Shared implementation for typing steps.
121
- * @internal
122
- */
123
- const typeStepImplementation = async function (
124
- this: CustomWorld,
125
- textOrAlias: string,
126
- ...rest: any[]
127
- ) {
128
- if (!this.element) throw new Error("No element selected to type into.");
129
-
130
- const maybeTable = rest[0];
131
- const options = maybeTable?.rowsHash ? parseFillOptions(maybeTable) : {};
132
- const text = textOrAlias.startsWith("@")
133
- ? (this.data[textOrAlias.slice(1)] ??
134
- (() => {
135
- throw new Error(`No value found for alias "${textOrAlias}".`);
136
- })())
137
- : evaluateFaker(textOrAlias); // Evaluate faker directly here if it's not an alias
138
-
139
- // Playwright's fill clears the field by default, but explicitly clearing can be safer
140
- // await this.element.fill(""); // This explicit clear might not be necessary depending on Playwright version/behavior
141
- await this.element.fill(text, options);
142
- this.data.lastTyped = text; // Store the actual text typed for potential later use
143
- this.log?.(`โŒจ๏ธ Typed "${text}" into selected element.`);
144
- };
145
-
146
- /**
147
- * Types the given text into the previously selected element.
148
- *
149
- * ```gherkin
150
- * When I type {string}
151
- * ```
152
- *
153
- * @example
154
- * When I find element by selector "input[name='email']"
155
- * And I type "user@example.com"
156
- *
157
- * @remarks
158
- * This step requires a preceding step that sets the {@link CustomWorld.element | current element}
159
- * to an input field (or any element that supports `fill`). The provided text can be a literal
160
- * string or a faker expression (e.g., `{{internet.email}}`).
161
- * Optional Playwright `FillOptions` can be provided via a data table.
162
- */
163
- export const When_I_type = typeStepImplementation;
164
- When("I type {string}", When_I_type);
165
-
166
- /**
167
- * Types the value stored in an alias into the previously selected element.
168
- *
169
- * ```gherkin
170
- * When I type stored {string}
171
- * ```
172
- *
173
- * @example
174
- * Given I store "my.user@example.com" as "userEmail"
175
- * When I find element by selector "input[name='email']"
176
- * And I type stored "userEmail"
177
- *
178
- * @remarks
179
- * This step requires a preceding step that sets the {@link CustomWorld.element | current element}
180
- * to an input field. The `string` argument must be an alias (e.g., `userEmail`).
181
- * The value associated with this alias in `this.data` will be typed into the element.
182
- * Optional Playwright `FillOptions` can be provided via a data table.
183
- */
184
- export const When_I_type_stored = typeStepImplementation;
185
- When("I type stored {string}", When_I_type_stored);
186
-
187
- /**
188
- * Types a randomly generated value (using Faker.js) into the previously selected element.
189
- *
190
- * ```gherkin
191
- * When I type random {string}
192
- * ```
193
- *
194
- * @example
195
- * When I find element by selector "input[name='username']"
196
- * And I type random "internet.userName"
197
- *
198
- * @remarks
199
- * This step requires a preceding step that sets the {@link CustomWorld.element | current element}
200
- * to an input field. The `string` argument should be a Faker.js path (e.g., `internet.email`,
201
- * `person.firstName`). A random value generated by Faker will be typed into the element.
202
- * Optional Playwright `FillOptions` can be provided via a data table.
203
- */
204
- export const When_I_type_random = typeStepImplementation;
205
- When("I type random {string}", When_I_type_random);
206
-
207
- /**
208
- * Sets the value of the previously selected element.
209
- *
210
- * ```gherkin
211
- * When I set value {string}
212
- * ```
213
- *
214
- * @example
215
- * When I find element by selector "input[name='password']"
216
- * And I set value "@userPassword"
217
- *
218
- * @remarks
219
- * This step requires a preceding step that sets the {@link CustomWorld.element | current element}
220
- * to an input field (or any element that supports `fill`). The provided `valueOrAlias` can be
221
- * a literal string, an alias (prefixed with `@`), or a Faker expression. This will directly
222
- * set the input's value, which is generally faster than typing for non-interactive scenarios.
223
- * Optional Playwright `FillOptions` can be provided via a data table.
224
- */
225
- export async function When_I_set_value(this: CustomWorld, valueOrAlias: string, ...rest: any[]) {
226
- if (!this.element) throw new Error("No element selected to set value.");
227
-
228
- const maybeTable = rest[0];
229
- const options = maybeTable?.rowsHash ? parseFillOptions(maybeTable) : {};
230
- const value = valueOrAlias.startsWith("@")
231
- ? (this.data[valueOrAlias.slice(1)] ??
232
- (() => {
233
- throw new Error(`No value found for alias "${valueOrAlias}".`);
234
- })())
235
- : evaluateFaker(valueOrAlias); // Evaluate faker directly here if it's not an alias
236
-
237
- await this.element.fill(value, options);
238
- this.data.lastValueSet = value; // Store the actual value set for potential later use
239
- this.log?.(`๐Ÿ“ Set value of selected element to "${value}".`);
240
- }
241
- When("I set value {string}", When_I_set_value);
242
-
243
- /**
244
- * Clears the value of the previously selected element.
245
- *
246
- * ```gherkin
247
- * When I clear
248
- * ```
249
- *
250
- * @example
251
- * When I find element by selector "input[name='search']"
252
- * And I clear
253
- *
254
- * @remarks
255
- * This step requires a preceding step that sets the {@link CustomWorld.element | current element}
256
- * to an input field (or any element that supports `fill`). It effectively empties the input field.
257
- */
258
- export async function When_I_clear(this: CustomWorld) {
259
- if (!this.element) throw new Error("No element selected to clear.");
260
- await this.element.fill("");
261
- this.log?.("๐Ÿงผ Cleared value of selected element.");
262
- }
263
- When("I clear", When_I_clear);
264
-
265
- /**
266
- * Submits the form associated with the previously selected element, or the first form on the page.
267
- *
268
- * ```gherkin
269
- * When I submit
270
- * ```
271
- *
272
- * @example
273
- * When I find element by selector "form#loginForm"
274
- * And I submit
275
- * # OR (submits the first form found if no element is selected)
276
- * When I go to "/login"
277
- * And I submit
278
- *
279
- * @remarks
280
- * This step will find a form to submit. If {@link CustomWorld.element | this.element}
281
- * is a form or an element within a form, that form will be submitted. Otherwise, it will
282
- * attempt to submit the first `<form>` element found on the page.
283
- * It uses a direct DOM `submit()` call, which bypasses Playwright's default event handling
284
- * and can be useful for testing native form submission behavior.
285
- */
286
- export async function When_I_submit(this: CustomWorld) {
287
- // Use the current element if it's a form or within a form, otherwise default to the first form on the page.
288
- const formLocator = this.element
289
- ? this.element.locator("xpath=ancestor-or-self::form")
290
- : this.page.locator("form").first();
291
-
292
- if (!(await formLocator.isVisible())) {
293
- throw new Error(
294
- "No visible form found to submit. Ensure an element within a form is selected, or a form exists on the page."
295
- );
296
- }
297
-
298
- await formLocator.evaluate((f: HTMLFormElement) => f.submit());
299
- this.log?.("๐Ÿ“จ Submitted form.");
300
- }
301
- When("I submit", When_I_submit);
302
-
303
- /**
304
- * Selects an option by its visible text label in a `<select>` element.
305
- *
306
- * ```gherkin
307
- * When I select option {string}
308
- * ```
309
- *
310
- * @example
311
- * When I find element by selector "select[name='role']"
312
- * And I select option "Administrator"
313
- *
314
- * @remarks
315
- * This step requires a preceding step that sets the {@link CustomWorld.element | current element}
316
- * to a `<select>` HTML element. It will then select the option whose visible text matches
317
- * the provided `option` string.
318
- * Optional Playwright `SelectOptionOptions` can be provided via a data table.
319
- */
320
- export async function When_I_select_option(this: CustomWorld, option: string, ...rest: any[]) {
321
- if (!this.element) throw new Error("No select element stored to select an option.");
322
- const maybeTable = rest[0];
323
- const options = maybeTable?.rowsHash ? parseSelectOptions(maybeTable) : {};
324
- await this.element.selectOption({ label: option }, options);
325
- this.log?.(`๐Ÿ”ฝ Selected option "${option}".`);
326
- }
327
- When("I select option {string}", When_I_select_option);
328
-
329
- /**
330
- * Sets the file input of the previously selected element to the specified file path.
331
- *
332
- * ```gherkin
333
- * When I select file {string}
334
- * ```
335
- *
336
- * @example
337
- * When I find element by selector "input[type='file']"
338
- * And I select file "path/to/my/document.pdf"
339
- *
340
- * @remarks
341
- * This step requires a preceding step that sets the {@link CustomWorld.element | current element}
342
- * to an `input` element of `type="file"`. The `filePath` should be relative to your project's
343
- * root or an absolute path. Playwright will automatically handle making the file available
344
- * for upload.
345
- * Optional Playwright `SetInputFilesOptions` can be provided via a data table.
346
- */
347
- export async function When_I_select_file(this: CustomWorld, filePath: string, ...rest: any[]) {
348
- if (!this.element) throw new Error("No file input selected to set a file.");
349
- const maybeTable = rest[0];
350
- const options = maybeTable?.rowsHash ? parseSelectOptions(maybeTable) : {}; // Note: parseSelectOptions is used here, assuming it returns `SetInputFilesOptions` compatible object or is generic enough. If not, a new parser for SetInputFilesOptions would be ideal.
351
- await this.element.setInputFiles(filePath, options);
352
- this.log?.(`๐Ÿ“ Set input file to "${filePath}".`);
353
- }
354
- When("I select file {string}", When_I_select_file);