@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.
Files changed (301) hide show
  1. package/AGENTS.md +148 -0
  2. package/CLAUDE.md +544 -0
  3. package/README.md +22 -0
  4. package/bin/generate-reports.js +363 -0
  5. package/bin/init-varbase-e2e.js +330 -0
  6. package/bin/postinstall.js +26 -0
  7. package/cucumber.js +125 -0
  8. package/docs/00-quick-start.md +102 -0
  9. package/docs/01-getting-started.md +102 -0
  10. package/docs/02-bbr-smart-waits.md +77 -0
  11. package/docs/03-selector-registry.md +95 -0
  12. package/docs/04-step-reference.md +801 -0
  13. package/docs/05-web-first-assertions.md +91 -0
  14. package/docs/06-network-and-dialogs.md +83 -0
  15. package/docs/07-auth-state.md +75 -0
  16. package/docs/08-clock-mocking.md +59 -0
  17. package/docs/09-api-testing.md +114 -0
  18. package/docs/10-accessibility.md +167 -0
  19. package/docs/11-debugging.md +129 -0
  20. package/docs/12-ai-agent-guide.md +338 -0
  21. package/docs/13-faq.md +236 -0
  22. package/docs/14-recipes-cookbook.md +255 -0
  23. package/docs/15-tag-conventions.md +76 -0
  24. package/docs/16-ci-cd.md +90 -0
  25. package/docs/README.md +125 -0
  26. package/docs/advanced-screenshots/then-i-save-fullscreen-screenshot.md +36 -0
  27. package/docs/advanced-screenshots/then-i-save-width-x-height-screenshot.md +34 -0
  28. package/docs/advanced-screenshots/when-i-save-screenshot-name-filename.md +45 -0
  29. package/docs/advanced-screenshots.md +52 -0
  30. package/docs/advanced-selectors.md +18 -0
  31. package/docs/api-step-definitions.md +45 -0
  32. package/docs/global-settings.md +267 -0
  33. package/docs/install-varbase-e2e/ddev-varbase-e2e.md +52 -0
  34. package/docs/install-varbase-e2e.md +34 -0
  35. package/docs/overview.md +40 -0
  36. package/docs/step-definitions/given-i-am-anonymous-user.md +67 -0
  37. package/docs/step-definitions/given-i-am-homepage.md +68 -0
  38. package/docs/step-definitions/given-i-am-specific-page.md +72 -0
  39. package/docs/step-definitions/then-i-should-be.md +56 -0
  40. package/docs/step-definitions/then-i-should-see.md +62 -0
  41. package/docs/step-definitions/then-item-checkbox.md +54 -0
  42. package/docs/step-definitions/then-item-link-should-contain-url.md +84 -0
  43. package/docs/step-definitions/then-item-should-contain.md +67 -0
  44. package/docs/step-definitions/then-radio-button-value-should-be-selected.md +68 -0
  45. package/docs/step-definitions/then-response-status-code-should-be-number.md +69 -0
  46. package/docs/step-definitions/then-value-radio-button-selected.md +62 -0
  47. package/docs/step-definitions/when-i-attach-file-file-name-element.md +71 -0
  48. package/docs/step-definitions/when-i-check-checkbox.md +60 -0
  49. package/docs/step-definitions/when-i-click-link.md +51 -0
  50. package/docs/step-definitions/when-i-click-operation-text-row.md +71 -0
  51. package/docs/step-definitions/when-i-fill.md +59 -0
  52. package/docs/step-definitions/when-i-go-homepage.md +60 -0
  53. package/docs/step-definitions/when-i-go-specific-page.md +67 -0
  54. package/docs/step-definitions/when-i-move-backward-one-page.md +49 -0
  55. package/docs/step-definitions/when-i-move-forward-one-page.md +58 -0
  56. package/docs/step-definitions/when-i-press-button.md +58 -0
  57. package/docs/step-definitions/when-i-reload-page.md +51 -0
  58. package/docs/step-definitions/when-i-scroll.md +73 -0
  59. package/docs/step-definitions/when-i-select-option-select-list.md +53 -0
  60. package/docs/step-definitions/when-i-select-radio-button-value.md +62 -0
  61. package/docs/step-definitions/when-i-uncheck-checkbox.md +57 -0
  62. package/docs/step-definitions/when-i-wait-ajax-finish.md +64 -0
  63. package/docs/step-definitions/when-i-wait.md +62 -0
  64. package/docs/step-definitions.md +82 -0
  65. package/docs/steps/README.md +137 -0
  66. package/docs/steps/a11y.md +731 -0
  67. package/docs/steps/action.md +181 -0
  68. package/docs/steps/api.md +679 -0
  69. package/docs/steps/assertion.md +328 -0
  70. package/docs/steps/auth.md +108 -0
  71. package/docs/steps/clock.md +171 -0
  72. package/docs/steps/cookie.md +293 -0
  73. package/docs/steps/debug.md +52 -0
  74. package/docs/steps/dialog.md +226 -0
  75. package/docs/steps/drupal-canvas.md +414 -0
  76. package/docs/steps/drupal-ckeditor.md +118 -0
  77. package/docs/steps/drupal-core.md +688 -0
  78. package/docs/steps/drupal-layout-builder.md +437 -0
  79. package/docs/steps/drupal-media.md +107 -0
  80. package/docs/steps/drupal-moderation.md +81 -0
  81. package/docs/steps/drupal-paragraphs.md +36 -0
  82. package/docs/steps/element.md +455 -0
  83. package/docs/steps/field.md +648 -0
  84. package/docs/steps/file-download.md +235 -0
  85. package/docs/steps/form.md +344 -0
  86. package/docs/steps/iframe.md +248 -0
  87. package/docs/steps/input.md +220 -0
  88. package/docs/steps/javascript.md +111 -0
  89. package/docs/steps/keyboard.md +107 -0
  90. package/docs/steps/link.md +217 -0
  91. package/docs/steps/metatag.md +99 -0
  92. package/docs/steps/modal.md +232 -0
  93. package/docs/steps/navigation.md +275 -0
  94. package/docs/steps/network.md +284 -0
  95. package/docs/steps/path.md +203 -0
  96. package/docs/steps/response.md +102 -0
  97. package/docs/steps/responsive.md +139 -0
  98. package/docs/steps/rest.md +140 -0
  99. package/docs/steps/screenshot.md +211 -0
  100. package/docs/steps/scroll.md +266 -0
  101. package/docs/steps/selectors.md +848 -0
  102. package/docs/steps/storage.md +254 -0
  103. package/docs/steps/table.md +216 -0
  104. package/docs/steps/varbase.md +391 -0
  105. package/docs/steps/video.md +111 -0
  106. package/docs/steps/wait.md +553 -0
  107. package/docs/steps/web-first.md +289 -0
  108. package/docs/steps/xml.md +505 -0
  109. package/docs/steps/yaml.md +951 -0
  110. package/examples/a11y.html +31 -0
  111. package/examples/about-us.html +11 -0
  112. package/examples/account.html +32 -0
  113. package/examples/ajax-wait-examples.html +147 -0
  114. package/examples/contact-us.html +10 -0
  115. package/examples/cookies.html +10 -0
  116. package/examples/date.html +12 -0
  117. package/examples/dynamic-examples.html +498 -0
  118. package/examples/element.html +11 -0
  119. package/examples/example-api.json +28 -0
  120. package/examples/existing-page.html +11 -0
  121. package/examples/field.html +24 -0
  122. package/examples/index.html +37 -0
  123. package/examples/keyboard.html +11 -0
  124. package/examples/links.html +9 -0
  125. package/examples/modal.html +14 -0
  126. package/examples/path.html +3 -0
  127. package/examples/table.html +13 -0
  128. package/examples/test--modal-dialogs.html +252 -0
  129. package/examples/test--then--i-should--not--see-text-in-element.html +48 -0
  130. package/examples/test--then--i-should--see-text-in-element.html +48 -0
  131. package/examples/test--then--i-should--see-text-matching-pattern.html +31 -0
  132. package/examples/test--then--i-should-see-text-in-table-row.html +232 -0
  133. package/examples/test--then--the-checkbox-checked.html +68 -0
  134. package/examples/test--then--the-element-should_not-contain-cssproperty.html +47 -0
  135. package/examples/test--then--the-link-should-contain.html +21 -0
  136. package/examples/test--when--i-additionally-select.html +27 -0
  137. package/examples/test--when--i-attach-file-to-feild.html +50 -0
  138. package/examples/test--when--i-check-uncheck-checkbox.html +47 -0
  139. package/examples/test--when--i-click-link.html +18 -0
  140. package/examples/test--when--i-click-text-in-table-row.html +178 -0
  141. package/examples/test--when--i-fill-in.html +48 -0
  142. package/examples/test--when--i-press-button.html +31 -0
  143. package/examples/test--when--i-reload-page.html +27 -0
  144. package/examples/test--when--i-scroll-functionality.html +177 -0
  145. package/examples/test--when--i-scroll-left-right-functionality.html +149 -0
  146. package/examples/test--when--i-select-option-from-dropdownlist.html +35 -0
  147. package/examples/test--when--i-select-radio-button.html +95 -0
  148. package/examples/test--when-i-wait-max-of-time.html +39 -0
  149. package/examples/test--when-i-wait-page-until-is-loaded.html +47 -0
  150. package/examples/test--when-i-wait-specific-time.html +39 -0
  151. package/examples/test-acceptable-url-path.html +11 -0
  152. package/examples/video-demo.html +43 -0
  153. package/package.json +56 -0
  154. package/playwright.config.ts +39 -0
  155. package/tests/assets/test-document.pdf +14 -0
  156. package/tests/assets/vardot.png +0 -0
  157. package/tests/features/a11y.feature +39 -0
  158. package/tests/features/ajax-wait-examples.feature +24 -0
  159. package/tests/features/api-step-definitions-individual.feature +185 -0
  160. package/tests/features/complete-api-testing-examples.feature +205 -0
  161. package/tests/features/cookie.feature +13 -0
  162. package/tests/features/date.feature +11 -0
  163. package/tests/features/dynamic-content-examples.feature +156 -0
  164. package/tests/features/element.feature +16 -0
  165. package/tests/features/field.feature +25 -0
  166. package/tests/features/keyboard.feature +10 -0
  167. package/tests/features/link.feature +11 -0
  168. package/tests/features/modal-advanced-example.feature +54 -0
  169. package/tests/features/modal-basic-example.feature +42 -0
  170. package/tests/features/modal-error-handling-example.feature +37 -0
  171. package/tests/features/modal.feature +11 -0
  172. package/tests/features/path.feature +16 -0
  173. package/tests/features/response.feature +8 -0
  174. package/tests/features/responsive.feature +10 -0
  175. package/tests/features/table.feature +18 -0
  176. package/tests/features/test--given--i-am-an-anonymous-user.feature +10 -0
  177. package/tests/features/test--given--i-am-on-homepage.feature +16 -0
  178. package/tests/features/test--then--i-save-screenshot.feature +33 -0
  179. package/tests/features/test--then--i-see-locator.feature +320 -0
  180. package/tests/features/test--then--i-should--not--see-text-in-element.feature +13 -0
  181. package/tests/features/test--then--i-should--see-text-in-element-by-attr.feature +10 -0
  182. package/tests/features/test--then--i-should--see-text-in-element.feature +10 -0
  183. package/tests/features/test--then--i-should--see-text-matching-pattern-in-element.feature +13 -0
  184. package/tests/features/test--then--i-should--see-text-matching-pattern.feature +18 -0
  185. package/tests/features/test--then--i-should-be-on-page.feature +10 -0
  186. package/tests/features/test--then--i-should-be-on-the-homepage.feature +10 -0
  187. package/tests/features/test--then--i-should-not-see-an-element-by-attr.feature +8 -0
  188. package/tests/features/test--then--i-should-not-see-an-element.feature +8 -0
  189. package/tests/features/test--then--i-should-see-an-element-by-attr.feature +8 -0
  190. package/tests/features/test--then--i-should-see-an-element.feature +8 -0
  191. package/tests/features/test--then--i-should-see-n-elements.feature +24 -0
  192. package/tests/features/test--then--i-should-see-text-in-table-row.feature +96 -0
  193. package/tests/features/test--then--print-current-url-and-response.feature +21 -0
  194. package/tests/features/test--then--the-checkbox-is-checked.feature +18 -0
  195. package/tests/features/test--then--the-checkbox-should-be-checked.feature +16 -0
  196. package/tests/features/test--then--the-element-should-contain-cssproperty.feature +9 -0
  197. package/tests/features/test--then--the-element-should-not-contain-cssproperty.feature +10 -0
  198. package/tests/features/test--then--the-field-should-contain-value.feature +11 -0
  199. package/tests/features/test--then--the-response-should-contain.feature +11 -0
  200. package/tests/features/test--then--the-response-status-code-should-be.feature +31 -0
  201. package/tests/features/test--then--the-url-should-match.feature +7 -0
  202. package/tests/features/test--then-the-link-should-contain-by-attribute.feature +18 -0
  203. package/tests/features/test--then-the-link-should-contain.feature +7 -0
  204. package/tests/features/test--when--i-additionally-select.feature +30 -0
  205. package/tests/features/test--when--i-attach-file-to-feild.feature +9 -0
  206. package/tests/features/test--when--i-check-checkbox.feature +21 -0
  207. package/tests/features/test--when--i-click-link-by-attribute.feature +21 -0
  208. package/tests/features/test--when--i-click-link.feature +9 -0
  209. package/tests/features/test--when--i-click-text-in-table-row.feature +37 -0
  210. package/tests/features/test--when--i-fill-in-by-attr.feature +31 -0
  211. package/tests/features/test--when--i-fill-in-table-by-attr.feature +19 -0
  212. package/tests/features/test--when--i-fill-in.feature +32 -0
  213. package/tests/features/test--when--i-follow.feature +22 -0
  214. package/tests/features/test--when--i-go-to-homepage.feature +9 -0
  215. package/tests/features/test--when--i-move-forward-backward-one-page.feature +14 -0
  216. package/tests/features/test--when--i-press-button-by-attribute.feature +18 -0
  217. package/tests/features/test--when--i-press-button.feature +13 -0
  218. package/tests/features/test--when--i-reload-page.feature +11 -0
  219. package/tests/features/test--when--i-scroll-left-right.feature +48 -0
  220. package/tests/features/test--when--i-scroll.feature +48 -0
  221. package/tests/features/test--when--i-select-option-from-dropdownlist.feature +21 -0
  222. package/tests/features/test--when--i-select-radio-button.feature +37 -0
  223. package/tests/features/test--when--i-wait-page-is-loaded.feature +8 -0
  224. package/tests/features/test--when--i-wait-time.feature +21 -0
  225. package/tests/features/video.feature +18 -0
  226. package/tests/features/wait.feature +7 -0
  227. package/tests/features/yaml.feature +102 -0
  228. package/tests/selectors/_canonical-keys.json +113 -0
  229. package/tests/selectors/back-end-selectors.json +19 -0
  230. package/tests/selectors/cms-contentful-admin.json +35 -0
  231. package/tests/selectors/cms-craft-admin.json +39 -0
  232. package/tests/selectors/cms-drupal-cms-gin.json +56 -0
  233. package/tests/selectors/cms-drupal-core-claro.json +50 -0
  234. package/tests/selectors/cms-generic-admin.json +34 -0
  235. package/tests/selectors/cms-ghost-admin.json +34 -0
  236. package/tests/selectors/cms-joomla-admin.json +41 -0
  237. package/tests/selectors/cms-magento2-admin.json +32 -0
  238. package/tests/selectors/cms-prestashop-admin.json +38 -0
  239. package/tests/selectors/cms-shopify-admin.json +25 -0
  240. package/tests/selectors/cms-strapi-admin.json +42 -0
  241. package/tests/selectors/cms-typo3-admin.json +41 -0
  242. package/tests/selectors/cms-woocommerce-front.json +50 -0
  243. package/tests/selectors/cms-wordpress-admin.json +31 -0
  244. package/tests/selectors/framework-ant-design.json +80 -0
  245. package/tests/selectors/framework-bootstrap.json +86 -0
  246. package/tests/selectors/framework-bulma.json +74 -0
  247. package/tests/selectors/framework-chakra.json +58 -0
  248. package/tests/selectors/framework-foundation.json +51 -0
  249. package/tests/selectors/framework-material-ui.json +72 -0
  250. package/tests/selectors/framework-shadcn.json +51 -0
  251. package/tests/selectors/framework-tailwind.json +40 -0
  252. package/tests/selectors/framework-vuetify.json +61 -0
  253. package/tests/selectors/front-end-selectors.json +16 -0
  254. package/tests/selectors/homepage-selectors.json +13 -0
  255. package/tests/step-definitions/a11y.steps.js +800 -0
  256. package/tests/step-definitions/action.steps.js +167 -0
  257. package/tests/step-definitions/api.steps.js +732 -0
  258. package/tests/step-definitions/assertion.steps.js +352 -0
  259. package/tests/step-definitions/auth.steps.js +126 -0
  260. package/tests/step-definitions/clock.steps.js +128 -0
  261. package/tests/step-definitions/cookie.steps.js +216 -0
  262. package/tests/step-definitions/debug.steps.js +39 -0
  263. package/tests/step-definitions/dialog.steps.js +193 -0
  264. package/tests/step-definitions/drupal-canvas.steps.js +764 -0
  265. package/tests/step-definitions/drupal-ckeditor.steps.js +108 -0
  266. package/tests/step-definitions/drupal-core.steps.js +665 -0
  267. package/tests/step-definitions/drupal-helpers.js +371 -0
  268. package/tests/step-definitions/drupal-layout-builder.steps.js +466 -0
  269. package/tests/step-definitions/drupal-media.steps.js +99 -0
  270. package/tests/step-definitions/drupal-moderation.steps.js +71 -0
  271. package/tests/step-definitions/drupal-paragraphs.steps.js +50 -0
  272. package/tests/step-definitions/element.steps.js +356 -0
  273. package/tests/step-definitions/field.steps.js +653 -0
  274. package/tests/step-definitions/file-download.steps.js +211 -0
  275. package/tests/step-definitions/form.steps.js +333 -0
  276. package/tests/step-definitions/iframe.steps.js +228 -0
  277. package/tests/step-definitions/input.steps.js +202 -0
  278. package/tests/step-definitions/javascript.steps.js +232 -0
  279. package/tests/step-definitions/keyboard.steps.js +112 -0
  280. package/tests/step-definitions/link.steps.js +142 -0
  281. package/tests/step-definitions/metatag.steps.js +91 -0
  282. package/tests/step-definitions/modal.steps.js +244 -0
  283. package/tests/step-definitions/navigation.steps.js +218 -0
  284. package/tests/step-definitions/network.steps.js +280 -0
  285. package/tests/step-definitions/path.steps.js +141 -0
  286. package/tests/step-definitions/response.steps.js +98 -0
  287. package/tests/step-definitions/responsive.steps.js +125 -0
  288. package/tests/step-definitions/rest.steps.js +111 -0
  289. package/tests/step-definitions/screenshot.steps.js +423 -0
  290. package/tests/step-definitions/scroll.steps.js +189 -0
  291. package/tests/step-definitions/selectors.steps.js +1003 -0
  292. package/tests/step-definitions/storage.steps.js +194 -0
  293. package/tests/step-definitions/table.steps.js +181 -0
  294. package/tests/step-definitions/varbase-e2e.js +897 -0
  295. package/tests/step-definitions/varbase.steps.js +458 -0
  296. package/tests/step-definitions/video.steps.js +178 -0
  297. package/tests/step-definitions/wait.steps.js +400 -0
  298. package/tests/step-definitions/web-first.steps.js +298 -0
  299. package/tests/step-definitions/xml.steps.js +411 -0
  300. package/tests/step-definitions/yaml.steps.js +913 -0
  301. 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
+ });