@sun-asterisk/sungen 3.2.28 → 3.2.30

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 (486) hide show
  1. package/dist/capabilities/discover.d.ts +29 -0
  2. package/dist/capabilities/discover.d.ts.map +1 -1
  3. package/dist/capabilities/discover.js +144 -10
  4. package/dist/capabilities/discover.js.map +1 -1
  5. package/dist/capabilities/registry.d.ts +18 -0
  6. package/dist/capabilities/registry.d.ts.map +1 -1
  7. package/dist/capabilities/registry.js +25 -0
  8. package/dist/capabilities/registry.js.map +1 -1
  9. package/dist/cli/commands/audit.d.ts.map +1 -1
  10. package/dist/cli/commands/audit.js +9 -0
  11. package/dist/cli/commands/audit.js.map +1 -1
  12. package/dist/cli/commands/capability.js +1 -1
  13. package/dist/cli/commands/capability.js.map +1 -1
  14. package/dist/cli/commands/delivery.d.ts.map +1 -1
  15. package/dist/cli/commands/delivery.js +2 -1
  16. package/dist/cli/commands/delivery.js.map +1 -1
  17. package/dist/cli/commands/generate.d.ts.map +1 -1
  18. package/dist/cli/commands/generate.js +6 -2
  19. package/dist/cli/commands/generate.js.map +1 -1
  20. package/dist/cli/commands/inspect.js +1 -1
  21. package/dist/cli/commands/inspect.js.map +1 -1
  22. package/dist/cli/commands/results-merge.d.ts.map +1 -1
  23. package/dist/cli/commands/results-merge.js +9 -2
  24. package/dist/cli/commands/results-merge.js.map +1 -1
  25. package/dist/cli/commands/results-sanitize.d.ts +24 -0
  26. package/dist/cli/commands/results-sanitize.d.ts.map +1 -0
  27. package/dist/cli/commands/results-sanitize.js +126 -0
  28. package/dist/cli/commands/results-sanitize.js.map +1 -0
  29. package/dist/cli/commands/script-check.d.ts.map +1 -1
  30. package/dist/cli/commands/script-check.js +3 -1
  31. package/dist/cli/commands/script-check.js.map +1 -1
  32. package/dist/cli/commands/update.d.ts.map +1 -1
  33. package/dist/cli/commands/update.js +3 -0
  34. package/dist/cli/commands/update.js.map +1 -1
  35. package/dist/dashboard/templates/index.html +1 -1
  36. package/dist/exporters/api-testcase-formatter.d.ts +3 -6
  37. package/dist/exporters/api-testcase-formatter.d.ts.map +1 -1
  38. package/dist/exporters/api-testcase-formatter.js +7 -10
  39. package/dist/exporters/api-testcase-formatter.js.map +1 -1
  40. package/dist/exporters/feature-parser.d.ts +1 -1
  41. package/dist/exporters/feature-parser.js +1 -1
  42. package/dist/exporters/matrix/build.d.ts.map +1 -1
  43. package/dist/exporters/matrix/build.js +18 -3
  44. package/dist/exporters/matrix/build.js.map +1 -1
  45. package/dist/exporters/matrix/item-labels.d.ts +35 -0
  46. package/dist/exporters/matrix/item-labels.d.ts.map +1 -0
  47. package/dist/exporters/matrix/item-labels.js +90 -0
  48. package/dist/exporters/matrix/item-labels.js.map +1 -0
  49. package/dist/exporters/matrix/render-coverage-sheet.d.ts +14 -0
  50. package/dist/exporters/matrix/render-coverage-sheet.d.ts.map +1 -0
  51. package/dist/exporters/matrix/render-coverage-sheet.js +162 -0
  52. package/dist/exporters/matrix/render-coverage-sheet.js.map +1 -0
  53. package/dist/exporters/matrix/render-csv.d.ts.map +1 -1
  54. package/dist/exporters/matrix/render-csv.js +3 -1
  55. package/dist/exporters/matrix/render-csv.js.map +1 -1
  56. package/dist/exporters/matrix/render-overview-sheet.d.ts +20 -0
  57. package/dist/exporters/matrix/render-overview-sheet.d.ts.map +1 -0
  58. package/dist/exporters/matrix/render-overview-sheet.js +32 -0
  59. package/dist/exporters/matrix/render-overview-sheet.js.map +1 -0
  60. package/dist/exporters/matrix/render-testcase-sheet.d.ts +30 -0
  61. package/dist/exporters/matrix/render-testcase-sheet.d.ts.map +1 -0
  62. package/dist/exporters/matrix/render-testcase-sheet.js +156 -0
  63. package/dist/exporters/matrix/render-testcase-sheet.js.map +1 -0
  64. package/dist/exporters/matrix/render-xlsx.d.ts +41 -31
  65. package/dist/exporters/matrix/render-xlsx.d.ts.map +1 -1
  66. package/dist/exporters/matrix/render-xlsx.js +72 -425
  67. package/dist/exporters/matrix/render-xlsx.js.map +1 -1
  68. package/dist/exporters/matrix/result-totals.d.ts +23 -0
  69. package/dist/exporters/matrix/result-totals.d.ts.map +1 -0
  70. package/dist/exporters/matrix/result-totals.js +21 -0
  71. package/dist/exporters/matrix/result-totals.js.map +1 -0
  72. package/dist/exporters/matrix/testcase-summary-band.d.ts +30 -0
  73. package/dist/exporters/matrix/testcase-summary-band.d.ts.map +1 -0
  74. package/dist/exporters/matrix/testcase-summary-band.js +71 -0
  75. package/dist/exporters/matrix/testcase-summary-band.js.map +1 -0
  76. package/dist/exporters/matrix/wording.d.ts.map +1 -1
  77. package/dist/exporters/matrix/wording.js +13 -0
  78. package/dist/exporters/matrix/wording.js.map +1 -1
  79. package/dist/exporters/matrix/xlsx-cells.d.ts +38 -0
  80. package/dist/exporters/matrix/xlsx-cells.d.ts.map +1 -0
  81. package/dist/exporters/matrix/xlsx-cells.js +52 -0
  82. package/dist/exporters/matrix/xlsx-cells.js.map +1 -0
  83. package/dist/exporters/qa-env-secret-denylist.js +2 -2
  84. package/dist/exporters/qa-env-secret-denylist.js.map +1 -1
  85. package/dist/exporters/report-path-sanitizer.d.ts +38 -0
  86. package/dist/exporters/report-path-sanitizer.d.ts.map +1 -0
  87. package/dist/exporters/report-path-sanitizer.js +137 -0
  88. package/dist/exporters/report-path-sanitizer.js.map +1 -0
  89. package/dist/exporters/result-variants.d.ts.map +1 -1
  90. package/dist/exporters/result-variants.js +2 -1
  91. package/dist/exporters/result-variants.js.map +1 -1
  92. package/dist/exporters/sensitive-key.d.ts +31 -0
  93. package/dist/exporters/sensitive-key.d.ts.map +1 -0
  94. package/dist/exporters/sensitive-key.js +50 -0
  95. package/dist/exporters/sensitive-key.js.map +1 -0
  96. package/dist/exporters/test-data-resolver.d.ts +4 -1
  97. package/dist/exporters/test-data-resolver.d.ts.map +1 -1
  98. package/dist/exporters/test-data-resolver.js +12 -2
  99. package/dist/exporters/test-data-resolver.js.map +1 -1
  100. package/dist/exporters/xlsx-report-builder.d.ts +12 -1
  101. package/dist/exporters/xlsx-report-builder.d.ts.map +1 -1
  102. package/dist/exporters/xlsx-report-builder.js +10 -2
  103. package/dist/exporters/xlsx-report-builder.js.map +1 -1
  104. package/dist/generators/gherkin-parser/index.d.ts +10 -0
  105. package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
  106. package/dist/generators/gherkin-parser/index.js +20 -12
  107. package/dist/generators/gherkin-parser/index.js.map +1 -1
  108. package/dist/generators/gherkin-parser/parent-scope-clause.d.ts +54 -0
  109. package/dist/generators/gherkin-parser/parent-scope-clause.d.ts.map +1 -0
  110. package/dist/generators/gherkin-parser/parent-scope-clause.js +109 -0
  111. package/dist/generators/gherkin-parser/parent-scope-clause.js.map +1 -0
  112. package/dist/generators/test-generator/adapters/adapter-interface.d.ts +23 -18
  113. package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
  114. package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts +4 -19
  115. package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts.map +1 -1
  116. package/dist/generators/test-generator/adapters/appium/appium-adapter.js.map +1 -1
  117. package/dist/generators/test-generator/adapters/appium/os-support.d.ts +53 -0
  118. package/dist/generators/test-generator/adapters/appium/os-support.d.ts.map +1 -0
  119. package/dist/generators/test-generator/adapters/appium/os-support.js +200 -0
  120. package/dist/generators/test-generator/adapters/appium/os-support.js.map +1 -0
  121. package/dist/generators/test-generator/adapters/appium/templates/after-all.hbs +18 -0
  122. package/dist/generators/test-generator/adapters/appium/templates/after-each.hbs +18 -0
  123. package/dist/generators/test-generator/adapters/appium/templates/before-all.hbs +18 -0
  124. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/alert-fill-action.hbs +9 -2
  125. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/capture-variable.hbs +24 -1
  126. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/click-at-position-action.hbs +1 -0
  127. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/click-element-with-text.hbs +1 -1
  128. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/double-click-action.hbs +7 -0
  129. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/frame-enter-action.hbs +3 -6
  130. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/hover-action.hbs +6 -2
  131. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/hover-element-with-text.hbs +4 -3
  132. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/table-paginate-action.hbs +3 -0
  133. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/unknown-element-action.hbs +9 -0
  134. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/have-value-assertion.hbs +34 -1
  135. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +14 -6
  136. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-assertion.hbs +1 -1
  137. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/go-back-action.hbs +21 -0
  138. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/long-press-action.hbs +4 -0
  139. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/pinch-zoom-action.hbs +4 -0
  140. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/set-geolocation-action.hbs +16 -0
  141. package/dist/generators/test-generator/adapters/appium/templates/steps/partials/locator.hbs +9 -1
  142. package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +62 -23
  143. package/dist/generators/test-generator/adapters/index.d.ts +1 -1
  144. package/dist/generators/test-generator/adapters/index.d.ts.map +1 -1
  145. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +4 -19
  146. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
  147. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
  148. package/dist/generators/test-generator/adapters/playwright/templates/after-all.hbs +19 -1
  149. package/dist/generators/test-generator/adapters/playwright/templates/after-each.hbs +18 -0
  150. package/dist/generators/test-generator/adapters/playwright/templates/before-all.hbs +19 -1
  151. package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  152. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/check-action.hbs +6 -1
  153. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/click-at-position-action.hbs +23 -0
  154. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/expand-action.hbs +8 -8
  155. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/fill-editor-action.hbs +7 -2
  156. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row-nth.hbs +7 -2
  157. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +7 -2
  158. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-paginate-action.hbs +5 -0
  159. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/uncheck-action.hbs +3 -1
  160. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/unknown-element-action.hbs +1 -1
  161. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/wait-for-page.hbs +18 -1
  162. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-assertion.hbs +32 -4
  163. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-element.hbs +5 -2
  164. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/checked-assertion.hbs +5 -1
  165. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/column-cell-assertion.hbs +13 -2
  166. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/contain-text-assertion.hbs +1 -1
  167. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +2 -2
  168. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-variable-assertion.hbs +1 -1
  169. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/have-text-assertion.hbs +1 -1
  170. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +2 -2
  171. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-variable-assertion.hbs +1 -1
  172. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/not-checked-assertion.hbs +2 -1
  173. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/selected-assertion.hbs +21 -3
  174. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/state-with-filter-assertion.hbs +20 -2
  175. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-empty.hbs +4 -1
  176. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-match-data.hbs +4 -1
  177. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-count.hbs +4 -1
  178. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-exists.hbs +12 -4
  179. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-not-exists.hbs +14 -2
  180. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +3 -3
  181. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-value-assertion.hbs +1 -1
  182. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-variable-assertion.hbs +1 -1
  183. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/checked-opts.hbs +8 -0
  184. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +8 -8
  185. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-nth.hbs +1 -1
  186. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/id.hbs +1 -1
  187. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/testid.hbs +1 -1
  188. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +8 -8
  189. package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +10 -8
  190. package/dist/generators/test-generator/code-generator.d.ts +37 -0
  191. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  192. package/dist/generators/test-generator/code-generator.js +165 -40
  193. package/dist/generators/test-generator/code-generator.js.map +1 -1
  194. package/dist/generators/test-generator/diagnostics.d.ts +116 -6
  195. package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
  196. package/dist/generators/test-generator/diagnostics.js +115 -5
  197. package/dist/generators/test-generator/diagnostics.js.map +1 -1
  198. package/dist/generators/test-generator/patterns/index.d.ts +57 -11
  199. package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
  200. package/dist/generators/test-generator/patterns/index.js +163 -21
  201. package/dist/generators/test-generator/patterns/index.js.map +1 -1
  202. package/dist/generators/test-generator/patterns/types.d.ts +8 -0
  203. package/dist/generators/test-generator/patterns/types.d.ts.map +1 -1
  204. package/dist/generators/test-generator/patterns/with-parent-scope.d.ts +18 -0
  205. package/dist/generators/test-generator/patterns/with-parent-scope.d.ts.map +1 -0
  206. package/dist/generators/test-generator/patterns/with-parent-scope.js +40 -0
  207. package/dist/generators/test-generator/patterns/with-parent-scope.js.map +1 -0
  208. package/dist/generators/test-generator/step-mapper.d.ts +103 -10
  209. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  210. package/dist/generators/test-generator/step-mapper.js +266 -45
  211. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  212. package/dist/generators/test-generator/template-engine.d.ts +4 -19
  213. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  214. package/dist/generators/test-generator/template-engine.js +17 -12
  215. package/dist/generators/test-generator/template-engine.js.map +1 -1
  216. package/dist/generators/test-generator/utils/data-resolver.d.ts +7 -0
  217. package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
  218. package/dist/generators/test-generator/utils/data-resolver.js +19 -2
  219. package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
  220. package/dist/generators/test-generator/utils/dialog-scope-identity.d.ts +41 -0
  221. package/dist/generators/test-generator/utils/dialog-scope-identity.d.ts.map +1 -0
  222. package/dist/generators/test-generator/utils/dialog-scope-identity.js +61 -0
  223. package/dist/generators/test-generator/utils/dialog-scope-identity.js.map +1 -0
  224. package/dist/generators/test-generator/utils/escape-js-string.d.ts +17 -0
  225. package/dist/generators/test-generator/utils/escape-js-string.d.ts.map +1 -0
  226. package/dist/generators/test-generator/utils/escape-js-string.js +29 -0
  227. package/dist/generators/test-generator/utils/escape-js-string.js.map +1 -0
  228. package/dist/generators/test-generator/utils/nearest-match.d.ts +24 -0
  229. package/dist/generators/test-generator/utils/nearest-match.d.ts.map +1 -0
  230. package/dist/generators/test-generator/utils/nearest-match.js +56 -0
  231. package/dist/generators/test-generator/utils/nearest-match.js.map +1 -0
  232. package/dist/generators/test-generator/utils/selector-entry-fields.d.ts +58 -0
  233. package/dist/generators/test-generator/utils/selector-entry-fields.d.ts.map +1 -0
  234. package/dist/generators/test-generator/utils/selector-entry-fields.js +119 -0
  235. package/dist/generators/test-generator/utils/selector-entry-fields.js.map +1 -0
  236. package/dist/generators/test-generator/utils/selector-entry-walk.d.ts +25 -0
  237. package/dist/generators/test-generator/utils/selector-entry-walk.d.ts.map +1 -0
  238. package/dist/generators/test-generator/utils/selector-entry-walk.js +54 -0
  239. package/dist/generators/test-generator/utils/selector-entry-walk.js.map +1 -0
  240. package/dist/generators/test-generator/utils/selector-nth-validation.d.ts +59 -0
  241. package/dist/generators/test-generator/utils/selector-nth-validation.d.ts.map +1 -0
  242. package/dist/generators/test-generator/utils/selector-nth-validation.js +91 -0
  243. package/dist/generators/test-generator/utils/selector-nth-validation.js.map +1 -0
  244. package/dist/generators/test-generator/utils/selector-resolver.d.ts +53 -2
  245. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
  246. package/dist/generators/test-generator/utils/selector-resolver.js +273 -27
  247. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
  248. package/dist/generators/test-generator/utils/selector-type-validation.d.ts +51 -0
  249. package/dist/generators/test-generator/utils/selector-type-validation.d.ts.map +1 -0
  250. package/dist/generators/test-generator/utils/selector-type-validation.js +77 -0
  251. package/dist/generators/test-generator/utils/selector-type-validation.js.map +1 -0
  252. package/dist/harness/audit.d.ts +3 -1
  253. package/dist/harness/audit.d.ts.map +1 -1
  254. package/dist/harness/audit.js +51 -28
  255. package/dist/harness/audit.js.map +1 -1
  256. package/dist/harness/capability-plan.d.ts +4 -0
  257. package/dist/harness/capability-plan.d.ts.map +1 -1
  258. package/dist/harness/capability-plan.js +32 -2
  259. package/dist/harness/capability-plan.js.map +1 -1
  260. package/dist/harness/catalog/drivers.yaml +1 -1
  261. package/dist/harness/catalog/universal-viewpoints.yaml +145 -0
  262. package/dist/harness/data-driven-lint.d.ts.map +1 -1
  263. package/dist/harness/data-driven-lint.js +217 -33
  264. package/dist/harness/data-driven-lint.js.map +1 -1
  265. package/dist/harness/parse.d.ts +6 -0
  266. package/dist/harness/parse.d.ts.map +1 -1
  267. package/dist/harness/parse.js +1 -1
  268. package/dist/harness/parse.js.map +1 -1
  269. package/dist/harness/repair.d.ts.map +1 -1
  270. package/dist/harness/repair.js +8 -1
  271. package/dist/harness/repair.js.map +1 -1
  272. package/dist/harness/script-check.d.ts +2 -2
  273. package/dist/harness/script-check.d.ts.map +1 -1
  274. package/dist/harness/script-check.js +186 -25
  275. package/dist/harness/script-check.js.map +1 -1
  276. package/dist/harness/sensors.d.ts +48 -0
  277. package/dist/harness/sensors.d.ts.map +1 -1
  278. package/dist/harness/sensors.js +26 -1
  279. package/dist/harness/sensors.js.map +1 -1
  280. package/dist/harness/viewpoint-ledger.d.ts +10 -0
  281. package/dist/harness/viewpoint-ledger.d.ts.map +1 -1
  282. package/dist/harness/viewpoint-ledger.js +23 -2
  283. package/dist/harness/viewpoint-ledger.js.map +1 -1
  284. package/dist/index.d.ts +2 -0
  285. package/dist/index.d.ts.map +1 -1
  286. package/dist/index.js +10 -1
  287. package/dist/index.js.map +1 -1
  288. package/dist/orchestrator/project-initializer.d.ts +6 -0
  289. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  290. package/dist/orchestrator/project-initializer.js +16 -0
  291. package/dist/orchestrator/project-initializer.js.map +1 -1
  292. package/dist/orchestrator/report-sanitize-reporter-install.d.ts +24 -0
  293. package/dist/orchestrator/report-sanitize-reporter-install.d.ts.map +1 -0
  294. package/dist/orchestrator/report-sanitize-reporter-install.js +161 -0
  295. package/dist/orchestrator/report-sanitize-reporter-install.js.map +1 -0
  296. package/dist/orchestrator/templates/ai-src/commands/delivery.md +4 -3
  297. package/dist/orchestrator/templates/ai-src/commands/run-test.md +9 -1
  298. package/dist/orchestrator/templates/ai-src/skills/sungen-api-coverage-model/SKILL.md +10 -0
  299. package/dist/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +14 -5
  300. package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +62 -3
  301. package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +126 -11
  302. package/dist/orchestrator/templates/ai-src/skills/sungen-mobile-gestures/SKILL.md +16 -1
  303. package/dist/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +12 -0
  304. package/dist/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +19 -5
  305. package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +88 -2
  306. package/dist/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +62 -5
  307. package/dist/orchestrator/templates/ai-src/skills/sungen-test-design-techniques/SKILL.md +13 -0
  308. package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/SKILL.md +59 -10
  309. package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/group-f-mobile-native.md +419 -0
  310. package/dist/orchestrator/templates/playwright.config.ts +7 -0
  311. package/dist/orchestrator/templates/readme.md +1 -1
  312. package/dist/orchestrator/templates/specs-checked-state.d.ts +83 -0
  313. package/dist/orchestrator/templates/specs-checked-state.d.ts.map +1 -0
  314. package/dist/orchestrator/templates/specs-checked-state.js +261 -0
  315. package/dist/orchestrator/templates/specs-checked-state.js.map +1 -0
  316. package/dist/orchestrator/templates/specs-checked-state.ts +324 -0
  317. package/dist/orchestrator/templates/specs-pw-shape-reporter.ts +0 -0
  318. package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.d.ts +36 -0
  319. package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.d.ts.map +1 -0
  320. package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.js +155 -0
  321. package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.js.map +1 -0
  322. package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.ts +142 -0
  323. package/dist/orchestrator/templates/specs-url-assert.d.ts +50 -25
  324. package/dist/orchestrator/templates/specs-url-assert.d.ts.map +1 -1
  325. package/dist/orchestrator/templates/specs-url-assert.js +65 -24
  326. package/dist/orchestrator/templates/specs-url-assert.js.map +1 -1
  327. package/dist/orchestrator/templates/specs-url-assert.ts +101 -28
  328. package/dist/orchestrator/templates/wdio.conf.ts +105 -1
  329. package/dist/utils/selector-types.d.ts +43 -1
  330. package/dist/utils/selector-types.d.ts.map +1 -1
  331. package/dist/utils/selector-types.js +51 -7
  332. package/dist/utils/selector-types.js.map +1 -1
  333. package/package.json +3 -3
  334. package/src/capabilities/discover.ts +146 -10
  335. package/src/capabilities/registry.ts +30 -0
  336. package/src/cli/commands/audit.ts +9 -0
  337. package/src/cli/commands/capability.ts +1 -1
  338. package/src/cli/commands/delivery.ts +2 -1
  339. package/src/cli/commands/generate.ts +5 -2
  340. package/src/cli/commands/inspect.ts +1 -1
  341. package/src/cli/commands/results-merge.ts +9 -2
  342. package/src/cli/commands/results-sanitize.ts +98 -0
  343. package/src/cli/commands/script-check.ts +3 -1
  344. package/src/cli/commands/update.ts +3 -0
  345. package/src/dashboard/templates/index.html +1 -1
  346. package/src/exporters/api-testcase-formatter.ts +5 -7
  347. package/src/exporters/feature-parser.ts +1 -1
  348. package/src/exporters/matrix/build.ts +17 -3
  349. package/src/exporters/matrix/item-labels.ts +78 -0
  350. package/src/exporters/matrix/render-coverage-sheet.ts +170 -0
  351. package/src/exporters/matrix/render-csv.ts +3 -1
  352. package/src/exporters/matrix/render-overview-sheet.ts +51 -0
  353. package/src/exporters/matrix/render-testcase-sheet.ts +178 -0
  354. package/src/exporters/matrix/render-xlsx.ts +67 -448
  355. package/src/exporters/matrix/result-totals.ts +37 -0
  356. package/src/exporters/matrix/testcase-summary-band.ts +74 -0
  357. package/src/exporters/matrix/wording.ts +13 -0
  358. package/src/exporters/matrix/xlsx-cells.ts +56 -0
  359. package/src/exporters/qa-env-secret-denylist.ts +1 -1
  360. package/src/exporters/report-path-sanitizer.ts +144 -0
  361. package/src/exporters/result-variants.ts +2 -1
  362. package/src/exporters/sensitive-key.ts +48 -0
  363. package/src/exporters/test-data-resolver.ts +12 -2
  364. package/src/exporters/xlsx-report-builder.ts +16 -2
  365. package/src/generators/gherkin-parser/index.ts +22 -11
  366. package/src/generators/gherkin-parser/parent-scope-clause.ts +130 -0
  367. package/src/generators/test-generator/adapters/adapter-interface.ts +25 -3
  368. package/src/generators/test-generator/adapters/appium/appium-adapter.ts +4 -4
  369. package/src/generators/test-generator/adapters/appium/os-support.ts +176 -0
  370. package/src/generators/test-generator/adapters/appium/templates/after-all.hbs +18 -0
  371. package/src/generators/test-generator/adapters/appium/templates/after-each.hbs +18 -0
  372. package/src/generators/test-generator/adapters/appium/templates/before-all.hbs +18 -0
  373. package/src/generators/test-generator/adapters/appium/templates/steps/actions/alert-fill-action.hbs +9 -2
  374. package/src/generators/test-generator/adapters/appium/templates/steps/actions/capture-variable.hbs +24 -1
  375. package/src/generators/test-generator/adapters/appium/templates/steps/actions/click-at-position-action.hbs +1 -0
  376. package/src/generators/test-generator/adapters/appium/templates/steps/actions/click-element-with-text.hbs +1 -1
  377. package/src/generators/test-generator/adapters/appium/templates/steps/actions/double-click-action.hbs +7 -0
  378. package/src/generators/test-generator/adapters/appium/templates/steps/actions/frame-enter-action.hbs +3 -6
  379. package/src/generators/test-generator/adapters/appium/templates/steps/actions/hover-action.hbs +6 -2
  380. package/src/generators/test-generator/adapters/appium/templates/steps/actions/hover-element-with-text.hbs +4 -3
  381. package/src/generators/test-generator/adapters/appium/templates/steps/actions/table-paginate-action.hbs +3 -0
  382. package/src/generators/test-generator/adapters/appium/templates/steps/actions/unknown-element-action.hbs +9 -0
  383. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/have-value-assertion.hbs +34 -1
  384. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +14 -6
  385. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-assertion.hbs +1 -1
  386. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/go-back-action.hbs +21 -0
  387. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/long-press-action.hbs +4 -0
  388. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/pinch-zoom-action.hbs +4 -0
  389. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/set-geolocation-action.hbs +16 -0
  390. package/src/generators/test-generator/adapters/appium/templates/steps/partials/locator.hbs +9 -1
  391. package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +62 -23
  392. package/src/generators/test-generator/adapters/index.ts +1 -1
  393. package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +4 -4
  394. package/src/generators/test-generator/adapters/playwright/templates/after-all.hbs +19 -1
  395. package/src/generators/test-generator/adapters/playwright/templates/after-each.hbs +18 -0
  396. package/src/generators/test-generator/adapters/playwright/templates/before-all.hbs +19 -1
  397. package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  398. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/check-action.hbs +6 -1
  399. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/click-at-position-action.hbs +23 -0
  400. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/expand-action.hbs +8 -8
  401. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/fill-editor-action.hbs +7 -2
  402. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row-nth.hbs +7 -2
  403. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +7 -2
  404. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-paginate-action.hbs +5 -0
  405. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/uncheck-action.hbs +3 -1
  406. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/unknown-element-action.hbs +1 -1
  407. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/wait-for-page.hbs +18 -1
  408. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-assertion.hbs +32 -4
  409. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-element.hbs +5 -2
  410. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/checked-assertion.hbs +5 -1
  411. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/column-cell-assertion.hbs +13 -2
  412. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/contain-text-assertion.hbs +1 -1
  413. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +2 -2
  414. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-variable-assertion.hbs +1 -1
  415. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/have-text-assertion.hbs +1 -1
  416. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +2 -2
  417. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-variable-assertion.hbs +1 -1
  418. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/not-checked-assertion.hbs +2 -1
  419. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/selected-assertion.hbs +21 -3
  420. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/state-with-filter-assertion.hbs +20 -2
  421. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-empty.hbs +4 -1
  422. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-match-data.hbs +4 -1
  423. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-count.hbs +4 -1
  424. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-exists.hbs +12 -4
  425. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-not-exists.hbs +14 -2
  426. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +3 -3
  427. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-value-assertion.hbs +1 -1
  428. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-variable-assertion.hbs +1 -1
  429. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/checked-opts.hbs +8 -0
  430. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +8 -8
  431. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-nth.hbs +1 -1
  432. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/id.hbs +1 -1
  433. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/testid.hbs +1 -1
  434. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +8 -8
  435. package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +10 -8
  436. package/src/generators/test-generator/code-generator.ts +181 -46
  437. package/src/generators/test-generator/diagnostics.ts +117 -6
  438. package/src/generators/test-generator/patterns/index.ts +175 -24
  439. package/src/generators/test-generator/patterns/types.ts +8 -0
  440. package/src/generators/test-generator/patterns/with-parent-scope.ts +41 -0
  441. package/src/generators/test-generator/step-mapper.ts +277 -43
  442. package/src/generators/test-generator/template-engine.ts +21 -21
  443. package/src/generators/test-generator/utils/data-resolver.ts +23 -2
  444. package/src/generators/test-generator/utils/dialog-scope-identity.ts +71 -0
  445. package/src/generators/test-generator/utils/escape-js-string.ts +24 -0
  446. package/src/generators/test-generator/utils/nearest-match.ts +55 -0
  447. package/src/generators/test-generator/utils/selector-entry-fields.ts +131 -0
  448. package/src/generators/test-generator/utils/selector-entry-walk.ts +58 -0
  449. package/src/generators/test-generator/utils/selector-nth-validation.ts +99 -0
  450. package/src/generators/test-generator/utils/selector-resolver.ts +333 -28
  451. package/src/generators/test-generator/utils/selector-type-validation.ts +87 -0
  452. package/src/harness/audit.ts +55 -30
  453. package/src/harness/capability-plan.ts +33 -1
  454. package/src/harness/catalog/drivers.yaml +1 -1
  455. package/src/harness/catalog/universal-viewpoints.yaml +145 -0
  456. package/src/harness/data-driven-lint.ts +192 -32
  457. package/src/harness/parse.ts +7 -1
  458. package/src/harness/repair.ts +8 -1
  459. package/src/harness/script-check.ts +186 -21
  460. package/src/harness/sensors.ts +75 -1
  461. package/src/harness/viewpoint-ledger.ts +26 -5
  462. package/src/index.ts +7 -0
  463. package/src/orchestrator/project-initializer.ts +17 -0
  464. package/src/orchestrator/report-sanitize-reporter-install.ts +109 -0
  465. package/src/orchestrator/templates/ai-src/commands/delivery.md +4 -3
  466. package/src/orchestrator/templates/ai-src/commands/run-test.md +9 -1
  467. package/src/orchestrator/templates/ai-src/skills/sungen-api-coverage-model/SKILL.md +10 -0
  468. package/src/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +14 -5
  469. package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +62 -3
  470. package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +126 -11
  471. package/src/orchestrator/templates/ai-src/skills/sungen-mobile-gestures/SKILL.md +16 -1
  472. package/src/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +12 -0
  473. package/src/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +19 -5
  474. package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +88 -2
  475. package/src/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +62 -5
  476. package/src/orchestrator/templates/ai-src/skills/sungen-test-design-techniques/SKILL.md +13 -0
  477. package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/SKILL.md +59 -10
  478. package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/group-f-mobile-native.md +419 -0
  479. package/src/orchestrator/templates/playwright.config.ts +7 -0
  480. package/src/orchestrator/templates/readme.md +1 -1
  481. package/src/orchestrator/templates/specs-checked-state.ts +324 -0
  482. package/src/orchestrator/templates/specs-pw-shape-reporter.ts +0 -0
  483. package/src/orchestrator/templates/specs-report-path-sanitize-reporter.ts +142 -0
  484. package/src/orchestrator/templates/specs-url-assert.ts +101 -28
  485. package/src/orchestrator/templates/wdio.conf.ts +105 -1
  486. package/src/utils/selector-types.ts +54 -5
