@vardot/varbase-e2e 2.0.0
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.
- package/AGENTS.md +148 -0
- package/CLAUDE.md +544 -0
- package/README.md +22 -0
- package/bin/generate-reports.js +363 -0
- package/bin/init-varbase-e2e.js +330 -0
- package/bin/postinstall.js +26 -0
- package/cucumber.js +125 -0
- package/docs/00-quick-start.md +102 -0
- package/docs/01-getting-started.md +102 -0
- package/docs/02-bbr-smart-waits.md +77 -0
- package/docs/03-selector-registry.md +95 -0
- package/docs/04-step-reference.md +801 -0
- package/docs/05-web-first-assertions.md +91 -0
- package/docs/06-network-and-dialogs.md +83 -0
- package/docs/07-auth-state.md +75 -0
- package/docs/08-clock-mocking.md +59 -0
- package/docs/09-api-testing.md +114 -0
- package/docs/10-accessibility.md +167 -0
- package/docs/11-debugging.md +129 -0
- package/docs/12-ai-agent-guide.md +338 -0
- package/docs/13-faq.md +236 -0
- package/docs/14-recipes-cookbook.md +255 -0
- package/docs/15-tag-conventions.md +76 -0
- package/docs/16-ci-cd.md +90 -0
- package/docs/README.md +125 -0
- package/docs/advanced-screenshots/then-i-save-fullscreen-screenshot.md +36 -0
- package/docs/advanced-screenshots/then-i-save-width-x-height-screenshot.md +34 -0
- package/docs/advanced-screenshots/when-i-save-screenshot-name-filename.md +45 -0
- package/docs/advanced-screenshots.md +52 -0
- package/docs/advanced-selectors.md +18 -0
- package/docs/api-step-definitions.md +45 -0
- package/docs/global-settings.md +267 -0
- package/docs/install-varbase-e2e/ddev-varbase-e2e.md +52 -0
- package/docs/install-varbase-e2e.md +34 -0
- package/docs/overview.md +40 -0
- package/docs/step-definitions/given-i-am-anonymous-user.md +67 -0
- package/docs/step-definitions/given-i-am-homepage.md +68 -0
- package/docs/step-definitions/given-i-am-specific-page.md +72 -0
- package/docs/step-definitions/then-i-should-be.md +56 -0
- package/docs/step-definitions/then-i-should-see.md +62 -0
- package/docs/step-definitions/then-item-checkbox.md +54 -0
- package/docs/step-definitions/then-item-link-should-contain-url.md +84 -0
- package/docs/step-definitions/then-item-should-contain.md +67 -0
- package/docs/step-definitions/then-radio-button-value-should-be-selected.md +68 -0
- package/docs/step-definitions/then-response-status-code-should-be-number.md +69 -0
- package/docs/step-definitions/then-value-radio-button-selected.md +62 -0
- package/docs/step-definitions/when-i-attach-file-file-name-element.md +71 -0
- package/docs/step-definitions/when-i-check-checkbox.md +60 -0
- package/docs/step-definitions/when-i-click-link.md +51 -0
- package/docs/step-definitions/when-i-click-operation-text-row.md +71 -0
- package/docs/step-definitions/when-i-fill.md +59 -0
- package/docs/step-definitions/when-i-go-homepage.md +60 -0
- package/docs/step-definitions/when-i-go-specific-page.md +67 -0
- package/docs/step-definitions/when-i-move-backward-one-page.md +49 -0
- package/docs/step-definitions/when-i-move-forward-one-page.md +58 -0
- package/docs/step-definitions/when-i-press-button.md +58 -0
- package/docs/step-definitions/when-i-reload-page.md +51 -0
- package/docs/step-definitions/when-i-scroll.md +73 -0
- package/docs/step-definitions/when-i-select-option-select-list.md +53 -0
- package/docs/step-definitions/when-i-select-radio-button-value.md +62 -0
- package/docs/step-definitions/when-i-uncheck-checkbox.md +57 -0
- package/docs/step-definitions/when-i-wait-ajax-finish.md +64 -0
- package/docs/step-definitions/when-i-wait.md +62 -0
- package/docs/step-definitions.md +82 -0
- package/docs/steps/README.md +137 -0
- package/docs/steps/a11y.md +731 -0
- package/docs/steps/action.md +181 -0
- package/docs/steps/api.md +679 -0
- package/docs/steps/assertion.md +328 -0
- package/docs/steps/auth.md +108 -0
- package/docs/steps/clock.md +171 -0
- package/docs/steps/cookie.md +293 -0
- package/docs/steps/debug.md +52 -0
- package/docs/steps/dialog.md +226 -0
- package/docs/steps/drupal-canvas.md +414 -0
- package/docs/steps/drupal-ckeditor.md +118 -0
- package/docs/steps/drupal-core.md +688 -0
- package/docs/steps/drupal-layout-builder.md +437 -0
- package/docs/steps/drupal-media.md +107 -0
- package/docs/steps/drupal-moderation.md +81 -0
- package/docs/steps/drupal-paragraphs.md +36 -0
- package/docs/steps/element.md +455 -0
- package/docs/steps/field.md +648 -0
- package/docs/steps/file-download.md +235 -0
- package/docs/steps/form.md +344 -0
- package/docs/steps/iframe.md +248 -0
- package/docs/steps/input.md +220 -0
- package/docs/steps/javascript.md +111 -0
- package/docs/steps/keyboard.md +107 -0
- package/docs/steps/link.md +217 -0
- package/docs/steps/metatag.md +99 -0
- package/docs/steps/modal.md +232 -0
- package/docs/steps/navigation.md +275 -0
- package/docs/steps/network.md +284 -0
- package/docs/steps/path.md +203 -0
- package/docs/steps/response.md +102 -0
- package/docs/steps/responsive.md +139 -0
- package/docs/steps/rest.md +140 -0
- package/docs/steps/screenshot.md +211 -0
- package/docs/steps/scroll.md +266 -0
- package/docs/steps/selectors.md +848 -0
- package/docs/steps/storage.md +254 -0
- package/docs/steps/table.md +216 -0
- package/docs/steps/varbase.md +391 -0
- package/docs/steps/video.md +111 -0
- package/docs/steps/wait.md +553 -0
- package/docs/steps/web-first.md +289 -0
- package/docs/steps/xml.md +505 -0
- package/docs/steps/yaml.md +951 -0
- package/examples/a11y.html +31 -0
- package/examples/about-us.html +11 -0
- package/examples/account.html +32 -0
- package/examples/ajax-wait-examples.html +147 -0
- package/examples/contact-us.html +10 -0
- package/examples/cookies.html +10 -0
- package/examples/date.html +12 -0
- package/examples/dynamic-examples.html +498 -0
- package/examples/element.html +11 -0
- package/examples/example-api.json +28 -0
- package/examples/existing-page.html +11 -0
- package/examples/field.html +24 -0
- package/examples/index.html +37 -0
- package/examples/keyboard.html +11 -0
- package/examples/links.html +9 -0
- package/examples/modal.html +14 -0
- package/examples/path.html +3 -0
- package/examples/table.html +13 -0
- package/examples/test--modal-dialogs.html +252 -0
- package/examples/test--then--i-should--not--see-text-in-element.html +48 -0
- package/examples/test--then--i-should--see-text-in-element.html +48 -0
- package/examples/test--then--i-should--see-text-matching-pattern.html +31 -0
- package/examples/test--then--i-should-see-text-in-table-row.html +232 -0
- package/examples/test--then--the-checkbox-checked.html +68 -0
- package/examples/test--then--the-element-should_not-contain-cssproperty.html +47 -0
- package/examples/test--then--the-link-should-contain.html +21 -0
- package/examples/test--when--i-additionally-select.html +27 -0
- package/examples/test--when--i-attach-file-to-feild.html +50 -0
- package/examples/test--when--i-check-uncheck-checkbox.html +47 -0
- package/examples/test--when--i-click-link.html +18 -0
- package/examples/test--when--i-click-text-in-table-row.html +178 -0
- package/examples/test--when--i-fill-in.html +48 -0
- package/examples/test--when--i-press-button.html +31 -0
- package/examples/test--when--i-reload-page.html +27 -0
- package/examples/test--when--i-scroll-functionality.html +177 -0
- package/examples/test--when--i-scroll-left-right-functionality.html +149 -0
- package/examples/test--when--i-select-option-from-dropdownlist.html +35 -0
- package/examples/test--when--i-select-radio-button.html +95 -0
- package/examples/test--when-i-wait-max-of-time.html +39 -0
- package/examples/test--when-i-wait-page-until-is-loaded.html +47 -0
- package/examples/test--when-i-wait-specific-time.html +39 -0
- package/examples/test-acceptable-url-path.html +11 -0
- package/examples/video-demo.html +43 -0
- package/package.json +56 -0
- package/playwright.config.ts +39 -0
- package/tests/assets/test-document.pdf +14 -0
- package/tests/assets/vardot.png +0 -0
- package/tests/features/a11y.feature +39 -0
- package/tests/features/ajax-wait-examples.feature +24 -0
- package/tests/features/api-step-definitions-individual.feature +185 -0
- package/tests/features/complete-api-testing-examples.feature +205 -0
- package/tests/features/cookie.feature +13 -0
- package/tests/features/date.feature +11 -0
- package/tests/features/dynamic-content-examples.feature +156 -0
- package/tests/features/element.feature +16 -0
- package/tests/features/field.feature +25 -0
- package/tests/features/keyboard.feature +10 -0
- package/tests/features/link.feature +11 -0
- package/tests/features/modal-advanced-example.feature +54 -0
- package/tests/features/modal-basic-example.feature +42 -0
- package/tests/features/modal-error-handling-example.feature +37 -0
- package/tests/features/modal.feature +11 -0
- package/tests/features/path.feature +16 -0
- package/tests/features/response.feature +8 -0
- package/tests/features/responsive.feature +10 -0
- package/tests/features/table.feature +18 -0
- package/tests/features/test--given--i-am-an-anonymous-user.feature +10 -0
- package/tests/features/test--given--i-am-on-homepage.feature +16 -0
- package/tests/features/test--then--i-save-screenshot.feature +33 -0
- package/tests/features/test--then--i-see-locator.feature +320 -0
- package/tests/features/test--then--i-should--not--see-text-in-element.feature +13 -0
- package/tests/features/test--then--i-should--see-text-in-element-by-attr.feature +10 -0
- package/tests/features/test--then--i-should--see-text-in-element.feature +10 -0
- package/tests/features/test--then--i-should--see-text-matching-pattern-in-element.feature +13 -0
- package/tests/features/test--then--i-should--see-text-matching-pattern.feature +18 -0
- package/tests/features/test--then--i-should-be-on-page.feature +10 -0
- package/tests/features/test--then--i-should-be-on-the-homepage.feature +10 -0
- package/tests/features/test--then--i-should-not-see-an-element-by-attr.feature +8 -0
- package/tests/features/test--then--i-should-not-see-an-element.feature +8 -0
- package/tests/features/test--then--i-should-see-an-element-by-attr.feature +8 -0
- package/tests/features/test--then--i-should-see-an-element.feature +8 -0
- package/tests/features/test--then--i-should-see-n-elements.feature +24 -0
- package/tests/features/test--then--i-should-see-text-in-table-row.feature +96 -0
- package/tests/features/test--then--print-current-url-and-response.feature +21 -0
- package/tests/features/test--then--the-checkbox-is-checked.feature +18 -0
- package/tests/features/test--then--the-checkbox-should-be-checked.feature +16 -0
- package/tests/features/test--then--the-element-should-contain-cssproperty.feature +9 -0
- package/tests/features/test--then--the-element-should-not-contain-cssproperty.feature +10 -0
- package/tests/features/test--then--the-field-should-contain-value.feature +11 -0
- package/tests/features/test--then--the-response-should-contain.feature +11 -0
- package/tests/features/test--then--the-response-status-code-should-be.feature +31 -0
- package/tests/features/test--then--the-url-should-match.feature +7 -0
- package/tests/features/test--then-the-link-should-contain-by-attribute.feature +18 -0
- package/tests/features/test--then-the-link-should-contain.feature +7 -0
- package/tests/features/test--when--i-additionally-select.feature +30 -0
- package/tests/features/test--when--i-attach-file-to-feild.feature +9 -0
- package/tests/features/test--when--i-check-checkbox.feature +21 -0
- package/tests/features/test--when--i-click-link-by-attribute.feature +21 -0
- package/tests/features/test--when--i-click-link.feature +9 -0
- package/tests/features/test--when--i-click-text-in-table-row.feature +37 -0
- package/tests/features/test--when--i-fill-in-by-attr.feature +31 -0
- package/tests/features/test--when--i-fill-in-table-by-attr.feature +19 -0
- package/tests/features/test--when--i-fill-in.feature +32 -0
- package/tests/features/test--when--i-follow.feature +22 -0
- package/tests/features/test--when--i-go-to-homepage.feature +9 -0
- package/tests/features/test--when--i-move-forward-backward-one-page.feature +14 -0
- package/tests/features/test--when--i-press-button-by-attribute.feature +18 -0
- package/tests/features/test--when--i-press-button.feature +13 -0
- package/tests/features/test--when--i-reload-page.feature +11 -0
- package/tests/features/test--when--i-scroll-left-right.feature +48 -0
- package/tests/features/test--when--i-scroll.feature +48 -0
- package/tests/features/test--when--i-select-option-from-dropdownlist.feature +21 -0
- package/tests/features/test--when--i-select-radio-button.feature +37 -0
- package/tests/features/test--when--i-wait-page-is-loaded.feature +8 -0
- package/tests/features/test--when--i-wait-time.feature +21 -0
- package/tests/features/video.feature +18 -0
- package/tests/features/wait.feature +7 -0
- package/tests/features/yaml.feature +102 -0
- package/tests/selectors/_canonical-keys.json +113 -0
- package/tests/selectors/back-end-selectors.json +19 -0
- package/tests/selectors/cms-contentful-admin.json +35 -0
- package/tests/selectors/cms-craft-admin.json +39 -0
- package/tests/selectors/cms-drupal-cms-gin.json +56 -0
- package/tests/selectors/cms-drupal-core-claro.json +50 -0
- package/tests/selectors/cms-generic-admin.json +34 -0
- package/tests/selectors/cms-ghost-admin.json +34 -0
- package/tests/selectors/cms-joomla-admin.json +41 -0
- package/tests/selectors/cms-magento2-admin.json +32 -0
- package/tests/selectors/cms-prestashop-admin.json +38 -0
- package/tests/selectors/cms-shopify-admin.json +25 -0
- package/tests/selectors/cms-strapi-admin.json +42 -0
- package/tests/selectors/cms-typo3-admin.json +41 -0
- package/tests/selectors/cms-woocommerce-front.json +50 -0
- package/tests/selectors/cms-wordpress-admin.json +31 -0
- package/tests/selectors/framework-ant-design.json +80 -0
- package/tests/selectors/framework-bootstrap.json +86 -0
- package/tests/selectors/framework-bulma.json +74 -0
- package/tests/selectors/framework-chakra.json +58 -0
- package/tests/selectors/framework-foundation.json +51 -0
- package/tests/selectors/framework-material-ui.json +72 -0
- package/tests/selectors/framework-shadcn.json +51 -0
- package/tests/selectors/framework-tailwind.json +40 -0
- package/tests/selectors/framework-vuetify.json +61 -0
- package/tests/selectors/front-end-selectors.json +16 -0
- package/tests/selectors/homepage-selectors.json +13 -0
- package/tests/step-definitions/a11y.steps.js +800 -0
- package/tests/step-definitions/action.steps.js +167 -0
- package/tests/step-definitions/api.steps.js +732 -0
- package/tests/step-definitions/assertion.steps.js +352 -0
- package/tests/step-definitions/auth.steps.js +126 -0
- package/tests/step-definitions/clock.steps.js +128 -0
- package/tests/step-definitions/cookie.steps.js +216 -0
- package/tests/step-definitions/debug.steps.js +39 -0
- package/tests/step-definitions/dialog.steps.js +193 -0
- package/tests/step-definitions/drupal-canvas.steps.js +764 -0
- package/tests/step-definitions/drupal-ckeditor.steps.js +108 -0
- package/tests/step-definitions/drupal-core.steps.js +665 -0
- package/tests/step-definitions/drupal-helpers.js +371 -0
- package/tests/step-definitions/drupal-layout-builder.steps.js +466 -0
- package/tests/step-definitions/drupal-media.steps.js +99 -0
- package/tests/step-definitions/drupal-moderation.steps.js +71 -0
- package/tests/step-definitions/drupal-paragraphs.steps.js +50 -0
- package/tests/step-definitions/element.steps.js +356 -0
- package/tests/step-definitions/field.steps.js +653 -0
- package/tests/step-definitions/file-download.steps.js +211 -0
- package/tests/step-definitions/form.steps.js +333 -0
- package/tests/step-definitions/iframe.steps.js +228 -0
- package/tests/step-definitions/input.steps.js +202 -0
- package/tests/step-definitions/javascript.steps.js +232 -0
- package/tests/step-definitions/keyboard.steps.js +112 -0
- package/tests/step-definitions/link.steps.js +142 -0
- package/tests/step-definitions/metatag.steps.js +91 -0
- package/tests/step-definitions/modal.steps.js +244 -0
- package/tests/step-definitions/navigation.steps.js +218 -0
- package/tests/step-definitions/network.steps.js +280 -0
- package/tests/step-definitions/path.steps.js +141 -0
- package/tests/step-definitions/response.steps.js +98 -0
- package/tests/step-definitions/responsive.steps.js +125 -0
- package/tests/step-definitions/rest.steps.js +111 -0
- package/tests/step-definitions/screenshot.steps.js +423 -0
- package/tests/step-definitions/scroll.steps.js +189 -0
- package/tests/step-definitions/selectors.steps.js +1003 -0
- package/tests/step-definitions/storage.steps.js +194 -0
- package/tests/step-definitions/table.steps.js +181 -0
- package/tests/step-definitions/varbase-e2e.js +897 -0
- package/tests/step-definitions/varbase.steps.js +458 -0
- package/tests/step-definitions/video.steps.js +178 -0
- package/tests/step-definitions/wait.steps.js +400 -0
- package/tests/step-definitions/web-first.steps.js +298 -0
- package/tests/step-definitions/xml.steps.js +411 -0
- package/tests/step-definitions/yaml.steps.js +913 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,732 @@
|
|
|
1
|
+
const { Given, When, Then, Before } = require('@cucumber/cucumber');
|
|
2
|
+
const axios = require('axios');
|
|
3
|
+
const assert = require('assert');
|
|
4
|
+
const { friendly } = require('./varbase-e2e');
|
|
5
|
+
|
|
6
|
+
// Global variables to store API response and request data
|
|
7
|
+
let apiResponse = null;
|
|
8
|
+
let apiRequestData = null;
|
|
9
|
+
let apiHeaders = {};
|
|
10
|
+
let baseURL = '';
|
|
11
|
+
let authorization = '';
|
|
12
|
+
let placeHolders = {};
|
|
13
|
+
|
|
14
|
+
Before(async function () {
|
|
15
|
+
// Reset API state before each scenario
|
|
16
|
+
apiResponse = null;
|
|
17
|
+
apiRequestData = null;
|
|
18
|
+
apiHeaders = {};
|
|
19
|
+
authorization = '';
|
|
20
|
+
placeHolders = {};
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Helper function to replace placeholders in text
|
|
25
|
+
*/
|
|
26
|
+
function replacePlaceHolder(string) {
|
|
27
|
+
for (const [key, value] of Object.entries(placeHolders)) {
|
|
28
|
+
string = string.replace(new RegExp(key, 'g'), value);
|
|
29
|
+
}
|
|
30
|
+
return string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Helper function to prepare URL by replacing placeholders and trimming slashes
|
|
35
|
+
*/
|
|
36
|
+
function prepareUrl(url) {
|
|
37
|
+
return replacePlaceHolder(url).replace(/^\/+/, '');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Helper function to get nested property from object using dot notation.
|
|
42
|
+
*/
|
|
43
|
+
function getNestedProperty(obj, path) {
|
|
44
|
+
return path.split('.').reduce((current, prop) => {
|
|
45
|
+
return current && current[prop] !== undefined ? current[prop] : undefined;
|
|
46
|
+
}, obj);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Adds Basic Authentication header to the next request.
|
|
51
|
+
*
|
|
52
|
+
* Example #1: Given I am authenticating as "admin" with "password123" password
|
|
53
|
+
* Example #2: Given we are authenticating as "user@example.com" with "secret" password
|
|
54
|
+
* Example #3: Given I am authenticating as "api-user" with "pass!word" password
|
|
55
|
+
* Example #4: Given we are authenticating as "reader" with "readonly" password
|
|
56
|
+
*
|
|
57
|
+
*/
|
|
58
|
+
Given(/^(?:I am|we are) authenticating as "([^"]*)" with "([^"]*)" password$/, function (username, password) {
|
|
59
|
+
delete apiHeaders['Authorization'];
|
|
60
|
+
authorization = Buffer.from(username + ':' + password).toString('base64');
|
|
61
|
+
apiHeaders['Authorization'] = 'Basic ' + authorization;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Sets a single HTTP request header.
|
|
66
|
+
*
|
|
67
|
+
* Example #1: Given I set header "Content-Type" with value "application/json"
|
|
68
|
+
* Example #2: Given I set header "Authorization" with value "Bearer token123"
|
|
69
|
+
* Example #3: Given I set header "Accept" with value "application/xml"
|
|
70
|
+
* Example #4: Given I set header "Accept" with value "application/json"
|
|
71
|
+
* Example #5: Given we set header "X-Api-Key" with value "abcd-1234"
|
|
72
|
+
* Example #6: Given I set header "User-Agent" with value "test-runner"
|
|
73
|
+
* Example #7: Given we set header "Accept-Language" with value "en-US"
|
|
74
|
+
* Example #8: Given I set header "If-None-Match" with value "etag-xyz"
|
|
75
|
+
*
|
|
76
|
+
*/
|
|
77
|
+
Given(/^(?:I |we )?set header "([^"]*)" with value "([^"]*)"$/, function (name, value) {
|
|
78
|
+
apiHeaders[name] = replacePlaceHolder(value);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Set the base URL for API calls. Accepts full URLs or paths relative to LAUNCH_URL.
|
|
83
|
+
*
|
|
84
|
+
* Example #1: Given the API base URL is "https://jsonplaceholder.typicode.com"
|
|
85
|
+
* Example #2: Given I set the API base URL to "https://api.example.com/v1"
|
|
86
|
+
* Example #3: Given the base URL is "http://localhost:3000/api"
|
|
87
|
+
* Example #4: Given the API base URL is "https://un.org/api"
|
|
88
|
+
* Example #5: Given I set the API base URL to "/api/v2"
|
|
89
|
+
* Example #6: Given the base URL is "https://staging.example.com/api"
|
|
90
|
+
* Example #7: Given the API base URL is "http://127.0.0.1:8080"
|
|
91
|
+
*
|
|
92
|
+
*/
|
|
93
|
+
Given(/^(?:the API base URL is|I set the API base URL to|the base URL is) "([^"]*)"$/, function (url) {
|
|
94
|
+
// Check if the input is a full URL (contains protocol)
|
|
95
|
+
if (url.match(/^https?:\/\//)) {
|
|
96
|
+
baseURL = url.replace(/\/$/, ''); // Remove trailing slash
|
|
97
|
+
} else {
|
|
98
|
+
// If not a full URL, combine with the LAUNCH_URL env variable or default
|
|
99
|
+
const launchUrl = (process.env.LAUNCH_URL || 'http://localhost:8080').replace(/\/$/, '');
|
|
100
|
+
baseURL = (launchUrl + '/' + url).replace(/\/+/g, '/').replace(/\/$/, '');
|
|
101
|
+
baseURL = baseURL.replace(':/', '://');
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Set a request header. Alternative syntax.
|
|
107
|
+
*
|
|
108
|
+
* Example #1: Given I set the header "Content-Type" to "application/json"
|
|
109
|
+
* Example #2: Given I set the header "Authorization" to "Bearer token123"
|
|
110
|
+
* Example #3: Given the header "Accept" is "application/json"
|
|
111
|
+
* Example #4: Given we set the header "X-Api-Key" to "abcd-1234"
|
|
112
|
+
* Example #5: Given I set the header "If-Match" to "etag-123"
|
|
113
|
+
* Example #6: Given the header "User-Agent" is "test-runner"
|
|
114
|
+
* Example #7: Given we set the header "Accept-Language" to "en-US"
|
|
115
|
+
* Example #8: Given I set the header "Cache-Control" to "no-cache"
|
|
116
|
+
*
|
|
117
|
+
*/
|
|
118
|
+
Given(/^(?:I set the header|we set the header|the header) "([^"]*)" (?:to|is) "([^"]*)"$/, function (headerName, headerValue) {
|
|
119
|
+
apiHeaders[headerName] = replacePlaceHolder(headerValue);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Set multiple headers using a table.
|
|
124
|
+
*
|
|
125
|
+
* Example #1: Given I set the following headers:
|
|
126
|
+
* | Content-Type | application/json |
|
|
127
|
+
* | Authorization | Bearer token123 |
|
|
128
|
+
* | Accept | application/json |
|
|
129
|
+
*
|
|
130
|
+
* Example #2: Given we set the following headers:
|
|
131
|
+
* | Content-Type | application/json |
|
|
132
|
+
* | X-Api-Key | abcd-1234 |
|
|
133
|
+
* | Accept-Language | en-US |
|
|
134
|
+
*
|
|
135
|
+
* Example #3: Given I set the following headers:
|
|
136
|
+
* | User-Agent | test-runner |
|
|
137
|
+
* | Cache-Control | no-cache |
|
|
138
|
+
* | If-None-Match | etag-xyz |
|
|
139
|
+
*
|
|
140
|
+
* Example #4: Given we set the following headers:
|
|
141
|
+
* | Authorization | Bearer {{token}} |
|
|
142
|
+
* | Accept | application/json |
|
|
143
|
+
* | X-Request-ID | req-001 |
|
|
144
|
+
*/
|
|
145
|
+
Given(/^(?:I|we) set the following headers:$/, function (table) {
|
|
146
|
+
table.rows().forEach(row => {
|
|
147
|
+
apiHeaders[row[0]] = replacePlaceHolder(row[1]);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Set request body data for POST/PUT requests.
|
|
153
|
+
*
|
|
154
|
+
* Example #1: Given I set the request body to '{"name": "John", "email": "john@example.com"}'
|
|
155
|
+
* Example #2: Given the request body is '{"title": "Test Post", "body": "This is a test"}'
|
|
156
|
+
* Example #3: Given we set the request body to '{"org": "Vardot", "active": true}'
|
|
157
|
+
* Example #4: Given I set the request body to '{"id": 1, "tags": ["qa","api"]}'
|
|
158
|
+
* Example #5: Given the request body is '{"username": "admin", "password": "secret"}'
|
|
159
|
+
* Example #6: Given we set the request body to '{"email": "hello@un.org"}'
|
|
160
|
+
* Example #7: Given I set the request body to '{"status": "published"}'
|
|
161
|
+
*/
|
|
162
|
+
Given(/^(?:I set the request body to|we set the request body to|the request body is) '([^']*)'$/, function (jsonData) {
|
|
163
|
+
try {
|
|
164
|
+
const processedData = replacePlaceHolder(jsonData);
|
|
165
|
+
apiRequestData = JSON.parse(processedData);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
throw friendly(`Invalid JSON in request body: ${error.message}`);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Set request body using a table.
|
|
173
|
+
*
|
|
174
|
+
* Example #1: Given I set the request body with:
|
|
175
|
+
* | name | John Doe |
|
|
176
|
+
* | email | john@example.com |
|
|
177
|
+
* | age | 30 |
|
|
178
|
+
*
|
|
179
|
+
* Example #2: Given we set the request body with:
|
|
180
|
+
* | org | Vardot |
|
|
181
|
+
* | country | UN |
|
|
182
|
+
* | active | true |
|
|
183
|
+
*
|
|
184
|
+
* Example #3: Given I set the request body with:
|
|
185
|
+
* | title | Hello World |
|
|
186
|
+
* | body | First post |
|
|
187
|
+
* | userId | 1 |
|
|
188
|
+
*
|
|
189
|
+
* Example #4: Given we set the request body with:
|
|
190
|
+
* | status | published |
|
|
191
|
+
* | priority | 5 |
|
|
192
|
+
* | featured | false |
|
|
193
|
+
*/
|
|
194
|
+
Given(/^(?:I|we) set the request body with:$/, function (table) {
|
|
195
|
+
apiRequestData = {};
|
|
196
|
+
table.rows().forEach(row => {
|
|
197
|
+
let value = replacePlaceHolder(row[1]);
|
|
198
|
+
|
|
199
|
+
// Try to parse as number or boolean, otherwise keep as string
|
|
200
|
+
if (value === 'true') value = true;
|
|
201
|
+
else if (value === 'false') value = false;
|
|
202
|
+
else if (!isNaN(value) && !isNaN(parseFloat(value))) value = parseFloat(value);
|
|
203
|
+
|
|
204
|
+
apiRequestData[row[0]] = value;
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Sends HTTP request to specific relative URL.
|
|
210
|
+
*
|
|
211
|
+
* Example #1: When I send a GET request to "/users"
|
|
212
|
+
* Example #2: When we send a POST request to "/posts"
|
|
213
|
+
* Example #3: When I send a PUT request to "/users/1"
|
|
214
|
+
* Example #4: When we send a DELETE request to "/posts/1"
|
|
215
|
+
* Example #5: When I send a GET request to "/posts?userId=1"
|
|
216
|
+
* Example #6: When we send a PATCH request to "/users/42"
|
|
217
|
+
* Example #7: When I send a GET request to "/health"
|
|
218
|
+
* Example #8: When we send a HEAD request to "/users"
|
|
219
|
+
* Example #9: When I send a OPTIONS request to "/api"
|
|
220
|
+
* Example #10: When we send a GET request to "/posts/{{postId}}"
|
|
221
|
+
*/
|
|
222
|
+
When(/^(?:I |we )?send a ([A-Z]+) request to "([^"]+)"$/, async function (method, endpoint) {
|
|
223
|
+
const url = baseURL + '/' + prepareUrl(endpoint);
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
apiResponse = await axios({
|
|
227
|
+
method: method,
|
|
228
|
+
url: url,
|
|
229
|
+
headers: apiHeaders,
|
|
230
|
+
data: apiRequestData,
|
|
231
|
+
validateStatus: function (status) {
|
|
232
|
+
return true; // Don't throw on any status code
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
} catch (error) {
|
|
236
|
+
throw friendly(`${method} request failed: ${error.message}`);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Sends HTTP request to specific URL with field values from Table.
|
|
242
|
+
*
|
|
243
|
+
* Example #1: When I send a POST request to "/users" with values:
|
|
244
|
+
* | name | John Doe |
|
|
245
|
+
* | email | john@example.com |
|
|
246
|
+
* | age | 30 |
|
|
247
|
+
*
|
|
248
|
+
* Example #2: When we send a POST request to "/users" with values:
|
|
249
|
+
* | name | Alice |
|
|
250
|
+
* | email | alice@un.org |
|
|
251
|
+
* | role | editor |
|
|
252
|
+
*
|
|
253
|
+
* Example #3: When I send a PUT request to "/users/1" with values:
|
|
254
|
+
* | name | Updated Name |
|
|
255
|
+
* | active | true |
|
|
256
|
+
*
|
|
257
|
+
* Example #4: When we send a POST request to "/posts" with values:
|
|
258
|
+
* | title | Welcome |
|
|
259
|
+
* | body | Hello Vardot|
|
|
260
|
+
* | userId | 1 |
|
|
261
|
+
*/
|
|
262
|
+
When(/^(?:I |we )?send a ([A-Z]+) request to "([^"]+)" with values:$/, async function (method, endpoint, table) {
|
|
263
|
+
const url = baseURL + '/' + prepareUrl(endpoint);
|
|
264
|
+
const fields = {};
|
|
265
|
+
|
|
266
|
+
const rowsHash = table.rowsHash();
|
|
267
|
+
Object.keys(rowsHash).forEach(key => {
|
|
268
|
+
fields[key] = replacePlaceHolder(rowsHash[key]);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
apiResponse = await axios({
|
|
273
|
+
method: method,
|
|
274
|
+
url: url,
|
|
275
|
+
headers: { ...apiHeaders, 'Content-Type': 'application/json' },
|
|
276
|
+
data: JSON.stringify(fields),
|
|
277
|
+
validateStatus: function (status) {
|
|
278
|
+
return true; // Don't throw on any status code
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
} catch (error) {
|
|
282
|
+
throw friendly(`${method} request with values failed: ${error.message}`);
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Sends HTTP request to specific URL with raw body from PyString.
|
|
288
|
+
*
|
|
289
|
+
* Example #1: When I send a POST request to "/users" with body:
|
|
290
|
+
* """
|
|
291
|
+
* {
|
|
292
|
+
* "name": "John Doe",
|
|
293
|
+
* "email": "john@example.com"
|
|
294
|
+
* }
|
|
295
|
+
* """
|
|
296
|
+
*
|
|
297
|
+
* Example #2: When we send a POST request to "/posts" with body:
|
|
298
|
+
* """
|
|
299
|
+
* {
|
|
300
|
+
* "title": "Hello",
|
|
301
|
+
* "body": "World",
|
|
302
|
+
* "userId": 1
|
|
303
|
+
* }
|
|
304
|
+
* """
|
|
305
|
+
*
|
|
306
|
+
* Example #3: When I send a PUT request to "/users/1" with body:
|
|
307
|
+
* """
|
|
308
|
+
* {
|
|
309
|
+
* "name": "Updated",
|
|
310
|
+
* "org": "Vardot"
|
|
311
|
+
* }
|
|
312
|
+
* """
|
|
313
|
+
*
|
|
314
|
+
* Example #4: When we send a POST request to "/contacts" with body:
|
|
315
|
+
* """
|
|
316
|
+
* {
|
|
317
|
+
* "email": "info@un.org",
|
|
318
|
+
* "subscribe": true
|
|
319
|
+
* }
|
|
320
|
+
* """
|
|
321
|
+
*/
|
|
322
|
+
When(/^(?:I |we )?send a ([A-Z]+) request to "([^"]+)" with body:$/, async function (method, endpoint, docString) {
|
|
323
|
+
const url = baseURL + '/' + prepareUrl(endpoint);
|
|
324
|
+
const body = replacePlaceHolder(docString.trim());
|
|
325
|
+
|
|
326
|
+
// Parse JSON if the body looks like JSON, otherwise send as string
|
|
327
|
+
let requestData = body;
|
|
328
|
+
try {
|
|
329
|
+
// Check if it's JSON by trying to parse it
|
|
330
|
+
requestData = JSON.parse(body);
|
|
331
|
+
} catch (error) {
|
|
332
|
+
// If parsing fails, use the raw string
|
|
333
|
+
requestData = body;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Set appropriate Content-Type header if not already set
|
|
337
|
+
const headers = { ...apiHeaders };
|
|
338
|
+
if (!headers['Content-Type'] && !headers['content-type']) {
|
|
339
|
+
if (typeof requestData === 'object') {
|
|
340
|
+
headers['Content-Type'] = 'application/json';
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
try {
|
|
345
|
+
apiResponse = await axios({
|
|
346
|
+
method: method,
|
|
347
|
+
url: url,
|
|
348
|
+
headers: headers,
|
|
349
|
+
data: requestData,
|
|
350
|
+
validateStatus: function (status) {
|
|
351
|
+
return true; // Don't throw on any status code
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
} catch (error) {
|
|
355
|
+
throw friendly(`${method} request with body failed: ${error.message}`);
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Sends HTTP request to specific URL with form data.
|
|
361
|
+
*
|
|
362
|
+
* Example #1: When I send a POST request to "/login" with form data:
|
|
363
|
+
* """
|
|
364
|
+
* username=admin
|
|
365
|
+
* password=secret
|
|
366
|
+
* remember=true
|
|
367
|
+
* """
|
|
368
|
+
*
|
|
369
|
+
* Example #2: When we send a POST request to "/subscribe" with form data:
|
|
370
|
+
* """
|
|
371
|
+
* email=hello@un.org
|
|
372
|
+
* list=newsletter
|
|
373
|
+
* """
|
|
374
|
+
*
|
|
375
|
+
* Example #3: When I send a POST request to "/contact" with form data:
|
|
376
|
+
* """
|
|
377
|
+
* name=Alice
|
|
378
|
+
* org=Vardot
|
|
379
|
+
* message=Hello
|
|
380
|
+
* """
|
|
381
|
+
*
|
|
382
|
+
* Example #4: When we send a PUT request to "/profile" with form data:
|
|
383
|
+
* """
|
|
384
|
+
* name=Updated
|
|
385
|
+
* country=UN
|
|
386
|
+
* """
|
|
387
|
+
*/
|
|
388
|
+
When(/^(?:I |we )?send a ([A-Z]+) request to "([^"]+)" with form data:$/, async function (method, endpoint, docString) {
|
|
389
|
+
const url = baseURL + '/' + prepareUrl(endpoint);
|
|
390
|
+
const body = replacePlaceHolder(docString.trim());
|
|
391
|
+
|
|
392
|
+
// Parse form data
|
|
393
|
+
const fields = {};
|
|
394
|
+
body.split('\n').forEach(line => {
|
|
395
|
+
const [key, value] = line.split('=');
|
|
396
|
+
if (key && value !== undefined) {
|
|
397
|
+
fields[key.trim()] = value.trim();
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
// Convert to URL encoded format
|
|
402
|
+
const formData = Object.keys(fields)
|
|
403
|
+
.map(key => encodeURIComponent(key) + '=' + encodeURIComponent(fields[key]))
|
|
404
|
+
.join('&');
|
|
405
|
+
|
|
406
|
+
try {
|
|
407
|
+
apiResponse = await axios({
|
|
408
|
+
method: method,
|
|
409
|
+
url: url,
|
|
410
|
+
headers: {
|
|
411
|
+
...apiHeaders,
|
|
412
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
413
|
+
},
|
|
414
|
+
data: formData,
|
|
415
|
+
validateStatus: function (status) {
|
|
416
|
+
return true; // Don't throw on any status code
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
} catch (error) {
|
|
420
|
+
throw friendly(`${method} request with form data failed: ${error.message}`);
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Checks that API response has specific status code.
|
|
426
|
+
*
|
|
427
|
+
* Example #1: Then the API response code should be 200
|
|
428
|
+
* Example #2: Then API response code should be 404
|
|
429
|
+
* Example #3: Then the API response code should be 201
|
|
430
|
+
* Example #4: Then API response code should be 204
|
|
431
|
+
* Example #5: Then the API response code should be 400
|
|
432
|
+
* Example #6: Then API response code should be 401
|
|
433
|
+
* Example #7: Then the API response code should be 403
|
|
434
|
+
* Example #8: Then API response code should be 500
|
|
435
|
+
* Example #9: Then the API response code should be 301
|
|
436
|
+
* Example #10: Then API response code should be 302
|
|
437
|
+
*/
|
|
438
|
+
Then(/^(?:the )?API response code should be (\d+)$/, function (expectedCode) {
|
|
439
|
+
if (!apiResponse) {
|
|
440
|
+
throw friendly('No API response available. Make sure to send a request first.');
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const expected = parseInt(expectedCode);
|
|
444
|
+
const actual = parseInt(apiResponse.status);
|
|
445
|
+
assert.strictEqual(actual, expected,
|
|
446
|
+
`Expected status code ${expected}, but got ${actual}`);
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Checks that API response body contains specific text.
|
|
451
|
+
*
|
|
452
|
+
* Example #1: Then the API response should contain "success"
|
|
453
|
+
* Example #2: Then API response should contain "John Doe"
|
|
454
|
+
* Example #3: Then the API response should contain "Vardot"
|
|
455
|
+
* Example #4: Then API response should contain "UN"
|
|
456
|
+
* Example #5: Then the API response should contain "published"
|
|
457
|
+
* Example #6: Then API response should contain "id"
|
|
458
|
+
* Example #7: Then the API response should contain "email"
|
|
459
|
+
* Example #8: Then API response should contain "hello@un.org"
|
|
460
|
+
*/
|
|
461
|
+
Then(/^(?:the )?API response should contain "([^"]*)"$/, function (expectedText) {
|
|
462
|
+
if (!apiResponse) {
|
|
463
|
+
throw friendly('No API response available. Make sure to send a request first.');
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const responseBody = typeof apiResponse.data === 'string' ?
|
|
467
|
+
apiResponse.data : JSON.stringify(apiResponse.data);
|
|
468
|
+
|
|
469
|
+
const expectedRegexp = new RegExp(expectedText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i');
|
|
470
|
+
assert(expectedRegexp.test(responseBody),
|
|
471
|
+
`Response should contain "${expectedText}", but got: ${responseBody}`);
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Checks that API response body doesn't contain specific text.
|
|
476
|
+
*
|
|
477
|
+
* Example #1: Then the API response should not contain "error"
|
|
478
|
+
* Example #2: Then API response should not contain "failed"
|
|
479
|
+
* Example #3: Then the API response should not contain "forbidden"
|
|
480
|
+
* Example #4: Then API response should not contain "unauthorized"
|
|
481
|
+
* Example #5: Then the API response should not contain "password"
|
|
482
|
+
* Example #6: Then API response should not contain "secret"
|
|
483
|
+
* Example #7: Then the API response should not contain "exception"
|
|
484
|
+
* Example #8: Then API response should not contain "stack trace"
|
|
485
|
+
*/
|
|
486
|
+
Then(/^(?:the )?API response should not contain "([^"]*)"$/, function (unexpectedText) {
|
|
487
|
+
if (!apiResponse) {
|
|
488
|
+
throw friendly('No API response available. Make sure to send a request first.');
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const responseBody = typeof apiResponse.data === 'string' ?
|
|
492
|
+
apiResponse.data : JSON.stringify(apiResponse.data);
|
|
493
|
+
|
|
494
|
+
const expectedRegexp = new RegExp(unexpectedText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
|
495
|
+
assert(!expectedRegexp.test(responseBody),
|
|
496
|
+
`Response should not contain "${unexpectedText}", but it does. Response: ${responseBody}`);
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Checks that API response body contains JSON from PyString.
|
|
501
|
+
*
|
|
502
|
+
* Example #1: Then the API response should contain json:
|
|
503
|
+
* """
|
|
504
|
+
* {
|
|
505
|
+
* "name": "John Doe",
|
|
506
|
+
* "email": "john@example.com"
|
|
507
|
+
* }
|
|
508
|
+
* """
|
|
509
|
+
*
|
|
510
|
+
* Example #2: Then API response should contain json:
|
|
511
|
+
* """
|
|
512
|
+
* {
|
|
513
|
+
* "org": "Vardot",
|
|
514
|
+
* "active": true
|
|
515
|
+
* }
|
|
516
|
+
* """
|
|
517
|
+
*
|
|
518
|
+
* Example #3: Then the API response should contain json:
|
|
519
|
+
* """
|
|
520
|
+
* {
|
|
521
|
+
* "id": 1,
|
|
522
|
+
* "title": "Hello"
|
|
523
|
+
* }
|
|
524
|
+
* """
|
|
525
|
+
*
|
|
526
|
+
* Example #4: Then API response should contain json:
|
|
527
|
+
* """
|
|
528
|
+
* {
|
|
529
|
+
* "email": "hello@un.org"
|
|
530
|
+
* }
|
|
531
|
+
* """
|
|
532
|
+
*/
|
|
533
|
+
Then(/^(?:the )?API response should contain json:$/, function (docString) {
|
|
534
|
+
if (!apiResponse) {
|
|
535
|
+
throw friendly('No API response available. Make sure to send a request first.');
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
let expectedJson;
|
|
539
|
+
let actualJson;
|
|
540
|
+
|
|
541
|
+
try {
|
|
542
|
+
expectedJson = JSON.parse(replacePlaceHolder(docString.trim()));
|
|
543
|
+
} catch (error) {
|
|
544
|
+
throw friendly(`Cannot convert expected JSON: ${docString}`);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
try {
|
|
548
|
+
actualJson = typeof apiResponse.data === 'string' ?
|
|
549
|
+
JSON.parse(apiResponse.data) : apiResponse.data;
|
|
550
|
+
} catch (error) {
|
|
551
|
+
throw friendly(`Cannot convert actual response to JSON: ${apiResponse.data}`);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Check that expected properties exist in actual response and match values
|
|
555
|
+
Object.keys(expectedJson).forEach(key => {
|
|
556
|
+
assert(actualJson.hasOwnProperty(key),
|
|
557
|
+
`Expected property "${key}" not found in response. Actual response: ${JSON.stringify(actualJson, null, 2)}`);
|
|
558
|
+
assert.deepStrictEqual(actualJson[key], expectedJson[key],
|
|
559
|
+
`Property "${key}" mismatch. Expected: ${expectedJson[key]}, Actual: ${actualJson[key]}`);
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Alternative syntax for JSON property verification.
|
|
565
|
+
*
|
|
566
|
+
* Example #1: Then the JSON response should have "name" equal to "John Doe"
|
|
567
|
+
* Example #2: Then the API response should have "id" equal to 1
|
|
568
|
+
* Example #3: Then the JSON property "active" should be true
|
|
569
|
+
* Example #4: Then the JSON response should have "org" equal to "Vardot"
|
|
570
|
+
* Example #5: Then the API response should have "email" equal to "hello@un.org"
|
|
571
|
+
* Example #6: Then the JSON property "count" should be 42
|
|
572
|
+
* Example #7: Then the JSON response should have "user.role" equal to "admin"
|
|
573
|
+
* Example #8: Then the JSON property "published" should be false
|
|
574
|
+
*/
|
|
575
|
+
Then(/^(?:the JSON response should have|the API response should have|the JSON property) "([^"]*)" (?:equal to|should be) (.+)$/, function (propertyPath, expectedValue) {
|
|
576
|
+
if (!apiResponse) {
|
|
577
|
+
throw friendly('No API response available. Make sure to send a request first.');
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Replace placeholders in expected value first
|
|
581
|
+
const processedExpectedValue = replacePlaceHolder(expectedValue);
|
|
582
|
+
|
|
583
|
+
// Parse expected value (remove quotes if string, parse numbers/booleans)
|
|
584
|
+
let parsedExpectedValue = processedExpectedValue;
|
|
585
|
+
if (processedExpectedValue.startsWith('"') && processedExpectedValue.endsWith('"')) {
|
|
586
|
+
parsedExpectedValue = processedExpectedValue.slice(1, -1);
|
|
587
|
+
} else if (processedExpectedValue === 'true') {
|
|
588
|
+
parsedExpectedValue = true;
|
|
589
|
+
} else if (processedExpectedValue === 'false') {
|
|
590
|
+
parsedExpectedValue = false;
|
|
591
|
+
} else if (!isNaN(processedExpectedValue) && !isNaN(parseFloat(processedExpectedValue))) {
|
|
592
|
+
parsedExpectedValue = parseFloat(processedExpectedValue);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Get the actual value from response
|
|
596
|
+
const actualValue = getNestedProperty(apiResponse.data, propertyPath);
|
|
597
|
+
|
|
598
|
+
assert.strictEqual(actualValue, parsedExpectedValue,
|
|
599
|
+
`Expected "${propertyPath}" to be ${parsedExpectedValue}, but got ${actualValue}`);
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Verify a JSON property exists.
|
|
604
|
+
*
|
|
605
|
+
* Example #1: Then the JSON response should have property "id"
|
|
606
|
+
* Example #2: Then the API response should contain property "user.email"
|
|
607
|
+
* Example #3: Then the JSON response should have property "title"
|
|
608
|
+
* Example #4: Then the API response should contain property "data"
|
|
609
|
+
* Example #5: Then the JSON response should have property "user.org"
|
|
610
|
+
* Example #6: Then the API response should contain property "meta.total"
|
|
611
|
+
* Example #7: Then the JSON response should have property "createdAt"
|
|
612
|
+
* Example #8: Then the API response should contain property "links.self"
|
|
613
|
+
*/
|
|
614
|
+
Then(/^(?:the JSON response should have property|the API response should contain property) "([^"]*)"$/, function (propertyPath) {
|
|
615
|
+
if (!apiResponse) {
|
|
616
|
+
throw friendly('No API response available. Make sure to send a request first.');
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
const actualValue = getNestedProperty(apiResponse.data, propertyPath);
|
|
620
|
+
|
|
621
|
+
assert(actualValue !== undefined,
|
|
622
|
+
`Expected property "${propertyPath}" to exist in response, but it doesn't`);
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Verify a JSON property does not exist.
|
|
627
|
+
*
|
|
628
|
+
* Example #1: Then the JSON response should not have property "password"
|
|
629
|
+
* Example #2: Then the API response should not contain property "secret"
|
|
630
|
+
* Example #3: Then the JSON response should not have property "token"
|
|
631
|
+
* Example #4: Then the API response should not contain property "apiKey"
|
|
632
|
+
* Example #5: Then the JSON response should not have property "user.password"
|
|
633
|
+
* Example #6: Then the API response should not contain property "internal"
|
|
634
|
+
* Example #7: Then the JSON response should not have property "debug"
|
|
635
|
+
* Example #8: Then the API response should not contain property "stack"
|
|
636
|
+
*/
|
|
637
|
+
Then(/^(?:the JSON response should not have property|the API response should not contain property) "([^"]*)"$/, function (propertyPath) {
|
|
638
|
+
if (!apiResponse) {
|
|
639
|
+
throw friendly('No API response available. Make sure to send a request first.');
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
const actualValue = getNestedProperty(apiResponse.data, propertyPath);
|
|
643
|
+
|
|
644
|
+
assert(actualValue === undefined,
|
|
645
|
+
`Expected property "${propertyPath}" not to exist in response, but it does`);
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* Verify the response is valid JSON.
|
|
650
|
+
*
|
|
651
|
+
* Two equivalent phrasings.
|
|
652
|
+
*
|
|
653
|
+
* Example #1: Then the response should be valid JSON
|
|
654
|
+
* Example #2: Then the API response should be valid JSON
|
|
655
|
+
* Example #3: And the response should be valid JSON
|
|
656
|
+
* Example #4: When I send a GET request to "/api/users"
|
|
657
|
+
* Then the API response should be valid JSON
|
|
658
|
+
* Example #5: When I send a POST request to "/api/orders"
|
|
659
|
+
* Then the response should be valid JSON
|
|
660
|
+
*/
|
|
661
|
+
Then(/^(?:the response should be valid JSON|the API response should be valid JSON)$/, function () {
|
|
662
|
+
if (!apiResponse) {
|
|
663
|
+
throw friendly('No API response available. Make sure to send a request first.');
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
assert(typeof apiResponse.data === 'object' && apiResponse.data !== null,
|
|
667
|
+
'Response should be valid JSON');
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Verify response header value.
|
|
672
|
+
*
|
|
673
|
+
* Example #1: Then the response header "Content-Type" should be "application/json"
|
|
674
|
+
* Example #2: Then the header "Cache-Control" should contain "no-cache"
|
|
675
|
+
* Example #3: Then the response header "Content-Type" should contain "charset=utf-8"
|
|
676
|
+
* Example #4: Then the header "X-Powered-By" should be "Vardot"
|
|
677
|
+
* Example #5: Then the response header "ETag" should contain "etag"
|
|
678
|
+
* Example #6: Then the header "Location" should contain "/users/1"
|
|
679
|
+
* Example #7: Then the response header "Access-Control-Allow-Origin" should be "*"
|
|
680
|
+
* Example #8: Then the header "Content-Length" should contain "0"
|
|
681
|
+
*/
|
|
682
|
+
Then(/^(?:the response header|the header) "([^"]*)" should (?:be|contain) "([^"]*)"$/, function (headerName, expectedValue) {
|
|
683
|
+
if (!apiResponse) {
|
|
684
|
+
throw friendly('No API response available. Make sure to send a request first.');
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const actualValue = apiResponse.headers[headerName.toLowerCase()];
|
|
688
|
+
|
|
689
|
+
assert(actualValue && actualValue.includes(expectedValue),
|
|
690
|
+
`Expected header "${headerName}" to contain "${expectedValue}", but got "${actualValue}"`);
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Prints the most recent API response body to stdout for debugging.
|
|
695
|
+
*
|
|
696
|
+
* Strip before merging — this is a developer aid only.
|
|
697
|
+
*
|
|
698
|
+
* Example #1: Then print API response
|
|
699
|
+
* Example #2: When I send a GET request to "/api/users"
|
|
700
|
+
* Then print API response
|
|
701
|
+
* Example #3: And print API response
|
|
702
|
+
* Example #4: When I send a POST request to "/api/login"
|
|
703
|
+
* Then the API response code should be 200
|
|
704
|
+
* And print API response
|
|
705
|
+
* Example #5: When I send a DELETE request to "/api/orders/1"
|
|
706
|
+
* Then print API response
|
|
707
|
+
*/
|
|
708
|
+
Then(/^print API response$/, function () {
|
|
709
|
+
if (!apiResponse) {
|
|
710
|
+
console.log('No API response available');
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
console.log(`${apiResponse.config.method.toUpperCase()} ${apiResponse.config.url} => ${apiResponse.status}:`);
|
|
715
|
+
console.log(typeof apiResponse.data === 'string' ? apiResponse.data : JSON.stringify(apiResponse.data, null, 2));
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* Sets placeholder for replacement in URLs, requests, and responses.
|
|
720
|
+
*
|
|
721
|
+
* Example #1: Given I set placeholder "{{userId}}" to "123"
|
|
722
|
+
* Example #2: Given we set placeholder "{{token}}" to "abcd-1234"
|
|
723
|
+
* Example #3: Given I set placeholder "{{postId}}" to "42"
|
|
724
|
+
* Example #4: Given we set placeholder "{{org}}" to "Vardot"
|
|
725
|
+
* Example #5: Given I set placeholder "{{email}}" to "hello@un.org"
|
|
726
|
+
* Example #6: Given we set placeholder "{{apiKey}}" to "secret-xyz"
|
|
727
|
+
* Example #7: Given I set placeholder "{{version}}" to "v2"
|
|
728
|
+
* Example #8: Given we set placeholder "{{locale}}" to "en-US"
|
|
729
|
+
*/
|
|
730
|
+
Given(/^(?:I|we) set placeholder "([^"]*)" to "([^"]*)"$/, function (placeholder, value) {
|
|
731
|
+
placeHolders[placeholder] = value;
|
|
732
|
+
});
|