playwright-cucumber-ts-steps 1.0.1 → 1.0.3

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 (137) hide show
  1. package/README.md +270 -244
  2. package/dist/backend/actions/formTable.js +34 -0
  3. package/dist/backend/actions/index.js +5 -0
  4. package/dist/backend/actions/interactions.js +23 -0
  5. package/dist/backend/actions/navigation.js +19 -0
  6. package/dist/backend/api/assertions.js +26 -0
  7. package/dist/backend/api/index.js +5 -0
  8. package/dist/backend/api/mock.js +75 -0
  9. package/dist/backend/api/requests.js +86 -0
  10. package/dist/backend/api/state.js +15 -0
  11. package/dist/backend/assertions/expectVisible.js +8 -0
  12. package/dist/backend/assertions/index.js +5 -0
  13. package/dist/backend/assertions/pageState.js +25 -0
  14. package/dist/backend/assertions/text.js +20 -0
  15. package/dist/backend/assertions/visibility.js +20 -0
  16. package/dist/backend/auth/index.js +71 -0
  17. package/dist/backend/db/index.js +6 -0
  18. package/dist/backend/db/state.js +24 -0
  19. package/dist/backend/db/steps.js +43 -0
  20. package/dist/backend/elements/alerts.js +21 -0
  21. package/dist/backend/elements/forms.js +59 -0
  22. package/dist/backend/elements/frames.js +25 -0
  23. package/dist/backend/elements/index.js +5 -0
  24. package/dist/core/registry.js +20 -0
  25. package/dist/core/runner.js +151 -0
  26. package/dist/index.js +10 -0
  27. package/dist/reporting/index.js +43 -0
  28. package/package.json +19 -101
  29. package/LICENSE +0 -21
  30. package/lib/actions/clickSteps.d.ts +0 -251
  31. package/lib/actions/clickSteps.js +0 -415
  32. package/lib/actions/cookieSteps.d.ts +0 -18
  33. package/lib/actions/cookieSteps.js +0 -93
  34. package/lib/actions/debugSteps.d.ts +0 -14
  35. package/lib/actions/debugSteps.js +0 -23
  36. package/lib/actions/elementFindSteps.d.ts +0 -668
  37. package/lib/actions/elementFindSteps.js +0 -931
  38. package/lib/actions/fillFormSteps.d.ts +0 -69
  39. package/lib/actions/fillFormSteps.js +0 -237
  40. package/lib/actions/index.d.ts +0 -11
  41. package/lib/actions/index.js +0 -28
  42. package/lib/actions/inputSteps.d.ts +0 -218
  43. package/lib/actions/inputSteps.js +0 -343
  44. package/lib/actions/interceptionSteps.d.ts +0 -169
  45. package/lib/actions/interceptionSteps.js +0 -291
  46. package/lib/actions/miscSteps.d.ts +0 -645
  47. package/lib/actions/miscSteps.js +0 -1061
  48. package/lib/actions/mouseSteps.d.ts +0 -143
  49. package/lib/actions/mouseSteps.js +0 -234
  50. package/lib/actions/scrollSteps.d.ts +0 -82
  51. package/lib/actions/scrollSteps.js +0 -123
  52. package/lib/actions/storageSteps.d.ts +0 -174
  53. package/lib/actions/storageSteps.js +0 -292
  54. package/lib/assertions/buttonAndTextVisibilitySteps.d.ts +0 -245
  55. package/lib/assertions/buttonAndTextVisibilitySteps.js +0 -401
  56. package/lib/assertions/cookieSteps.d.ts +0 -75
  57. package/lib/assertions/cookieSteps.js +0 -113
  58. package/lib/assertions/elementSteps.d.ts +0 -264
  59. package/lib/assertions/elementSteps.js +0 -388
  60. package/lib/assertions/formInputSteps.d.ts +0 -248
  61. package/lib/assertions/formInputSteps.js +0 -350
  62. package/lib/assertions/index.d.ts +0 -10
  63. package/lib/assertions/index.js +0 -27
  64. package/lib/assertions/interceptionRequestsSteps.d.ts +0 -353
  65. package/lib/assertions/interceptionRequestsSteps.js +0 -593
  66. package/lib/assertions/locationSteps.d.ts +0 -217
  67. package/lib/assertions/locationSteps.js +0 -310
  68. package/lib/assertions/roleTestIdSteps.d.ts +0 -159
  69. package/lib/assertions/roleTestIdSteps.js +0 -221
  70. package/lib/assertions/semanticSteps.d.ts +0 -176
  71. package/lib/assertions/semanticSteps.js +0 -252
  72. package/lib/assertions/storageSteps.d.ts +0 -149
  73. package/lib/assertions/storageSteps.js +0 -210
  74. package/lib/assertions/visualSteps.d.ts +0 -74
  75. package/lib/assertions/visualSteps.js +0 -209
  76. package/lib/custom_setups/loginHooks.d.ts +0 -1
  77. package/lib/custom_setups/loginHooks.js +0 -130
  78. package/lib/helpers/checkPeerDeps.d.ts +0 -1
  79. package/lib/helpers/checkPeerDeps.js +0 -19
  80. package/lib/helpers/compareSnapshots.d.ts +0 -6
  81. package/lib/helpers/compareSnapshots.js +0 -20
  82. package/lib/helpers/hooks.d.ts +0 -1
  83. package/lib/helpers/hooks.js +0 -210
  84. package/lib/helpers/utils/fakerUtils.d.ts +0 -1
  85. package/lib/helpers/utils/fakerUtils.js +0 -60
  86. package/lib/helpers/utils/index.d.ts +0 -4
  87. package/lib/helpers/utils/index.js +0 -20
  88. package/lib/helpers/utils/optionsUtils.d.ts +0 -24
  89. package/lib/helpers/utils/optionsUtils.js +0 -88
  90. package/lib/helpers/utils/resolveUtils.d.ts +0 -6
  91. package/lib/helpers/utils/resolveUtils.js +0 -72
  92. package/lib/helpers/utils/sessionUtils.d.ts +0 -3
  93. package/lib/helpers/utils/sessionUtils.js +0 -40
  94. package/lib/helpers/world.d.ts +0 -34
  95. package/lib/helpers/world.js +0 -110
  96. package/lib/iframes/frames.d.ts +0 -1
  97. package/lib/iframes/frames.js +0 -11
  98. package/lib/index.d.ts +0 -10
  99. package/lib/index.js +0 -28
  100. package/lib/register.d.ts +0 -1
  101. package/lib/register.js +0 -6
  102. package/src/actions/clickSteps.ts +0 -429
  103. package/src/actions/cookieSteps.ts +0 -95
  104. package/src/actions/debugSteps.ts +0 -21
  105. package/src/actions/elementFindSteps.ts +0 -961
  106. package/src/actions/fillFormSteps.ts +0 -270
  107. package/src/actions/index.ts +0 -12
  108. package/src/actions/inputSteps.ts +0 -354
  109. package/src/actions/interceptionSteps.ts +0 -325
  110. package/src/actions/miscSteps.ts +0 -1144
  111. package/src/actions/mouseSteps.ts +0 -256
  112. package/src/actions/scrollSteps.ts +0 -122
  113. package/src/actions/storageSteps.ts +0 -308
  114. package/src/assertions/buttonAndTextVisibilitySteps.ts +0 -436
  115. package/src/assertions/cookieSteps.ts +0 -131
  116. package/src/assertions/elementSteps.ts +0 -432
  117. package/src/assertions/formInputSteps.ts +0 -377
  118. package/src/assertions/index.ts +0 -11
  119. package/src/assertions/interceptionRequestsSteps.ts +0 -640
  120. package/src/assertions/locationSteps.ts +0 -315
  121. package/src/assertions/roleTestIdSteps.ts +0 -254
  122. package/src/assertions/semanticSteps.ts +0 -267
  123. package/src/assertions/storageSteps.ts +0 -250
  124. package/src/assertions/visualSteps.ts +0 -275
  125. package/src/custom_setups/loginHooks.ts +0 -154
  126. package/src/helpers/checkPeerDeps.ts +0 -19
  127. package/src/helpers/compareSnapshots.ts +0 -35
  128. package/src/helpers/hooks.ts +0 -212
  129. package/src/helpers/utils/fakerUtils.ts +0 -64
  130. package/src/helpers/utils/index.ts +0 -4
  131. package/src/helpers/utils/optionsUtils.ts +0 -104
  132. package/src/helpers/utils/resolveUtils.ts +0 -74
  133. package/src/helpers/utils/sessionUtils.ts +0 -36
  134. package/src/helpers/world.ts +0 -119
  135. package/src/iframes/frames.ts +0 -15
  136. package/src/index.ts +0 -18
  137. package/src/register.ts +0 -4
