playwright-cucumber-ts-steps 0.1.5 → 0.1.7

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 (35) hide show
  1. package/package.json +6 -6
  2. package/src/actions/clickSteps.ts +207 -0
  3. package/src/actions/cookieSteps.ts +29 -0
  4. package/src/actions/debugSteps.ts +7 -0
  5. package/src/actions/elementFindSteps.ts +256 -0
  6. package/src/actions/fillFormSteps.ts +213 -0
  7. package/src/actions/inputSteps.ts +118 -0
  8. package/src/actions/interceptionSteps.ts +87 -0
  9. package/src/actions/miscSteps.ts +414 -0
  10. package/src/actions/mouseSteps.ts +99 -0
  11. package/src/actions/scrollSteps.ts +24 -0
  12. package/src/actions/storageSteps.ts +83 -0
  13. package/src/assertions/buttonAndTextVisibilitySteps.ts +178 -0
  14. package/src/assertions/cookieSteps.ts +52 -0
  15. package/src/assertions/elementSteps.ts +103 -0
  16. package/src/assertions/formInputSteps.ts +110 -0
  17. package/src/assertions/interceptionRequestsSteps.ts +216 -0
  18. package/src/assertions/locationSteps.ts +99 -0
  19. package/src/assertions/roleTestIdSteps.ts +36 -0
  20. package/src/assertions/semanticSteps.ts +79 -0
  21. package/src/assertions/storageSteps.ts +89 -0
  22. package/src/assertions/visualSteps.ts +98 -0
  23. package/src/custom_setups/loginHooks.ts +135 -0
  24. package/src/helpers/checkPeerDeps.ts +19 -0
  25. package/src/helpers/compareSnapshots.ts +35 -0
  26. package/src/helpers/hooks.ts +212 -0
  27. package/src/helpers/utils/fakerUtils.ts +64 -0
  28. package/src/helpers/utils/index.ts +4 -0
  29. package/src/helpers/utils/optionsUtils.ts +104 -0
  30. package/src/helpers/utils/resolveUtils.ts +74 -0
  31. package/src/helpers/utils/sessionUtils.ts +36 -0
  32. package/src/helpers/world.ts +93 -0
  33. package/src/iframes/frames.ts +15 -0
  34. package/src/index.ts +39 -0
  35. package/src/register.ts +4 -0
