playwright-cucumber-ts-steps 0.1.6 → 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 +1 -1
  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,178 @@
1
+ import { Then } from "@cucumber/cucumber";
2
+ import { expect } from "@playwright/test";
3
+ import { evaluateFaker } from "../helpers/utils/fakerUtils";
4
+ import type { CustomWorld } from "../helpers/world";
5
+
6
+ Then("I count {int} elements", async function (this: CustomWorld, expectedCount: number) {
7
+ if (!this.elements) throw new Error("No elements found to count");
8
+ const count = await this.elements.count();
9
+ await this.page.waitForLoadState("networkidle");
10
+
11
+ expect(count).toBe(expectedCount);
12
+ });
13
+ /**
14
+ * THEN: I see button "Submit"
15
+ */
16
+ Then(/^I see button "(.*)"$/, async function (this: CustomWorld, rawText: string) {
17
+ let buttonText = rawText.startsWith("@") ? this.data[rawText.slice(1)] : rawText;
18
+
19
+ if (!buttonText) {
20
+ throw new Error(`No value found for alias: "${rawText}"`);
21
+ }
22
+
23
+ const button = this.page.getByRole("button", {
24
+ name: buttonText,
25
+ exact: false,
26
+ });
27
+
28
+ await this.page.waitForLoadState("networkidle");
29
+ await expect(button).toBeVisible();
30
+ });
31
+
32
+ /**
33
+ * THEN: I do not see button "Cancel"
34
+ */
35
+ Then(/^I do not see button "(.*)"$/, async function (this: CustomWorld, rawText: string) {
36
+ let buttonText = rawText.startsWith("@") ? this.data[rawText.slice(1)] : rawText;
37
+
38
+ if (!buttonText) {
39
+ throw new Error(`No value found for alias: "${rawText}"`);
40
+ }
41
+
42
+ const button = this.page.getByRole("button", {
43
+ name: buttonText,
44
+ exact: false,
45
+ });
46
+
47
+ await this.page.waitForLoadState("networkidle");
48
+ const count = await button.count();
49
+ for (let i = 0; i < count; i++) {
50
+ const item = button.nth(i);
51
+ if (await item.isVisible()) {
52
+ throw new Error(`Button "${buttonText}" is visible but should not be`);
53
+ }
54
+ }
55
+ });
56
+
57
+ /**
58
+ * THEN: I see text "Welcome"
59
+ */
60
+
61
+ Then("I see text {string}", async function (this: CustomWorld, expected: string) {
62
+ const scope = this.getScope(); // ✅ Supports iframe OR main page
63
+ const locator = scope.locator(`text=${expected}`);
64
+ await locator.waitFor({ state: "visible", timeout: 5000 });
65
+ this.log?.(`✅ Verified text visible: ${expected}`);
66
+ });
67
+
68
+ /**
69
+ * THEN: I see visible text "Dashboard"
70
+ */
71
+
72
+ Then("I see visible text {string}", async function (this: CustomWorld, text: string) {
73
+ await this.page.waitForLoadState("networkidle");
74
+
75
+ const locator = this.page.locator(`:text-is("${text}")`);
76
+ if (!(await locator.first().isVisible())) {
77
+ throw new Error(`Visible text "${text}" not found`);
78
+ }
79
+ });
80
+
81
+ /**
82
+ * THEN: I do not see visible text "Logout"
83
+ */
84
+
85
+ Then("I do not see visible text {string}", async function (this: CustomWorld, text: string) {
86
+ const locator = this.page.locator(`:text-is("${text}")`);
87
+ if ((await locator.count()) > 0 && (await locator.first().isVisible())) {
88
+ throw new Error(`Visible text "${text}" should not be visible`);
89
+ }
90
+ });
91
+ Then("I see value {string}", async function (this: CustomWorld, expected: string) {
92
+ if (!this.element) throw new Error("No element selected");
93
+ const value = await this.element.inputValue();
94
+ if (value !== expected) {
95
+ throw new Error(`Expected value "${expected}", but got "${value}"`);
96
+ }
97
+ });
98
+ Then("I do not see value {string}", async function (this: CustomWorld, unwanted: string) {
99
+ if (!this.element) throw new Error("No element selected");
100
+ const value = await this.element.inputValue();
101
+ if (value === unwanted) {
102
+ throw new Error(`Value "${unwanted}" is present but should not be`);
103
+ }
104
+ });
105
+
106
+ //
107
+ // 📃 VISIBLE TEXT ASSERTIONS
108
+ //
109
+
110
+ Then(/^I do not see text "(.*)"$/, async function (this: any, unexpectedText: string) {
111
+ const el = this.page.getByText(unexpectedText, { exact: true });
112
+ await expect(el).toHaveCount(0);
113
+ });
114
+
115
+ //
116
+ // 🆕 Visible Text - Alias for clarity (optional if you want separate steps for naming)
117
+ //
118
+
119
+ Then("I see {string} in the element", async function (this: CustomWorld, expected: string) {
120
+ const element = this.element;
121
+ if (!element) throw new Error("No element selected");
122
+
123
+ // ✅ Resolve alias
124
+ if (expected.startsWith("@")) {
125
+ const alias = expected.substring(1);
126
+ expected = this.data[alias];
127
+ if (!expected) throw new Error(`No data stored for alias "@${alias}"`);
128
+ }
129
+
130
+ // ✅ Resolve faker syntax
131
+ expected = evaluateFaker(expected);
132
+
133
+ const textContent = await element.textContent();
134
+ if (!textContent) throw new Error("Element has no text content");
135
+
136
+ expect(textContent).toContain(expected);
137
+ this.log?.(`Verified "${expected}" in element text`);
138
+ });
139
+
140
+ Then("I see @{word} in the element", async function (this: CustomWorld, alias: string) {
141
+ const storedValue = this.data[alias];
142
+
143
+ if (!storedValue) {
144
+ throw new Error(`No value found in data storage under alias "@${alias}".`);
145
+ }
146
+
147
+ if (!this.element) {
148
+ throw new Error("No element found. You must get an element before asserting its contents.");
149
+ }
150
+
151
+ const actualText = (await this.element.textContent())?.trim() || "";
152
+ expect(actualText).toContain(storedValue);
153
+ this.log(
154
+ `Verified element contains value from "@${alias}" = "${storedValue}". Actual: "${actualText}"`
155
+ );
156
+ });
157
+ Then("I see button {string} is disabled", async function (this: CustomWorld, rawText: string) {
158
+ // Resolve alias
159
+ let buttonText = rawText.startsWith("@") ? this.data[rawText.slice(1)] : rawText;
160
+
161
+ if (!buttonText) {
162
+ throw new Error(`No value found for alias: "${rawText}"`);
163
+ }
164
+
165
+ const button = this.page.getByRole("button", {
166
+ name: buttonText,
167
+ exact: false,
168
+ });
169
+
170
+ await expect(button).toBeVisible({ timeout: 5000 });
171
+ const isDisabled = await button.isDisabled();
172
+
173
+ if (!isDisabled) {
174
+ throw new Error(`🚫 Button "${buttonText}" is not disabled as expected.`);
175
+ }
176
+
177
+ this.log?.(`✅ Verified button "${buttonText}" is disabled.`);
178
+ });
@@ -0,0 +1,52 @@
1
+ import { Then } from "@cucumber/cucumber";
2
+ import type { CustomWorld } from "../helpers/world";
3
+
4
+ /**
5
+ * THEN: I see cookie "myCookie"
6
+ */
7
+ Then("I see cookie {string}", async function (this: CustomWorld, cookieName: string) {
8
+ const cookies = await this.context.cookies();
9
+ const cookie = cookies.find((c) => c.name === cookieName);
10
+ if (!cookie) throw new Error(`Cookie "${cookieName}" not found`);
11
+ });
12
+
13
+ /**
14
+ * THEN: I do not see cookie "myCookie"
15
+ */
16
+ Then("I do not see cookie {string}", async function (this: CustomWorld, name: string) {
17
+ const cookies = await this.context.cookies();
18
+ const cookie = cookies.find((c) => c.name === name);
19
+ if (cookie) throw new Error(`Cookie "${name}" was found but should not exist`);
20
+ });
21
+
22
+ /**
23
+ * THEN: I see cookie "sessionId" has value "abc123"
24
+ */
25
+ Then(
26
+ "I see cookie {string} has value {string}",
27
+ async function (this: CustomWorld, name: string, expectedValue: string) {
28
+ const cookies = await this.context.cookies();
29
+ const cookie = cookies.find((c) => c.name === name);
30
+ if (!cookie) throw new Error(`Cookie "${name}" not found`);
31
+ if (cookie.value !== expectedValue) {
32
+ throw new Error(
33
+ `Expected cookie "${name}" to have value "${expectedValue}", but got "${cookie.value}"`
34
+ );
35
+ }
36
+ }
37
+ );
38
+
39
+ /**
40
+ * THEN: I see cookie "token" contains value "auth"
41
+ */
42
+ Then(
43
+ "I see cookie {string} contains value {string}",
44
+ async function (this: CustomWorld, name: string, valuePart: string) {
45
+ const cookies = await this.context.cookies();
46
+ const cookie = cookies.find((c) => c.name === name);
47
+ if (!cookie) throw new Error(`Cookie "${name}" not found`);
48
+ if (!cookie.value.includes(valuePart)) {
49
+ throw new Error(`Cookie "${name}" does not contain value "${valuePart}"`);
50
+ }
51
+ }
52
+ );
@@ -0,0 +1,103 @@
1
+ import { Then } from "@cucumber/cucumber";
2
+ import { expect } from "@playwright/test";
3
+ import type { CustomWorld } from "../helpers/world";
4
+ //
5
+ // ✅ ELEMENT EXISTS
6
+ //
7
+
8
+ Then(/^I see element "([^"]+)" exists$/, async function (this: any, selector: string) {
9
+ const el = await this.page.locator(selector);
10
+ await expect(el).toHaveCount(1);
11
+ });
12
+ Then("I see element exists", async function (this: CustomWorld) {
13
+ if (!this.element) throw new Error("No element stored in context");
14
+ const count = (await this.element.count?.()) ?? 1;
15
+ if (count === 0) throw new Error("Element does not exist");
16
+ });
17
+ Then("I see element does not exist", async function (this: CustomWorld) {
18
+ if (!this.element) throw new Error("No element stored in context");
19
+ const count = (await this.element.count?.()) ?? 1;
20
+ if (count > 0) throw new Error("Element exists but should not");
21
+ });
22
+ Then("I see element is visible", async function (this: CustomWorld) {
23
+ if (!this.element) throw new Error("No element in context");
24
+ const isVisible = await this.element.isVisible();
25
+ if (!isVisible) throw new Error("Element is not visible");
26
+ });
27
+ Then("I see element is not visible", async function (this: CustomWorld) {
28
+ if (!this.element) throw new Error("No element in context");
29
+ const isVisible = await this.element.isVisible();
30
+ if (isVisible) throw new Error("Element is visible but should not be");
31
+ });
32
+
33
+ Then(/^I see element "([^"]+)" does not exist$/, async function (this: any, selector: string) {
34
+ const el = await this.page.locator(selector);
35
+ await expect(el).toHaveCount(0);
36
+ });
37
+
38
+ //
39
+ // 👁️ ELEMENT VISIBILITY
40
+ //
41
+
42
+ Then(/^I see element "([^"]+)" is visible$/, async function (this: any, selector: string) {
43
+ const el = this.page.locator(selector);
44
+ await expect(el).toBeVisible();
45
+ });
46
+
47
+ Then(/^I see element "([^"]+)" is not visible$/, async function (this: any, selector: string) {
48
+ const el = this.page.locator(selector);
49
+ await expect(el).not.toBeVisible();
50
+ });
51
+
52
+ //
53
+ // 🔎 ATTRIBUTE ASSERTIONS
54
+ //
55
+
56
+ Then(
57
+ /^I see element "([^"]+)" attribute "([^"]+)" equals "(.*)"$/,
58
+ async function (this: any, selector: string, attribute: string, expected: string) {
59
+ const el = this.page.locator(selector);
60
+ await expect(el).toHaveAttribute(attribute, expected);
61
+ }
62
+ );
63
+ Then(
64
+ 'I see element attribute "{word}" equals {string}',
65
+ async function (this: CustomWorld, attr: string, expected: string) {
66
+ if (!this.element) {
67
+ throw new Error("No element is currently selected. Use a 'find' step before asserting.");
68
+ }
69
+ await expect(this.element).toHaveAttribute(attr, expected);
70
+ }
71
+ );
72
+
73
+ Then("I see element has attribute {string}", async function (this: CustomWorld, attr: string) {
74
+ if (!this.element) throw new Error("No element in context");
75
+ const value = await this.element.getAttribute(attr);
76
+ if (value === null) throw new Error(`Attribute "${attr}" not found`);
77
+ });
78
+ Then(
79
+ 'I see element attribute "{word}" contains {string}',
80
+ async function (this: CustomWorld, attr: string, part: string) {
81
+ if (!this.element) throw new Error("No element in context");
82
+ const value = await this.element.getAttribute(attr);
83
+ if (!value?.includes(part)) {
84
+ throw new Error(`Attribute "${attr}" does not contain "${part}". Got: "${value}"`);
85
+ }
86
+ }
87
+ );
88
+
89
+ Then(
90
+ /^I see element "([^"]+)" attribute "([^"]+)" contains "(.*)"$/,
91
+ async function (this: any, selector: string, attribute: string, substring: string) {
92
+ const attr = await this.page.locator(selector).getAttribute(attribute);
93
+ expect(attr?.includes(substring)).toBeTruthy();
94
+ }
95
+ );
96
+
97
+ Then(
98
+ /^I see element "([^"]+)" has attribute "([^"]+)"$/,
99
+ async function (this: any, selector: string, attribute: string) {
100
+ const attr = await this.page.locator(selector).getAttribute(attribute);
101
+ expect(attr).not.toBeNull();
102
+ }
103
+ );
@@ -0,0 +1,110 @@
1
+ import { Then } from "@cucumber/cucumber";
2
+ import { expect } from "@playwright/test";
3
+ import { CustomWorld } from "../helpers/world";
4
+ //
5
+ // 🧾 INPUT VALUES
6
+ //
7
+
8
+ Then(
9
+ /^I see input "([^"]+)" has value "(.*)"$/,
10
+ async function (this: any, selector: string, value: string) {
11
+ const el = this.page.locator(selector);
12
+ await expect(el).toHaveValue(value);
13
+ }
14
+ );
15
+ Then("I see input value {string}", async function (this: CustomWorld, expected: string) {
16
+ if (!this.element) throw new Error("No element stored in context");
17
+ const value = await this.element.inputValue();
18
+ expect(value).toBe(expected);
19
+ });
20
+ Then("I see input value contains {string}", async function (this: CustomWorld, part: string) {
21
+ if (!this.element) throw new Error("No element stored in context");
22
+ const value = await this.element.inputValue();
23
+ expect(value).toContain(part);
24
+ });
25
+
26
+ Then(
27
+ /^I see input "([^"]+)" value contains "(.*)"$/,
28
+ async function (this: any, selector: string, partial: string) {
29
+ const val = await this.page.locator(selector).inputValue();
30
+ expect(val).toContain(partial);
31
+ }
32
+ );
33
+
34
+ //
35
+ // 📝 TEXTAREA VALUES
36
+ //
37
+
38
+ Then(
39
+ /^I see textarea "([^"]+)" has value "(.*)"$/,
40
+ async function (this: any, selector: string, value: string) {
41
+ const el = this.page.locator(selector);
42
+ await expect(el).toHaveValue(value);
43
+ }
44
+ );
45
+ Then("I see textarea value {string}", async function (this: CustomWorld, expected: string) {
46
+ if (!this.element) throw new Error("No textarea selected");
47
+ const value = await this.element.inputValue();
48
+ if (value !== expected) throw new Error(`Expected "${expected}", got "${value}"`);
49
+ });
50
+ Then("I see textarea value contains {string}", async function (this: CustomWorld, part: string) {
51
+ if (!this.element) throw new Error("No textarea selected");
52
+ const value = await this.element.inputValue();
53
+ if (!value.includes(part)) {
54
+ throw new Error(`Textarea does not contain "${part}". Got: "${value}"`);
55
+ }
56
+ });
57
+
58
+ Then(
59
+ /^I see textarea "([^"]+)" value contains "(.*)"$/,
60
+ async function (this: any, selector: string, partial: string) {
61
+ const val = await this.page.locator(selector).inputValue();
62
+ expect(val).toContain(partial);
63
+ }
64
+ );
65
+
66
+ //
67
+ // ✅ GENERIC VALUE MATCHING
68
+ //
69
+
70
+ Then(
71
+ /^I see value "(.*)" in "([^"]+)"$/,
72
+ async function (this: any, value: string, selector: string) {
73
+ const el = this.page.locator(selector);
74
+ await expect(el).toHaveValue(value);
75
+ }
76
+ );
77
+
78
+ Then(
79
+ /^I do not see value "(.*)" in "([^"]+)"$/,
80
+ async function (this: any, value: string, selector: string) {
81
+ const actual = await this.page.locator(selector).inputValue();
82
+ expect(actual).not.toBe(value);
83
+ }
84
+ );
85
+
86
+ //
87
+ // ⬇️ OPTION IN SELECT
88
+ //
89
+
90
+ Then(/^I see option "(.*)"$/, async function (this: any, optionText: string) {
91
+ const el = this.page.locator(`option`, { hasText: optionText });
92
+ await expect(el).toHaveCount(1);
93
+ });
94
+ Then("I see option {string}", async function (this: CustomWorld, optionText: string) {
95
+ const option = this.page.locator("option", { hasText: optionText });
96
+ if (!(await option.isVisible())) {
97
+ throw new Error(`Option "${optionText}" not visible`);
98
+ }
99
+ });
100
+ Then("I do not see option {string}", async function (this: CustomWorld, optionText: string) {
101
+ const option = this.page.locator("option", { hasText: optionText });
102
+ if ((await option.count()) > 0 && (await option.first().isVisible())) {
103
+ throw new Error(`Option "${optionText}" is visible but should not be`);
104
+ }
105
+ });
106
+
107
+ Then(/^I do not see option "(.*)"$/, async function (this: any, optionText: string) {
108
+ const el = this.page.locator(`option`, { hasText: optionText });
109
+ await expect(el).toHaveCount(0);
110
+ });
@@ -0,0 +1,216 @@
1
+ import { Then } from "@cucumber/cucumber";
2
+ import { expect } from "@playwright/test";
3
+ import type { CustomWorld } from "../helpers/world";
4
+ // Accessing the Last Response
5
+
6
+ Then("I should see response status {int}", function (expectedStatus: number) {
7
+ expect(this.data.lastResponse?.status).toBe(expectedStatus);
8
+ this.log(`Verified response status is ${expectedStatus}`);
9
+ });
10
+
11
+ Then("I should see response body contains {string}", function (expectedText: string) {
12
+ expect(this.data.lastResponse?.body).toContain(expectedText);
13
+ this.log(`Verified response body contains "${expectedText}"`);
14
+ });
15
+ Then("I see response body {string}", async function (this: CustomWorld, expected: string) {
16
+ const res = this.data.lastResponse;
17
+ const body = await res.text();
18
+ if (body !== expected) throw new Error(`Expected body "${expected}", got "${body}"`);
19
+ });
20
+ Then("I see response body contains {string}", async function (this: CustomWorld, part: string) {
21
+ const res = this.data.lastResponse;
22
+ const body = await res.text();
23
+ if (!body.includes(part)) throw new Error(`Body does not contain "${part}"`);
24
+ });
25
+ Then(
26
+ "I see response body matches JSON schema {string}",
27
+ async function (this: CustomWorld, schemaPath: string) {
28
+ const res = this.data.lastResponse;
29
+ const body = await res.text();
30
+ const schema = require(schemaPath); // Assuming schema is a JSON file
31
+ const Ajv = require("ajv");
32
+ const ajv = new Ajv();
33
+ const validate = ajv.compile(schema);
34
+ const valid = validate(JSON.parse(body));
35
+ if (!valid) {
36
+ throw new Error(`Response body does not match schema: ${ajv.errorsText(validate.errors)}`);
37
+ }
38
+ }
39
+ );
40
+ Then(
41
+ "I see response header {string} equals {string}",
42
+ async function (this: CustomWorld, headerName: string, expectedValue: string) {
43
+ const res = this.data.lastResponse;
44
+ const headerValue = res.headers()[headerName.toLowerCase()];
45
+ if (headerValue !== expectedValue) {
46
+ throw new Error(
47
+ `Expected header "${headerName}" to be "${expectedValue}", got "${headerValue}"`
48
+ );
49
+ }
50
+ this.log(`Verified response header "${headerName}" equals "${expectedValue}"`);
51
+ }
52
+ );
53
+ Then(
54
+ "I see response header {string} contains {string}",
55
+ async function (this: CustomWorld, headerName: string, expectedValue: string) {
56
+ const res = this.data.lastResponse;
57
+ const headerValue = res.headers()[headerName.toLowerCase()];
58
+ if (!headerValue || !headerValue.includes(expectedValue)) {
59
+ throw new Error(
60
+ `Expected header "${headerName}" to contain "${expectedValue}", got "${headerValue}"`
61
+ );
62
+ }
63
+ this.log(`Verified response header "${headerName}" contains "${expectedValue}"`);
64
+ }
65
+ );
66
+ Then(
67
+ "I see response header {string} does not contain {string}",
68
+ async function (this: CustomWorld, headerName: string, unexpectedValue: string) {
69
+ const res = this.data.lastResponse;
70
+ const headerValue = res.headers()[headerName.toLowerCase()];
71
+ if (headerValue && headerValue.includes(unexpectedValue)) {
72
+ throw new Error(
73
+ `Expected header "${headerName}" to not contain "${unexpectedValue}", but it does`
74
+ );
75
+ }
76
+ this.log(`Verified response header "${headerName}" does not contain "${unexpectedValue}"`);
77
+ }
78
+ );
79
+ Then(
80
+ "I see response header {string} does not equal {string}",
81
+ async function (this: CustomWorld, headerName: string, unexpectedValue: string) {
82
+ const res = this.data.lastResponse;
83
+ const headerValue = res.headers()[headerName.toLowerCase()];
84
+ if (headerValue === unexpectedValue) {
85
+ throw new Error(
86
+ `Expected header "${headerName}" to not equal "${unexpectedValue}", but it does`
87
+ );
88
+ }
89
+ this.log(`Verified response header "${headerName}" does not equal "${unexpectedValue}"`);
90
+ }
91
+ );
92
+ Then(
93
+ "I see response header {string} exists",
94
+ async function (this: CustomWorld, headerName: string) {
95
+ const res = this.data.lastResponse;
96
+ const headerValue = res.headers()[headerName.toLowerCase()];
97
+ if (!headerValue) {
98
+ throw new Error(`Expected header "${headerName}" to exist, but it does not`);
99
+ }
100
+ this.log(`Verified response header "${headerName}" exists`);
101
+ }
102
+ );
103
+ Then(
104
+ "I see response header {string} does not exist",
105
+ async function (this: CustomWorld, headerName: string) {
106
+ const res = this.data.lastResponse;
107
+ const headerValue = res.headers()[headerName.toLowerCase()];
108
+ if (headerValue) {
109
+ throw new Error(`Expected header "${headerName}" to not exist, but it does`);
110
+ }
111
+ this.log(`Verified response header "${headerName}" does not exist`);
112
+ }
113
+ );
114
+ Then("I see response status {int}", async function (this: CustomWorld, status: number) {
115
+ const res = this.data.lastResponse;
116
+ if (!res) throw new Error("No response available");
117
+ const actual = res.status();
118
+ if (actual !== status) throw new Error(`Expected status ${status}, got ${actual}`);
119
+ });
120
+ Then("I see response status is not {int}", async function (this: CustomWorld, status: number) {
121
+ const res = this.data.lastResponse;
122
+ if (!res) throw new Error("No response available");
123
+ const actual = res.status();
124
+ if (actual === status) throw new Error(`Expected status not to be ${status}, but it is`);
125
+ });
126
+ Then("I see response body matches JSON schema", async function (this: CustomWorld, schema: object) {
127
+ const res = this.data.lastResponse;
128
+ if (!res) throw new Error("No response available");
129
+ const body = await res.text();
130
+ const Ajv = require("ajv");
131
+ const ajv = new Ajv();
132
+ const validate = ajv.compile(schema);
133
+ const valid = validate(JSON.parse(body));
134
+ if (!valid) {
135
+ throw new Error(`Response body does not match schema: ${ajv.errorsText(validate.errors)}`);
136
+ }
137
+ this.log(`Response body matches JSON schema`);
138
+ });
139
+ Then("I see response body is empty", async function (this: CustomWorld) {
140
+ const res = this.data.lastResponse;
141
+ if (!res) throw new Error("No response available");
142
+ const body = await res.text();
143
+ if (body.trim() !== "") {
144
+ throw new Error(`Expected empty response body, got "${body}"`);
145
+ }
146
+ this.log(`Verified response body is empty`);
147
+ });
148
+ Then("I see response body is not empty", async function (this: CustomWorld) {
149
+ const res = this.data.lastResponse;
150
+ if (!res) throw new Error("No response available");
151
+ const body = await res.text();
152
+ if (body.trim() === "") {
153
+ throw new Error(`Expected non-empty response body, got empty`);
154
+ }
155
+ this.log(`Verified response body is not empty`);
156
+ });
157
+ Then("I see response body matches {string}", async function (this: CustomWorld, expected: string) {
158
+ const res = this.data.lastResponse;
159
+ if (!res) throw new Error("No response available");
160
+ const body = await res.text();
161
+ if (body !== expected) {
162
+ throw new Error(`Expected body "${expected}", got "${body}"`);
163
+ }
164
+ this.log(`Verified response body matches: ${expected}`);
165
+ });
166
+ Then("I see response body contains {string}", async function (this: CustomWorld, part: string) {
167
+ const res = this.data.lastResponse;
168
+ if (!res) throw new Error("No response available");
169
+ const body = await res.text();
170
+ if (!body.includes(part)) {
171
+ throw new Error(`Body does not contain "${part}"`);
172
+ }
173
+ this.log(`Verified response body contains: ${part}`);
174
+ });
175
+ Then(
176
+ "I see response body does not contain {string}",
177
+ async function (this: CustomWorld, part: string) {
178
+ const res = this.data.lastResponse;
179
+ if (!res) throw new Error("No response available");
180
+ const body = await res.text();
181
+ if (body.includes(part)) {
182
+ throw new Error(`Body contains "${part}", but it should not`);
183
+ }
184
+ this.log(`Verified response body does not contain: ${part}`);
185
+ }
186
+ );
187
+ Then("I see response body is JSON", async function (this: CustomWorld) {
188
+ const res = this.data.lastResponse;
189
+ if (!res) throw new Error("No response available");
190
+ const body = await res.text();
191
+ try {
192
+ JSON.parse(body);
193
+ this.log(`Verified response body is valid JSON`);
194
+ } catch (e) {
195
+ if (e instanceof Error) {
196
+ throw new Error(`Response body is not valid JSON: ${e.message}`);
197
+ } else {
198
+ throw new Error(`Response body is not valid JSON: ${String(e)}`);
199
+ }
200
+ }
201
+ });
202
+ Then("I see response body is not JSON", async function (this: CustomWorld) {
203
+ const res = this.data.lastResponse;
204
+ if (!res) throw new Error("No response available");
205
+ const body = await res.text();
206
+ try {
207
+ JSON.parse(body);
208
+ throw new Error(`Expected response body to not be JSON, but it is`);
209
+ } catch (e) {
210
+ if (e instanceof Error) {
211
+ this.log(`Verified response body is not JSON: ${e.message}`);
212
+ } else {
213
+ this.log(`Verified response body is not JSON: ${String(e)}`);
214
+ }
215
+ }
216
+ });