@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,218 @@
1
+ 'use strict';
2
+
3
+ // All navigation step definitions live in this file: anonymous user, going
4
+ // to the homepage / a specific path, browser history (back/forward), reload,
5
+ // and URL/path assertions.
6
+
7
+ const { Given, When, Then } = require('@cucumber/cucumber');
8
+ const assert = require('assert');
9
+ const { gotoUrl, waitForPageLoad, smartSettle } = require('./varbase-e2e');
10
+
11
+ // ---------------------------------------------------------------------------
12
+ // Sessions / visits
13
+ // ---------------------------------------------------------------------------
14
+
15
+ /**
16
+ * Clear cookies and navigate to the launch URL as an anonymous visitor.
17
+ *
18
+ * Example #1: Given I am an anonymous user
19
+ * Example #2: Given we are an anonymous user
20
+ * Example #3: Given an anonymous user
21
+ *
22
+ */
23
+ Given(/^(I am |we are )?an anonymous user$/, async function (pronounCase) {
24
+ await this.context.clearCookies();
25
+ await gotoUrl(this.page, this.launchUrl);
26
+ await waitForPageLoad(this.page, this.minWaitTime.page || 3000);
27
+ });
28
+
29
+ /**
30
+ * Open the homepage.
31
+ *
32
+ * Example #1: Given I am on homepage
33
+ * Example #2: Given I am on the homepage
34
+ * Example #3: Given I am on frontpage
35
+ * Example #4: Given I am on the frontpage
36
+ * Example #5: Given we are on homepage
37
+ * Example #6: Given we are on the frontpage
38
+ * Example #7: Given on homepage
39
+ * Example #8: Given on the homepage
40
+ * Example #9: Given on frontpage
41
+ * Example #10: Given on the frontpage
42
+ *
43
+ */
44
+ Given(/^(I am |we are )?on( the)* (homepage|frontpage)$/, async function (pronounCase, theCase, pageCase) {
45
+ const defaultTime = this.minWaitTime.page || 3000;
46
+ await gotoUrl(this.page, this.launchUrl);
47
+ await this.page.waitForSelector('body', { state: 'attached', timeout: defaultTime });
48
+ await waitForPageLoad(this.page, defaultTime);
49
+ });
50
+
51
+ /**
52
+ * Open a specific page under the launch URL.
53
+ *
54
+ * Example #1: Given I am on "/about-us.html"
55
+ * Example #2: Given I am on the "/about-us.html" page
56
+ * Example #3: Given we are on "/user/login"
57
+ * Example #4: Given we are on the "/contact-us.html" page
58
+ * Example #5: Given on "/about-us.html"
59
+ * Example #6: Given on the "/about-us.html" page
60
+ * Example #7: Given I am on "https://un.org"
61
+ *
62
+ */
63
+ Given(/^(I am |we are )*on( the)* "([^"]*)?"( page)*$/, async function (pronounCase, theCase, url, pageCase) {
64
+ await gotoUrl(this.page, this.launchUrl + url);
65
+ await this.page.waitForSelector('body', { state: 'attached', timeout: 10000 });
66
+ await waitForPageLoad(this.page);
67
+ });
68
+
69
+ /**
70
+ * Navigate to the homepage.
71
+ *
72
+ * Example #1: When I go to homepage
73
+ * Example #2: When I go to the homepage
74
+ * Example #3: When I navigate to the homepage
75
+ * Example #4: When navigating to the homepage
76
+ * Example #5: When navigating to homepage
77
+ * Example #6: When navigating to the frontpage
78
+ * Example #7: When we go to the homepage
79
+ * Example #8: When we navigate to the homepage
80
+ *
81
+ */
82
+ When(/^(I go |I navigate |we go |we navigate |navigating )?to( the)* (homepage|frontpage)$/, async function (pronounCase, theCase, pageCase) {
83
+ const defaultTime = this.minWaitTime.page || 3000;
84
+ await gotoUrl(this.page, this.launchUrl);
85
+ await this.page.waitForSelector('body', { state: 'attached', timeout: defaultTime });
86
+ await waitForPageLoad(this.page, defaultTime);
87
+ });
88
+
89
+ /**
90
+ * Navigate to a specific page.
91
+ *
92
+ * Example #1: When I go to "/contact-us.html"
93
+ * Example #2: When I go to "/user/login"
94
+ * Example #3: When I navigate to "/admin/dashboard"
95
+ * Example #4: When navigating to "/products"
96
+ * Example #5: When we go to "/products"
97
+ * Example #6: When we navigate to "/terms"
98
+ *
99
+ */
100
+ When(/^(I go |I navigate |we go |we navigate |navigating )?to "([^"]*)?"$/, async function (pronounCase, url) {
101
+ const defaultTime = this.minWaitTime.page || 3000;
102
+ await gotoUrl(this.page, this.launchUrl + url);
103
+ await this.page.waitForSelector('body', { state: 'attached', timeout: defaultTime });
104
+ await waitForPageLoad(this.page, defaultTime);
105
+ });
106
+
107
+ // ---------------------------------------------------------------------------
108
+ // Browser history + reload
109
+ // ---------------------------------------------------------------------------
110
+
111
+ /**
112
+ * Moves forward one page in browser history.
113
+ *
114
+ * Example #1: When I move forward one page
115
+ * Example #2: When we move forward one page
116
+ * Example #3: And move forward one page
117
+ *
118
+ */
119
+ When(/^(I |we )*move forward one page$/, async function (pronounCase) {
120
+ await this.page.goForward();
121
+ });
122
+
123
+ /**
124
+ * Moves backward one page in browser history.
125
+ *
126
+ * Example #1: When I move backward one page
127
+ * Example #2: When we move backward one page
128
+ * Example #3: And move backward one page
129
+ *
130
+ */
131
+ When(/^(I |we )*move backward one page$/, async function (pronounCase) {
132
+ await this.page.goBack();
133
+ });
134
+
135
+ /**
136
+ * Reloads the current page.
137
+ *
138
+ * Example #1: When I reload
139
+ * Example #2: And I reload the page
140
+ * Example #3: And we reload page
141
+ * Example #4: And we reload the page
142
+ *
143
+ */
144
+ When(/^(I |we )*reload( the)*( page)*$/, async function (pronounCase, theCase, pageCase) {
145
+ await this.page.reload();
146
+ });
147
+
148
+ // ---------------------------------------------------------------------------
149
+ // URL / path assertions
150
+ // ---------------------------------------------------------------------------
151
+
152
+ /**
153
+ * Assert that the current page is or is not the homepage.
154
+ *
155
+ * Example #1: Then I should be on homepage
156
+ * Example #2: And I should be on the homepage
157
+ * Example #3: Then I should be on frontpage
158
+ * Example #4: And should be on the homepage
159
+ * Example #5: Then should be on homepage
160
+ * Example #6: And we should be on homepage
161
+ * Example #7: Then should be on frontpage
162
+ * Example #8: And we should be on the homepage
163
+ * Example #9: Then I should not be on homepage
164
+ * Example #10: And I should not be on the homepage
165
+ *
166
+ */
167
+ Then(/^(I |we )*should( not)* be on( the)* (homepage|frontpage)$/, async function (pronounCase, notCase, theCase, pageCase) {
168
+ const currentUrl = this.page.url();
169
+ if (notCase) {
170
+ assert.ok(currentUrl !== this.launchUrl && !currentUrl.startsWith(this.launchUrl + '/'),
171
+ `Should NOT be on homepage but current URL is: ${currentUrl}`);
172
+ } else {
173
+ assert.ok(currentUrl.startsWith(this.launchUrl),
174
+ `Should be on homepage but current URL is: ${currentUrl}`);
175
+ }
176
+ });
177
+
178
+ /**
179
+ * Assert that the current path is or is not equal to the specified path.
180
+ *
181
+ * Example #1: Then I should be on "/"
182
+ * Example #2: And I should be on "/user/login"
183
+ * Example #3: And I should be on "https://un.org"
184
+ * Example #4: Then we should be on the "/" page
185
+ * Example #5: And we should be on "/user/login"
186
+ * Example #6: Then should be on the "/user/reset" page
187
+ * Example #7: Then I should not be on "/"
188
+ * Example #8: And I should not be on "/user/login"
189
+ * Example #9: And I should not be on "https://un.org"
190
+ * Example #10: And we should not be on the "https://un.org" page
191
+ *
192
+ */
193
+ Then(/^(I |we )*should( not)* be on( the)* "([^"]*)?"( page)*$/, async function (pronounCase, notCase, theCase, url, pageCase) {
194
+ const currentUrl = this.page.url();
195
+ if (notCase) {
196
+ assert.ok(!currentUrl.includes(url), `URL should NOT contain "${url}" but it is: ${currentUrl}`);
197
+ } else {
198
+ assert.ok(currentUrl.includes(url), `URL should contain "${url}" but it is: ${currentUrl}`);
199
+ }
200
+ });
201
+
202
+ /**
203
+ * Assert that the current URL matches or does not match a regex pattern.
204
+ *
205
+ * Example #1: Then the url should match "/contact-us.html"
206
+ * Example #2: Then the url should not match "/contact-us.html"
207
+ * Example #3: And the url should match "^https://"
208
+ *
209
+ */
210
+ Then(/^(the )*url should( not)* match "([^"]*)?"$/, async function (theCase, notCase, pattern) {
211
+ const currentUrl = this.page.url();
212
+ const regex = new RegExp(pattern);
213
+ if (notCase) {
214
+ assert.ok(!regex.test(currentUrl), `URL "${currentUrl}" should NOT match "${pattern}" but it does.`);
215
+ } else {
216
+ assert.ok(regex.test(currentUrl), `URL "${currentUrl}" should match "${pattern}" but it does not.`);
217
+ }
218
+ });
@@ -0,0 +1,280 @@
1
+ 'use strict';
2
+
3
+ const { friendly } = require('./varbase-e2e');
4
+
5
+ // Network interception and mocking via Playwright's `page.route()` API.
6
+ //
7
+ // Lets BDD scenarios stub external dependencies, simulate slow / offline
8
+ // conditions, and assert what the client actually requested — without
9
+ // leaving the test runner. Patterns use Playwright glob matching: `**/path`,
10
+ // `*.gif`, regex inside parentheses, etc.
11
+ //
12
+ // Order matters: register routes BEFORE the action that triggers the request.
13
+
14
+ const { Given, When, Then } = require('@cucumber/cucumber');
15
+ const assert = require('assert');
16
+
17
+ function ensureRequestLog(world) {
18
+ if (world._requestLog) return;
19
+ world._requestLog = [];
20
+ world.page.on('request', (req) => {
21
+ world._requestLog.push({
22
+ url: req.url(),
23
+ method: req.method(),
24
+ resourceType: req.resourceType(),
25
+ });
26
+ });
27
+ }
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Stubbing
31
+ // ---------------------------------------------------------------------------
32
+
33
+ /**
34
+ * Stub a URL pattern with a JSON response body.
35
+ *
36
+ * Body is sent verbatim with `Content-Type: application/json` and HTTP 200.
37
+ *
38
+ * Example #1: Given the URL "**\/api/users" returns the JSON:
39
+ * """
40
+ * {"users": [{"id": 1, "name": "Alice"}]}
41
+ * """
42
+ * Example #2: Given the URL "**\/api/me" returns the JSON:
43
+ * """
44
+ * {"id": 1, "name": "Alice", "role": "admin"}
45
+ * """
46
+ * Example #3: Given the URL "**\/api/products?*" returns the JSON:
47
+ * """
48
+ * {"items": [], "total": 0}
49
+ * """
50
+ * Example #4: Given the URL "https://api.stripe.com/v1/charges" returns the JSON:
51
+ * """
52
+ * {"id": "ch_test", "status": "succeeded"}
53
+ * """
54
+ * Example #5: Given the URL "**\/api/feature-flags" returns the JSON:
55
+ * """
56
+ * {"darkMode": true, "betaUI": false}
57
+ * """
58
+ *
59
+ */
60
+ Given(/^the URL "([^"]*)" returns the JSON:$/, async function (urlPattern, body) {
61
+ try {
62
+ await this.page.route(urlPattern, async (route) => {
63
+ await route.fulfill({
64
+ status: 200,
65
+ contentType: 'application/json',
66
+ body,
67
+ });
68
+ });
69
+ } catch (e) {
70
+ throw friendly({
71
+ action: `register a JSON stub for "${urlPattern}"`,
72
+ cause: e,
73
+ hint: `URL pattern uses glob syntax — try "**/api/users" or "*.png".`,
74
+ });
75
+ }
76
+ });
77
+
78
+ /**
79
+ * Stub a URL pattern with a status code and plain-text body.
80
+ *
81
+ * Example #1: Given the URL "**\/api/login" returns status 401 with body "Unauthorized"
82
+ * Example #2: Given the URL "**\/api/health" returns status 503
83
+ * Example #3: Given the URL "**\/api/users" returns status 500 with body "Database down"
84
+ * Example #4: Given the URL "**\/api/orders/*" returns status 404
85
+ * Example #5: Given the URL "**\/api/admin/*" returns status 403 with body "Forbidden"
86
+ *
87
+ */
88
+ Given(/^the URL "([^"]*)" returns status (\d+)(?: with body "([^"]*)")?$/, async function (urlPattern, status, body) {
89
+ try {
90
+ await this.page.route(urlPattern, async (route) => {
91
+ await route.fulfill({ status: parseInt(status, 10), body: body || '' });
92
+ });
93
+ } catch (e) {
94
+ throw friendly({
95
+ action: `register a status-${status} stub for "${urlPattern}"`,
96
+ cause: e,
97
+ hint: `URL pattern uses glob syntax — try "**/api/users" or "*.png".`,
98
+ });
99
+ }
100
+ });
101
+
102
+ /**
103
+ * Block a URL pattern entirely (network failure simulation).
104
+ *
105
+ * Example #1: Given the URL "**\/analytics.js" is blocked
106
+ * Example #2: Given the URL "**\/google-analytics.com/**" is blocked
107
+ * Example #3: Given the URL "**\/sentry.io/**" is blocked
108
+ * Example #4: Given the URL "*.gif" is blocked
109
+ * Example #5: Given the URL "**\/api/tracking" is blocked
110
+ *
111
+ */
112
+ Given(/^the URL "([^"]*)" is blocked$/, async function (urlPattern) {
113
+ try {
114
+ await this.page.route(urlPattern, (route) => route.abort());
115
+ } catch (e) {
116
+ throw friendly({
117
+ action: `block "${urlPattern}"`,
118
+ cause: e,
119
+ hint: `URL pattern uses glob syntax — try "**/analytics.js" or "*.gif".`,
120
+ });
121
+ }
122
+ });
123
+
124
+ /**
125
+ * Delay matching requests by N milliseconds (slow-network simulation).
126
+ *
127
+ * The request still completes against the real upstream — only its arrival
128
+ * time at the page is shifted. Useful for verifying loading-state UI.
129
+ *
130
+ * Example #1: Given the URL "**\/api/**" is delayed by 1500 ms
131
+ * Example #2: Given the URL "**\/api/users" is delayed by 3000 ms
132
+ * Example #3: Given the URL "**\/api/heavy-report" is delayed by 5000 ms
133
+ * Example #4: Given the URL "*.png" is delayed by 500 ms
134
+ * Example #5: Given the URL "**\/api/checkout" is delayed by 2000 ms
135
+ *
136
+ */
137
+ Given(/^the URL "([^"]*)" is delayed by (\d+) ?ms$/, async function (urlPattern, delay) {
138
+ const ms = parseInt(delay, 10);
139
+ try {
140
+ await this.page.route(urlPattern, async (route) => {
141
+ await new Promise((r) => setTimeout(r, ms));
142
+ await route.continue();
143
+ });
144
+ } catch (e) {
145
+ throw friendly({
146
+ action: `delay "${urlPattern}" by ${ms} ms`,
147
+ cause: e,
148
+ hint: `URL pattern uses glob syntax — try "**/api/**" or "*.png".`,
149
+ });
150
+ }
151
+ });
152
+
153
+ /**
154
+ * Simulate offline mode for the rest of the scenario.
155
+ *
156
+ * Example #1: Given the network is offline
157
+ * Example #2: Given I am on the homepage
158
+ * When the network is offline
159
+ * And I follow "Refresh"
160
+ * Then I should see "Offline"
161
+ * Example #3: When the network is offline
162
+ * And I press "Sync"
163
+ * Then I should see "Cannot reach server"
164
+ * Example #4: Given the network is offline
165
+ * And I am on "/dashboard"
166
+ * Example #5: Given the network is offline
167
+ * When I click "Reload"
168
+ * Then "<.error-banner>" should be visible
169
+ *
170
+ */
171
+ Given(/^the network is offline$/, async function () {
172
+ try { await this.context.setOffline(true); }
173
+ catch (e) { throw friendly({ action: 'switch the network offline', cause: e, hint: 'the browser context may have been closed; try a fresh scenario.' }); }
174
+ });
175
+
176
+ /**
177
+ * Restore network connectivity after `the network is offline`.
178
+ *
179
+ * Example #1: Given the network is online
180
+ * Example #2: When the network is offline
181
+ * And I press "Sync"
182
+ * Then I should see "Cannot reach server"
183
+ * When the network is online
184
+ * And I press "Retry"
185
+ * Then I should see "Synced"
186
+ * Example #3: Given the network is online
187
+ * And I am on the homepage
188
+ * Example #4: When the network is online
189
+ * Example #5: Given the network is online
190
+ * When I follow "Reload"
191
+ * Then I should see "Welcome"
192
+ *
193
+ */
194
+ Given(/^the network is online$/, async function () {
195
+ try { await this.context.setOffline(false); }
196
+ catch (e) { throw friendly({ action: 'switch the network online', cause: e, hint: 'the browser context may have been closed; try a fresh scenario.' }); }
197
+ });
198
+
199
+ // ---------------------------------------------------------------------------
200
+ // Request logging
201
+ // ---------------------------------------------------------------------------
202
+
203
+ /**
204
+ * Start recording every outgoing request for later assertion.
205
+ *
206
+ * Example #1: Given I start recording network requests
207
+ * Example #2: Given I start recording network requests
208
+ * When I press "Buy now"
209
+ * Then a POST request to "**\/api/checkout" should have been made
210
+ * Example #3: Given we start recording network requests
211
+ * Example #4: Given I start recording network requests
212
+ * When I follow "Pricing"
213
+ * Then no request to "**\/tracking" should have been made
214
+ * Example #5: Given I start recording network requests
215
+ * When I scroll to the bottom
216
+ * Then a GET request to "**\/api/feed/page=2" should have been made
217
+ *
218
+ */
219
+ Given(/^(I |we )*start recording network requests$/, function () {
220
+ ensureRequestLog(this);
221
+ });
222
+
223
+ /**
224
+ * Assert at least one request matching a URL pattern was recorded.
225
+ *
226
+ * Patterns use simple `*` wildcards (regex internally).
227
+ *
228
+ * Example #1: Then a request to "**\/api/users" should have been made
229
+ * Example #2: Then a request to "**\/api/checkout" should have been made
230
+ * Example #3: Then a request to "**\/api/orders/*" should have been made
231
+ * Example #4: Then a request to "*.css" should have been made
232
+ * Example #5: Then a request to "https://api.example.com/**" should have been made
233
+ *
234
+ */
235
+ Then(/^a request to "([^"]*)" should have been made$/, function (urlPattern) {
236
+ ensureRequestLog(this);
237
+ let re;
238
+ try { re = new RegExp(urlPattern.replace(/\*/g, '.*')); }
239
+ catch (e) { throw friendly({ action: `match URL pattern "${urlPattern}"`, cause: e, hint: "URL pattern must be a valid regex or glob; check for unmatched brackets." }); }
240
+ const found = this._requestLog.some((r) => re.test(r.url));
241
+ assert.ok(found, `Expected at least one request matching "${urlPattern}".`);
242
+ });
243
+
244
+ /**
245
+ * Assert at least one HTTP-method-specific request was recorded.
246
+ *
247
+ * Example #1: Then a GET request to "**\/api/users" should have been made
248
+ * Example #2: Then a POST request to "**\/api/login" should have been made
249
+ * Example #3: Then a PUT request to "**\/api/users/1" should have been made
250
+ * Example #4: Then a PATCH request to "**\/api/orders/*" should have been made
251
+ * Example #5: Then a DELETE request to "**\/api/cart/items/*" should have been made
252
+ *
253
+ */
254
+ Then(/^a (GET|POST|PUT|PATCH|DELETE) request to "([^"]*)" should have been made$/, function (method, urlPattern) {
255
+ ensureRequestLog(this);
256
+ let re;
257
+ try { re = new RegExp(urlPattern.replace(/\*/g, '.*')); }
258
+ catch (e) { throw friendly({ action: `match URL pattern "${urlPattern}"`, cause: e, hint: "URL pattern must be a valid regex or glob; check for unmatched brackets." }); }
259
+ const found = this._requestLog.some((r) => r.method === method && re.test(r.url));
260
+ assert.ok(found, `Expected at least one ${method} request matching "${urlPattern}".`);
261
+ });
262
+
263
+ /**
264
+ * Assert NO request matching a URL pattern was recorded.
265
+ *
266
+ * Example #1: Then no request to "**\/tracking" should have been made
267
+ * Example #2: Then no request to "**\/google-analytics.com/**" should have been made
268
+ * Example #3: Then no request to "**\/api/admin/**" should have been made
269
+ * Example #4: Then no request to "*.gif" should have been made
270
+ * Example #5: Then no request to "**\/api/v1/legacy/**" should have been made
271
+ *
272
+ */
273
+ Then(/^no request to "([^"]*)" should have been made$/, function (urlPattern) {
274
+ ensureRequestLog(this);
275
+ let re;
276
+ try { re = new RegExp(urlPattern.replace(/\*/g, '.*')); }
277
+ catch (e) { throw friendly({ action: `match URL pattern "${urlPattern}"`, cause: e, hint: "URL pattern must be a valid regex or glob; check for unmatched brackets." }); }
278
+ const found = this._requestLog.some((r) => re.test(r.url));
279
+ assert.ok(!found, `Did not expect any request matching "${urlPattern}".`);
280
+ });
@@ -0,0 +1,141 @@
1
+ 'use strict';
2
+
3
+ // URL path and query parameter assertions, basic auth, history navigation.
4
+
5
+ const { Given, When, Then } = require('@cucumber/cucumber');
6
+ const assert = require('assert');
7
+
8
+ function currentUrl(page) { return new URL(page.url()); }
9
+
10
+ /**
11
+ * Assert the current URL pathname equals an expected string (no query/fragment).
12
+ *
13
+ * Example #1: Then the path should be "/dashboard"
14
+ * Example #2: Then the path should be "/about"
15
+ * Example #3: And the path should be "/checkout/step-2"
16
+ * Example #4: Then the path should be "/admin/users"
17
+ * Example #5: Then the path should be "/"
18
+ *
19
+ */
20
+ Then('the path should be {string}', async function (path) {
21
+ assert.strictEqual(currentUrl(this.page).pathname, path);
22
+ });
23
+
24
+ /**
25
+ * Assert the current URL pathname is NOT equal to an expected string.
26
+ *
27
+ * Example #1: Then the path should not be "/login"
28
+ * Example #2: Then the path should not be "/error"
29
+ * Example #3: And the path should not be "/admin"
30
+ * Example #4: Then the path should not be "/maintenance"
31
+ * Example #5: Then the path should not be "/forbidden"
32
+ *
33
+ */
34
+ Then('the path should not be {string}', async function (path) {
35
+ assert.notStrictEqual(currentUrl(this.page).pathname, path);
36
+ });
37
+
38
+ /**
39
+ * Assert the current URL has the named query parameter (any value).
40
+ *
41
+ * Example #1: Then current url should have the "lang" parameter
42
+ * Example #2: Then current url should have the "page" parameter
43
+ * Example #3: And current url should have the "q" parameter
44
+ * Example #4: Then current url should have the "ref" parameter
45
+ * Example #5: Then current url should have the "sort" parameter
46
+ *
47
+ */
48
+ Then('current url should have the {string} parameter', async function (param) {
49
+ const u = currentUrl(this.page);
50
+ assert.ok(u.searchParams.has(param), `Query param "${param}" not present in ${u.href}.`);
51
+ });
52
+
53
+ /**
54
+ * Assert the current URL has the named query parameter with the exact value.
55
+ *
56
+ * Example #1: Then current url should have the "lang" parameter with the "en" value
57
+ * Example #2: Then current url should have the "page" parameter with the "2" value
58
+ * Example #3: And current url should have the "sort" parameter with the "price" value
59
+ * Example #4: Then current url should have the "ref" parameter with the "newsletter" value
60
+ * Example #5: Then current url should have the "q" parameter with the "laptops" value
61
+ *
62
+ */
63
+ Then('current url should have the {string} parameter with the {string} value', async function (param, value) {
64
+ const u = currentUrl(this.page);
65
+ assert.strictEqual(u.searchParams.get(param), value);
66
+ });
67
+
68
+ /**
69
+ * Assert the current URL does NOT carry the named query parameter.
70
+ *
71
+ * Example #1: Then current url should not have the "missing" parameter
72
+ * Example #2: Then current url should not have the "debug" parameter
73
+ * Example #3: And current url should not have the "preview" parameter
74
+ * Example #4: Then current url should not have the "force" parameter
75
+ * Example #5: Then current url should not have the "test" parameter
76
+ *
77
+ */
78
+ Then('current url should not have the {string} parameter', async function (param) {
79
+ const u = currentUrl(this.page);
80
+ assert.ok(!u.searchParams.has(param), `Query param "${param}" should not be present.`);
81
+ });
82
+
83
+ /**
84
+ * Assert the current URL does NOT carry a parameter+value pair.
85
+ *
86
+ * Example #1: Then current url should not have the "lang" parameter with the "fr" value
87
+ * Example #2: Then current url should not have the "page" parameter with the "100" value
88
+ * Example #3: And current url should not have the "sort" parameter with the "old" value
89
+ * Example #4: Then current url should not have the "preview" parameter with the "1" value
90
+ * Example #5: Then current url should not have the "debug" parameter with the "true" value
91
+ *
92
+ */
93
+ Then('current url should not have the {string} parameter with the {string} value', async function (param, value) {
94
+ const u = currentUrl(this.page);
95
+ assert.notStrictEqual(u.searchParams.get(param), value);
96
+ });
97
+
98
+ /**
99
+ * Re-open the browser context with HTTP basic-auth credentials.
100
+ *
101
+ * Example #1: Given the basic authentication with the username "admin" and the password "secret"
102
+ * Example #2: Given the basic authentication with the username "guest" and the password "guest"
103
+ * Example #3: And the basic authentication with the username "alice" and the password "s3cret"
104
+ * Example #4: Given the basic authentication with the username "ci" and the password "ci-token-123"
105
+ * Example #5: Given the basic authentication with the username "tester" and the password ""
106
+ *
107
+ */
108
+ Given('the basic authentication with the username {string} and the password {string}', async function (user, pass) {
109
+ // Replace context with one that has HTTP basic auth credentials.
110
+ await this.context.close();
111
+ const playwright = require('playwright');
112
+ const playwrightConfig = require(require('path').join(process.cwd(), 'playwright.config'));
113
+ this.context = await this.playwrightBrowser.newContext({
114
+ ...playwrightConfig.contextOptions,
115
+ httpCredentials: { username: user, password: pass },
116
+ });
117
+ this.page = await this.context.newPage();
118
+ });
119
+
120
+ /**
121
+ * Navigate one step back in the browser history.
122
+ *
123
+ * Equivalent to clicking the browser back button. Throws no error if there
124
+ * is no history (returns null from `page.goBack()`).
125
+ *
126
+ * Example #1: When I go back
127
+ * Example #2: When I follow "About"
128
+ * And I go back
129
+ * Then I should be on the homepage
130
+ * Example #3: And I go back
131
+ * Example #4: When I am on "/cart"
132
+ * When I am on "/checkout"
133
+ * And I go back
134
+ * Then I should be on "/cart"
135
+ * Example #5: When I go back
136
+ * And I wait until the URL contains "/products"
137
+ *
138
+ */
139
+ When(/^(I |we )*go back$/, async function (pronoun) {
140
+ await this.page.goBack();
141
+ });