playwright-cucumber-ts-steps 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +174 -39
  2. package/{dist → lib}/actions/clickSteps.js +10 -3
  3. package/{dist → lib}/actions/debugSteps.js +2 -5
  4. package/{dist → lib}/actions/elementFindSteps.js +15 -18
  5. package/lib/actions/fillFormSteps.js +130 -0
  6. package/{dist → lib}/actions/inputSteps.js +9 -76
  7. package/{dist → lib}/actions/interceptionSteps.js +8 -0
  8. package/{dist → lib}/actions/miscSteps.js +60 -12
  9. package/{dist → lib}/actions/storageSteps.js +26 -4
  10. package/{dist → lib}/assertions/buttonAndTextVisibilitySteps.js +3 -23
  11. package/{dist → lib}/assertions/elementSteps.js +7 -1
  12. package/{dist → lib}/assertions/locationSteps.js +16 -0
  13. package/{dist → lib}/assertions/semanticSteps.js +16 -3
  14. package/{dist → lib}/assertions/visualSteps.js +9 -5
  15. package/lib/custom_setups/loginHooks.js +113 -0
  16. package/lib/helpers/hooks.js +210 -0
  17. package/{dist → lib}/helpers/utils/index.d.ts +1 -0
  18. package/{dist → lib}/helpers/utils/index.js +1 -0
  19. package/{dist → lib}/helpers/utils/optionsUtils.d.ts +5 -0
  20. package/{dist → lib}/helpers/utils/optionsUtils.js +12 -3
  21. package/{dist → lib}/helpers/utils/resolveUtils.d.ts +2 -0
  22. package/{dist → lib}/helpers/utils/resolveUtils.js +13 -3
  23. package/lib/helpers/utils/sessionUtils.d.ts +3 -0
  24. package/lib/helpers/utils/sessionUtils.js +40 -0
  25. package/{dist → lib}/helpers/world.d.ts +13 -0
  26. package/{dist → lib}/helpers/world.js +17 -10
  27. package/lib/iframes/frames.d.ts +1 -0
  28. package/{dist → lib}/index.d.ts +1 -1
  29. package/{dist → lib}/index.js +1 -1
  30. package/{dist → lib}/register.js +1 -5
  31. package/package.json +40 -6
  32. package/dist/custom_setups/globalLogin.d.ts +0 -2
  33. package/dist/custom_setups/globalLogin.js +0 -25
  34. package/dist/custom_setups/loginHooks.js +0 -141
  35. package/dist/helpers/hooks.js +0 -184
  36. package/{dist → lib}/actions/clickSteps.d.ts +0 -0
  37. package/{dist → lib}/actions/cookieSteps.d.ts +0 -0
  38. package/{dist → lib}/actions/cookieSteps.js +0 -0
  39. package/{dist → lib}/actions/debugSteps.d.ts +0 -0
  40. package/{dist → lib}/actions/elementFindSteps.d.ts +0 -0
  41. package/{dist/actions/inputSteps.d.ts → lib/actions/fillFormSteps.d.ts} +0 -0
  42. package/{dist/actions/interceptionSteps.d.ts → lib/actions/inputSteps.d.ts} +0 -0
  43. package/{dist/actions/miscSteps.d.ts → lib/actions/interceptionSteps.d.ts} +0 -0
  44. package/{dist/actions/mouseSteps.d.ts → lib/actions/miscSteps.d.ts} +0 -0
  45. package/{dist/actions/scrollSteps.d.ts → lib/actions/mouseSteps.d.ts} +0 -0
  46. package/{dist → lib}/actions/mouseSteps.js +0 -0
  47. package/{dist/actions/storageSteps.d.ts → lib/actions/scrollSteps.d.ts} +0 -0
  48. package/{dist → lib}/actions/scrollSteps.js +0 -0
  49. package/{dist/assertions → lib/actions}/storageSteps.d.ts +0 -0
  50. package/{dist → lib}/assertions/buttonAndTextVisibilitySteps.d.ts +0 -0
  51. package/{dist → lib}/assertions/cookieSteps.d.ts +0 -0
  52. package/{dist → lib}/assertions/cookieSteps.js +0 -0
  53. package/{dist → lib}/assertions/elementSteps.d.ts +0 -0
  54. package/{dist → lib}/assertions/formInputSteps.d.ts +0 -0
  55. package/{dist → lib}/assertions/formInputSteps.js +0 -0
  56. package/{dist → lib}/assertions/interceptionRequestsSteps.d.ts +0 -0
  57. package/{dist → lib}/assertions/interceptionRequestsSteps.js +0 -0
  58. package/{dist → lib}/assertions/locationSteps.d.ts +0 -0
  59. package/{dist → lib}/assertions/roleTestIdSteps.d.ts +0 -0
  60. package/{dist → lib}/assertions/roleTestIdSteps.js +0 -0
  61. package/{dist → lib}/assertions/semanticSteps.d.ts +0 -0
  62. package/{dist/assertions/visualSteps.d.ts → lib/assertions/storageSteps.d.ts} +0 -0
  63. package/{dist → lib}/assertions/storageSteps.js +1 -1
  64. /package/{dist/custom_setups/loginHooks.d.ts → lib/assertions/visualSteps.d.ts} +0 -0
  65. /package/{dist/helpers/hooks.d.ts → lib/custom_setups/loginHooks.d.ts} +0 -0
  66. /package/{dist → lib}/helpers/checkPeerDeps.d.ts +0 -0
  67. /package/{dist → lib}/helpers/checkPeerDeps.js +0 -0
  68. /package/{dist → lib}/helpers/compareSnapshots.d.ts +0 -0
  69. /package/{dist → lib}/helpers/compareSnapshots.js +0 -0
  70. /package/{dist/iframes/frames.d.ts → lib/helpers/hooks.d.ts} +0 -0
  71. /package/{dist → lib}/helpers/utils/fakerUtils.d.ts +0 -0
  72. /package/{dist → lib}/helpers/utils/fakerUtils.js +0 -0
  73. /package/{dist → lib}/iframes/frames.js +0 -0
  74. /package/{dist → lib}/register.d.ts +0 -0
