@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,91 @@
|
|
|
1
|
+
# Web-first Assertions
|
|
2
|
+
|
|
3
|
+
A "web-first" assertion is an auto-retrying matcher: it polls the page until the condition holds or a budget elapses, instead of taking a single snapshot. Varbase E2E exposes a thin BDD wrapper over Playwright's locator state-checks so feature files never need explicit waits between an action and its assertion.
|
|
4
|
+
|
|
5
|
+
## Default timeout
|
|
6
|
+
|
|
7
|
+
Every web-first step has a 5-second budget. Override per step with the trailing `within N seconds` clause:
|
|
8
|
+
|
|
9
|
+
```gherkin
|
|
10
|
+
Then "#dashboard" should be visible
|
|
11
|
+
Then "#dashboard" should be visible within 12 seconds
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## State matchers
|
|
15
|
+
|
|
16
|
+
```gherkin
|
|
17
|
+
Then "<sel>" should be visible
|
|
18
|
+
Then "<sel>" should be hidden
|
|
19
|
+
Then "<sel>" should be attached
|
|
20
|
+
Then "<sel>" should be focused
|
|
21
|
+
Then "<sel>" should be enabled
|
|
22
|
+
Then "<sel>" should be disabled
|
|
23
|
+
Then "<sel>" should be editable
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Negated form:
|
|
27
|
+
|
|
28
|
+
```gherkin
|
|
29
|
+
Then "<sel>" should not be visible
|
|
30
|
+
Then "<sel>" should not be focused
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Viewport
|
|
34
|
+
|
|
35
|
+
```gherkin
|
|
36
|
+
Then "<sel>" should be in the viewport
|
|
37
|
+
Then "<sel>" should not be in the viewport within 3 seconds
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Count
|
|
41
|
+
|
|
42
|
+
```gherkin
|
|
43
|
+
Then "<sel>" should have a count of 5
|
|
44
|
+
Then "<sel>" should have a count of 0 within 10 seconds
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Text / value
|
|
48
|
+
|
|
49
|
+
```gherkin
|
|
50
|
+
Then "<sel>" should have text "Total: $99.00"
|
|
51
|
+
Then "<sel>" should contain text "$99"
|
|
52
|
+
Then "<sel>" should have value "alice@example.com"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Attributes / classes
|
|
56
|
+
|
|
57
|
+
```gherkin
|
|
58
|
+
Then "<sel>" should have attribute "data-state" with value "open"
|
|
59
|
+
Then "<sel>" should have class "is-active"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Role-based interactions
|
|
63
|
+
|
|
64
|
+
When you want accessibility-aligned tests, use the role family. These resolve via `page.getByRole(role, { name })` — the same rules screen readers use:
|
|
65
|
+
|
|
66
|
+
```gherkin
|
|
67
|
+
When I click the "Sign in" button
|
|
68
|
+
When I click the "Profile" link
|
|
69
|
+
When I click the "Notifications" tab
|
|
70
|
+
Then the "Save changes" button should be visible
|
|
71
|
+
Then the "Privacy" tab should be visible within 3 seconds
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Supported roles: `button`, `link`, `tab`, `menuitem`, `checkbox`, `radio`, `option`.
|
|
75
|
+
|
|
76
|
+
## Why prefer web-first
|
|
77
|
+
|
|
78
|
+
A boolean check + sleep is brittle:
|
|
79
|
+
|
|
80
|
+
```gherkin
|
|
81
|
+
# BAD — sleep then check; fails if the server is 1ms slower than expected.
|
|
82
|
+
When I press "Save"
|
|
83
|
+
And I wait 2 seconds
|
|
84
|
+
Then "<sel>" should be visible
|
|
85
|
+
|
|
86
|
+
# GOOD — wait IS the assertion; passes the moment the element appears.
|
|
87
|
+
When I press "Save"
|
|
88
|
+
Then "<sel>" should be visible within 5 seconds
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The web-first wrapper is also strictly faster on the happy path: it returns the instant the matcher passes, instead of always sleeping the full configured duration.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Networking & Dialogs
|
|
2
|
+
|
|
3
|
+
Varbase E2E makes it easy to stub external dependencies, simulate offline / slow networks, and respond to native browser dialogs from within a feature file.
|
|
4
|
+
|
|
5
|
+
## Stubbing requests
|
|
6
|
+
|
|
7
|
+
```gherkin
|
|
8
|
+
Scenario: Dashboard renders mocked data
|
|
9
|
+
Given I am on the homepage
|
|
10
|
+
And the URL "**/api/users" returns the JSON:
|
|
11
|
+
"""
|
|
12
|
+
{"users": [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]}
|
|
13
|
+
"""
|
|
14
|
+
When I go to "/dashboard"
|
|
15
|
+
And I wait for AJAX to finish
|
|
16
|
+
Then "<#user-list>" should contain text "Alice"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Patterns use Playwright `page.route()` matching: `**/path`, `*.gif`, regex inside CSS.
|
|
20
|
+
|
|
21
|
+
## Simulating failure
|
|
22
|
+
|
|
23
|
+
```gherkin
|
|
24
|
+
Given the URL "**/api/login" returns status 401 with body "Unauthorized"
|
|
25
|
+
Given the URL "**/analytics.js" is blocked
|
|
26
|
+
Given the URL "**/api/**" is delayed by 1500 ms
|
|
27
|
+
Given the network is offline
|
|
28
|
+
Given the network is online
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Recording requests
|
|
32
|
+
|
|
33
|
+
```gherkin
|
|
34
|
+
Given I start recording network requests
|
|
35
|
+
When I click "Buy now"
|
|
36
|
+
Then a POST request to "**/api/checkout" should have been made
|
|
37
|
+
And no request to "**/tracking" should have been made
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The matcher converts `*` to `.*` so URL patterns stay BDD-friendly.
|
|
41
|
+
|
|
42
|
+
## Browser dialogs
|
|
43
|
+
|
|
44
|
+
Native `alert`/`confirm`/`prompt` dialogs block tests until handled. Attach a handler **before** the action that triggers the dialog:
|
|
45
|
+
|
|
46
|
+
```gherkin
|
|
47
|
+
Given I will accept the next dialog
|
|
48
|
+
When I click "Delete"
|
|
49
|
+
Then the last dialog message should contain "Are you sure"
|
|
50
|
+
|
|
51
|
+
Given I will dismiss the next dialog
|
|
52
|
+
When I click "Leave page"
|
|
53
|
+
|
|
54
|
+
Given I will accept the next dialog with "user@example.com"
|
|
55
|
+
When I click "Reset password"
|
|
56
|
+
Then the last dialog type should be "prompt"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The most recent dialog is captured on `this._lastDialog` for inspection inside custom step definitions.
|
|
60
|
+
|
|
61
|
+
For scenarios that trigger many confirmation dialogs in a row, use a persistent handler instead of a single-shot `will accept the next dialog`:
|
|
62
|
+
|
|
63
|
+
```gherkin
|
|
64
|
+
Given I accept all confirmation dialogs
|
|
65
|
+
# … many clicks that each fire a confirm() …
|
|
66
|
+
Given I do not accept any confirmation dialogs
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
When recording requests you can match either any method or a specific verb:
|
|
70
|
+
|
|
71
|
+
```gherkin
|
|
72
|
+
Then a request to "**/api/users" should have been made
|
|
73
|
+
And a GET request to "**/api/users" should have been made
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## When to stub vs hit a real backend
|
|
77
|
+
|
|
78
|
+
| Stub | Real backend |
|
|
79
|
+
| --- | --- |
|
|
80
|
+
| Third-party APIs (payments, mail, maps) | Your own happy-path flows |
|
|
81
|
+
| Slow/flaky external services | Schema validation tests |
|
|
82
|
+
| Edge cases (5xx, timeouts, malformed JSON) | End-to-end smoke tests |
|
|
83
|
+
| Tests run without internet | Pre-production smoke |
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Auth State
|
|
2
|
+
|
|
3
|
+
Logging in once per scenario adds 1–3 seconds per test. Varbase E2E uses Playwright's `storageState` API to **save the cookies + localStorage of an authenticated session** and replay it in later scenarios — instant sign-in, no UI flow.
|
|
4
|
+
|
|
5
|
+
## Save once, restore everywhere
|
|
6
|
+
|
|
7
|
+
### One-off setup feature
|
|
8
|
+
|
|
9
|
+
`tests/features/_setup-auth.feature`:
|
|
10
|
+
|
|
11
|
+
```gherkin
|
|
12
|
+
@auth-setup
|
|
13
|
+
Feature: Auth state setup
|
|
14
|
+
|
|
15
|
+
Scenario: Save admin auth state
|
|
16
|
+
Given I am on "/login"
|
|
17
|
+
When I fill in "admin@example.com" for "Email"
|
|
18
|
+
And I fill in "secret" for "Password"
|
|
19
|
+
And I press "Sign in"
|
|
20
|
+
And I wait until the URL contains "/dashboard"
|
|
21
|
+
Then I save the auth state to "tests/auth/admin.json"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Run once:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npx cucumber-js --tags @auth-setup
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Use in regular scenarios
|
|
31
|
+
|
|
32
|
+
```gherkin
|
|
33
|
+
Feature: Admin dashboard
|
|
34
|
+
|
|
35
|
+
Background:
|
|
36
|
+
Given I restore the auth state from "tests/auth/admin.json"
|
|
37
|
+
And I am on "/dashboard"
|
|
38
|
+
|
|
39
|
+
Scenario: Admin sees user list
|
|
40
|
+
Then "<#user-list>" should be visible
|
|
41
|
+
And "<#user-list>" should have a count of 5 within 5 seconds
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`I restore the auth state from "..."` swaps the current browser context for a fresh one with the saved cookies/localStorage. The tracker init script is reinstalled automatically so smart waits keep working.
|
|
45
|
+
|
|
46
|
+
## Clearing auth
|
|
47
|
+
|
|
48
|
+
```gherkin
|
|
49
|
+
Given I clear the auth state
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Removes cookies and clears `localStorage` / `sessionStorage` on the current page.
|
|
53
|
+
|
|
54
|
+
## Multiple roles
|
|
55
|
+
|
|
56
|
+
Save one file per role:
|
|
57
|
+
|
|
58
|
+
- `tests/auth/admin.json`
|
|
59
|
+
- `tests/auth/editor.json`
|
|
60
|
+
- `tests/auth/customer.json`
|
|
61
|
+
|
|
62
|
+
Then pick a role per scenario:
|
|
63
|
+
|
|
64
|
+
```gherkin
|
|
65
|
+
Scenario: Editor cannot delete
|
|
66
|
+
Given I restore the auth state from "tests/auth/editor.json"
|
|
67
|
+
When I am on "/admin/products"
|
|
68
|
+
Then the "Delete" button should not be visible
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Caveats
|
|
72
|
+
|
|
73
|
+
- Session keys signed against IP or User-Agent will reject the restored cookie. Use a longer session lifetime in test environments.
|
|
74
|
+
- CSRF tokens stored on form pages need to be re-fetched after restore. Use `Given I am on "/path"` after the restore step.
|
|
75
|
+
- Two-factor flows cannot be replayed — keep an environment-specific TOTP bypass behind a feature flag.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Clock Mocking
|
|
2
|
+
|
|
3
|
+
Time-dependent UI is hard to test in real time: a "5 minutes ago" label takes 5 minutes to verify, a session-timeout warning sits idle for 14 minutes before firing. Varbase E2E wraps Playwright's `page.clock` API in BDD steps so scenarios can advance virtual time at full speed.
|
|
4
|
+
|
|
5
|
+
## Install a fake clock
|
|
6
|
+
|
|
7
|
+
```gherkin
|
|
8
|
+
Given the system time is "2026-05-08T10:00:00Z"
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
After this step, `Date.now()` inside the page returns the fake time. Pending `setTimeout` and `setInterval` callbacks queue but do not fire until you advance the clock.
|
|
12
|
+
|
|
13
|
+
## Advance time
|
|
14
|
+
|
|
15
|
+
```gherkin
|
|
16
|
+
When I advance the clock by 500 ms
|
|
17
|
+
When I advance the clock by 30 seconds
|
|
18
|
+
When I advance the clock by 15 minutes
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Each advance fires every timer scheduled within the new interval, in order, in real-time speed (i.e. instantly).
|
|
22
|
+
|
|
23
|
+
## Pause / resume / set
|
|
24
|
+
|
|
25
|
+
```gherkin
|
|
26
|
+
When I pause the clock
|
|
27
|
+
When I resume the clock
|
|
28
|
+
When I set the system time to "2026-12-31T23:59:55Z"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`pause` freezes time at the current value. `set` jumps to a new instant **without** firing the timers in between (use `advance` if you want them to fire).
|
|
32
|
+
|
|
33
|
+
## Recipe: relative-time labels
|
|
34
|
+
|
|
35
|
+
```gherkin
|
|
36
|
+
Scenario: "5 minutes ago" updates as time passes
|
|
37
|
+
Given the system time is "2026-05-08T10:00:00Z"
|
|
38
|
+
And I am on "/feed"
|
|
39
|
+
And "<.timestamp>" should have text "just now"
|
|
40
|
+
When I advance the clock by 5 minutes
|
|
41
|
+
Then "<.timestamp>" should have text "5 minutes ago"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Recipe: session timeout
|
|
45
|
+
|
|
46
|
+
```gherkin
|
|
47
|
+
Scenario: Idle warning fires at 14 minutes
|
|
48
|
+
Given the system time is "2026-05-08T10:00:00Z"
|
|
49
|
+
And I restore the auth state from "tests/auth/admin.json"
|
|
50
|
+
And I am on "/dashboard"
|
|
51
|
+
When I advance the clock by 14 minutes
|
|
52
|
+
Then "<#session-warning>" should be visible
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Caveats
|
|
56
|
+
|
|
57
|
+
- `requestAnimationFrame` and `MutationObserver` are not on the fake clock. Animations driven by `rAF` keep playing in real time.
|
|
58
|
+
- The Date constructor reads the fake clock, but server timestamps embedded at page load do not change.
|
|
59
|
+
- Install the clock **before** loading the page if you need scripts to see the fake time on first execution.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# API Testing
|
|
2
|
+
|
|
3
|
+
Varbase E2E scenarios can talk to an HTTP API directly without leaving the cucumber-js runtime. The browser context's cookie jar is shared, so a UI login can be followed by REST-level assertions in the same scenario.
|
|
4
|
+
|
|
5
|
+
## Setting headers and base URL
|
|
6
|
+
|
|
7
|
+
```gherkin
|
|
8
|
+
Given the API base URL is "https://api.example.com"
|
|
9
|
+
And I set the header "Authorization" to "Bearer abc123"
|
|
10
|
+
And I set the following headers:
|
|
11
|
+
| X-Request-ID | test-123 |
|
|
12
|
+
| Accept | application/json |
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Sending requests
|
|
16
|
+
|
|
17
|
+
```gherkin
|
|
18
|
+
When I send a GET request to "/users"
|
|
19
|
+
When I send a POST request to "/users" with body:
|
|
20
|
+
"""
|
|
21
|
+
{"name": "Alice", "email": "alice@example.com"}
|
|
22
|
+
"""
|
|
23
|
+
When I send a PUT request to "/users/1" with form data:
|
|
24
|
+
"""
|
|
25
|
+
name=Alice&role=admin
|
|
26
|
+
"""
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Asserting responses
|
|
30
|
+
|
|
31
|
+
```gherkin
|
|
32
|
+
Then the API response code should be 200
|
|
33
|
+
And the API response should contain "Alice"
|
|
34
|
+
And the API response should not contain "<script>"
|
|
35
|
+
And the response header "content-type" should contain "json"
|
|
36
|
+
And the response should be valid JSON
|
|
37
|
+
And the JSON property "user.name" should be "Alice"
|
|
38
|
+
And the JSON response should have property "user.id"
|
|
39
|
+
And the JSON response should not have property "password"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Combining UI and API
|
|
43
|
+
|
|
44
|
+
```gherkin
|
|
45
|
+
Scenario: Order placed via UI shows up in API
|
|
46
|
+
Given I restore the auth state from "tests/auth/customer.json"
|
|
47
|
+
And I am on "/cart"
|
|
48
|
+
When I press "Place Order"
|
|
49
|
+
And I wait until the URL contains "/orders/"
|
|
50
|
+
When I send a GET request to "/api/orders?latest=1"
|
|
51
|
+
Then the API response code should be 200
|
|
52
|
+
And the JSON property "orders.0.status" should be "Pending"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Date placeholders
|
|
56
|
+
|
|
57
|
+
API bodies often need fresh dates. Use `[relative:OFFSET#FORMAT]` tokens — they're resolved before the request is sent:
|
|
58
|
+
|
|
59
|
+
```gherkin
|
|
60
|
+
Given I set the request body to '{"start": "[relative:now#YYYY-MM-DD]", "end": "[relative:+7 days#YYYY-MM-DD]"}'
|
|
61
|
+
When I send a POST request to "/api/bookings"
|
|
62
|
+
Then the API response code should be 201
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Other request shapes
|
|
66
|
+
|
|
67
|
+
Use a data-table when the body is a flat key/value map:
|
|
68
|
+
|
|
69
|
+
```gherkin
|
|
70
|
+
When I send a POST request to "/users" with values:
|
|
71
|
+
| name | Alice |
|
|
72
|
+
| email | alice@example.com |
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Assert against an exact JSON shape with a doc string:
|
|
76
|
+
|
|
77
|
+
```gherkin
|
|
78
|
+
Then the API response should contain json:
|
|
79
|
+
"""
|
|
80
|
+
{"id": 1, "name": "Alice"}
|
|
81
|
+
"""
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Substitute placeholders captured from earlier responses or scenario data:
|
|
85
|
+
|
|
86
|
+
```gherkin
|
|
87
|
+
Given I set placeholder "{{userId}}" to "42"
|
|
88
|
+
When I send a GET request to "/users/{{userId}}"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
For HTTP basic auth on the next page navigation, see also `path.steps.js`:
|
|
92
|
+
|
|
93
|
+
```gherkin
|
|
94
|
+
Given the basic authentication with the username "admin" and the password "secret"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Short-form REST steps
|
|
98
|
+
|
|
99
|
+
A parallel short-form lives in `rest.steps.js` for quick smoke tests:
|
|
100
|
+
|
|
101
|
+
```gherkin
|
|
102
|
+
Given a REST header "Authorization" with value "Bearer abc123"
|
|
103
|
+
When I send a REST "GET" request to "/api/users"
|
|
104
|
+
Then the REST response status code should be 200
|
|
105
|
+
And the REST response should contain "Alice"
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Print response (debugging only)
|
|
109
|
+
|
|
110
|
+
```gherkin
|
|
111
|
+
Then print API response
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Outputs status, headers, and body to stdout. Strip before committing.
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Accessibility (a11y)
|
|
2
|
+
|
|
3
|
+
Varbase E2E ships **two layers** of accessibility coverage:
|
|
4
|
+
|
|
5
|
+
1. **Custom JS probes** — fast, dependency-free assertions for POUR
|
|
6
|
+
fundamentals (Perceivable, Operable, Understandable, Robust). Each
|
|
7
|
+
probe maps to a specific WCAG 2.1 / 2.2 success criterion.
|
|
8
|
+
2. **axe-core full audit** — official Deque engine (`@axe-core/playwright`
|
|
9
|
+
+ `axe-core`). Runs ~100 rules covering WCAG 2.0 / 2.1 / 2.2 levels
|
|
10
|
+
A, AA, AAA plus best-practice and experimental rules. Each violation
|
|
11
|
+
carries an `impact` (`minor` / `moderate` / `serious` / `critical`)
|
|
12
|
+
for triage.
|
|
13
|
+
|
|
14
|
+
## Standards & references
|
|
15
|
+
|
|
16
|
+
* [WCAG 2.1](https://www.w3.org/TR/WCAG21/) — current baseline most laws cite.
|
|
17
|
+
* [WCAG 2.2](https://www.w3.org/TR/WCAG22/) — September 2023 update.
|
|
18
|
+
* [W3C WAI](https://www.w3.org/WAI/) — Web Accessibility Initiative overview.
|
|
19
|
+
* [W3C Evaluation Tools List](https://www.w3.org/WAI/test-evaluate/tools/list/)
|
|
20
|
+
* [MDN — Accessibility](https://developer.mozilla.org/en-US/docs/Web/Accessibility)
|
|
21
|
+
* [axe-core rule descriptions](https://github.com/dequelabs/axe-core/blob/develop/doc/rule-descriptions.md)
|
|
22
|
+
* [Deque Labs](https://github.com/dequelabs/) — axe-core, axe-linter, IDE plugins.
|
|
23
|
+
|
|
24
|
+
## Page structure
|
|
25
|
+
|
|
26
|
+
```gherkin
|
|
27
|
+
Then the page should declare a language
|
|
28
|
+
And the page language should be "en"
|
|
29
|
+
And the page should have a main landmark
|
|
30
|
+
And the page should have a navigation landmark
|
|
31
|
+
And the page should have exactly one h1
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Images & form labels
|
|
35
|
+
|
|
36
|
+
```gherkin
|
|
37
|
+
Then every image should have an alt attribute
|
|
38
|
+
And every form field should have an accessible label
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`every image` allows empty `alt=""` (decorative) and `role="presentation"`. Missing alt fails.
|
|
42
|
+
|
|
43
|
+
`every form field` counts as labelled when **any** of the following is true:
|
|
44
|
+
- An `aria-label` attribute is present
|
|
45
|
+
- An `aria-labelledby` attribute resolves to an element
|
|
46
|
+
- A `<label for="...">` matches the field's id
|
|
47
|
+
- The field is wrapped by a `<label>`
|
|
48
|
+
|
|
49
|
+
Submit/reset/button/image inputs and `type="hidden"` are exempt.
|
|
50
|
+
|
|
51
|
+
## Focus assertions
|
|
52
|
+
|
|
53
|
+
```gherkin
|
|
54
|
+
When I focus on the element "#email"
|
|
55
|
+
Then the focused element should match "#email"
|
|
56
|
+
And the focused element should be labeled "Email"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The labeled assertion resolves the accessible name in priority order:
|
|
60
|
+
1. `aria-label`
|
|
61
|
+
2. `aria-labelledby` referenced element
|
|
62
|
+
3. `<label for="...">` text
|
|
63
|
+
4. Wrapping `<label>` text
|
|
64
|
+
5. `value` / `placeholder` / `title` / `textContent`
|
|
65
|
+
|
|
66
|
+
## Keyboard navigation
|
|
67
|
+
|
|
68
|
+
The `keyboard.steps.js` file exposes:
|
|
69
|
+
|
|
70
|
+
```gherkin
|
|
71
|
+
When I press the "Tab" key
|
|
72
|
+
When I press the "Tab" key 3 times
|
|
73
|
+
When I press the "Enter" key
|
|
74
|
+
When I press the "Escape" key
|
|
75
|
+
When I press the "ArrowDown" key
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Combine with focus assertions:
|
|
79
|
+
|
|
80
|
+
```gherkin
|
|
81
|
+
Scenario: Tab order is correct
|
|
82
|
+
Given I am on "/login"
|
|
83
|
+
And I focus on the element "body"
|
|
84
|
+
When I press the "Tab" key
|
|
85
|
+
Then the focused element should match "#email"
|
|
86
|
+
When I press the "Tab" key
|
|
87
|
+
Then the focused element should match "#password"
|
|
88
|
+
When I press the "Tab" key
|
|
89
|
+
Then the focused element should match "button[type=submit]"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## WCAG hygiene probes
|
|
93
|
+
|
|
94
|
+
Lightweight checks that map to specific WCAG criteria. No axe-core required.
|
|
95
|
+
|
|
96
|
+
```gherkin
|
|
97
|
+
Then the page should have a title
|
|
98
|
+
And user zoom should be allowed
|
|
99
|
+
And the heading hierarchy should be valid
|
|
100
|
+
And the page should have a skip link
|
|
101
|
+
And every button should have an accessible name
|
|
102
|
+
And every link should have an accessible name
|
|
103
|
+
And no element should have a positive tabindex
|
|
104
|
+
And every ARIA reference should resolve
|
|
105
|
+
And every ARIA role should be valid
|
|
106
|
+
And required fields should be consistently marked
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
| Step | WCAG SC |
|
|
110
|
+
| --- | --- |
|
|
111
|
+
| `the heading hierarchy should be valid` | 1.3.1, 2.4.6 |
|
|
112
|
+
| `the page should have a skip link` | 2.4.1 |
|
|
113
|
+
| `every button should have an accessible name` | 4.1.2 |
|
|
114
|
+
| `every link should have an accessible name` | 2.4.4, 4.1.2 |
|
|
115
|
+
| `no element should have a positive tabindex` | 2.4.3 |
|
|
116
|
+
| `every ARIA reference should resolve` | 4.1.2 |
|
|
117
|
+
| `every ARIA role should be valid` | 4.1.2 |
|
|
118
|
+
| `the page should have a title` | 2.4.2 |
|
|
119
|
+
| `user zoom should be allowed` | 1.4.4, 1.4.10 |
|
|
120
|
+
| `required fields should be consistently marked` | 3.3.2 |
|
|
121
|
+
|
|
122
|
+
## axe-core full audit
|
|
123
|
+
|
|
124
|
+
```gherkin
|
|
125
|
+
Then the page should pass an accessibility audit
|
|
126
|
+
And the page should pass an accessibility audit at level "AA"
|
|
127
|
+
And the page should pass an accessibility audit at level "AAA"
|
|
128
|
+
And the page should have no critical accessibility violations
|
|
129
|
+
And the page should have no serious accessibility violations
|
|
130
|
+
And the element "main" should pass an accessibility audit
|
|
131
|
+
And the page should pass an accessibility audit excluding "iframe.payment"
|
|
132
|
+
And the page should not violate the accessibility rule "color-contrast"
|
|
133
|
+
And the page should pass the accessibility rules "image-alt, label, button-name"
|
|
134
|
+
Then I print accessibility violations
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Default audit level is **AA** — required by EU Web Accessibility
|
|
138
|
+
Directive, US Section 508, UK PSBAR. Use `at level "AAA"` for stricter
|
|
139
|
+
gates.
|
|
140
|
+
|
|
141
|
+
Common axe rule ids worth pinning: `color-contrast`, `image-alt`,
|
|
142
|
+
`label`, `link-name`, `button-name`, `heading-order`, `landmark-one-main`,
|
|
143
|
+
`region`, `aria-valid-attr`, `tabindex`, `bypass`, `frame-title`,
|
|
144
|
+
`meta-viewport`, `valid-lang`, `duplicate-id-aria`, `list`, `listitem`.
|
|
145
|
+
|
|
146
|
+
## Recommended baseline
|
|
147
|
+
|
|
148
|
+
For a public site under WCAG 2.1 AA obligations, gate every page on:
|
|
149
|
+
|
|
150
|
+
```gherkin
|
|
151
|
+
Then the page should pass an accessibility audit at level "AA"
|
|
152
|
+
And the page should have a title
|
|
153
|
+
And the page should have a main landmark
|
|
154
|
+
And the heading hierarchy should be valid
|
|
155
|
+
And user zoom should be allowed
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
For an admin / authenticated UI where some third-party widgets are
|
|
159
|
+
known-bad, gate on the impact-based step instead:
|
|
160
|
+
|
|
161
|
+
```gherkin
|
|
162
|
+
Then the page should have no critical accessibility violations
|
|
163
|
+
And the page should have no serious accessibility violations
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Tag scenarios `@a11y` so CI can run the accessibility suite in
|
|
167
|
+
isolation: `npx cucumber-js --tags "@a11y"`.
|