@@ -1,18 +1,49 @@
1
1
  import { ParsedStep } from '../gherkin-parser';
2
+ import { PARENT_CONTAINER_TYPES } from '../gherkin-parser/parent-scope-clause';
2
3
  import { TemplateEngine } from './template-engine';
3
4
  import { adapterRegistry } from './adapters';
4
5
  import { PatternRegistry, PatternContext, Platform } from './patterns';
5
6
  import { SelectorResolver } from './utils/selector-resolver';
6
7
  import { DataResolver } from './utils/data-resolver';
8
+ import { locatorIdentityKey } from './utils/dialog-scope-identity';
7
9
  import { DiagnosticsCollector } from './diagnostics';
8
10
  import { unsupportedStepCode } from './patterns/unsupported-step-code';
11
+ import { maskRefsForMatching } from './utils/mask-refs-for-matching';
12
+ import { isKnownSelectorType } from './utils/selector-type-validation';
9
13
  import { loadAppProfile, ResolvedAppProfile } from './app-profile';
10
14
  import { capabilityRegistry } from '../../capabilities/registry';
11
15
 
16
+ /**
17
+ * The correction SG-W001 owes a project that gated a platform driver away.
18
+ *
19
+ * Gating (plan `260903-1654` phase 11) keeps a mobile-only pattern out of a web project's registry.
20
+ * Right isolation — but `platformMiss` then has nothing to catch, so a mobile-only step falls
21
+ * through to SG-W001, whose sentence "sungen has no step form for it" is FALSE: the form exists in
22
+ * a vocabulary this project declined. Ungated, the author would have seen SG-W020 naming the
23
+ * alternative.
24
+ *
25
+ * It states only what discovery knows. Which STEP the skipped driver owned is unknowable here — the
26
+ * patterns were never loaded — and a static list of off-platform step texts would rot on the next
27
+ * pattern anyone adds, putting a second wrong sentence where the first one was.
28
+ *
29
+ * Returns '' when nothing was gated, so every project that predates the platform profile keeps the
30
+ * message it has today, word for word.
31
+ */
32
+ function offPlatformHint(): string {
33
+ const gated = capabilityRegistry.gatedDrivers();
34
+ if (!gated.length) return '';
35
+ const platform = capabilityRegistry.gatedForPlatform();
36
+ const names = gated.map(([pkg, serves]) => `${pkg} (${serves})`).join(', ');
37
+ return `this project declares platform: ${platform}, so ${names} was not loaded; a ${gated[0][1]}-only step form exists but is out of scope here. Per-platform vocabulary: docs/spec/step-support-matrix.md.`;
38
+ }
39
+
40
+
12
41
  export interface MappedStep {
13
42
  code: string; // Generated Playwright code
14
43
  comment?: string; // Optional comment
15
44
  imports?: string[]; // Required imports
45
+ pattern?: string; // Name of the step form that produced the code (post-map guards read it)
46
+ templateData?: Record<string, any>; // What the resolver handed the template (e.g. a recorded refusal)
16
47
  }
