@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,228 @@
1
+ 'use strict';
2
+
3
+ const { friendly } = require('./varbase-e2e');
4
+
5
+ // Iframe / frame steps backed by Playwright's `frameLocator` API.
6
+ //
7
+ // Frames are addressed by CSS selector. Once switched, frame-scoped
8
+ // interaction steps target the chosen frame instead of the main document.
9
+ // Switch back with "I switch to the root document".
10
+
11
+ const { When, Then } = require('@cucumber/cucumber');
12
+ const assert = require('assert');
13
+
14
+ function frameRoot(world) {
15
+ return world.frame || world.page;
16
+ }
17
+
18
+ function explain(action, detail, e) {
19
+ return friendly({
20
+ action,
21
+ cause: e,
22
+ hint: `wait for the iframe to load first ("When I wait for \\"iframe\\" to appear"). Detail: ${detail}.`,
23
+ });
24
+ }
25
+
26
+ /**
27
+ * Switch the active scope to an iframe addressed by CSS selector.
28
+ *
29
+ * Example #1: When I switch to iframe "iframe.payment"
30
+ * Example #2: When I switch to the iframe "#stripe-frame"
31
+ * Example #3: And we switch to iframe "iframe[name=widget]"
32
+ * Example #4: When I switch to iframe ".captcha-frame"
33
+ * Example #5: When I switch to the iframe "iframe[src*=embed]"
34
+ *
35
+ */
36
+ When(/^(I |we )*switch to (the )?iframe "([^"]*)"$/, async function (pronoun, theCase, sel) {
37
+ try {
38
+ if (!this._rootPage) this._rootPage = this.page;
39
+ this.frame = this.page.frameLocator(sel);
40
+ } catch (e) {
41
+ throw explain(`switch to iframe "${sel}"`, `selector tried: ${sel}`, e);
42
+ }
43
+ });
44
+
45
+ /**
46
+ * Switch the active scope to an iframe by Playwright frame-locator string.
47
+ *
48
+ * Example #1: When I switch to iframe with locator "iframe.payment"
49
+ * Example #2: When I switch to iframe with locator "#stripe-frame"
50
+ * Example #3: And I switch to iframe with locator "iframe[name=widget]"
51
+ * Example #4: When I switch to iframe with locator ".captcha-frame"
52
+ * Example #5: When I switch to iframe with locator "iframe[src*=embed]"
53
+ *
54
+ */
55
+ When(/^(I |we )*switch to iframe with locator "([^"]*)"$/, async function (pronoun, sel) {
56
+ try {
57
+ if (!this._rootPage) this._rootPage = this.page;
58
+ this.frame = this.page.frameLocator(sel);
59
+ } catch (e) {
60
+ throw explain(`switch to iframe with locator "${sel}"`, `locator: ${sel}`, e);
61
+ }
62
+ });
63
+
64
+ /**
65
+ * Return the active scope to the top document (clear the iframe scope).
66
+ *
67
+ * Example #1: When I switch to the root document
68
+ * Example #2: When I switch to iframe ".captcha"
69
+ * And I click "I am not a robot" inside the iframe
70
+ * And I switch to the root document
71
+ * Example #3: And we switch to the root document
72
+ * Example #4: When I switch to iframe "#payment"
73
+ * And I press "Pay"
74
+ * And I switch to the root document
75
+ * Then I should see "Payment received"
76
+ * Example #5: When I switch to the root document
77
+ * And I press "Refresh"
78
+ *
79
+ */
80
+ When(/^(I |we )*switch to the root document$/, async function () {
81
+ this.frame = null;
82
+ });
83
+
84
+ /**
85
+ * Switch the active scope to an iframe by its `title` attribute.
86
+ *
87
+ * Example #1: When I switch to the iframe with title "Payment"
88
+ * Example #2: When I switch to the iframe with title "Captcha"
89
+ * Example #3: And we switch to the iframe with title "Widget"
90
+ * Example #4: When I switch to the iframe with title "Embedded video"
91
+ * Example #5: When I switch to the iframe with title "Stripe checkout"
92
+ *
93
+ */
94
+ When(/^(I |we )*switch to the iframe with title "([^"]*)"$/, async function (pronoun, title) {
95
+ try {
96
+ if (!this._rootPage) this._rootPage = this.page;
97
+ this.frame = this.page.frameLocator(`iframe[title="${title}"]`);
98
+ } catch (e) {
99
+ throw explain(`switch to iframe by title "${title}"`, `selector: iframe[title="${title}"]`, e);
100
+ }
101
+ });
102
+
103
+ /**
104
+ * Switch the active scope to an iframe by its `name` attribute.
105
+ *
106
+ * Example #1: When I switch to the iframe with name "payment"
107
+ * Example #2: When I switch to the iframe with name "captcha"
108
+ * Example #3: And we switch to the iframe with name "widget"
109
+ * Example #4: When I switch to the iframe with name "embed"
110
+ * Example #5: When I switch to the iframe with name "stripe"
111
+ *
112
+ */
113
+ When(/^(I |we )*switch to the iframe with name "([^"]*)"$/, async function (pronoun, name) {
114
+ try {
115
+ if (!this._rootPage) this._rootPage = this.page;
116
+ this.frame = this.page.frameLocator(`iframe[name="${name}"]`);
117
+ } catch (e) {
118
+ throw explain(`switch to iframe by name "${name}"`, `selector: iframe[name="${name}"]`, e);
119
+ }
120
+ });
121
+
122
+ // ---------------------------------------------------------------------------
123
+ // Frame-scoped click + fill + assertions
124
+ // ---------------------------------------------------------------------------
125
+
126
+ /**
127
+ * Click visible text inside the active iframe.
128
+ *
129
+ * Example #1: When I click "Confirm" inside the iframe
130
+ * Example #2: When I click "Cancel" inside the iframe
131
+ * Example #3: And we click "Continue" inside the iframe
132
+ * Example #4: When I click "OK" inside the iframe
133
+ * Example #5: When I click "Close" inside the iframe
134
+ *
135
+ */
136
+ When(/^(I |we )*click "([^"]*)" inside the iframe$/, async function (pronoun, text) {
137
+ try {
138
+ const root = frameRoot(this);
139
+ if (!root.locator) throw friendly('No active iframe. Switch first.');
140
+ await root.locator(`text="${text}"`).first().click();
141
+ } catch (e) {
142
+ throw friendly({
143
+ action: `click "${text}" inside the iframe`,
144
+ cause: e,
145
+ hint: `switch to the iframe first ("When I switch to iframe '<selector>'"), then check the visible text matches exactly.`,
146
+ });
147
+ }
148
+ });
149
+
150
+ /**
151
+ * Click an attribute-addressed element inside the active iframe.
152
+ *
153
+ * Tries id, class, name, and data-testid in that order.
154
+ *
155
+ * Example #1: When I click "submit-btn" by attr inside the iframe
156
+ * Example #2: When I click "cancel" by attr inside the iframe
157
+ * Example #3: And we click "continue" by attr inside the iframe
158
+ * Example #4: When I click "close" by attr inside the iframe
159
+ * Example #5: When I click "primary-cta" by attr inside the iframe
160
+ *
161
+ */
162
+ When(/^(I |we )*click "([^"]*)" by attr inside the iframe$/, async function (pronoun, attrValue) {
163
+ try {
164
+ const root = frameRoot(this);
165
+ await root.locator(`#${attrValue}, .${attrValue}, [name="${attrValue}"], [data-testid="${attrValue}"]`).first().click();
166
+ } catch (e) {
167
+ throw friendly({
168
+ action: `click "${attrValue}" by attribute inside the iframe`,
169
+ cause: e,
170
+ hint: `switch to the iframe first; checked id, class, [name], [data-testid] — none matched.`,
171
+ });
172
+ }
173
+ });
174
+
175
+ /**
176
+ * Fill a field inside the active iframe by `[name]` or id.
177
+ *
178
+ * Example #1: When I fill in "card_number" with "4242 4242 4242 4242" inside the iframe
179
+ * Example #2: When I fill in "cvv" with "123" inside the iframe
180
+ * Example #3: And we fill in "exp_date" with "12/30" inside the iframe
181
+ * Example #4: When I fill in "email" with "alice@example.com" inside the iframe
182
+ * Example #5: When I fill in "name" with "Alice" inside the iframe
183
+ *
184
+ */
185
+ When(/^(I |we )*fill in "([^"]*)" with "([^"]*)" inside the iframe$/, async function (pronoun, field, value) {
186
+ try {
187
+ const root = frameRoot(this);
188
+ await root.locator(`[name="${field}"], #${field}`).first().fill(value);
189
+ } catch (e) {
190
+ throw friendly({
191
+ action: `fill "${field}" inside the iframe`,
192
+ cause: e,
193
+ hint: `switch to the iframe first; checked [name] and id — neither matched.`,
194
+ });
195
+ }
196
+ });
197
+
198
+ /**
199
+ * Assert visible text exists inside the active iframe.
200
+ *
201
+ * Example #1: Then I should see "Payment received" inside the iframe
202
+ * Example #2: Then I should see "Welcome" inside the iframe
203
+ * Example #3: And I should see "Confirmed" inside the iframe
204
+ * Example #4: Then I should see "Email verified" inside the iframe
205
+ * Example #5: Then I should see "Subscribed" inside the iframe
206
+ *
207
+ */
208
+ Then(/^I should see "([^"]*)" inside the iframe$/, async function (text) {
209
+ const root = frameRoot(this);
210
+ const count = await root.locator(`text="${text}"`).count();
211
+ assert.ok(count > 0, `Expected to see "${text}" inside the iframe.`);
212
+ });
213
+
214
+ /**
215
+ * Assert visible text does NOT exist inside the active iframe.
216
+ *
217
+ * Example #1: Then I should not see "Error" inside the iframe
218
+ * Example #2: Then I should not see "Declined" inside the iframe
219
+ * Example #3: And I should not see "Loading" inside the iframe
220
+ * Example #4: Then I should not see "Pending" inside the iframe
221
+ * Example #5: Then I should not see "Try again" inside the iframe
222
+ *
223
+ */
224
+ Then(/^I should not see "([^"]*)" inside the iframe$/, async function (text) {
225
+ const root = frameRoot(this);
226
+ const count = await root.locator(`text="${text}"`).count();
227
+ assert.strictEqual(count, 0, `Expected NOT to see "${text}" inside the iframe.`);
228
+ });
@@ -0,0 +1,202 @@
1
+ 'use strict';
2
+
3
+ const { friendly } = require('./varbase-e2e');
4
+
5
+ // Pointer input steps not covered by the core element/click steps:
6
+ // hover variants, drag-and-drop, double-click, right-click, viewport size.
7
+
8
+ const { When } = require('@cucumber/cucumber');
9
+
10
+ function explainPtr(action, sel, e) {
11
+ return friendly({
12
+ action: `${action} "${sel}"`,
13
+ cause: e,
14
+ hint: `check the element exists on the page, is visible, and is not covered by an overlay.`,
15
+ });
16
+ }
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Hover (variants)
20
+ // ---------------------------------------------------------------------------
21
+
22
+ /**
23
+ * Hover the mouse pointer over a CSS-addressed element.
24
+ *
25
+ * Example #1: When I hover over "#nav-products"
26
+ * Example #2: When I hover over ".tooltip-trigger"
27
+ * Example #3: And we hover over "[data-testid=user-menu]"
28
+ * Example #4: When I hover over "button.help"
29
+ * Example #5: When I hover over ".chart-bar:nth-child(3)"
30
+ *
31
+ */
32
+ When(/^(I |we )*hover over "([^"]*)"$/, async function (pronoun, sel) {
33
+ try { await this.page.locator(sel).first().hover(); }
34
+ catch (e) { throw explainPtr('hover over', sel, e); }
35
+ });
36
+
37
+ /**
38
+ * Move the mouse pointer over a CSS-addressed element (alias for hover).
39
+ *
40
+ * Example #1: When I move the pointer to "#cta"
41
+ * Example #2: When we move the pointer to ".dropdown-trigger"
42
+ * Example #3: And I move the pointer to "[data-testid=avatar]"
43
+ * Example #4: When I move the pointer to "img.gallery-thumb"
44
+ * Example #5: When I move the pointer to ".legend-item"
45
+ *
46
+ */
47
+ When(/^(I |we )*move the pointer to "([^"]*)"$/, async function (pronoun, sel) {
48
+ try { await this.page.locator(sel).first().hover(); }
49
+ catch (e) { throw explainPtr('move pointer to', sel, e); }
50
+ });
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // Click variants
54
+ // ---------------------------------------------------------------------------
55
+
56
+ /**
57
+ * Double-click on a CSS-addressed element.
58
+ *
59
+ * Example #1: When I double-click on "#row-1"
60
+ * Example #2: When I double click on ".file-tile"
61
+ * Example #3: And we double-click on "[data-testid=cell-A1]"
62
+ * Example #4: When I double-click on "img.editable"
63
+ * Example #5: When I double click on "td.editable-cell"
64
+ *
65
+ */
66
+ When(/^(I |we )*double[- ]click on "([^"]*)"$/, async function (pronoun, sel) {
67
+ try { await this.page.locator(sel).first().dblclick(); }
68
+ catch (e) { throw explainPtr('double-click', sel, e); }
69
+ });
70
+
71
+ /**
72
+ * Right-click (context menu) on a CSS-addressed element.
73
+ *
74
+ * Example #1: When I right-click on "#row-1"
75
+ * Example #2: When I right click on ".tree-node"
76
+ * Example #3: And we right-click on "[data-testid=image]"
77
+ * Example #4: When I right-click on "li.contact"
78
+ * Example #5: When I right click on "td.cell"
79
+ *
80
+ */
81
+ When(/^(I |we )*right[- ]click on "([^"]*)"$/, async function (pronoun, sel) {
82
+ try { await this.page.locator(sel).first().click({ button: 'right' }); }
83
+ catch (e) { throw explainPtr('right-click', sel, e); }
84
+ });
85
+
86
+ /**
87
+ * Middle-click on a CSS-addressed element.
88
+ *
89
+ * Example #1: When I middle-click on "a.external"
90
+ * Example #2: When I middle click on "#tab-2"
91
+ * Example #3: And we middle-click on "[data-testid=link]"
92
+ * Example #4: When I middle-click on "li.bookmark"
93
+ * Example #5: When I middle click on "a.product-link"
94
+ *
95
+ */
96
+ When(/^(I |we )*middle[- ]click on "([^"]*)"$/, async function (pronoun, sel) {
97
+ try { await this.page.locator(sel).first().click({ button: 'middle' }); }
98
+ catch (e) { throw explainPtr('middle-click', sel, e); }
99
+ });
100
+
101
+ /**
102
+ * Click on a CSS-addressed element while holding a modifier key.
103
+ *
104
+ * Modifier names follow Playwright: `Shift`, `Control`, `Alt`, `Meta`.
105
+ *
106
+ * Example #1: When I click on "#row-1" while holding "Shift"
107
+ * Example #2: When I click on ".checkbox" while holding "Control"
108
+ * Example #3: And we click on "a.external" while holding "Meta"
109
+ * Example #4: When I click on ".item" while holding "Alt"
110
+ * Example #5: When I click on "#tab-2" while holding "Shift"
111
+ *
112
+ */
113
+ When(/^(I |we )*click on "([^"]*)" while holding "([^"]*)"$/, async function (pronoun, sel, key) {
114
+ try {
115
+ await this.page.locator(sel).first().click({ modifiers: [key] });
116
+ } catch (e) {
117
+ throw friendly({
118
+ action: `click "${sel}" while holding "${key}"`,
119
+ cause: e,
120
+ hint: `modifier must be one of: Shift, Control, Alt, Meta.`,
121
+ });
122
+ }
123
+ });
124
+
125
+ // ---------------------------------------------------------------------------
126
+ // Drag and drop
127
+ // ---------------------------------------------------------------------------
128
+
129
+ /**
130
+ * Drag a source element onto a target element.
131
+ *
132
+ * Example #1: When I drag "#card-1" to "#column-done"
133
+ * Example #2: When I drag ".file-tile" to "#trash"
134
+ * Example #3: And we drag "[data-testid=task-1]" to "[data-testid=in-progress]"
135
+ * Example #4: When I drag "#image" to "#canvas"
136
+ * Example #5: When I drag "li.draggable" to "ul.dropzone"
137
+ *
138
+ */
139
+ When(/^(I |we )*drag "([^"]*)" to "([^"]*)"$/, async function (pronoun, source, target) {
140
+ try {
141
+ await this.page.locator(source).first().dragTo(this.page.locator(target).first());
142
+ } catch (e) {
143
+ throw friendly({
144
+ action: `drag "${source}" to "${target}"`,
145
+ cause: e,
146
+ hint: `both items must exist on the page; the target must accept drops.`,
147
+ });
148
+ }
149
+ });
150
+
151
+ // ---------------------------------------------------------------------------
152
+ // Viewport
153
+ // ---------------------------------------------------------------------------
154
+
155
+ /**
156
+ * Resize the browser viewport to a specific WxH.
157
+ *
158
+ * Example #1: When I set the viewport size to 1280x720
159
+ * Example #2: When I set the viewport size to 375x667
160
+ * Example #3: And we set the viewport size to 768x1024
161
+ * Example #4: When I set the viewport size to 1920x1080
162
+ * Example #5: When I set the viewport size to 320x568
163
+ *
164
+ */
165
+ When(/^(I |we )*set the viewport size to (\d+)x(\d+)$/, async function (pronoun, w, h) {
166
+ try {
167
+ await this.page.setViewportSize({ width: parseInt(w, 10), height: parseInt(h, 10) });
168
+ } catch (e) {
169
+ throw friendly({
170
+ action: `set viewport size to ${w}x${h}`,
171
+ cause: e,
172
+ hint: `the browser was opened in fluid-viewport mode; set a fixed viewport in playwright.config.ts contextOptions.viewport.`,
173
+ });
174
+ }
175
+ });
176
+
177
+ // ---------------------------------------------------------------------------
178
+ // Tap (touch)
179
+ // ---------------------------------------------------------------------------
180
+
181
+ /**
182
+ * Touch-tap a CSS-addressed element. Falls back to a regular click if the
183
+ * browser context was not opened with `hasTouch: true`.
184
+ *
185
+ * Example #1: When I tap on "#cta"
186
+ * Example #2: When I tap on ".bottom-nav-item"
187
+ * Example #3: And we tap on "[data-testid=fab]"
188
+ * Example #4: When I tap on "button.menu"
189
+ * Example #5: When I tap on ".swipe-card"
190
+ *
191
+ */
192
+ When(/^(I |we )*tap on "([^"]*)"$/, async function (pronoun, sel) {
193
+ try {
194
+ await this.page.locator(sel).first().tap();
195
+ } catch (e) {
196
+ if (/hasTouch/i.test(e.message)) {
197
+ await this.page.locator(sel).first().click();
198
+ } else {
199
+ throw e;
200
+ }
201
+ }
202
+ });
@@ -0,0 +1,232 @@
1
+ 'use strict';
2
+
3
+ // Auto-detect JavaScript errors during test execution.
4
+ //
5
+ // Listeners are attached on Before (order 200) once a page is open:
6
+ // page.on('pageerror') → uncaught errors
7
+ // page.on('console', level) → console messages (filtered by config)
8
+ //
9
+ // After every scenario the collector reports per the active "mode":
10
+ // - 'warn' (default) → log a yellow warning, scenario still passes
11
+ // - 'fail' → assert.fail with the error list
12
+ // - 'off' → silent
13
+ //
14
+ // Mode resolution priority (first match wins):
15
+ // 1. scenario tag @js-fail / @js-warn / @js-off / @javascript / @js-errors
16
+ // 2. env var VARBASE_E2E_JS_ERROR_MODE = warn | fail | off
17
+ // 3. worldParameters parameters.javascript.mode
18
+ // 4. default 'warn'
19
+ //
20
+ // Back-compat: @javascript → fail (legacy "always fail" tag). @js-errors → off.
21
+
22
+ const { Before, After, Then } = require('@cucumber/cucumber');
23
+ const assert = require('assert');
24
+
25
+ const VALID_MODES = new Set(['warn', 'fail', 'off']);
26
+ const DEFAULT_CONSOLE_LEVELS = ['error'];
27
+
28
+ /**
29
+ * Resolve effective settings for the current scenario.
30
+ *
31
+ * @returns {{ mode: 'warn'|'fail'|'off', levels: string[], ignore: RegExp|null,
32
+ * beforeScenario: boolean, afterScenario: boolean }}
33
+ */
34
+ function resolveSettings(world, scope) {
35
+ const cfg = (world.parameters && world.parameters.javascript) || {};
36
+ const tag = (name) => hasTag(scope, name);
37
+
38
+ let mode;
39
+ if (tag('@js-fail') || tag('@javascript')) mode = 'fail';
40
+ else if (tag('@js-warn')) mode = 'warn';
41
+ else if (tag('@js-off') || tag('@js-errors')) mode = 'off';
42
+ else if (process.env.VARBASE_E2E_JS_ERROR_MODE) mode = process.env.VARBASE_E2E_JS_ERROR_MODE;
43
+ else if (cfg.mode) mode = cfg.mode;
44
+ else mode = 'warn';
45
+ if (!VALID_MODES.has(mode)) mode = 'warn';
46
+
47
+ const levelsRaw =
48
+ process.env.VARBASE_E2E_JS_ERROR_LEVELS ||
49
+ (Array.isArray(cfg.levels) ? cfg.levels.join(',') : cfg.levels) ||
50
+ DEFAULT_CONSOLE_LEVELS.join(',');
51
+ const levels = String(levelsRaw).split(',').map((s) => s.trim()).filter(Boolean);
52
+
53
+ const ignoreSrc = process.env.VARBASE_E2E_JS_ERROR_IGNORE || cfg.ignore || null;
54
+ let ignore = null;
55
+ if (ignoreSrc) {
56
+ try { ignore = new RegExp(ignoreSrc); }
57
+ catch { ignore = null; }
58
+ }
59
+
60
+ const beforeScenario =
61
+ process.env.VARBASE_E2E_JS_ERROR_BEFORE === '1'
62
+ ? true
63
+ : cfg.beforeScenario === true;
64
+ const afterScenario =
65
+ process.env.VARBASE_E2E_JS_ERROR_AFTER === '0'
66
+ ? false
67
+ : cfg.afterScenario !== false; // default true
68
+
69
+ return { mode, levels, ignore, beforeScenario, afterScenario };
70
+ }
71
+
72
+ function hasTag(scope, name) {
73
+ if (!scope || !Array.isArray(scope.pickle && scope.pickle.tags)) return false;
74
+ return scope.pickle.tags.some((t) => t.name === name);
75
+ }
76
+
77
+ function formatErrors(errs) {
78
+ return errs.map((e, i) => ` ${i + 1}. [${e.type}] ${e.message}`).join('\n');
79
+ }
80
+
81
+ function filterErrors(errs, ignore) {
82
+ if (!ignore) return errs;
83
+ return errs.filter((e) => !ignore.test(e.message));
84
+ }
85
+
86
+ function reportWarning(scope, errs) {
87
+ const name = (scope && scope.pickle && scope.pickle.name) || 'scenario';
88
+ // Yellow ANSI when FORCE_COLOR is on, plain otherwise — let the runtime
89
+ // decide. cucumber-js sets FORCE_COLOR=1 by default in interactive shells.
90
+ const head = `[varbase-e2e] JavaScript errors during "${name}" (mode=warn):`;
91
+ process.stderr.write(`\n\x1b[33m${head}\n${formatErrors(errs)}\x1b[0m\n`);
92
+ }
93
+
94
+ // ---------------------------------------------------------------------------
95
+ // Before — attach listeners + (optional) pre-snapshot.
96
+ // ---------------------------------------------------------------------------
97
+
98
+ Before({ order: 200 }, function (scope) {
99
+ this._jsErrors = [];
100
+ this._jsErrorsAsserted = false;
101
+ this._jsSettings = resolveSettings(this, scope);
102
+ if (this._jsSettings.mode === 'off') return;
103
+ if (!this.page) return;
104
+
105
+ const levels = new Set(this._jsSettings.levels);
106
+ this.page.on('pageerror', (err) => {
107
+ this._jsErrors.push({ type: 'pageerror', message: String(err && err.message || err) });
108
+ });
109
+ this.page.on('console', (msg) => {
110
+ if (levels.has(msg.type())) {
111
+ this._jsErrors.push({ type: `console.${msg.type()}`, message: msg.text() });
112
+ }
113
+ });
114
+
115
+ if (this._jsSettings.beforeScenario) {
116
+ // Pre-scenario snapshot — typically empty unless the same page is reused.
117
+ const errs = filterErrors(this._jsErrors, this._jsSettings.ignore);
118
+ if (errs.length > 0) {
119
+ process.stderr.write(
120
+ `\n\x1b[33m[varbase-e2e] Pre-scenario JavaScript errors:\n${formatErrors(errs)}\x1b[0m\n`
121
+ );
122
+ }
123
+ }
124
+ });
125
+
126
+ // ---------------------------------------------------------------------------
127
+ // Steps
128
+ // ---------------------------------------------------------------------------
129
+
130
+ /**
131
+ * Assert that no JavaScript errors have been collected so far.
132
+ *
133
+ * Always fails on error (ignoring the active mode) — this is the explicit
134
+ * "hard check" step. Calling it also prevents the auto-report at scenario
135
+ * end so a single error is not reported twice.
136
+ *
137
+ * Example #1: Then there should be no JavaScript errors
138
+ * Example #2: And there should be no JavaScript errors
139
+ * Example #3: But there should be no JavaScript errors
140
+ * Example #4: Then there should be no JavaScript errors
141
+ * Example #5: Then there should be no JavaScript errors
142
+ *
143
+ */
144
+ Then(/^there should be no JavaScript errors$/, function () {
145
+ this._jsErrorsAsserted = true;
146
+ const errs = filterErrors(this._jsErrors || [], this._jsSettings && this._jsSettings.ignore);
147
+ if (errs.length === 0) return;
148
+ assert.fail(`JavaScript errors detected:\n${formatErrors(errs)}`);
149
+ });
150
+
151
+ /**
152
+ * Assert that no JavaScript console warnings were collected. Requires
153
+ * 'warning' to be in the captured console levels (see worldParameters or
154
+ * VARBASE_E2E_JS_ERROR_LEVELS).
155
+ *
156
+ * Example #1: Then there should be no JavaScript warnings
157
+ * Example #2: And there should be no JavaScript warnings
158
+ * Example #3: But there should be no JavaScript warnings
159
+ * Example #4: Then there should be no JavaScript warnings
160
+ * Example #5: Then there should be no JavaScript warnings
161
+ *
162
+ */
163
+ Then(/^there should be no JavaScript warnings$/, function () {
164
+ this._jsErrorsAsserted = true;
165
+ const errs = (this._jsErrors || []).filter((e) => e.type === 'console.warning');
166
+ if (errs.length === 0) return;
167
+ assert.fail(`JavaScript warnings detected:\n${formatErrors(errs)}`);
168
+ });
169
+
170
+ /**
171
+ * Assert that no collected JavaScript error message matches a regular
172
+ * expression. Useful when some noise is unavoidable but a specific bug
173
+ * pattern must never appear.
174
+ *
175
+ * Example #1: Then JavaScript errors should not match "TypeError"
176
+ * Example #2: Then JavaScript errors should not match "is not a function"
177
+ * Example #3: And JavaScript errors should not match "Cannot read property"
178
+ * Example #4: Then JavaScript errors should not match "ReferenceError: .* is not defined"
179
+ * Example #5: Then JavaScript errors should not match "Uncaught"
180
+ *
181
+ */
182
+ Then(/^JavaScript errors should not match "([^"]*)"$/, function (pattern) {
183
+ let re;
184
+ try { re = new RegExp(pattern); }
185
+ catch (e) {
186
+ assert.fail(`Invalid regular expression: "${pattern}" — ${e.message}`);
187
+ }
188
+ const errs = (this._jsErrors || []).filter((e) => re.test(e.message));
189
+ if (errs.length === 0) return;
190
+ assert.fail(`JavaScript errors matched /${pattern}/:\n${formatErrors(errs)}`);
191
+ });
192
+
193
+ /**
194
+ * Print the JavaScript errors collected so far. Diagnostic only — never
195
+ * asserts, never fails. Useful inside a scenario being debugged.
196
+ *
197
+ * Example #1: Then print JavaScript errors
198
+ * Example #2: And print JavaScript errors
199
+ * Example #3: But print JavaScript errors
200
+ * Example #4: Then print JavaScript errors
201
+ * Example #5: Then print JavaScript errors
202
+ *
203
+ */
204
+ Then(/^print JavaScript errors$/, function () {
205
+ const errs = this._jsErrors || [];
206
+ if (errs.length === 0) {
207
+ console.log('\n--- JavaScript errors: none ---');
208
+ return;
209
+ }
210
+ console.log('\n--- JavaScript errors ---');
211
+ console.log(formatErrors(errs));
212
+ });
213
+
214
+ // ---------------------------------------------------------------------------
215
+ // After — report per mode.
216
+ // ---------------------------------------------------------------------------
217
+
218
+ After({ order: 200 }, function (scope) {
219
+ const s = this._jsSettings || resolveSettings(this, scope);
220
+ if (s.mode === 'off') return;
221
+ if (!s.afterScenario) return;
222
+ if (this._jsErrorsAsserted) return; // already handled by explicit step
223
+
224
+ const errs = filterErrors(this._jsErrors || [], s.ignore);
225
+ if (errs.length === 0) return;
226
+
227
+ if (s.mode === 'fail') {
228
+ assert.fail(`JavaScript errors detected during scenario:\n${formatErrors(errs)}`);
229
+ } else {
230
+ reportWarning(scope, errs);
231
+ }
232
+ });