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