@@ -0,0 +1,213 @@
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";
6
+ import type { CustomWorld } from "../helpers/world";
7
+
8
+ /*
9
+ * This file contains the step definitions for filling out forms in Playwright.
10
+ * It supports various actions like filling inputs, clicking buttons, checking checkboxes,
11
+ * uploading files, and handling requests.
12
+ * Feature: User login with hybrid form and API session
13
+
14
+ Background:
15
+ Given I open the application homepage
16
+
17
+ Scenario: Login as Admin via UI and save session
18
+ When I fill the following "Login" form data:
19
+ | Target | Value |
20
+ | input[placeholder='email'] | test@email.com |
21
+ | input[placeholder='password'] | @adminPassword |
22
+ | input[type='checkbox'] | check |
23
+ | select[name='role'] | select |
24
+ | button:has-text("Sign In") | click |
25
+ | .dashboard-header | assert:visible |
26
+ | .user-role | assert:text:Admin |
27
+ Then I save session as "sessionAdmin.json"
28
+
29
+ Scenario: Restore session and assert dashboard
30
+ Given I restore session cookies "sessionAdmin.json" with reload
31
+ Then .user-role should contain text "Admin"
32
+
33
+ Scenario: Login via API request and inject session
34
+ When I fill the following "Login" form data:
35
+ | Target | Value |
36
+ | request:POST:/api/auth/login:adminLogin.json | |
37
+ | set:localStorage:auth_token | @lastApiResponse.token |
38
+ | wait | wait:1000 |
39
+ | reload | |
40
+ | .dashboard-header | assert:visible |
41
+ Then I save session as "apiSessionAdmin.json"
42
+
43
+ Scenario: Upload, drag, and assert form
44
+ When I fill the following "Profile" form data:
45
+ | Target | Value |
46
+ | input[type='file'] | upload:fixtures/profile-pic.jpg |
47
+ | div.upload-target | drag:.upload-preview |
48
+ | .upload-success | assert:visible |
49
+
50
+ Scenario: Login as reviewer and save session for approval flows
51
+ When I fill the following "Login" form data:
52
+ | Target | Value |
53
+ | input[placeholder='email'] | reviewer@email.com |
54
+ | input[placeholder='password'] | REVIEWER_PASS |
55
+ | button:has-text("Login") | click |
56
+ | .user-role | assert:text:Reviewer |
57
+ Then I save session as "sessionReviewer.json"
58
+
59
+ */
60
+ // Explicitly type each row
61
+ type ActionRow = {
62
+ Target: string;
63
+ Value: string;
64
+ PayloadDir?: string;
65
+ SaveAs?: string;
66
+ };
67
+
68
+ When(
69
+ "I fill the following {string} form data:",
70
+ async function (this: CustomWorld, _formName: string, dataTable) {
71
+ // const scope = this.frame ?? this.page;
72
+ const rows = dataTable.hashes() as ActionRow[];
73
+
74
+ for (const row of rows) {
75
+ const target = row.Target.trim();
76
+ const rawValue = row.Value.trim();
77
+ const locator = this.getLocator(target);
78
+ const value = resolveLoginValue(rawValue, this);
79
+
80
+ // ✅ Assertions
81
+ if (rawValue.startsWith("assert:")) {
82
+ const [, type, expected] = rawValue.split(":");
83
+
84
+ if (type === "visible") {
85
+ await expect(locator).toBeVisible();
86
+ } else if (type === "text") {
87
+ await expect(locator).toHaveText(expected ?? "", {
88
+ useInnerText: true,
89
+ });
90
+ } else if (type === "value") {
91
+ await expect(locator).toHaveValue(expected ?? "");
92
+ } else {
93
+ throw new Error(`❌ Unknown assertion: ${type}`);
94
+ }
95
+
96
+ continue;
97
+ }
98
+
99
+ // ✅ UI interactions
100
+ if (rawValue === "click") {
101
+ await locator.click();
102
+ continue;
103
+ }
104
+
105
+ if (rawValue === "check") {
106
+ await locator.check();
107
+ continue;
108
+ }
109
+
110
+ if (rawValue === "uncheck") {
111
+ await locator.uncheck();
112
+ continue;
113
+ }
114
+
115
+ if (rawValue === "select") {
116
+ await locator.selectOption({ index: 0 });
117
+ continue;
118
+ }
119
+
120
+ // ✅ File upload
121
+ if (rawValue.startsWith("upload:")) {
122
+ const filePath = rawValue.split("upload:")[1].trim();
123
+ const resolvedPath = path.resolve(filePath);
124
+ if (!fs.existsSync(resolvedPath)) throw new Error(`File not found: ${filePath}`);
125
+ await locator.setInputFiles(resolvedPath);
126
+ continue;
127
+ }
128
+
129
+ // ✅ Drag and drop
130
+ if (rawValue.startsWith("drag:")) {
131
+ const targetSelector = rawValue.split("drag:")[1].trim();
132
+ const targetLocator = this.getLocator(targetSelector);
133
+ await locator.dragTo(targetLocator);
134
+ continue;
135
+ }
136
+
137
+ // ✅ Local/sessionStorage
138
+ if (rawValue.startsWith("set:localStorage:")) {
139
+ const [, , key] = rawValue.split(":");
140
+ if (typeof key !== "string" || !key) {
141
+ throw new Error("Local storage key must be a non-empty string");
142
+ }
143
+ await this.page.evaluate(([k, v]) => localStorage.setItem(k, v), [key, value ?? ""]);
144
+ continue;
145
+ }
146
+
147
+ if (rawValue.startsWith("set:sessionStorage:")) {
148
+ const [, , key] = rawValue.split(":");
149
+ if (typeof key !== "string" || key === undefined) {
150
+ throw new Error("Session storage key must be a string");
151
+ }
152
+ await this.page.evaluate(
153
+ (args: string[]) => {
154
+ const [k, v] = args;
155
+ sessionStorage.setItem(k, v);
156
+ },
157
+ [key, value ?? ""]
158
+ );
159
+ continue;
160
+ }
161
+
162
+ // ✅ Wait
163
+ if (rawValue.startsWith("wait:")) {
164
+ const [, timeMs] = rawValue.split(":");
165
+ const waitTime = Number(timeMs);
166
+ if (!isNaN(waitTime)) {
167
+ await this.page.waitForTimeout(waitTime);
168
+ }
169
+ continue;
170
+ }
171
+
172
+ // ✅ Reload
173
+ if (rawValue === "reload") {
174
+ await this.page.reload();
175
+ continue;
176
+ }
177
+
178
+ // ✅ Request handling
179
+ if (rawValue.startsWith("request:")) {
180
+ const [, method, url, file] = rawValue.replace("request:", "").split(":");
181
+
182
+ const payloadDir = row.PayloadDir || this.parameters?.payloadDir || "payload";
183
+ const filePath = path.resolve(payloadDir, file);
184
+
185
+ if (!fs.existsSync(filePath)) {
186
+ throw new Error(`Payload file not found: ${filePath}`);
187
+ }
188
+
189
+ const payload = JSON.parse(fs.readFileSync(filePath, "utf-8"));
190
+ const response = await this.page.request[
191
+ method.toLowerCase() as "post" | "put" | "patch" | "get"
192
+ ](url, {
193
+ data: payload,
194
+ });
195
+
196
+ const responseBody = await response.json();
197
+ this.data.lastApiResponse = responseBody;
198
+ this.data.lastStatusCode = response.status();
199
+
200
+ if (row.SaveAs) {
201
+ this.data[row.SaveAs] = responseBody;
202
+ }
203
+
204
+ continue;
205
+ }
206
+
207
+ // ✅ Default: fill
208
+ if (value !== undefined) {
209
+ await locator.fill(String(value));
210
+ }
211
+ }
212
+ }
213
+ );
@@ -0,0 +1,118 @@
1
+ // e2e/step_definitions/common/actions/inputSteps.ts
2
+ // import fs from "fs";
3
+ // import path from "path";
4
+ import { When } from "@cucumber/cucumber";
5
+ import { evaluateFaker } from "../helpers/utils/fakerUtils";
6
+ import {
7
+ parseCheckOptions,
8
+ parseFillOptions,
9
+ parseSelectOptions,
10
+ } from "../helpers/utils/optionsUtils";
11
+ import { CustomWorld } from "../helpers/world";
12
+
13
+ When("I check", async function (this: CustomWorld, ...rest: any[]) {
14
+ const maybeTable = rest[0];
15
+ const options = maybeTable?.rowsHash ? parseCheckOptions(maybeTable) : {};
16
+ await this.element?.check(options);
17
+ this.log?.("✅ Checked stored checkbox");
18
+ });
19
+
20
+ When("I uncheck", async function (this: CustomWorld, ...rest: any[]) {
21
+ const maybeTable = rest[0];
22
+ const options = maybeTable?.rowsHash ? parseCheckOptions(maybeTable) : {};
23
+ await this.element?.uncheck(options);
24
+ this.log?.("✅ Unchecked stored checkbox");
25
+ });
26
+
27
+ When("I check input", async function (this: CustomWorld, ...rest: any[]) {
28
+ const maybeTable = rest[0];
29
+ const options = maybeTable?.rowsHash ? parseCheckOptions(maybeTable) : {};
30
+ if (!this.element) throw new Error("No input selected");
31
+ await this.element.check(options);
32
+ });
33
+
34
+ When("I uncheck input", async function (this: CustomWorld, ...rest: any[]) {
35
+ const maybeTable = rest[0];
36
+ const options = maybeTable?.rowsHash ? parseCheckOptions(maybeTable) : {};
37
+ if (!this.element) throw new Error("No input selected");
38
+ await this.element.uncheck(options);
39
+ });
40
+
41
+ // const DEFAULT_PAYLOAD_DIR = "payload";
42
+
43
+ const typeStep = async function (this: CustomWorld, textOrAlias: string, ...rest: any[]) {
44
+ if (!this.element) throw new Error("No element selected");
45
+
46
+ const maybeTable = rest[0];
47
+ const options = maybeTable?.rowsHash ? parseFillOptions(maybeTable) : {};
48
+ const text = textOrAlias.startsWith("@")
49
+ ? (this.data[textOrAlias.slice(1)] ??
50
+ (() => {
51
+ throw new Error(`No value found for alias "${textOrAlias}"`);
52
+ })())
53
+ : evaluateFaker(textOrAlias);
54
+
55
+ await this.element.fill("");
56
+ await this.element.fill(text, options);
57
+ this.data.lastTyped = text;
58
+ this.log?.(`⌨️ Typed "${text}" into selected element`);
59
+ };
60
+
61
+ When("I type {string}", typeStep);
62
+ When("I type stored {string}", typeStep);
63
+ When("I type random {string}", typeStep);
64
+
65
+ When(
66
+ "I set value {string}",
67
+ async function (this: CustomWorld, valueOrAlias: string, ...rest: any[]) {
68
+ if (!this.element) throw new Error("No element selected");
69
+
70
+ const maybeTable = rest[0];
71
+ const options = maybeTable?.rowsHash ? parseFillOptions(maybeTable) : {};
72
+ const value = valueOrAlias.startsWith("@")
73
+ ? (this.data[valueOrAlias.slice(1)] ??
74
+ (() => {
75
+ throw new Error(`No value found for alias "${valueOrAlias}"`);
76
+ })())
77
+ : evaluateFaker(valueOrAlias);
78
+
79
+ await this.element.fill(value, options);
80
+ this.data.lastValueSet = value;
81
+ this.log?.(`📝 Set value to "${value}"`);
82
+ }
83
+ );
84
+
85
+ When("I clear", async function (this: CustomWorld) {
86
+ if (!this.element) throw new Error("No element selected");
87
+ await this.element.fill("");
88
+ this.log?.("🧼 Cleared value of selected element");
89
+ });
90
+
91
+ When("I submit", async function (this: CustomWorld) {
92
+ // const maybeTable = rest[0];
93
+ const form = this.element ?? this.page.locator("form");
94
+ await form.evaluate((f: HTMLFormElement) => f.submit());
95
+ this.log?.("📨 Submitted form");
96
+ });
97
+
98
+ When(
99
+ "I select option {string}",
100
+ async function (this: CustomWorld, option: string, ...rest: any[]) {
101
+ if (!this.element) throw new Error("No select element stored");
102
+ const maybeTable = rest[0];
103
+ const options = maybeTable?.rowsHash ? parseSelectOptions(maybeTable) : {};
104
+ await this.element.selectOption({ label: option }, options);
105
+ this.log?.(`🔽 Selected option "${option}"`);
106
+ }
107
+ );
108
+
109
+ When(
110
+ "I select file {string}",
111
+ async function (this: CustomWorld, filePath: string, ...rest: any[]) {
112
+ if (!this.element) throw new Error("No file input selected");
113
+ const maybeTable = rest[0];
114
+ const options = maybeTable?.rowsHash ? parseSelectOptions(maybeTable) : {};
115
+ await this.element.setInputFiles(filePath, options);
116
+ this.log?.(`📁 Set input file to "${filePath}"`);
117
+ }
118
+ );
@@ -0,0 +1,87 @@
1
+ // e2e/step_definitions/common/interceptionSteps.ts
2
+ import { When, DataTable } from "@cucumber/cucumber";
3
+ // import { expect } from "@playwright/test";
4
+ import { CustomWorld } from "../helpers/world";
5
+ let lastResponse: any;
6
+
7
+ When("I intercept URL {string} and stub body:", async function (url: string, body: string) {
8
+ let parsedBody: any;
9
+ try {
10
+ parsedBody = JSON.parse(body);
11
+ } catch (e) {
12
+ const message = e instanceof Error ? e.message : String(e);
13
+ throw new Error(`Failed to parse JSON body: ${message}`);
14
+ }
15
+
16
+ await this.page.route(url, (route: import("playwright").Route) => {
17
+ route.fulfill({
18
+ status: 200,
19
+ contentType: "application/json",
20
+ body: JSON.stringify(parsedBody),
21
+ });
22
+ });
23
+
24
+ this.log(`Intercepted and stubbed URL "${url}" with body: ${JSON.stringify(parsedBody)}`);
25
+ });
26
+
27
+ //Making Direct API Requests (Optional, Advanced)
28
+ When("I make request to {string}", async function (url: string) {
29
+ const response = await this.page.request.get(url);
30
+ const status = response.status();
31
+ const body = await response.text();
32
+ this.data.lastResponse = { status, body };
33
+ this.log(`Made GET request to "${url}" — Status: ${status}`);
34
+ });
35
+
36
+ When(
37
+ "I make a POST request to {string} with JSON body:",
38
+ async function (url: string, docString: string) {
39
+ let payload: any;
40
+ try {
41
+ payload = JSON.parse(docString);
42
+ } catch (e) {
43
+ const message = e instanceof Error ? e.message : String(e);
44
+ throw new Error(`Invalid JSON: ${message}`);
45
+ }
46
+
47
+ const response = await this.page.request.post(url, { data: payload });
48
+ const status = response.status();
49
+ const body = await response.text();
50
+ this.data.lastResponse = { status, body };
51
+ this.log(`Made POST request to "${url}" — Status: ${status}`);
52
+ }
53
+ );
54
+
55
+ When("I intercept URL {string}", async function (this: CustomWorld, url: string) {
56
+ await this.page.route(url, async (route) => {
57
+ await route.continue();
58
+ });
59
+ });
60
+
61
+ When(
62
+ "I intercept URL {string} and stub body {string}",
63
+ async function (this: CustomWorld, url: string, body: string) {
64
+ await this.page.route(url, (route) => {
65
+ route.fulfill({
66
+ status: 200,
67
+ contentType: "application/json",
68
+ body,
69
+ });
70
+ });
71
+ }
72
+ );
73
+
74
+ When("I make a request to {string}", async function (this: CustomWorld, url: string) {
75
+ const response = await this.page.request.get(url);
76
+ this.data.lastResponse = response;
77
+ });
78
+
79
+ When(
80
+ 'I make a "{word}" request to {string}',
81
+ async function (method: string, url: string, table?: DataTable) {
82
+ const options = table ? Object.fromEntries(table.rows()) : {};
83
+ if (options.body) options.body = JSON.stringify(JSON.parse(options.body));
84
+ const res = await fetch(url, { method, ...options });
85
+ lastResponse = res;
86
+ }
87
+ );