@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,400 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// All wait/settle step definitions live in this file.
|
|
4
|
+
//
|
|
5
|
+
// Behavior-Based Robotics (BBR) principle: react to the environment, not the
|
|
6
|
+
// clock. A wait step never blocks for a fixed duration — it returns as soon
|
|
7
|
+
// as the page is at the *edge* of activity, bounded by a budget so a runaway
|
|
8
|
+
// page cannot stall the run.
|
|
9
|
+
//
|
|
10
|
+
// Shared smartSettle and modal probes are imported from varbase-e2e.js
|
|
11
|
+
// so this file never duplicates BBR plumbing.
|
|
12
|
+
|
|
13
|
+
const { When } = require('@cucumber/cucumber');
|
|
14
|
+
const { smartSettle, waitForModalState } = require('./varbase-e2e');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Wait UP TO N seconds for the page to settle. Returns early on idle.
|
|
18
|
+
*
|
|
19
|
+
* "Settle" = DOM ready, no in-flight fetch/XHR, no pending setTimeout
|
|
20
|
+
* callbacks, and no MutationObserver activity for at least 250 ms.
|
|
21
|
+
*
|
|
22
|
+
* Example #1: When I wait for 1 second
|
|
23
|
+
* Example #2: When I wait for 5 seconds
|
|
24
|
+
* Example #3: And I wait for 3 seconds
|
|
25
|
+
* Example #4: When we wait for 2 seconds
|
|
26
|
+
* Example #5: And we wait for 10 seconds
|
|
27
|
+
* Example #6: When I press "Save"
|
|
28
|
+
* And I wait for 2 seconds
|
|
29
|
+
* Then I should see "Saved"
|
|
30
|
+
*
|
|
31
|
+
*/
|
|
32
|
+
When(/^(I |we )*wait for (\d+) seconds?$/, async function (pronoun, seconds) {
|
|
33
|
+
await smartSettle(this.page, parseInt(seconds, 10) * 1000);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Wait UP TO N seconds for the page (and AJAX) to settle. Returns early.
|
|
38
|
+
*
|
|
39
|
+
* Same probe as `wait for N seconds` — kept for legacy phrasing parity with
|
|
40
|
+
* older feature files that say "for AJAX to finish" explicitly.
|
|
41
|
+
*
|
|
42
|
+
* Example #1: When I wait for 1 second for AJAX to finish
|
|
43
|
+
* Example #2: When I wait for 3 seconds for AJAX to finish
|
|
44
|
+
* Example #3: And I wait for 5 seconds for AJAX to finish
|
|
45
|
+
* Example #4: When we wait for 2 seconds for AJAX to finish
|
|
46
|
+
* Example #5: When I press "Search"
|
|
47
|
+
* And I wait for 5 seconds for AJAX to finish
|
|
48
|
+
* Then I should see "Results"
|
|
49
|
+
*
|
|
50
|
+
*/
|
|
51
|
+
When(/^(I |we )*wait for (\d+) seconds? for AJAX to finish$/, async function (pronoun, seconds) {
|
|
52
|
+
await smartSettle(this.page, parseInt(seconds, 10) * 1000);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Wait UP TO a number of seconds for the page to settle.
|
|
57
|
+
*
|
|
58
|
+
* Example #1: When I wait 1 second
|
|
59
|
+
* Example #2: When I wait 5 seconds
|
|
60
|
+
* Example #3: When we wait 3s
|
|
61
|
+
* Example #4: And wait 2s
|
|
62
|
+
* Example #5: And wait 2 seconds
|
|
63
|
+
* Example #6: When we wait 1 second
|
|
64
|
+
* Example #7: When we wait 5 seconds
|
|
65
|
+
* Example #8: When we wait 4s
|
|
66
|
+
*
|
|
67
|
+
*/
|
|
68
|
+
When(/^(I |we )*wait (\d+)( second| seconds|s)?$/, async function (pronounCase, number, withSecondWord) {
|
|
69
|
+
await smartSettle(this.page, parseInt(number, 10) * 1000);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Wait UP TO a maximum number of seconds for the page to settle.
|
|
74
|
+
*
|
|
75
|
+
* Example #1: When I wait max of 1 second
|
|
76
|
+
* Example #2: When I wait max of 5 seconds
|
|
77
|
+
* Example #3: When we wait max of 3s
|
|
78
|
+
* Example #4: And wait max of 2s
|
|
79
|
+
* Example #5: And wait max of 2 seconds
|
|
80
|
+
* Example #6: When we wait max of 1 second
|
|
81
|
+
* Example #7: When we wait max of 5 seconds
|
|
82
|
+
* Example #8: When we wait max of 4s
|
|
83
|
+
*
|
|
84
|
+
*/
|
|
85
|
+
When(/^(I |we )*wait max of (\d+)( second| seconds|s)?$/, async function (pronounCase, number, withSecondWord) {
|
|
86
|
+
await smartSettle(this.page, parseInt(number, 10) * 1000);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Wait UP TO a number of minutes for the page to settle.
|
|
91
|
+
*
|
|
92
|
+
* Example #1: When I wait 1 minute
|
|
93
|
+
* Example #2: When I wait 10 minutes
|
|
94
|
+
* Example #3: When we wait 1m
|
|
95
|
+
* Example #4: And wait 2m
|
|
96
|
+
* Example #5: And wait 2 minutes
|
|
97
|
+
*
|
|
98
|
+
*/
|
|
99
|
+
When(/^(I |we )*wait (\d+)( minute| minutes|m)?$/, async function (pronounCase, number, withMinuteWord) {
|
|
100
|
+
await smartSettle(this.page, parseInt(number, 10) * 1000 * 60);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Wait UP TO a maximum number of minutes for the page to settle.
|
|
105
|
+
*
|
|
106
|
+
* Example #1: When I wait max of 1 minute
|
|
107
|
+
* Example #2: When I wait max of 10 minutes
|
|
108
|
+
* Example #3: When we wait max of 1m
|
|
109
|
+
* Example #4: And wait max of 2m
|
|
110
|
+
* Example #5: And wait max of 2 minutes
|
|
111
|
+
*
|
|
112
|
+
*/
|
|
113
|
+
When(/^(I |we )*wait max of (\d+)( minute| minutes|m)?$/, async function (pronounCase, number, withMinuteWord) {
|
|
114
|
+
await smartSettle(this.page, parseInt(number, 10) * 1000 * 60);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Wait until the page is loaded.
|
|
119
|
+
*
|
|
120
|
+
* Example #1: When I wait until the page is loaded
|
|
121
|
+
* Example #2: When we wait until the page is loaded
|
|
122
|
+
* Example #3: When wait until page loaded
|
|
123
|
+
*
|
|
124
|
+
*/
|
|
125
|
+
When(/^(I |we )*wait until( the)* page( is)* loaded*$/, async function (pronounCase, theCase, withIs) {
|
|
126
|
+
await smartSettle(this.page, 10000);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Wait for active XHR/fetch requests to complete.
|
|
131
|
+
*
|
|
132
|
+
* Example #1: When I wait for AJAX to finish
|
|
133
|
+
* Example #2: And I wait for AJAX to finish
|
|
134
|
+
* Example #3: When we wait for AJAX to finish
|
|
135
|
+
* Example #4: And wait for AJAX to finish
|
|
136
|
+
*
|
|
137
|
+
*/
|
|
138
|
+
When(/^(I |we )*wait for AJAX to finish$/, async function (pronounCase) {
|
|
139
|
+
await smartSettle(this.page, 10000);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Wait for a modal dialog to appear or disappear.
|
|
144
|
+
*
|
|
145
|
+
* Example #1: When I wait for the modal to appear
|
|
146
|
+
* Example #2: When I wait for the modal to disappear
|
|
147
|
+
* Example #3: When we wait for modal to appear
|
|
148
|
+
* Example #4: And I wait for the modal dialog to disappear
|
|
149
|
+
*
|
|
150
|
+
*/
|
|
151
|
+
When(/^(I |we )*wait for( the)* modal( dialog)* to (appear|disappear)$/, async function (pronounCase, theCase, dialogCase, appearOrDisappear) {
|
|
152
|
+
const state = appearOrDisappear === 'appear' ? 'visible' : 'hidden';
|
|
153
|
+
await waitForModalState(this.page, state, 10000, this);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Wait until a CSS selector becomes visible. Returns the moment the
|
|
158
|
+
* matching element is on screen (display !== 'none', visibility !== 'hidden',
|
|
159
|
+
* has a non-zero box). This is the canonical "wait until X appears" probe.
|
|
160
|
+
*
|
|
161
|
+
* Example #1: When I wait for "#dashboard" to appear
|
|
162
|
+
* Example #2: When I wait for ".success-banner" to appear
|
|
163
|
+
* Example #3: And I wait for "[data-testid=user-list]" to appear
|
|
164
|
+
* Example #4: When we wait for ".modal.show" to appear
|
|
165
|
+
* Example #5: When I press "Save"
|
|
166
|
+
* And I wait for ".toast-success" to appear
|
|
167
|
+
*
|
|
168
|
+
*/
|
|
169
|
+
When(/^(I |we )*wait for "([^"]*)" to appear$/, async function (pronounCase, selector) {
|
|
170
|
+
await this.page.waitForSelector(selector, { state: 'visible', timeout: 10000 });
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Wait until a CSS selector becomes hidden (display:none, visibility:hidden,
|
|
175
|
+
* detached, or zero-size).
|
|
176
|
+
*
|
|
177
|
+
* Example #1: When I wait for "#loading-spinner" to disappear
|
|
178
|
+
* Example #2: When I wait for ".overlay" to disappear
|
|
179
|
+
* Example #3: And I wait for ".modal.show" to disappear
|
|
180
|
+
* Example #4: When we wait for "[data-testid=skeleton]" to disappear
|
|
181
|
+
* Example #5: When I press "Submit"
|
|
182
|
+
* And I wait for ".loader" to disappear
|
|
183
|
+
* Then I should see "Saved"
|
|
184
|
+
*
|
|
185
|
+
*/
|
|
186
|
+
When(/^(I |we )*wait for "([^"]*)" to disappear$/, async function (pronounCase, selector) {
|
|
187
|
+
await this.page.waitForSelector(selector, { state: 'hidden', timeout: 10000 });
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Wait until visible text appears anywhere on the page.
|
|
192
|
+
*
|
|
193
|
+
* Polls `document.body.innerText` every 100 ms for up to 10 s. Match is
|
|
194
|
+
* case-sensitive substring.
|
|
195
|
+
*
|
|
196
|
+
* Example #1: When I wait for the text "Dashboard" to appear
|
|
197
|
+
* Example #2: When I wait for the text "Loading complete" to appear
|
|
198
|
+
* Example #3: And I wait for text "Welcome" to appear
|
|
199
|
+
* Example #4: When we wait for the text "Order #1234" to appear
|
|
200
|
+
* Example #5: When I press "Submit"
|
|
201
|
+
* And I wait for the text "Thank you" to appear
|
|
202
|
+
*
|
|
203
|
+
*/
|
|
204
|
+
When(/^(I |we )*wait for( the)* text "([^"]*)" to appear$/, async function (pronounCase, theCase, text) {
|
|
205
|
+
await this.page.waitForFunction(
|
|
206
|
+
(t) => document.body && document.body.innerText.includes(t),
|
|
207
|
+
text, { timeout: 10000, polling: 100 }
|
|
208
|
+
);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Wait until visible text is gone from the page.
|
|
213
|
+
*
|
|
214
|
+
* Example #1: When I wait for the text "Loading…" to disappear
|
|
215
|
+
* Example #2: When I wait for the text "Saving" to disappear
|
|
216
|
+
* Example #3: And I wait for text "Pending" to disappear
|
|
217
|
+
* Example #4: When we wait for the text "Connecting" to disappear
|
|
218
|
+
* Example #5: When I press "Submit"
|
|
219
|
+
* And I wait for the text "Validating" to disappear
|
|
220
|
+
* Then I should see "Done"
|
|
221
|
+
*
|
|
222
|
+
*/
|
|
223
|
+
When(/^(I |we )*wait for( the)* text "([^"]*)" to disappear$/, async function (pronounCase, theCase, text) {
|
|
224
|
+
await this.page.waitForFunction(
|
|
225
|
+
(t) => !document.body || !document.body.innerText.includes(t),
|
|
226
|
+
text, { timeout: 10000, polling: 100 }
|
|
227
|
+
);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Wait until the current URL contains a fragment.
|
|
232
|
+
*
|
|
233
|
+
* Useful after a redirect, push-state navigation, or hash-based routing.
|
|
234
|
+
*
|
|
235
|
+
* Example #1: When I wait until the URL contains "/dashboard"
|
|
236
|
+
* Example #2: When I wait until the URL contains "?step=2"
|
|
237
|
+
* Example #3: And I wait until the URL contains "#confirmation"
|
|
238
|
+
* Example #4: When we wait until the URL contains "/orders/"
|
|
239
|
+
* Example #5: When I follow "Sign in"
|
|
240
|
+
* And I wait until the URL contains "/auth/callback"
|
|
241
|
+
*
|
|
242
|
+
*/
|
|
243
|
+
When(/^(I |we )*wait until( the)* URL contains "([^"]*)"$/, async function (pronounCase, theCase, fragment) {
|
|
244
|
+
await this.page.waitForFunction(
|
|
245
|
+
(frag) => window.location.href.includes(frag),
|
|
246
|
+
fragment, { timeout: 10000, polling: 100 }
|
|
247
|
+
);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Wait until `document.title` equals (`is`) or contains the given value.
|
|
252
|
+
*
|
|
253
|
+
* Example #1: When I wait until the page title is "Dashboard - MyApp"
|
|
254
|
+
* Example #2: When I wait until the page title contains "Dashboard"
|
|
255
|
+
* Example #3: And I wait until the page title contains "(3 unread)"
|
|
256
|
+
* Example #4: When we wait until the page title is "Order Placed"
|
|
257
|
+
* Example #5: When I follow "Inbox"
|
|
258
|
+
* And I wait until the page title contains "Inbox"
|
|
259
|
+
*
|
|
260
|
+
*/
|
|
261
|
+
When(/^(I |we )*wait until( the)* page title (is|contains) "([^"]*)"$/, async function (pronounCase, theCase, op, value) {
|
|
262
|
+
await this.page.waitForFunction(
|
|
263
|
+
({ op, value }) => op === 'is' ? document.title === value : document.title.includes(value),
|
|
264
|
+
{ op, value }, { timeout: 10000, polling: 100 }
|
|
265
|
+
);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Wait until exactly N elements match a CSS selector.
|
|
270
|
+
*
|
|
271
|
+
* Use 0 to wait for elements to disappear, or a positive count to wait for
|
|
272
|
+
* a specific number to render.
|
|
273
|
+
*
|
|
274
|
+
* Example #1: When I wait until 5 elements match ".product-card"
|
|
275
|
+
* Example #2: When I wait until 0 elements match ".loading-skeleton"
|
|
276
|
+
* Example #3: And I wait until 1 element matches ".active-row"
|
|
277
|
+
* Example #4: When we wait until 10 elements match "tr.user"
|
|
278
|
+
* Example #5: When I press "Load more"
|
|
279
|
+
* And I wait until 20 elements match ".feed-item"
|
|
280
|
+
*
|
|
281
|
+
*/
|
|
282
|
+
When(/^(I |we )*wait until (\d+) elements? match(?:es)? "([^"]*)"$/, async function (pronounCase, count, selector) {
|
|
283
|
+
const expected = parseInt(count, 10);
|
|
284
|
+
await this.page.waitForFunction(
|
|
285
|
+
({ sel, n }) => document.querySelectorAll(sel).length === n,
|
|
286
|
+
{ sel: selector, n: expected }, { timeout: 10000, polling: 100 }
|
|
287
|
+
);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Wait until at least N elements match a CSS selector.
|
|
292
|
+
*
|
|
293
|
+
* Useful for infinite-scroll / pagination tests where the lower bound matters
|
|
294
|
+
* but the exact count may vary.
|
|
295
|
+
*
|
|
296
|
+
* Example #1: When I wait until at least 3 elements match ".item"
|
|
297
|
+
* Example #2: When I wait until at least 1 element matches ".notification"
|
|
298
|
+
* Example #3: And I wait until at least 10 elements match ".feed-item"
|
|
299
|
+
* Example #4: When we wait until at least 5 elements match "tr"
|
|
300
|
+
* Example #5: When I scroll to the bottom
|
|
301
|
+
* And I wait until at least 25 elements match ".product-card"
|
|
302
|
+
*
|
|
303
|
+
*/
|
|
304
|
+
When(/^(I |we )*wait until at least (\d+) elements? match(?:es)? "([^"]*)"$/, async function (pronounCase, count, selector) {
|
|
305
|
+
const expected = parseInt(count, 10);
|
|
306
|
+
await this.page.waitForFunction(
|
|
307
|
+
({ sel, n }) => document.querySelectorAll(sel).length >= n,
|
|
308
|
+
{ sel: selector, n: expected }, { timeout: 10000, polling: 100 }
|
|
309
|
+
);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Wait until the network goes quiet (full smart settle, 10 s budget).
|
|
314
|
+
*
|
|
315
|
+
* Three equivalent phrasings.
|
|
316
|
+
*
|
|
317
|
+
* Example #1: When I wait until the network is idle
|
|
318
|
+
* Example #2: And I wait until requests are complete
|
|
319
|
+
* Example #3: When we wait until network goes quiet
|
|
320
|
+
* Example #4: When I press "Search"
|
|
321
|
+
* And I wait until the network is idle
|
|
322
|
+
* Then I should see "Results"
|
|
323
|
+
* Example #5: When I press "Refresh"
|
|
324
|
+
* And I wait until requests are complete
|
|
325
|
+
*
|
|
326
|
+
*/
|
|
327
|
+
When(/^(I |we )*wait until (the network is idle|requests are complete|network goes quiet)$/, async function () {
|
|
328
|
+
await smartSettle(this.page, 10000);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Wait until the page is interactive (DOMContentLoaded + body attached).
|
|
333
|
+
*
|
|
334
|
+
* Lighter than `wait until the network is idle` — does not wait for late
|
|
335
|
+
* fetch/XHR. Useful when you need to interact with the document early.
|
|
336
|
+
*
|
|
337
|
+
* Example #1: When I wait until the page is interactive
|
|
338
|
+
* Example #2: Given I am on "/heavy-page"
|
|
339
|
+
* When I wait until the page is interactive
|
|
340
|
+
* Example #3: And I wait until the page is interactive
|
|
341
|
+
* Example #4: When we wait until the page is interactive
|
|
342
|
+
* Example #5: Given I am on "/spa"
|
|
343
|
+
* When I wait until the page is interactive
|
|
344
|
+
* Then "<#root>" should be attached
|
|
345
|
+
*
|
|
346
|
+
*/
|
|
347
|
+
When(/^(I |we )*wait until( the)* page is interactive$/, async function () {
|
|
348
|
+
await this.page.waitForSelector('body', { state: 'attached', timeout: 10000 });
|
|
349
|
+
await this.page.waitForLoadState('domcontentloaded', { timeout: 10000 });
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Wait until every tracked `setTimeout` callback has fired.
|
|
354
|
+
*
|
|
355
|
+
* Catches "fade-out then display:none" close transitions, debounced renders,
|
|
356
|
+
* and any UI flow that schedules a delayed update without making a network
|
|
357
|
+
* request. Reads `window.__varbaseE2ePendingTimers`, the counter installed by
|
|
358
|
+
* varbase-e2e.js's init script.
|
|
359
|
+
*
|
|
360
|
+
* Example #1: When I wait until pending timers settle
|
|
361
|
+
* Example #2: When I press "Close"
|
|
362
|
+
* And I wait until pending timers settle
|
|
363
|
+
* Then I should not see the modal
|
|
364
|
+
* Example #3: And I wait until pending timers settle
|
|
365
|
+
* Example #4: When we wait until pending timers settle
|
|
366
|
+
* Example #5: When I press "Toast"
|
|
367
|
+
* And I wait until pending timers settle
|
|
368
|
+
* Then I should not see "Saved"
|
|
369
|
+
*
|
|
370
|
+
*/
|
|
371
|
+
When(/^(I |we )*wait until pending timers settle$/, async function () {
|
|
372
|
+
await this.page.waitForFunction(
|
|
373
|
+
() => {
|
|
374
|
+
const t = window.__varbaseE2ePendingTimers;
|
|
375
|
+
return typeof t !== 'number' || t <= 0;
|
|
376
|
+
},
|
|
377
|
+
null, { timeout: 10000, polling: 100 }
|
|
378
|
+
);
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Pollable text assertion — retries the inner step until passing or timeout.
|
|
383
|
+
*
|
|
384
|
+
* Use sparingly; prefer explicit edge waits (`wait for "X" to appear`).
|
|
385
|
+
*
|
|
386
|
+
* Example #1: Then eventually I should see "Done"
|
|
387
|
+
* Example #2: Then eventually I should see "Loaded" within 5 seconds
|
|
388
|
+
* Example #3: And eventually we should see "Saved"
|
|
389
|
+
* Example #4: Then eventually I should see "Order Confirmed" within 30 seconds
|
|
390
|
+
* Example #5: When I press "Submit"
|
|
391
|
+
* Then eventually I should see "Submitted" within 8 seconds
|
|
392
|
+
*
|
|
393
|
+
*/
|
|
394
|
+
When(/^eventually (I |we )*should see "([^"]*)"(?: within (\d+) seconds?)?$/, async function (pronoun, text, seconds) {
|
|
395
|
+
const total = (seconds ? parseInt(seconds, 10) : 10) * 1000;
|
|
396
|
+
await this.page.waitForFunction(
|
|
397
|
+
(t) => document.body && document.body.innerText.includes(t),
|
|
398
|
+
text, { timeout: total, polling: 100 }
|
|
399
|
+
);
|
|
400
|
+
});
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { friendly } = require('./varbase-e2e');
|
|
4
|
+
|
|
5
|
+
// Web-first assertion steps — each matcher polls the live page until the
|
|
6
|
+
// condition holds or the per-step timeout elapses. The assertion IS the
|
|
7
|
+
// wait, so no `wait for AJAX` is required between an action and its check.
|
|
8
|
+
//
|
|
9
|
+
// Pattern (all steps): "<selector>" should [not] be <state> [within N seconds]
|
|
10
|
+
// Default budget is 5s; override with the trailing "within N seconds" clause.
|
|
11
|
+
|
|
12
|
+
const { Then, When } = require('@cucumber/cucumber');
|
|
13
|
+
const assert = require('assert');
|
|
14
|
+
|
|
15
|
+
function parseTimeout(secondsStr) {
|
|
16
|
+
const n = secondsStr ? parseInt(secondsStr, 10) : 0;
|
|
17
|
+
return n > 0 ? n * 1000 : 5000;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Poll fn() until it returns truthy, or timeout. fn() returns a value;
|
|
21
|
+
// `predicate` decides pass/fail. Returns the last value seen.
|
|
22
|
+
async function poll(fn, predicate, timeout, message) {
|
|
23
|
+
const deadline = Date.now() + timeout;
|
|
24
|
+
let last;
|
|
25
|
+
while (Date.now() < deadline) {
|
|
26
|
+
try {
|
|
27
|
+
last = await fn();
|
|
28
|
+
if (predicate(last)) return last;
|
|
29
|
+
} catch (e) {
|
|
30
|
+
last = e;
|
|
31
|
+
}
|
|
32
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
33
|
+
}
|
|
34
|
+
throw friendly(`${message} (last seen: ${typeof last === 'object' ? JSON.stringify(last) : String(last)})`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function checkState(locator, state) {
|
|
38
|
+
switch (state) {
|
|
39
|
+
case 'visible': return locator.first().isVisible();
|
|
40
|
+
case 'hidden': return !(await locator.first().isVisible().catch(() => false));
|
|
41
|
+
case 'attached': return (await locator.count()) > 0;
|
|
42
|
+
case 'focused': return locator.first().evaluate((el) => el === document.activeElement);
|
|
43
|
+
case 'enabled': return locator.first().isEnabled();
|
|
44
|
+
case 'disabled': return locator.first().isDisabled();
|
|
45
|
+
case 'editable': return locator.first().isEditable();
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// State matchers
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Auto-retrying state assertion. Polls the live page until the matcher passes
|
|
56
|
+
* or the budget elapses. Default budget 5 s.
|
|
57
|
+
*
|
|
58
|
+
* Example #1: Then "#dashboard" should be visible
|
|
59
|
+
* Example #2: Then ".success-banner" should be visible within 10 seconds
|
|
60
|
+
* Example #3: And "[data-testid=signup-cta]" should be focused
|
|
61
|
+
* Example #4: Then "button.submit" should be enabled
|
|
62
|
+
* Example #5: Then "input#email" should be editable
|
|
63
|
+
*
|
|
64
|
+
*/
|
|
65
|
+
Then(/^"([^"]*)" should be (visible|hidden|attached|focused|enabled|disabled|editable)(?: within (\d+) seconds?)?$/, async function (selector, state, sec) {
|
|
66
|
+
const loc = this.page.locator(selector);
|
|
67
|
+
await poll(
|
|
68
|
+
() => checkState(loc, state),
|
|
69
|
+
(v) => v === true,
|
|
70
|
+
parseTimeout(sec),
|
|
71
|
+
`Expected "${selector}" to be ${state}`
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Auto-retrying negated state assertion.
|
|
77
|
+
*
|
|
78
|
+
* Example #1: Then "#loading-spinner" should not be visible
|
|
79
|
+
* Example #2: Then ".error" should not be visible within 5 seconds
|
|
80
|
+
* Example #3: And "button.submit" should not be disabled
|
|
81
|
+
* Example #4: Then "input#email" should not be editable
|
|
82
|
+
* Example #5: Then "[data-testid=password]" should not be focused
|
|
83
|
+
*
|
|
84
|
+
*/
|
|
85
|
+
Then(/^"([^"]*)" should not be (visible|hidden|attached|focused|enabled|disabled|editable)(?: within (\d+) seconds?)?$/, async function (selector, state, sec) {
|
|
86
|
+
const loc = this.page.locator(selector);
|
|
87
|
+
await poll(
|
|
88
|
+
() => checkState(loc, state),
|
|
89
|
+
(v) => v === false,
|
|
90
|
+
parseTimeout(sec),
|
|
91
|
+
`Expected "${selector}" to not be ${state}`
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Assert an element's bounding box overlaps the visible viewport rectangle.
|
|
97
|
+
*
|
|
98
|
+
* Example #1: Then "#hero" should be in the viewport
|
|
99
|
+
* Example #2: Then "[data-testid=cta]" should be in the viewport within 3 seconds
|
|
100
|
+
* Example #3: When I scroll to the element "#footer"
|
|
101
|
+
* Then "#footer" should be in the viewport
|
|
102
|
+
* Example #4: Then "h1" should be in the viewport
|
|
103
|
+
* Example #5: Then ".banner" should be in the viewport
|
|
104
|
+
*
|
|
105
|
+
*/
|
|
106
|
+
Then(/^"([^"]*)" should be in the viewport(?: within (\d+) seconds?)?$/, async function (selector, sec) {
|
|
107
|
+
const loc = this.page.locator(selector).first();
|
|
108
|
+
await poll(
|
|
109
|
+
async () => loc.evaluate((el) => {
|
|
110
|
+
const r = el.getBoundingClientRect();
|
|
111
|
+
return r.bottom > 0 && r.top < (window.innerHeight || 0)
|
|
112
|
+
&& r.right > 0 && r.left < (window.innerWidth || 0);
|
|
113
|
+
}),
|
|
114
|
+
(v) => v === true,
|
|
115
|
+
parseTimeout(sec),
|
|
116
|
+
`Expected "${selector}" to be in viewport`
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Assert an element is OUTSIDE the visible viewport rectangle.
|
|
122
|
+
*
|
|
123
|
+
* Example #1: Then "#footer" should not be in the viewport
|
|
124
|
+
* Example #2: Then "[data-testid=hidden]" should not be in the viewport within 3 seconds
|
|
125
|
+
* Example #3: And ".off-screen" should not be in the viewport
|
|
126
|
+
* Example #4: Then "section.below-fold" should not be in the viewport
|
|
127
|
+
* Example #5: When I scroll to the top
|
|
128
|
+
* Then "#footer" should not be in the viewport
|
|
129
|
+
*
|
|
130
|
+
*/
|
|
131
|
+
Then(/^"([^"]*)" should not be in the viewport(?: within (\d+) seconds?)?$/, async function (selector, sec) {
|
|
132
|
+
const loc = this.page.locator(selector).first();
|
|
133
|
+
await poll(
|
|
134
|
+
async () => loc.evaluate((el) => {
|
|
135
|
+
const r = el.getBoundingClientRect();
|
|
136
|
+
return r.bottom > 0 && r.top < (window.innerHeight || 0)
|
|
137
|
+
&& r.right > 0 && r.left < (window.innerWidth || 0);
|
|
138
|
+
}).catch(() => false),
|
|
139
|
+
(v) => v === false,
|
|
140
|
+
parseTimeout(sec),
|
|
141
|
+
`Expected "${selector}" to not be in viewport`
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Assert exactly N elements match a selector (auto-retry).
|
|
147
|
+
*
|
|
148
|
+
* Example #1: Then ".product-card" should have a count of 12
|
|
149
|
+
* Example #2: Then ".error" should have a count of 0 within 5 seconds
|
|
150
|
+
* Example #3: And "tr" should have a count of 21
|
|
151
|
+
* Example #4: Then "[data-testid=item]" should have a count of 5
|
|
152
|
+
* Example #5: Then ".feed-item" should have a count of 25 within 10 seconds
|
|
153
|
+
*
|
|
154
|
+
*/
|
|
155
|
+
Then(/^"([^"]*)" should have a count of (\d+)(?: within (\d+) seconds?)?$/, async function (selector, count, sec) {
|
|
156
|
+
const expected = parseInt(count, 10);
|
|
157
|
+
const loc = this.page.locator(selector);
|
|
158
|
+
await poll(
|
|
159
|
+
() => loc.count(),
|
|
160
|
+
(v) => v === expected,
|
|
161
|
+
parseTimeout(sec),
|
|
162
|
+
`Expected "${selector}" count to be ${expected}`
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Assert an element's textContent equals an expected string (trimmed).
|
|
168
|
+
*
|
|
169
|
+
* Example #1: Then "h1" should have text "Welcome"
|
|
170
|
+
* Example #2: Then ".badge" should have text "12" within 3 seconds
|
|
171
|
+
* Example #3: And ".total" should have text "$99.00"
|
|
172
|
+
* Example #4: Then "[data-testid=subtitle]" should have text "Premium"
|
|
173
|
+
* Example #5: Then ".alert" should have text "Saved"
|
|
174
|
+
*
|
|
175
|
+
*/
|
|
176
|
+
Then(/^"([^"]*)" should have text "([^"]*)"(?: within (\d+) seconds?)?$/, async function (selector, text, sec) {
|
|
177
|
+
const loc = this.page.locator(selector).first();
|
|
178
|
+
await poll(
|
|
179
|
+
() => loc.textContent(),
|
|
180
|
+
(v) => (v || '').trim() === text,
|
|
181
|
+
parseTimeout(sec),
|
|
182
|
+
`Expected "${selector}" text to be "${text}"`
|
|
183
|
+
);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Assert an element's textContent contains an expected substring.
|
|
188
|
+
*
|
|
189
|
+
* Example #1: Then "h1" should contain text "Welcome"
|
|
190
|
+
* Example #2: Then ".badge" should contain text "unread" within 3 seconds
|
|
191
|
+
* Example #3: And ".total" should contain text "$99"
|
|
192
|
+
* Example #4: Then "[data-testid=subtitle]" should contain text "Premium"
|
|
193
|
+
* Example #5: Then ".alert" should contain text "Saved"
|
|
194
|
+
*
|
|
195
|
+
*/
|
|
196
|
+
Then(/^"([^"]*)" should contain text "([^"]*)"(?: within (\d+) seconds?)?$/, async function (selector, text, sec) {
|
|
197
|
+
const loc = this.page.locator(selector).first();
|
|
198
|
+
await poll(
|
|
199
|
+
() => loc.textContent(),
|
|
200
|
+
(v) => (v || '').includes(text),
|
|
201
|
+
parseTimeout(sec),
|
|
202
|
+
`Expected "${selector}" to contain text "${text}"`
|
|
203
|
+
);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Assert an input's `value` property equals an expected string.
|
|
208
|
+
*
|
|
209
|
+
* Example #1: Then "#email" should have value "alice@example.com"
|
|
210
|
+
* Example #2: Then "input[name=q]" should have value "laptops"
|
|
211
|
+
* Example #3: And "[data-testid=phone]" should have value "0790000000"
|
|
212
|
+
* Example #4: Then "#bio" should have value "" within 3 seconds
|
|
213
|
+
* Example #5: Then "select#country" should have value "JO"
|
|
214
|
+
*
|
|
215
|
+
*/
|
|
216
|
+
Then(/^"([^"]*)" should have value "([^"]*)"(?: within (\d+) seconds?)?$/, async function (selector, value, sec) {
|
|
217
|
+
const loc = this.page.locator(selector).first();
|
|
218
|
+
await poll(
|
|
219
|
+
() => loc.inputValue(),
|
|
220
|
+
(v) => v === value,
|
|
221
|
+
parseTimeout(sec),
|
|
222
|
+
`Expected "${selector}" value to be "${value}"`
|
|
223
|
+
);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Assert an element's attribute equals an expected value.
|
|
228
|
+
*
|
|
229
|
+
* Example #1: Then "#tab-1" should have attribute "aria-selected" with value "true"
|
|
230
|
+
* Example #2: Then "[data-testid=cta]" should have attribute "data-state" with value "open"
|
|
231
|
+
* Example #3: And "img.logo" should have attribute "alt" with value "Vardot"
|
|
232
|
+
* Example #4: Then "a.signup" should have attribute "href" with value "/signup"
|
|
233
|
+
* Example #5: Then "input#email" should have attribute "type" with value "email"
|
|
234
|
+
*
|
|
235
|
+
*/
|
|
236
|
+
Then(/^"([^"]*)" should have attribute "([^"]*)" with value "([^"]*)"(?: within (\d+) seconds?)?$/, async function (selector, attr, value, sec) {
|
|
237
|
+
const loc = this.page.locator(selector).first();
|
|
238
|
+
await poll(
|
|
239
|
+
() => loc.getAttribute(attr),
|
|
240
|
+
(v) => v === value,
|
|
241
|
+
parseTimeout(sec),
|
|
242
|
+
`Expected "${selector}" attribute "${attr}" to be "${value}"`
|
|
243
|
+
);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Assert an element's `class` attribute contains a class token.
|
|
248
|
+
*
|
|
249
|
+
* Example #1: Then "#tab-1" should have class "is-active"
|
|
250
|
+
* Example #2: Then ".modal" should have class "show" within 3 seconds
|
|
251
|
+
* Example #3: And ".dropdown" should have class "open"
|
|
252
|
+
* Example #4: Then "tr.row-1" should have class "selected"
|
|
253
|
+
* Example #5: Then "[data-testid=card]" should have class "highlighted"
|
|
254
|
+
*
|
|
255
|
+
*/
|
|
256
|
+
Then(/^"([^"]*)" should have class "([^"]*)"(?: within (\d+) seconds?)?$/, async function (selector, cls, sec) {
|
|
257
|
+
const loc = this.page.locator(selector).first();
|
|
258
|
+
await poll(
|
|
259
|
+
() => loc.getAttribute('class'),
|
|
260
|
+
(v) => (v || '').split(/\s+/).includes(cls),
|
|
261
|
+
parseTimeout(sec),
|
|
262
|
+
`Expected "${selector}" to have class "${cls}"`
|
|
263
|
+
);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Click an element by its accessible role + name (Playwright `getByRole`).
|
|
268
|
+
*
|
|
269
|
+
* Example #1: When I click the "Sign in" button
|
|
270
|
+
* Example #2: When I click the "Profile" link
|
|
271
|
+
* Example #3: And we click the "Notifications" tab
|
|
272
|
+
* Example #4: When I click the "Subscribe" checkbox
|
|
273
|
+
* Example #5: When I click the "Premium" radio
|
|
274
|
+
*
|
|
275
|
+
*/
|
|
276
|
+
When(/^(I |we )*click the "([^"]*)" (button|link|tab|menuitem|checkbox|radio|option)$/, async function (pronoun, name, role) {
|
|
277
|
+
await this.page.getByRole(role, { name }).first().click();
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Assert a role-addressed element is visible (auto-retry).
|
|
282
|
+
*
|
|
283
|
+
* Example #1: Then the "Save changes" button should be visible
|
|
284
|
+
* Example #2: Then the "Profile" link should be visible within 5 seconds
|
|
285
|
+
* Example #3: And the "Privacy" tab should be visible
|
|
286
|
+
* Example #4: Then the "I agree" checkbox should be visible
|
|
287
|
+
* Example #5: Then the "Premium" radio should be visible
|
|
288
|
+
*
|
|
289
|
+
*/
|
|
290
|
+
Then(/^the "([^"]*)" (button|link|tab|menuitem|checkbox|radio|option) should be visible(?: within (\d+) seconds?)?$/, async function (name, role, sec) {
|
|
291
|
+
const loc = this.page.getByRole(role, { name }).first();
|
|
292
|
+
await poll(
|
|
293
|
+
() => loc.isVisible(),
|
|
294
|
+
(v) => v === true,
|
|
295
|
+
parseTimeout(sec),
|
|
296
|
+
`Expected ${role} "${name}" to be visible`
|
|
297
|
+
);
|
|
298
|
+
});
|