@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
@@ -10,6 +10,7 @@ import { clipNoteField } from '../playwright-report-parser';
10
10
  import { variantState } from './build';
11
11
  import { coverageSummary, itemModeLabel, itemResultLabel } from './render-xlsx';
12
12
  import { MatrixModel } from './types';
13
+ import { formatTestOutcome, resultTotals } from './result-totals';
13
14
 
14
15
  const HEADERS = [
15
16
  'Level', 'ID', 'Target', 'Test Intent / Condition', 'Category', 'Priority',
@@ -33,7 +34,8 @@ export function renderMatrixCsv(model: MatrixModel): string {
33
34
  const BOM = '\ufeff'; // Excel compatibility (Vietnamese/Japanese content)
34
35
  const lines: string[] = [];
35
36
  lines.push(`# Test Case & Coverage Matrix — ${model.unit}`);
36
- lines.push(`# delivery items: ${model.manifest.itemCount} · coverage variants: ${model.manifest.variantCount} · state: ${model.manifest.approvalState}`);
37
+ // Two different facts, two different words: the map's design REVIEW state, and the runs' OUTCOME.
38
+ lines.push(`# delivery items: ${model.manifest.itemCount} · coverage variants: ${model.manifest.variantCount} · review: ${model.manifest.approvalState} (design) · test outcome: ${formatTestOutcome(resultTotals(model.items))}`);
37
39
  lines.push(HEADERS.map(esc).join(','));
38
40
 
39
41
  for (const item of model.items) {
@@ -13,6 +13,7 @@
13
13
  import * as ExcelJS from 'exceljs';
14
14
  import { addOverviewSheet } from '../xlsx-report-builder';
15
15
  import { DeliveryItem, MatrixModel } from './types';
16
+ import { resultTotals } from './result-totals';
16
17
 
17
18
  /** One detail sheet: its tab name and the items projected onto it. */
18
19
  export interface SheetProjection {
@@ -29,16 +30,10 @@ export interface SheetProjection {
29
30
  function summaryOf(unit: string, items: DeliveryItem[]): {
30
31
  screen: string; total: number; passed: number; failed: number; pending: number; na: number;
31
32
  } {
32
- const sum = (k: 'passed' | 'failed' | 'notRun' | 'na'): number =>
33
- items.reduce((a, i) => a + i.resultCounts[k], 0);
34
- return {
35
- screen: unit,
36
- total: items.reduce((a, i) => a + i.variants.length, 0),
37
- passed: sum('passed'),
38
- failed: sum('failed'),
39
- pending: sum('notRun'),
40
- na: sum('na'),
41
- };
33
+ // The Overview form has no Blocked column: a blocked variant stays inside `Remain` (total − passed − N/A),
34
+ // exactly as the sheet's live band counts it.
35
+ const t = resultTotals(items);
36
+ return { screen: unit, total: t.total, passed: t.passed, failed: t.failed, pending: t.pending, na: t.na };
42
37
  }
43
38
 
44
39
  export function addMatrixOverviewSheet(
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Test-outcome totals of a matrix — the five result buckets plus the variant total.
3
+ *
4
+ * "Review state" (`manifest.approvalState`: proposed/approved/draft — a DESIGN fact about the
5
+ * delivery map) and "test outcome" (how the runs went) used to share one header word, `state:`, so
6
+ * a client read `state: approved` next to failing rows and asked which was true. Every surface that
7
+ * prints totals — CSV header, XLSX manifest, Overview roll-up, summary band, CLI — sums the SAME
8
+ * five buckets from here, so the numbers cannot disagree with each other.
9
+ */
10
+ import { DeliveryItem } from './types';
11
+
12
+ export interface ResultTotals {
13
+ total: number;
14
+ passed: number;
15
+ failed: number;
16
+ blocked: number;
17
+ pending: number; // not run yet (a variant with no result)
18
+ na: number; // intentionally skipped — leaves the denominator
19
+ }
20
+
21
+ /** Sum the derived per-item roll-ups (Gate F: never entered independently). total = Σ variants. */
22
+ export function resultTotals(items: DeliveryItem[]): ResultTotals {
23
+ const sum = (k: keyof DeliveryItem['resultCounts']): number => items.reduce((a, i) => a + i.resultCounts[k], 0);
24
+ return {
25
+ total: items.reduce((a, i) => a + i.variants.length, 0),
26
+ passed: sum('passed'),
27
+ failed: sum('failed'),
28
+ blocked: sum('blocked'),
29
+ pending: sum('notRun'),
30
+ na: sum('na'),
31
+ };
32
+ }
33
+
34
+ /** `72 passed / 10 failed / 0 blocked / 3 pending / 1 n/a` — the five buckets, always all five, in this order. */
35
+ export function formatTestOutcome(t: ResultTotals): string {
36
+ return `${t.passed} passed / ${t.failed} failed / ${t.blocked} blocked / ${t.pending} pending / ${t.na} n/a`;
37
+ }
@@ -6,6 +6,7 @@
6
6
  import * as ExcelJS from 'exceljs';
7
7
  import { BLACK, ARIAL_FONT, allBordersBlack, WHITE } from '../xlsx-report-builder';
8
8
  import { DeliveryItem } from './types';
9
+ import { resultTotals } from './result-totals';
9
10
  import { headerCell } from './xlsx-cells';
10
11
 
11
12
  /** The counted data area — the template's own range, so a QA-appended row counts. */
@@ -37,13 +38,7 @@ const BAND_LABELS = ['Total TCs', 'Passed', 'Failed', 'Pending', 'N/A', 'Remaini
37
38
  * cell is filled, not merely its ID. See the spec, §8.3.
38
39
  */
39
40
  export function addSummaryBand(ws: ExcelJS.Worksheet, items: DeliveryItem[]): void {
40
- const sum = (k: 'passed' | 'failed' | 'notRun' | 'na'): number =>
41
- items.reduce((a, i) => a + i.resultCounts[k], 0);
42
- const total = items.reduce((a, i) => a + i.variants.length, 0);
43
- const passed = sum('passed');
44
- const failed = sum('failed');
45
- const pending = sum('notRun');
46
- const na = sum('na');
41
+ const { total, passed, failed, pending, na } = resultTotals(items);
47
42
  const remaining = total - passed - na;
48
43
  const denom = Math.max(1, total - na);
49
44
 
@@ -26,7 +26,7 @@ import * as fs from 'fs';
26
26
  import * as path from 'path';
27
27
  // Sensitive-name signal shared with the API exporter's key/body-field classifier —
28
28
  // here it is applied to the ENV VAR NAME (QA_ACCESS_TOKEN), not the yaml key.
29
- import { SENSITIVE_NAME } from './api-testcase-formatter';
29
+ import { SENSITIVE_NAME } from './sensitive-key';
30
30
 
31
31
  /** `${QA_*}` env reference — same shape the runtime resolver allowlists (uppercase, QA_ prefix). */
32
32
  const QA_ENV_REF = /\$\{(QA_[A-Z0-9_]+)\}/g;
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Report path sanitizer — rewrites the operator's machine paths out of a result/report JSON.
3
+ *
4
+ * Playwright's JSON reporter (and sungen's own script-check report) record ABSOLUTE paths:
5
+ * `config.configFile`, `rootDir`, `projects[].testDir/outputDir`, every `error.location.file`,
6
+ * attachment paths, and the paths quoted inside `stack`/`message`/`snippet` text. Teams commit these
7
+ * files next to the spec (the delivery reads them), so a username and a machine layout travel into
8
+ * the repository and the deliverable. Nothing downstream needs them absolute: delivery, dashboard and
9
+ * merge read titles, statuses and attachment names.
10
+ *
11
+ * Rules (pure — no I/O, no mutation of the input):
12
+ * - A string that IS an absolute path (POSIX, `C:\…`, `file:///…`) under the report's project root
13
+ * becomes the POSIX path relative to that root (the root itself → `.`); outside the root a path that
14
+ * names a machine (a home directory, a drive) keeps only its basename; any other absolute string —
15
+ * a URL route such as `/products` — is left alone.
16
+ * - Inside longer text (stack traces, messages, snippets) every occurrence of the root becomes a
17
+ * relative path, and a home directory (`/Users/<name>/`, `/home/<name>/`, `C:\Users\<name>\`) becomes
18
+ * `~/` — the text is rewritten, never dropped, so the trace stays readable.
19
+ * - URLs are untouched: a `/home/...` inside `https://host/home/...` is not a filesystem path.
20
+ * - Idempotent: sanitizing an already-sanitized report rewrites nothing.
21
+ *
22
+ * The root is the report's OWN `dirname(config.configFile)` when it carries one (self-describing —
23
+ * a report produced on another machine relativizes against ITS root, not the reader's cwd); callers
24
+ * fall back to their cwd otherwise.
25
+ */
26
+
27
+ export interface SanitizeOptions {
28
+ /** Project root the report's paths are made relative to (absolute; POSIX or Windows). */
29
+ root: string;
30
+ }
31
+
32
+ export interface SanitizeOutcome<T> {
33
+ doc: T;
34
+ /** Number of string values that changed. */
35
+ rewritten: number;
36
+ }
37
+
38
+ const ABSOLUTE = /^(?:file:\/\/\/?|[A-Za-z]:[\\/]|\/)/;
39
+ const WINDOWS_DRIVE = /^[A-Za-z]:/;
40
+ /**
41
+ * A home directory quoted inside text, not embedded in a URL or a longer token: `/Users/<name>/`,
42
+ * `/home/<name>/`, `C:\Users\<name>\`, each optionally behind a `file://` scheme (Node ESM errors
43
+ * quote modules as `file:///Users/<name>/…`).
44
+ */
45
+ const HOME_DIR = /(?<![\w.:/\\-])(?:file:\/\/\/?)?(?:\/(?:Users|home)\/[^/\\\s"'`)\]]+[/\\]|[A-Za-z]:[\\/]Users[\\/][^/\\\s"'`)\]]+[\\/])/g;
46
+
47
+ /** The project root a report describes itself against, if it says. */
48
+ export function detectReportRoot(doc: unknown): string | undefined {
49
+ const configFile = (doc as { config?: { configFile?: unknown } } | null)?.config?.configFile;
50
+ if (typeof configFile !== 'string' || !ABSOLUTE.test(configFile)) return undefined;
51
+ const p = toPosix(stripFileScheme(configFile));
52
+ const i = p.lastIndexOf('/');
53
+ return i > 0 ? p.slice(0, i) : undefined;
54
+ }
55
+
56
+ export function sanitizeReportPaths<T>(doc: T, opts: SanitizeOptions): SanitizeOutcome<T> {
57
+ const root = normalizeRoot(opts.root);
58
+ // A root of `/` (or empty) would make every `/` a "root prefix" and shred the document.
59
+ if (root.posix.length < 2) return { doc, rewritten: 0 };
60
+ let rewritten = 0;
61
+ const rewrite = (value: string): string => {
62
+ const next = sanitizeString(value, root);
63
+ if (next !== value) rewritten++;
64
+ return next;
65
+ };
66
+ const walk = (v: unknown): unknown => {
67
+ if (typeof v === 'string') return rewrite(v);
68
+ if (Array.isArray(v)) return v.map(walk);
69
+ if (v && typeof v === 'object') {
70
+ const out: Record<string, unknown> = {};
71
+ for (const [k, x] of Object.entries(v as Record<string, unknown>)) out[k] = walk(x);
72
+ return out;
73
+ }
74
+ return v;
75
+ };
76
+ return { doc: walk(doc) as T, rewritten };
77
+ }
78
+
79
+ interface Root { posix: string; caseInsensitive: boolean; pattern: RegExp }
80
+
81
+ function normalizeRoot(raw: string): Root {
82
+ const posix = toPosix(stripFileScheme(raw)).replace(/\/+$/, '');
83
+ const caseInsensitive = WINDOWS_DRIVE.test(posix);
84
+ // The root as it may appear inside text: either separator style, optional file:// scheme, and the
85
+ // separator that follows it (consumed, so what remains starts at the relative path).
86
+ const body = posix.split('/').map(escapeRegExp).join('[\\\\/]');
87
+ return { posix, caseInsensitive, pattern: new RegExp(`(?:file:\\/\\/\\/?)?${body}(?:[\\\\/](?![\\\\/])|(?=$|[\\s"'\`):,]))`, caseInsensitive ? 'gi' : 'g') };
88
+ }
89
+
90
+ function sanitizeString(value: string, root: Root): string {
91
+ const trimmed = value.trim();
92
+ if (ABSOLUTE.test(trimmed) && !/\s/.test(trimmed)) {
93
+ // The whole value is one path.
94
+ const p = toPosix(stripFileScheme(trimmed)).replace(/\/+$/, '');
95
+ const rel = relativeToRoot(p, root);
96
+ if (rel !== undefined) return rel;
97
+ // Outside the root: only a path that names a MACHINE (a home directory, a drive) is cut to its
98
+ // basename. Any other absolute string is left alone — `/products` in an audit report is a route.
99
+ if (isMachinePath(p)) {
100
+ // Another machine's checkout of THIS project (`…/work/<project>/specs/x.ts`, root `/home/me/<project>`):
101
+ // the part after the project directory is the honest relative path; only then fall back to the basename.
102
+ const marker = '/' + root.posix.slice(root.posix.lastIndexOf('/') + 1) + '/';
103
+ const i = marker.length > 2 ? (root.caseInsensitive ? p.toLowerCase().indexOf(marker.toLowerCase()) : p.indexOf(marker)) : -1;
104
+ if (i >= 0) return p.slice(i + marker.length);
105
+ return p.slice(p.lastIndexOf('/') + 1) || p;
106
+ }
107
+ return value;
108
+ }
109
+ // Paths quoted inside text: root → relative (the root alone → `.`), home → ~/.
110
+ const out = value.replace(root.pattern, (m: string) => (/[\\/]$/.test(m) ? '' : '.'));
111
+ return out.replace(HOME_DIR, '~/');
112
+ }
113
+
114
+ /**
115
+ * A path that identifies the operator's machine: a drive-letter path, or a home directory with
116
+ * something UNDER the user's folder (`/Users/<name>/<…>`). Case-sensitive and two segments deep on
117
+ * purpose — `/users/42/orders` and `/home/dashboard` are routes an audit report lists, not homes.
118
+ */
119
+ function isMachinePath(posixPath: string): boolean {
120
+ return WINDOWS_DRIVE.test(posixPath) || /\/(?:Users|home)\/[^/]+\/./.test(posixPath);
121
+ }
122
+
123
+ function relativeToRoot(p: string, root: Root): string | undefined {
124
+ const a = root.caseInsensitive ? p.toLowerCase() : p;
125
+ const b = root.caseInsensitive ? root.posix.toLowerCase() : root.posix;
126
+ if (a === b) return '.';
127
+ if (a.startsWith(b + '/')) return p.slice(root.posix.length + 1);
128
+ return undefined;
129
+ }
130
+
131
+ function stripFileScheme(p: string): string {
132
+ if (!p.startsWith('file://')) return p;
133
+ const rest = p.slice('file://'.length);
134
+ // file:///C:/x → C:/x ; file:///home/x → /home/x
135
+ return /^\/[A-Za-z]:/.test(rest) ? rest.slice(1) : rest;
136
+ }
137
+
138
+ function toPosix(p: string): string {
139
+ return p.replace(/\\/g, '/');
140
+ }
141
+
142
+ function escapeRegExp(s: string): string {
143
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
144
+ }
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { MergedScenario } from './scenario-merger';
7
7
  import { PlaywrightResult } from './types';
8
+ import { maskCredentialValue } from './sensitive-key';
8
9
 
9
10
  export interface ResultVariant {
10
11
  /** Appended to the scenario display name: `''` for a plain scenario, ` — <label>` per
@@ -102,6 +103,6 @@ export function formatVariantRow(variant: ResultVariant, separator: string = '\n
102
103
  const useBullet = separator.includes('\n');
103
104
  return Object.entries(variant.row)
104
105
  .filter(([k]) => !k.startsWith('__'))
105
- .map(([k, v]) => `${useBullet ? '• ' : ''}${k}: ${v}`)
106
+ .map(([k, v]) => `${useBullet ? '• ' : ''}${k}: ${maskCredentialValue(k, String(v))}`)
106
107
  .join(separator);
107
108
  }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Credential-name matching shared by the delivery exporters (key-based masking, value-based
3
+ * redaction, the qa-env secret denylist). Kept in its own module so the test-data loader can
4
+ * mask a cell without importing the API formatter.
5
+ */
6
+
7
+ /**
8
+ * Credential name matcher (substring, not anchored) for body-field keys and test-data keys —
9
+ * catches variants like confirmPassword / new_password / password_confirmation / access_token /
10
+ * client_secret / otp / passcode.
11
+ */
12
+ export const SENSITIVE_NAME = /pass(word|code)?|passwd|pwd|secret|token|otp|api[-_]?key|apikey|credential|authorization|signature/i;
13
+
14
+ /**
15
+ * The segment of a flattened test-data key that NAMES the value: the last dot-segment, skipping
16
+ * array indexes. `accounts.0.password` → `password`; `passwords.0` → `passwords`; `tokens.0.label`
17
+ * → `label`. Matching the whole key would treat every field under a list called `tokens` as a
18
+ * secret — `tokens.0.label: "New"` is a label, not a token.
19
+ */
20
+ export function sensitivityKeyName(key: string): string {
21
+ const segments = key.split('.').filter((seg) => !/^\d+$/.test(seg));
22
+ return segments[segments.length - 1] ?? key;
23
+ }
24
+
25
+ /** Key path that walks into an array (`list.0.field`, `tags.0`). */
26
+ const HAS_INDEX_SEGMENT = /(^|\.)\d+(\.|$)/;
27
+
28
+ /**
29
+ * Is this test-data key a credential? A non-indexed key is judged as a whole, exactly as before
30
+ * arrays were flattened per index — `api_key.value` and `token.value` stay credentials. An indexed
31
+ * key is judged by the segment that names the value, so `tokens.0.label` is not one.
32
+ */
33
+ export function isSensitiveKey(key: string): boolean {
34
+ if (SENSITIVE_NAME.test(sensitivityKeyName(key))) return true;
35
+ return !HAS_INDEX_SEGMENT.test(key) && SENSITIVE_NAME.test(key);
36
+ }
37
+
38
+ /** A value that still carries a raw `${QA_*}` env placeholder (nothing substituted at export time). */
39
+ const UNRESOLVED_QA_ENV_REF = /\$\{QA_[A-Z0-9_]+\}/;
40
+
41
+ /**
42
+ * What a Test Data cell may show for `key`: `***` for a credential-named key, else the value. An
43
+ * unresolved `${QA_*}` placeholder is not a value but the name of the variable the tester must
44
+ * set — it stays literal (also inside a composed value like `Bearer ${QA_TOKEN}`) so they can find it.
45
+ */
46
+ export function maskCredentialValue(key: string, value: string): string {
47
+ return isSensitiveKey(key) && !UNRESOLVED_QA_ENV_REF.test(value) ? '***' : value;
48
+ }
@@ -5,6 +5,7 @@
5
5
  import * as fs from 'fs';
6
6
  import * as path from 'path';
7
7
  import { parse as parseYaml } from 'yaml';
8
+ import { maskCredentialValue } from './sensitive-key';
8
9
 
9
10
  /**
10
11
  * Load test-data.yaml into a flat key→value map.
@@ -12,7 +13,10 @@ import { parse as parseYaml } from 'yaml';
12
13
  * kudos:
13
14
  * recipient: "An"
14
15
  * become { "kudos.recipient": "An" }, matching {{kudos.recipient}} refs.
15
- * Arrays are JSON-stringified. Returns empty map if both files are missing.
16
+ * An array is kept whole as a JSON string under its own key (the `@cases` dataset
17
+ * reader parses it back) AND flattened per index, so `{{list.0.field}}` /
18
+ * `{{tags.0}}` / `{{m.0.1}}` refs resolve exactly as the compile-time resolver
19
+ * resolves them. Returns empty map if both files are missing.
16
20
  *
17
21
  * When `SUNGEN_ENV` is set, also merges the matching overlay file alongside
18
22
  * the base (e.g. `login.yaml` + `login.vi.yaml`). Shallow root-level merge —
@@ -89,6 +93,10 @@ function flatten(obj: Record<string, unknown>, prefix: string, out: Record<strin
89
93
  const path = prefix ? `${prefix}.${key}` : key;
90
94
  if (Array.isArray(value)) {
91
95
  out[path] = JSON.stringify(value);
96
+ // Per-index leaves (`list.0.field`, `tags.0`, nested `m.0.1`) — dot form only, matching
97
+ // the compile-time resolver; a shorter overlay list replaces the whole array, so no
98
+ // stale `.N` leaves survive from the base.
99
+ flatten(Object.fromEntries(value.map((el, i) => [String(i), el])), path, out);
92
100
  } else if (typeof value === 'object') {
93
101
  flatten(value as Record<string, unknown>, path, out);
94
102
  } else {
@@ -122,7 +130,9 @@ export function formatTestData(
122
130
  for (const key of referencedVars) {
123
131
  const value = testData[key];
124
132
  if (value === undefined) continue;
125
- const line = `${key}: ${truncate(value, maxLen)}`;
133
+ // A credential-named key shows `***` in this cell, whatever the unit kind (the same rule the
134
+ // matrix data column and @cases rows apply); unresolved `${QA_*}` placeholders stay literal.
135
+ const line = `${key}: ${truncate(maskCredentialValue(key, value), maxLen)}`;
126
136
  pairs.push(useBullet ? `• ${line}` : line);
127
137
  }
128
138
  return pairs.join(separator);
@@ -6,6 +6,7 @@ import path from 'path';
6
6
  import { SelectorResolver } from '../test-generator/utils/selector-resolver';
7
7
  import { maskRefsForMatching } from '../test-generator/utils/mask-refs-for-matching';
8
8
  import type { StateClause } from '../test-generator/patterns/types';
9
+ import { extractParentScopeClause } from './parent-scope-clause';
9
10
 
10
11
  /**
11
12
  * Canonical state clauses, longest-first. Order is load-bearing twice over: `sorted ascending` must
@@ -71,7 +72,10 @@ export interface ParsedStep {
71
72
  nth?: number; // Positional index from "[Element] field 3" → 3 (0 = not specified)
72
73
  featurePath?: string; // NEW: Feature path for page type (e.g., "/", "/dashboard")
73
74
  parentRef?: string; // Parent scope reference: "in [Parent Name] parentType" → parentRef = "Parent Name"
74
- parentType?: string; // Parent scope type: table, list, section, dialog, form
75
+ parentType?: string; // Parent scope container type — see PARENT_CONTAINER_TYPES
76
+ parentValueRef?: string; // Parent scoped BY CONTENT: "in [Job Card] card with {{job_title}}" → "job_title"
77
+ parentValueText?: string; // Same, written as a quoted literal: `… card with "Senior Dev"` → "Senior Dev"
78
+ unsupportedScopeType?: string; // An `in [X] <word>` clause whose <word> is no container — dropped, reported as SG-W028
75
79
  dataTable?: ParsedDataTable; // Inline data table (Cucumber DataTable syntax)
76
80
  state?: StateClause; // Trailing canonical state clause, e.g. "hidden" from "… is hidden" (drives SG-E021)
77
81
  }
@@ -231,17 +235,21 @@ export class GherkinParser {
231
235
  private parseStep(step: Messages.Step): ParsedStep {
232
236
  let text = step.text;
233
237
 
234
- // === Extract parent scoping: "in [Parent Name] parentType" ===
235
- // Must be extracted first so the parent [brackets] don't interfere with target selector extraction.
236
- // Valid parent types: table, list, section, dialog, form
237
- const parentMatch = text.match(/\s+in\s+\[([^\]]+)\]\s+(table|list|section|dialog|form)\b/i);
238
+ // === Extract parent scoping: "in [Parent Name] containerType [with <value>]" ===
239
+ // Must be extracted first so the parent [brackets] don't interfere with target selector
240
+ // extraction. See parent-scope-clause.ts for the container vocabulary and why an unrecognized
241
+ // clause is reported (SG-W028) rather than left to be absorbed into the target's tokens.
242
+ const { clause: parentScope, unsupportedType } = extractParentScopeClause(text);
238
243
  let parentRef: string | undefined;
239
244
  let parentType: string | undefined;
240
- if (parentMatch) {
241
- parentRef = parentMatch[1];
242
- parentType = parentMatch[2].toLowerCase();
243
- // Remove parent scoping from text so it doesn't interfere with other parsing
244
- text = text.slice(0, parentMatch.index) + text.slice(parentMatch.index! + parentMatch[0].length);
245
+ let parentValueRef: string | undefined;
246
+ let parentValueText: string | undefined;
247
+ if (parentScope) {
248
+ parentRef = parentScope.ref;
249
+ parentType = parentScope.type;
250
+ parentValueRef = parentScope.valueRef;
251
+ parentValueText = parentScope.valueText;
252
+ text = parentScope.remainingText;
245
253
  }
246
254
 
247
255
  // Extract selector reference: [Email Address] or [login:email_field] (legacy)
@@ -321,6 +329,9 @@ export class GherkinParser {
321
329
  nth,
322
330
  parentRef,
323
331
  parentType,
332
+ parentValueRef,
333
+ parentValueText,
334
+ unsupportedScopeType: unsupportedType,
324
335
  dataTable,
325
336
  state: extractStateClause(step.text),
326
337
  };
@@ -0,0 +1,130 @@
1
+ /**
2
+ * The trailing scope clause — `… in [Parent] <containerType>` and its optional `with <value>` tail.
3
+ *
4
+ * Lifted out of `parseStep` because the clause failed SILENTLY (#667): the container whitelist held
5
+ * five types, anything else fell through, and the unmatched words stayed inline in the sentence
6
+ * where the first-match extractors below simply ignored them. The step compiled clean
7
+ * and the assertion ran against the WHOLE page instead of the named container — a test that passes
8
+ * for the wrong reason, findable only by diffing the generated spec against the Gherkin.
9
+ *
10
+ * So this module answers two questions, not one:
11
+ * - what the clause MEANS when it is recognized (`parentRef` / `parentType` / the value tail), and
12
+ * - what was DISCARDED when it is not (`unsupportedType`), so the compiler can say so (SG-W028)
13
+ * instead of quietly truncating the sentence.
14
+ */
15
+
16
+ /**
17
+ * Container types a step may scope into. Every one of these is something that HOLDS other elements
18
+ * on a page; `button`, `field`, `badge` and friends are deliberately absent — a leaf cannot be a
19
+ * parent, and admitting one would turn a typo into a silently wrong locator rather than a report.
20
+ *
21
+ * Ordered longest-first where one type is a prefix of another (`list-item` before `list`), because
22
+ * the alternation is matched left-to-right: `in [X] list-item` must not resolve as `list` with a
23
+ * stray `-item` left behind.
24
+ *
25
+ * `header` and `footer` are NOT here, though both hold elements. The auto-inferred fallback role
26
+ * for them would be the page landmarks `banner`/`contentinfo`, and a `<header>` nested inside an
27
+ * `<article>`/`<section>` — which is exactly how a repeating card is marked up — carries neither.
28
+ * That fallback would match nothing and fail for a reason unreadable from the Gherkin, so the
29
+ * clause is refused (SG-W028) and the author is pointed at a container type that resolves.
30
+ */
31
+ export const PARENT_CONTAINER_TYPES: readonly string[] = [
32
+ 'list-item', 'listitem', 'list',
33
+ 'table', 'section', 'dialog', 'modal', 'drawer', 'overlay', 'form',
34
+ 'card', 'item', 'row', 'group', 'panel', 'region', 'container',
35
+ 'tab', 'menu',
36
+ ];
37
+
38
+ const TYPES_ALT = PARENT_CONTAINER_TYPES.join('|');
39
+
40
+ /** `in [Parent] <containerType>` — the clause proper, without any value tail. */
41
+ const SCOPE_CLAUSE_BASE = new RegExp(`\\s+in\\s+\\[([^\\]]+)\\]\\s+(${TYPES_ALT})\\b`, 'i');
42
+
43
+ /** The same, plus the optional `with {{v}}` / `with "text"` tail. */
44
+ const SCOPE_CLAUSE = new RegExp(
45
+ `${SCOPE_CLAUSE_BASE.source}(?:\\s+with\\s+(\\{\\{[a-z0-9\\-._]+\\}\\}|"[^"]*"))?`,
46
+ 'i',
47
+ );
48
+
49
+ /** The same shape with ANY word where a container type belongs — used only to report what was lost. */
50
+ const SCOPE_CLAUSE_ANY_TYPE = /\s+in\s+\[([^\]]+)\]\s+([\w-]+)/i;
51
+
52
+ export interface ParentScopeClause {
53
+ /** The `[Parent]` reference — a selectors.yaml key, or `{{var}}` naming the container's text. */
54
+ ref: string;
55
+ /** The container type, lowercased. */
56
+ type: string;
57
+ /** `{{var}}` name when the value tail scopes the parent BY CONTENT. */
58
+ valueRef?: string;
59
+ /** Quoted literal when the value tail scopes the parent by content without a variable. */
60
+ valueText?: string;
61
+ /** The sentence with the clause removed, so the target extractors never see the parent's tokens. */
62
+ remainingText: string;
63
+ }
64
+
65
+ /**
66
+ * Is the `[…]` at `clauseAt` a CONTAINER, or is it the step's own target?
67
+ *
68
+ * A scope clause narrows a target, so the target's bracket must already have appeared. Without this
69
+ * test, `User select {{country}} in [Country Picker] dropdown` reads its one and only target as a
70
+ * parent — the bracket is what the step acts ON, and the `in` is English, not grammar.
71
+ */
72
+ function scopesATarget(text: string, clauseAt: number): boolean {
73
+ return /\[[^\]]*\]/.test(text.slice(0, clauseAt));
74
+ }
75
+
76
+ /**
77
+ * Does the segment before the scope clause already carry its own `with`?
78
+ *
79
+ * This is what keeps the value tail backward compatible. The documented grammar
80
+ * (`see [first] row in [apply list] table with {{job_name}}`) spends its single `with` on the
81
+ * TARGET, and re-reading that as a parent filter would silently invert the meaning of every such
82
+ * step already written. A SECOND `with` — `see [Tag] header with {{label}} in [Job Card] card with
83
+ * {{title}}` — cannot belong to the target, which already has one; only then does the tail bind to
84
+ * the parent. `[Ref]` contents are blanked first so a bracket like `[Filter With Tags]` is not
85
+ * mistaken for the keyword.
86
+ */
87
+ function targetAlreadyHasValue(head: string): boolean {
88
+ return /\s+with\s+/i.test(head.replace(/\[[^\]]*\]/g, ''));
89
+ }
90
+
91
+ /**
92
+ * Extract the scope clause from a step sentence.
93
+ *
94
+ * Returns `{ clause }` when a container type was recognized, `{ unsupportedType }` when the
95
+ * sentence carries an `in [X] <word>` clause whose `<word>` is not a container (it is dropped, and
96
+ * the caller reports it), or an empty object when there is no scope clause at all.
97
+ */
98
+ export function extractParentScopeClause(text: string): {
99
+ clause?: ParentScopeClause;
100
+ unsupportedType?: string;
101
+ } {
102
+ const match = text.match(SCOPE_CLAUSE);
103
+ if (!match) {
104
+ const loose = text.match(SCOPE_CLAUSE_ANY_TYPE);
105
+ return loose && scopesATarget(text, loose.index!)
106
+ ? { unsupportedType: loose[2].toLowerCase() }
107
+ : {};
108
+ }
109
+ if (!scopesATarget(text, match.index!)) return {};
110
+
111
+ const head = text.slice(0, match.index);
112
+ const tail = match[3];
113
+ const bindsToParent = Boolean(tail) && targetAlreadyHasValue(head);
114
+ // A value tail the target has already claimed stays in the sentence, exactly where it was, for the
115
+ // target extractors to pick up — what gets consumed is then only `in [Parent] <type>`.
116
+ const consumedLength = bindsToParent
117
+ ? match[0].length
118
+ : match[0].match(SCOPE_CLAUSE_BASE)![0].length;
119
+
120
+ const clause: ParentScopeClause = {
121
+ ref: match[1],
122
+ type: match[2].toLowerCase(),
123
+ remainingText: head + text.slice(match.index! + consumedLength),
124
+ };
125
+ if (bindsToParent) {
126
+ if (tail!.startsWith('{{')) clause.valueRef = tail!.slice(2, -2);
127
+ else clause.valueText = tail!.slice(1, -1);
128
+ }
129
+ return { clause };
130
+ }
@@ -8,6 +8,27 @@ export interface AuthGroup {
8
8
  scenarios: string[];
9
9
  }
10
10
 
11
+ /**
12
+ * One statement of a hook block (`@beforeAll` / `@afterEach` / `@afterAll`). `wrap` → the template
13
+ * guards the statement with try/catch and records a failure under `label` (a JSON string literal,
14
+ * safe to paste into code); `stopOnFailure` → a failure also ends the hook right there (action
15
+ * steps: later steps would run on state the failed action never produced). A plain step runs
16
+ * unwrapped and throws as before.
17
+ */
18
+ export interface HookStep {
19
+ comment?: string;
20
+ code: string;
21
+ wrap?: boolean;
22
+ stopOnFailure?: boolean;
23
+ label?: string;
24
+ }
25
+
26
+ /** Hook block render input. `collect` → declare the failure list and end with the fail-once guard. */
27
+ export interface HookRenderData {
28
+ steps: HookStep[];
29
+ collect?: boolean;
30
+ }
31
+
11
32
  export interface TestFileData {
12
33
  imports: string;
13
34
  featureName: string;
@@ -79,6 +100,7 @@ export interface RenderImportsOptions {
79
100
  needsMock?: boolean;
80
101
  urlAssertImports?: string;
81
102
  regexEscapeImport?: boolean;
103
+ checkedStateImports?: string;
82
104
  }
83
105
 
84
106
  export interface TestGeneratorAdapter {
@@ -91,9 +113,9 @@ export interface TestGeneratorAdapter {
91
113
  renderScenario(data: ScenarioData): string;
92
114
  renderImports(options?: RenderImportsOptions): string;
93
115
  renderBeforeEach(data: { steps: Array<{ comment?: string; code: string }> }): string;
94
- renderBeforeAll(data: { steps: Array<{ comment?: string; code: string }> }): string;
95
- renderAfterEach(data: { steps: Array<{ comment?: string; code: string }> }): string;
96
- renderAfterAll(data: { steps: Array<{ comment?: string; code: string }> }): string;
116
+ renderBeforeAll(data: HookRenderData): string;
117
+ renderAfterEach(data: HookRenderData): string;
118
+ renderAfterAll(data: HookRenderData): string;
97
119
 
98
120
  // Step rendering
99
121
  renderStep(templateName: string, data: any): string;
@@ -1,5 +1,5 @@
1
1
  import path from 'path';
2
- import { TestGeneratorAdapter, TestFileData, ScenarioData, RenderImportsOptions } from '../adapter-interface';
2
+ import { TestGeneratorAdapter, TestFileData, ScenarioData, RenderImportsOptions, HookRenderData } from '../adapter-interface';
3
3
  import { TemplateEngine } from '../../template-engine';
4
4
 
5
5
  /**
@@ -39,15 +39,15 @@ export class AppiumAdapter implements TestGeneratorAdapter {
39
39
  return this.templateEngine.renderBeforeEach(data);
40
40
  }
41
41
 
42
- renderBeforeAll(data: { steps: Array<{ comment?: string; code: string }> }): string {
42
+ renderBeforeAll(data: HookRenderData): string {
43
43
  return this.templateEngine.renderBeforeAll(data);
44
44
  }
45
45
 
46
- renderAfterEach(data: { steps: Array<{ comment?: string; code: string }> }): string {
46
+ renderAfterEach(data: HookRenderData): string {
47
47
  return this.templateEngine.renderAfterEach(data);
48
48
  }
49
49
 
50
- renderAfterAll(data: { steps: Array<{ comment?: string; code: string }> }): string {
50
+ renderAfterAll(data: HookRenderData): string {
51
51
  return this.templateEngine.renderAfterAll(data);
52
52
  }
53
53