@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,423 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Varbase E2E — Screenshot step definitions
|
|
5
|
+
//
|
|
6
|
+
// Saves PNG (and matching .html) screenshots to a configurable directory.
|
|
7
|
+
// Supports manual steps, automatic capture on failed step, and per-step
|
|
8
|
+
// capture for scenarios tagged @screenshots.
|
|
9
|
+
//
|
|
10
|
+
// Configuration: resolved per-scenario with this priority:
|
|
11
|
+
// 1. process.env.VARBASE_E2E_SCREENSHOT_* (CI / shell — highest)
|
|
12
|
+
// 2. cucumber.js worldParameters.screenshot.* (project-level defaults)
|
|
13
|
+
// 3. built-in defaults
|
|
14
|
+
//
|
|
15
|
+
// Supported env vars / worldParameters keys:
|
|
16
|
+
// VARBASE_E2E_SCREENSHOT_DIR dir default "./screenshots"
|
|
17
|
+
// VARBASE_E2E_SCREENSHOT_PURGE purge "true"/"false" — default false
|
|
18
|
+
// VARBASE_E2E_SCREENSHOT_ON_FAILED onFailed default true
|
|
19
|
+
// VARBASE_E2E_SCREENSHOT_ON_EVERY_STEP onEveryStep default false
|
|
20
|
+
// VARBASE_E2E_SCREENSHOT_FULLSCREEN alwaysFullscreen default false
|
|
21
|
+
// VARBASE_E2E_SCREENSHOT_FAILED_PREFIX failedPrefix default "failed_"
|
|
22
|
+
// VARBASE_E2E_SCREENSHOT_PATTERN filenamePattern default "{datetime}.{feature_file}.feature_{step_line}.{ext}"
|
|
23
|
+
// VARBASE_E2E_SCREENSHOT_PATTERN_FAIL filenamePatternFailed default "{failed_prefix}{datetime}.{feature_file}.feature_{step_line}.{ext}"
|
|
24
|
+
// VARBASE_E2E_SCREENSHOT_INFO_TYPES infoTypes comma list: url,feature,step,datetime
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
const { When, Then, Before, After, AfterStep, BeforeAll } = require('@cucumber/cucumber');
|
|
28
|
+
const fs = require('fs');
|
|
29
|
+
const path = require('path');
|
|
30
|
+
const { pad } = require('./varbase-e2e');
|
|
31
|
+
|
|
32
|
+
function pick(envVal, paramVal, fallback) {
|
|
33
|
+
if (envVal !== undefined && envVal !== '') return envVal;
|
|
34
|
+
if (paramVal !== undefined && paramVal !== '' && paramVal !== null) return paramVal;
|
|
35
|
+
return fallback;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function asBool(v, fallback) {
|
|
39
|
+
if (v === undefined || v === null || v === '') return fallback;
|
|
40
|
+
if (typeof v === 'boolean') return v;
|
|
41
|
+
const s = String(v).toLowerCase();
|
|
42
|
+
return s === '1' || s === 'true' || s === 'yes';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function resolveConfig(parameters) {
|
|
46
|
+
const p = (parameters && parameters.screenshot) || {};
|
|
47
|
+
return {
|
|
48
|
+
dir: pick(process.env.VARBASE_E2E_SCREENSHOT_DIR, p.dir, './screenshots'),
|
|
49
|
+
purge: asBool(pick(process.env.VARBASE_E2E_SCREENSHOT_PURGE, p.purge, undefined), false),
|
|
50
|
+
onFailed: asBool(pick(process.env.VARBASE_E2E_SCREENSHOT_ON_FAILED, p.onFailed, undefined), true),
|
|
51
|
+
onEveryStep: asBool(pick(process.env.VARBASE_E2E_SCREENSHOT_ON_EVERY_STEP, p.onEveryStep, undefined), false),
|
|
52
|
+
alwaysFullscreen: asBool(pick(process.env.VARBASE_E2E_SCREENSHOT_FULLSCREEN, p.alwaysFullscreen, undefined), false),
|
|
53
|
+
failedPrefix: pick(process.env.VARBASE_E2E_SCREENSHOT_FAILED_PREFIX, p.failedPrefix, 'failed_'),
|
|
54
|
+
filenamePattern: pick(
|
|
55
|
+
process.env.VARBASE_E2E_SCREENSHOT_PATTERN,
|
|
56
|
+
p.filenamePattern,
|
|
57
|
+
'{datetime}.{feature_file}.feature_{step_line}.{ext}'
|
|
58
|
+
),
|
|
59
|
+
filenamePatternFailed: pick(
|
|
60
|
+
process.env.VARBASE_E2E_SCREENSHOT_PATTERN_FAIL,
|
|
61
|
+
p.filenamePatternFailed,
|
|
62
|
+
'{failed_prefix}{datetime}.{feature_file}.feature_{step_line}.{ext}'
|
|
63
|
+
),
|
|
64
|
+
infoTypes: String(pick(process.env.VARBASE_E2E_SCREENSHOT_INFO_TYPES, p.infoTypes, ''))
|
|
65
|
+
.split(',')
|
|
66
|
+
.map(s => s.trim())
|
|
67
|
+
.filter(Boolean),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Filename tokenizer
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
function formatDate(d, format) {
|
|
75
|
+
const yyyy = d.getFullYear();
|
|
76
|
+
const MM = pad(d.getMonth() + 1, 2);
|
|
77
|
+
const dd = pad(d.getDate(), 2);
|
|
78
|
+
const HH = pad(d.getHours(), 2);
|
|
79
|
+
const mm = pad(d.getMinutes(), 2);
|
|
80
|
+
const ss = pad(d.getSeconds(), 2);
|
|
81
|
+
if (!format || format === 'Ymd_His') return `${yyyy}${MM}${dd}_${HH}${mm}${ss}`;
|
|
82
|
+
return format
|
|
83
|
+
.replace(/YYYY/g, yyyy)
|
|
84
|
+
.replace(/MM/g, MM)
|
|
85
|
+
.replace(/DD/g, dd)
|
|
86
|
+
.replace(/HH/g, HH)
|
|
87
|
+
.replace(/mm/g, mm)
|
|
88
|
+
.replace(/ss/g, ss);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function urlToken(url, qualifier) {
|
|
92
|
+
if (!url) return '';
|
|
93
|
+
let parsed;
|
|
94
|
+
try { parsed = new URL(url); } catch { return url; }
|
|
95
|
+
let out;
|
|
96
|
+
switch (qualifier) {
|
|
97
|
+
case 'origin': out = `${parsed.protocol}//${parsed.host}`; break;
|
|
98
|
+
case 'domain': out = parsed.host; break;
|
|
99
|
+
case 'path': out = parsed.pathname.replace(/^\/+|\/+$/g, ''); break;
|
|
100
|
+
case 'query': out = parsed.search.replace(/^\?/, ''); break;
|
|
101
|
+
case 'fragment': out = parsed.hash.replace(/^#/, ''); break;
|
|
102
|
+
case 'relative':
|
|
103
|
+
out = parsed.pathname.replace(/^\/+|\/+$/g, '') + parsed.search + parsed.hash;
|
|
104
|
+
break;
|
|
105
|
+
default: out = url;
|
|
106
|
+
}
|
|
107
|
+
return out.replace(/[^\w\-]+/g, '_');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function replaceTokens(pattern, data) {
|
|
111
|
+
return pattern.replace(/\{([^}]+)\}/g, (match, raw) => {
|
|
112
|
+
const [head, format] = raw.split(':');
|
|
113
|
+
const parts = head.split('_');
|
|
114
|
+
const name = parts.shift();
|
|
115
|
+
const qualifier = parts.join('_') || null;
|
|
116
|
+
|
|
117
|
+
if (name === 'ext') return data.ext || 'html';
|
|
118
|
+
if (name === 'failed' && qualifier === 'prefix') return data.failed_prefix || match;
|
|
119
|
+
if (name === 'feature' && qualifier === 'file') {
|
|
120
|
+
return data.feature_file ? path.basename(data.feature_file, '.feature') : match;
|
|
121
|
+
}
|
|
122
|
+
if (name === 'step') {
|
|
123
|
+
if (qualifier === 'line') {
|
|
124
|
+
const n = Number(data.step_line);
|
|
125
|
+
if (Number.isNaN(n)) return match;
|
|
126
|
+
// sprintf-like: {step_line:%03d}
|
|
127
|
+
if (format && /^%\d*d$/.test(format)) {
|
|
128
|
+
const w = parseInt(format.match(/\d+/)?.[0] || '0', 10);
|
|
129
|
+
return pad(n, w);
|
|
130
|
+
}
|
|
131
|
+
return String(n);
|
|
132
|
+
}
|
|
133
|
+
if (qualifier === 'name' && data.step_name) {
|
|
134
|
+
return String(data.step_name).replace(/[ "]+/g, c => (c === '"' ? '' : '_'));
|
|
135
|
+
}
|
|
136
|
+
return match;
|
|
137
|
+
}
|
|
138
|
+
if (name === 'url') return urlToken(data.url, qualifier);
|
|
139
|
+
if (name === 'datetime') return data.timestamp ? formatDate(new Date(data.timestamp), format) : match;
|
|
140
|
+
return match;
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
// State tracked per-scenario
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
let purgedOnce = false;
|
|
148
|
+
|
|
149
|
+
function ensureDir(dir) {
|
|
150
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function purgeDir(dir) {
|
|
154
|
+
if (!fs.existsSync(dir)) return;
|
|
155
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
156
|
+
const full = path.join(dir, entry);
|
|
157
|
+
const stat = fs.lstatSync(full);
|
|
158
|
+
if (stat.isDirectory()) fs.rmSync(full, { recursive: true, force: true });
|
|
159
|
+
else fs.unlinkSync(full);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function buildInfo(cfg, ctx) {
|
|
164
|
+
const lines = [];
|
|
165
|
+
for (const type of cfg.infoTypes) {
|
|
166
|
+
if (type === 'url' && ctx.url) lines.push(`Current URL: ${ctx.url}`);
|
|
167
|
+
if (type === 'feature' && ctx.featureTitle) lines.push(`Feature: ${ctx.featureTitle}`);
|
|
168
|
+
if (type === 'step' && ctx.stepText) lines.push(`Step: ${ctx.stepText} (line ${ctx.stepLine || '?'})`);
|
|
169
|
+
if (type === 'datetime') lines.push(`Datetime: ${formatDate(new Date(), 'YYYY-MM-DD HH:mm:ss')}`);
|
|
170
|
+
}
|
|
171
|
+
return lines.join('\n');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function makeFileName(cfg, ctx, ext, explicitName, isFailed) {
|
|
175
|
+
let pattern;
|
|
176
|
+
if (isFailed) pattern = cfg.filenamePatternFailed;
|
|
177
|
+
else if (explicitName) pattern = explicitName;
|
|
178
|
+
else pattern = cfg.filenamePattern;
|
|
179
|
+
|
|
180
|
+
if (!pattern.endsWith('.{ext}')) pattern += '.{ext}';
|
|
181
|
+
|
|
182
|
+
return replaceTokens(pattern, {
|
|
183
|
+
ext,
|
|
184
|
+
failed_prefix: cfg.failedPrefix,
|
|
185
|
+
feature_file: ctx.featureFile,
|
|
186
|
+
step_line: ctx.stepLine,
|
|
187
|
+
step_name: ctx.stepText,
|
|
188
|
+
timestamp: Date.now(),
|
|
189
|
+
url: ctx.url,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function captureScreenshot(world, options = {}) {
|
|
194
|
+
const cfg = resolveConfig(world.parameters);
|
|
195
|
+
if (!world.page) return;
|
|
196
|
+
|
|
197
|
+
const fullscreen = Boolean(options.fullscreen || cfg.alwaysFullscreen);
|
|
198
|
+
const ctx = world.__screenshotCtx || {};
|
|
199
|
+
|
|
200
|
+
let url = '';
|
|
201
|
+
try { url = world.page.url(); } catch { /* page closed */ }
|
|
202
|
+
ctx.url = url;
|
|
203
|
+
|
|
204
|
+
ensureDir(cfg.dir);
|
|
205
|
+
|
|
206
|
+
// HTML capture (works for all drivers, Playwright content()).
|
|
207
|
+
try {
|
|
208
|
+
let html = await world.page.content();
|
|
209
|
+
const info = buildInfo(cfg, ctx);
|
|
210
|
+
if (info) html = info.replace(/\n/g, '<br/>\n') + '<hr/>\n' + html;
|
|
211
|
+
const htmlName = makeFileName(cfg, ctx, 'html', options.filename, options.isFailed);
|
|
212
|
+
fs.writeFileSync(path.join(cfg.dir, htmlName), html);
|
|
213
|
+
} catch { /* no content yet */ }
|
|
214
|
+
|
|
215
|
+
// PNG capture.
|
|
216
|
+
try {
|
|
217
|
+
const pngName = makeFileName(cfg, ctx, 'png', options.filename, options.isFailed);
|
|
218
|
+
await world.page.screenshot({
|
|
219
|
+
path: path.join(cfg.dir, pngName),
|
|
220
|
+
fullPage: fullscreen,
|
|
221
|
+
});
|
|
222
|
+
} catch { /* driver without screenshot support */ }
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ---------------------------------------------------------------------------
|
|
226
|
+
// Hooks
|
|
227
|
+
// ---------------------------------------------------------------------------
|
|
228
|
+
BeforeAll(function () {
|
|
229
|
+
const cfg = resolveConfig(this && this.parameters);
|
|
230
|
+
if (cfg.purge && !purgedOnce) {
|
|
231
|
+
purgeDir(cfg.dir);
|
|
232
|
+
purgedOnce = true;
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
Before(function (scenario) {
|
|
237
|
+
const pickledTags = (scenario.pickle && scenario.pickle.tags) || [];
|
|
238
|
+
this.__screenshotCtx = {
|
|
239
|
+
hasScreenshotsTag: pickledTags.some(t => t.name === '@screenshots'),
|
|
240
|
+
featureFile: (scenario.gherkinDocument && scenario.gherkinDocument.uri) || '',
|
|
241
|
+
featureTitle: (scenario.gherkinDocument && scenario.gherkinDocument.feature && scenario.gherkinDocument.feature.name) || '',
|
|
242
|
+
stepText: '',
|
|
243
|
+
stepLine: 0,
|
|
244
|
+
url: '',
|
|
245
|
+
};
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
AfterStep(async function (step) {
|
|
249
|
+
if (!this.__screenshotCtx) return;
|
|
250
|
+
this.__screenshotCtx.stepText = (step.pickleStep && step.pickleStep.text) || '';
|
|
251
|
+
this.__screenshotCtx.stepLine = step.pickleStep && step.pickleStep.astNodeIds ? 0 : 0;
|
|
252
|
+
|
|
253
|
+
if (step.gherkinDocument && step.pickleStep) {
|
|
254
|
+
const astIds = step.pickleStep.astNodeIds || [];
|
|
255
|
+
const steps = [];
|
|
256
|
+
(step.gherkinDocument.feature.children || []).forEach(child => {
|
|
257
|
+
const container = child.scenario || child.background;
|
|
258
|
+
if (container && container.steps) steps.push(...container.steps);
|
|
259
|
+
});
|
|
260
|
+
const match = steps.find(s => astIds.includes(s.id));
|
|
261
|
+
if (match && match.location) this.__screenshotCtx.stepLine = match.location.line;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const cfg = resolveConfig(this.parameters);
|
|
265
|
+
const passed = step.result && step.result.status === 'PASSED';
|
|
266
|
+
|
|
267
|
+
if (!passed && cfg.onFailed) {
|
|
268
|
+
await captureScreenshot(this, { isFailed: true, fullscreen: cfg.alwaysFullscreen });
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (passed && (cfg.onEveryStep || this.__screenshotCtx.hasScreenshotsTag)) {
|
|
272
|
+
await captureScreenshot(this, { fullscreen: cfg.alwaysFullscreen });
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// ---------------------------------------------------------------------------
|
|
277
|
+
// Step definitions
|
|
278
|
+
// ---------------------------------------------------------------------------
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Save a screenshot at the current viewport size.
|
|
282
|
+
*
|
|
283
|
+
* Example #1: Then I save screenshot
|
|
284
|
+
* Example #2: When I save screenshot
|
|
285
|
+
* Example #3: Then save screenshot
|
|
286
|
+
* Example #4: Given I am on "/about-us"
|
|
287
|
+
* Then I save screenshot
|
|
288
|
+
* Example #5: When I go to homepage
|
|
289
|
+
* Then I save screenshot
|
|
290
|
+
* Example #6: Given I am on "/news"
|
|
291
|
+
* When I scroll to "#latest"
|
|
292
|
+
* Then I save screenshot
|
|
293
|
+
* Example #7: Given I am on "/contact"
|
|
294
|
+
* When I fill in "email" with "info@vardot.com"
|
|
295
|
+
* Then I save screenshot
|
|
296
|
+
* Example #8: Given I am on "/search?q=UN"
|
|
297
|
+
* Then I save screenshot
|
|
298
|
+
* Example #9: Then I reload page
|
|
299
|
+
* Then I save screenshot
|
|
300
|
+
* Example #10: Given I am on "/partners/un.org"
|
|
301
|
+
* Then I save screenshot
|
|
302
|
+
*/
|
|
303
|
+
When(/^(I |we )*save screenshot$/, async function (pronounCase) {
|
|
304
|
+
await captureScreenshot(this, {});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Save a full-page screenshot (entire scrollable area).
|
|
309
|
+
*
|
|
310
|
+
* Example #1: Then I save fullscreen screenshot
|
|
311
|
+
* Example #2: When I save fullscreen screenshot
|
|
312
|
+
* Example #3: Then save fullscreen screenshot
|
|
313
|
+
* Example #4: Given I am on "/news"
|
|
314
|
+
* Then I save fullscreen screenshot
|
|
315
|
+
* Example #5: Given I am on "/about-us"
|
|
316
|
+
* Then I save fullscreen screenshot
|
|
317
|
+
* Example #6: Given I am on homepage
|
|
318
|
+
* Then I save fullscreen screenshot
|
|
319
|
+
* Example #7: Given I am on "/partners"
|
|
320
|
+
* When I wait 2 seconds
|
|
321
|
+
* Then I save fullscreen screenshot
|
|
322
|
+
* Example #8: Given I am on "/varbase-e2e"
|
|
323
|
+
* Then I save fullscreen screenshot
|
|
324
|
+
* Example #9: Given I am on "/un.org/reports"
|
|
325
|
+
* Then I save fullscreen screenshot
|
|
326
|
+
* Example #10: Given I am on "/long-article"
|
|
327
|
+
* Then I save fullscreen screenshot
|
|
328
|
+
*/
|
|
329
|
+
When(/^(I |we )*save fullscreen screenshot$/, async function (pronounCase) {
|
|
330
|
+
await captureScreenshot(this, { fullscreen: true });
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Resize the viewport to <width> x <height> then save a screenshot.
|
|
335
|
+
*
|
|
336
|
+
* Example #1: Then I save 1440 x 900 screenshot
|
|
337
|
+
* Example #2: Then I save 1200 x 800 screenshot
|
|
338
|
+
* Example #3: Then I save 375 x 667 screenshot
|
|
339
|
+
* Example #4: Then I save 768 x 1024 screenshot
|
|
340
|
+
* Example #5: Then I save 1920 x 1080 screenshot
|
|
341
|
+
* Example #6: Given I am on "/about-us"
|
|
342
|
+
* Then I save 1440 x 900 screenshot
|
|
343
|
+
* Example #7: Given I am on homepage
|
|
344
|
+
* Then I save 375 x 812 screenshot
|
|
345
|
+
* Example #8: Given I am on "/news"
|
|
346
|
+
* Then I save 1024 x 768 screenshot
|
|
347
|
+
* Example #9: Given I am on "/about"
|
|
348
|
+
* Then I save 1366 x 768 screenshot
|
|
349
|
+
* Example #10: Given I am on "/un.org"
|
|
350
|
+
* Then I save 414 x 896 screenshot
|
|
351
|
+
*/
|
|
352
|
+
When(/^(I |we )*save (\d+) x (\d+) screenshot$/, async function (pronounCase, width, height) {
|
|
353
|
+
await this.page.setViewportSize({ width: parseInt(width, 10), height: parseInt(height, 10) });
|
|
354
|
+
await captureScreenshot(this, {});
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Resize the viewport to <width> x <height> then save a full-page screenshot.
|
|
359
|
+
*
|
|
360
|
+
* Example #1: Then I save fullscreen 1440 x 900 screenshot
|
|
361
|
+
* Example #2: Then I save fullscreen 1200 x 800 screenshot
|
|
362
|
+
* Example #3: Then I save fullscreen 375 x 667 screenshot
|
|
363
|
+
* Example #4: Then I save fullscreen 768 x 1024 screenshot
|
|
364
|
+
* Example #5: Then I save fullscreen 1920 x 1080 screenshot
|
|
365
|
+
* Example #6: Given I am on "/about-us"
|
|
366
|
+
* Then I save fullscreen 1440 x 900 screenshot
|
|
367
|
+
* Example #7: Given I am on homepage
|
|
368
|
+
* Then I save fullscreen 375 x 812 screenshot
|
|
369
|
+
* Example #8: Given I am on "/about"
|
|
370
|
+
* Then I save fullscreen 1366 x 768 screenshot
|
|
371
|
+
* Example #9: Given I am on "/un.org"
|
|
372
|
+
* Then I save fullscreen 414 x 896 screenshot
|
|
373
|
+
* Example #10: Given I am on "/long-article"
|
|
374
|
+
* Then I save fullscreen 1440 x 900 screenshot
|
|
375
|
+
*/
|
|
376
|
+
When(/^(I |we )*save fullscreen (\d+) x (\d+) screenshot$/, async function (pronounCase, width, height) {
|
|
377
|
+
await this.page.setViewportSize({ width: parseInt(width, 10), height: parseInt(height, 10) });
|
|
378
|
+
await captureScreenshot(this, { fullscreen: true });
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Save a screenshot using an explicit filename (tokens supported).
|
|
383
|
+
*
|
|
384
|
+
* Example #1: Then I save screenshot with name "homepage.png"
|
|
385
|
+
* Example #2: Then I save screenshot with name "varbase-e2e-home"
|
|
386
|
+
* Example #3: Then I save screenshot with name "un-landing-{datetime}.png"
|
|
387
|
+
* Example #4: Then I save screenshot with name "about-us.png"
|
|
388
|
+
* Example #5: Then I save screenshot with name "{feature_file}_{step_line}"
|
|
389
|
+
* Example #6: Then I save screenshot with name "{url_path}.png"
|
|
390
|
+
* Example #7: Given I am on "/news"
|
|
391
|
+
* Then I save screenshot with name "news-latest.png"
|
|
392
|
+
* Example #8: Given I am on "/about"
|
|
393
|
+
* Then I save screenshot with name "varbase-e2e-index.png"
|
|
394
|
+
* Example #9: Given I am on "/un.org"
|
|
395
|
+
* Then I save screenshot with name "un-home.png"
|
|
396
|
+
* Example #10: Then I save screenshot with name "contact-form-before-submit.png"
|
|
397
|
+
*/
|
|
398
|
+
When(/^(I |we )*save screenshot with name "([^"]*)"$/, async function (pronounCase, filename) {
|
|
399
|
+
await captureScreenshot(this, { filename });
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Save a full-page screenshot using an explicit filename (tokens supported).
|
|
404
|
+
*
|
|
405
|
+
* Example #1: Then I save fullscreen screenshot with name "homepage-full.png"
|
|
406
|
+
* Example #2: Then I save fullscreen screenshot with name "varbase-e2e-home-full"
|
|
407
|
+
* Example #3: Then I save fullscreen screenshot with name "news-{datetime}.png"
|
|
408
|
+
* Example #4: Then I save fullscreen screenshot with name "about-us-full.png"
|
|
409
|
+
* Example #5: Then I save fullscreen screenshot with name "{feature_file}_full.png"
|
|
410
|
+
* Example #6: Then I save fullscreen screenshot with name "{url_path}-full.png"
|
|
411
|
+
* Example #7: Given I am on "/news"
|
|
412
|
+
* Then I save fullscreen screenshot with name "news-full.png"
|
|
413
|
+
* Example #8: Given I am on "/about"
|
|
414
|
+
* Then I save fullscreen screenshot with name "varbase-e2e-full.png"
|
|
415
|
+
* Example #9: Given I am on "/un.org"
|
|
416
|
+
* Then I save fullscreen screenshot with name "un-home-full.png"
|
|
417
|
+
* Example #10: Then I save fullscreen screenshot with name "long-article-full.png"
|
|
418
|
+
*/
|
|
419
|
+
When(/^(I |we )*save fullscreen screenshot with name "([^"]*)"$/, async function (pronounCase, filename) {
|
|
420
|
+
await captureScreenshot(this, { filename, fullscreen: true });
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
module.exports = { resolveConfig, replaceTokens, makeFileName };
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// All scroll step definitions live in this file.
|
|
4
|
+
//
|
|
5
|
+
// Vertical: down / up / to top / to bottom (page or scoped element).
|
|
6
|
+
// Horizontal: right / left / to start / to end (page or scoped element).
|
|
7
|
+
//
|
|
8
|
+
// Default delta when no number is supplied is 350 px.
|
|
9
|
+
|
|
10
|
+
const { When } = require('@cucumber/cucumber');
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Vertical scrolling — page
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Scrolls the page down by a custom number of pixels (default 350).
|
|
18
|
+
*
|
|
19
|
+
* Example #1: And I scroll down
|
|
20
|
+
* Example #2: When I scroll down 800
|
|
21
|
+
* Example #3: And we scroll down 500
|
|
22
|
+
* Example #4: When scrolling down 1200
|
|
23
|
+
*
|
|
24
|
+
*/
|
|
25
|
+
When(/^(I scroll|we scroll|scrolling)? down(?: (\d+))?$/, async function (pronounCase, numValue) {
|
|
26
|
+
await this.page.evaluate((v) => window.scrollBy(0, v), numValue ? parseInt(numValue, 10) : 350);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Scrolls the page up by a custom number of pixels (default 350).
|
|
31
|
+
*
|
|
32
|
+
* Example #1: And I scroll up
|
|
33
|
+
* Example #2: When I scroll up 1000
|
|
34
|
+
* Example #3: And we scroll up 300
|
|
35
|
+
* Example #4: When scrolling up 750
|
|
36
|
+
*
|
|
37
|
+
*/
|
|
38
|
+
When(/^(I scroll|we scroll|scrolling)? up(?: (\d+))?$/, async function (pronounCase, numValue) {
|
|
39
|
+
await this.page.evaluate((v) => window.scrollBy(0, -v), numValue ? parseInt(numValue, 10) : 350);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Scrolls to the very top of the current page.
|
|
44
|
+
*
|
|
45
|
+
* Example #1: When I scroll to top
|
|
46
|
+
* Example #2: And we scroll to the top
|
|
47
|
+
* Example #3: When scrolling to the top of the page
|
|
48
|
+
*
|
|
49
|
+
*/
|
|
50
|
+
When(/^(I scroll|we scroll|scrolling)? to( the)* top( of the page)*$/, async function (pronounCase, theCase, pageCase) {
|
|
51
|
+
await this.page.evaluate(() => window.scrollTo(0, 0));
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Scrolls to the bottom of the current page.
|
|
56
|
+
*
|
|
57
|
+
* Example #1: When I scroll to the bottom
|
|
58
|
+
* Example #2: And we scroll to bottom
|
|
59
|
+
* Example #3: When scrolling to the bottom of the page
|
|
60
|
+
*
|
|
61
|
+
*/
|
|
62
|
+
When(/^(I scroll|we scroll|scrolling)? to( the)* bottom( of the page)*$/, async function (pronounCase, theCase, pageCase) {
|
|
63
|
+
await this.page.evaluate(() => {
|
|
64
|
+
window.scrollTo(0, document.body.scrollHeight);
|
|
65
|
+
document.querySelectorAll('*').forEach(el => {
|
|
66
|
+
const s = window.getComputedStyle(el);
|
|
67
|
+
if ((s.overflowY === 'scroll' || s.overflowY === 'auto') && el.scrollHeight > el.clientHeight) {
|
|
68
|
+
el.scrollTop = el.scrollHeight;
|
|
69
|
+
el.dispatchEvent(new Event('scroll', { bubbles: true }));
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
// Vertical scrolling — scoped to a CSS selector
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Scrolls to the top of a specific element identified by a CSS selector.
|
|
81
|
+
*
|
|
82
|
+
* Example #1: When I scroll to top of "#off-canvas"
|
|
83
|
+
* Example #2: And we scroll to top of "#sidebar"
|
|
84
|
+
* Example #3: When scrolling to top of "#main-container"
|
|
85
|
+
*
|
|
86
|
+
*/
|
|
87
|
+
When(/^(I scroll|we scroll|scrolling)? to top of "([^"]*)"$/, async function (pronounCase, selector) {
|
|
88
|
+
await this.page.locator(selector).evaluate(el => {
|
|
89
|
+
el.scrollTop = 0;
|
|
90
|
+
el.dispatchEvent(new Event('scroll', { bubbles: true }));
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Scrolls to the bottom of a specific element identified by a CSS selector.
|
|
96
|
+
*
|
|
97
|
+
* Example #1: When I scroll to bottom of "#off-canvas"
|
|
98
|
+
* Example #2: And we scroll to bottom of "#sidebar"
|
|
99
|
+
* Example #3: When scrolling to bottom of "#main-container"
|
|
100
|
+
*
|
|
101
|
+
*/
|
|
102
|
+
When(/^(I scroll|we scroll|scrolling)? to bottom of "([^"]*)"$/, async function (pronounCase, selector) {
|
|
103
|
+
await this.page.locator(selector).evaluate(el => {
|
|
104
|
+
el.scrollTop = el.scrollHeight;
|
|
105
|
+
el.dispatchEvent(new Event('scroll', { bubbles: true }));
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
// Horizontal scrolling — page
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Scrolls the page right by a custom number of pixels (default 350).
|
|
115
|
+
*
|
|
116
|
+
* Example #1: And I scroll right
|
|
117
|
+
* Example #2: When I scroll right 1000
|
|
118
|
+
* Example #3: And we scroll right 300
|
|
119
|
+
* Example #4: When scrolling right 750
|
|
120
|
+
*
|
|
121
|
+
*/
|
|
122
|
+
When(/^(I scroll|we scroll|scrolling)? right(?: (\d+))?$/, async function (pronounCase, numValue) {
|
|
123
|
+
await this.page.evaluate((v) => window.scrollBy(v, 0), numValue ? parseInt(numValue, 10) : 350);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Scrolls the page left by a custom number of pixels (default 350).
|
|
128
|
+
*
|
|
129
|
+
* Example #1: And I scroll left
|
|
130
|
+
* Example #2: When I scroll left 800
|
|
131
|
+
* Example #3: And we scroll left 500
|
|
132
|
+
* Example #4: When scrolling left 1200
|
|
133
|
+
*
|
|
134
|
+
*/
|
|
135
|
+
When(/^(I scroll|we scroll|scrolling)? left(?: (\d+))?$/, async function (pronounCase, numValue) {
|
|
136
|
+
await this.page.evaluate((v) => window.scrollBy(-v, 0), numValue ? parseInt(numValue, 10) : 350);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Scrolls to the start (horizontal origin) of the page.
|
|
141
|
+
*
|
|
142
|
+
* Example #1: When I scroll to start
|
|
143
|
+
* Example #2: And we scroll to the start
|
|
144
|
+
* Example #3: When scrolling to the start of the page
|
|
145
|
+
*
|
|
146
|
+
*/
|
|
147
|
+
When(/^(I scroll|we scroll|scrolling)? to( the)* start( of the page)*$/, async function (pronounCase, theCase, pageCase) {
|
|
148
|
+
await this.page.evaluate(() => window.scrollTo(0, window.scrollY));
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Scrolls to the end (horizontal maximum) of the page.
|
|
153
|
+
*
|
|
154
|
+
* Example #1: When I scroll to the end
|
|
155
|
+
* Example #2: And we scroll to end
|
|
156
|
+
* Example #3: When scrolling to the end of the page
|
|
157
|
+
*
|
|
158
|
+
*/
|
|
159
|
+
When(/^(I scroll|we scroll|scrolling)? to( the)* end( of the page)*$/, async function (pronounCase, theCase, pageCase) {
|
|
160
|
+
await this.page.evaluate(() => window.scrollTo(document.body.scrollWidth, window.scrollY));
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
// Horizontal scrolling — scoped to a CSS selector
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Scrolls to the start of a specific element identified by a CSS selector.
|
|
169
|
+
*
|
|
170
|
+
* Example #1: When I scroll to start of "#off-canvas"
|
|
171
|
+
* Example #2: And we scroll to start of "#sidebar"
|
|
172
|
+
* Example #3: When scrolling to start of "#main-container"
|
|
173
|
+
*
|
|
174
|
+
*/
|
|
175
|
+
When(/^(I scroll|we scroll|scrolling)? to start of "([^"]*)"$/, async function (pronounCase, selector) {
|
|
176
|
+
await this.page.locator(selector).evaluate(el => { el.scrollLeft = 0; });
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Scrolls to the end of a specific element identified by a CSS selector.
|
|
181
|
+
*
|
|
182
|
+
* Example #1: When I scroll to end of "#off-canvas"
|
|
183
|
+
* Example #2: And we scroll to end of "#sidebar"
|
|
184
|
+
* Example #3: When scrolling to end of "#main-container"
|
|
185
|
+
*
|
|
186
|
+
*/
|
|
187
|
+
When(/^(I scroll|we scroll|scrolling)? to end of "([^"]*)"$/, async function (pronounCase, selector) {
|
|
188
|
+
await this.page.locator(selector).evaluate(el => { el.scrollLeft = el.scrollWidth; });
|
|
189
|
+
});
|