@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,466 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// -----------------------------------------------------------------------------
|
|
4
|
+
// Layout Builder step definitions: add a section at the end of a layout and
|
|
5
|
+
// configure it - container type and width, breakpoint, gutters, background and
|
|
6
|
+
// text colour, alignment, edge-to-edge background - then save it.
|
|
7
|
+
//
|
|
8
|
+
// Ported from the Varbase Project BDD suites (11.0.x, 10.1.x, 9.2.x) so every
|
|
9
|
+
// Varbase site gets them from @vardot/varbase-e2e instead of copying them into
|
|
10
|
+
// each project's own tests/step-definitions/.
|
|
11
|
+
//
|
|
12
|
+
// The form controls a section scenario drives the configuration sidebar with
|
|
13
|
+
// live here too, next to the section steps that use them, rather than in
|
|
14
|
+
// drupal-core.steps.js:
|
|
15
|
+
//
|
|
16
|
+
// I check the box "With Gutters"
|
|
17
|
+
// I uncheck the box "Edge to Edge"
|
|
18
|
+
// I select the "Boxed" radio button
|
|
19
|
+
// I select the radio button "Edge to Edge"
|
|
20
|
+
// I expand the field "edit-..."
|
|
21
|
+
// I press the confirm button in modal
|
|
22
|
+
// I click the delete button
|
|
23
|
+
//
|
|
24
|
+
// They are ordinary Drupal form controls and work anywhere, not only in a
|
|
25
|
+
// layout: every *.steps.js is auto-loaded, so any feature can use them.
|
|
26
|
+
// -----------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
const { When } = require('@cucumber/cucumber');
|
|
29
|
+
const { smartSettle, friendly } = require('./varbase-e2e');
|
|
30
|
+
const { budget, openSectionMenu, setCheckbox } = require('./drupal-helpers');
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Add a basic section (with an optional layout, default "1 Col") at the end of
|
|
34
|
+
* the Layout Builder layout.
|
|
35
|
+
*
|
|
36
|
+
* Ports VarbaseContext::iAddABasicSectionAtTheEndOfLayout.
|
|
37
|
+
*
|
|
38
|
+
* Example #1: When I add a basic "4 Cols" section at the end of layout
|
|
39
|
+
* Example #2: And I add a basic section at the end of layout
|
|
40
|
+
* Example #3: When I add a basic "2 Cols" section at the end of layout
|
|
41
|
+
* Example #4: And we add a basic section at the end of layout
|
|
42
|
+
* Example #5: When I add a basic "3 Cols" section at the end of layout
|
|
43
|
+
*/
|
|
44
|
+
When(/^(?:I |we )*add a basic(?: "([^"]*)")? section at the end of layout$/, async function (cols) {
|
|
45
|
+
const layout = cols || '1 Col';
|
|
46
|
+
await this.page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
|
|
47
|
+
const addLink = this.page.locator("a.layout-builder__link--add:has-text('at end of layout'), a.layout-builder__link--add").last();
|
|
48
|
+
await addLink.click();
|
|
49
|
+
await smartSettle(this.page, budget(this));
|
|
50
|
+
const layoutLink = this.page.locator(`a.use-ajax:has-text("${layout}")`).first();
|
|
51
|
+
if (!(await layoutLink.count())) throw friendly(`The "${layout}" layout option was not found in the Add section list.`);
|
|
52
|
+
await layoutLink.click();
|
|
53
|
+
await smartSettle(this.page, budget(this));
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Save (add) the currently configured Layout Builder section.
|
|
58
|
+
*
|
|
59
|
+
* Ports VarbaseContext::iSaveTheSection (clicks the "Add section" submit).
|
|
60
|
+
*
|
|
61
|
+
* Example #1: When I save the section
|
|
62
|
+
* Example #2: And I save the section
|
|
63
|
+
* Example #3: When we save the section
|
|
64
|
+
* Example #4: Given I save the section
|
|
65
|
+
* Example #5: And we save the section
|
|
66
|
+
*/
|
|
67
|
+
When(/^(?:I |we )*save the section$/, async function () {
|
|
68
|
+
// A raw DOM click (page.evaluate -> btn.click()) does not reliably trigger
|
|
69
|
+
// Drupal's jQuery-bound AJAX submit handler for this button - it can leave
|
|
70
|
+
// the section silently un-added. Use a real Playwright mouse click instead,
|
|
71
|
+
// which dispatches the full native event sequence.
|
|
72
|
+
const btn = this.page.locator('input[type="submit"], button').filter({ hasText: /Add section/i }).first();
|
|
73
|
+
if (!(await btn.count())) throw friendly('The "Add section" button was not found.');
|
|
74
|
+
await btn.click();
|
|
75
|
+
await smartSettle(this.page, budget(this));
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Add gutters to the section being configured (checks "With Gutters").
|
|
80
|
+
*
|
|
81
|
+
* Ports VarbaseContext::iAddSectionGutters.
|
|
82
|
+
*
|
|
83
|
+
* Example #1: When I add section gutters
|
|
84
|
+
* Example #2: And I add section gutters
|
|
85
|
+
* Example #3: When we add section gutters
|
|
86
|
+
* Example #4: Given I add section gutters
|
|
87
|
+
* Example #5: And we add section gutters
|
|
88
|
+
*/
|
|
89
|
+
When(/^(?:I |we )*add section gutters$/, async function () {
|
|
90
|
+
const lbl = this.page.locator("label:has-text('With Gutters')").first();
|
|
91
|
+
if (!(await lbl.count())) throw friendly('The "With Gutters" option was not found.');
|
|
92
|
+
await lbl.click();
|
|
93
|
+
await smartSettle(this.page, budget(this));
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Select a section container type (and optional Boxed width).
|
|
98
|
+
*
|
|
99
|
+
* Ports VarbaseContext::iSelectTheContainerType / iSelectTheContainerWidth.
|
|
100
|
+
*
|
|
101
|
+
* Example #1: When I select the "Edge to Edge" container type
|
|
102
|
+
* Example #2: And I select the "Boxed" container type with a "Tiny" width
|
|
103
|
+
* Example #3: When I select the "Full" container type
|
|
104
|
+
* Example #4: And I select the "Boxed" container type with a "Narrow" width
|
|
105
|
+
* Example #5: When we select the "Edge to Edge" container type
|
|
106
|
+
*/
|
|
107
|
+
When(/^(?:I |we )*select the "([^"]*)" container type(?: with a "([^"]*)" width)?$/, async function (type, width) {
|
|
108
|
+
// Real Playwright clicks, not a raw DOM click inside page.evaluate(): the
|
|
109
|
+
// LB config sidebar's radio labels are wired through Drupal AJAX behaviors,
|
|
110
|
+
// which a synthetic click can silently fail to trigger (see the identical
|
|
111
|
+
// issue fixed for "save the section").
|
|
112
|
+
const clickLabel = async (text, forPrefix) => {
|
|
113
|
+
const lbl = this.page.locator('label').filter({ hasText: text }).and(this.page.locator(`label[for*="${forPrefix}"]`)).first();
|
|
114
|
+
if (!(await lbl.count())) return false;
|
|
115
|
+
await lbl.click();
|
|
116
|
+
return true;
|
|
117
|
+
};
|
|
118
|
+
if (!(await clickLabel(type, 'edit-layout-settings-ui-tab-content-layout-container-type'))) {
|
|
119
|
+
throw friendly(`The "${type}" container type was not found.`);
|
|
120
|
+
}
|
|
121
|
+
if (type === 'Boxed' && width) {
|
|
122
|
+
if (!(await clickLabel(width, 'edit-layout-settings-ui-tab-content-layout-container-width'))) {
|
|
123
|
+
throw friendly(`The "${width}" container width was not found.`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
await smartSettle(this.page, budget(this));
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Select a section breakpoint column ratio for a given screen size.
|
|
131
|
+
*
|
|
132
|
+
* Ports VarbaseContext::iSelectTheSectionBreakpoint.
|
|
133
|
+
*
|
|
134
|
+
* Example #1: When I select the "md" "33% 67%" section breakpoint
|
|
135
|
+
* Example #2: And I select the "xs" "75% 25%" section breakpoint
|
|
136
|
+
* Example #3: When I select the "lg" "50% 50%" section breakpoint
|
|
137
|
+
* Example #4: And I select the "sm" "100%" section breakpoint
|
|
138
|
+
* Example #5: When we select the "md" "67% 33%" section breakpoint
|
|
139
|
+
*/
|
|
140
|
+
When(/^(?:I |we )*select the "([^"]*)" "([^"]*)" section breakpoint$/, async function (size, point) {
|
|
141
|
+
// Real Playwright click (see "select the container type" above for why a
|
|
142
|
+
// raw page.evaluate() click is unreliable on these AJAX-bound controls).
|
|
143
|
+
const el = this.page.locator(`[class*="${size}"]`).filter({ hasText: point }).first();
|
|
144
|
+
if (!(await el.count())) throw friendly(`The "${point}" breakpoint for the "${size}" screen size was not found.`);
|
|
145
|
+
await el.click();
|
|
146
|
+
await smartSettle(this.page, budget(this));
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Select a section background color.
|
|
151
|
+
*
|
|
152
|
+
* Ports VarbaseContext::iSelectTheSectionBackgroundColor (opens Background,
|
|
153
|
+
* switches to the color tab, clicks the color label).
|
|
154
|
+
*
|
|
155
|
+
* Example #1: When I select the "Primary" section background color
|
|
156
|
+
* Example #2: And I select the "Light" section background color
|
|
157
|
+
* Example #3: When I select the "Dark" section background color
|
|
158
|
+
* Example #4: And we select the "Info" section background color
|
|
159
|
+
* Example #5: When I select the "White" section background color
|
|
160
|
+
*/
|
|
161
|
+
When(/^(?:I |we )*select the "([^"]*)" section background color$/, async function (color) {
|
|
162
|
+
await openSectionMenu(this.page, 'Background');
|
|
163
|
+
await this.page.evaluate(() => {
|
|
164
|
+
const t = [...document.querySelectorAll('label')].find((l) => (l.getAttribute('for') || '').includes('appearance-background-background-type-color'));
|
|
165
|
+
if (t) t.click();
|
|
166
|
+
});
|
|
167
|
+
const ok = await this.page.evaluate((color) => {
|
|
168
|
+
const lbl = [...document.querySelectorAll('label')].find((l) => (l.textContent || '').includes(color) && (l.getAttribute('for') || '').includes('appearance-background-background-color'));
|
|
169
|
+
if (!lbl) return false; lbl.click(); return true;
|
|
170
|
+
}, color);
|
|
171
|
+
if (!ok) throw friendly(`The "${color}" section background color was not found.`);
|
|
172
|
+
await smartSettle(this.page, budget(this));
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Select a section text color.
|
|
177
|
+
*
|
|
178
|
+
* Ports VarbaseContext::iSelectTheSectionTextColor.
|
|
179
|
+
*
|
|
180
|
+
* Example #1: When I select the "Dark" section text color
|
|
181
|
+
* Example #2: And I select the "White" section text color
|
|
182
|
+
* Example #3: When I select the "Primary" section text color
|
|
183
|
+
* Example #4: And we select the "Light" section text color
|
|
184
|
+
* Example #5: When I select the "Muted" section text color
|
|
185
|
+
*/
|
|
186
|
+
When(/^(?:I |we )*select the "([^"]*)" section text color$/, async function (color) {
|
|
187
|
+
await openSectionMenu(this.page, 'Typography');
|
|
188
|
+
const ok = await this.page.evaluate((color) => {
|
|
189
|
+
const lbl = [...document.querySelectorAll('label')].find((l) => (l.textContent || '').includes(color) && (l.getAttribute('for') || '').includes('appearance-typography-text-color-text'));
|
|
190
|
+
if (!lbl) return false; lbl.click(); return true;
|
|
191
|
+
}, color);
|
|
192
|
+
if (!ok) throw friendly(`The "${color}" section text color was not found.`);
|
|
193
|
+
await smartSettle(this.page, budget(this));
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Set the section text alignment.
|
|
198
|
+
*
|
|
199
|
+
* Ports VarbaseContext::iSetTheAlignmentTo.
|
|
200
|
+
*
|
|
201
|
+
* Example #1: When I set the alignment to "End"
|
|
202
|
+
* Example #2: And I set the alignment to "Start"
|
|
203
|
+
* Example #3: When I set the alignment to "Center"
|
|
204
|
+
* Example #4: And we set the alignment to "Justify"
|
|
205
|
+
* Example #5: When I set the alignment to "End"
|
|
206
|
+
*/
|
|
207
|
+
When(/^(?:I |we )*set the alignment to "([^"]*)"$/, async function (align) {
|
|
208
|
+
await openSectionMenu(this.page, 'Typography');
|
|
209
|
+
const ok = await this.page.evaluate((align) => {
|
|
210
|
+
const lbl = [...document.querySelectorAll('label')].find((l) => (l.textContent || '').includes(align) && (l.getAttribute('for') || '').includes('appearance-typography-text-alignment'));
|
|
211
|
+
if (!lbl) return false; lbl.click(); return true;
|
|
212
|
+
}, align);
|
|
213
|
+
if (!ok) throw friendly(`The "${align}" text alignment was not found.`);
|
|
214
|
+
await smartSettle(this.page, budget(this));
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Uncheck the section "Edge to Edge Background" option.
|
|
219
|
+
*
|
|
220
|
+
* Ports VarbaseContext::iUncheckTheEdgeToEdgeBackground.
|
|
221
|
+
*
|
|
222
|
+
* Example #1: When I uncheck the Edge to Edge Background
|
|
223
|
+
* Example #2: And I uncheck the Edge to Edge Background
|
|
224
|
+
* Example #3: When we uncheck the Edge to Edge Background
|
|
225
|
+
* Example #4: Given I uncheck the Edge to Edge Background
|
|
226
|
+
* Example #5: And we uncheck the Edge to Edge Background
|
|
227
|
+
*/
|
|
228
|
+
When(/^(?:I |we )*uncheck the Edge to Edge Background$/, async function () {
|
|
229
|
+
await openSectionMenu(this.page, 'Background');
|
|
230
|
+
const ok = await this.page.evaluate(() => {
|
|
231
|
+
const cb = document.querySelector('input.field-background-edge-to-edge');
|
|
232
|
+
if (!cb) return false; cb.click(); return true;
|
|
233
|
+
});
|
|
234
|
+
if (!ok) throw friendly('The "Edge to Edge Background" checkbox was not found.');
|
|
235
|
+
await smartSettle(this.page, budget(this));
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Select an option (by its visible option text, falling back to value) from a
|
|
240
|
+
* <select> resolved by its visible label, or a partial name / id when the label
|
|
241
|
+
* is ambiguous. Needed for paragraph subform selects whose label is a single
|
|
242
|
+
* word (e.g. "Block", "Webform") — the Varbase E2E core "select from" step treats a
|
|
243
|
+
* single-word target as a name/id, never a label, and the subform select's DOM
|
|
244
|
+
* id carries a random "--XXXX" suffix so it cannot be addressed by a fixed #id.
|
|
245
|
+
*
|
|
246
|
+
* Example #1: And I select "Site branding" from the "Block" dropdown
|
|
247
|
+
* Example #2: And I select "Contact" from the "Webform" dropdown
|
|
248
|
+
*/
|
|
249
|
+
When(/^(?:I |we )*select "([^"]*)" from the "([^"]*)" dropdown$/, async function (option, target) {
|
|
250
|
+
const candidates = [
|
|
251
|
+
this.page.getByLabel(target, { exact: true }),
|
|
252
|
+
this.page.getByLabel(target, { exact: false }),
|
|
253
|
+
this.page.locator(`select[name="${target}"]`),
|
|
254
|
+
this.page.locator(`select[name*="${target}"]`),
|
|
255
|
+
this.page.locator(`select[id*="${target}"]`),
|
|
256
|
+
];
|
|
257
|
+
let loc = null;
|
|
258
|
+
for (const c of candidates) {
|
|
259
|
+
try {
|
|
260
|
+
if (await c.first().count()) { loc = c.first(); break; }
|
|
261
|
+
} catch (e) { /* invalid selector — skip */ }
|
|
262
|
+
}
|
|
263
|
+
if (!loc) throw friendly(`No <select> resolvable from "${target}" (label / name / id) was found.`);
|
|
264
|
+
try {
|
|
265
|
+
await loc.selectOption({ label: option }, { timeout: 5000 });
|
|
266
|
+
} catch (e) {
|
|
267
|
+
try {
|
|
268
|
+
// Fall back to matching by the option's value attribute.
|
|
269
|
+
await loc.selectOption(option, { timeout: 3000 });
|
|
270
|
+
} catch (e2) {
|
|
271
|
+
// Last resort: partial (contains) option-text match. Drupal sometimes
|
|
272
|
+
// suffixes the option label (e.g. Views Bulk Operations renders "Delete
|
|
273
|
+
// selected entities / translations" once content_translation is enabled).
|
|
274
|
+
const value = await loc.first().evaluate((sel, text) => {
|
|
275
|
+
const opt = [...sel.options].find((o) => o.textContent.trim().includes(text));
|
|
276
|
+
return opt ? opt.value : null;
|
|
277
|
+
}, option);
|
|
278
|
+
if (value === null) {
|
|
279
|
+
throw friendly(`No option matching "${option}" was found in the "${target}" dropdown.`);
|
|
280
|
+
}
|
|
281
|
+
await loc.selectOption(value);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
await smartSettle(this.page, budget(this));
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Check a checkbox by its visible label (or id / name / css selector).
|
|
289
|
+
*
|
|
290
|
+
* Ports the Varbase suite's `I check the box "..."`. Varbase E2E core only
|
|
291
|
+
* offers `I check "..."`; the profile features use "check the box", so this
|
|
292
|
+
* matches that phrasing. Resolves the control by label text first (Drupal
|
|
293
|
+
* renders role permission / field labels), then falls back to id / name / css.
|
|
294
|
+
*
|
|
295
|
+
* Example #1: When I check the box "Editor"
|
|
296
|
+
* Example #2: And I check the box "Site Admin"
|
|
297
|
+
* Example #3: When we check the box "Content Admin"
|
|
298
|
+
* Example #4: And I check the box "Super Admin"
|
|
299
|
+
* Example #5: Given I check the box "SEO Admin"
|
|
300
|
+
*/
|
|
301
|
+
When(/^(?:I |we )*check the box "([^"]*)"$/, async function (label) {
|
|
302
|
+
await setCheckbox.call(this, label, true);
|
|
303
|
+
await smartSettle(this.page, budget(this));
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Uncheck a checkbox by its visible label (or id / name / css selector).
|
|
308
|
+
*
|
|
309
|
+
* Ports the Varbase suite's `I uncheck the box "..."`.
|
|
310
|
+
*
|
|
311
|
+
* Example #1: When I uncheck the box "Editor"
|
|
312
|
+
* Example #2: And I uncheck the box "Subscribe"
|
|
313
|
+
* Example #3: When we uncheck the box "Site Admin"
|
|
314
|
+
* Example #4: And I uncheck the box "Enable"
|
|
315
|
+
* Example #5: Given I uncheck the box "Published"
|
|
316
|
+
*/
|
|
317
|
+
When(/^(?:I |we )*uncheck the box "([^"]*)"$/, async function (label) {
|
|
318
|
+
await setCheckbox.call(this, label, false);
|
|
319
|
+
await smartSettle(this.page, budget(this));
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Select a radio button by its visible label text.
|
|
324
|
+
*
|
|
325
|
+
* Ports VarbaseContext::iSelectTheRadioButton (`@When /^I select the "..."
|
|
326
|
+
* radio button$/`): finds the <label> whose text matches, follows its `for`
|
|
327
|
+
* attribute to the input, and selects it.
|
|
328
|
+
*
|
|
329
|
+
* Example #1: When I select the "Male" radio button
|
|
330
|
+
* Example #2: And I select the "Female" radio button
|
|
331
|
+
* Example #3: When we select the "Yes" radio button
|
|
332
|
+
* Example #4: And I select the "Unpublished" radio button
|
|
333
|
+
* Example #5: Given I select the "Published" radio button
|
|
334
|
+
*/
|
|
335
|
+
When(/^(?:I |we )*select the "([^"]*)" radio button$/, async function (label) {
|
|
336
|
+
const ok = await this.page.evaluate((label) => {
|
|
337
|
+
const lbl = [...document.querySelectorAll('label')].find((l) => l.textContent.trim() === label);
|
|
338
|
+
if (!lbl) return false;
|
|
339
|
+
const forId = lbl.getAttribute('for');
|
|
340
|
+
const radio = forId ? document.getElementById(forId) : lbl.querySelector('input[type="radio"]');
|
|
341
|
+
if (!radio) return false;
|
|
342
|
+
if (!radio.checked) radio.click();
|
|
343
|
+
return true;
|
|
344
|
+
}, label);
|
|
345
|
+
if (!ok) throw friendly(`Could not find a radio button labelled "${label}".`, "The label must carry a 'for' attribute pointing to the radio input.");
|
|
346
|
+
await smartSettle(this.page, budget(this));
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Select a radio button by its visible label text (alternate phrasing).
|
|
351
|
+
*
|
|
352
|
+
* Example #1: When I select the radio button "Published"
|
|
353
|
+
* Example #2: And I select the radio button "Draft"
|
|
354
|
+
* Example #3: When we select the radio button "Needs Review"
|
|
355
|
+
* Example #4: And I select the radio button "Archived"
|
|
356
|
+
* Example #5: Given I select the radio button "Yes"
|
|
357
|
+
*/
|
|
358
|
+
When(/^(?:I |we )*select the radio button "([^"]*)"$/, async function (label) {
|
|
359
|
+
const radio = this.page.getByRole('radio', { name: label, exact: false }).first();
|
|
360
|
+
try {
|
|
361
|
+
await radio.check({ timeout: budget(this) });
|
|
362
|
+
} catch (e) {
|
|
363
|
+
// Fall back to clicking a label that contains the text.
|
|
364
|
+
const byLabel = this.page.locator('label', { hasText: label }).first();
|
|
365
|
+
await byLabel.click({ timeout: budget(this) });
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Open a collapsed <details>/fieldset by its element id so its inner fields
|
|
371
|
+
* become interactable (e.g. the node form "Menu settings", the entityqueue
|
|
372
|
+
* form widget).
|
|
373
|
+
*
|
|
374
|
+
* Example #1: And I expand the field "edit-menu"
|
|
375
|
+
* Example #2: And I expand the field "edit-entityqueue-form-widget"
|
|
376
|
+
*/
|
|
377
|
+
When(/^(?:I |we )*expand the field "([^"]*)"$/, async function (fieldId) {
|
|
378
|
+
const ok = await this.page.evaluate((id) => {
|
|
379
|
+
const el = document.getElementById(id);
|
|
380
|
+
if (!el) return false;
|
|
381
|
+
const details = el.tagName.toLowerCase() === 'details' ? el : el.closest('details');
|
|
382
|
+
if (details) {
|
|
383
|
+
details.setAttribute('open', '');
|
|
384
|
+
const summary = details.querySelector('summary');
|
|
385
|
+
if (summary) summary.setAttribute('aria-expanded', 'true');
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
// Fallback: a non-details collapsible — remove a "collapsed" class.
|
|
389
|
+
el.classList.remove('collapsed');
|
|
390
|
+
el.setAttribute('open', '');
|
|
391
|
+
return true;
|
|
392
|
+
}, fieldId);
|
|
393
|
+
if (!ok) throw friendly(`Could not find a collapsible field with id "${fieldId}".`);
|
|
394
|
+
await smartSettle(this.page, budget(this));
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Press the confirm (Restore / OK / primary) button in a jQuery UI modal.
|
|
399
|
+
*
|
|
400
|
+
* Ports VarbaseContext::iPressTheConfirmButton (used by trash restore).
|
|
401
|
+
*
|
|
402
|
+
* Example #1: When I press the confirm button in modal
|
|
403
|
+
* Example #2: And I press the confirm button in modal
|
|
404
|
+
* Example #3: When we press the confirm button in modal
|
|
405
|
+
* Example #4: Given I press the confirm button in modal
|
|
406
|
+
* Example #5: And we press the confirm button in modal
|
|
407
|
+
*/
|
|
408
|
+
When(/^(?:I |we )*press the confirm button in modal$/, async function () {
|
|
409
|
+
const ok = await this.page.evaluate(() => {
|
|
410
|
+
const scope = document.querySelector('.ui-dialog, [role="dialog"]') || document;
|
|
411
|
+
const candidates = [...scope.querySelectorAll('button, input[type="submit"]')];
|
|
412
|
+
const btn = candidates.find((b) => /^(Restore|OK|Confirm|Yes)$/i.test((b.textContent || b.value || '').trim()))
|
|
413
|
+
|| scope.querySelector('.button--primary, .ui-dialog-buttonset button');
|
|
414
|
+
if (!btn) return false;
|
|
415
|
+
btn.click();
|
|
416
|
+
return true;
|
|
417
|
+
});
|
|
418
|
+
if (!ok) throw friendly('No confirm button was found in the modal.');
|
|
419
|
+
await smartSettle(this.page, budget(this));
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Click the first "Delete" button on the page (action link or submit).
|
|
424
|
+
*
|
|
425
|
+
* Ports VarbaseContext::iClickTheDeleteButton.
|
|
426
|
+
*
|
|
427
|
+
* Example #1: When I click the delete button
|
|
428
|
+
* Example #2: And I click the delete button
|
|
429
|
+
* Example #3: When we click the delete button
|
|
430
|
+
* Example #4: Given I click the delete button
|
|
431
|
+
* Example #5: And we click the delete button
|
|
432
|
+
*/
|
|
433
|
+
When(/^(?:I |we )*click the delete button$/, async function () {
|
|
434
|
+
const ok = await this.page.evaluate(() => {
|
|
435
|
+
const label = (b) => (b.value || b.textContent || '').trim();
|
|
436
|
+
// The Varbase/Gin media & content delete confirm renders as an AJAX modal
|
|
437
|
+
// whose actual "Delete" control is a form SUBMIT button (input[type=submit]
|
|
438
|
+
// / button[type=submit]). The same page also carries several `use-ajax`
|
|
439
|
+
// "Delete" ACTION LINKS (the Gin sticky action bar #gin-sticky-edit-delete,
|
|
440
|
+
// the top-bar dropdown link, the edit-form #edit-delete link) which only
|
|
441
|
+
// (re)open that confirm dialog — clicking one of those never submits the
|
|
442
|
+
// deletion. So target the real submit button, preferring the one inside the
|
|
443
|
+
// open dialog, and never a use-ajax/action-link.
|
|
444
|
+
const isSubmit = (b) =>
|
|
445
|
+
(b.tagName === 'INPUT' && b.type === 'submit') ||
|
|
446
|
+
(b.tagName === 'BUTTON' && (b.type === 'submit' || !b.type));
|
|
447
|
+
const openDialog = [...document.querySelectorAll('.ui-dialog, [role="dialog"]')]
|
|
448
|
+
.find((d) => d.offsetParent !== null || getComputedStyle(d).display !== 'none');
|
|
449
|
+
const scopes = openDialog ? [openDialog, document] : [document];
|
|
450
|
+
for (const scope of scopes) {
|
|
451
|
+
const submit = [...scope.querySelectorAll('input[type="submit"], button')]
|
|
452
|
+
.find((b) => isSubmit(b) && label(b) === 'Delete');
|
|
453
|
+
if (submit) { submit.click(); return true; }
|
|
454
|
+
}
|
|
455
|
+
// Fallback: any "Delete" element that is not a use-ajax/action link (a
|
|
456
|
+
// plain full-page confirm form, e.g. the trash purge confirm).
|
|
457
|
+
const other = [...document.querySelectorAll('button, input[type="submit"], a')]
|
|
458
|
+
.find((b) => label(b) === 'Delete'
|
|
459
|
+
&& !b.classList.contains('use-ajax')
|
|
460
|
+
&& !b.classList.contains('action-link'));
|
|
461
|
+
if (other) { other.click(); return true; }
|
|
462
|
+
return false;
|
|
463
|
+
});
|
|
464
|
+
if (!ok) throw friendly('No "Delete" submit button was found on the page.');
|
|
465
|
+
await smartSettle(this.page, budget(this));
|
|
466
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// -----------------------------------------------------------------------------
|
|
4
|
+
// Media library step definitions: open the library on a field, pick an item in
|
|
5
|
+
// the grid and submit the dialog.
|
|
6
|
+
//
|
|
7
|
+
// Ported from the Varbase Project BDD suites (11.0.x, 10.1.x, 9.2.x) so every
|
|
8
|
+
// Varbase site gets them from @vardot/varbase-e2e instead of copying them into
|
|
9
|
+
// each project's own tests/step-definitions/.
|
|
10
|
+
// -----------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
const { When } = require('@cucumber/cucumber');
|
|
13
|
+
const { smartSettle, friendly } = require('./varbase-e2e');
|
|
14
|
+
const { budget } = require('./drupal-helpers');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Submit the Media Library dialog (the "Insert selected" action).
|
|
18
|
+
*
|
|
19
|
+
* Ports VarbaseContext::iSubmitMediaLibraryDialog.
|
|
20
|
+
*
|
|
21
|
+
* Example #1: When I submit the media library dialog
|
|
22
|
+
* Example #2: And I submit the media library dialog
|
|
23
|
+
* Example #3: When we submit the media library dialog
|
|
24
|
+
* Example #4: Given I submit the media library dialog
|
|
25
|
+
* Example #5: And we submit media library dialog
|
|
26
|
+
*/
|
|
27
|
+
When(/^(?:I |we )*submit (?:the )?media library dialog$/, async function () {
|
|
28
|
+
const ok = await this.page.evaluate(() => {
|
|
29
|
+
const btn = document.querySelector(".media-library-select[value='dialog-submit'], .ui-dialog button.media-library-select");
|
|
30
|
+
if (!btn) return false;
|
|
31
|
+
btn.click();
|
|
32
|
+
return true;
|
|
33
|
+
});
|
|
34
|
+
if (!ok) throw friendly('The Media Library "Insert selected" button was not found.');
|
|
35
|
+
await smartSettle(this.page, budget(this));
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Open the Media Library widget of a specific field by its machine name.
|
|
40
|
+
*
|
|
41
|
+
* On Drupal 11.4 the media-library "Add media" open button renders with a
|
|
42
|
+
* random "--XXXX" id suffix, and a single paragraph subform can contain several
|
|
43
|
+
* "Add media" buttons (e.g. the text_and_image bundle has both field_image and
|
|
44
|
+
* a bp_image field), so neither the old fixed id nor the "Add media" label is
|
|
45
|
+
* unique. Match the visible open button whose id contains "<field>-open-button".
|
|
46
|
+
*
|
|
47
|
+
* Example #1: And I open the "field_image" media library
|
|
48
|
+
* Example #2: And I open the "field_media_single" media library
|
|
49
|
+
*/
|
|
50
|
+
When(/^(?:I |we )*open the "([^"]*)" media library$/, async function (field) {
|
|
51
|
+
const frag = field.replace(/_/g, '-');
|
|
52
|
+
const btn = this.page
|
|
53
|
+
.locator(`input[id*="${frag}-open-button"]:visible, button[id*="${frag}-open-button"]:visible`)
|
|
54
|
+
.first();
|
|
55
|
+
if (!(await btn.count())) {
|
|
56
|
+
throw friendly(`No media library open button for the "${field}" field was found.`);
|
|
57
|
+
}
|
|
58
|
+
await btn.click({ timeout: 8000 });
|
|
59
|
+
await smartSettle(this.page, budget(this));
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Select a Media Library grid item by its "Select <name>" checkbox, tolerating
|
|
64
|
+
* duplicate items. A retried upload scenario (cucumber `retry: 1`) can leave two
|
|
65
|
+
* media with the same name, so Varbase E2E's built-in "I check" would strict-fail on
|
|
66
|
+
* two identically labelled checkboxes — pick the first.
|
|
67
|
+
*
|
|
68
|
+
* Example #1: And I select the media "Embed Flag Earth"
|
|
69
|
+
* Example #2: And I select the media "Flag Earth"
|
|
70
|
+
*/
|
|
71
|
+
When(/^(?:I |we )*select the media "([^"]*)"$/, async function (name) {
|
|
72
|
+
const cb = this.page.getByLabel(`Select ${name}`, { exact: true }).first();
|
|
73
|
+
if (!(await cb.count())) {
|
|
74
|
+
throw friendly(`No media library item labelled "Select ${name}" was found.`);
|
|
75
|
+
}
|
|
76
|
+
await cb.check({ timeout: 8000 });
|
|
77
|
+
await smartSettle(this.page, budget(this));
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Insert the currently selected item(s) in the open Media Library dialog.
|
|
83
|
+
* The dialog submit ("Insert selected") is cloned by jQuery UI into the dialog
|
|
84
|
+
* button pane, so the original form button is hidden; target the visible one.
|
|
85
|
+
* Replaces the old `press "dialog-submit" by its "id"` which no longer resolves
|
|
86
|
+
* on Drupal 11.4 (the media dialog button has no "dialog-submit" id).
|
|
87
|
+
*
|
|
88
|
+
* Example #1: And I insert the selected media
|
|
89
|
+
*/
|
|
90
|
+
When(/^(?:I |we )*insert the selected media$/, async function () {
|
|
91
|
+
const btn = this.page
|
|
92
|
+
.locator('button:has-text("Insert selected"):visible, input[value="Insert selected"]:visible')
|
|
93
|
+
.first();
|
|
94
|
+
if (!(await btn.count())) {
|
|
95
|
+
throw friendly('No "Insert selected" button was found in the media library dialog.');
|
|
96
|
+
}
|
|
97
|
+
await btn.click({ timeout: 8000 });
|
|
98
|
+
await smartSettle(this.page, budget(this));
|
|
99
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// -----------------------------------------------------------------------------
|
|
4
|
+
// Content moderation step definitions: open the moderation sidebar and assert
|
|
5
|
+
// what it shows for the current revision.
|
|
6
|
+
//
|
|
7
|
+
// Ported from the Varbase Project BDD suites (11.0.x, 10.1.x, 9.2.x) so every
|
|
8
|
+
// Varbase site gets them from @vardot/varbase-e2e instead of copying them into
|
|
9
|
+
// each project's own tests/step-definitions/.
|
|
10
|
+
// -----------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
const { When, Then } = require('@cucumber/cucumber');
|
|
13
|
+
const assert = require('assert');
|
|
14
|
+
const { smartSettle, friendly } = require('./varbase-e2e');
|
|
15
|
+
const { budget, moderationSidebarText } = require('./drupal-helpers');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Open the moderation sidebar from the administration toolbar / tasks.
|
|
19
|
+
*
|
|
20
|
+
* Ports VarbaseContext::iOpenTheModerationSidebar.
|
|
21
|
+
*
|
|
22
|
+
* Example #1: When I open the moderation sidebar
|
|
23
|
+
* Example #2: And I open moderation sidebar
|
|
24
|
+
* Example #3: When I click on tasks in the toolbar
|
|
25
|
+
* Example #4: And click on tasks in the toolbar
|
|
26
|
+
* Example #5: When we open the moderation sidebar
|
|
27
|
+
*/
|
|
28
|
+
When(/^(?:I |we )*(?:open (?:the )?moderation sidebar|click on tasks in the toolbar)$/, async function () {
|
|
29
|
+
const ok = await this.page.evaluate(() => {
|
|
30
|
+
const link = document.querySelector('#toolbar-bar .moderation-sidebar-toolbar-tab a, .moderation-sidebar-toolbar-tab a');
|
|
31
|
+
if (!link) return false;
|
|
32
|
+
link.click();
|
|
33
|
+
return true;
|
|
34
|
+
});
|
|
35
|
+
if (!ok) throw friendly('The moderation sidebar toolbar tab was not found.');
|
|
36
|
+
await smartSettle(this.page, budget(this));
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Assert the moderation sidebar shows the given text for the current revision.
|
|
41
|
+
*
|
|
42
|
+
* Example #1: Then the moderation sidebar should show "Draft"
|
|
43
|
+
* Example #2: And moderation sidebar should show "Published"
|
|
44
|
+
* Example #3: Then the moderation sidebar should show "Needs Review"
|
|
45
|
+
* Example #4: And the moderation sidebar should show "Archived"
|
|
46
|
+
* Example #5: Then moderation sidebar should show "Latest version"
|
|
47
|
+
*/
|
|
48
|
+
Then(/^(?:the )?moderation sidebar should show "([^"]*)"$/, async function (text) {
|
|
49
|
+
const body = await moderationSidebarText(this.page);
|
|
50
|
+
assert.ok(
|
|
51
|
+
body.includes(text.toLowerCase()),
|
|
52
|
+
friendly(`The moderation sidebar does not show "${text}".`)
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Assert the moderation sidebar does not show the given text for the current revision.
|
|
58
|
+
*
|
|
59
|
+
* Example #1: Then the moderation sidebar should not show "Archived"
|
|
60
|
+
* Example #2: And moderation sidebar should not show "Delete"
|
|
61
|
+
* Example #3: Then the moderation sidebar should not show "Published"
|
|
62
|
+
* Example #4: And the moderation sidebar should not show "Unpublish"
|
|
63
|
+
* Example #5: Then moderation sidebar should not show "Needs Review"
|
|
64
|
+
*/
|
|
65
|
+
Then(/^(?:the )?moderation sidebar should not show "([^"]*)"$/, async function (text) {
|
|
66
|
+
const body = await moderationSidebarText(this.page);
|
|
67
|
+
assert.ok(
|
|
68
|
+
!body.includes(text.toLowerCase()),
|
|
69
|
+
friendly(`The moderation sidebar unexpectedly shows "${text}".`)
|
|
70
|
+
);
|
|
71
|
+
});
|