@sun-asterisk/sungen 3.2.29 → 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 (335) hide show
  1. package/dist/cli/commands/delivery.d.ts.map +1 -1
  2. package/dist/cli/commands/delivery.js +2 -1
  3. package/dist/cli/commands/delivery.js.map +1 -1
  4. package/dist/cli/commands/generate.d.ts.map +1 -1
  5. package/dist/cli/commands/generate.js +6 -2
  6. package/dist/cli/commands/generate.js.map +1 -1
  7. package/dist/cli/commands/inspect.js +1 -1
  8. package/dist/cli/commands/inspect.js.map +1 -1
  9. package/dist/cli/commands/results-merge.d.ts.map +1 -1
  10. package/dist/cli/commands/results-merge.js +9 -2
  11. package/dist/cli/commands/results-merge.js.map +1 -1
  12. package/dist/cli/commands/results-sanitize.d.ts +24 -0
  13. package/dist/cli/commands/results-sanitize.d.ts.map +1 -0
  14. package/dist/cli/commands/results-sanitize.js +126 -0
  15. package/dist/cli/commands/results-sanitize.js.map +1 -0
  16. package/dist/cli/commands/script-check.d.ts.map +1 -1
  17. package/dist/cli/commands/script-check.js +3 -1
  18. package/dist/cli/commands/script-check.js.map +1 -1
  19. package/dist/cli/commands/update.d.ts.map +1 -1
  20. package/dist/cli/commands/update.js +3 -0
  21. package/dist/cli/commands/update.js.map +1 -1
  22. package/dist/exporters/api-testcase-formatter.d.ts +3 -6
  23. package/dist/exporters/api-testcase-formatter.d.ts.map +1 -1
  24. package/dist/exporters/api-testcase-formatter.js +7 -10
  25. package/dist/exporters/api-testcase-formatter.js.map +1 -1
  26. package/dist/exporters/matrix/build.d.ts.map +1 -1
  27. package/dist/exporters/matrix/build.js +4 -2
  28. package/dist/exporters/matrix/build.js.map +1 -1
  29. package/dist/exporters/matrix/render-coverage-sheet.d.ts.map +1 -1
  30. package/dist/exporters/matrix/render-coverage-sheet.js +3 -1
  31. package/dist/exporters/matrix/render-coverage-sheet.js.map +1 -1
  32. package/dist/exporters/matrix/render-csv.d.ts.map +1 -1
  33. package/dist/exporters/matrix/render-csv.js +3 -1
  34. package/dist/exporters/matrix/render-csv.js.map +1 -1
  35. package/dist/exporters/matrix/render-overview-sheet.d.ts.map +1 -1
  36. package/dist/exporters/matrix/render-overview-sheet.js +5 -9
  37. package/dist/exporters/matrix/render-overview-sheet.js.map +1 -1
  38. package/dist/exporters/matrix/result-totals.d.ts +23 -0
  39. package/dist/exporters/matrix/result-totals.d.ts.map +1 -0
  40. package/dist/exporters/matrix/result-totals.js +21 -0
  41. package/dist/exporters/matrix/result-totals.js.map +1 -0
  42. package/dist/exporters/matrix/testcase-summary-band.d.ts.map +1 -1
  43. package/dist/exporters/matrix/testcase-summary-band.js +2 -6
  44. package/dist/exporters/matrix/testcase-summary-band.js.map +1 -1
  45. package/dist/exporters/qa-env-secret-denylist.js +2 -2
  46. package/dist/exporters/qa-env-secret-denylist.js.map +1 -1
  47. package/dist/exporters/report-path-sanitizer.d.ts +38 -0
  48. package/dist/exporters/report-path-sanitizer.d.ts.map +1 -0
  49. package/dist/exporters/report-path-sanitizer.js +137 -0
  50. package/dist/exporters/report-path-sanitizer.js.map +1 -0
  51. package/dist/exporters/result-variants.d.ts.map +1 -1
  52. package/dist/exporters/result-variants.js +2 -1
  53. package/dist/exporters/result-variants.js.map +1 -1
  54. package/dist/exporters/sensitive-key.d.ts +31 -0
  55. package/dist/exporters/sensitive-key.d.ts.map +1 -0
  56. package/dist/exporters/sensitive-key.js +50 -0
  57. package/dist/exporters/sensitive-key.js.map +1 -0
  58. package/dist/exporters/test-data-resolver.d.ts +4 -1
  59. package/dist/exporters/test-data-resolver.d.ts.map +1 -1
  60. package/dist/exporters/test-data-resolver.js +12 -2
  61. package/dist/exporters/test-data-resolver.js.map +1 -1
  62. package/dist/generators/gherkin-parser/index.d.ts +3 -0
  63. package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
  64. package/dist/generators/gherkin-parser/index.js +17 -9
  65. package/dist/generators/gherkin-parser/index.js.map +1 -1
  66. package/dist/generators/gherkin-parser/parent-scope-clause.d.ts +54 -0
  67. package/dist/generators/gherkin-parser/parent-scope-clause.d.ts.map +1 -0
  68. package/dist/generators/gherkin-parser/parent-scope-clause.js +109 -0
  69. package/dist/generators/gherkin-parser/parent-scope-clause.js.map +1 -0
  70. package/dist/generators/test-generator/adapters/adapter-interface.d.ts +23 -18
  71. package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
  72. package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts +4 -19
  73. package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts.map +1 -1
  74. package/dist/generators/test-generator/adapters/appium/appium-adapter.js.map +1 -1
  75. package/dist/generators/test-generator/adapters/appium/templates/after-all.hbs +18 -0
  76. package/dist/generators/test-generator/adapters/appium/templates/after-each.hbs +18 -0
  77. package/dist/generators/test-generator/adapters/appium/templates/before-all.hbs +18 -0
  78. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/click-at-position-action.hbs +1 -0
  79. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/click-element-with-text.hbs +1 -1
  80. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/double-click-action.hbs +7 -0
  81. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/frame-enter-action.hbs +3 -6
  82. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/table-paginate-action.hbs +3 -0
  83. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-assertion.hbs +1 -1
  84. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/long-press-action.hbs +4 -0
  85. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/pinch-zoom-action.hbs +4 -0
  86. package/dist/generators/test-generator/adapters/appium/templates/steps/partials/locator.hbs +9 -1
  87. package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +54 -23
  88. package/dist/generators/test-generator/adapters/index.d.ts +1 -1
  89. package/dist/generators/test-generator/adapters/index.d.ts.map +1 -1
  90. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +4 -19
  91. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
  92. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
  93. package/dist/generators/test-generator/adapters/playwright/templates/after-all.hbs +19 -1
  94. package/dist/generators/test-generator/adapters/playwright/templates/after-each.hbs +18 -0
  95. package/dist/generators/test-generator/adapters/playwright/templates/before-all.hbs +19 -1
  96. package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  97. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/check-action.hbs +6 -1
  98. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/click-at-position-action.hbs +23 -0
  99. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/expand-action.hbs +8 -8
  100. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/fill-editor-action.hbs +7 -2
  101. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row-nth.hbs +3 -1
  102. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +3 -1
  103. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-paginate-action.hbs +5 -0
  104. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/uncheck-action.hbs +3 -1
  105. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/unknown-element-action.hbs +1 -1
  106. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/wait-for-page.hbs +18 -1
  107. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-assertion.hbs +32 -4
  108. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-element.hbs +5 -2
  109. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/checked-assertion.hbs +5 -1
  110. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/column-cell-assertion.hbs +13 -2
  111. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/contain-text-assertion.hbs +1 -1
  112. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +2 -2
  113. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-variable-assertion.hbs +1 -1
  114. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/have-text-assertion.hbs +1 -1
  115. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +2 -2
  116. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-variable-assertion.hbs +1 -1
  117. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/not-checked-assertion.hbs +2 -1
  118. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/selected-assertion.hbs +14 -3
  119. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/state-with-filter-assertion.hbs +13 -2
  120. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +3 -3
  121. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-value-assertion.hbs +1 -1
  122. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-variable-assertion.hbs +1 -1
  123. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/checked-opts.hbs +8 -0
  124. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-nth.hbs +1 -1
  125. package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +10 -8
  126. package/dist/generators/test-generator/code-generator.d.ts +37 -0
  127. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  128. package/dist/generators/test-generator/code-generator.js +160 -39
  129. package/dist/generators/test-generator/code-generator.js.map +1 -1
  130. package/dist/generators/test-generator/diagnostics.d.ts +79 -5
  131. package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
  132. package/dist/generators/test-generator/diagnostics.js +78 -4
  133. package/dist/generators/test-generator/diagnostics.js.map +1 -1
  134. package/dist/generators/test-generator/patterns/index.d.ts +24 -5
  135. package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
  136. package/dist/generators/test-generator/patterns/index.js +92 -14
  137. package/dist/generators/test-generator/patterns/index.js.map +1 -1
  138. package/dist/generators/test-generator/patterns/with-parent-scope.d.ts +18 -0
  139. package/dist/generators/test-generator/patterns/with-parent-scope.d.ts.map +1 -0
  140. package/dist/generators/test-generator/patterns/with-parent-scope.js +40 -0
  141. package/dist/generators/test-generator/patterns/with-parent-scope.js.map +1 -0
  142. package/dist/generators/test-generator/step-mapper.d.ts +63 -9
  143. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  144. package/dist/generators/test-generator/step-mapper.js +166 -28
  145. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  146. package/dist/generators/test-generator/template-engine.d.ts +4 -19
  147. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  148. package/dist/generators/test-generator/template-engine.js +17 -12
  149. package/dist/generators/test-generator/template-engine.js.map +1 -1
  150. package/dist/generators/test-generator/utils/data-resolver.d.ts +7 -0
  151. package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
  152. package/dist/generators/test-generator/utils/data-resolver.js +19 -2
  153. package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
  154. package/dist/generators/test-generator/utils/dialog-scope-identity.d.ts +41 -0
  155. package/dist/generators/test-generator/utils/dialog-scope-identity.d.ts.map +1 -0
  156. package/dist/generators/test-generator/utils/dialog-scope-identity.js +61 -0
  157. package/dist/generators/test-generator/utils/dialog-scope-identity.js.map +1 -0
  158. package/dist/generators/test-generator/utils/escape-js-string.d.ts +17 -0
  159. package/dist/generators/test-generator/utils/escape-js-string.d.ts.map +1 -0
  160. package/dist/generators/test-generator/utils/escape-js-string.js +29 -0
  161. package/dist/generators/test-generator/utils/escape-js-string.js.map +1 -0
  162. package/dist/generators/test-generator/utils/nearest-match.d.ts +24 -0
  163. package/dist/generators/test-generator/utils/nearest-match.d.ts.map +1 -0
  164. package/dist/generators/test-generator/utils/nearest-match.js +56 -0
  165. package/dist/generators/test-generator/utils/nearest-match.js.map +1 -0
  166. package/dist/generators/test-generator/utils/selector-entry-fields.d.ts +58 -0
  167. package/dist/generators/test-generator/utils/selector-entry-fields.d.ts.map +1 -0
  168. package/dist/generators/test-generator/utils/selector-entry-fields.js +119 -0
  169. package/dist/generators/test-generator/utils/selector-entry-fields.js.map +1 -0
  170. package/dist/generators/test-generator/utils/selector-entry-walk.d.ts +25 -0
  171. package/dist/generators/test-generator/utils/selector-entry-walk.d.ts.map +1 -0
  172. package/dist/generators/test-generator/utils/selector-entry-walk.js +54 -0
  173. package/dist/generators/test-generator/utils/selector-entry-walk.js.map +1 -0
  174. package/dist/generators/test-generator/utils/selector-nth-validation.d.ts +59 -0
  175. package/dist/generators/test-generator/utils/selector-nth-validation.d.ts.map +1 -0
  176. package/dist/generators/test-generator/utils/selector-nth-validation.js +91 -0
  177. package/dist/generators/test-generator/utils/selector-nth-validation.js.map +1 -0
  178. package/dist/generators/test-generator/utils/selector-resolver.d.ts +30 -1
  179. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
  180. package/dist/generators/test-generator/utils/selector-resolver.js +195 -15
  181. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
  182. package/dist/generators/test-generator/utils/selector-type-validation.d.ts +51 -0
  183. package/dist/generators/test-generator/utils/selector-type-validation.d.ts.map +1 -0
  184. package/dist/generators/test-generator/utils/selector-type-validation.js +77 -0
  185. package/dist/generators/test-generator/utils/selector-type-validation.js.map +1 -0
  186. package/dist/harness/data-driven-lint.d.ts.map +1 -1
  187. package/dist/harness/data-driven-lint.js +217 -33
  188. package/dist/harness/data-driven-lint.js.map +1 -1
  189. package/dist/harness/script-check.d.ts +2 -2
  190. package/dist/harness/script-check.d.ts.map +1 -1
  191. package/dist/harness/script-check.js +186 -25
  192. package/dist/harness/script-check.js.map +1 -1
  193. package/dist/index.d.ts +2 -0
  194. package/dist/index.d.ts.map +1 -1
  195. package/dist/index.js +10 -1
  196. package/dist/index.js.map +1 -1
  197. package/dist/orchestrator/project-initializer.d.ts +6 -0
  198. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  199. package/dist/orchestrator/project-initializer.js +16 -0
  200. package/dist/orchestrator/project-initializer.js.map +1 -1
  201. package/dist/orchestrator/report-sanitize-reporter-install.d.ts +24 -0
  202. package/dist/orchestrator/report-sanitize-reporter-install.d.ts.map +1 -0
  203. package/dist/orchestrator/report-sanitize-reporter-install.js +161 -0
  204. package/dist/orchestrator/report-sanitize-reporter-install.js.map +1 -0
  205. package/dist/orchestrator/templates/ai-src/commands/run-test.md +9 -1
  206. package/dist/orchestrator/templates/ai-src/skills/sungen-api-coverage-model/SKILL.md +10 -0
  207. package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +14 -2
  208. package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +108 -8
  209. package/dist/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +1 -0
  210. package/dist/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +9 -2
  211. package/dist/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +4 -4
  212. package/dist/orchestrator/templates/playwright.config.ts +7 -0
  213. package/dist/orchestrator/templates/specs-checked-state.d.ts +83 -0
  214. package/dist/orchestrator/templates/specs-checked-state.d.ts.map +1 -0
  215. package/dist/orchestrator/templates/specs-checked-state.js +261 -0
  216. package/dist/orchestrator/templates/specs-checked-state.js.map +1 -0
  217. package/dist/orchestrator/templates/specs-checked-state.ts +324 -0
  218. package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.d.ts +36 -0
  219. package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.d.ts.map +1 -0
  220. package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.js +155 -0
  221. package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.js.map +1 -0
  222. package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.ts +142 -0
  223. package/dist/orchestrator/templates/specs-url-assert.d.ts +50 -25
  224. package/dist/orchestrator/templates/specs-url-assert.d.ts.map +1 -1
  225. package/dist/orchestrator/templates/specs-url-assert.js +65 -24
  226. package/dist/orchestrator/templates/specs-url-assert.js.map +1 -1
  227. package/dist/orchestrator/templates/specs-url-assert.ts +101 -28
  228. package/dist/utils/selector-types.d.ts +43 -1
  229. package/dist/utils/selector-types.d.ts.map +1 -1
  230. package/dist/utils/selector-types.js +51 -7
  231. package/dist/utils/selector-types.js.map +1 -1
  232. package/package.json +3 -3
  233. package/src/cli/commands/delivery.ts +2 -1
  234. package/src/cli/commands/generate.ts +5 -2
  235. package/src/cli/commands/inspect.ts +1 -1
  236. package/src/cli/commands/results-merge.ts +9 -2
  237. package/src/cli/commands/results-sanitize.ts +98 -0
  238. package/src/cli/commands/script-check.ts +3 -1
  239. package/src/cli/commands/update.ts +3 -0
  240. package/src/exporters/api-testcase-formatter.ts +5 -7
  241. package/src/exporters/matrix/build.ts +4 -2
  242. package/src/exporters/matrix/render-coverage-sheet.ts +3 -1
  243. package/src/exporters/matrix/render-csv.ts +3 -1
  244. package/src/exporters/matrix/render-overview-sheet.ts +5 -10
  245. package/src/exporters/matrix/result-totals.ts +37 -0
  246. package/src/exporters/matrix/testcase-summary-band.ts +2 -7
  247. package/src/exporters/qa-env-secret-denylist.ts +1 -1
  248. package/src/exporters/report-path-sanitizer.ts +144 -0
  249. package/src/exporters/result-variants.ts +2 -1
  250. package/src/exporters/sensitive-key.ts +48 -0
  251. package/src/exporters/test-data-resolver.ts +12 -2
  252. package/src/generators/gherkin-parser/index.ts +21 -10
  253. package/src/generators/gherkin-parser/parent-scope-clause.ts +130 -0
  254. package/src/generators/test-generator/adapters/adapter-interface.ts +25 -3
  255. package/src/generators/test-generator/adapters/appium/appium-adapter.ts +4 -4
  256. package/src/generators/test-generator/adapters/appium/templates/after-all.hbs +18 -0
  257. package/src/generators/test-generator/adapters/appium/templates/after-each.hbs +18 -0
  258. package/src/generators/test-generator/adapters/appium/templates/before-all.hbs +18 -0
  259. package/src/generators/test-generator/adapters/appium/templates/steps/actions/click-at-position-action.hbs +1 -0
  260. package/src/generators/test-generator/adapters/appium/templates/steps/actions/click-element-with-text.hbs +1 -1
  261. package/src/generators/test-generator/adapters/appium/templates/steps/actions/double-click-action.hbs +7 -0
  262. package/src/generators/test-generator/adapters/appium/templates/steps/actions/frame-enter-action.hbs +3 -6
  263. package/src/generators/test-generator/adapters/appium/templates/steps/actions/table-paginate-action.hbs +3 -0
  264. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-assertion.hbs +1 -1
  265. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/long-press-action.hbs +4 -0
  266. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/pinch-zoom-action.hbs +4 -0
  267. package/src/generators/test-generator/adapters/appium/templates/steps/partials/locator.hbs +9 -1
  268. package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +54 -23
  269. package/src/generators/test-generator/adapters/index.ts +1 -1
  270. package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +4 -4
  271. package/src/generators/test-generator/adapters/playwright/templates/after-all.hbs +19 -1
  272. package/src/generators/test-generator/adapters/playwright/templates/after-each.hbs +18 -0
  273. package/src/generators/test-generator/adapters/playwright/templates/before-all.hbs +19 -1
  274. package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  275. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/check-action.hbs +6 -1
  276. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/click-at-position-action.hbs +23 -0
  277. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/expand-action.hbs +8 -8
  278. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/fill-editor-action.hbs +7 -2
  279. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row-nth.hbs +3 -1
  280. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +3 -1
  281. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-paginate-action.hbs +5 -0
  282. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/uncheck-action.hbs +3 -1
  283. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/unknown-element-action.hbs +1 -1
  284. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/wait-for-page.hbs +18 -1
  285. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-assertion.hbs +32 -4
  286. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-element.hbs +5 -2
  287. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/checked-assertion.hbs +5 -1
  288. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/column-cell-assertion.hbs +13 -2
  289. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/contain-text-assertion.hbs +1 -1
  290. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +2 -2
  291. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-variable-assertion.hbs +1 -1
  292. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/have-text-assertion.hbs +1 -1
  293. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +2 -2
  294. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-variable-assertion.hbs +1 -1
  295. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/not-checked-assertion.hbs +2 -1
  296. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/selected-assertion.hbs +14 -3
  297. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/state-with-filter-assertion.hbs +13 -2
  298. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +3 -3
  299. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-value-assertion.hbs +1 -1
  300. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-variable-assertion.hbs +1 -1
  301. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/checked-opts.hbs +8 -0
  302. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-nth.hbs +1 -1
  303. package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +10 -8
  304. package/src/generators/test-generator/code-generator.ts +176 -45
  305. package/src/generators/test-generator/diagnostics.ts +79 -5
  306. package/src/generators/test-generator/patterns/index.ts +97 -16
  307. package/src/generators/test-generator/patterns/with-parent-scope.ts +41 -0
  308. package/src/generators/test-generator/step-mapper.ts +170 -26
  309. package/src/generators/test-generator/template-engine.ts +21 -21
  310. package/src/generators/test-generator/utils/data-resolver.ts +23 -2
  311. package/src/generators/test-generator/utils/dialog-scope-identity.ts +71 -0
  312. package/src/generators/test-generator/utils/escape-js-string.ts +24 -0
  313. package/src/generators/test-generator/utils/nearest-match.ts +55 -0
  314. package/src/generators/test-generator/utils/selector-entry-fields.ts +131 -0
  315. package/src/generators/test-generator/utils/selector-entry-walk.ts +58 -0
  316. package/src/generators/test-generator/utils/selector-nth-validation.ts +99 -0
  317. package/src/generators/test-generator/utils/selector-resolver.ts +245 -16
  318. package/src/generators/test-generator/utils/selector-type-validation.ts +87 -0
  319. package/src/harness/data-driven-lint.ts +192 -32
  320. package/src/harness/script-check.ts +186 -21
  321. package/src/index.ts +7 -0
  322. package/src/orchestrator/project-initializer.ts +17 -0
  323. package/src/orchestrator/report-sanitize-reporter-install.ts +109 -0
  324. package/src/orchestrator/templates/ai-src/commands/run-test.md +9 -1
  325. package/src/orchestrator/templates/ai-src/skills/sungen-api-coverage-model/SKILL.md +10 -0
  326. package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +14 -2
  327. package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +108 -8
  328. package/src/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +1 -0
  329. package/src/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +9 -2
  330. package/src/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +4 -4
  331. package/src/orchestrator/templates/playwright.config.ts +7 -0
  332. package/src/orchestrator/templates/specs-checked-state.ts +324 -0
  333. package/src/orchestrator/templates/specs-report-path-sanitize-reporter.ts +142 -0
  334. package/src/orchestrator/templates/specs-url-assert.ts +101 -28
  335. package/src/utils/selector-types.ts +54 -5