@@ -1,1061 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.When_I_use_fake_timers = When_I_use_fake_timers;
7
- exports.When_I_use_real_timers = When_I_use_real_timers;
8
- exports.When_I_advance_timers_by_milliseconds = When_I_advance_timers_by_milliseconds;
9
- exports.When_I_advance_timers_by_seconds = When_I_advance_timers_by_seconds;
10
- exports.When_I_wait_seconds = When_I_wait_seconds;
11
- exports.When_I_wait_milliseconds = When_I_wait_milliseconds;
12
- exports.When_I_set_step_timeout_to = When_I_set_step_timeout_to;
13
- exports.When_I_trigger_event_on_selector = When_I_trigger_event_on_selector;
14
- exports.When_I_trigger_event = When_I_trigger_event;
15
- exports.When_I_blur = When_I_blur;
16
- exports.When_I_focus = When_I_focus;
17
- exports.When_I_log = When_I_log;
18
- exports.When_I_debug = When_I_debug;
19
- exports.When_I_screenshot_named = When_I_screenshot_named;
20
- exports.When_I_screenshot = When_I_screenshot;
21
- exports.When_I_visit = When_I_visit;
22
- exports.When_I_reload_the_page = When_I_reload_the_page;
23
- exports.When_I_go_back = When_I_go_back;
24
- exports.When_I_go_forward = When_I_go_forward;
25
- exports.When_I_pause = When_I_pause;
26
- exports.When_I_store_date_offset = When_I_store_date_offset;
27
- exports.When_I_switch_to_iframe_with_selector = When_I_switch_to_iframe_with_selector;
28
- exports.When_I_switch_to_iframe_with_title = When_I_switch_to_iframe_with_title;
29
- exports.When_I_switch_to_iframe_with_selector_and_wait_for_text = When_I_switch_to_iframe_with_selector_and_wait_for_text;
30
- exports.When_I_exit_iframe = When_I_exit_iframe;
31
- exports.When_I_perform_action_on_subset_of_elements = When_I_perform_action_on_subset_of_elements;
32
- exports.When_I_perform_action_on_nth_element = When_I_perform_action_on_nth_element;
33
- exports.When_I_press_key = When_I_press_key;
34
- exports.When_I_set_viewport_to_device = When_I_set_viewport_to_device;
35
- exports.When_I_set_viewport_to_dimensions = When_I_set_viewport_to_dimensions;
36
- exports.When_I_set_playwright_page_config_key = When_I_set_playwright_page_config_key;
37
- exports.When_I_set_playwright_page_config_from_table = When_I_set_playwright_page_config_from_table;
38
- const cucumber_1 = require("@cucumber/cucumber");
39
- const test_1 = require("@playwright/test");
40
- const dayjs_1 = __importDefault(require("dayjs"));
41
- const optionsUtils_1 = require("../helpers/utils/optionsUtils"); // Assuming this path is correct
42
- const resolveUtils_1 = require("../helpers/utils/resolveUtils"); // Assuming this path is correct
43
- // ===================================================================================
44
- // UTILITY ACTIONS: TIMERS
45
- // ===================================================================================
46
- /**
47
- * Enables fake timers for the current page, fixing the time at the moment this step is executed.
48
- * This is useful for testing time-dependent UI components without actual time passing.
49
- *
50
- * ```gherkin
51
- * When I use fake timers
52
- * ```
53
- *
54
- * @example
55
- * When I use fake timers
56
- * And I go to "/countdown-page"
57
- * When I advance timers by 1000 milliseconds
58
- * Then I should see text "9 seconds remaining"
59
- *
60
- * @remarks
61
- * This step uses Playwright's `page.clock.setFixedTime()` to control the browser's internal
62
- * clock. All subsequent time-related operations (like `setTimeout`, `setInterval`, `Date.now()`)
63
- * will operate based on this fixed time. Use {@link When_I_advance_timers_by_milliseconds | "When I advance timers by X milliseconds"}
64
- * or {@link When_I_advance_timers_by_seconds | "When I advance timers by X seconds"} to progress time.
65
- * To revert, use {@link When_I_use_real_timers | "When I use real timers"}.
66
- * @category Timer Steps
67
- */
68
- async function When_I_use_fake_timers() {
69
- const initialTime = Date.now();
70
- await this.page.clock.setFixedTime(initialTime);
71
- this.fakeTimersActive = true; // Assuming CustomWorld has a fakeTimersActive property
72
- this.log?.(`⏱️ Fake timers enabled, fixed at ${new Date(initialTime).toISOString()}`);
73
- }
74
- (0, cucumber_1.When)(/^I use fake timers$/, When_I_use_fake_timers);
75
- /**
76
- * Restores real timers for the current page, releasing control over the browser's internal clock.
77
- *
78
- * ```gherkin
79
- * When I use real timers
80
- * ```
81
- *
82
- * @example
83
- * When I use fake timers
84
- * When I advance timers by 10 seconds
85
- * When I use real timers
86
- *
87
- * @remarks
88
- * This step uses Playwright's `page.clock.useRealTimers()`. After this step, `setTimeout`, `setInterval`,
89
- * and other time-related functions will behave normally, using the system's real time.
90
- * @category Timer Steps
91
- */
92
- async function When_I_use_real_timers() {
93
- // FIX: Use 'as any' to tell TypeScript that this property exists at runtime.
94
- // This is a common workaround for experimental/plugin-like APIs that aren't fully typed.
95
- await this.page.clock.useRealTimers();
96
- this.fakeTimersActive = false;
97
- this.log?.(`⏱️ Real timers restored.`);
98
- }
99
- (0, cucumber_1.When)(/^I use real timers$/, When_I_use_real_timers);
100
- async function When_I_advance_timers_by_milliseconds(ms) {
101
- if (this.fakeTimersActive) {
102
- // FIX: Use 'as any' for tick()
103
- await this.page.clock.tick(ms);
104
- this.log?.(`⏱️ Advanced fake timers by ${ms} milliseconds.`);
105
- }
106
- else {
107
- this.log?.("⚠️ Real timers are active. `When I advance timers by...` has no effect.");
108
- }
109
- }
110
- (0, cucumber_1.When)(/^I advance timers by (\d+) milliseconds$/, When_I_advance_timers_by_milliseconds); // This line remains unchanged
111
- /**
112
- * Advances fake timers by the given number of seconds. Requires fake timers to be enabled.
113
- *
114
- * ```gherkin
115
- * When I advance timers by {int} seconds
116
- * ```
117
- *
118
- * @param seconds - The number of seconds to advance the fake clock by.
119
- *
120
- * @example
121
- * When I use fake timers
122
- * When I advance timers by 2 seconds
123
- *
124
- * @remarks
125
- * This step converts seconds to milliseconds and uses Playwright's `page.clock.tick()`.
126
- * It will only have an effect if {@link When_I_use_fake_timers | "When I use fake timers"}
127
- * has been called previously. If real timers are active, a warning will be logged.
128
- * @category Timer Steps
129
- */
130
- async function When_I_advance_timers_by_seconds(seconds) {
131
- const ms = seconds * 1000;
132
- if (this.fakeTimersActive) {
133
- // FIX: Use 'as any' for tick()
134
- await this.page.clock.tick(ms);
135
- this.log?.(`⏱️ Advanced fake timers by ${seconds} seconds.`);
136
- }
137
- else {
138
- this.log?.("⚠️ Real timers are active. `When I advance timers by...` has no effect.");
139
- }
140
- }
141
- // This line remains unchanged, as it's just the Cucumber step definition linking to the function
142
- // When(/^I advance timers by (\d+) seconds$/, When_I_advance_timers_by_seconds);
143
- /**
144
- * Waits for the given number of seconds using `setTimeout`. This is a real-time wait.
145
- *
146
- * ```gherkin
147
- * When I wait {int} second[s]
148
- * ```
149
- *
150
- * @param seconds - The number of seconds to wait.
151
- *
152
- * @example
153
- * When I wait 3 seconds
154
- *
155
- * @remarks
156
- * This step pauses test execution for the specified duration using Node.js `setTimeout`.
157
- * It's generally preferred to use explicit waits for element conditions (e.g., `toBeVisible`)
158
- * over arbitrary waits, but this can be useful for debugging or waiting for external factors.
159
- * @category General Action Steps
160
- */
161
- async function When_I_wait_seconds(seconds) {
162
- await new Promise((resolve) => setTimeout(resolve, seconds * 1000));
163
- }
164
- (0, cucumber_1.When)(/^I wait (\d+) second[s]?$/, When_I_wait_seconds);
165
- /**
166
- * Waits for the given number of milliseconds using `setTimeout`. This is a real-time wait.
167
- *
168
- * ```gherkin
169
- * When I wait {int} millisecond[s]
170
- * ```
171
- *
172
- * @param ms - The number of milliseconds to wait.
173
- *
174
- * @example
175
- * When I wait 500 milliseconds
176
- *
177
- * @remarks
178
- * This step pauses test execution for the specified duration using Node.js `setTimeout`.
179
- * It's generally preferred to use explicit waits for element conditions (e.g., `toBeVisible`)
180
- * over arbitrary waits, but this can be useful for debugging or waiting for external factors.
181
- * @category General Action Steps
182
- */
183
- async function When_I_wait_milliseconds(ms) {
184
- await new Promise((res) => setTimeout(res, ms));
185
- }
186
- (0, cucumber_1.When)(/^I wait (\d+) millisecond[s]?$/, When_I_wait_milliseconds);
187
- /**
188
- * Sets the default step timeout for all subsequent Cucumber steps.
189
- * This can override the global timeout set in `cucumber.js` configuration.
190
- *
191
- * ```gherkin
192
- * When I set step timeout to {int} ms
193
- * ```
194
- *
195
- * @param timeoutMs - The new default timeout in milliseconds.
196
- *
197
- * @example
198
- * When I set step timeout to 10000 ms
199
- * And I find element by selector "#slow-loading-element"
200
- *
201
- * @remarks
202
- * This step uses Cucumber's `setDefaultTimeout()` function. It applies to all following
203
- * steps within the same test run. Use with caution as setting very high timeouts can
204
- * hide performance issues.
205
- * @category Configuration Steps
206
- */
207
- function When_I_set_step_timeout_to(timeoutMs) {
208
- (0, cucumber_1.setDefaultTimeout)(timeoutMs);
209
- this.log?.(`⏱️ Default Cucumber step timeout set to ${timeoutMs}ms`);
210
- }
211
- (0, cucumber_1.When)("I set step timeout to {int} ms", When_I_set_step_timeout_to);
212
- // ===================================================================================
213
- // UTILITY ACTIONS: EVENTS
214
- // ===================================================================================
215
- /**
216
- * Triggers a generic DOM event of the given type on the element matching the provided selector.
217
- *
218
- * ```gherkin
219
- * When I trigger {string} event on {string}
220
- * ```
221
- *
222
- * @param eventType - The type of DOM event to trigger (e.g., "change", "input", "focus").
223
- * @param selector - The CSS selector of the element to trigger the event on.
224
- *
225
- * @example
226
- * When I trigger "change" event on ".my-input"
227
- *
228
- * @remarks
229
- * This step uses Playwright's `locator.evaluate()` to dispatch a new `Event` directly
230
- * on the DOM element. It can be useful for simulating browser-level events that
231
- * might not be covered by Playwright's high-level actions (like `fill` for `input` events).
232
- * @category Event Steps
233
- */
234
- async function When_I_trigger_event_on_selector(eventType, selector) {
235
- await this.page.locator(selector).evaluate((el, type) => {
236
- const event = new Event(type, {
237
- bubbles: true,
238
- cancelable: true,
239
- });
240
- el.dispatchEvent(event);
241
- }, eventType);
242
- this.log?.(`💥 Triggered "${eventType}" event on element with selector "${selector}".`);
243
- }
244
- (0, cucumber_1.When)(/^I trigger "(.*)" event on "([^"]+)"$/, When_I_trigger_event_on_selector);
245
- /**
246
- * Triggers a generic DOM event of the given type on the previously selected element.
247
- *
248
- * ```gherkin
249
- * When I trigger event {string}
250
- * ```
251
- *
252
- * @param eventName - The name of the event to dispatch (e.g., "change", "input", "blur").
253
- *
254
- * @example
255
- * When I find element by selector ".my-input"
256
- * And I trigger event "change"
257
- *
258
- * @remarks
259
- * This step requires a preceding step that sets the {@link CustomWorld.element | current element}.
260
- * It uses Playwright's `locator.dispatchEvent()` to dispatch the specified event.
261
- * @category Event Steps
262
- */
263
- async function When_I_trigger_event(eventName) {
264
- if (!this.element)
265
- throw new Error("No element selected to trigger event on.");
266
- await this.element.dispatchEvent(eventName);
267
- this.log?.(`💥 Triggered "${eventName}" event on selected element.`);
268
- }
269
- (0, cucumber_1.When)("I trigger event {string}", When_I_trigger_event);
270
- /**
271
- * Removes focus from the previously selected element.
272
- *
273
- * ```gherkin
274
- * When I blur
275
- * ```
276
- *
277
- * @example
278
- * When I find element by selector "input[name='username']"
279
- * And I blur
280
- *
281
- * @remarks
282
- * This step requires a preceding step that sets the {@link CustomWorld.element | current element}.
283
- * It uses `locator.evaluate()` to call the DOM `blur()` method on the element,
284
- * simulating a loss of focus.
285
- * @category Event Steps
286
- */
287
- async function When_I_blur() {
288
- if (!this.element)
289
- throw new Error("No element selected to blur.");
290
- await this.element.evaluate((el) => el.blur());
291
- this.log?.(`👁️‍🗨️ Blurred selected element.`);
292
- }
293
- (0, cucumber_1.When)("I blur", When_I_blur);
294
- /**
295
- * Focuses the previously selected element.
296
- *
297
- * ```gherkin
298
- * When I focus
299
- * ```
300
- *
301
- * @example
302
- * When I find element by selector "input[name='search']"
303
- * And I focus
304
- *
305
- * @remarks
306
- * This step requires a preceding step that sets the {@link CustomWorld.element | current element}.
307
- * It uses Playwright's `locator.focus()` to bring the element into focus, simulating
308
- * a user tabbing to or clicking on the element.
309
- * @category Event Steps
310
- */
311
- async function When_I_focus() {
312
- if (!this.element)
313
- throw new Error("No element selected to focus.");
314
- await this.element.focus();
315
- this.log?.(`👁️‍🗨️ Focused selected element.`);
316
- }
317
- (0, cucumber_1.When)("I focus", When_I_focus);
318
- // ===================================================================================
319
- // UTILITY ACTIONS: DEBUGGING / LOGGING
320
- // ===================================================================================
321
- /**
322
- * Logs a message to the test output (stdout/console).
323
- *
324
- * ```gherkin
325
- * When I log {string}
326
- * ```
327
- *
328
- * @param message - The string message to log.
329
- *
330
- * @example
331
- * When I log "Test scenario started"
332
- *
333
- * @remarks
334
- * This step is useful for injecting debugging or informative messages directly
335
- * into the Cucumber test report or console output during test execution.
336
- * @category Debugging Steps
337
- */
338
- async function When_I_log(message) {
339
- this.log(message);
340
- }
341
- (0, cucumber_1.When)("I log {string}", When_I_log);
342
- /**
343
- * Triggers a debugger statement, pausing test execution if a debugger is attached.
344
- *
345
- * ```gherkin
346
- * When I debug
347
- * ```
348
- *
349
- * @example
350
- * When I find element by selector "#problematic-button"
351
- * And I debug
352
- * When I click current element
353
- *
354
- * @remarks
355
- * This step is extremely useful for interactive debugging. When executed with a debugger
356
- * (e.g., VS Code debugger attached to your Node.js process), it will pause execution
357
- * at this point, allowing you to inspect the browser state, variables, etc.
358
- * @category Debugging Steps
359
- */
360
- async function When_I_debug() {
361
- debugger; // This will pause execution if a debugger is attached
362
- }
363
- (0, cucumber_1.When)(/^I debug$/, When_I_debug);
364
- // ===================================================================================
365
- // UTILITY ACTIONS: SCREENSHOT
366
- // ===================================================================================
367
- /**
368
- * Takes a full-page screenshot of the current page and saves it with the given name.
369
- * The screenshot will be saved in the `e2e/screenshots/` directory (relative to your project root).
370
- *
371
- * ```gherkin
372
- * When I screenshot {string}
373
- * ```
374
- *
375
- * @param name - The desired filename for the screenshot (without extension).
376
- *
377
- * @example
378
- * When I screenshot "dashboard-view"
379
- *
380
- * @remarks
381
- * This step creates a PNG image. The `fullPage: true` option ensures that the
382
- * entire scrollable height of the page is captured.
383
- * @category Screenshot Steps
384
- */
385
- async function When_I_screenshot_named(name) {
386
- const screenshotPath = `e2e/screenshots/${name}.png`;
387
- await this.page.screenshot({
388
- path: screenshotPath,
389
- fullPage: true,
390
- });
391
- this.log?.(`📸 Saved screenshot to "${screenshotPath}"`);
392
- }
393
- (0, cucumber_1.When)(/^I screenshot "(.*)"$/, When_I_screenshot_named);
394
- /**
395
- * Takes a full-page screenshot of the current page and saves it with a timestamped filename.
396
- * The screenshot will be saved in the `screenshots/` directory (relative to your project root).
397
- *
398
- * ```gherkin
399
- * When I screenshot
400
- * ```
401
- *
402
- * @example
403
- * When I screenshot
404
- *
405
- * @remarks
406
- * This step is useful for quick visual debugging or capturing the state of the UI at
407
- * various points in the test without needing to manually name each file.
408
- * The filename will be in the format `screenshots/screenshot-TIMESTAMP.png`.
409
- * @category Screenshot Steps
410
- */
411
- async function When_I_screenshot() {
412
- const screenshotPath = `screenshots/screenshot-${Date.now()}.png`;
413
- await this.page.screenshot({ path: screenshotPath, fullPage: true });
414
- this.log?.(`📸 Saved screenshot to "${screenshotPath}"`);
415
- }
416
- (0, cucumber_1.When)("I screenshot", When_I_screenshot);
417
- // ===================================================================================
418
- // UTILITY ACTIONS: PAGE NAVIGATION
419
- // ===================================================================================
420
- /**
421
- * Navigates the browser to the given URL or an aliased URL.
422
- * If a relative path is provided (starts with `/`), it will be prepended with `process.env.BASE_URL`.
423
- *
424
- * ```gherkin
425
- * When I visit {string}
426
- * ```
427
- *
428
- * @param urlOrAlias - The URL to visit, or an alias (prefixed with `@`) pointing to a URL.
429
- *
430
- * @example
431
- * When I visit "/dashboard"
432
- * When I visit "https://www.example.com"
433
- * Given I store "https://my.app.com/profile" as "profilePageUrl"
434
- * When I visit "@profilePageUrl"
435
- *
436
- * @remarks
437
- * This step uses Playwright's `page.goto()`. Ensure `BASE_URL` environment variable is set
438
- * if you are using relative paths.
439
- * @category Page Navigation Steps
440
- */
441
- async function When_I_visit(urlOrAlias) {
442
- let url = urlOrAlias;
443
- if (url.startsWith("@")) {
444
- const alias = url.substring(1);
445
- url = this.data[alias];
446
- if (!url)
447
- throw new Error(`Alias "@${alias}" not found in test data.`);
448
- this.log?.(`🔗 Resolved alias "@${alias}" to URL: "${url}"`);
449
- }
450
- if (url.startsWith("/")) {
451
- const baseUrl = process.env.BASE_URL;
452
- if (!baseUrl)
453
- throw new Error("BASE_URL environment variable is not defined. Cannot visit relative URL.");
454
- // Ensure no double slashes if BASE_URL already ends with one
455
- url = `${baseUrl.replace(/\/+$/, "")}${url}`;
456
- }
457
- this.log?.(`🌍 Navigating to: "${url}"`);
458
- await this.page.goto(url);
459
- }
460
- (0, cucumber_1.When)("I visit {string}", When_I_visit);
461
- /**
462
- * Reloads the current page.
463
- *
464
- * ```gherkin
465
- * When I reload the page
466
- * ```
467
- *
468
- * @example
469
- * When I reload the page
470
- *
471
- * @remarks
472
- * This step is equivalent to hitting the browser's reload button.
473
- * It uses Playwright's `page.reload()`.
474
- * @category Page Navigation Steps
475
- */
476
- async function When_I_reload_the_page() {
477
- await this.page.reload();
478
- this.log?.(`🔄 Reloaded the current page.`);
479
- }
480
- (0, cucumber_1.When)("I reload the page", When_I_reload_the_page);
481
- /**
482
- * Navigates back in the browser's history.
483
- *
484
- * ```gherkin
485
- * When I go back
486
- * ```
487
- *
488
- * @example
489
- * Given I visit "/page1"
490
- * And I visit "/page2"
491
- * When I go back
492
- * Then I should be on "/page1"
493
- *
494
- * @remarks
495
- * This step is equivalent to hitting the browser's back button.
496
- * It uses Playwright's `page.goBack()`.
497
- * @category Page Navigation Steps
498
- */
499
- async function When_I_go_back() {
500
- await this.page.goBack();
501
- this.log?.(`⬅️ Navigated back in browser history.`);
502
- }
503
- (0, cucumber_1.When)("I go back", When_I_go_back);
504
- /**
505
- * Navigates forward in the browser's history.
506
- *
507
- * ```gherkin
508
- * When I go forward
509
- * ```
510
- *
511
- * @example
512
- * Given I visit "/page1"
513
- * And I visit "/page2"
514
- * When I go back
515
- * When I go forward
516
- * Then I should be on "/page2"
517
- *
518
- * @remarks
519
- * This step is equivalent to hitting the browser's forward button.
520
- * It uses Playwright's `page.goForward()`.
521
- * @category Page Navigation Steps
522
- */
523
- async function When_I_go_forward() {
524
- await this.page.goForward();
525
- this.log?.(`➡️ Navigated forward in browser history.`);
526
- }
527
- (0, cucumber_1.When)("I go forward", When_I_go_forward);
528
- /**
529
- * Pauses the test execution in debug mode.
530
- * This is useful for inspecting the browser state interactively during test runs.
531
- *
532
- * ```gherkin
533
- * When I pause
534
- * ```
535
- *
536
- * @example
537
- * When I perform an action
538
- * And I pause
539
- * Then I assert something visually
540
- *
541
- * @remarks
542
- * When running tests in debug mode (e.g., with `npx playwright test --debug`),
543
- * this step will open Playwright's inspector, allowing you to step through
544
- * actions, inspect elements, and troubleshoot. The test will resume when you
545
- * continue from the inspector.
546
- * @category Debugging Steps
547
- */
548
- async function When_I_pause() {
549
- await this.page.pause();
550
- this.log?.(`⏸️ Test paused. Use Playwright Inspector to continue.`);
551
- }
552
- (0, cucumber_1.When)("I pause", When_I_pause);
553
- // ===================================================================================
554
- // UTILITY ACTIONS: DATE/TIME ALIASING
555
- // ===================================================================================
556
- const validDateUnits = [
557
- "second",
558
- "seconds",
559
- "minute",
560
- "minutes",
561
- "hour",
562
- "hours",
563
- "day",
564
- "days",
565
- "week",
566
- "weeks",
567
- "month",
568
- "months",
569
- "year",
570
- "years",
571
- ];
572
- /**
573
- * Stores a new date calculated by offsetting an existing aliased date by a given amount and unit.
574
- *
575
- * ```gherkin
576
- * When I store {string} {int} {word} {word} as "{word}"
577
- * ```
578
- *
579
- * @param baseAlias - The alias of an existing date string in `this.data` (e.g., "today").
580
- * @param amount - The numerical amount to offset by (e.g., 2, 5).
581
- * @param unit - The unit of time (e.g., "days", "months", "hours").
582
- * @param direction - Whether to offset "before" or "after" the base date.
583
- * @param newAlias - The alias under which to store the newly calculated date.
584
- *
585
- * @example
586
- * Given I store "2024-01-15" as "invoiceDate"
587
- * When I store "invoiceDate" 30 days after as "dueDate"
588
- * Then the value of alias "dueDate" should be "2024-02-14"
589
- *
590
- * @remarks
591
- * This step uses the `dayjs` library for date manipulation. The `baseAlias` must
592
- * point to a valid date string that `dayjs` can parse. The `unit` must be one of:
593
- * "second", "minute", "hour", "day", "week", "month", "year" (plural forms also supported).
594
- * The new date is stored in `this.data` in "YYYY-MM-DD" format.
595
- * @category Data Manipulation Steps
596
- */
597
- async function When_I_store_date_offset(baseAlias, amount, unit, direction, // "before" or "after"
598
- newAlias) {
599
- const baseDateRaw = this.data?.[baseAlias];
600
- if (!baseDateRaw)
601
- throw new Error(`Alias "${baseAlias}" not found in test data.`);
602
- if (!validDateUnits.includes(unit)) {
603
- throw new Error(`Invalid unit "${unit}". Valid units are: ${validDateUnits.join(", ")}.`);
604
- }
605
- if (!["before", "after"].includes(direction)) {
606
- throw new Error(`Invalid direction "${direction}". Must be "before" or "after".`);
607
- }
608
- const baseDate = (0, dayjs_1.default)(baseDateRaw);
609
- if (!baseDate.isValid()) {
610
- throw new Error(`Value for alias "${baseAlias}" ("${baseDateRaw}") is not a valid date.`);
611
- }
612
- const result = baseDate[direction === "before" ? "subtract" : "add"](amount, unit);
613
- const formatted = result.format("YYYY-MM-DD");
614
- this.data[newAlias] = formatted;
615
- this.log?.(`📅 Stored ${amount} ${unit} ${direction} "${baseAlias}" as "@${newAlias}" = "${formatted}"`);
616
- }
617
- (0, cucumber_1.When)('I store {string} {int} {word} {word} as "{word}"', When_I_store_date_offset);
618
- // ===================================================================================
619
- // UTILITY ACTIONS: IFRAME
620
- // ===================================================================================
621
- /**
622
- * Switches the current Playwright context to an iframe located by a CSS selector.
623
- * The step waits for the iframe's `body` element to be visible before proceeding.
624
- *
625
- * ```gherkin
626
- * When I switch to iframe with selector {string}
627
- * ```
628
- *
629
- * @param selector - The CSS selector for the iframe element (e.g., "#my-iframe", "iframe[name='chatFrame']").
630
- *
631
- * @example
632
- * When I switch to iframe with selector "#payment-form-iframe"
633
- * And I find element by placeholder text "Card Number"
634
- * And I type "1234..."
635
- *
636
- * @remarks
637
- * Once inside an iframe, all subsequent element finding and interaction steps will
638
- * target elements within that iframe. To exit the iframe context, use
639
- * {@link When_I_exit_iframe | "When I exit iframe"}.
640
- * @category IFrame Steps
641
- */
642
- async function When_I_switch_to_iframe_with_selector(selector) {
643
- const frameLocator = this.page.frameLocator(selector);
644
- // Wait for an element inside the iframe to ensure it's loaded and ready
645
- await frameLocator.locator("body").waitFor({ state: "visible", timeout: 10000 });
646
- this.frame = frameLocator; // Store the frame locator in CustomWorld context
647
- this.log?.(`🪟 Switched to iframe with selector: "${selector}".`);
648
- }
649
- (0, cucumber_1.When)("I switch to iframe with selector {string}", When_I_switch_to_iframe_with_selector);
650
- /**
651
- * Switches the current Playwright context to an iframe located by its title attribute.
652
- *
653
- * ```gherkin
654
- * When I switch to iframe with title {string}
655
- * ```
656
- *
657
- * @param title - The title of the iframe to switch to.
658
- *
659
- * @example
660
- * When I switch to iframe with title "My Iframe"
661
- * And I find element by label text "Card Holder"
662
- *
663
- * @remarks
664
- * This step iterates through all frames on the page to find one whose title matches
665
- * (case-insensitively, partially matched with `includes`). Once found, subsequent
666
- * element operations will target elements within this iframe. To exit, use
667
- * {@link When_I_exit_iframe | "When I exit iframe"}.
668
- * @category IFrame Steps
669
- */
670
- async function When_I_switch_to_iframe_with_title(title) {
671
- // Find the frame by title first to ensure it exists before creating locator
672
- const frames = this.page.frames();
673
- const foundFrame = await Promise.race(frames.map(async (f) => {
674
- const frameTitle = await f.title();
675
- return frameTitle.includes(title) ? f : null;
676
- }));
677
- if (!foundFrame)
678
- throw new Error(`No iframe with title "${title}" found.`);
679
- // Playwright recommends using frameLocator for interacting with iframes,
680
- // even if found via frame object.
681
- this.frame = this.page.frameLocator(`iframe[title*="${title}"]`);
682
- this.log?.(`🪟 Switched to iframe titled: "${title}".`);
683
- }
684
- (0, cucumber_1.When)("I switch to iframe with title {string}", When_I_switch_to_iframe_with_title);
685
- /**
686
- * Switches the current Playwright context to an iframe located by a CSS selector,
687
- * and then waits for specific text to become visible inside that iframe.
688
- *
689
- * ```gherkin
690
- * When I switch to iframe with selector {string} and wait for text {string}
691
- * ```
692
- *
693
- * @param selector - The CSS selector for the iframe element.
694
- * @param expectedText - The text string to wait for inside the iframe.
695
- *
696
- * @example
697
- * When I switch to iframe with selector "#dynamic-content-iframe" and wait for text "Content Loaded"
698
- *
699
- * @remarks
700
- * This step combines switching into an iframe with a wait condition, which is
701
- * useful for dynamic iframe content. The `expectedText` must be present
702
- * and visible inside the iframe. To exit the iframe context, use
703
- * {@link When_I_exit_iframe | "When I exit iframe"}.
704
- * @category IFrame Steps
705
- */
706
- async function When_I_switch_to_iframe_with_selector_and_wait_for_text(selector, expectedText) {
707
- const frameLocator = this.page.frameLocator(selector);
708
- // Wait for the specific text inside the iframe
709
- await frameLocator.locator(`text=${expectedText}`).waitFor({ timeout: 10000 });
710
- this.frame = frameLocator;
711
- this.log?.(`🪟 Switched to iframe with selector: "${selector}", and waited for text: "${expectedText}".`);
712
- }
713
- (0, cucumber_1.When)("I switch to iframe with selector {string} and wait for text {string}", When_I_switch_to_iframe_with_selector_and_wait_for_text);
714
- /**
715
- * Exits the current iframe context, returning the Playwright context to the main page.
716
- * All subsequent element finding and interaction steps will operate on the main page.
717
- *
718
- * ```gherkin
719
- * When I exit iframe
720
- * ```
721
- *
722
- * @example
723
- * When I switch to iframe with selector "#my-iframe"
724
- * And I fill "my data"
725
- * When I exit iframe
726
- * And I click "Main Page Button"
727
- *
728
- * @remarks
729
- * This step is crucial for navigating back to the main document after interacting
730
- * with elements inside an iframe. It sets `this.frame` back to `undefined` (or the main page locator).
731
- * @category IFrame Steps
732
- */
733
- function When_I_exit_iframe() {
734
- this.exitIframe(); // Assuming CustomWorld has an exitIframe method
735
- this.log?.(`🪟 Exited iframe context, now interacting with the main page.`);
736
- }
737
- (0, cucumber_1.When)("I exit iframe", When_I_exit_iframe);
738
- // ===================================================================================
739
- // UTILITY ACTIONS: REUSABLE ACTIONS ON STORED ELEMENTS
740
- // ===================================================================================
741
- // Helper functions (keep these outside the exports or as internal helpers)
742
- function toOrdinal(n) {
743
- const s = ["th", "st", "nd", "rd"];
744
- const v = n % 100;
745
- return n + (s[(v - 20) % 10] || s[v] || s[0]);
746
- }
747
- async function getReadableLabel(el) {
748
- try {
749
- const tag = await el.evaluate((el) => el.tagName.toLowerCase());
750
- return tag === "input" ? await el.inputValue() : (await el.innerText()).trim();
751
- }
752
- catch {
753
- return "(unknown)";
754
- }
755
- }
756
- // Helper to get a subset of elements (first, last, random, or specific nth for action)
757
- async function getElementsSubset(world, mode, count) {
758
- const total = await world.elements?.count();
759
- if (!total || total < 1)
760
- throw new Error("No elements stored in 'this.elements' collection.");
761
- if (count > total)
762
- throw new Error(`Cannot get ${count} elements, only ${total} available.`);
763
- switch (mode) {
764
- case "first":
765
- return Array.from({ length: count }, (_, i) => world.elements.nth(i));
766
- case "last":
767
- return Array.from({ length: count }, (_, i) => world.elements.nth(total - count + i));
768
- case "random":
769
- // Generate unique random indices
770
- const indices = new Set();
771
- while (indices.size < count) {
772
- indices.add(Math.floor(Math.random() * total));
773
- }
774
- return Array.from(indices).map((i) => world.elements.nth(i));
775
- case "nth": // Used specifically by the "I (action) the Nth element" step
776
- if (count < 1)
777
- throw new Error(`Invalid Nth element index: ${count}. Must be 1 or greater.`);
778
- if (count > total)
779
- throw new Error(`Cannot get ${toOrdinal(count)} element, only ${total} available.`);
780
- return [world.elements.nth(count - 1)]; // Return as array for consistent loop below
781
- default:
782
- throw new Error(`Unsupported subset mode: "${mode}".`);
783
- }
784
- }
785
- const actionDisplayNames = {
786
- click: "Clicked",
787
- hover: "Hovered",
788
- check: "Checked",
789
- uncheck: "Unchecked",
790
- focus: "Focused",
791
- blur: "Blurred",
792
- fill: "Filled",
793
- };
794
- // Define the functions that perform the Playwright actions on a locator
795
- const locatorActions = {
796
- click: (el, table) => el.click((0, optionsUtils_1.parseClickOptions)(table)),
797
- hover: (el, table) => el.hover((0, optionsUtils_1.parseHoverOptions)(table)),
798
- check: (el, table) => el.check((0, optionsUtils_1.parseCheckOptions)(table)),
799
- uncheck: (el, table) => el.uncheck((0, optionsUtils_1.parseUncheckOptions)(table)),
800
- focus: (el) => el.focus(),
801
- blur: (el) => el.evaluate((e) => e.blur()),
802
- fill: (el, table) => el.fill("", (0, optionsUtils_1.parseFillOptions)(table)), // This `fill` is generic. If you need to fill with a specific value from the step,
803
- // you'll need to pass it as an argument to the actionFn in the step definition.
804
- // For now, it's just clearing.
805
- };
806
- /**
807
- * Performs a specified action (e.g., click, hover, check, uncheck, focus, blur)
808
- * on a subset of the previously stored elements (first N, last N, or random N).
809
- *
810
- * ```gherkin
811
- * When I {word} the {word} {int}
812
- * ```
813
- *
814
- * @param action - The action to perform (e.g., "click", "hover", "check", "uncheck", "focus", "blur", "fill").
815
- * @param mode - The selection mode: "first", "last", or "random".
816
- * @param count - The number of elements to apply the action to.
817
- * @param table - (Optional) A Cucumber DataTable for action-specific options (e.g., `ClickOptions`).
818
- *
819
- * @example
820
- * Given I find elements by selector ".item-checkbox"
821
- * When I check the first 2
822
- * When I hover the last 3
823
- *
824
- * @remarks
825
- * This step requires that `this.elements` (a Playwright `Locator` that points to multiple
826
- * elements) has been populated by a preceding step (e.g., `When I find elements by selector`).
827
- * The `action` must be one of the supported actions. The `count` specifies how many of
828
- * the matched elements to target.
829
- * @category Multi-Element Action Steps
830
- */
831
- async function When_I_perform_action_on_subset_of_elements(action, mode, count, table) {
832
- const elements = await getElementsSubset(this, mode, count);
833
- const actionFn = locatorActions[action];
834
- if (!actionFn)
835
- throw new Error(`Unsupported action: "${action}".`);
836
- for (const el of elements) {
837
- const label = await getReadableLabel(el);
838
- await actionFn(el, table);
839
- this.log?.(`✅ ${actionDisplayNames[action] || action} element: "${label}".`);
840
- }
841
- }
842
- (0, cucumber_1.When)(/^I (\w+) the (first|last|random) (\d+)$/, When_I_perform_action_on_subset_of_elements);
843
- /**
844
- * Performs a specified action (e.g., click, hover, check, uncheck, focus, blur)
845
- * on the Nth element of the previously stored elements collection.
846
- *
847
- * ```gherkin
848
- * When I {word} the {int}(?:st|nd|rd|th) element
849
- * ```
850
- *
851
- * @param action - The action to perform (e.g., "click", "hover", "check", "uncheck", "focus", "blur", "fill").
852
- * @param nth - The 1-based index of the element to target (e.g., 1 for 1st, 2 for 2nd).
853
- * @param table - (Optional) A Cucumber DataTable for action-specific options.
854
- *
855
- * @example
856
- * Given I find elements by selector ".product-card"
857
- * When I click the 2nd element
858
- * When I fill the 1st element
859
- *
860
- * @remarks
861
- * This step requires that `this.elements` has been populated by a preceding step.
862
- * It targets a single element at a specific 1-based ordinal position within that collection.
863
- * The `action` must be one of the supported actions.
864
- * @category Multi-Element Action Steps
865
- */
866
- async function When_I_perform_action_on_nth_element(action, nth, table) {
867
- // Use "nth" mode with getElementsSubset to correctly fetch a single element
868
- const elements = await getElementsSubset(this, "nth", nth); // This will return an array with one element
869
- const targetElement = elements[0]; // Get the single element
870
- const actionFn = locatorActions[action];
871
- if (!actionFn)
872
- throw new Error(`Unsupported action: "${action}".`);
873
- const label = await getReadableLabel(targetElement);
874
- await actionFn(targetElement, table);
875
- this.log?.(`✅ ${actionDisplayNames[action] || action} the ${toOrdinal(nth)} element: "${label}".`);
876
- }
877
- (0, cucumber_1.When)(/^I (\w+) the (\d+)(?:st|nd|rd|th) element$/, When_I_perform_action_on_nth_element);
878
- /**
879
- * Presses a specific key on the previously selected element.
880
- *
881
- * ```gherkin
882
- * When I press key {string}
883
- * ```
884
- *
885
- * @param key - The key to press (e.g., "Enter", "Escape", "ArrowDown", "Tab").
886
- *
887
- * @example
888
- * When I find element by selector "input[name='email']"
889
- * And I type "my query"
890
- * When I press key "Enter"
891
- *
892
- * @remarks
893
- * This step requires a preceding step that sets the {@link CustomWorld.element | current element}.
894
- * It first focuses the element and then simulates a key press.
895
- * This is useful for triggering keyboard shortcuts or submitting forms via "Enter".
896
- * @category Keyboard Interaction Steps
897
- */
898
- async function When_I_press_key(key) {
899
- if (!this.element)
900
- throw new Error("No element selected to press key on.");
901
- await this.element.focus();
902
- await this.page.waitForTimeout(50); // Small buffer to ensure focus
903
- await this.element.press(key);
904
- this.log?.(`🎹 Pressed key "{${key}}" on selected element.`);
905
- }
906
- (0, cucumber_1.When)("I press key {string}", When_I_press_key);
907
- // ===================================================================================
908
- // UTILITY ACTIONS: VIEWPORT
909
- // ===================================================================================
910
- /**
911
- * Sets the browser viewport to emulate a specific Playwright device profile and orientation.
912
- * This will close the current browser context and open a new one with the specified device settings.
913
- *
914
- * ```gherkin
915
- * When I set viewport to {string}
916
- * When I set viewport to {string} and {string}
917
- * ```
918
- *
919
- * @param deviceInput - The name of the Playwright device (e.g., "iPhone 12", "iPad", "Desktop Chrome").
920
- * @param orientation - (Optional) The orientation, either "landscape" or "portrait" (default if not specified).
921
- *
922
- * @example
923
- * When I set viewport to "iPhone 12"
924
- * When I set viewport to "iPad" and "landscape"
925
- *
926
- * @remarks
927
- * This step creates a *new* browser context and page, so any previous page state or
928
- * setup (like routes, localStorage) will be reset.
929
- * The `deviceInput` is normalized to match Playwright's `devices` object keys.
930
- * @category Browser Context Steps
931
- */
932
- async function When_I_set_viewport_to_device(deviceInput, orientation) {
933
- const normalizedDevice = (0, resolveUtils_1.normalizeDeviceName)(deviceInput);
934
- if (!normalizedDevice) {
935
- throw new Error(`🚫 Unknown device name: "${deviceInput}". Check Playwright 'devices' for valid names.`);
936
- }
937
- const baseDevice = test_1.devices[normalizedDevice];
938
- if (!baseDevice) {
939
- throw new Error(`🚫 Playwright device not found for normalized name: "${normalizedDevice}".`);
940
- }
941
- const isLandscape = orientation?.toLowerCase() === "landscape";
942
- const deviceSettings = isLandscape
943
- ? baseDevice.landscape // Use Playwright's built-in landscape config if available
944
- ? baseDevice.landscape
945
- : {
946
- // Otherwise, manually adjust for landscape
947
- ...baseDevice,
948
- isMobile: true, // Assuming mobile devices for landscape adjustment
949
- viewport: {
950
- width: baseDevice.viewport.height, // Swap width and height for landscape
951
- height: baseDevice.viewport.width,
952
- },
953
- }
954
- : baseDevice; // Use base device settings for portrait or non-mobile
955
- // Close current context and page before creating a new one
956
- if (this.page)
957
- await this.page.close();
958
- if (this.context)
959
- await this.context.close();
960
- this.context = await this.browser.newContext(deviceSettings);
961
- this.page = await this.context.newPage();
962
- this.log?.(`📱 Set viewport to ${normalizedDevice}${isLandscape ? " in landscape" : ""}.`);
963
- }
964
- (0, cucumber_1.When)(/^I set viewport to "([^"]+)"(?: and "([^"]+)")?$/, When_I_set_viewport_to_device);
965
- /**
966
- * Sets the viewport to the given width and height in pixels.
967
- * This will close the current browser context and open a new one with the specified dimensions.
968
- *
969
- * ```gherkin
970
- * When I set viewport to {int}px by {int}px
971
- * ```
972
- *
973
- * @param width - The desired viewport width in pixels.
974
- * @param height - The desired viewport height in pixels.
975
- *
976
- * @example
977
- * When I set viewport to 1280px by 720px
978
- *
979
- * @remarks
980
- * This step creates a *new* browser context and page, so any previous page state or
981
- * setup (like routes, localStorage) will be reset.
982
- * @category Browser Context Steps
983
- */
984
- async function When_I_set_viewport_to_dimensions(width, height) {
985
- // Close current context and page before creating a new one
986
- if (this.page)
987
- await this.page.close();
988
- if (this.context)
989
- await this.context.close();
990
- // Recreate new context with the desired viewport
991
- this.context = await this.browser.newContext({
992
- viewport: { width, height },
993
- });
994
- this.page = await this.context.newPage();
995
- this.log?.(`🖥️ Set viewport to ${width}px by ${height}px.`);
996
- }
997
- (0, cucumber_1.When)("I set viewport to {int}px by {int}px", When_I_set_viewport_to_dimensions);
998
- // ===================================================================================
999
- // UTILITY ACTIONS: DYNAMIC PLAYWRIGHT CONFIG SETTERS (FOR PAGE-ONLY CONFIG)
1000
- // ===================================================================================
1001
- /**
1002
- * Sets a specific Playwright page configuration property to the given value.
1003
- * This can be used to dynamically change page-level settings during a test.
1004
- *
1005
- * ```gherkin
1006
- * When I set Playwright config {word} to {string}
1007
- * ```
1008
- *
1009
- * @param key - The name of the Playwright `Page` property to set (e.g., "userAgent", "defaultTimeout").
1010
- * @param value - The string value to set the property to. Note: All values are treated as strings.
1011
- *
1012
- * @example
1013
- * When I set Playwright config "userAgent" to "MyCustomAgent"
1014
- *
1015
- * @remarks
1016
- * This step directly assigns a value to a property on the `this.page` object.
1017
- * It's important to know which properties are settable and what their expected
1018
- * types are. Using incorrect keys or values may lead to unexpected behavior or errors.
1019
- * Not all Playwright page properties are designed to be set this way after page creation.
1020
- * @category Configuration Steps
1021
- */
1022
- async function When_I_set_playwright_page_config_key(key, value) {
1023
- // Directly assign property. Using 'as any' to bypass strict type checking,
1024
- // but be cautious as not all page properties are meant to be set this way dynamically.
1025
- this.page[key] = value;
1026
- this.log?.(`⚙️ Set Playwright page config "${key}" to "${value}".`);
1027
- }
1028
- (0, cucumber_1.When)('I set Playwright config "{word}" to {string}', When_I_set_playwright_page_config_key);
1029
- /**
1030
- * Sets multiple Playwright page configuration properties using a data table.
1031
- *
1032
- * ```gherkin
1033
- * When I set Playwright config
1034
- * | key | value |
1035
- * | userAgent | MyAgent |
1036
- * | defaultTimeout | 5000 |
1037
- * ```
1038
- *
1039
- * @param table - A Cucumber DataTable with two columns: `key` (the property name)
1040
- * and `value` (the string value to set).
1041
- *
1042
- * @example
1043
- * When I set Playwright config
1044
- * | key | value |
1045
- * | userAgent | TestBot |
1046
- * | defaultTimeout | 10000 |
1047
- *
1048
- * @remarks
1049
- * Similar to the single-key version, this step dynamically assigns values to
1050
- * properties on the `this.page` object. All values from the data table are
1051
- * treated as strings. Use with caution, understanding which properties can
1052
- * be dynamically set.
1053
- * @category Configuration Steps
1054
- */
1055
- async function When_I_set_playwright_page_config_from_table(table) {
1056
- for (const [key, value] of table.rows()) {
1057
- this.page[key] = value; // Direct assignment with 'as any'
1058
- this.log?.(`⚙️ Set Playwright page config "${key}" to "${value}".`);
1059
- }
1060
- }
1061
- (0, cucumber_1.When)("I set Playwright config", When_I_set_playwright_page_config_from_table);