17
48
 
18
49
  /**
@@ -28,6 +59,8 @@ export class StepMapper {
28
59
  private dataResolver: DataResolver;
29
60
  private baseURL: string;
30
61
  private featureName?: string;
62
+ /** The feature's declared `@platform:` OS, for the compile-time iOS gate (SG-E025). */
63
+ private featurePlatform?: 'mobile' | 'android' | 'ios';
31
64
  private screenName?: string;
32
65
  private featurePath?: string;
33
66
  private currentScenarioSteps?: ParsedStep[];
@@ -74,7 +107,9 @@ export class StepMapper {
74
107
  this.platform = adapter.frameworkName === 'appium' ? 'mobile' : 'web';
75
108
  this.templateEngine = new TemplateEngine(templatesDir);
76
109
  this.patternRegistry = new PatternRegistry();
77
- this.selectorResolver = new SelectorResolver(undefined, options.screenName);
110
+ // `platform` is set just above from the ADAPTER — the resolver needs it to decline the
111
+ // list-member auto-infer that has no mobile equivalent (#665).
112
+ this.selectorResolver = new SelectorResolver(undefined, options.screenName, this.platform);
78
113
  this.dataResolver = new DataResolver(undefined, options.screenName, options.runtimeData);
79
114
 
80
115
  // App profile (qa/app.yaml) — loaded after PatternRegistry so capability discovery has
@@ -100,8 +135,9 @@ export class StepMapper {
100
135
  /**
101
136
  * Set feature context for data and selector resolution
102
137
  */
103
- setFeatureContext(featureName: string, featurePath?: string): void {
138
+ setFeatureContext(featureName: string, featurePath?: string, featurePlatform?: 'mobile' | 'android' | 'ios'): void {
104
139
  this.featureName = featureName;
140
+ this.featurePlatform = featurePlatform;
105
141
  this.featurePath = featurePath;
106
142
  this.selectorResolver.setFeatureContext(featureName);
107
143
  // One StepMapper serves every feature in a run, so the step counter that names the
@@ -173,6 +209,19 @@ export class StepMapper {
173
209
  this.dataResolver.registerCaptured(name);
174
210
  }
175
211
 
212
+ /**
213
+ * Register the head of a variable bound by the feature's `@beforeAll` hook. Feature-scoped: it
214
+ * survives setScenarioContext() so every scenario can read `{{head.*}}` as a runtime get().
215
+ */
216
+ registerFeatureCaptured(head: string): void {
217
+ this.dataResolver.registerFeatureCaptured(head);
218
+ }
219
+
220
+ /** Drop feature-scoped captured vars — call once per feature, before its hooks and scenarios map. */
221
+ clearFeatureCaptured(): void {
222
+ this.dataResolver.clearFeatureCaptured();
223
+ }
224
+
176
225
  /**
177
226
  * Map a Gherkin step to Playwright code
178
227
  * Uses pattern registry first, falls back to AI if enabled
@@ -186,6 +235,8 @@ export class StepMapper {
186
235
  this.lastPrimaryKeyword = keyword;
187
236
  }
188
237
 
238
+ this.warnScopeClauseDiscarded(step);
239
+
189
240
  // SG-W015 — `see [X] card with {{n}}` where n is a bare number compiles to a TEXT
190
241
  // assertion (toHaveText('3')), not a count. An author reaching for "3 cards are shown"
191
242
  // gets an assertion that can only pass if the card's TEXT is the digit — a wrong test
@@ -265,20 +316,25 @@ export class StepMapper {
265
316
  this.templateEngine.setBaseContext({ inFrame: true, frameSelector });
266
317
  // C1 — routed through the adapter instead of building `page.frameLocator(...)` inline:
267
318
  // the Appium adapter's own template performs its webview-context switch instead.
268
- const code = this.templateEngine.renderStep('frame-enter-action', { frameSelector });
319
+ // `frameRef` carries the name as WRITTEN in the Gherkin: the Appium twin can fail (#705),
320
+ // and its error has to quote the step the author would go and look at, not the resolved
321
+ // xpath they never typed.
322
+ const code = this.templateEngine.renderStep('frame-enter-action', { frameSelector, frameRef: selectorRef });
269
323
  return { code, comment: `Switch to ${selectorRef} frame` };
270
324
  }
271
325
 
272
326
  // Dialog scope directives — intercept before pattern matching
273
327
  if (/\buse dialog\b/i.test(step.text)) {
274
328
  this.inDialogScope = true;
275
- this.templateEngine.setBaseContext({ inDialog: true });
329
+ // setBaseContext MERGES — an earlier scope's root keys in the same scenario would
330
+ // otherwise leak into this unnamed one.
331
+ this.templateEngine.setBaseContext({ inDialog: true, dialogScopeKey: undefined });
276
332
  this.warnDialogScopeIgnoredOnMobile(step);
277
333
  return { code: '', comment: 'Enter dialog scope' };
278
334
  }
279
335
  if (/\b(?:close|dismiss|exit)\s+dialog\b/i.test(step.text)) {
280
336
  this.inDialogScope = false;
281
- this.templateEngine.setBaseContext({ inDialog: false });
337
+ this.templateEngine.setBaseContext({ inDialog: false, dialogScopeKey: undefined });
282
338
  return { code: '', comment: 'Exit dialog scope' };
283
339
  }
284
340
  // `is on [X] <type>` supports exactly two shapes: `page` (URL from the page-type selector
@@ -314,6 +370,7 @@ export class StepMapper {
314
370
  dialogRootExpr: undefined,
315
371
  dialogRoleName: undefined,
316
372
  dialogFilterText: undefined,
373
+ dialogScopeKey: undefined,
317
374
  };
318
375
 
319
376
  if (step.selectorRef && step.dataRef) {
@@ -342,6 +399,16 @@ export class StepMapper {
342
399
  }
343
400
  // Case 1: no selectorRef → plain page.getByRole('dialog')
344
401
 
402
+ // Record what this root IS, so a later step naming the same element is recognized as the
403
+ // root rather than nested inside it (issue #692). The declared entry when there is one;
404
+ // otherwise the named-dialog fallback the element itself will resolve to.
405
+ if (step.selectorRef) {
406
+ const entry = this.resolveDialogEntry(step.selectorRef);
407
+ contextVars.dialogScopeKey = entry
408
+ ? locatorIdentityKey(entry)
409
+ : locatorIdentityKey({ strategy: 'role', role: 'dialog', name: step.selectorRef });
410
+ }
411
+
345
412
  this.templateEngine.setBaseContext(contextVars);
346
413
  return { code: '', comment: `Enter dialog scope${step.selectorRef ? ': ' + step.selectorRef : ''}` };
347
414
  }
@@ -396,10 +463,15 @@ export class StepMapper {
396
463
  this.checkDataRefConsumed(step, mappedStep);
397
464
  this.checkPageAssertionNoOpOnMobile(step, mappedStep);
398
465
  this.checkSetupNoOpOnMobile(step, mappedStep);
399
- this.checkPageAssertionFallback(step, mappedStep);
466
+ this.checkNavigationNoOpOnMobile(step, mappedStep);
467
+ this.checkPageStepFallback(step, mappedStep);
400
468
  this.checkDeclaredUrlHasNoQuery(step, mappedStep);
469
+ const positionDropped = this.checkClickPositionDropped(step, mappedStep);
470
+ if (positionDropped) return positionDropped;
401
471
  const swallowed = this.checkAssertionClauseSwallowed(step, mappedStep);
402
472
  if (swallowed) return swallowed;
473
+ const badType = this.checkUnknownSelectorType(step, mappedStep);
474
+ if (badType) return badType;
403
475
  return mappedStep;
404
476
  }
405
477
 
@@ -422,7 +494,13 @@ export class StepMapper {
422
494
  step: step.text,
423
495
  });
424
496
  } else {
425
- reason = `step "${step.text}" matched no pattern — sungen has no step form for it. Rephrase using a supported step form (see the sungen-gherkin-syntax reference), or tag the scenario @manual if the check cannot be automated.`;
497
+ // Two shapes, because the ORIGINAL CLAUSE is what was wrong. Appending a correction to
498
+ // "sungen has no step form for it" leaves the reader a sentence that contradicts itself one
499
+ // clause later. When the gate dropped a vocabulary, that clause is simply not written.
500
+ const hint = offPlatformHint();
501
+ reason = hint
502
+ ? `step "${step.text}" matched no pattern here — ${hint} If that is not what you meant, rephrase using a supported step form (see the sungen-gherkin-syntax reference), or tag the scenario @manual if the check cannot be automated.`
503
+ : `step "${step.text}" matched no pattern — sungen has no step form for it. Rephrase using a supported step form (see the sungen-gherkin-syntax reference), or tag the scenario @manual if the check cannot be automated.`;
426
504
  this.diagnostics?.add('SG-W001', reason, {
427
505
  feature: this.featureName,
428
506
  step: step.text,
@@ -450,6 +528,27 @@ export class StepMapper {
450
528
  * falls back to the legacy named-dialog root, so behavior is unchanged for those entries.
451
529
  */
452
530
  private renderDialogRoot(selectorRef: string, opts: { nonRoleOnly?: boolean } = {}): string | undefined {
531
+ const resolved = this.resolveDialogEntry(selectorRef);
532
+ if (!resolved) return undefined;
533
+ if (!StepMapper.DIALOG_ROOT_STRATEGIES.includes(resolved.strategy)) return undefined;
534
+ if (opts.nonRoleOnly && resolved.strategy === 'role') return undefined;
535
+
536
+ // Render the root PAGE-rooted: leaving inDialog set (or `scope: dialog` on the entry
537
+ // itself) would send the partial back through dialog-root and nest the dialog in itself.
538
+ const expr = this.templateEngine.renderStep('locator', {
539
+ ...resolved,
540
+ scope: resolved.scope === 'dialog' ? undefined : resolved.scope,
541
+ inDialog: false,
542
+ }).trim();
543
+ return expr || undefined;
544
+ }
545
+
546
+ /**
547
+ * The selectors.yaml entry behind a dialog reference, normalized the way the `locator` partial
548
+ * needs it. Shared by `renderDialogRoot` (which turns it into the root expression) and the
549
+ * scope-entry path (which keys off it to recognize the root again later — issue #692).
550
+ */
551
+ private resolveDialogEntry(selectorRef: string): any | undefined {
453
552
  let resolved: any;
454
553
  try {
455
554
  resolved = this.selectorResolver.resolveSelector(selectorRef, this.featureName, 'dialog', 0);
@@ -463,34 +562,27 @@ export class StepMapper {
463
562
  if (resolved.strategy === 'css' && resolved.locator) {
464
563
  resolved = { ...resolved, strategy: 'locator', value: resolved.locator };
465
564
  }
466
- if (!StepMapper.DIALOG_ROOT_STRATEGIES.includes(resolved.strategy)) return undefined;
467
- if (opts.nonRoleOnly && resolved.strategy === 'role') return undefined;
468
-
469
- // Render the root PAGE-rooted: leaving inDialog set (or `scope: dialog` on the entry
470
- // itself) would send the partial back through dialog-root and nest the dialog in itself.
471
- const expr = this.templateEngine.renderStep('locator', {
472
- ...resolved,
473
- scope: resolved.scope === 'dialog' ? undefined : resolved.scope,
474
- inDialog: false,
475
- }).trim();
476
- return expr || undefined;
565
+ return resolved;
477
566
  }
478
567
 
479
568
  /**
480
- * SG-W013 — a page assertion whose path came from a fallback rather than from a `type: page`
481
- * selector entry. Both resolvers degrade quietly when `[Ref]` does not resolve: `see [X] page`
482
- * falls back to the feature's own path, `is on [X] page` to `/<ref>/`. Before the assertion was
483
- * anchored that produced a test which always PASSED; anchored, it produces one that can never
484
- * pass. Neither is the assertion the author wrote, and nothing said so.
569
+ * SG-W013 — a page STEP whose path came from a fallback rather than from a `type: page`
570
+ * selector entry. Every resolver degrades quietly when `[Ref]` does not resolve: `see [X] page`
571
+ * and `wait for [X] page` fall back to the feature's own path, `is on [X] page` to `/<ref>/`.
572
+ * Before the match was anchored that produced a test which always PASSED; anchored, it produces
573
+ * one that can never pass — an assertion that always fails, or, since #677 anchored the wait
574
+ * too, a wait that can only time out. None is the step the author wrote, and nothing said so.
485
575
  *
486
- * Detected here rather than in the resolver on purpose: `MappedStep` carries only
487
- * `{code, comment, imports}`, and the resolvers live in the driver packages while the collector
488
- * lives in core. Re-asking the SAME resolver the step-mapper already owns costs three lines and
576
+ * Detected here rather than in the resolver on purpose: the resolvers live in the driver packages
577
+ * while the collector lives in core (the mapped step now also names its pattern and carries the
578
+ * resolver's data, but the fallback itself is only visible by re-resolving the selector). Re-asking the SAME resolver the step-mapper already owns costs three lines and
489
579
  * duplicates no logic — the alternative is widening the adapter contract for every driver.
490
580
  */
491
- private checkPageAssertionFallback(step: ParsedStep, mapped: MappedStep): void {
581
+ private checkPageStepFallback(step: ParsedStep, mapped: MappedStep): void {
492
582
  if (!this.diagnostics || !step.selectorRef || step.elementType !== 'page') return;
493
- if (!/\b(?:urlMatches|expectPage)\(/.test(mapped.code || '')) return; // both URL-assertion shapes (#662)
583
+ // every shape that compiles a page path into an ANCHORED match: the two assertions (#662)
584
+ // and the wait (#677). A step that emitted none of them has no path to get wrong.
585
+ if (!/\b(?:urlMatches|expectPage|waitForPage)\(/.test(mapped.code || '')) return;
494
586
  let resolvedAsPage = false;
495
587
  try {
496
588
  const resolved = this.selectorResolver.resolveSelector(
@@ -502,7 +594,7 @@ export class StepMapper {
502
594
  }
503
595
  if (resolvedAsPage) return;
504
596
  this.diagnostics.add('SG-W013',
505
- `page assertion for "[${step.selectorRef}]" has no "type: page" selector entry, so it asserts a FALLBACK path — the anchored assertion cannot match the real URL. Declare the page, e.g. "${step.selectorRef.toLowerCase()}:\\n type: page\\n value: '/your/path'"`,
597
+ `page step "[${step.selectorRef}]" has no "type: page" selector entry, so it targets a FALLBACK path — the anchored match can never reach the real URL (an assertion always fails; a wait can only time out). Declare the page, e.g. "${step.selectorRef.toLowerCase()}:\\n type: page\\n value: '/your/path'"`,
506
598
  { feature: this.featureName, step: step.text });
507
599
  }
508
600
 
@@ -551,6 +643,43 @@ export class StepMapper {
551
643
  * Deliberately narrow — it fires only on the bare-visibility shape, so a pattern that genuinely
552
644
  * consumed the clause (`is-hidden`, `contains`, `matches-format`, the state family) is untouched.
553
645
  */
646
+ /**
647
+ * SG-E027 (#711) — the step names an entry whose `type:` is not a selector type, so the resolver
648
+ * fell to its placeholder default and this step compiled to `getByPlaceholder('<the value>')`:
649
+ * a locator that cannot match, which would fail much later as a timeout naming placeholder text
650
+ * the author never wrote. Compile it to a `throw` instead — the same reasoning as SG-E023 above,
651
+ * that a step which reads as a check and can only ever fail obscurely is worse than one that
652
+ * fails loudly and says why.
653
+ *
654
+ * The DIAGNOSTIC is not added here. `SelectorResolver.loadNewSelectorFile` already reported this
655
+ * entry once for the whole file; re-reporting per step would print the same line twenty times for
656
+ * one typo. This method only replaces the code, so the compile-time message and the runtime
657
+ * failure stay one fact reported once.
658
+ *
659
+ * A resolve throw means the key is missing entirely — a different defect that the resolver's own
660
+ * error already owns — and an auto-inferred selector stamps no `selectorType`, so both skip.
661
+ */
662
+ private checkUnknownSelectorType(step: ParsedStep, mapped: MappedStep): MappedStep | null {
663
+ if (!step.selectorRef) return null;
664
+ let selectorType: unknown;
665
+ try {
666
+ selectorType = this.selectorResolver.resolveSelector(
667
+ step.selectorRef, this.featureName, step.elementType, step.nth,
668
+ ).selectorType;
669
+ } catch {
670
+ return null; // missing key — the resolver's own error owns that
671
+ }
672
+ if (selectorType === undefined || isKnownSelectorType(selectorType)) return null;
673
+ const reason = `step "${step.text}" names selector "${step.selectorRef}", whose selectors.yaml `
674
+ + `entry declares type: "${String(selectorType)}" — not a selector type, so the entry `
675
+ + `resolved to a placeholder locator that cannot match. See SG-E027 for the valid types and `
676
+ + `the nearest spelling to what was written.`;
677
+ return {
678
+ code: unsupportedStepCode(reason),
679
+ comment: `Unknown selector type "${String(selectorType)}" — see SG-E027: ${step.text}`,
680
+ };
681
+ }
682
+
554
683
  private checkAssertionClauseSwallowed(step: ParsedStep, mapped: MappedStep): MappedStep | null {
555
684
  const code = mapped.code || '';
556
685
  // Bare visibility: nothing but a visible/displayed assertion on one locator.
@@ -593,6 +722,42 @@ export class StepMapper {
593
722
  };
594
723
  }
595
724
 
725
+ /**
726
+ * A click step that names a POSITION (`at position "top-left"`, `at offset x=…, y=…`) must compile
727
+ * through the positioned-click form with a position it understood. Anything else — a typo in the
728
+ * keyword, a value the grammar rejects, or a combination another click form claims first (`try to
729
+ * click … at position`, `… at offset … and accept [OK] alert`, `… with {{v}} … at position`) —
730
+ * would otherwise click the element's CENTRE and pass, which for a backdrop-dismiss test is the
731
+ * one outcome that proves nothing. Owned by the compiler rather than the driver so it holds no
732
+ * matter which pattern wins.
733
+ */
734
+ private static readonly CLICK_POSITION_HINT =
735
+ /\bat\s+(?:position|offset)\b|\bat\s+\w+\s+(?:"(?:top|bottom)-(?:left|right)"|x\s*=)|\b(?:top|bottom)[- ](?:left|right)\b|\bat\s+x\s*=/i;
736
+
737
+ private checkClickPositionDropped(step: ParsedStep, mapped: MappedStep): MappedStep | null {
738
+ // Refs are masked; so is every quoted VALUE except a preset name — `with "Engineer at position 3"`
739
+ // is a text filter, not a position, while a mistyped `at postion "top-left"` must stay visible.
740
+ const masked = maskRefsForMatching(step.text)
741
+ .replace(/"([^"]*)"/g, (quoted, inner: string) => (/^(?:top|bottom)-(?:left|right)$/i.test(inner.trim()) ? quoted : '"~"'));
742
+ if (!/\bclick/i.test(masked) || !StepMapper.CLICK_POSITION_HINT.test(masked)) return null;
743
+ // Already refused upstream (off-platform SG-W020, a dropped state clause, a missing template…):
744
+ // that refusal names the real cause; this guard only exists to stop a SILENT centre click.
745
+ if (/^throw new Error\("\[sungen\]/.test(mapped.code.trim())) return null;
746
+ const refused = mapped.templateData?.positionError as string | undefined;
747
+ if (mapped.pattern === 'click-element-at-position' && !refused) return null;
748
+ const reason = refused
749
+ ? `step "${step.text}": ${refused}`
750
+ : `step "${step.text}" asks for a click position but compiled through the "${mapped.pattern ?? 'unknown'}" `
751
+ + 'step form, which clicks the element\'s centre — the position would be silently dropped. Write '
752
+ + 'it as `click [X] <type> at position "<preset>"` or `… at offset x=<n>px|%, y=<n>px|%` on its own: '
753
+ + '`try to click`, a `with {{v}}` text filter and `… and accept [OK] alert` do not take a position.';
754
+ this.diagnostics?.add('SG-E026', reason, { feature: this.featureName, step: step.text });
755
+ return {
756
+ code: unsupportedStepCode(reason),
757
+ comment: `Click position dropped — see SG-E026: ${step.text}`,
758
+ };
759
+ }
760
+
596
761
  private checkDataRefConsumed(step: ParsedStep, mapped: MappedStep): void {
597
762
  if (!step.dataRef || !this.diagnostics) return;
598
763
  const code = mapped.code || '';
@@ -665,6 +830,70 @@ export class StepMapper {
665
830
  /**
666
831
  * Create pattern context for generators
667
832
  */
833
+ /**
834
+ * Whether a mapped step emits anything that actually RUNS.
835
+ *
836
+ * Comments are stripped — block comments as well as line ones, because a template written with
837
+ * `/* … *\/` would otherwise read as executable code and silence the no-op diagnostics exactly
838
+ * when they were needed. Keyed on the emitted CODE rather than on a template name (MappedStep
839
+ * does not carry one), so any future template that degrades to a comment is caught without being
840
+ * enumerated anywhere.
841
+ *
842
+ * Shared by the SG-W022 / SG-W023 / SG-W026 checks — the three of them ask the same question, and
843
+ * three copies of this would drift.
844
+ */
845
+ private emitsExecutableCode(mapped: MappedStep): boolean {
846
+ return Boolean(
847
+ (mapped.code || '')
848
+ .replace(/\/\*[\s\S]*?\*\//g, '')
849
+ .split('\n')
850
+ .map((l) => l.replace(/\/\/.*$/, '').trim())
851
+ .filter(Boolean)
852
+ .join(''),
853
+ );
854
+ }
855
+
856
+ /**
857
+ * SG-W026 — a mobile ACTIVE-NAVIGATION step that emitted no executable code, so a promised screen
858
+ * change never happens.
859
+ *
860
+ * `When User open [Settings] page` compiles to the one-line `navigation` template: a comment
861
+ * noting the app was already launched by the wdio capabilities. The step reads as "go to Settings"
862
+ * and goes nowhere. Pair it with `Then User see [X] page` — itself a no-op, reported as SG-W022 —
863
+ * and the whole scenario passes without ever touching the app.
864
+ *
865
+ * This completes a class that was handled in halves: `route-assertion` already throws loudly and
866
+ * `page-assertion` already reports W022; `navigation` alone was silent.
867
+ *
868
+ * Scoped to the ACTIVE forms. `is on [X] page` and its mobile `screen` twin are mobile's
869
+ * app-LAUNCH anchor, whose empty output is correct and documented as such where IS_ON_TYPES is
870
+ * defined — warning there would cry wolf on every valid mobile feature, and a diagnostic that
871
+ * cries wolf gets filtered out, taking the real ones with it. The exclusion is matched on
872
+ * `is on` FIRST, before the open/navigate test, because "the user is on the page they opened"
873
+ * phrasings contain both.
874
+ *
875
+ * Deliberately NOT a `throw`, for the same reason as SG-W023: an unfulfilled navigation weakens
876
+ * the scenario rather than inverting an assertion, and throwing would break every mobile suite
877
+ * already carrying one of these steps. `generate --strict` is the opt-in that makes it fail.
878
+ */
879
+ private checkNavigationNoOpOnMobile(step: ParsedStep, mapped: MappedStep): void {
880
+ if (this.platform !== 'mobile' || !this.diagnostics) return;
881
+
882
+ const text = step.text.toLowerCase();
883
+ // The anchor is checked first and wins: it is legitimately empty on mobile.
884
+ if (/\bis\s+on\b/.test(text)) return;
885
+ if (!/\bopens?\b|\bnavigates?\s+to\b|\bgo(es)?\s+to\b/.test(text)) return;
886
+ // Only the navigation TARGET forms — `open [X] page|screen`. `open [X] dialog`/`menu` is a tap
887
+ // on a control, which compiles to real code and never reaches here anyway.
888
+ if (step.elementType && !['page', 'screen'].includes(step.elementType)) return;
889
+
890
+ if (this.emitsExecutableCode(mapped)) return;
891
+
892
+ this.diagnostics.add('SG-W026',
893
+ 'this navigation step compiled to a COMMENT — a native app is already launched by the wdio capabilities, so nothing navigates and the following steps run against whatever screen the app happens to be on. Change screens the way a user does, with a tap/gesture on the control that opens it (`When User tap [Settings] button`), and assert a marker on the new screen; keep `is on [X] screen` only as the feature\'s opening anchor',
894
+ { feature: this.featureName, step: step.text });
895
+ }
896
+
668
897
  /**
669
898
  * SG-W023 — a mobile SETUP step that emitted no executable code, so a declared precondition is
670
899
  * never actually established.
@@ -699,13 +928,7 @@ export class StepMapper {
699
928
  // Strip comments; whatever remains is what actually runs. Block comments too — a template written
700
929
  // with `/* … */` would otherwise read as executable code and silence this check exactly when it
701
930
  // was needed.
702
- const executable = (mapped.code || '')
703
- .replace(/\/\*[\s\S]*?\*\//g, '')
704
- .split('\n')
705
- .map((l) => l.replace(/\/\/.*$/, '').trim())
706
- .filter(Boolean)
707
- .join('');
708
- if (executable) return;
931
+ if (this.emitsExecutableCode(mapped)) return;
709
932
 
710
933
  const remedy = isBackendState
711
934
  ? 'backend state is not app state — reset it through a test API/script, or bind a `@query:` precondition, the same way the web adapter has to'
@@ -738,18 +961,28 @@ export class StepMapper {
738
961
  // Strip comments; whatever remains is what actually runs. Block comments are handled as well as
739
962
  // line ones: a template written with `/* … */` would otherwise read as executable code and
740
963
  // silently defeat this check — the diagnostic would go quiet exactly when it was needed.
741
- const executable = (mapped.code || '')
742
- .replace(/\/\*[\s\S]*?\*\//g, '')
743
- .split('\n')
744
- .map((l) => l.replace(/\/\/.*$/, '').trim())
745
- .filter(Boolean)
746
- .join('');
747
- if (executable) return;
964
+ if (this.emitsExecutableCode(mapped)) return;
748
965
  this.diagnostics.add('SG-W022',
749
966
  'a native app has no URL, so this page assertion compiled to a COMMENT — it asserts nothing and the scenario passes regardless. Assert a marker that is actually on the screen instead (`Then User see [Some Element] ...`), or tag the scenario @manual',
750
967
  { feature: this.featureName, step: step.text });
751
968
  }
752
969
 
970
+ /**
971
+ * SG-W028 — a trailing `in [X] <word>` scope clause whose `<word>` is not a container type.
972
+ *
973
+ * The clause is dropped, and everything downstream takes the FIRST `[ref]` and the FIRST
974
+ * `{{data}}` it finds, so the sentence compiles clean while the assertion silently widens to the
975
+ * whole screen (#667). Unlike an unrecognized STEP, which at least raises SG-W001, a partially
976
+ * understood step used to say nothing at all — and a check that runs against more than it names
977
+ * passes for the wrong reason in exactly the list-of-identical-cards case it was written for.
978
+ */
979
+ private warnScopeClauseDiscarded(step: ParsedStep): void {
980
+ if (!step.unsupportedScopeType) return;
981
+ this.diagnostics?.add('SG-W028',
982
+ `\`${step.unsupportedScopeType}\` is not a container type, so the scope clause was DROPPED and this step runs against the whole page, not the named container. Supported: ${PARENT_CONTAINER_TYPES.join(', ')}`,
983
+ { feature: this.featureName, step: step.text });
984
+ }
985
+
753
986
  /**
754
987
  * SG-W021 — dialog scope is a no-op on the Appium adapter.
755
988
  *
@@ -779,6 +1012,7 @@ export class StepMapper {
779
1012
  renderLocator: (resolved) => this.templateEngine.renderStep('locator', resolved),
780
1013
  appProfile: this.appProfile,
781
1014
  platform: this.platform,
1015
+ featurePlatform: this.featurePlatform,
782
1016
  adapterName: this.adapterName,
783
1017
  diagnostics: this.diagnostics,
784
1018
  baseURL: this.baseURL,
@@ -1,7 +1,9 @@
1
1
  import Handlebars from 'handlebars';
2
+ import { escapeSingleQuoted } from './utils/escape-js-string';
2
3
  import fs from 'fs';
3
4
  import path from 'path';
4
- import type { RenderImportsOptions } from './adapters/adapter-interface';
5
+ import type { RenderImportsOptions, HookRenderData } from './adapters/adapter-interface';
6
+ import { locatorIdentityKey } from './utils/dialog-scope-identity';
5
7
 
6
8
  export class TemplateEngine {
7
9
  private templates: Map<string, HandlebarsTemplateDelegate> = new Map();
@@ -28,16 +30,9 @@ export class TemplateEngine {
28
30
  // the result in '…' (selector values, attribute values, scope), so a value containing a newline,
29
31
  // backslash, or quote must be escaped or the generated spec is a syntax error (e.g. a Flutter
30
32
  // composite content-desc like "Quick Convert\nSelect File\n…" used as an accessibility-id).
31
- // Order matters: backslash first, then the rest.
32
- Handlebars.registerHelper('escapeQuotes', function(text: string) {
33
- if (text == null) return '';
34
- return String(text)
35
- .replace(/\\/g, '\\\\')
36
- .replace(/'/g, "\\'")
37
- .replace(/\n/g, '\\n')
38
- .replace(/\r/g, '\\r')
39
- .replace(/\t/g, '\\t');
40
- });
33
+ // The rule itself lives in utils/escape-js-string so the pattern layer, which builds some
34
+ // locator strings by hand, cannot drift from what the templates do.
35
+ Handlebars.registerHelper('escapeQuotes', (text: string) => escapeSingleQuoted(text));
41
36
 
42
37
  // Escape a raw string for embedding inside a REGEX LITERAL (/…/). Metacharacters first —
43
38
  // that class doubles a backslash, so the control-char passes below cannot be mis-read as
@@ -152,6 +147,17 @@ export class TemplateEngine {
152
147
  // element is dialog-rooted even if its role is portal-exempt (issue #465).
153
148
  const explicitDialog = this.scope === 'dialog';
154
149
  if (!this.inDialog && !explicitDialog) return 'page';
150
+ // The element being resolved IS the active scope's own dialog root (issue #692). Prefixing
151
+ // it with that root asks for a dialog nested inside itself —
152
+ // getByRole('dialog',{name:X}).getByRole('dialog',{name:X}) — which can never resolve, yet
153
+ // compiles clean and fails as a plain timeout reading "the dialog never opened". So the
154
+ // root is idempotent: re-asserting the dialog you are scoped inside means what it reads as.
155
+ // Skipped for a `with {{v}}` root, whose .filter({ hasText }) form is not expressible as
156
+ // the element's own locator — identity says nothing about it there.
157
+ if (this.inDialog && !this.dialogFilterText && this.dialogScopeKey &&
158
+ locatorIdentityKey(this) === this.dialogScopeKey) {
159
+ return 'page';
160
+ }
155
161
  // Portal-rendered roles (Radix/MUI/Headless UI/antd render option/menu/tooltip
156
162
  // layers onto document.body) live OUTSIDE the dialog's DOM subtree — wrapping
157
163
  // them in the dialog root guarantees a timeout. List comes from the app profile
@@ -297,7 +303,7 @@ export class TemplateEngine {
297
303
  }
298
304
 
299
305
  renderImports(options?: RenderImportsOptions): string {
300
- return this.render('imports', { runtimeData: options?.runtimeData, basePath: options?.basePath || '..', isParallel: options?.isParallel, needsCleanupImport: options?.needsCleanupImport, needsDb: options?.needsDb, needsApi: options?.needsApi, needsMock: options?.needsMock, urlAssertImports: options?.urlAssertImports, regexEscapeImport: options?.regexEscapeImport });
306
+ return this.render('imports', { runtimeData: options?.runtimeData, basePath: options?.basePath || '..', isParallel: options?.isParallel, needsCleanupImport: options?.needsCleanupImport, needsDb: options?.needsDb, needsApi: options?.needsApi, needsMock: options?.needsMock, urlAssertImports: options?.urlAssertImports, regexEscapeImport: options?.regexEscapeImport, checkedStateImports: options?.checkedStateImports });
301
307
  }
302
308
 
303
309
  renderTestFile(data: {
@@ -332,21 +338,15 @@ export class TemplateEngine {
332
338
  return this.render('before-each', data);
333
339
  }
334
340
 
335
- renderBeforeAll(data: {
336
- steps: Array<{ comment?: string; code: string }>;
337
- }): string {
341
+ renderBeforeAll(data: HookRenderData): string {
338
342
  return this.render('before-all', data);
339
343
  }
340
344
 
341
- renderAfterEach(data: {
342
- steps: Array<{ comment?: string; code: string }>;
343
- }): string {
345
+ renderAfterEach(data: HookRenderData): string {
344
346
  return this.render('after-each', data);
345
347
  }
346
348
 
347
- renderAfterAll(data: {
348
- steps: Array<{ comment?: string; code: string }>;
349
- }): string {
349
+ renderAfterAll(data: HookRenderData): string {
350
350
  return this.render('after-all', data);
351
351
  }
352
352
 
@@ -29,6 +29,27 @@ export class DataResolver {
29
29
  this.capturedVars.clear();
30
30
  }
31
31
 
32
+ // Vars bound by a `@beforeAll` hook's `@api:`/`@query:` preconditions. The hook runs before every
33
+ // test of the feature, so its bindings are readable from EVERY scenario — they survive the
34
+ // per-scenario clearCaptured(). Stored by head (`login`) and matched against the ref's head
35
+ // (`login.body.token` → `login`), since a scenario may read any sub-path of the bound response.
36
+ private featureCaptured = new Set<string>();
37
+
38
+ /** Register the head of a variable bound by the feature's `@beforeAll` hook (feature-scoped). */
39
+ registerFeatureCaptured(head: string): void {
40
+ this.featureCaptured.add(head);
41
+ }
42
+
43
+ /** Clear feature-scoped captured vars — call at the start of each feature. */
44
+ clearFeatureCaptured(): void {
45
+ this.featureCaptured.clear();
46
+ }
47
+
48
+ /** Runtime-only ref (scenario-captured, or bound by the feature's `@beforeAll` hook)? */
49
+ private isCaptured(dataRef: string): boolean {
50
+ return this.capturedVars.has(dataRef) || this.featureCaptured.has(dataRef.split(/[.[]/)[0]);
51
+ }
52
+
32
53
  constructor(testDataDir?: string, screenName?: string, runtimeMode: boolean = false) {
33
54
  this.testDataDir = testDataDir || path.join(process.cwd(), 'qa', 'test-data');
34
55
  this.screenName = screenName;
@@ -52,7 +73,7 @@ export class DataResolver {
52
73
  resolveData(dataRef: string, featureName?: string): string {
53
74
  // Captured vars exist only at runtime → emit a marker (→ testData.get) and
54
75
  // skip YAML validation. Requires runtime mode (capture writes testData.set).
55
- if (this.capturedVars.has(dataRef)) {
76
+ if (this.isCaptured(dataRef)) {
56
77
  return DataResolver.encodeMarker(dataRef);
57
78
  }
58
79
 
@@ -75,7 +96,7 @@ export class DataResolver {
75
96
  * anything said about it belongs in a warning, never a gate.
76
97
  */
77
98
  peekBaseValue(dataRef: string, featureName?: string): string | undefined {
78
- if (this.capturedVars.has(dataRef)) return undefined;
99
+ if (this.isCaptured(dataRef)) return undefined;
79
100
  try {
80
101
  return this.resolveValue(dataRef, featureName);
81
102
  } catch {