@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,228 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { friendly } = require('./varbase-e2e');
|
|
4
|
+
|
|
5
|
+
// Iframe / frame steps backed by Playwright's `frameLocator` API.
|
|
6
|
+
//
|
|
7
|
+
// Frames are addressed by CSS selector. Once switched, frame-scoped
|
|
8
|
+
// interaction steps target the chosen frame instead of the main document.
|
|
9
|
+
// Switch back with "I switch to the root document".
|
|
10
|
+
|
|
11
|
+
const { When, Then } = require('@cucumber/cucumber');
|
|
12
|
+
const assert = require('assert');
|
|
13
|
+
|
|
14
|
+
function frameRoot(world) {
|
|
15
|
+
return world.frame || world.page;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function explain(action, detail, e) {
|
|
19
|
+
return friendly({
|
|
20
|
+
action,
|
|
21
|
+
cause: e,
|
|
22
|
+
hint: `wait for the iframe to load first ("When I wait for \\"iframe\\" to appear"). Detail: ${detail}.`,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Switch the active scope to an iframe addressed by CSS selector.
|
|
28
|
+
*
|
|
29
|
+
* Example #1: When I switch to iframe "iframe.payment"
|
|
30
|
+
* Example #2: When I switch to the iframe "#stripe-frame"
|
|
31
|
+
* Example #3: And we switch to iframe "iframe[name=widget]"
|
|
32
|
+
* Example #4: When I switch to iframe ".captcha-frame"
|
|
33
|
+
* Example #5: When I switch to the iframe "iframe[src*=embed]"
|
|
34
|
+
*
|
|
35
|
+
*/
|
|
36
|
+
When(/^(I |we )*switch to (the )?iframe "([^"]*)"$/, async function (pronoun, theCase, sel) {
|
|
37
|
+
try {
|
|
38
|
+
if (!this._rootPage) this._rootPage = this.page;
|
|
39
|
+
this.frame = this.page.frameLocator(sel);
|
|
40
|
+
} catch (e) {
|
|
41
|
+
throw explain(`switch to iframe "${sel}"`, `selector tried: ${sel}`, e);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Switch the active scope to an iframe by Playwright frame-locator string.
|
|
47
|
+
*
|
|
48
|
+
* Example #1: When I switch to iframe with locator "iframe.payment"
|
|
49
|
+
* Example #2: When I switch to iframe with locator "#stripe-frame"
|
|
50
|
+
* Example #3: And I switch to iframe with locator "iframe[name=widget]"
|
|
51
|
+
* Example #4: When I switch to iframe with locator ".captcha-frame"
|
|
52
|
+
* Example #5: When I switch to iframe with locator "iframe[src*=embed]"
|
|
53
|
+
*
|
|
54
|
+
*/
|
|
55
|
+
When(/^(I |we )*switch to iframe with locator "([^"]*)"$/, async function (pronoun, sel) {
|
|
56
|
+
try {
|
|
57
|
+
if (!this._rootPage) this._rootPage = this.page;
|
|
58
|
+
this.frame = this.page.frameLocator(sel);
|
|
59
|
+
} catch (e) {
|
|
60
|
+
throw explain(`switch to iframe with locator "${sel}"`, `locator: ${sel}`, e);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Return the active scope to the top document (clear the iframe scope).
|
|
66
|
+
*
|
|
67
|
+
* Example #1: When I switch to the root document
|
|
68
|
+
* Example #2: When I switch to iframe ".captcha"
|
|
69
|
+
* And I click "I am not a robot" inside the iframe
|
|
70
|
+
* And I switch to the root document
|
|
71
|
+
* Example #3: And we switch to the root document
|
|
72
|
+
* Example #4: When I switch to iframe "#payment"
|
|
73
|
+
* And I press "Pay"
|
|
74
|
+
* And I switch to the root document
|
|
75
|
+
* Then I should see "Payment received"
|
|
76
|
+
* Example #5: When I switch to the root document
|
|
77
|
+
* And I press "Refresh"
|
|
78
|
+
*
|
|
79
|
+
*/
|
|
80
|
+
When(/^(I |we )*switch to the root document$/, async function () {
|
|
81
|
+
this.frame = null;
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Switch the active scope to an iframe by its `title` attribute.
|
|
86
|
+
*
|
|
87
|
+
* Example #1: When I switch to the iframe with title "Payment"
|
|
88
|
+
* Example #2: When I switch to the iframe with title "Captcha"
|
|
89
|
+
* Example #3: And we switch to the iframe with title "Widget"
|
|
90
|
+
* Example #4: When I switch to the iframe with title "Embedded video"
|
|
91
|
+
* Example #5: When I switch to the iframe with title "Stripe checkout"
|
|
92
|
+
*
|
|
93
|
+
*/
|
|
94
|
+
When(/^(I |we )*switch to the iframe with title "([^"]*)"$/, async function (pronoun, title) {
|
|
95
|
+
try {
|
|
96
|
+
if (!this._rootPage) this._rootPage = this.page;
|
|
97
|
+
this.frame = this.page.frameLocator(`iframe[title="${title}"]`);
|
|
98
|
+
} catch (e) {
|
|
99
|
+
throw explain(`switch to iframe by title "${title}"`, `selector: iframe[title="${title}"]`, e);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Switch the active scope to an iframe by its `name` attribute.
|
|
105
|
+
*
|
|
106
|
+
* Example #1: When I switch to the iframe with name "payment"
|
|
107
|
+
* Example #2: When I switch to the iframe with name "captcha"
|
|
108
|
+
* Example #3: And we switch to the iframe with name "widget"
|
|
109
|
+
* Example #4: When I switch to the iframe with name "embed"
|
|
110
|
+
* Example #5: When I switch to the iframe with name "stripe"
|
|
111
|
+
*
|
|
112
|
+
*/
|
|
113
|
+
When(/^(I |we )*switch to the iframe with name "([^"]*)"$/, async function (pronoun, name) {
|
|
114
|
+
try {
|
|
115
|
+
if (!this._rootPage) this._rootPage = this.page;
|
|
116
|
+
this.frame = this.page.frameLocator(`iframe[name="${name}"]`);
|
|
117
|
+
} catch (e) {
|
|
118
|
+
throw explain(`switch to iframe by name "${name}"`, `selector: iframe[name="${name}"]`, e);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
// Frame-scoped click + fill + assertions
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Click visible text inside the active iframe.
|
|
128
|
+
*
|
|
129
|
+
* Example #1: When I click "Confirm" inside the iframe
|
|
130
|
+
* Example #2: When I click "Cancel" inside the iframe
|
|
131
|
+
* Example #3: And we click "Continue" inside the iframe
|
|
132
|
+
* Example #4: When I click "OK" inside the iframe
|
|
133
|
+
* Example #5: When I click "Close" inside the iframe
|
|
134
|
+
*
|
|
135
|
+
*/
|
|
136
|
+
When(/^(I |we )*click "([^"]*)" inside the iframe$/, async function (pronoun, text) {
|
|
137
|
+
try {
|
|
138
|
+
const root = frameRoot(this);
|
|
139
|
+
if (!root.locator) throw friendly('No active iframe. Switch first.');
|
|
140
|
+
await root.locator(`text="${text}"`).first().click();
|
|
141
|
+
} catch (e) {
|
|
142
|
+
throw friendly({
|
|
143
|
+
action: `click "${text}" inside the iframe`,
|
|
144
|
+
cause: e,
|
|
145
|
+
hint: `switch to the iframe first ("When I switch to iframe '<selector>'"), then check the visible text matches exactly.`,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Click an attribute-addressed element inside the active iframe.
|
|
152
|
+
*
|
|
153
|
+
* Tries id, class, name, and data-testid in that order.
|
|
154
|
+
*
|
|
155
|
+
* Example #1: When I click "submit-btn" by attr inside the iframe
|
|
156
|
+
* Example #2: When I click "cancel" by attr inside the iframe
|
|
157
|
+
* Example #3: And we click "continue" by attr inside the iframe
|
|
158
|
+
* Example #4: When I click "close" by attr inside the iframe
|
|
159
|
+
* Example #5: When I click "primary-cta" by attr inside the iframe
|
|
160
|
+
*
|
|
161
|
+
*/
|
|
162
|
+
When(/^(I |we )*click "([^"]*)" by attr inside the iframe$/, async function (pronoun, attrValue) {
|
|
163
|
+
try {
|
|
164
|
+
const root = frameRoot(this);
|
|
165
|
+
await root.locator(`#${attrValue}, .${attrValue}, [name="${attrValue}"], [data-testid="${attrValue}"]`).first().click();
|
|
166
|
+
} catch (e) {
|
|
167
|
+
throw friendly({
|
|
168
|
+
action: `click "${attrValue}" by attribute inside the iframe`,
|
|
169
|
+
cause: e,
|
|
170
|
+
hint: `switch to the iframe first; checked id, class, [name], [data-testid] — none matched.`,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Fill a field inside the active iframe by `[name]` or id.
|
|
177
|
+
*
|
|
178
|
+
* Example #1: When I fill in "card_number" with "4242 4242 4242 4242" inside the iframe
|
|
179
|
+
* Example #2: When I fill in "cvv" with "123" inside the iframe
|
|
180
|
+
* Example #3: And we fill in "exp_date" with "12/30" inside the iframe
|
|
181
|
+
* Example #4: When I fill in "email" with "alice@example.com" inside the iframe
|
|
182
|
+
* Example #5: When I fill in "name" with "Alice" inside the iframe
|
|
183
|
+
*
|
|
184
|
+
*/
|
|
185
|
+
When(/^(I |we )*fill in "([^"]*)" with "([^"]*)" inside the iframe$/, async function (pronoun, field, value) {
|
|
186
|
+
try {
|
|
187
|
+
const root = frameRoot(this);
|
|
188
|
+
await root.locator(`[name="${field}"], #${field}`).first().fill(value);
|
|
189
|
+
} catch (e) {
|
|
190
|
+
throw friendly({
|
|
191
|
+
action: `fill "${field}" inside the iframe`,
|
|
192
|
+
cause: e,
|
|
193
|
+
hint: `switch to the iframe first; checked [name] and id — neither matched.`,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Assert visible text exists inside the active iframe.
|
|
200
|
+
*
|
|
201
|
+
* Example #1: Then I should see "Payment received" inside the iframe
|
|
202
|
+
* Example #2: Then I should see "Welcome" inside the iframe
|
|
203
|
+
* Example #3: And I should see "Confirmed" inside the iframe
|
|
204
|
+
* Example #4: Then I should see "Email verified" inside the iframe
|
|
205
|
+
* Example #5: Then I should see "Subscribed" inside the iframe
|
|
206
|
+
*
|
|
207
|
+
*/
|
|
208
|
+
Then(/^I should see "([^"]*)" inside the iframe$/, async function (text) {
|
|
209
|
+
const root = frameRoot(this);
|
|
210
|
+
const count = await root.locator(`text="${text}"`).count();
|
|
211
|
+
assert.ok(count > 0, `Expected to see "${text}" inside the iframe.`);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Assert visible text does NOT exist inside the active iframe.
|
|
216
|
+
*
|
|
217
|
+
* Example #1: Then I should not see "Error" inside the iframe
|
|
218
|
+
* Example #2: Then I should not see "Declined" inside the iframe
|
|
219
|
+
* Example #3: And I should not see "Loading" inside the iframe
|
|
220
|
+
* Example #4: Then I should not see "Pending" inside the iframe
|
|
221
|
+
* Example #5: Then I should not see "Try again" inside the iframe
|
|
222
|
+
*
|
|
223
|
+
*/
|
|
224
|
+
Then(/^I should not see "([^"]*)" inside the iframe$/, async function (text) {
|
|
225
|
+
const root = frameRoot(this);
|
|
226
|
+
const count = await root.locator(`text="${text}"`).count();
|
|
227
|
+
assert.strictEqual(count, 0, `Expected NOT to see "${text}" inside the iframe.`);
|
|
228
|
+
});
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { friendly } = require('./varbase-e2e');
|
|
4
|
+
|
|
5
|
+
// Pointer input steps not covered by the core element/click steps:
|
|
6
|
+
// hover variants, drag-and-drop, double-click, right-click, viewport size.
|
|
7
|
+
|
|
8
|
+
const { When } = require('@cucumber/cucumber');
|
|
9
|
+
|
|
10
|
+
function explainPtr(action, sel, e) {
|
|
11
|
+
return friendly({
|
|
12
|
+
action: `${action} "${sel}"`,
|
|
13
|
+
cause: e,
|
|
14
|
+
hint: `check the element exists on the page, is visible, and is not covered by an overlay.`,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Hover (variants)
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Hover the mouse pointer over a CSS-addressed element.
|
|
24
|
+
*
|
|
25
|
+
* Example #1: When I hover over "#nav-products"
|
|
26
|
+
* Example #2: When I hover over ".tooltip-trigger"
|
|
27
|
+
* Example #3: And we hover over "[data-testid=user-menu]"
|
|
28
|
+
* Example #4: When I hover over "button.help"
|
|
29
|
+
* Example #5: When I hover over ".chart-bar:nth-child(3)"
|
|
30
|
+
*
|
|
31
|
+
*/
|
|
32
|
+
When(/^(I |we )*hover over "([^"]*)"$/, async function (pronoun, sel) {
|
|
33
|
+
try { await this.page.locator(sel).first().hover(); }
|
|
34
|
+
catch (e) { throw explainPtr('hover over', sel, e); }
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Move the mouse pointer over a CSS-addressed element (alias for hover).
|
|
39
|
+
*
|
|
40
|
+
* Example #1: When I move the pointer to "#cta"
|
|
41
|
+
* Example #2: When we move the pointer to ".dropdown-trigger"
|
|
42
|
+
* Example #3: And I move the pointer to "[data-testid=avatar]"
|
|
43
|
+
* Example #4: When I move the pointer to "img.gallery-thumb"
|
|
44
|
+
* Example #5: When I move the pointer to ".legend-item"
|
|
45
|
+
*
|
|
46
|
+
*/
|
|
47
|
+
When(/^(I |we )*move the pointer to "([^"]*)"$/, async function (pronoun, sel) {
|
|
48
|
+
try { await this.page.locator(sel).first().hover(); }
|
|
49
|
+
catch (e) { throw explainPtr('move pointer to', sel, e); }
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Click variants
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Double-click on a CSS-addressed element.
|
|
58
|
+
*
|
|
59
|
+
* Example #1: When I double-click on "#row-1"
|
|
60
|
+
* Example #2: When I double click on ".file-tile"
|
|
61
|
+
* Example #3: And we double-click on "[data-testid=cell-A1]"
|
|
62
|
+
* Example #4: When I double-click on "img.editable"
|
|
63
|
+
* Example #5: When I double click on "td.editable-cell"
|
|
64
|
+
*
|
|
65
|
+
*/
|
|
66
|
+
When(/^(I |we )*double[- ]click on "([^"]*)"$/, async function (pronoun, sel) {
|
|
67
|
+
try { await this.page.locator(sel).first().dblclick(); }
|
|
68
|
+
catch (e) { throw explainPtr('double-click', sel, e); }
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Right-click (context menu) on a CSS-addressed element.
|
|
73
|
+
*
|
|
74
|
+
* Example #1: When I right-click on "#row-1"
|
|
75
|
+
* Example #2: When I right click on ".tree-node"
|
|
76
|
+
* Example #3: And we right-click on "[data-testid=image]"
|
|
77
|
+
* Example #4: When I right-click on "li.contact"
|
|
78
|
+
* Example #5: When I right click on "td.cell"
|
|
79
|
+
*
|
|
80
|
+
*/
|
|
81
|
+
When(/^(I |we )*right[- ]click on "([^"]*)"$/, async function (pronoun, sel) {
|
|
82
|
+
try { await this.page.locator(sel).first().click({ button: 'right' }); }
|
|
83
|
+
catch (e) { throw explainPtr('right-click', sel, e); }
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Middle-click on a CSS-addressed element.
|
|
88
|
+
*
|
|
89
|
+
* Example #1: When I middle-click on "a.external"
|
|
90
|
+
* Example #2: When I middle click on "#tab-2"
|
|
91
|
+
* Example #3: And we middle-click on "[data-testid=link]"
|
|
92
|
+
* Example #4: When I middle-click on "li.bookmark"
|
|
93
|
+
* Example #5: When I middle click on "a.product-link"
|
|
94
|
+
*
|
|
95
|
+
*/
|
|
96
|
+
When(/^(I |we )*middle[- ]click on "([^"]*)"$/, async function (pronoun, sel) {
|
|
97
|
+
try { await this.page.locator(sel).first().click({ button: 'middle' }); }
|
|
98
|
+
catch (e) { throw explainPtr('middle-click', sel, e); }
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Click on a CSS-addressed element while holding a modifier key.
|
|
103
|
+
*
|
|
104
|
+
* Modifier names follow Playwright: `Shift`, `Control`, `Alt`, `Meta`.
|
|
105
|
+
*
|
|
106
|
+
* Example #1: When I click on "#row-1" while holding "Shift"
|
|
107
|
+
* Example #2: When I click on ".checkbox" while holding "Control"
|
|
108
|
+
* Example #3: And we click on "a.external" while holding "Meta"
|
|
109
|
+
* Example #4: When I click on ".item" while holding "Alt"
|
|
110
|
+
* Example #5: When I click on "#tab-2" while holding "Shift"
|
|
111
|
+
*
|
|
112
|
+
*/
|
|
113
|
+
When(/^(I |we )*click on "([^"]*)" while holding "([^"]*)"$/, async function (pronoun, sel, key) {
|
|
114
|
+
try {
|
|
115
|
+
await this.page.locator(sel).first().click({ modifiers: [key] });
|
|
116
|
+
} catch (e) {
|
|
117
|
+
throw friendly({
|
|
118
|
+
action: `click "${sel}" while holding "${key}"`,
|
|
119
|
+
cause: e,
|
|
120
|
+
hint: `modifier must be one of: Shift, Control, Alt, Meta.`,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
// Drag and drop
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Drag a source element onto a target element.
|
|
131
|
+
*
|
|
132
|
+
* Example #1: When I drag "#card-1" to "#column-done"
|
|
133
|
+
* Example #2: When I drag ".file-tile" to "#trash"
|
|
134
|
+
* Example #3: And we drag "[data-testid=task-1]" to "[data-testid=in-progress]"
|
|
135
|
+
* Example #4: When I drag "#image" to "#canvas"
|
|
136
|
+
* Example #5: When I drag "li.draggable" to "ul.dropzone"
|
|
137
|
+
*
|
|
138
|
+
*/
|
|
139
|
+
When(/^(I |we )*drag "([^"]*)" to "([^"]*)"$/, async function (pronoun, source, target) {
|
|
140
|
+
try {
|
|
141
|
+
await this.page.locator(source).first().dragTo(this.page.locator(target).first());
|
|
142
|
+
} catch (e) {
|
|
143
|
+
throw friendly({
|
|
144
|
+
action: `drag "${source}" to "${target}"`,
|
|
145
|
+
cause: e,
|
|
146
|
+
hint: `both items must exist on the page; the target must accept drops.`,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
// Viewport
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Resize the browser viewport to a specific WxH.
|
|
157
|
+
*
|
|
158
|
+
* Example #1: When I set the viewport size to 1280x720
|
|
159
|
+
* Example #2: When I set the viewport size to 375x667
|
|
160
|
+
* Example #3: And we set the viewport size to 768x1024
|
|
161
|
+
* Example #4: When I set the viewport size to 1920x1080
|
|
162
|
+
* Example #5: When I set the viewport size to 320x568
|
|
163
|
+
*
|
|
164
|
+
*/
|
|
165
|
+
When(/^(I |we )*set the viewport size to (\d+)x(\d+)$/, async function (pronoun, w, h) {
|
|
166
|
+
try {
|
|
167
|
+
await this.page.setViewportSize({ width: parseInt(w, 10), height: parseInt(h, 10) });
|
|
168
|
+
} catch (e) {
|
|
169
|
+
throw friendly({
|
|
170
|
+
action: `set viewport size to ${w}x${h}`,
|
|
171
|
+
cause: e,
|
|
172
|
+
hint: `the browser was opened in fluid-viewport mode; set a fixed viewport in playwright.config.ts contextOptions.viewport.`,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
// Tap (touch)
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Touch-tap a CSS-addressed element. Falls back to a regular click if the
|
|
183
|
+
* browser context was not opened with `hasTouch: true`.
|
|
184
|
+
*
|
|
185
|
+
* Example #1: When I tap on "#cta"
|
|
186
|
+
* Example #2: When I tap on ".bottom-nav-item"
|
|
187
|
+
* Example #3: And we tap on "[data-testid=fab]"
|
|
188
|
+
* Example #4: When I tap on "button.menu"
|
|
189
|
+
* Example #5: When I tap on ".swipe-card"
|
|
190
|
+
*
|
|
191
|
+
*/
|
|
192
|
+
When(/^(I |we )*tap on "([^"]*)"$/, async function (pronoun, sel) {
|
|
193
|
+
try {
|
|
194
|
+
await this.page.locator(sel).first().tap();
|
|
195
|
+
} catch (e) {
|
|
196
|
+
if (/hasTouch/i.test(e.message)) {
|
|
197
|
+
await this.page.locator(sel).first().click();
|
|
198
|
+
} else {
|
|
199
|
+
throw e;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
});
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Auto-detect JavaScript errors during test execution.
|
|
4
|
+
//
|
|
5
|
+
// Listeners are attached on Before (order 200) once a page is open:
|
|
6
|
+
// page.on('pageerror') → uncaught errors
|
|
7
|
+
// page.on('console', level) → console messages (filtered by config)
|
|
8
|
+
//
|
|
9
|
+
// After every scenario the collector reports per the active "mode":
|
|
10
|
+
// - 'warn' (default) → log a yellow warning, scenario still passes
|
|
11
|
+
// - 'fail' → assert.fail with the error list
|
|
12
|
+
// - 'off' → silent
|
|
13
|
+
//
|
|
14
|
+
// Mode resolution priority (first match wins):
|
|
15
|
+
// 1. scenario tag @js-fail / @js-warn / @js-off / @javascript / @js-errors
|
|
16
|
+
// 2. env var VARBASE_E2E_JS_ERROR_MODE = warn | fail | off
|
|
17
|
+
// 3. worldParameters parameters.javascript.mode
|
|
18
|
+
// 4. default 'warn'
|
|
19
|
+
//
|
|
20
|
+
// Back-compat: @javascript → fail (legacy "always fail" tag). @js-errors → off.
|
|
21
|
+
|
|
22
|
+
const { Before, After, Then } = require('@cucumber/cucumber');
|
|
23
|
+
const assert = require('assert');
|
|
24
|
+
|
|
25
|
+
const VALID_MODES = new Set(['warn', 'fail', 'off']);
|
|
26
|
+
const DEFAULT_CONSOLE_LEVELS = ['error'];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Resolve effective settings for the current scenario.
|
|
30
|
+
*
|
|
31
|
+
* @returns {{ mode: 'warn'|'fail'|'off', levels: string[], ignore: RegExp|null,
|
|
32
|
+
* beforeScenario: boolean, afterScenario: boolean }}
|
|
33
|
+
*/
|
|
34
|
+
function resolveSettings(world, scope) {
|
|
35
|
+
const cfg = (world.parameters && world.parameters.javascript) || {};
|
|
36
|
+
const tag = (name) => hasTag(scope, name);
|
|
37
|
+
|
|
38
|
+
let mode;
|
|
39
|
+
if (tag('@js-fail') || tag('@javascript')) mode = 'fail';
|
|
40
|
+
else if (tag('@js-warn')) mode = 'warn';
|
|
41
|
+
else if (tag('@js-off') || tag('@js-errors')) mode = 'off';
|
|
42
|
+
else if (process.env.VARBASE_E2E_JS_ERROR_MODE) mode = process.env.VARBASE_E2E_JS_ERROR_MODE;
|
|
43
|
+
else if (cfg.mode) mode = cfg.mode;
|
|
44
|
+
else mode = 'warn';
|
|
45
|
+
if (!VALID_MODES.has(mode)) mode = 'warn';
|
|
46
|
+
|
|
47
|
+
const levelsRaw =
|
|
48
|
+
process.env.VARBASE_E2E_JS_ERROR_LEVELS ||
|
|
49
|
+
(Array.isArray(cfg.levels) ? cfg.levels.join(',') : cfg.levels) ||
|
|
50
|
+
DEFAULT_CONSOLE_LEVELS.join(',');
|
|
51
|
+
const levels = String(levelsRaw).split(',').map((s) => s.trim()).filter(Boolean);
|
|
52
|
+
|
|
53
|
+
const ignoreSrc = process.env.VARBASE_E2E_JS_ERROR_IGNORE || cfg.ignore || null;
|
|
54
|
+
let ignore = null;
|
|
55
|
+
if (ignoreSrc) {
|
|
56
|
+
try { ignore = new RegExp(ignoreSrc); }
|
|
57
|
+
catch { ignore = null; }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const beforeScenario =
|
|
61
|
+
process.env.VARBASE_E2E_JS_ERROR_BEFORE === '1'
|
|
62
|
+
? true
|
|
63
|
+
: cfg.beforeScenario === true;
|
|
64
|
+
const afterScenario =
|
|
65
|
+
process.env.VARBASE_E2E_JS_ERROR_AFTER === '0'
|
|
66
|
+
? false
|
|
67
|
+
: cfg.afterScenario !== false; // default true
|
|
68
|
+
|
|
69
|
+
return { mode, levels, ignore, beforeScenario, afterScenario };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function hasTag(scope, name) {
|
|
73
|
+
if (!scope || !Array.isArray(scope.pickle && scope.pickle.tags)) return false;
|
|
74
|
+
return scope.pickle.tags.some((t) => t.name === name);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function formatErrors(errs) {
|
|
78
|
+
return errs.map((e, i) => ` ${i + 1}. [${e.type}] ${e.message}`).join('\n');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function filterErrors(errs, ignore) {
|
|
82
|
+
if (!ignore) return errs;
|
|
83
|
+
return errs.filter((e) => !ignore.test(e.message));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function reportWarning(scope, errs) {
|
|
87
|
+
const name = (scope && scope.pickle && scope.pickle.name) || 'scenario';
|
|
88
|
+
// Yellow ANSI when FORCE_COLOR is on, plain otherwise — let the runtime
|
|
89
|
+
// decide. cucumber-js sets FORCE_COLOR=1 by default in interactive shells.
|
|
90
|
+
const head = `[varbase-e2e] JavaScript errors during "${name}" (mode=warn):`;
|
|
91
|
+
process.stderr.write(`\n\x1b[33m${head}\n${formatErrors(errs)}\x1b[0m\n`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// Before — attach listeners + (optional) pre-snapshot.
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
Before({ order: 200 }, function (scope) {
|
|
99
|
+
this._jsErrors = [];
|
|
100
|
+
this._jsErrorsAsserted = false;
|
|
101
|
+
this._jsSettings = resolveSettings(this, scope);
|
|
102
|
+
if (this._jsSettings.mode === 'off') return;
|
|
103
|
+
if (!this.page) return;
|
|
104
|
+
|
|
105
|
+
const levels = new Set(this._jsSettings.levels);
|
|
106
|
+
this.page.on('pageerror', (err) => {
|
|
107
|
+
this._jsErrors.push({ type: 'pageerror', message: String(err && err.message || err) });
|
|
108
|
+
});
|
|
109
|
+
this.page.on('console', (msg) => {
|
|
110
|
+
if (levels.has(msg.type())) {
|
|
111
|
+
this._jsErrors.push({ type: `console.${msg.type()}`, message: msg.text() });
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
if (this._jsSettings.beforeScenario) {
|
|
116
|
+
// Pre-scenario snapshot — typically empty unless the same page is reused.
|
|
117
|
+
const errs = filterErrors(this._jsErrors, this._jsSettings.ignore);
|
|
118
|
+
if (errs.length > 0) {
|
|
119
|
+
process.stderr.write(
|
|
120
|
+
`\n\x1b[33m[varbase-e2e] Pre-scenario JavaScript errors:\n${formatErrors(errs)}\x1b[0m\n`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
// Steps
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Assert that no JavaScript errors have been collected so far.
|
|
132
|
+
*
|
|
133
|
+
* Always fails on error (ignoring the active mode) — this is the explicit
|
|
134
|
+
* "hard check" step. Calling it also prevents the auto-report at scenario
|
|
135
|
+
* end so a single error is not reported twice.
|
|
136
|
+
*
|
|
137
|
+
* Example #1: Then there should be no JavaScript errors
|
|
138
|
+
* Example #2: And there should be no JavaScript errors
|
|
139
|
+
* Example #3: But there should be no JavaScript errors
|
|
140
|
+
* Example #4: Then there should be no JavaScript errors
|
|
141
|
+
* Example #5: Then there should be no JavaScript errors
|
|
142
|
+
*
|
|
143
|
+
*/
|
|
144
|
+
Then(/^there should be no JavaScript errors$/, function () {
|
|
145
|
+
this._jsErrorsAsserted = true;
|
|
146
|
+
const errs = filterErrors(this._jsErrors || [], this._jsSettings && this._jsSettings.ignore);
|
|
147
|
+
if (errs.length === 0) return;
|
|
148
|
+
assert.fail(`JavaScript errors detected:\n${formatErrors(errs)}`);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Assert that no JavaScript console warnings were collected. Requires
|
|
153
|
+
* 'warning' to be in the captured console levels (see worldParameters or
|
|
154
|
+
* VARBASE_E2E_JS_ERROR_LEVELS).
|
|
155
|
+
*
|
|
156
|
+
* Example #1: Then there should be no JavaScript warnings
|
|
157
|
+
* Example #2: And there should be no JavaScript warnings
|
|
158
|
+
* Example #3: But there should be no JavaScript warnings
|
|
159
|
+
* Example #4: Then there should be no JavaScript warnings
|
|
160
|
+
* Example #5: Then there should be no JavaScript warnings
|
|
161
|
+
*
|
|
162
|
+
*/
|
|
163
|
+
Then(/^there should be no JavaScript warnings$/, function () {
|
|
164
|
+
this._jsErrorsAsserted = true;
|
|
165
|
+
const errs = (this._jsErrors || []).filter((e) => e.type === 'console.warning');
|
|
166
|
+
if (errs.length === 0) return;
|
|
167
|
+
assert.fail(`JavaScript warnings detected:\n${formatErrors(errs)}`);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Assert that no collected JavaScript error message matches a regular
|
|
172
|
+
* expression. Useful when some noise is unavoidable but a specific bug
|
|
173
|
+
* pattern must never appear.
|
|
174
|
+
*
|
|
175
|
+
* Example #1: Then JavaScript errors should not match "TypeError"
|
|
176
|
+
* Example #2: Then JavaScript errors should not match "is not a function"
|
|
177
|
+
* Example #3: And JavaScript errors should not match "Cannot read property"
|
|
178
|
+
* Example #4: Then JavaScript errors should not match "ReferenceError: .* is not defined"
|
|
179
|
+
* Example #5: Then JavaScript errors should not match "Uncaught"
|
|
180
|
+
*
|
|
181
|
+
*/
|
|
182
|
+
Then(/^JavaScript errors should not match "([^"]*)"$/, function (pattern) {
|
|
183
|
+
let re;
|
|
184
|
+
try { re = new RegExp(pattern); }
|
|
185
|
+
catch (e) {
|
|
186
|
+
assert.fail(`Invalid regular expression: "${pattern}" — ${e.message}`);
|
|
187
|
+
}
|
|
188
|
+
const errs = (this._jsErrors || []).filter((e) => re.test(e.message));
|
|
189
|
+
if (errs.length === 0) return;
|
|
190
|
+
assert.fail(`JavaScript errors matched /${pattern}/:\n${formatErrors(errs)}`);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Print the JavaScript errors collected so far. Diagnostic only — never
|
|
195
|
+
* asserts, never fails. Useful inside a scenario being debugged.
|
|
196
|
+
*
|
|
197
|
+
* Example #1: Then print JavaScript errors
|
|
198
|
+
* Example #2: And print JavaScript errors
|
|
199
|
+
* Example #3: But print JavaScript errors
|
|
200
|
+
* Example #4: Then print JavaScript errors
|
|
201
|
+
* Example #5: Then print JavaScript errors
|
|
202
|
+
*
|
|
203
|
+
*/
|
|
204
|
+
Then(/^print JavaScript errors$/, function () {
|
|
205
|
+
const errs = this._jsErrors || [];
|
|
206
|
+
if (errs.length === 0) {
|
|
207
|
+
console.log('\n--- JavaScript errors: none ---');
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
console.log('\n--- JavaScript errors ---');
|
|
211
|
+
console.log(formatErrors(errs));
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
// After — report per mode.
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
|
|
218
|
+
After({ order: 200 }, function (scope) {
|
|
219
|
+
const s = this._jsSettings || resolveSettings(this, scope);
|
|
220
|
+
if (s.mode === 'off') return;
|
|
221
|
+
if (!s.afterScenario) return;
|
|
222
|
+
if (this._jsErrorsAsserted) return; // already handled by explicit step
|
|
223
|
+
|
|
224
|
+
const errs = filterErrors(this._jsErrors || [], s.ignore);
|
|
225
|
+
if (errs.length === 0) return;
|
|
226
|
+
|
|
227
|
+
if (s.mode === 'fail') {
|
|
228
|
+
assert.fail(`JavaScript errors detected during scenario:\n${formatErrors(errs)}`);
|
|
229
|
+
} else {
|
|
230
|
+
reportWarning(scope, errs);
|
|
231
|
+
}
|
|
232
|
+
});
|