@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,653 @@
1
+ 'use strict';
2
+
3
+ // Form field assertions and interactions.
4
+
5
+ const { Given, When, Then } = require('@cucumber/cucumber');
6
+ const assert = require('assert');
7
+
8
+ // Resolve a field by label, name, id, or selector.
9
+ function fieldLocator(page, field) {
10
+ if (field.startsWith('#') || field.startsWith('.') || field.startsWith('[')) {
11
+ return page.locator(field).first();
12
+ }
13
+ // Try label, then by name/id.
14
+ return page.locator(
15
+ `label:has-text("${field}") >> xpath=..//input | label:has-text("${field}") >> xpath=..//textarea | label:has-text("${field}") >> xpath=..//select, [name="${field}"], #${field}`
16
+ ).first().or(page.getByLabel(field).first());
17
+ }
18
+
19
+ async function getField(page, field) {
20
+ // Prefer Playwright's accessible getByLabel; fallback to name/id/CSS.
21
+ if (field.startsWith('#') || field.startsWith('.') || field.startsWith('[')) {
22
+ return page.locator(field).first();
23
+ }
24
+ let loc = page.getByLabel(field).first();
25
+ if (await loc.count() > 0) return loc;
26
+ loc = page.locator(`[name="${field}"], #${field}`).first();
27
+ return loc;
28
+ }
29
+
30
+ /**
31
+ * Assert a field's current value is the empty string.
32
+ *
33
+ * Example #1: Then the field "username" should be empty
34
+ * Example #2: Then the field "Email" should be empty
35
+ * Example #3: And the field "#search" should be empty
36
+ * Example #4: Then the field "[name=phone]" should be empty
37
+ * Example #5: When I fill in "" for "Username"
38
+ * Then the field "username" should be empty
39
+ *
40
+ */
41
+ Then('the field {string} should be empty', async function (field) {
42
+ const loc = await getField(this.page, field);
43
+ const v = await loc.inputValue();
44
+ assert.strictEqual(v, '', `Field "${field}" should be empty, got "${v}".`);
45
+ });
46
+
47
+ /**
48
+ * Assert a field has a non-empty value.
49
+ *
50
+ * Example #1: Then the field "username" should not be empty
51
+ * Example #2: Then the field "Email" should not be empty
52
+ * Example #3: And the field "#search" should not be empty
53
+ * Example #4: When I fill in "alice" for "Username"
54
+ * Then the field "username" should not be empty
55
+ * Example #5: Then the field "[name=phone]" should not be empty
56
+ *
57
+ */
58
+ Then('the field {string} should not be empty', async function (field) {
59
+ const loc = await getField(this.page, field);
60
+ const v = await loc.inputValue();
61
+ assert.notStrictEqual(v, '', `Field "${field}" should not be empty.`);
62
+ });
63
+
64
+ /**
65
+ * Assert a field exists in the DOM (any state).
66
+ *
67
+ * Example #1: Then the field "username" should exist
68
+ * Example #2: Then the field "Email" should exist
69
+ * Example #3: And the field "#search" should exist
70
+ * Example #4: Then the field "Password" should exist
71
+ * Example #5: Then the field "[name=phone]" should exist
72
+ *
73
+ */
74
+ Then('the field {string} should exist', async function (field) {
75
+ const loc = await getField(this.page, field);
76
+ assert.ok(await loc.count() > 0, `Field "${field}" not found.`);
77
+ });
78
+
79
+ /**
80
+ * Assert a field does NOT exist in the DOM.
81
+ *
82
+ * Example #1: Then the field "missing-field" should not exist
83
+ * Example #2: Then the field "Legacy field" should not exist
84
+ * Example #3: And the field "#deprecated" should not exist
85
+ * Example #4: Then the field "Old name" should not exist
86
+ * Example #5: Then the field "[name=internal_only]" should not exist
87
+ *
88
+ */
89
+ Then('the field {string} should not exist', async function (field) {
90
+ const loc = await getField(this.page, field);
91
+ assert.strictEqual(await loc.count(), 0, `Field "${field}" should not exist.`);
92
+ });
93
+
94
+ /**
95
+ * Assert a field's enabled / disabled state.
96
+ *
97
+ * Example #1: Then the field "username" should have "enabled" state
98
+ * Example #2: Then the field "Email" should have "disabled" state
99
+ * Example #3: And the field "#search" should have "enabled" state
100
+ * Example #4: Then the field "Password" should have "disabled" state
101
+ * Example #5: Then the field "Country" should have "enabled" state
102
+ *
103
+ */
104
+ Then('the field {string} should have {string} state', async function (field, state) {
105
+ const loc = await getField(this.page, field);
106
+ const disabled = await loc.isDisabled();
107
+ if (state === 'disabled') assert.ok(disabled, `Field "${field}" should be disabled.`);
108
+ else assert.ok(!disabled, `Field "${field}" should be enabled.`);
109
+ });
110
+
111
+ /**
112
+ * Assert a field has the `required` or `aria-required="true"` attribute.
113
+ *
114
+ * Example #1: Then the field "username" should be required
115
+ * Example #2: Then the field "Email" should be required
116
+ * Example #3: And the field "Password" should be required
117
+ * Example #4: Then the field "Country" should be required
118
+ * Example #5: Then the field "Card number" should be required
119
+ *
120
+ */
121
+ Then('the field {string} should be required', async function (field) {
122
+ const loc = await getField(this.page, field);
123
+ const req = await loc.getAttribute('required');
124
+ const aria = await loc.getAttribute('aria-required');
125
+ assert.ok(req !== null || aria === 'true', `Field "${field}" should be required.`);
126
+ });
127
+
128
+ /**
129
+ * Assert a field is NOT required.
130
+ *
131
+ * Example #1: Then the field "bio" should not be required
132
+ * Example #2: Then the field "Nickname" should not be required
133
+ * Example #3: And the field "Phone" should not be required
134
+ * Example #4: Then the field "Comments" should not be required
135
+ * Example #5: Then the field "Twitter handle" should not be required
136
+ *
137
+ */
138
+ Then('the field {string} should not be required', async function (field) {
139
+ const loc = await getField(this.page, field);
140
+ const req = await loc.getAttribute('required');
141
+ const aria = await loc.getAttribute('aria-required');
142
+ assert.ok(req === null && aria !== 'true', `Field "${field}" should not be required.`);
143
+ });
144
+
145
+ /**
146
+ * Fill a multi-value form field (e.g. `name[]` array inputs) from a table.
147
+ *
148
+ * Example #1: When I fill in the multi-value field "tags" with the following values:
149
+ * | bdd |
150
+ * | testing |
151
+ * Example #2: When I fill in the multi-value field "skills" with the following values:
152
+ * | js |
153
+ * | css |
154
+ * | sql |
155
+ * Example #3: And we fill in the multi-value field "phones" with the following values:
156
+ * | 0790000000 |
157
+ * | 0791111111 |
158
+ * Example #4: When I fill in the multi-value field "ids" with the following values:
159
+ * | 1 |
160
+ * | 2 |
161
+ * Example #5: When I fill in the multi-value field "categories" with the following values:
162
+ * | tech |
163
+ * | devops |
164
+ * | testing |
165
+ *
166
+ */
167
+ When(/^(I |we )*fill in the multi-value field "([^"]*)" with the following values:$/, async function (pronoun, field, table) {
168
+ const values = table.raw().flat();
169
+ for (let i = 0; i < values.length; i++) {
170
+ const inputs = this.page.locator(`[name="${field}[${i}]"], [name="${field}[]"]`);
171
+ const count = await inputs.count();
172
+ const target = count > i ? inputs.nth(i) : inputs.first();
173
+ await target.fill(values[i]);
174
+ }
175
+ });
176
+
177
+ /**
178
+ * Fill an `<input type="color">` field with a hex value.
179
+ *
180
+ * Example #1: When I fill in the color field "favorite" with the value "#ff0000"
181
+ * Example #2: When I fill in the color field "primary" with the value "#0066cc"
182
+ * Example #3: And we fill in the color field "Accent" with the value "#00aa66"
183
+ * Example #4: When I fill in the color field "#bg-color" with the value "#000000"
184
+ * Example #5: When I fill in the color field "Theme" with the value "#ffaa00"
185
+ *
186
+ */
187
+ When(/^(I |we )*fill in the color field "([^"]*)" with the value "([^"]*)"$/, async function (pronoun, field, value) {
188
+ const loc = await getField(this.page, field);
189
+ await loc.fill(value);
190
+ });
191
+
192
+ /**
193
+ * Assert an `<input type="color">` field's current value (case-insensitive).
194
+ *
195
+ * Example #1: Then the color field "favorite" should have the value "#ff0000"
196
+ * Example #2: Then the color field "primary" should have the value "#0066cc"
197
+ * Example #3: And the color field "Accent" should have the value "#00aa66"
198
+ * Example #4: Then the color field "#bg-color" should have the value "#000000"
199
+ * Example #5: Then the color field "Theme" should have the value "#ffaa00"
200
+ *
201
+ */
202
+ Then('the color field {string} should have the value {string}', async function (field, value) {
203
+ const loc = await getField(this.page, field);
204
+ assert.strictEqual((await loc.inputValue()).toLowerCase(), value.toLowerCase());
205
+ });
206
+
207
+ /**
208
+ * Fill a CKEditor / contenteditable rich-text field with HTML.
209
+ *
210
+ * Example #1: When I fill in the WYSIWYG field "body" with the "Hello world"
211
+ * Example #2: When I fill in the WYSIWYG field "Description" with the "<strong>Bold</strong>"
212
+ * Example #3: And we fill in the WYSIWYG field "content" with the "Plain paragraph"
213
+ * Example #4: When I fill in the WYSIWYG field "post" with the "Multi line\ntext"
214
+ * Example #5: When I fill in the WYSIWYG field "Notes" with the "Final notes here"
215
+ *
216
+ */
217
+ When(/^(I |we )*fill in the WYSIWYG field "([^"]*)" with the "([^"]*)"$/, async function (pronoun, field, value) {
218
+ // CKEditor5/4 fallback: try contenteditable.
219
+ await this.page.evaluate(({ field, value }) => {
220
+ const editor = document.querySelector(`[name="${field}"], #${field}`);
221
+ if (editor && 'value' in editor) editor.value = value;
222
+ const ce = document.querySelector('.ck-editor__editable, [contenteditable="true"]');
223
+ if (ce) ce.innerHTML = `<p>${value}</p>`;
224
+ }, { field, value });
225
+ });
226
+
227
+ /**
228
+ * Assert a `<select>` contains an option with the given visible text.
229
+ *
230
+ * Example #1: Then the option "Mercedes" should exist within the select element "#cars"
231
+ * Example #2: Then the option "Editor" should exist within the select element "select[name=role]"
232
+ * Example #3: And the option "English" should exist within the select element "#language"
233
+ * Example #4: Then the option "Premium" should exist within the select element "#plan"
234
+ * Example #5: Then the option "Jordan" should exist within the select element "#country"
235
+ *
236
+ */
237
+ Then('the option {string} should exist within the select element {string}', async function (option, sel) {
238
+ const count = await this.page.locator(`${sel} option:has-text("${option}")`).count();
239
+ assert.ok(count > 0, `Option "${option}" not found in "${sel}".`);
240
+ });
241
+
242
+ /**
243
+ * Assert a `<select>` does NOT contain an option with the given text.
244
+ *
245
+ * Example #1: Then the option "Manager" should not exist within the select element "#role"
246
+ * Example #2: Then the option "Trial" should not exist within the select element "#plan"
247
+ * Example #3: And the option "Old country" should not exist within the select element "#country"
248
+ * Example #4: Then the option "Hidden" should not exist within the select element "#status"
249
+ * Example #5: Then the option "Legacy" should not exist within the select element "#mode"
250
+ *
251
+ */
252
+ Then('the option {string} should not exist within the select element {string}', async function (option, sel) {
253
+ const count = await this.page.locator(`${sel} option:has-text("${option}")`).count();
254
+ assert.strictEqual(count, 0, `Option "${option}" should not exist in "${sel}".`);
255
+ });
256
+
257
+ /**
258
+ * Assert an option is currently selected in a `<select>`.
259
+ *
260
+ * Example #1: Then the option "Mercedes" should be selected within the select element "#cars"
261
+ * Example #2: Then the option "Editor" should be selected within the select element "#role"
262
+ * Example #3: And the option "English" should be selected within the select element "#language"
263
+ * Example #4: Then the option "Premium" should be selected within the select element "#plan"
264
+ * Example #5: Then the option "Jordan" should be selected within the select element "#country"
265
+ *
266
+ */
267
+ Then('the option {string} should be selected within the select element {string}', async function (option, sel) {
268
+ const selected = await this.page.locator(sel).first().evaluate((el) => Array.from(el.selectedOptions || []).map((o) => o.text));
269
+ assert.ok(selected.includes(option), `Option "${option}" not selected in "${sel}".`);
270
+ });
271
+
272
+ /**
273
+ * Assert an option is NOT currently selected in a `<select>`.
274
+ *
275
+ * Example #1: Then the option "Admin" should not be selected within the select element "#role"
276
+ * Example #2: Then the option "Pro" should not be selected within the select element "#plan"
277
+ * Example #3: And the option "Other" should not be selected within the select element "#country"
278
+ * Example #4: Then the option "Inactive" should not be selected within the select element "#status"
279
+ * Example #5: Then the option "Trial" should not be selected within the select element "#mode"
280
+ *
281
+ */
282
+ Then('the option {string} should not be selected within the select element {string}', async function (option, sel) {
283
+ const selected = await this.page.locator(sel).first().evaluate((el) => Array.from(el.selectedOptions || []).map((o) => o.text));
284
+ assert.ok(!selected.includes(option), `Option "${option}" should not be selected in "${sel}".`);
285
+ });
286
+
287
+ /**
288
+ * Unselect a single option from a `<select multiple>`.
289
+ *
290
+ * Example #1: When I unselect "Red" from "#colors"
291
+ * Example #2: When I unselect "Editor" from "#role"
292
+ * Example #3: And we unselect "tag-a" from "select[name=tags]"
293
+ * Example #4: When I unselect "EN" from "#languages"
294
+ * Example #5: When I unselect "Beta" from "#features"
295
+ *
296
+ */
297
+ When(/^(I |we )*unselect "([^"]*)" from "([^"]*)"$/, async function (pronoun, option, sel) {
298
+ await this.page.locator(sel).first().evaluate((el, opt) => {
299
+ Array.from(el.options).forEach((o) => { if (o.text === opt || o.value === opt) o.selected = false; });
300
+ el.dispatchEvent(new Event('change', { bubbles: true }));
301
+ }, option);
302
+ });
303
+
304
+ /**
305
+ * Clear all selections in a `<select multiple>`.
306
+ *
307
+ * Example #1: When I clear the select "#colors"
308
+ * Example #2: When I clear the select "#tags"
309
+ * Example #3: And I clear the select "select[name=roles]"
310
+ * Example #4: When I clear the select "#languages"
311
+ * Example #5: When I clear the select "#features"
312
+ *
313
+ */
314
+ When(/^(I |we )*clear the select "([^"]*)"$/, async function (pronoun, sel) {
315
+ await this.page.locator(sel).first().evaluate((el) => {
316
+ Array.from(el.options).forEach((o) => (o.selected = false));
317
+ el.dispatchEvent(new Event('change', { bubbles: true }));
318
+ });
319
+ });
320
+
321
+ /**
322
+ * Check a CSS-addressed checkbox.
323
+ *
324
+ * Example #1: When I check the checkbox "#agree"
325
+ * Example #2: When I check the checkbox "input[name=newsletter]"
326
+ * Example #3: And we check the checkbox ".terms"
327
+ * Example #4: When I check the checkbox "#remember-me"
328
+ * Example #5: When I check the checkbox "[data-testid=privacy-checkbox]"
329
+ *
330
+ */
331
+ When(/^(I |we )*check the checkbox "([^"]*)"$/, async function (pronoun, sel) {
332
+ await this.page.locator(sel).first().check();
333
+ });
334
+
335
+ /**
336
+ * Uncheck a CSS-addressed checkbox.
337
+ *
338
+ * Example #1: When I uncheck the checkbox "#newsletter"
339
+ * Example #2: When I uncheck the checkbox "input[name=marketing]"
340
+ * Example #3: And we uncheck the checkbox ".analytics-opt-in"
341
+ * Example #4: When I uncheck the checkbox "#remember-me"
342
+ * Example #5: When I uncheck the checkbox "[data-testid=privacy-checkbox]"
343
+ *
344
+ */
345
+ When(/^(I |we )*uncheck the checkbox "([^"]*)"$/, async function (pronoun, sel) {
346
+ await this.page.locator(sel).first().uncheck();
347
+ });
348
+
349
+ /**
350
+ * Select a CSS-addressed radio button.
351
+ *
352
+ * Example #1: When I choose the radio button "#gender-male"
353
+ * Example #2: When I choose the radio button "input[value=premium]"
354
+ * Example #3: And we choose the radio button ".plan-monthly"
355
+ * Example #4: When I choose the radio button "#yes"
356
+ * Example #5: When I choose the radio button "[data-testid=annual]"
357
+ *
358
+ */
359
+ When(/^(I |we )*choose the radio button "([^"]*)"$/, async function (pronoun, sel) {
360
+ await this.page.locator(sel).first().check();
361
+ });
362
+
363
+ // Note: "the radio button :sel should be selected" is already provided by
364
+ // varbase-e2e.js core via a regex with the same phrasing. Skipped to avoid
365
+ // ambiguous step matches.
366
+
367
+ /**
368
+ * Fill a CSS-addressed input/textarea with a value.
369
+ *
370
+ * Example #1: When I fill in the field "#username" with "alice"
371
+ * Example #2: When I fill in the field "input[name=email]" with "alice@example.com"
372
+ * Example #3: And we fill in the field "#search" with "laptops"
373
+ * Example #4: When I fill in the field "[data-testid=phone]" with "0790000000"
374
+ * Example #5: When I fill in the field "textarea#message" with "Hello"
375
+ *
376
+ */
377
+ When(/^(I |we )*fill in the field "([^"]*)" with "([^"]*)"$/, async function (pronoun, sel, value) {
378
+ await this.page.locator(sel).first().fill(value);
379
+ });
380
+
381
+ /**
382
+ * Disable native browser validation on a form (sets `novalidate`).
383
+ *
384
+ * Useful when a test needs to submit an intentionally invalid form to
385
+ * exercise server-side error rendering.
386
+ *
387
+ * Example #1: Given browser validation for the form "#signup" is disabled
388
+ * Example #2: Given browser validation for the form "form[name=login]" is disabled
389
+ * Example #3: And browser validation for the form ".checkout-form" is disabled
390
+ * Example #4: Given browser validation for the form "#contact" is disabled
391
+ * Example #5: Given browser validation for the form "[data-testid=newsletter]" is disabled
392
+ *
393
+ */
394
+ Given('browser validation for the form {string} is disabled', async function (sel) {
395
+ await this.page.evaluate((s) => {
396
+ const f = document.querySelector(s);
397
+ if (f) f.setAttribute('novalidate', 'novalidate');
398
+ }, sel);
399
+ });
400
+
401
+ /**
402
+ * Fill paired `<input type=date>` + `<input type=time>` controls for a label.
403
+ *
404
+ * Example #1: When I fill in the datetime field "Start" with date "2026-05-08" and time "10:00"
405
+ * Example #2: When I fill in the datetime field "Departure" with date "2026-12-31" and time "23:59"
406
+ * Example #3: And we fill in the datetime field "Pickup" with date "2026-07-04" and time "08:30"
407
+ * Example #4: When I fill in the datetime field "Booking" with date "2026-01-01" and time "00:00"
408
+ * Example #5: When I fill in the datetime field "Reminder" with date "2026-09-15" and time "14:00"
409
+ *
410
+ */
411
+ When(/^(I |we )*fill in the datetime field "([^"]*)" with date "([^"]*)" and time "([^"]*)"$/, async function (pronoun, label, date, time) {
412
+ const root = this.page.locator(`label:has-text("${label}")`).first();
413
+ await this.page.locator(`input[type="date"][name*="${label}"], label:has-text("${label}") ~ * input[type="date"]`).first().fill(date).catch(() => {});
414
+ await this.page.locator(`input[type="time"][name*="${label}"], label:has-text("${label}") ~ * input[type="time"]`).first().fill(time).catch(() => {});
415
+ });
416
+
417
+ /**
418
+ * Fill only the date component of a paired datetime field.
419
+ *
420
+ * Example #1: When I fill in the date part of the datetime field "Start" with "2026-05-08"
421
+ * Example #2: When I fill in the date part of the datetime field "Birth date" with "1990-01-15"
422
+ * Example #3: And we fill in the date part of the datetime field "Booking" with "2026-12-31"
423
+ * Example #4: When I fill in the date part of the datetime field "Pickup" with "2026-07-04"
424
+ * Example #5: When I fill in the date part of the datetime field "Reminder" with "2026-09-15"
425
+ *
426
+ */
427
+ When(/^(I |we )*fill in the date part of the datetime field "([^"]*)" with "([^"]*)"$/, async function (pronoun, label, date) {
428
+ await this.page.locator(`label:has-text("${label}") ~ * input[type="date"], input[type="date"][name*="${label}"]`).first().fill(date);
429
+ });
430
+
431
+ /**
432
+ * Fill only the time component of a paired datetime field.
433
+ *
434
+ * Example #1: When I fill in the time part of the datetime field "Start" with "10:00"
435
+ * Example #2: When I fill in the time part of the datetime field "Pickup" with "08:30"
436
+ * Example #3: And we fill in the time part of the datetime field "Booking" with "23:59"
437
+ * Example #4: When I fill in the time part of the datetime field "Reminder" with "14:00"
438
+ * Example #5: When I fill in the time part of the datetime field "Departure" with "06:15"
439
+ *
440
+ */
441
+ When(/^(I |we )*fill in the time part of the datetime field "([^"]*)" with "([^"]*)"$/, async function (pronoun, label, time) {
442
+ await this.page.locator(`label:has-text("${label}") ~ * input[type="time"], input[type="time"][name*="${label}"]`).first().fill(time);
443
+ });
444
+
445
+ /**
446
+ * Fill the `start` half of a date-range pair.
447
+ *
448
+ * Example #1: When I fill in the start datetime field "Event" with date "2026-05-08" and time "10:00"
449
+ * Example #2: When I fill in the start datetime field "Trip" with date "2026-12-31" and time "08:00"
450
+ * Example #3: And we fill in the start datetime field "Window" with date "2026-07-04" and time "12:00"
451
+ * Example #4: When I fill in the start datetime field "Promo" with date "2026-01-01" and time "00:00"
452
+ * Example #5: When I fill in the start datetime field "Booking" with date "2026-09-15" and time "14:00"
453
+ *
454
+ */
455
+ When(/^(I |we )*fill in the start datetime field "([^"]*)" with date "([^"]*)" and time "([^"]*)"$/, async function (pronoun, label, date, time) {
456
+ await this.page.locator(`input[name*="${label}"][name*="start"][type="date"]`).first().fill(date).catch(() => {});
457
+ await this.page.locator(`input[name*="${label}"][name*="start"][type="time"]`).first().fill(time).catch(() => {});
458
+ });
459
+
460
+ /**
461
+ * Fill the `end` half of a date-range pair.
462
+ *
463
+ * Example #1: When I fill in the end datetime field "Event" with date "2026-05-09" and time "18:00"
464
+ * Example #2: When I fill in the end datetime field "Trip" with date "2027-01-07" and time "20:00"
465
+ * Example #3: And we fill in the end datetime field "Window" with date "2026-07-04" and time "18:00"
466
+ * Example #4: When I fill in the end datetime field "Promo" with date "2026-01-31" and time "23:59"
467
+ * Example #5: When I fill in the end datetime field "Booking" with date "2026-09-15" and time "16:00"
468
+ *
469
+ */
470
+ When(/^(I |we )*fill in the end datetime field "([^"]*)" with date "([^"]*)" and time "([^"]*)"$/, async function (pronoun, label, date, time) {
471
+ await this.page.locator(`input[name*="${label}"][name*="end"][type="date"]`).first().fill(date).catch(() => {});
472
+ await this.page.locator(`input[name*="${label}"][name*="end"][type="time"]`).first().fill(time).catch(() => {});
473
+ });
474
+
475
+ // ---------------------------------------------------------------------------
476
+ // Field text content + checkbox / radio state assertions
477
+ // (relocated from varbase-e2e.js; field-related assertions live in this file).
478
+ // ---------------------------------------------------------------------------
479
+
480
+ const { Then: ThenF } = require('@cucumber/cucumber');
481
+ const assertF = require('assert');
482
+ const { getLocatorText: getLocatorTextF } = require('./varbase-e2e');
483
+
484
+ /**
485
+ * Assert that a form field contains or does not contain specific text.
486
+ *
487
+ * Example #1: Then the "Username" field should contain "John Smith"
488
+ * Example #2: Then the "#username" field should not contain "John Smith"
489
+ * Example #3: And the "Email" field should contain "jon@example.com"
490
+ *
491
+ */
492
+ ThenF(/^(the )*"([^"]*)?" field should( not)* contain "([^"]*)?"$/, async function (theCase, field, notCase, expectedText) {
493
+ let selector = field;
494
+ if (!field.startsWith('#') && !field.startsWith('.')) {
495
+ const forAttr = await this.page.getByText(field, { exact: true }).getAttribute('for').catch(() => null);
496
+ if (forAttr) selector = '#' + forAttr;
497
+ }
498
+ const loc = this.page.locator(selector).first();
499
+ await loc.waitFor({ timeout: 5000 });
500
+ const content = await getLocatorTextF(loc);
501
+ if (notCase) {
502
+ assertF.ok(!content.includes(expectedText), `Field should NOT contain "${expectedText}" but it does.`);
503
+ } else {
504
+ assertF.ok(content.includes(expectedText), `Field should contain "${expectedText}" but it does not.`);
505
+ }
506
+ });
507
+
508
+ /**
509
+ * Assert that a checkbox should or should not be checked.
510
+ *
511
+ * Example #1: Then the "#privacy-policy" checkbox should be checked
512
+ * Example #2: Then the "#newsletter" checkbox should not be checked
513
+ * Example #3: And the "#agree" checkbox should be checked
514
+ * Example #4: Then the ".terms" checkbox should not be checked
515
+ * Example #5: When I check "Remember me"
516
+ * Then the "#remember" checkbox should be checked
517
+ *
518
+ */
519
+ ThenF(/^(the )*"([^"]*)?" checkbox should( not)* be checked$/, async function (theCase, checkbox, notCase) {
520
+ const isChecked = await this.page.locator(checkbox).isChecked();
521
+ if (notCase) {
522
+ assertF.ok(!isChecked, `Checkbox "${checkbox}" should NOT be checked but it is.`);
523
+ } else {
524
+ assertF.ok(isChecked, `Checkbox "${checkbox}" should be checked but it is not.`);
525
+ }
526
+ });
527
+
528
+ /**
529
+ * Assert that a checkbox is or is not checked.
530
+ *
531
+ * Alternate phrasing of "should be / should not be checked".
532
+ *
533
+ * Example #1: Then the "#remember-me" checkbox is checked
534
+ * Example #2: Then the "#newsletter" checkbox is not checked
535
+ * Example #3: And the "#agree" checkbox is checked
536
+ * Example #4: Then the ".terms" checkbox is not checked
537
+ * Example #5: When I check "Subscribe"
538
+ * Then the "#subscribe" checkbox is checked
539
+ *
540
+ */
541
+ ThenF(/^(the )*"([^"]*)?" checkbox is( not)* checked$/, async function (theCase, checkbox, notCase) {
542
+ const isChecked = await this.page.locator(checkbox).isChecked();
543
+ if (notCase) {
544
+ assertF.ok(!isChecked, `Checkbox "${checkbox}" should NOT be checked but it is.`);
545
+ } else {
546
+ assertF.ok(isChecked, `Checkbox "${checkbox}" should be checked but it is not.`);
547
+ }
548
+ });
549
+
550
+ /**
551
+ * Assert that a named checkbox should or should not be checked.
552
+ *
553
+ * Reverse phrasing — "checkbox <selector>" instead of "<selector> checkbox".
554
+ *
555
+ * Example #1: Then the checkbox "#privacy-policy" should be checked
556
+ * Example #2: Then the checkbox "#newsletter" should not be checked
557
+ * Example #3: And the checkbox "#agree" should be checked
558
+ * Example #4: Then the checkbox ".terms" should not be checked
559
+ * Example #5: When I check "Subscribe"
560
+ * Then the checkbox "#subscribe" should be checked
561
+ *
562
+ */
563
+ ThenF(/^(the )*checkbox "([^"]*)?" should( not)* be checked$/, async function (theCase, checkbox, notCase) {
564
+ const isChecked = await this.page.locator(checkbox).isChecked();
565
+ if (notCase) {
566
+ assertF.ok(!isChecked, `Checkbox "${checkbox}" should NOT be checked but it is.`);
567
+ } else {
568
+ assertF.ok(isChecked, `Checkbox "${checkbox}" should be checked but it is not.`);
569
+ }
570
+ });
571
+
572
+ /**
573
+ * Assert that a named checkbox is or is not checked.
574
+ *
575
+ * Reverse phrasing using "is / is not".
576
+ *
577
+ * Example #1: Then the checkbox "#remember-me" is checked
578
+ * Example #2: Then the checkbox "#newsletter" is not checked
579
+ * Example #3: And the checkbox "#agree" is checked
580
+ * Example #4: Then the checkbox ".terms" is not checked
581
+ * Example #5: When I check "Subscribe"
582
+ * Then the checkbox "#subscribe" is checked
583
+ *
584
+ */
585
+ ThenF(/^(the )*checkbox "([^"]*)?" is( not)* checked$/, async function (theCase, checkbox, notCase) {
586
+ const isChecked = await this.page.locator(checkbox).isChecked();
587
+ if (notCase) {
588
+ assertF.ok(!isChecked, `Checkbox "${checkbox}" should NOT be checked but it is.`);
589
+ } else {
590
+ assertF.ok(isChecked, `Checkbox "${checkbox}" should be checked but it is not.`);
591
+ }
592
+ });
593
+
594
+ /**
595
+ * Assert that a radio button should or should not be selected.
596
+ *
597
+ * Example #1: Then the radio button "#gender-male" should be selected
598
+ * Example #2: Then the radio button "#gender-female" should not be selected
599
+ * Example #3: Then the radio button ".option-1" should be selected
600
+ *
601
+ */
602
+ ThenF(/^(the )*radio button "([^"]*)?" should( not)* be selected$/, async function (theCase, radioButton, notCase) {
603
+ const isChecked = await this.page.locator(radioButton).isChecked();
604
+ if (notCase) {
605
+ assertF.ok(!isChecked, `Radio button "${radioButton}" should NOT be selected but it is.`);
606
+ } else {
607
+ assertF.ok(isChecked, `Radio button "${radioButton}" should be selected but it is not.`);
608
+ }
609
+ });
610
+
611
+ /**
612
+ * Assert that a radio button with a given value should or should not be selected.
613
+ *
614
+ * Looks up the radio by `[value="..."]`.
615
+ *
616
+ * Example #1: Then the radio button with value "male" should be selected
617
+ * Example #2: Then the radio button with value "female" should not be selected
618
+ * Example #3: And the radio button with value "premium" should be selected
619
+ * Example #4: Then the radio button with value "monthly" should not be selected
620
+ * Example #5: When I select radio button "Premium"
621
+ * Then the radio button with value "premium" should be selected
622
+ *
623
+ */
624
+ ThenF(/^(the )*radio button with value "([^"]*)?" should( not)* be selected$/, async function (theCase, radioValue, notCase) {
625
+ const isChecked = await this.page.locator(`input[type="radio"][value="${radioValue}"]`).first().isChecked();
626
+ if (notCase) {
627
+ assertF.ok(!isChecked, `Radio button with value "${radioValue}" should NOT be selected but it is.`);
628
+ } else {
629
+ assertF.ok(isChecked, `Radio button with value "${radioValue}" should be selected but it is not.`);
630
+ }
631
+ });
632
+
633
+ /**
634
+ * Assert that the radio button is or is not selected.
635
+ *
636
+ * Alternate phrasing using "is / is not selected".
637
+ *
638
+ * Example #1: Then the "#gender-male" radio button is selected
639
+ * Example #2: Then the "#gender-female" radio button is not selected
640
+ * Example #3: And the "#plan-premium" radio button is selected
641
+ * Example #4: Then the ".option-1" radio button is not selected
642
+ * Example #5: When I select radio button "Female"
643
+ * Then the "#gender-female" radio button is selected
644
+ *
645
+ */
646
+ ThenF(/^(the )*"([^"]*)?" radio button is( not)* selected$/, async function (theCase, radioButton, notCase) {
647
+ const isChecked = await this.page.locator(radioButton).isChecked();
648
+ if (notCase) {
649
+ assertF.ok(!isChecked, `Radio button "${radioButton}" should NOT be selected but it is.`);
650
+ } else {
651
+ assertF.ok(isChecked, `Radio button "${radioButton}" should be selected but it is not.`);
652
+ }
653
+ });