@@ -1,3 +1,14 @@
1
- await expect({{> locator}}.getByRole('columnheader', { name: '{{columnName}}', includeHidden: true })).toBeVisible();
1
+ {{!-- Rooted at `page`, like the two lines below it. This line used to render `{{> locator}}` from
2
+ step data that carries no strategy/value at all, so it emitted `page.locator('')` — an empty
3
+ CSS selector, which Playwright refuses to parse. The step could not run.
4
+
5
+ Block-scoped: the column-index const is named after the [Reference], so two steps naming the
6
+ same column — or two non-ASCII references, which sanitize down to the same identifier — would
7
+ redeclare it in one scenario and the spec would not compile. The braces make each step's
8
+ binding its own.
9
+
10
+ Not dialog/frame scoped: this step form carries no selector entry, so there is nothing for
11
+ `pageRoot` to read. A column assertion inside a modal needs the row-scoped form. --~}}{ await expect(page.getByRole('columnheader', { name: '{{columnName}}', includeHidden: true })).toBeVisible();
2
12
  const {{columnIndexVar}} = (await page.getByRole('columnheader', { includeHidden: true }).filter({ visible: true }).allTextContents()).findIndex(h => h.includes('{{columnName}}'));
3
- await expect(page.getByRole('row', { includeHidden: true }).filter({ visible: true }).nth({{rowNth}}).getByRole('cell', { includeHidden: true }).filter({ visible: true }).nth({{columnIndexVar}})).toHaveText('{{dataValue}}');
13
+ expect({{columnIndexVar}}, 'Column "{{escapeQuotes columnName}}" is not among the visible headers').toBeGreaterThanOrEqual(0);
14
+ await expect(page.getByRole('row', { includeHidden: true }).filter({ visible: true }).nth({{rowNth}}).getByRole('cell', { includeHidden: true }).filter({ visible: true }).nth({{columnIndexVar}})).toHaveText('{{dataValue}}'); }
@@ -1 +1 @@
1
- await expect({{> locator}}).toContainText('{{expectedText}}');
1
+ await expect({{> locator}}).toContainText('{{escapeQuotes expectedText}}');
@@ -1,5 +1,5 @@
1
1
  {{#if name}}
2
- await expect(page.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeDisabled();
2
+ await expect(page.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{> locator-nth}}).toBeDisabled();
3
3
  {{else}}
4
- await expect(page.getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeDisabled();
4
+ await expect(page.getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{> locator-nth}}).toBeDisabled();
5
5
  {{/if}}
@@ -1 +1 @@
1
- await expect(page.getByText('{{escapeQuotes selectorRef}}').filter({ hasText: {{#if (eq selectorValue "")}}/.*{{escapeRegex dataValue}}$/{{else}}'{{escapeQuotes dataValue}}'{{/if}} }){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeDisabled();
1
+ await expect(page.getByText('{{escapeQuotes selectorRef}}').filter({ hasText: {{#if (eq selectorValue "")}}/.*{{escapeRegex dataValue}}$/{{else}}'{{escapeQuotes dataValue}}'{{/if}} }){{> locator-nth}}).toBeDisabled();
@@ -1 +1 @@
1
- await expect({{> locator}}).toHaveText('{{expectedText}}');
1
+ await expect({{> locator}}).toHaveText('{{escapeQuotes expectedText}}');
@@ -1,5 +1,5 @@
1
1
  {{#if name}}
2
- await expect(page.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeHidden();
2
+ await expect(page.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{> locator-nth}}).toBeHidden();
3
3
  {{else}}
4
- await expect(page.getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeHidden();
4
+ await expect(page.getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{> locator-nth}}).toBeHidden();
5
5
  {{/if}}
@@ -1 +1 @@
1
- await expect(page.getByText('{{escapeQuotes selectorRef}}').filter({ hasText: {{#if (eq selectorValue "")}}/.*{{escapeRegex dataValue}}$/{{else}}'{{escapeQuotes dataValue}}'{{/if}} }){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeHidden();
1
+ await expect(page.getByText('{{escapeQuotes selectorRef}}').filter({ hasText: {{#if (eq selectorValue "")}}/.*{{escapeRegex dataValue}}$/{{else}}'{{escapeQuotes dataValue}}'{{/if}} }){{> locator-nth}}).toBeHidden();
@@ -1 +1,2 @@
1
- await expect({{> locator}}).toBeChecked({ checked: false });
1
+ {{!-- Inverse of checked-assertion.hbs — see there (#675). --}}
2
+ await expectCheckedState({{> locator}}, false, '{{escapeQuotes selectorRef}}'{{> checked-opts cls=checkedClass attr=checkedAttribute}});
@@ -1,7 +1,16 @@
1
- {{#if (or (eq role 'checkbox') (eq role 'radio') (eq role 'switch'))}}
1
+ {{#if (or checkedClass checkedAttribute)}}
2
+ {{!-- A DECLARED signal (issue #730) short-circuits the whole ARIA probe below: the author
3
+ reached for `selected:`/`checked:` precisely because this component publishes no aria-selected,
4
+ no aria-current and no checked state — a Tailwind card/tab picker. Probing for them first would
5
+ just spend the budget confirming they are absent. --}}
6
+ await expectCheckedState({{> locator}}, true, '{{escapeQuotes selectorRef}}'{{> checked-opts cls=checkedClass attr=checkedAttribute}});
7
+ {{else if (or (eq role 'checkbox') (eq role 'radio') (eq role 'switch'))}}
2
8
  {{!-- Form control: "selected" is the checked state, never aria-selected. A native checkbox/radio
3
9
  exposes no aria-selected attribute — its state lives in the `checked` property. --}}
4
- await expect({{> locator}}).toBeChecked();
10
+ {{!-- "selected" on a form control IS the checked state — routed through expectCheckedState so an
11
+ aria-pressed toggle does not throw "Not a checkbox or radio button" (#675), and so the selector
12
+ that declares role: switch while the element publishes aria-pressed still reads correctly. --}}
13
+ await expectCheckedState({{> locator}}, true, '{{escapeQuotes selectorRef}}');
5
14
  {{else}}
6
15
  {{!-- Other/unknown role (tab, option, or a text/testid/css selector): probe at runtime. An ARIA
7
16
  single-select widget exposes aria-selected → assert it; anything without it falls back to the
@@ -17,7 +26,9 @@ await expect(el).toHaveAttribute('aria-selected', 'true');
17
26
  role=tab owns the stronger signal. --}}
18
27
  await expect(el).toHaveAttribute('aria-current', /^(?!false$)./);
19
28
  } else {
20
- await expect(el).toBeChecked();
29
+ {{!-- Last resort, same as the -with-filter twin: neither aria-selected nor aria-current, so read the
30
+ checked/pressed state through the #675-safe path rather than toBeChecked(). --}}
31
+ await expectCheckedState(el, true, '{{escapeQuotes selectorRef}}');
21
32
  }
22
33
  }
23
34
  {{/if}}
@@ -1,6 +1,12 @@
1
1
  {{!-- Filter contract shared with the visible/hidden/contains siblings (has-text-value):
2
2
  substring by default, `exact: true` in the selector YAML anchors. --}}
3
3
  {{#if selectedProbe}}
4
+ {{#if (or checkedClass checkedAttribute)}}
5
+ {{!-- Declared class signal (issue #730) — short-circuits the ARIA probe, exactly as the unfiltered
6
+ twin selected-assertion.hbs does. Without this branch the filtered form stayed broken while the
7
+ unfiltered one worked, which is the inconsistency #730's second field report ran into. --}}
8
+ await expectCheckedState({{> locator-base}}.filter({ hasText: {{> has-text-value dataValue=dataValue exact=exact}} }){{> locator-nth}}, true, '{{escapeQuotes selectorRef}}'{{> checked-opts cls=checkedClass attr=checkedAttribute}});
9
+ {{else}}
4
10
  { const el = {{> locator-base}}.filter({ hasText: {{> has-text-value dataValue=dataValue exact=exact}} }){{> locator-nth}};
5
11
  if ((await el.getAttribute('aria-selected')) !== null) {
6
12
  await expect(el).toHaveAttribute('aria-selected', 'true');
@@ -12,9 +18,14 @@ await expect(el).toHaveAttribute('aria-selected', 'true');
12
18
  role=tab owns the stronger signal. --}}
13
19
  await expect(el).toHaveAttribute('aria-current', /^(?!false$)./);
14
20
  } else {
15
- await expect(el).toBeChecked();
21
+ {{!-- Last resort: the element publishes neither aria-selected nor aria-current. toBeChecked() used
22
+ to sit here and threw "Not a checkbox or radio button" on an aria-pressed toggle (#675). --}}
23
+ await expectCheckedState(el, true, '{{escapeQuotes selectorRef}}');
16
24
  }
17
25
  }
26
+ {{/if}}
27
+ {{else}}{{#if isCheckedAssertion}}
28
+ await expectCheckedState({{> locator-base}}.filter({ hasText: {{> has-text-value dataValue=dataValue exact=exact}} }){{> locator-nth}}, {{checkedWant}}, '{{escapeQuotes selectorRef}}'{{> checked-opts cls=checkedClass attr=checkedAttribute}});
18
29
  {{else}}
19
30
  await expect({{> locator-base}}.filter({ hasText: {{> has-text-value dataValue=dataValue exact=exact}} }){{> locator-nth}}).{{{stateAssertion}}};
20
- {{/if}}
31
+ {{/if}}{{/if}}
@@ -1,9 +1,9 @@
1
1
  {{#if name}}
2
2
  {{#if dataValue}}
3
- await expect(page.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeVisible();
3
+ await expect(page.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{> locator-nth}}).toBeVisible();
4
4
  {{else}}
5
- await expect(page.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeVisible();
5
+ await expect(page.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }){{> locator-nth}}).toBeVisible();
6
6
  {{/if}}
7
7
  {{else}}
8
- await expect(page.getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeVisible();
8
+ await expect(page.getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{> locator-nth}}).toBeVisible();
9
9
  {{/if}}
@@ -1 +1 @@
1
- await expect(page.getByText('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeVisible();
1
+ await expect(page.getByText('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{> locator-nth}}).toBeVisible();
@@ -1 +1 @@
1
- await expect(page.getByText('{{escapeQuotes selectorValue}}').filter({ hasText: /.*{{escapeRegex dataValue}}$/ }){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeVisible();
1
+ await expect(page.getByText('{{escapeQuotes selectorValue}}').filter({ hasText: /.*{{escapeRegex dataValue}}$/ }){{> locator-nth}}).toBeVisible();
@@ -0,0 +1,8 @@
1
+ {{!-- The `checked:`/`selected:` signal from selectors.yaml, appended as the options argument of
2
+ setCheckedState/expectCheckedState (issue #730). One partial so all nine call sites carry it
3
+ identically, and so a site that declares nothing stays BYTE-IDENTICAL to before — the whole
4
+ argument disappears rather than becoming an empty object. Takes `cls`/`attr` so a caller can
5
+ pass a signal resolved from a DIFFERENT entry than the one it spread (table-action-in-row
6
+ spreads the TABLE but acts on the row's own control). A selector declares one or the other;
7
+ the resolver refuses both, so the two branches never collide here. --~}}
8
+ {{~#if cls}}, { checkedClass: '{{escapeQuotes cls}}' }{{else if attr}}, { checkedAttribute: '{{escapeQuotes attr}}' }{{/if~}}
@@ -1 +1 @@
1
- {{#if (gte nth 0)}}.nth({{nth}}){{/if}}
1
+ {{#if (gte nth 0)}}.nth({{nth}}){{else if (eq nth -1)}}.last(){{/if}}
@@ -98,6 +98,16 @@ test.describe.serial('{{featureName}}', () => {
98
98
  });
99
99
  {{/if}}
100
100
 
101
+ {{#if beforeAll}}
102
+ {{beforeAll}}
103
+
104
+ {{/if}}
105
+ {{#if afterAll}}
106
+ // User @afterAll runs BEFORE the built-in close below (Playwright runs afterAll hooks in registration
107
+ // order), so the shared page is still open for its cleanup steps.
108
+ {{afterAll}}
109
+
110
+ {{/if}}
101
111
  test.afterAll(async () => {
102
112
  await page.close();
103
113
  await context.close();
@@ -112,18 +122,10 @@ test.describe.serial('{{featureName}}', () => {
112
122
  {{#if screenshotOnFailure}}
113
123
  test.use({ screenshotOnFailure: true });
114
124
 
115
- {{/if}}
116
- {{#if beforeAll}}
117
- {{beforeAll}}
118
-
119
125
  {{/if}}
120
126
  {{#if afterEach}}
121
127
  {{afterEach}}
122
128
 
123
- {{/if}}
124
- {{#if afterAll}}
125
- {{afterAll}}
126
-
127
129
  {{/if}}
128
130
  {{#if authGroups}}
129
131
  {{#each authGroups}}
@@ -2,7 +2,7 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { ParsedFeature, ParsedScenario, ParsedStep } from '../gherkin-parser';
4
4
  import { StepMapper } from './step-mapper';
5
- import { TestGeneratorAdapter, adapterRegistry } from './adapters';
5
+ import { TestGeneratorAdapter, adapterRegistry, HookStep } from './adapters';
6
6
  import { transformToRuntimeData } from './utils/runtime-data-transformer';
7
7
  import { DiagnosticsCollector } from './diagnostics';
8
8
  import { parseResetTier, DEFAULT_RESET_TIER } from './reset-tier';
@@ -12,6 +12,9 @@ import { readCapabilities } from '../../harness/capability';
12
12
  import { parseManualComments } from '../../exporters/scenario-merger';
13
13
  import { findSyntaxError } from './utils/spec-syntax-check';
14
14
 
15
+ /** One capability-injected precondition statement (`@api:`/`@query:` → `testData.bind(name, …)`). */
16
+ type CapabilityPrecondition = { comment?: string; code: string; boundVars?: string[] };
17
+
15
18
  /**
16
19
  * Filter base scenario steps for @extend: only keep Given→When steps.
17
20
  * Skips Then and any And/But that follow Then (since And/But inherit
@@ -346,7 +349,7 @@ export class CodeGenerator {
346
349
  // helper into every UI spec — including the many that touch no URL at all. Hence the body is
347
350
  // rendered BEFORE the imports. Importing exactly the names used keeps the import honest; the
348
351
  // helper file itself is synced only when something needs it (an api-only unit carries none).
349
- const urlAssertImports = ['urlMatches', 'joinPath', 'expectPage'].filter((n) => testCode.includes(`${n}(`)).join(', ');
352
+ const urlAssertImports = ['urlMatches', 'joinPath', 'expectPage', 'waitForPage'].filter((n) => testCode.includes(`${n}(`)).join(', ');
350
353
  if (urlAssertImports) this.syncGeneratedHelper(outputDir, 'url-assert.ts', 'specs-url-assert.ts');
351
354
 
352
355
  // Same body-first rule for the runtime regex-escape helper: only a spec whose
@@ -354,7 +357,16 @@ export class CodeGenerator {
354
357
  const regexEscapeImport = /\bescapeRegExp\(/.test(testCode);
355
358
  if (regexEscapeImport) this.syncGeneratedHelper(outputDir, 'regex-escape.ts', 'specs-regex-escape.ts');
356
359
 
357
- const imports = this.adapter.renderImports({ runtimeData: this.options.runtimeData, basePath, needsCleanupImport, needsDb, needsApi, needsMock, urlAssertImports, regexEscapeImport });
360
+ // Same body-first rule for the checked-state helper (#675): only a spec that actually compiled a
361
+ // `check`/`uncheck` step imports (and syncs) it, so the many specs that touch no toggle carry
362
+ // neither the import nor the file.
363
+ // Import exactly the names the body used, like urlAssertImports above — the action half and the
364
+ // assertion half of #675 are independent, and most specs need only one of them.
365
+ const checkedStateImports = ['setCheckedState', 'expectCheckedState']
366
+ .filter((n) => testCode.includes(`${n}(`)).join(', ');
367
+ if (checkedStateImports) this.syncGeneratedHelper(outputDir, 'checked-state.ts', 'specs-checked-state.ts');
368
+
369
+ const imports = this.adapter.renderImports({ runtimeData: this.options.runtimeData, basePath, needsCleanupImport, needsDb, needsApi, needsMock, urlAssertImports, regexEscapeImport, checkedStateImports });
358
370
 
359
371
  // Combine and collapse any runs of 3+ newlines down to 2 (one blank line max)
360
372
  const raw = `${imports}\n\n${testCode}`;
@@ -482,10 +494,10 @@ export class CodeGenerator {
482
494
  * Each capability owns its annotation codegen via the SPI (`preconditionCodegen`); the compiler
483
495
  * just composes + indents the returned statements (R4).
484
496
  */
485
- private capabilityPreconditions(scenario: ParsedScenario): Array<{ comment?: string; code: string; boundVars?: string[] }> {
497
+ private capabilityPreconditions(scenario: ParsedScenario): CapabilityPrecondition[] {
486
498
  discoverAndRegisterCapabilities();
487
499
  const tags = scenario.tags || [];
488
- const out: Array<{ comment?: string; code: string; boundVars?: string[] }> = [];
500
+ const out: CapabilityPrecondition[] = [];
489
501
  for (const cap of capabilityRegistry.all()) {
490
502
  if (!cap.preconditionCodegen) continue;
491
503
  out.push(...cap.preconditionCodegen({ tags, screenName: this.queryScreenName, cwd: process.cwd() }));
@@ -527,6 +539,11 @@ export class CodeGenerator {
527
539
  if (this.options.runtimeData) {
528
540
  this.syncGeneratedHelper(outputDir, 'test-data.ts', 'specs-test-data.ts');
529
541
  }
542
+ // The Playwright reporter that keeps machine paths out of the committed JSON result — refreshed
543
+ // like base.ts so an existing project picks it up on its next generate (mobile has no Playwright).
544
+ if (this.adapter.frameworkName !== 'appium') {
545
+ this.syncGeneratedHelper(outputDir, 'report-path-sanitize-reporter.ts', 'specs-report-path-sanitize-reporter.ts');
546
+ }
530
547
  }
531
548
 
532
549
  private async generateTestCode(feature: ParsedFeature): Promise<string> {
@@ -600,6 +617,26 @@ export class CodeGenerator {
600
617
  // never skip-cascades). `@serial` opts into the shared-page serial suite for a stateful sequence.
601
618
  const isParallel = !(feature.tags || []).includes('@serial');
602
619
 
620
+ // A `@beforeAll` hook runs before every test of the describe, so the `{{name}}` its `@api:`/`@query:`
621
+ // preconditions bind are readable from EVERY scenario of the feature. Register the bound heads
622
+ // before the scenarios map: the per-scenario captured set is cleared at each scenario, and without
623
+ // this a scenario's `{{login.body.token}}` compiles to a dead literal. `@afterEach`/`@afterAll` run
624
+ // after the tests — their bindings stay hook-local. The set is per feature: clear it FIRST, before
625
+ // the Background maps, or the previous feature's bindings would leak into this Background.
626
+ // Whether the Background itself may read them depends on where it runs: per-scenario (parallel)
627
+ // Background is a beforeEach and runs after the user beforeAll → register before it; @serial
628
+ // Background is inlined into the built-in beforeAll that runs BEFORE the user hook → register
629
+ // only after it has mapped, so a serial Background ref stays an unresolved literal, as it should.
630
+ this.stepMapper.clearFeatureCaptured();
631
+ const registerBeforeAllBindings = (): void => {
632
+ const beforeAllHook = hookScenarios.get('beforeAll');
633
+ if (!beforeAllHook) return;
634
+ for (const p of this.capabilityPreconditions(beforeAllHook)) {
635
+ for (const head of p.boundVars || []) this.stepMapper.registerFeatureCaptured(head);
636
+ }
637
+ };
638
+ if (isParallel) registerBeforeAllBindings();
639
+
603
640
  // Generate background if exists
604
641
  let background: string | undefined;
605
642
  let backgroundSteps: Array<{ comment?: string; code: string }> | undefined;
@@ -642,6 +679,8 @@ export class CodeGenerator {
642
679
  }
643
680
  }
644
681
 
682
+ if (!isParallel) registerBeforeAllBindings();
683
+
645
684
  // Generate hook blocks
646
685
  const beforeAll = hookScenarios.has('beforeAll')
647
686
  ? await this.generateHook(hookScenarios.get('beforeAll')!, 'beforeAll')
@@ -858,6 +897,21 @@ export class CodeGenerator {
858
897
  }
859
898
  if (iosPart) iosBundleId = iosPart;
860
899
  }
900
+ // SG-W029 — the appium adapter reached codegen with NO usable app package. Both ways in are
901
+ // silent: a feature with no `Path:` line at all, and a web-style route `Path: /applied-jobs`
902
+ // that the guard above deliberately rejects. Before this code that combination quietly emitted
903
+ // Mocha's `before()` — once per FILE — so Background ran once and every scenario shared one
904
+ // continuous app session, leaking tab/scroll/login state forward; the `@reset:` tier ladder was
905
+ // dropped along with it, making the tag inert. The generated spec now falls back to the session
906
+ // capability instead, but the feature file is still missing something it should declare, and
907
+ // the run throws if wdio.conf cannot supply it either — so it is reported, not assumed.
908
+ // A warning rather than an error on SG-W004's reasoning: the run usually still works (wdio.conf
909
+ // carries the id), and `--strict` already turns the channel non-zero for anyone who wants a gate.
910
+ if (this.adapter.frameworkName === 'appium' && !appPackage && !iosBundleId) {
911
+ this.diagnostics.add('SG-W029',
912
+ `this mobile feature declares no app-package \`Path:\`${feature.path ? ` (\`${feature.path}\` is a web route, not a package)` : ''}, so the compiler cannot name the app to reset between scenarios. The spec now resolves the app id from the session capability (APP_PACKAGE / IOS_BUNDLE_ID in wdio.conf) and THROWS at run time if that is absent too. Declare it on the feature instead: \`Path: com.example.app/.MainActivity\` (or \`<androidPkg>/<activity> | <iosBundleId>\` for a dual-id feature).`,
913
+ { feature: feature.name });
914
+ }
861
915
 
862
916
  // Use adapter to render the complete test file structure
863
917
  return this.adapter.renderTestFile({
@@ -920,23 +974,66 @@ export class CodeGenerator {
920
974
  });
921
975
  }
922
976
 
977
+ /**
978
+ * Hook block (`@beforeAll` / `@afterEach` / `@afterAll`). Shares prepare/finalize with ordinary
979
+ * scenarios, so an `@api:` / `@query:` precondition on the hook runs INSIDE it (bind {{name}}) and
980
+ * the hook's own `{{name.*}}` refs resolve at runtime — a hook used to map only its steps, so
981
+ * `@beforeAll @api:login` compiled to a beforeAll with no api.call and a testData.get() of nothing.
982
+ * Not applied to hooks: `@cases` (a hook has no dataset rows) and `@extend` (no base scenario) —
983
+ * warned and ignored; `@auth:` grouping and the mock epilogue are scenario-only. `@concurrent:N`
984
+ * IS valid: the api capability reads it off the same tags.
985
+ *
986
+ * Failure collection: a failing hook skips every test in its describe, so one run must report
987
+ * EVERY failed check, not just the first. The hook template wraps each mapped step in try/catch:
988
+ * an assertion step (effective keyword Then) records its failure and lets the next step run; an
989
+ * action step (Given/When) records it and fails the hook right there, since the steps after it
990
+ * would run on state the failed action never produced. The block ends with a guard that fails once
991
+ * with the full list. `expect.soft` is not an option here — a soft failure marks the owning test,
992
+ * and a hook has none.
993
+ */
923
994
  private async generateHook(
924
995
  scenario: ParsedScenario,
925
996
  hookType: 'beforeAll' | 'afterEach' | 'afterAll'
926
997
  ): Promise<string> {
927
- const steps: Array<{ comment?: string; code: string }> = [];
928
- for (const step of scenario.steps) {
998
+ const unsupported = scenario.tags.filter((t) => t.startsWith('@cases:') || t.startsWith('@extend:'));
999
+ for (const tag of unsupported) {
1000
+ console.warn(`⚠ @${hookType} hook "${scenario.name}": ${tag} is not supported on hooks — ignored`);
1001
+ }
1002
+ const hookScenario: ParsedScenario = {
1003
+ ...scenario,
1004
+ tags: scenario.tags.filter((t) => !unsupported.includes(t)),
1005
+ extendsName: undefined,
1006
+ };
1007
+ const { preconditions } = this.prepareScenarioBody(hookScenario, hookScenario.steps);
1008
+
1009
+ const steps: HookStep[] = [];
1010
+ let effectiveKeyword = 'Given'; // And/But inherit the last Given/When/Then
1011
+ for (const step of hookScenario.steps) {
1012
+ const keyword = step.keyword.trim();
1013
+ if (keyword === 'Given' || keyword === 'When' || keyword === 'Then') effectiveKeyword = keyword;
929
1014
  const mapped = await Promise.resolve(this.stepMapper.mapStep(step));
1015
+ // A step whose code declares a top-level binding (`const frame = …`, `let tableRow = …`) is read
1016
+ // by the steps AFTER it — a try/catch would scope the binding away from them. Such a step runs
1017
+ // unwrapped and throws on failure exactly as before.
1018
+ if (/^(const|let|var)\s/m.test(mapped.code)) {
1019
+ steps.push({ comment: mapped.comment, code: this.indentCode(mapped.code, 4) });
1020
+ continue;
1021
+ }
930
1022
  steps.push({
931
1023
  comment: mapped.comment,
932
- code: this.indentCode(mapped.code, 4),
1024
+ code: this.indentCode(mapped.code, 6),
1025
+ wrap: true,
1026
+ stopOnFailure: effectiveKeyword !== 'Then',
1027
+ label: JSON.stringify(mapped.comment || step.text || 'hook step'),
933
1028
  });
934
1029
  }
1030
+ this.finalizeScenarioBody(steps, preconditions);
1031
+ const collect = steps.some((s) => s.wrap);
935
1032
 
936
1033
  const renderMap = {
937
- beforeAll: () => this.adapter.renderBeforeAll({ steps }),
938
- afterEach: () => this.adapter.renderAfterEach({ steps }),
939
- afterAll: () => this.adapter.renderAfterAll({ steps }),
1034
+ beforeAll: () => this.adapter.renderBeforeAll({ steps, collect }),
1035
+ afterEach: () => this.adapter.renderAfterEach({ steps, collect }),
1036
+ afterAll: () => this.adapter.renderAfterAll({ steps, collect }),
940
1037
  };
941
1038
  return renderMap[hookType]();
942
1039
  }
@@ -956,6 +1053,63 @@ export class CodeGenerator {
956
1053
  ].join('\n');
957
1054
  }
958
1055
 
1056
+ /**
1057
+ * Scenario-body preparation shared by ordinary scenarios and hook blocks — runs BEFORE the steps
1058
+ * are mapped (the step mapper reads what it sets): scenario context, `@cases` row-column capture,
1059
+ * and the capability preconditions (`@api:<name>` / `@query:<name>` → bind {{name}}) whose bound
1060
+ * `{{name.*}}` refs exist only at runtime, so they are registered as captured and compile to a
1061
+ * runtime get() instead of a compile-time YAML lookup that would fail. Pair with
1062
+ * finalizeScenarioBody(), which prepends the preconditions once the steps are mapped.
1063
+ */
1064
+ private prepareScenarioBody(
1065
+ scenario: ParsedScenario,
1066
+ stepsToMap: ParsedStep[],
1067
+ ): { preconditions: CapabilityPrecondition[]; casesDataset?: string } {
1068
+ this.stepMapper.setScenarioContext(stepsToMap);
1069
+
1070
+ // Data-driven (@cases): the scenario's {{col}} refs are dataset *row columns* that
1071
+ // exist only at runtime — register them as captured so they resolve to a runtime
1072
+ // get() (→ rowData.get) instead of compile-time YAML lookup.
1073
+ const casesTag = scenario.tags.find((t) => t.startsWith('@cases:'));
1074
+ const casesDataset = casesTag ? casesTag.slice('@cases:'.length).trim() : undefined;
1075
+ if (casesDataset) {
1076
+ const refs = new Set<string>();
1077
+ for (const st of stepsToMap) {
1078
+ for (const m of (st.text || '').matchAll(/\{\{\s*([\w.]+)\s*\}\}/g)) refs.add(m[1]);
1079
+ }
1080
+ for (const r of refs) this.stepMapper.registerCaptured(r);
1081
+ }
1082
+
1083
+ const preconditions = this.capabilityPreconditions(scenario);
1084
+ const boundVars = preconditions.flatMap((p) => p.boundVars || []);
1085
+ if (boundVars.length) {
1086
+ for (const st of stepsToMap) {
1087
+ for (const mt of (st.text || '').matchAll(/\{\{\s*([^}]+?)\s*\}\}/g)) {
1088
+ const head = mt[1].split(/[.[]/)[0];
1089
+ if (boundVars.includes(head)) this.stepMapper.registerCaptured(mt[1]);
1090
+ }
1091
+ }
1092
+ }
1093
+ return { preconditions, casesDataset };
1094
+ }
1095
+
1096
+ /**
1097
+ * Capability preconditions run BEFORE the body's own steps — prepend them (indented) once the
1098
+ * steps are mapped. Second half of the prepareScenarioBody() pair.
1099
+ */
1100
+ private finalizeScenarioBody(steps: Array<{ comment?: string; code: string }>, preconditions: CapabilityPrecondition[]): void {
1101
+ if (preconditions.length) {
1102
+ steps.unshift(...preconditions.map((p) => ({ comment: p.comment, code: this.indentCode(p.code, 4) })));
1103
+ }
1104
+ }
1105
+
1106
+ /**
1107
+ * Step-boundary trace (#673): scenario name → the ordered step-comment texts emitted for it.
1108
+ * Read by script-check to attribute emitted code to steps structurally; see the populating
1109
+ * site in generateScenario for why comment adjacency cannot do the job.
1110
+ */
1111
+ readonly stepTrace = new Map<string, string[]>();
1112
+
959
1113
  private async generateScenario(
960
1114
  scenario: ParsedScenario,
961
1115
  hasBackground: boolean,
@@ -987,35 +1141,7 @@ export class CodeGenerator {
987
1141
  warnMultipleAuthTags(scenario.tags, `scenario '${scenario.name}'`);
988
1142
  }
989
1143
 
990
- // Set scenario context for path variable resolution (full merged list)
991
- this.stepMapper.setScenarioContext(stepsToMap);
992
-
993
- // Data-driven (@cases): the scenario's {{col}} refs are dataset *row columns* that
994
- // exist only at runtime — register them as captured so they resolve to a runtime
995
- // get() (→ rowData.get) instead of compile-time YAML lookup.
996
- const casesTag = scenario.tags.find((t) => t.startsWith('@cases:'));
997
- const casesDataset = casesTag ? casesTag.slice('@cases:'.length).trim() : undefined;
998
- if (casesDataset) {
999
- const refs = new Set<string>();
1000
- for (const st of stepsToMap) {
1001
- for (const m of (st.text || '').matchAll(/\{\{\s*([\w.]+)\s*\}\}/g)) refs.add(m[1]);
1002
- }
1003
- for (const r of refs) this.stepMapper.registerCaptured(r);
1004
- }
1005
-
1006
- // Capability preconditions (db `@query:<name>` → bind {{name}}) are owned by the capability via
1007
- // the SPI. Their bound `{{name.*}}` vars exist only at runtime → register them as captured so
1008
- // they resolve to a runtime get() instead of a compile-time YAML lookup that would fail.
1009
- const preconditions = this.capabilityPreconditions(scenario);
1010
- const boundVars = preconditions.flatMap((p) => p.boundVars || []);
1011
- if (boundVars.length) {
1012
- for (const st of stepsToMap) {
1013
- for (const mt of (st.text || '').matchAll(/\{\{\s*([^}]+?)\s*\}\}/g)) {
1014
- const head = mt[1].split(/[.[]/)[0];
1015
- if (boundVars.includes(head)) this.stepMapper.registerCaptured(mt[1]);
1016
- }
1017
- }
1018
- }
1144
+ const { preconditions, casesDataset } = this.prepareScenarioBody(scenario, stepsToMap);
1019
1145
 
1020
1146
  const steps: Array<{ comment?: string; code: string }> = [];
1021
1147
 
@@ -1046,11 +1172,7 @@ export class CodeGenerator {
1046
1172
  }
1047
1173
  }
1048
1174
 
1049
- // Capability preconditions (db `@query:<name>` / api `@api:<name>` → bind {{name}}; computed
1050
- // above) run BEFORE the scenario's own steps — prepend them, indenting the capability statements.
1051
- if (preconditions.length) {
1052
- steps.unshift(...preconditions.map((p) => ({ comment: p.comment, code: this.indentCode(p.code, 4) })));
1053
- }
1175
+ this.finalizeScenarioBody(steps, preconditions);
1054
1176
 
1055
1177
  // Mock scenario epilogue. (a) EVERY path: the fail-on-unmocked verdict — the guard
1056
1178
  // records+aborts unmatched requests (it must not throw inside a route handler:
@@ -1080,6 +1202,15 @@ export class CodeGenerator {
1080
1202
  const tags = formatPassThroughTags(extractPassThroughTags(scenario.tags, featureTags), this.adapter.frameworkName);
1081
1203
 
1082
1204
  // Use adapter to render scenario
1205
+ // #673 — record which `//` lines of this scenario are STEP markers, in order. A generated
1206
+ // spec has no textual way to tell a step comment from a template body comment (both land at
1207
+ // the same indent), so script-check attributed code to steps by comment ADJACENCY: any two
1208
+ // adjacent comments gave the first one an empty region → a false hollow BYPASS. Three shipped
1209
+ // templates emit consecutive `//` lines (user-login-todo 6, clear-database 4,
1210
+ // alert-text-assertion 2), as do `// [from @steps:…]` headers, so the check fired on sungen's
1211
+ // own output. The trace makes the attribution structural instead of positional.
1212
+ this.stepTrace.set(scenario.name, steps.map((s) => s.comment).filter((c): c is string => !!c));
1213
+
1083
1214
  const rendered = this.adapter.renderScenario({
1084
1215
  scenarioName: scenario.name,
1085
1216
  steps,