@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,112 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { friendly } = require('./varbase-e2e');
|
|
4
|
+
|
|
5
|
+
// Simulate keyboard interactions on the page or on a specific element.
|
|
6
|
+
|
|
7
|
+
const { When } = require('@cucumber/cucumber');
|
|
8
|
+
|
|
9
|
+
// Common name aliases — lowercase names like "enter", "tab".
|
|
10
|
+
function normalizeKey(key) {
|
|
11
|
+
const map = {
|
|
12
|
+
enter: 'Enter', return: 'Enter', tab: 'Tab', escape: 'Escape', esc: 'Escape',
|
|
13
|
+
space: 'Space', backspace: 'Backspace', delete: 'Delete',
|
|
14
|
+
up: 'ArrowUp', down: 'ArrowDown', left: 'ArrowLeft', right: 'ArrowRight',
|
|
15
|
+
home: 'Home', end: 'End', pageup: 'PageUp', pagedown: 'PageDown',
|
|
16
|
+
};
|
|
17
|
+
const lower = key.toLowerCase();
|
|
18
|
+
return map[lower] || key;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Press a single key against the page (whatever element is currently focused).
|
|
23
|
+
*
|
|
24
|
+
* Common aliases (`enter`, `tab`, `esc`, `space`, `up/down/left/right`) are
|
|
25
|
+
* normalised to Playwright's canonical key names.
|
|
26
|
+
*
|
|
27
|
+
* Example #1: When I press the key "Enter"
|
|
28
|
+
* Example #2: When I press the key "Escape"
|
|
29
|
+
* Example #3: And I press the key "Tab"
|
|
30
|
+
* Example #4: When I press the key "ArrowDown"
|
|
31
|
+
* Example #5: When I focus on the element "#search"
|
|
32
|
+
* And I press the key "Enter"
|
|
33
|
+
*
|
|
34
|
+
*/
|
|
35
|
+
When(/^(I |we )*press the key "([^"]*)"$/, async function (pronoun, key) {
|
|
36
|
+
try {
|
|
37
|
+
await this.page.keyboard.press(normalizeKey(key));
|
|
38
|
+
} catch (e) {
|
|
39
|
+
throw friendly({
|
|
40
|
+
action: `press the key "${key}"`,
|
|
41
|
+
cause: e,
|
|
42
|
+
hint: `use a real key name like Enter, Escape, Tab, ArrowDown, or a short alias (enter, esc, tab, up, down, left, right, space).`,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Press a single key while a specific element is focused.
|
|
49
|
+
*
|
|
50
|
+
* Example #1: When I press the key "Enter" on the element "#search"
|
|
51
|
+
* Example #2: When I press the key "Tab" on the element "input[type=email]"
|
|
52
|
+
* Example #3: And I press the key "ArrowDown" on the element ".combobox"
|
|
53
|
+
* Example #4: When I press the key "Backspace" on the element "#name"
|
|
54
|
+
* Example #5: When I press the key "Escape" on the element "[role=dialog]"
|
|
55
|
+
*
|
|
56
|
+
*/
|
|
57
|
+
When(/^(I |we )*press the key "([^"]*)" on the element "([^"]*)"$/, async function (pronoun, key, sel) {
|
|
58
|
+
try {
|
|
59
|
+
await this.page.locator(sel).first().press(normalizeKey(key));
|
|
60
|
+
} catch (e) {
|
|
61
|
+
throw friendly({
|
|
62
|
+
action: `press the key "${key}" on "${sel}"`,
|
|
63
|
+
cause: e,
|
|
64
|
+
hint: `check that "${sel}" matches an element on the page that can be focused (input, button, link).`,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Press a key combination against the page. Combos use `+` between modifiers
|
|
71
|
+
* and the final key (e.g. `Control+a`, `Meta+s`, `Shift+Tab`).
|
|
72
|
+
*
|
|
73
|
+
* Example #1: When I press the keys "Control+a"
|
|
74
|
+
* Example #2: When I press the keys "Meta+s"
|
|
75
|
+
* Example #3: And I press the keys "Shift+Tab"
|
|
76
|
+
* Example #4: When I press the keys "Alt+ArrowLeft"
|
|
77
|
+
* Example #5: When I press the keys "Control+Shift+P"
|
|
78
|
+
*
|
|
79
|
+
*/
|
|
80
|
+
When(/^(I |we )*press the keys "([^"]*)"$/, async function (pronoun, keys) {
|
|
81
|
+
try {
|
|
82
|
+
await this.page.keyboard.press(keys);
|
|
83
|
+
} catch (e) {
|
|
84
|
+
throw friendly({
|
|
85
|
+
action: `press the keys "${keys}"`,
|
|
86
|
+
cause: e,
|
|
87
|
+
hint: `use a "+" between modifier and key, e.g. "Control+a", "Meta+s", or "Shift+Tab".`,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Press a key combination while a specific element is focused.
|
|
94
|
+
*
|
|
95
|
+
* Example #1: When I press the keys "Control+a" on the element "#editor"
|
|
96
|
+
* Example #2: When I press the keys "Meta+s" on the element "form"
|
|
97
|
+
* Example #3: And I press the keys "Shift+Tab" on the element "#email"
|
|
98
|
+
* Example #4: When I press the keys "Control+Enter" on the element "#message"
|
|
99
|
+
* Example #5: When I press the keys "Alt+Down" on the element ".combobox"
|
|
100
|
+
*
|
|
101
|
+
*/
|
|
102
|
+
When(/^(I |we )*press the keys "([^"]*)" on the element "([^"]*)"$/, async function (pronoun, keys, sel) {
|
|
103
|
+
try {
|
|
104
|
+
await this.page.locator(sel).first().press(keys);
|
|
105
|
+
} catch (e) {
|
|
106
|
+
throw friendly({
|
|
107
|
+
action: `press "${keys}" on "${sel}"`,
|
|
108
|
+
cause: e,
|
|
109
|
+
hint: `check the element exists and use "+" between modifier and key, e.g. "Control+s".`,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
});
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Link existence + href + title assertions.
|
|
4
|
+
|
|
5
|
+
const { When, Then } = require('@cucumber/cucumber');
|
|
6
|
+
const assert = require('assert');
|
|
7
|
+
|
|
8
|
+
function linkSelector(text) {
|
|
9
|
+
return `a:has-text("${text}")`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Assert a link with the given visible text and an href containing a fragment exists.
|
|
14
|
+
*
|
|
15
|
+
* Example #1: Then the link "About" with the href "/about" should exist
|
|
16
|
+
* Example #2: Then the link "Sign in" with the href "/login" should exist
|
|
17
|
+
* Example #3: And the link "Pricing" with the href "/pricing" should exist
|
|
18
|
+
* Example #4: Then the link "Docs" with the href "docs.example.com" should exist
|
|
19
|
+
* Example #5: Then the link "Contact" with the href "/contact-us" should exist
|
|
20
|
+
*
|
|
21
|
+
*/
|
|
22
|
+
Then('the link {string} with the href {string} should exist', async function (text, href) {
|
|
23
|
+
const loc = this.page.locator(`${linkSelector(text)}[href*="${href}"]`);
|
|
24
|
+
assert.ok(await loc.count() > 0, `Link "${text}" with href containing "${href}" not found.`);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Assert a link exists inside a specific parent element.
|
|
29
|
+
*
|
|
30
|
+
* Example #1: Then the link "Home" with the href "/" within the element "#main-nav" should exist
|
|
31
|
+
* Example #2: Then the link "Privacy" with the href "/privacy" within the element "footer" should exist
|
|
32
|
+
* Example #3: And the link "Sign in" with the href "/login" within the element ".header" should exist
|
|
33
|
+
* Example #4: Then the link "Docs" with the href "/docs" within the element "nav" should exist
|
|
34
|
+
* Example #5: Then the link "Pricing" with the href "/pricing" within the element ".cta-block" should exist
|
|
35
|
+
*
|
|
36
|
+
*/
|
|
37
|
+
Then('the link {string} with the href {string} within the element {string} should exist', async function (text, href, parent) {
|
|
38
|
+
const loc = this.page.locator(`${parent} ${linkSelector(text)}[href*="${href}"]`);
|
|
39
|
+
assert.ok(await loc.count() > 0, `Link "${text}" with href containing "${href}" not found within "${parent}".`);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Assert NO link with the given visible text and href fragment exists.
|
|
44
|
+
*
|
|
45
|
+
* Example #1: Then the link "Sign in" with the href "/login" should not exist
|
|
46
|
+
* Example #2: Then the link "Admin" with the href "/admin" should not exist
|
|
47
|
+
* Example #3: And the link "Old docs" with the href "/v1/docs" should not exist
|
|
48
|
+
* Example #4: Then the link "Logout" with the href "/logout" should not exist
|
|
49
|
+
* Example #5: Then the link "Beta" with the href "/beta" should not exist
|
|
50
|
+
*
|
|
51
|
+
*/
|
|
52
|
+
Then('the link {string} with the href {string} should not exist', async function (text, href) {
|
|
53
|
+
const loc = this.page.locator(`${linkSelector(text)}[href*="${href}"]`);
|
|
54
|
+
assert.strictEqual(await loc.count(), 0, `Link "${text}" with href containing "${href}" should not exist.`);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Assert NO link with text+href exists inside a specific parent element.
|
|
59
|
+
*
|
|
60
|
+
* Example #1: Then the link "Logout" with the href "/logout" within the element ".guest-nav" should not exist
|
|
61
|
+
* Example #2: Then the link "Admin" with the href "/admin" within the element "footer" should not exist
|
|
62
|
+
* Example #3: And the link "Beta" with the href "/beta" within the element "#main-nav" should not exist
|
|
63
|
+
* Example #4: Then the link "Old" with the href "/v1" within the element ".breadcrumb" should not exist
|
|
64
|
+
* Example #5: Then the link "Internal" with the href "/internal" within the element ".public-nav" should not exist
|
|
65
|
+
*
|
|
66
|
+
*/
|
|
67
|
+
Then('the link {string} with the href {string} within the element {string} should not exist', async function (text, href, parent) {
|
|
68
|
+
const loc = this.page.locator(`${parent} ${linkSelector(text)}[href*="${href}"]`);
|
|
69
|
+
assert.strictEqual(await loc.count(), 0, `Link "${text}" with href containing "${href}" should not exist within "${parent}".`);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Assert at least one link has the exact `title` attribute.
|
|
74
|
+
*
|
|
75
|
+
* Example #1: Then the link with the title "Open menu" should exist
|
|
76
|
+
* Example #2: Then the link with the title "Edit profile" should exist
|
|
77
|
+
* Example #3: And the link with the title "Download PDF" should exist
|
|
78
|
+
* Example #4: Then the link with the title "Print page" should exist
|
|
79
|
+
* Example #5: Then the link with the title "Sign in" should exist
|
|
80
|
+
*
|
|
81
|
+
*/
|
|
82
|
+
Then('the link with the title {string} should exist', async function (title) {
|
|
83
|
+
assert.ok(await this.page.locator(`a[title="${title}"]`).count() > 0, `Link with title "${title}" not found.`);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Assert NO link has the given exact `title` attribute.
|
|
88
|
+
*
|
|
89
|
+
* Example #1: Then the link with the title "Delete user" should not exist
|
|
90
|
+
* Example #2: Then the link with the title "Open admin" should not exist
|
|
91
|
+
* Example #3: And the link with the title "Edit billing" should not exist
|
|
92
|
+
* Example #4: Then the link with the title "Sign out" should not exist
|
|
93
|
+
* Example #5: Then the link with the title "Reveal secret" should not exist
|
|
94
|
+
*
|
|
95
|
+
*/
|
|
96
|
+
Then('the link with the title {string} should not exist', async function (title) {
|
|
97
|
+
assert.strictEqual(await this.page.locator(`a[title="${title}"]`).count(), 0, `Link with title "${title}" should not exist.`);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Assert a link's href is absolute (starts with `http://` or `https://`).
|
|
102
|
+
*
|
|
103
|
+
* Example #1: Then the link "Twitter" should be an absolute link
|
|
104
|
+
* Example #2: Then the link "GitHub" should be an absolute link
|
|
105
|
+
* Example #3: And the link "Documentation" should be an absolute link
|
|
106
|
+
* Example #4: Then the link "Status page" should be an absolute link
|
|
107
|
+
* Example #5: Then the link "External docs" should be an absolute link
|
|
108
|
+
*
|
|
109
|
+
*/
|
|
110
|
+
Then('the link {string} should be an absolute link', async function (text) {
|
|
111
|
+
const href = await this.page.locator(linkSelector(text)).first().getAttribute('href');
|
|
112
|
+
assert.ok(href && /^https?:\/\//.test(href), `Link "${text}" href "${href}" is not absolute.`);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Assert a link's href is relative (no `http://` / `https://` scheme).
|
|
117
|
+
*
|
|
118
|
+
* Example #1: Then the link "Home" should not be an absolute link
|
|
119
|
+
* Example #2: Then the link "About" should not be an absolute link
|
|
120
|
+
* Example #3: And the link "Privacy" should not be an absolute link
|
|
121
|
+
* Example #4: Then the link "Pricing" should not be an absolute link
|
|
122
|
+
* Example #5: Then the link "Docs" should not be an absolute link
|
|
123
|
+
*
|
|
124
|
+
*/
|
|
125
|
+
Then('the link {string} should not be an absolute link', async function (text) {
|
|
126
|
+
const href = await this.page.locator(linkSelector(text)).first().getAttribute('href');
|
|
127
|
+
assert.ok(href && !/^https?:\/\//.test(href), `Link "${text}" href "${href}" should not be absolute.`);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Click a link addressed by its `title` attribute.
|
|
132
|
+
*
|
|
133
|
+
* Example #1: When I click on the link with the title "Open menu"
|
|
134
|
+
* Example #2: When I click on the link with the title "Edit profile"
|
|
135
|
+
* Example #3: And we click on the link with the title "Download PDF"
|
|
136
|
+
* Example #4: When I click on the link with the title "Print page"
|
|
137
|
+
* Example #5: When I click on the link with the title "Sign in"
|
|
138
|
+
*
|
|
139
|
+
*/
|
|
140
|
+
When(/^(I |we )*click on the link with the title "([^"]*)"$/, async function (pronoun, title) {
|
|
141
|
+
await this.page.locator(`a[title="${title}"]`).first().click();
|
|
142
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Assert <meta> tag presence and contents.
|
|
4
|
+
|
|
5
|
+
const { Then } = require('@cucumber/cucumber');
|
|
6
|
+
const assert = require('assert');
|
|
7
|
+
|
|
8
|
+
async function getMetas(page) {
|
|
9
|
+
return await page.evaluate(() => Array.from(document.querySelectorAll('meta')).map((m) => {
|
|
10
|
+
const o = {};
|
|
11
|
+
for (const a of m.attributes) o[a.name] = a.value;
|
|
12
|
+
return o;
|
|
13
|
+
}));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function matchAttrs(meta, attrs) {
|
|
17
|
+
for (const [k, v] of Object.entries(attrs)) {
|
|
18
|
+
if (meta[k] !== v) return false;
|
|
19
|
+
}
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Assert at least one `<meta>` tag matches every attribute in the data table.
|
|
25
|
+
*
|
|
26
|
+
* Example #1: Then the meta tag should exist with the following attributes:
|
|
27
|
+
* | name | description |
|
|
28
|
+
* | content | Documentation site |
|
|
29
|
+
* Example #2: Then the meta tag should exist with the following attributes:
|
|
30
|
+
* | property | og:title |
|
|
31
|
+
* | content | Home |
|
|
32
|
+
* Example #3: And the meta tag should exist with the following attributes:
|
|
33
|
+
* | name | viewport |
|
|
34
|
+
* | content | width=device-width, initial-scale=1 |
|
|
35
|
+
* Example #4: Then the meta tag should exist with the following attributes:
|
|
36
|
+
* | name | robots |
|
|
37
|
+
* | content | noindex,nofollow |
|
|
38
|
+
* Example #5: Then the meta tag should exist with the following attributes:
|
|
39
|
+
* | charset | UTF-8 |
|
|
40
|
+
*
|
|
41
|
+
*/
|
|
42
|
+
Then('the meta tag should exist with the following attributes:', async function (table) {
|
|
43
|
+
const want = Object.fromEntries(table.raw());
|
|
44
|
+
const metas = await getMetas(this.page);
|
|
45
|
+
assert.ok(metas.some((m) => matchAttrs(m, want)), `No meta tag matching ${JSON.stringify(want)} was found.`);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Assert NO `<meta>` tag matches every attribute in the data table.
|
|
50
|
+
*
|
|
51
|
+
* Example #1: Then the meta tag should not exist with the following attributes:
|
|
52
|
+
* | name | description |
|
|
53
|
+
* | content | Coming soon |
|
|
54
|
+
* Example #2: Then the meta tag should not exist with the following attributes:
|
|
55
|
+
* | name | robots |
|
|
56
|
+
* | content | noindex |
|
|
57
|
+
* Example #3: And the meta tag should not exist with the following attributes:
|
|
58
|
+
* | property | og:title |
|
|
59
|
+
* | content | Placeholder |
|
|
60
|
+
* Example #4: Then the meta tag should not exist with the following attributes:
|
|
61
|
+
* | http-equiv | refresh |
|
|
62
|
+
* | content | 5 |
|
|
63
|
+
* Example #5: Then the meta tag should not exist with the following attributes:
|
|
64
|
+
* | name | author |
|
|
65
|
+
* | content | Anonymous |
|
|
66
|
+
*
|
|
67
|
+
*/
|
|
68
|
+
Then('the meta tag should not exist with the following attributes:', async function (table) {
|
|
69
|
+
const want = Object.fromEntries(table.raw());
|
|
70
|
+
const metas = await getMetas(this.page);
|
|
71
|
+
assert.ok(!metas.some((m) => matchAttrs(m, want)), `Meta tag matching ${JSON.stringify(want)} should not exist.`);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Assert the `content` attribute of a named meta tag contains no HTML markup.
|
|
76
|
+
* Useful for description / open-graph fields that should render as plain text.
|
|
77
|
+
*
|
|
78
|
+
* Example #1: Then the "description" meta tag should not contain any HTML tags
|
|
79
|
+
* Example #2: Then the "og:title" meta tag should not contain any HTML tags
|
|
80
|
+
* Example #3: And the "twitter:description" meta tag should not contain any HTML tags
|
|
81
|
+
* Example #4: Then the "keywords" meta tag should not contain any HTML tags
|
|
82
|
+
* Example #5: Then the "author" meta tag should not contain any HTML tags
|
|
83
|
+
*
|
|
84
|
+
*/
|
|
85
|
+
Then('the {string} meta tag should not contain any HTML tags', async function (metaName) {
|
|
86
|
+
const metas = await getMetas(this.page);
|
|
87
|
+
const m = metas.find((x) => x.name === metaName || x.property === metaName);
|
|
88
|
+
assert.ok(m, `Meta tag "${metaName}" not found.`);
|
|
89
|
+
const content = m.content || '';
|
|
90
|
+
assert.ok(!/<[a-z][\s\S]*>/i.test(content), `Meta tag "${metaName}" contains HTML: ${content}`);
|
|
91
|
+
});
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// All modal / dialog step definitions live in this file.
|
|
4
|
+
//
|
|
5
|
+
// Modal element resolution priority:
|
|
6
|
+
// 1. world.__selectorsCss['modal'] (set via the named-selector registry
|
|
7
|
+
// or the active CMS / framework selector preset)
|
|
8
|
+
// 2. fallback to "[role=\"dialog\"], dialog"
|
|
9
|
+
//
|
|
10
|
+
// Visibility is derived from computed style (display, visibility, opacity)
|
|
11
|
+
// rather than offsetParent — that check is unreliable for fixed-position
|
|
12
|
+
// modal containers. The wait step lives in wait.steps.js.
|
|
13
|
+
|
|
14
|
+
const { When, Then } = require('@cucumber/cucumber');
|
|
15
|
+
const assert = require('assert');
|
|
16
|
+
const {
|
|
17
|
+
getModalLocator,
|
|
18
|
+
findVisibleModal,
|
|
19
|
+
isAnyModalVisible,
|
|
20
|
+
waitForModalState,
|
|
21
|
+
friendly,
|
|
22
|
+
} = require('./varbase-e2e');
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Visibility
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Assert that a modal dialog is visible or not visible on the page.
|
|
30
|
+
*
|
|
31
|
+
* Example #1: Then I should see a modal
|
|
32
|
+
* Example #2: Then I should see the modal
|
|
33
|
+
* Example #3: Then we should see a modal dialog
|
|
34
|
+
* Example #4: Then I should not see a modal
|
|
35
|
+
* Example #5: Then I should not see the modal dialog
|
|
36
|
+
*
|
|
37
|
+
*/
|
|
38
|
+
Then(/^(I |we )*should( not)* see (a |the )*modal( dialog)*$/, async function (pronounCase, notCase, aTheCase, dialogCase) {
|
|
39
|
+
const visible = await isAnyModalVisible(this.page, this);
|
|
40
|
+
if (notCase) {
|
|
41
|
+
assert.ok(!visible, 'Modal dialog is visible, but it should not be.');
|
|
42
|
+
} else {
|
|
43
|
+
assert.ok(visible, 'Modal dialog is not visible, but it should be.');
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Assert that a modal dialog with a given title is visible or not visible.
|
|
49
|
+
*
|
|
50
|
+
* Example #1: Then I should see a modal with title "Confirm Action"
|
|
51
|
+
* Example #2: Then I should see the modal with title "Welcome"
|
|
52
|
+
* Example #3: Then we should see a modal with title "Welcome Message"
|
|
53
|
+
* Example #4: Then I should not see a modal with title "Error"
|
|
54
|
+
* Example #5: Then I should not see the modal with title "Validation Error"
|
|
55
|
+
*
|
|
56
|
+
*/
|
|
57
|
+
Then(/^(I |we )*should( not)* see (a |the )*modal with title "([^"]*)?"$/, async function (pronounCase, notCase, aTheCase, title) {
|
|
58
|
+
const modal = getModalLocator(this.page, this);
|
|
59
|
+
const byHeading = modal.locator('.modal-title, .dialog-title, h1, h2, h3').filter({ hasText: title });
|
|
60
|
+
const byAttr = modal.locator(`[title*="${title}"], [aria-label*="${title}"]`);
|
|
61
|
+
const found = await byHeading.count() > 0 || await byAttr.count() > 0;
|
|
62
|
+
if (notCase) {
|
|
63
|
+
assert.ok(!found, `Modal with title "${title}" is visible, but it should not be.`);
|
|
64
|
+
} else {
|
|
65
|
+
assert.ok(found, `Modal with title "${title}" is not visible or not found.`);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Assert that a specific modal by id/class/data-modal is visible or not.
|
|
71
|
+
*
|
|
72
|
+
* Example #1: Then I should see a "confirmation-modal" modal
|
|
73
|
+
* Example #2: Then I should see the "#delete-modal" modal
|
|
74
|
+
* Example #3: Then we should see a "settings-modal" modal
|
|
75
|
+
* Example #4: Then I should not see a "error-modal" modal
|
|
76
|
+
* Example #5: Then I should not see the "#success-modal" modal
|
|
77
|
+
*
|
|
78
|
+
*/
|
|
79
|
+
Then(/^(I |we )*should( not)* see (a |the )*"([^"]*)?" modal$/, async function (pronounCase, notCase, aTheCase, identifier) {
|
|
80
|
+
const selector = (identifier.startsWith('#') || identifier.startsWith('.'))
|
|
81
|
+
? identifier
|
|
82
|
+
: `#${identifier}, .${identifier}, [data-modal="${identifier}"]`;
|
|
83
|
+
const visible = await this.page.locator(selector).first().isVisible();
|
|
84
|
+
if (notCase) {
|
|
85
|
+
assert.ok(!visible, `Modal "${identifier}" is visible, but it should not be.`);
|
|
86
|
+
} else {
|
|
87
|
+
assert.ok(visible, `Modal "${identifier}" is not visible or not found.`);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
// Text content
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Assert that a modal contains or does not contain specific text.
|
|
97
|
+
*
|
|
98
|
+
* Example #1: Then I should see "Are you sure?" in the modal
|
|
99
|
+
* Example #2: Then I should see "Delete this item" in the modal
|
|
100
|
+
* Example #3: Then we should see "Confirmation required" in the modal dialog
|
|
101
|
+
* Example #4: Then I should not see "Error occurred" in the modal
|
|
102
|
+
*
|
|
103
|
+
*/
|
|
104
|
+
Then(/^(I |we )*should( not)* see "([^"]*)?" in( the)* modal( dialog)*$/, async function (pronounCase, notCase, expectedText, theCase, dialogCase) {
|
|
105
|
+
const found = await getModalLocator(this.page, this).filter({ hasText: expectedText }).count() > 0;
|
|
106
|
+
if (notCase) {
|
|
107
|
+
assert.ok(!found, `Found "${expectedText}" in modal, but it should not be there.`);
|
|
108
|
+
} else {
|
|
109
|
+
assert.ok(found, `Could not find "${expectedText}" in modal.`);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Assert that the modal contains the given text (alternate phrasing).
|
|
115
|
+
*
|
|
116
|
+
* Distinct from "I should see :text in the modal" — checks plain
|
|
117
|
+
* `innerText`, ignoring nested layout. Use when the text is part of the
|
|
118
|
+
* modal body rather than a specific element.
|
|
119
|
+
*
|
|
120
|
+
* Example #1: Then the modal should contain "Welcome modal text"
|
|
121
|
+
* Example #2: Then the modal should contain "Are you sure"
|
|
122
|
+
* Example #3: And the modal should contain "Order #1234"
|
|
123
|
+
* Example #4: When I press "Show details"
|
|
124
|
+
* Then the modal should contain "Address"
|
|
125
|
+
* Example #5: Given I am on "/dashboard"
|
|
126
|
+
* When I click "Open settings"
|
|
127
|
+
* And I wait for the modal to appear
|
|
128
|
+
* Then the modal should contain "Preferences"
|
|
129
|
+
*
|
|
130
|
+
*/
|
|
131
|
+
Then('the modal should contain {string}', async function (text) {
|
|
132
|
+
const inner = await getModalLocator(this.page, this).first().innerText();
|
|
133
|
+
assert.ok(inner.indexOf(text) !== -1, `Modal does not contain "${text}". Got: ${inner}`);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Assert that the modal does NOT contain the given text.
|
|
138
|
+
*
|
|
139
|
+
* Passes when no modal exists OR when the modal exists but lacks the text.
|
|
140
|
+
*
|
|
141
|
+
* Example #1: Then the modal should not contain "Goodbye"
|
|
142
|
+
* Example #2: Then the modal should not contain "Error"
|
|
143
|
+
* Example #3: And the modal should not contain "Payment failed"
|
|
144
|
+
* Example #4: When I close the modal
|
|
145
|
+
* Then the modal should not contain "Welcome"
|
|
146
|
+
* Example #5: When I press "Refresh"
|
|
147
|
+
* Then the modal should not contain "Loading"
|
|
148
|
+
*
|
|
149
|
+
*/
|
|
150
|
+
Then('the modal should not contain {string}', async function (text) {
|
|
151
|
+
const loc = getModalLocator(this.page, this);
|
|
152
|
+
const count = await loc.count();
|
|
153
|
+
if (count === 0) return;
|
|
154
|
+
const inner = await loc.first().innerText();
|
|
155
|
+
assert.ok(inner.indexOf(text) === -1, `Modal should not contain "${text}".`);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
// Interactions
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Click a button or link inside a modal dialog by its visible text.
|
|
164
|
+
*
|
|
165
|
+
* Example #1: When I click "Confirm" in the modal
|
|
166
|
+
* Example #2: When I click "Cancel" in the modal dialog
|
|
167
|
+
* Example #3: When we click "OK" button in the modal
|
|
168
|
+
* Example #4: And I click "Close" in the modal
|
|
169
|
+
*
|
|
170
|
+
*/
|
|
171
|
+
When(/^(I |we )*click "([^"]*)?"( button)* in( the)* modal( dialog)*$/, async function (pronounCase, buttonText, buttonCase, theCase, dialogCase) {
|
|
172
|
+
try {
|
|
173
|
+
await waitForModalState(this.page, 'visible', 10000, this);
|
|
174
|
+
const modal = await findVisibleModal(this.page, this);
|
|
175
|
+
await modal.locator('button, a, [role="button"], input[type="button"], input[type="submit"], .btn')
|
|
176
|
+
.filter({ hasText: buttonText })
|
|
177
|
+
.first()
|
|
178
|
+
.click();
|
|
179
|
+
} catch (e) {
|
|
180
|
+
throw friendly({
|
|
181
|
+
action: `click "${buttonText}" in the modal`,
|
|
182
|
+
cause: e,
|
|
183
|
+
hint: `make sure the modal is open ("When I wait for the modal to appear") and the button text matches exactly.`,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Click a CSS-selector-addressed element inside the active modal.
|
|
190
|
+
*
|
|
191
|
+
* Distinct from "click :text in the modal" which uses visible button text.
|
|
192
|
+
* Use this variant when the target lacks readable text (icon button) or
|
|
193
|
+
* when text is ambiguous across multiple buttons in the modal.
|
|
194
|
+
*
|
|
195
|
+
* Example #1: When I click on "#close-btn" in the modal
|
|
196
|
+
* Example #2: When I click on ".confirm-delete" in the modal
|
|
197
|
+
* Example #3: And I click on "[data-testid=accept]" in the modal
|
|
198
|
+
* Example #4: When I click on "button[aria-label=Close]" in the modal
|
|
199
|
+
* Example #5: When I press "Open settings"
|
|
200
|
+
* And I wait for the modal to appear
|
|
201
|
+
* Then I click on "#tab-notifications" in the modal
|
|
202
|
+
*
|
|
203
|
+
*/
|
|
204
|
+
When(/^(I |we )*click on "([^"]*)" in the modal$/, async function (pronoun, selector) {
|
|
205
|
+
try {
|
|
206
|
+
await getModalLocator(this.page, this).locator(selector).first().click();
|
|
207
|
+
} catch (e) {
|
|
208
|
+
throw friendly({
|
|
209
|
+
action: `click on "${selector}" in the modal`,
|
|
210
|
+
cause: e,
|
|
211
|
+
hint: `make sure the modal is open and the selector matches something inside it.`,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Close or dismiss a modal dialog (uses close button, or Escape fallback).
|
|
218
|
+
*
|
|
219
|
+
* Example #1: When I close the modal
|
|
220
|
+
* Example #2: When I dismiss the modal dialog
|
|
221
|
+
* Example #3: When we close the modal
|
|
222
|
+
* Example #4: And I dismiss the modal
|
|
223
|
+
*
|
|
224
|
+
*/
|
|
225
|
+
When(/^(I |we )*(close|dismiss)( the)* modal( dialog)*$/, async function (pronounCase, closeOrDismiss, theCase, dialogCase) {
|
|
226
|
+
try {
|
|
227
|
+
await waitForModalState(this.page, 'visible', 10000, this);
|
|
228
|
+
const modal = await findVisibleModal(this.page, this);
|
|
229
|
+
const closeBtn = modal.locator('.close, .modal-close, [data-dismiss="modal"], [aria-label="Close"], .btn-close, button[class*="close"]').first();
|
|
230
|
+
if (await closeBtn.isVisible()) {
|
|
231
|
+
await closeBtn.click();
|
|
232
|
+
} else {
|
|
233
|
+
await modal.press('Escape');
|
|
234
|
+
}
|
|
235
|
+
} catch (e) {
|
|
236
|
+
throw friendly({
|
|
237
|
+
action: `${closeOrDismiss} the modal`,
|
|
238
|
+
cause: e,
|
|
239
|
+
hint: `the modal had no recognised close button; check it is open or register a custom close-button selector.`,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// "When I wait for the modal to appear/disappear" is defined in wait.steps.js.
|