@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,194 @@
1
+ 'use strict';
2
+
3
+ // Cookie / local storage / session storage manipulation steps.
4
+ //
5
+ // Pre-seed per-scenario state without going through the UI. Useful for
6
+ // onboarding flags, A/B-test bucket assignments, feature toggles, language
7
+ // preferences, and any cookie-driven server behaviour.
8
+ //
9
+ // Cookies are scoped to the current page's domain (or `localhost` if no page
10
+ // is loaded yet). Local storage and session storage are scoped to the current
11
+ // page's origin and require a navigated page first.
12
+
13
+ const { Given } = require('@cucumber/cucumber');
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // Cookies — context-level
17
+ // ---------------------------------------------------------------------------
18
+
19
+ /**
20
+ * Set a cookie on the current browser context.
21
+ *
22
+ * Example #1: Given the cookie "session" is set to "abc123"
23
+ * Example #2: Given the cookie "lang" is set to "en"
24
+ * Example #3: Given cookie "ab_bucket" is set to "variant-b"
25
+ * Example #4: Given the cookie "consent" is set to "accepted"
26
+ * And I am on the homepage
27
+ * Example #5: Given the cookie "feature_flag" is set to "enabled"
28
+ * And I am on "/dashboard"
29
+ *
30
+ */
31
+ Given(/^(?:the cookie|cookie) "([^"]*)" is set to "([^"]*)"$/, async function (name, value) {
32
+ const url = this.page.url() && this.page.url() !== 'about:blank' ? this.page.url() : (this.launchUrl || 'http://localhost');
33
+ const u = new URL(url);
34
+ await this.context.addCookies([{
35
+ name,
36
+ value,
37
+ domain: u.hostname,
38
+ path: '/',
39
+ }]);
40
+ });
41
+
42
+ /**
43
+ * Remove a single cookie by name (other cookies preserved).
44
+ *
45
+ * Example #1: Given the cookie "session" is removed
46
+ * Example #2: Given the cookie "consent" is removed
47
+ * When I reload the page
48
+ * Then I should see "Accept cookies"
49
+ * Example #3: Given the cookie "ab_bucket" is removed
50
+ * Example #4: Given the cookie "lang" is removed
51
+ * And I am on the homepage
52
+ * Example #5: Given the cookie "feature_flag" is removed
53
+ * When I follow "Pricing"
54
+ *
55
+ */
56
+ Given(/^the cookie "([^"]*)" is removed$/, async function (name) {
57
+ const cookies = await this.context.cookies();
58
+ const remaining = cookies.filter((c) => c.name !== name);
59
+ await this.context.clearCookies();
60
+ if (remaining.length) await this.context.addCookies(remaining);
61
+ });
62
+
63
+ /**
64
+ * Wipe every cookie on the browser context.
65
+ *
66
+ * Example #1: Given all cookies are cleared
67
+ * Example #2: Given all cookies are cleared
68
+ * When I am on the homepage
69
+ * Then I should see "Sign in"
70
+ * Example #3: Given all cookies are cleared
71
+ * And I am an anonymous user
72
+ * Example #4: Given all cookies are cleared
73
+ * And localStorage is cleared
74
+ * And sessionStorage is cleared
75
+ * Example #5: Given all cookies are cleared
76
+ * When I follow "Login"
77
+ *
78
+ */
79
+ Given(/^all cookies are cleared$/, async function () {
80
+ await this.context.clearCookies();
81
+ });
82
+
83
+ // ---------------------------------------------------------------------------
84
+ // localStorage / sessionStorage — page-level
85
+ // ---------------------------------------------------------------------------
86
+
87
+ /**
88
+ * Set a local storage entry on the current page.
89
+ *
90
+ * The page must be loaded first (local storage is per-origin). Stored value
91
+ * is always a string — JSON-encode complex objects manually.
92
+ *
93
+ * Example #1: Given the local storage "user" is set to "Alice"
94
+ * Example #2: Given I am on the homepage
95
+ * And the local storage "theme" is set to "dark"
96
+ * When I reload the page
97
+ * Then "<body>" should have class "theme-dark"
98
+ * Example #3: Given local storage "lang" is set to "fr"
99
+ * Example #4: Given the local storage "onboarding_complete" is set to "true"
100
+ * And I am on "/dashboard"
101
+ * Then I should not see "Welcome tour"
102
+ * Example #5: Given the local storage "feature_flag" is set to "enabled"
103
+ *
104
+ */
105
+ Given(/^(?:the )?local storage "([^"]*)" is set to "([^"]*)"$/, async function (key, value) {
106
+ await this.page.evaluate(({ k, v }) => localStorage.setItem(k, v), { k: key, v: value });
107
+ });
108
+
109
+ /**
110
+ * Remove a single local storage entry.
111
+ *
112
+ * Example #1: Given the local storage "user" is removed
113
+ * Example #2: Given local storage "theme" is removed
114
+ * When I reload the page
115
+ * Then "<body>" should not have class "theme-dark"
116
+ * Example #3: Given the local storage "onboarding_complete" is removed
117
+ * Example #4: Given the local storage "cart" is removed
118
+ * And I am on "/cart"
119
+ * Then I should see "Your cart is empty"
120
+ * Example #5: Given the local storage "auth_token" is removed
121
+ *
122
+ */
123
+ Given(/^(?:the )?local storage "([^"]*)" is removed$/, async function (key) {
124
+ await this.page.evaluate((k) => localStorage.removeItem(k), key);
125
+ });
126
+
127
+ /**
128
+ * Wipe local storage for the current origin.
129
+ *
130
+ * Example #1: Given local storage is cleared
131
+ * Example #2: Given I am on the homepage
132
+ * And local storage is cleared
133
+ * When I reload the page
134
+ * Example #3: Given local storage is cleared
135
+ * And all cookies are cleared
136
+ * Example #4: Given local storage is cleared
137
+ * And session storage is cleared
138
+ * Example #5: Given local storage is cleared
139
+ * Then I should see "First-time visitor"
140
+ *
141
+ */
142
+ Given(/^local storage is cleared$/, async function () {
143
+ await this.page.evaluate(() => localStorage.clear());
144
+ });
145
+
146
+ /**
147
+ * Set a session storage entry on the current page.
148
+ *
149
+ * Example #1: Given the session storage "checkout step" is set to "2"
150
+ * Example #2: Given the session storage "temp form" is set to "draft"
151
+ * Example #3: Given session storage "tab id" is set to "tab-1"
152
+ * Example #4: Given the session storage "wizard progress" is set to "3/5"
153
+ * And I am on "/wizard"
154
+ * Example #5: Given the session storage "from checkout" is set to "true"
155
+ * And I am on "/cart"
156
+ *
157
+ */
158
+ Given(/^(?:the )?session storage "([^"]*)" is set to "([^"]*)"$/, async function (key, value) {
159
+ await this.page.evaluate(({ k, v }) => sessionStorage.setItem(k, v), { k: key, v: value });
160
+ });
161
+
162
+ /**
163
+ * Remove a single session storage entry.
164
+ *
165
+ * Example #1: Given the session storage "checkout step" is removed
166
+ * Example #2: Given session storage "temp form" is removed
167
+ * Example #3: Given the session storage "wizard progress" is removed
168
+ * When I reload the page
169
+ * Then I should see "Step 1"
170
+ * Example #4: Given the session storage "tab id" is removed
171
+ * Example #5: Given the session storage "from checkout" is removed
172
+ *
173
+ */
174
+ Given(/^(?:the )?session storage "([^"]*)" is removed$/, async function (key) {
175
+ await this.page.evaluate((k) => sessionStorage.removeItem(k), key);
176
+ });
177
+
178
+ /**
179
+ * Wipe session storage for the current origin.
180
+ *
181
+ * Example #1: Given session storage is cleared
182
+ * Example #2: Given session storage is cleared
183
+ * And local storage is cleared
184
+ * Example #3: Given session storage is cleared
185
+ * And all cookies are cleared
186
+ * Example #4: Given session storage is cleared
187
+ * When I reload the page
188
+ * Example #5: Given session storage is cleared
189
+ * And I am an anonymous user
190
+ *
191
+ */
192
+ Given(/^session storage is cleared$/, async function () {
193
+ await this.page.evaluate(() => sessionStorage.clear());
194
+ });
@@ -0,0 +1,181 @@
1
+ 'use strict';
2
+
3
+ // Inspect and assert HTML table contents.
4
+
5
+ const { Then } = require('@cucumber/cucumber');
6
+ const assert = require('assert');
7
+
8
+ async function getTable(page, sel) {
9
+ return await page.locator(sel).first().evaluate((tbl) => {
10
+ const rows = Array.from(tbl.querySelectorAll('tr'));
11
+ const head = Array.from((tbl.querySelector('thead') || tbl).querySelectorAll('th')).map((c) => c.textContent.trim());
12
+ const body = rows
13
+ .filter((r) => r.querySelector('td'))
14
+ .map((r) => Array.from(r.querySelectorAll('td')).map((c) => c.textContent.trim()));
15
+ return { head, body, rowCount: body.length, colCount: (body[0] || head).length };
16
+ });
17
+ }
18
+
19
+ /**
20
+ * Assert a table has exactly N body rows.
21
+ *
22
+ * Example #1: Then the table "#users" should have 5 rows
23
+ * Example #2: Then the table "table.orders" should have 1 row
24
+ * Example #3: And the table "[data-testid=products]" should have 12 rows
25
+ * Example #4: Then the table "#audit-log" should have 0 rows
26
+ * Example #5: Then the table "table.responsive" should have 20 rows
27
+ *
28
+ */
29
+ Then('the table {string} should have {int} row(s)', async function (sel, count) {
30
+ const t = await getTable(this.page, sel);
31
+ assert.strictEqual(t.rowCount, count, `Table "${sel}" has ${t.rowCount} rows, expected ${count}.`);
32
+ });
33
+
34
+ /**
35
+ * Assert a table has exactly N columns.
36
+ *
37
+ * Example #1: Then the table "#users" should have 4 columns
38
+ * Example #2: Then the table "table.orders" should have 6 columns
39
+ * Example #3: And the table "[data-testid=products]" should have 5 columns
40
+ * Example #4: Then the table "#summary" should have 2 columns
41
+ * Example #5: Then the table "table.responsive" should have 8 columns
42
+ *
43
+ */
44
+ Then('the table {string} should have {int} column(s)', async function (sel, count) {
45
+ const t = await getTable(this.page, sel);
46
+ assert.strictEqual(t.colCount, count, `Table "${sel}" has ${t.colCount} columns, expected ${count}.`);
47
+ });
48
+
49
+ /**
50
+ * Assert the table header contains every column listed in the table.
51
+ *
52
+ * Example #1: Then the table "#users" should contain the following columns:
53
+ * | Name |
54
+ * | Email |
55
+ * Example #2: Then the table "table.orders" should contain the following columns:
56
+ * | Order ID | Status | Total |
57
+ * Example #3: And the table "[data-testid=products]" should contain the following columns:
58
+ * | Title | Price | Stock |
59
+ * Example #4: Then the table "#audit-log" should contain the following columns:
60
+ * | Timestamp | Actor | Action |
61
+ * Example #5: Then the table ".grid" should contain the following columns:
62
+ * | Country | Population | Capital |
63
+ *
64
+ */
65
+ Then('the table {string} should contain the following columns:', async function (sel, table) {
66
+ const t = await getTable(this.page, sel);
67
+ const want = table.raw().flat();
68
+ for (const col of want) {
69
+ assert.ok(t.head.includes(col), `Column "${col}" not found in [${t.head.join(', ')}].`);
70
+ }
71
+ });
72
+
73
+ /**
74
+ * Assert a table has zero body rows.
75
+ *
76
+ * Example #1: Then the table "#users" should be empty
77
+ * Example #2: Then the table "table.orders" should be empty
78
+ * Example #3: And the table "[data-testid=products]" should be empty
79
+ * Example #4: Then the table "#audit-log" should be empty
80
+ * Example #5: Then the table ".grid" should be empty
81
+ *
82
+ */
83
+ Then('the table {string} should be empty', async function (sel) {
84
+ const t = await getTable(this.page, sel);
85
+ assert.strictEqual(t.rowCount, 0, `Table "${sel}" should be empty.`);
86
+ });
87
+
88
+ /**
89
+ * Assert a table has at least one body row.
90
+ *
91
+ * Example #1: Then the table "#users" should not be empty
92
+ * Example #2: Then the table "table.orders" should not be empty
93
+ * Example #3: And the table "[data-testid=products]" should not be empty
94
+ * Example #4: Then the table "#audit-log" should not be empty
95
+ * Example #5: Then the table ".grid" should not be empty
96
+ *
97
+ */
98
+ Then('the table {string} should not be empty', async function (sel) {
99
+ const t = await getTable(this.page, sel);
100
+ assert.ok(t.rowCount > 0, `Table "${sel}" should not be empty.`);
101
+ });
102
+
103
+ /**
104
+ * Assert a table is sorted ascending or descending by a header.
105
+ *
106
+ * Direction accepts `ascending` / `asc` / `descending` / `desc`.
107
+ *
108
+ * Example #1: Then the table "#users" should be sorted by "Name" in "ascending" order
109
+ * Example #2: Then the table "table.orders" should be sorted by "Total" in "descending" order
110
+ * Example #3: And the table "[data-testid=products]" should be sorted by "Price" in "asc" order
111
+ * Example #4: Then the table "#audit-log" should be sorted by "Timestamp" in "desc" order
112
+ * Example #5: Then the table ".grid" should be sorted by "Country" in "ascending" order
113
+ *
114
+ */
115
+ Then('the table {string} should be sorted by {string} in {string} order', async function (sel, column, direction) {
116
+ const t = await getTable(this.page, sel);
117
+ const idx = t.head.findIndex((h) => h === column);
118
+ assert.ok(idx !== -1, `Column "${column}" not found.`);
119
+ const values = t.body.map((r) => r[idx]);
120
+ const sorted = [...values].sort();
121
+ if (direction.toLowerCase() === 'descending' || direction.toLowerCase() === 'desc') sorted.reverse();
122
+ assert.deepStrictEqual(values, sorted, `Table "${sel}" not sorted by "${column}" ${direction}.`);
123
+ });
124
+
125
+ /**
126
+ * Assert a table contains every row described by the data table (substring match per cell).
127
+ *
128
+ * Example #1: Then the table "#users" should contain the following rows:
129
+ * | Alice | Editor |
130
+ * | Bob | Admin |
131
+ * Example #2: Then the table "table.orders" should contain the following rows:
132
+ * | 1234 | Shipped |
133
+ * | 5678 | Cancelled |
134
+ * Example #3: And the table "[data-testid=products]" should contain the following rows:
135
+ * | Laptop | $999.00 |
136
+ * Example #4: Then the table "#audit-log" should contain the following rows:
137
+ * | login |
138
+ * Example #5: Then the table ".grid" should contain the following rows:
139
+ * | Jordan | Amman |
140
+ *
141
+ */
142
+ Then('the table {string} should contain the following rows:', async function (sel, table) {
143
+ const t = await getTable(this.page, sel);
144
+ for (const row of table.raw()) {
145
+ const found = t.body.some((br) => row.every((cell) => br.some((bc) => bc.indexOf(cell) !== -1)));
146
+ assert.ok(found, `Row ${JSON.stringify(row)} not found in table.`);
147
+ }
148
+ });
149
+
150
+ /**
151
+ * Assert a row containing a given text fragment also contains every listed cell value.
152
+ *
153
+ * Example #1: Then the "Alice" row should contain the following:
154
+ * | Editor |
155
+ * | Active |
156
+ * Example #2: Then the "Order #1234" row should contain the following:
157
+ * | Shipped |
158
+ * Example #3: And the "Project Alpha" row should contain the following:
159
+ * | Open |
160
+ * | High |
161
+ * Example #4: Then the "alice@example.com" row should contain the following:
162
+ * | Admin |
163
+ * Example #5: Then the "Premium" row should contain the following:
164
+ * | $99 |
165
+ *
166
+ */
167
+ Then('the {string} row should contain the following:', async function (rowText, table) {
168
+ const cells = table.raw().flat();
169
+ // Find a tr whose text contains rowText.
170
+ const trs = await this.page.locator('tr').all();
171
+ let match = null;
172
+ for (const tr of trs) {
173
+ const txt = await tr.innerText();
174
+ if (txt.indexOf(rowText) !== -1) { match = tr; break; }
175
+ }
176
+ assert.ok(match, `No row matching "${rowText}" was found.`);
177
+ const text = await match.innerText();
178
+ for (const c of cells) {
179
+ assert.ok(text.indexOf(c) !== -1, `Row "${rowText}" does not contain "${c}".`);
180
+ }
181
+ });