package/README.md CHANGED
@@ -9,17 +9,20 @@
9
9
 
10
10
  > A collection of reusable Playwright step definitions for Cucumber in TypeScript, designed to streamline end-to-end testing across web, API, and mobile applications.
11
11
 
12
+ Here's the updated **README** with all the latest features and corrections integrated into your `playwright-cucumber-ts-steps` package:
13
+
12
14
  ---
13
15
 
14
16
  ## ✨ Features
15
17
 
16
18
  - 🧩 Plug-and-play Cucumber step definitions
17
- - 🎯 Support for UI, API, mobile, iframe, session reuse, and visual testing
18
- - 🗂️ Alias and Faker support for dynamic data
19
- - 📸 Optional screenshot/video capture and pixel-diff comparison
20
- - 🤖 Built with modern Playwright + Cucumber ecosystem
21
-
22
- ---
19
+ - 🎯 Support for **UI**, **API**, **mobile**, **iframe**, **hybrid login**, and **visual testing**
20
+ - 🧠 Smart **session management** via storageState, `localStorage`, `sessionStorage`, and alias reuse
21
+ - 🗂️ **Alias**, **Faker**, `.env`, and dynamic JSON fixture support
22
+ - 📸 **Screenshot** on failure, 🎥 **video recording**, and 🖼️ **visual diff** with baseline comparison
23
+ - 📤 Supports **file upload**, **drag-and-drop**, and **multi-user session flows**
24
+ - 🌐 Fully supports **API requests with inline assertions**, payload from custom folders, and session injection
25
+ - ✅ Compatible with both `Page` and `FrameLocator` contexts (iframe-aware)
23
26
 
24
27
  ## 📦 Installation
25
28
 
@@ -55,33 +58,14 @@ npx playwright install
55
58
 
56
59
  ## 🛠️ Usage
57
60
 
58
- 1. **Import the step definitions** in your Cucumber environment:
59
-
60
- ```ts
61
- // e2e/steps/world.ts
62
- import { setWorldConstructor } from "@cucumber/cucumber";
63
- import { CustomWorld } from "playwright-cucumber-ts-steps";
64
-
65
- setWorldConstructor(CustomWorld);
66
- ```
67
-
68
- 2. **Load step definitions** from the package:
61
+ 1. **Load step definitions** from the package:
69
62
 
70
63
  ```ts
71
64
  // e2e/steps/index.ts
72
- import "playwright-cucumber-ts-steps";
65
+ import "playwright-cucumber-ts-steps/register";
73
66
  ```
74
67
 
75
- Or import specific step categories:
76
-
77
- ```ts
78
- import "playwright-cucumber-ts-steps/src/assertions";
79
- import "playwright-cucumber-ts-steps/src/actions";
80
- import "playwright-cucumber-ts-steps/src/forms";
81
- import "playwright-cucumber-ts-steps/src/api";
82
- ```
83
-
84
- 3. **Use step definitions in your feature files**:
68
+ 2. **Use step definitions in your feature files**:
85
69
 
86
70
  ```gherkin
87
71
  Feature: Login
@@ -149,41 +133,192 @@ export class CustomWorld extends BaseWorld {
149
133
 
150
134
  ## 📸 Advanced Usage
151
135
 
152
- These features are **optional** and can be implemented in your own `hooks.ts`:
136
+ These features are **optional** and can be implemented in your own `cucumber.js`:
153
137
 
154
138
  - 📷 **Visual regression testing** with pixelmatch
155
139
  - 🎥 **Video recording per scenario**
156
140
  - 🔐 **Session reuse** using `storageState`
157
141
 
158
- To keep this package focused, no hooks or reporting setup is included directly. See [`e2e/support/hooks.ts`](./e2e/support/hooks.ts) in the example project for reference.
142
+ ---
143
+
144
+ ---
145
+
146
+ ### ✅ Additional Supported Step Features
147
+
148
+ | Feature Type | Description |
149
+ | ---------------- | ------------------------------------------------------------------------------------- |
150
+ | `fill form data` | Use `When I fill the following "Login" form data:` to perform actions like: |
151
+ | | - `fill`, `click`, `check`, `select`, `upload:<file>` |
152
+ | | - `drag:<targetSelector>` for drag-and-drop |
153
+ | | - `assert:visible`, `assert:text:<value>` |
154
+ | | - `request:<METHOD>:<URL>:<payload>.json` |
155
+ | | - `set:localStorage:<key>`, `set:sessionStorage:<key>` |
156
+ | | - `wait:<ms>`, `reload`, and use alias `@aliasName` |
157
+ | Session Handling | Steps like `I login with a session data "admin.json"`, `I save session as "customer"` |
158
+ | Session Restore | `I restore session cookies "customer" [with reload] [using localStorage]` |
159
+ | API Assertions | Validate JSON response with `assert:json:key=expectedValue` |
160
+
161
+ ---
162
+
163
+ ### ✅ Extended Usage Examples
164
+
165
+ ```gherkin
166
+ Scenario: Login and save session
167
+ When I fill the following "Login" form data:
168
+ | input[name='email'] | test@example.com |
169
+ | input[name='password'] | @userPassword |
170
+ | input[type='checkbox'] | check |
171
+ | button:has-text("Sign In") | click |
172
+ | .welcome | assert:visible |
173
+ | .role | assert:text:Admin |
174
+ And I save session as "admin"
175
+
176
+ Scenario: Restore user session
177
+ Given I restore session cookies "admin" with reload using localStorage
178
+ When I visit "/dashboard"
179
+ Then I see text "Welcome back"
180
+
181
+ Scenario: API login + inject session
182
+ When I fill the following "Login" form data:
183
+ | request:POST:/api/login:adminPayload.json | saveAs:loginData |
184
+ | set:localStorage:token | @loginData.token |
185
+ | save session as | adminViaAPI |
186
+ ```
159
187
 
160
188
  ---
161
189
 
162
- ## 📁 Folder Structure Suggestion
190
+ ### Folder Structure Suggestion
163
191
 
164
192
  ```text
165
193
  e2e/
166
- ├── features/
194
+ ├── features/ # .feature files
167
195
  ├── step_definitions/
168
- │ └── index.ts # import from this package
196
+ │ └── index.ts # import from this package
169
197
  ├── support/
170
- └── world.ts # extends CustomWorld
171
- └── hooks.ts # optional
198
+ ├── world.ts # CustomWorld extends with iframe support
199
+ ├── hooks.ts # artifact & session manager
200
+ │ └── helpers/
201
+ │ └── resolveUtils.ts # alias/env/json resolver
202
+ ├── test-data/ # JSON fixtures
203
+ ├── payload/ # API request payloads
172
204
  ```
173
205
 
174
206
  ---
175
207
 
176
- ## 🧪 Example Project
208
+ Here’s a full `cucumber.js` config file that includes **all the configurable options** integrated so far. This supports:
177
209
 
178
- Want to see it in action?
210
+ - CLI/env override for artifact directories
211
+ - ✅ Visual testing toggle
212
+ - ✅ Screenshot and video toggle
213
+ - ✅ Device emulation via `MOBILE_DEVICE` env or world param
214
+ - ✅ Multiple profiles (`default`, `mobile`, `visual`, `ci`)
215
+ - ✅ Parallel test execution and ts-node for TypeScript support
179
216
 
180
- 👉 [https://github.com/qaPaschalE/playwright-cucumber-ts-steps](https://github.com/qaPaschalE/playwright-cucumber-ts-steps)
217
+ ---
218
+
219
+ ### ✅ `cucumber.js`
220
+
221
+ ```js
222
+ import path from "path";
223
+ import dotenv from "dotenv";
224
+
225
+ dotenv.config();
226
+
227
+ const ARTIFACT_DIR = process.env.TEST_ARTIFACT_DIR || "test-artifacts";
228
+
229
+ const defaultWorldParams = {
230
+ artifactDir: ARTIFACT_DIR,
231
+ payloadDir: "payloads",
232
+ enableScreenshots: process.env.ENABLE_SCREENSHOTS !== "false",
233
+ enableVideos: process.env.ENABLE_VIDEOS !== "false",
234
+ enableVisualTest: process.env.ENABLE_VISUAL_TEST === "true",
235
+ device: process.env.MOBILE_DEVICE || undefined, // e.g., "Pixel 5"
236
+ };
237
+
238
+ export default {
239
+ default: {
240
+ require: ["ts-node/register", "src/test/steps/**/*.ts", "src/test/support/**/*.ts"],
241
+ format: ["progress", `html:${path.join(ARTIFACT_DIR, "report.html")}`],
242
+ publishQuiet: true,
243
+ paths: ["src/test/features/**/*.feature"],
244
+ parallel: 2,
245
+ worldParameters: defaultWorldParams,
246
+ },
247
+
248
+ mobile: {
249
+ require: ["ts-node/register", "src/test/steps/**/*.ts", "src/test/support/**/*.ts"],
250
+ format: ["progress"],
251
+ publishQuiet: true,
252
+ paths: ["src/test/features/**/*.feature"],
253
+ parallel: 1,
254
+ tags: "@mobile",
255
+ worldParameters: {
256
+ ...defaultWorldParams,
257
+ device: process.env.MOBILE_DEVICE || "iPhone 13 Pro",
258
+ },
259
+ },
260
+
261
+ visual: {
262
+ require: ["ts-node/register", "src/test/steps/**/*.ts", "src/test/support/**/*.ts"],
263
+ format: ["progress"],
264
+ publishQuiet: true,
265
+ paths: ["src/test/features/**/*.feature"],
266
+ tags: "@visual",
267
+ worldParameters: {
268
+ ...defaultWorldParams,
269
+ enableVisualTest: true,
270
+ },
271
+ },
272
+
273
+ ci: {
274
+ require: ["ts-node/register", "src/test/steps/**/*.ts", "src/test/support/**/*.ts"],
275
+ format: ["progress", `json:${path.join(ARTIFACT_DIR, "report.json")}`],
276
+ publishQuiet: true,
277
+ paths: ["src/test/features/**/*.feature"],
278
+ parallel: 4,
279
+ worldParameters: {
280
+ ...defaultWorldParams,
281
+ enableScreenshots: true,
282
+ enableVideos: true,
283
+ enableVisualTest: false,
284
+ },
285
+ },
286
+ };
287
+ ```
181
288
 
182
289
  ---
183
290
 
291
+ ### ✅ Usage Examples
292
+
293
+ ```bash
294
+ # Run default suite
295
+ npx cucumber-js --config cucumber.js
296
+
297
+ # Run mobile tests with device from env
298
+ MOBILE_DEVICE="Pixel 5" npx cucumber-js --config cucumber.js --profile mobile
299
+
300
+ # Run visual regression tests
301
+ ENABLE_VISUAL_TEST=true npx cucumber-js --config cucumber.js --profile visual
302
+
303
+ # Run in CI profile with JSON output
304
+ npx cucumber-js --config cucumber.js --profile ci
305
+ ```
306
+
307
+ ---
308
+
309
+ ### ✅ Summary of Supported Options
310
+
311
+ | Setting | CLI/Env Variable | `worldParameters` Key | Purpose |
312
+ | ------------------ | -------------------- | --------------------- | ------------------------------------------- |
313
+ | Screenshot toggle | `ENABLE_SCREENSHOTS` | `enableScreenshots` | Capture screenshots on failure |
314
+ | Video toggle | `ENABLE_VIDEOS` | `enableVideos` | Enable/disable video recording |
315
+ | Visual testing | `ENABLE_VISUAL_TEST` | `enableVisualTest` | Capture and compare visual snapshots |
316
+ | Artifact directory | `TEST_ARTIFACT_DIR` | `artifactDir` | Where to save screenshots, videos, etc. |
317
+ | Mobile device emu | `MOBILE_DEVICE` | `device` | Device name for Playwright mobile emulation |
318
+
184
319
  ## 🧾 License
185
320
 
186
- [ISC](LICENSE)
321
+ [MIT](LICENSE)
187
322
 
188
323
  ---
189
324
 
@@ -11,6 +11,14 @@ const optionsUtils_1 = require("../helpers/utils/optionsUtils");
11
11
  await this.element.click(options);
12
12
  this.log?.("🖱️ Clicked on stored element");
13
13
  });
14
+ (0, cucumber_1.When)("I click on element {string}", async function (selector, ...rest) {
15
+ const maybeTable = rest[0];
16
+ const options = maybeTable?.rowsHash ? (0, optionsUtils_1.parseClickOptions)(maybeTable) : {};
17
+ const element = this.getLocator(selector);
18
+ await element.click(options);
19
+ this.element = element;
20
+ this.log?.(`🖱️ Clicked on element "${selector}"`);
21
+ });
14
22
  (0, cucumber_1.When)("I click on button {string}", async function (label, ...rest) {
15
23
  const maybeTable = rest[0];
16
24
  const options = maybeTable?.rowsHash ? (0, optionsUtils_1.parseClickOptions)(maybeTable) : {};
@@ -151,8 +159,7 @@ const optionsUtils_1 = require("../helpers/utils/optionsUtils");
151
159
  this.log?.(`✅ Clicked all ${count} stored elements.`);
152
160
  });
153
161
  (0, cucumber_1.When)(/^I click on selector "([^"]+)"$/, async function (selector) {
154
- const scope = this.getScope();
155
- const element = scope.locator(selector);
156
- await element.click();
162
+ const locator = this.getLocator(selector);
163
+ await locator.click();
157
164
  this.log?.(`🖱️ Clicked on selector: ${selector}`);
158
165
  });
@@ -2,10 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  // e2e/step_definitions/common/actions/debugSteps.ts
4
4
  const cucumber_1 = require("@cucumber/cucumber");
5
- (0, cucumber_1.When)("I debug", async function () {
5
+ (0, cucumber_1.When)("I debug with message {string}", async function (message) {
6
6
  await this.page.pause();
7
- this.log("Paused test for debugging");
8
- });
9
- (0, cucumber_1.When)("I log {string}", async function (msg) {
10
- this.log(msg);
7
+ this.log(`Paused test for debugging: ${message}`);
11
8
  });
@@ -9,6 +9,15 @@ const test_1 = require("@playwright/test");
9
9
  this.element = this.page.locator(selector);
10
10
  await (0, test_1.expect)(this.element).toHaveCount(1);
11
11
  });
12
+ (0, cucumber_1.When)("I find link by text {string}", async function (text) {
13
+ this.currentLocator = this.getScope().getByRole("link", { name: text });
14
+ });
15
+ (0, cucumber_1.When)("I find heading by text {string}", async function (text) {
16
+ this.currentLocator = this.getScope().getByRole("heading", { name: text });
17
+ });
18
+ (0, cucumber_1.When)("I find headings by text {string}", async function (text) {
19
+ this.currentLocator = this.getScope().getByRole("heading", { name: text });
20
+ });
12
21
  (0, cucumber_1.When)("I find elements by selector {string}", async function (selector) {
13
22
  this.elements = this.page.locator(selector);
14
23
  const count = await this.elements.count();
@@ -38,6 +47,9 @@ const test_1 = require("@playwright/test");
38
47
  this.element = this.page.getByLabel(label);
39
48
  await (0, test_1.expect)(this.element).toHaveCount(1);
40
49
  });
50
+ (0, cucumber_1.When)("I find elements by label text {string}", async function (label) {
51
+ this.currentLocator = this.getScope().getByLabel(label);
52
+ });
41
53
  (0, cucumber_1.When)("I find element by alt text {string}", async function (alt) {
42
54
  this.element = this.page.getByAltText(alt);
43
55
  await (0, test_1.expect)(this.element).toHaveCount(1);
@@ -46,6 +58,9 @@ const test_1 = require("@playwright/test");
46
58
  this.element = this.page.getByRole("textbox", { name });
47
59
  await (0, test_1.expect)(this.element).toHaveCount(1);
48
60
  });
61
+ (0, cucumber_1.When)("I find elements by name {string}", async function (name) {
62
+ this.currentLocator = this.getScope().locator(`[name="${name}"]`);
63
+ });
49
64
  (0, cucumber_1.When)("I find buttons by text {string}", async function (buttonText) {
50
65
  // 🧠 Resolve alias
51
66
  if (buttonText.startsWith("@")) {
@@ -100,24 +115,6 @@ const test_1 = require("@playwright/test");
100
115
  this.elements = locator;
101
116
  this.log?.(`Stored ${count} elements with role "${role}"`);
102
117
  });
103
- // When(
104
- // "I get {int}{ordinal} element",
105
- // async function (this: CustomWorld, index: number) {
106
- // if (!this.elements) {
107
- // throw new Error(
108
- // "No element collection found. Did you call 'find elements by' first?"
109
- // );
110
- // }
111
- // const total = await this.elements.count();
112
- // if (index < 1 || index > total) {
113
- // throw new Error(
114
- // `Index ${index} out of bounds. Found only ${total} elements.`
115
- // );
116
- // }
117
- // this.element = this.elements.nth(index - 1);
118
- // this.log?.(`Stored ${index}th element from collection`);
119
- // }
120
- // );
121
118
  (0, cucumber_1.When)("I get {int}rd element", async function (index) {
122
119
  if (!this.elements)
123
120
  throw new Error("No element collection found");
@@ -0,0 +1,130 @@
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
+ const fs_1 = __importDefault(require("fs"));
7
+ const path_1 = __importDefault(require("path"));
8
+ const cucumber_1 = require("@cucumber/cucumber");
9
+ const test_1 = require("@playwright/test");
10
+ const resolveUtils_1 = require("../helpers/utils/resolveUtils");
11
+ (0, cucumber_1.When)("I fill the following {string} form data:", async function (_formName, dataTable) {
12
+ // const scope = this.frame ?? this.page;
13
+ const rows = dataTable.hashes();
14
+ for (const row of rows) {
15
+ const target = row.Target.trim();
16
+ const rawValue = row.Value.trim();
17
+ const locator = this.getLocator(target);
18
+ const value = (0, resolveUtils_1.resolveLoginValue)(rawValue, this);
19
+ // ✅ Assertions
20
+ if (rawValue.startsWith("assert:")) {
21
+ const [, type, expected] = rawValue.split(":");
22
+ if (type === "visible") {
23
+ await (0, test_1.expect)(locator).toBeVisible();
24
+ }
25
+ else if (type === "text") {
26
+ await (0, test_1.expect)(locator).toHaveText(expected ?? "", {
27
+ useInnerText: true,
28
+ });
29
+ }
30
+ else if (type === "value") {
31
+ await (0, test_1.expect)(locator).toHaveValue(expected ?? "");
32
+ }
33
+ else {
34
+ throw new Error(`❌ Unknown assertion: ${type}`);
35
+ }
36
+ continue;
37
+ }
38
+ // ✅ UI interactions
39
+ if (rawValue === "click") {
40
+ await locator.click();
41
+ continue;
42
+ }
43
+ if (rawValue === "check") {
44
+ await locator.check();
45
+ continue;
46
+ }
47
+ if (rawValue === "uncheck") {
48
+ await locator.uncheck();
49
+ continue;
50
+ }
51
+ if (rawValue === "select") {
52
+ await locator.selectOption({ index: 0 });
53
+ continue;
54
+ }
55
+ // ✅ File upload
56
+ if (rawValue.startsWith("upload:")) {
57
+ const filePath = rawValue.split("upload:")[1].trim();
58
+ const resolvedPath = path_1.default.resolve(filePath);
59
+ if (!fs_1.default.existsSync(resolvedPath))
60
+ throw new Error(`File not found: ${filePath}`);
61
+ await locator.setInputFiles(resolvedPath);
62
+ continue;
63
+ }
64
+ // ✅ Drag and drop
65
+ if (rawValue.startsWith("drag:")) {
66
+ const targetSelector = rawValue.split("drag:")[1].trim();
67
+ const targetLocator = this.getLocator(targetSelector);
68
+ await locator.dragTo(targetLocator);
69
+ continue;
70
+ }
71
+ // ✅ Local/sessionStorage
72
+ if (rawValue.startsWith("set:localStorage:")) {
73
+ const [, , key] = rawValue.split(":");
74
+ if (typeof key !== "string" || !key) {
75
+ throw new Error("Local storage key must be a non-empty string");
76
+ }
77
+ await this.page.evaluate(([k, v]) => localStorage.setItem(k, v), [key, value ?? ""]);
78
+ continue;
79
+ }
80
+ if (rawValue.startsWith("set:sessionStorage:")) {
81
+ const [, , key] = rawValue.split(":");
82
+ if (typeof key !== "string" || key === undefined) {
83
+ throw new Error("Session storage key must be a string");
84
+ }
85
+ await this.page.evaluate((args) => {
86
+ const [k, v] = args;
87
+ sessionStorage.setItem(k, v);
88
+ }, [key, value ?? ""]);
89
+ continue;
90
+ }
91
+ // ✅ Wait
92
+ if (rawValue.startsWith("wait:")) {
93
+ const [, timeMs] = rawValue.split(":");
94
+ const waitTime = Number(timeMs);
95
+ if (!isNaN(waitTime)) {
96
+ await this.page.waitForTimeout(waitTime);
97
+ }
98
+ continue;
99
+ }
100
+ // ✅ Reload
101
+ if (rawValue === "reload") {
102
+ await this.page.reload();
103
+ continue;
104
+ }
105
+ // ✅ Request handling
106
+ if (rawValue.startsWith("request:")) {
107
+ const [, method, url, file] = rawValue.replace("request:", "").split(":");
108
+ const payloadDir = row.PayloadDir || this.parameters?.payloadDir || "payload";
109
+ const filePath = path_1.default.resolve(payloadDir, file);
110
+ if (!fs_1.default.existsSync(filePath)) {
111
+ throw new Error(`Payload file not found: ${filePath}`);
112
+ }
113
+ const payload = JSON.parse(fs_1.default.readFileSync(filePath, "utf-8"));
114
+ const response = await this.page.request[method.toLowerCase()](url, {
115
+ data: payload,
116
+ });
117
+ const responseBody = await response.json();
118
+ this.data.lastApiResponse = responseBody;
119
+ this.data.lastStatusCode = response.status();
120
+ if (row.SaveAs) {
121
+ this.data[row.SaveAs] = responseBody;
122
+ }
123
+ continue;
124
+ }
125
+ // ✅ Default: fill
126
+ if (value !== undefined) {
127
+ await locator.fill(String(value));
128
+ }
129
+ }
130
+ });
@@ -1,13 +1,9 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  // e2e/step_definitions/common/actions/inputSteps.ts
4
+ // import fs from "fs";
5
+ // import path from "path";
7
6
  const cucumber_1 = require("@cucumber/cucumber");
8
- const test_1 = require("@playwright/test");
9
- const path_1 = __importDefault(require("path"));
10
- const fs_1 = __importDefault(require("fs"));
11
7
  const fakerUtils_1 = require("../helpers/utils/fakerUtils");
12
8
  const optionsUtils_1 = require("../helpers/utils/optionsUtils");
13
9
  (0, cucumber_1.When)("I check", async function (...rest) {
@@ -36,80 +32,17 @@ const optionsUtils_1 = require("../helpers/utils/optionsUtils");
36
32
  throw new Error("No input selected");
37
33
  await this.element.uncheck(options);
38
34
  });
39
- (0, cucumber_1.When)("I fill the following {string} form data:", async function (_formName, tableData, ...rest) {
40
- const maybeOptionsTable = rest[0];
41
- const globalOptions = maybeOptionsTable?.rowsHash
42
- ? (0, optionsUtils_1.parseClickOptions)(maybeOptionsTable)
43
- : {};
44
- const rows = tableData.raw().slice(1); // Remove header row
45
- for (const [rawTarget, rawValue] of rows) {
46
- let element;
47
- let resolvedValue;
48
- if (rawTarget.startsWith("@")) {
49
- element = this.data[rawTarget.slice(1)];
50
- if (!element)
51
- throw new Error(`No element found for alias: ${rawTarget}`);
52
- }
53
- else if (await this.page.locator(rawTarget).count()) {
54
- element = this.page.locator(rawTarget);
55
- }
56
- else {
57
- element = this.page.getByText(rawTarget, { exact: true }).first();
58
- }
59
- if (!element)
60
- throw new Error(`Unable to resolve element for target: ${rawTarget}`);
61
- await element.waitFor({ state: "visible", timeout: 7000 });
62
- if (rawValue.startsWith("@")) {
63
- resolvedValue = this.data[rawValue.slice(1)];
64
- if (!resolvedValue)
65
- throw new Error(`No value found for alias: ${rawValue}`);
66
- }
67
- else {
68
- resolvedValue = (0, fakerUtils_1.evaluateFaker)(rawValue);
69
- }
70
- if (resolvedValue === "Click") {
71
- await element.click(globalOptions);
72
- this.log?.(`🖱️ Clicked on "${rawTarget}"`);
73
- }
74
- else if (rawTarget.includes('input[type="file"]')) {
75
- const filePath = path_1.default.resolve("test-data", resolvedValue);
76
- if (!fs_1.default.existsSync(filePath))
77
- throw new Error(`File not found: ${filePath}`);
78
- await element.setInputFiles(filePath);
79
- this.log?.(`📁 Uploaded file to "${rawTarget}": ${resolvedValue}`);
80
- }
81
- else {
82
- const keyPattern = /{(\w+)}/g;
83
- const matches = [...resolvedValue.matchAll(keyPattern)];
84
- const keys = matches.map((m) => m[1]);
85
- const inputText = resolvedValue.replace(keyPattern, "").trim();
86
- const fillOptions = maybeOptionsTable?.rowsHash
87
- ? (0, optionsUtils_1.parseFillOptions)(maybeOptionsTable)
88
- : {};
89
- if (inputText) {
90
- await element.fill(inputText, fillOptions);
91
- this.log?.(`⌨️ Filled "${rawTarget}" with: ${inputText}`);
92
- }
93
- for (const key of keys) {
94
- await element.focus();
95
- await this.page.waitForTimeout(200);
96
- await element.press(key);
97
- this.log?.(`🎹 Pressed {${key}} on "${rawTarget}"`);
98
- }
99
- }
100
- await (0, test_1.expect)(element).toBeVisible();
101
- }
102
- });
35
+ // const DEFAULT_PAYLOAD_DIR = "payload";
103
36
  const typeStep = async function (textOrAlias, ...rest) {
104
37
  if (!this.element)
105
38
  throw new Error("No element selected");
106
39
  const maybeTable = rest[0];
107
40
  const options = maybeTable?.rowsHash ? (0, optionsUtils_1.parseFillOptions)(maybeTable) : {};
108
41
  const text = textOrAlias.startsWith("@")
109
- ? this.data[textOrAlias.slice(1)] ??
42
+ ? (this.data[textOrAlias.slice(1)] ??
110
43
  (() => {
111
44
  throw new Error(`No value found for alias "${textOrAlias}"`);
112
- })()
45
+ })())
113
46
  : (0, fakerUtils_1.evaluateFaker)(textOrAlias);
114
47
  await this.element.fill("");
115
48
  await this.element.fill(text, options);
@@ -125,10 +58,10 @@ const typeStep = async function (textOrAlias, ...rest) {
125
58
  const maybeTable = rest[0];
126
59
  const options = maybeTable?.rowsHash ? (0, optionsUtils_1.parseFillOptions)(maybeTable) : {};
127
60
  const value = valueOrAlias.startsWith("@")
128
- ? this.data[valueOrAlias.slice(1)] ??
61
+ ? (this.data[valueOrAlias.slice(1)] ??
129
62
  (() => {
130
63
  throw new Error(`No value found for alias "${valueOrAlias}"`);
131
- })()
64
+ })())
132
65
  : (0, fakerUtils_1.evaluateFaker)(valueOrAlias);
133
66
  await this.element.fill(value, options);
134
67
  this.data.lastValueSet = value;
@@ -140,8 +73,8 @@ const typeStep = async function (textOrAlias, ...rest) {
140
73
  await this.element.fill("");
141
74
  this.log?.("🧼 Cleared value of selected element");
142
75
  });
143
- (0, cucumber_1.When)("I submit", async function (...rest) {
144
- const maybeTable = rest[0];
76
+ (0, cucumber_1.When)("I submit", async function () {
77
+ // const maybeTable = rest[0];
145
78
  const form = this.element ?? this.page.locator("form");
146
79
  await form.evaluate((f) => f.submit());
147
80
  this.log?.("📨 Submitted form");
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  // e2e/step_definitions/common/interceptionSteps.ts
4
4
  const cucumber_1 = require("@cucumber/cucumber");
5
+ let lastResponse;
5
6
  (0, cucumber_1.When)("I intercept URL {string} and stub body:", async function (url, body) {
6
7
  let parsedBody;
7
8
  try {
@@ -61,3 +62,10 @@ const cucumber_1 = require("@cucumber/cucumber");
61
62
  const response = await this.page.request.get(url);
62
63
  this.data.lastResponse = response;
63
64
  });
65
+ (0, cucumber_1.When)('I make a "{word}" request to {string}', async function (method, url, table) {
66
+ const options = table ? Object.fromEntries(table.rows()) : {};
67
+ if (options.body)
68
+ options.body = JSON.stringify(JSON.parse(options.body));
69
+ const res = await fetch(url, { method, ...options });
70
+ lastResponse = res;
71
+ });