@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
@@ -2,7 +2,8 @@
2
2
  /**
3
3
  * Sungen URL assertion helper — auto-generated into specs/url-assert.ts. DO NOT EDIT.
4
4
  *
5
- * Every Then-side URL assertion (`see [X] page`, `is on [X] page`) compiles to one call here.
5
+ * Every URL step compiles to one call here: the Then-side assertions (`see [X] page`,
6
+ * `is on [X] page`) to `expectPage`, and `wait for [X] page` to `waitForPage`.
6
7
  *
7
8
  * Why a helper and not an inline predicate. The predicate used to be emitted inline into every
8
9
  * spec, so each correction to what "the URL matches" MEANS was a breaking change for every
@@ -28,6 +29,16 @@ export interface UrlExpectation {
28
29
  * against a full URL.
29
30
  */
30
31
  pathname: RegExp;
32
+ /**
33
+ * Anchored hostname pattern, emitted ONLY when the page selector's `value` is an ABSOLUTE URL
34
+ * (`https://app.example.com/reception/:id`) — a destination on a different host from the suite's
35
+ * baseURL. Absent means "whatever host we are on", which is what a relative page value says.
36
+ *
37
+ * It is a SEPARATE field rather than part of `pathname` because the two are read off different
38
+ * properties of the parsed URL: `u.hostname` never appears in `u.pathname`. One concatenated
39
+ * regex only worked while the match ran unanchored over the whole URL string (#677).
40
+ */
41
+ hostname?: RegExp;
31
42
  /**
32
43
  * The `with {{v}}` value, raw from test-data: '?q=1' · '/vi/search?q=1' · '#tab' · ''.
33
44
  * `undefined` means the step declared nothing, which is itself an assertion — see below.
@@ -83,6 +94,15 @@ function decodedPath(pathname: string): string {
83
94
  }
84
95
  }
85
96
 
97
+ /**
98
+ * Is the URL at the expected LOCATION — right host, right path? The half every URL step shares.
99
+ * The Then-side assertion goes on to judge the query; the wait stops here (see `waitForPage`).
100
+ */
101
+ function locationMatches(pathname: RegExp, hostname: RegExp | undefined, u: URL): boolean {
102
+ if (hostname && !hostname.test(u.hostname)) return false;
103
+ return pathname.test(u.pathname) || pathname.test(decodedPath(u.pathname));
104
+ }
105
+
86
106
  /**
87
107
  * Build the predicate for `expect(page).toHaveURL(...)`.
88
108
  *
@@ -94,9 +114,9 @@ function decodedPath(pathname: string): string {
94
114
  * A declared fragment is asserted too; a declared value carrying no query
95
115
  * at all constrains the path only (the compiler warns: SG-W014).
96
116
  */
97
- export function urlMatches({ pathname, declared, dataRef }: UrlExpectation): (u: URL) => boolean {
117
+ export function urlMatches({ pathname, hostname, declared, dataRef }: UrlExpectation): (u: URL) => boolean {
98
118
  return (u: URL): boolean => {
99
- if (!pathname.test(u.pathname) && !pathname.test(decodedPath(u.pathname))) return false;
119
+ if (!locationMatches(pathname, hostname, u)) return false;
100
120
 
101
121
  // `dataRef`, not `declared`, decides which rule applies. Both "the step declared nothing" and
102
122
  // "the step declared {{v}} but the key resolved to nothing at runtime" arrive here as
@@ -135,38 +155,43 @@ export function urlMatches({ pathname, declared, dataRef }: UrlExpectation): (u:
135
155
  };
136
156
  }
137
157
 
158
+ /** What `settleAndHold` needs of a page — Playwright's `Page` satisfies it; so does a test fake. */
159
+ type SettleablePage = {
160
+ url(): string;
161
+ waitForLoadState(state: 'networkidle', opts?: { timeout?: number }): Promise<void>;
162
+ };
163
+
164
+ /** The two runtime knobs, shared by both URL steps. */
165
+ interface SettleOpts { settleMs?: number; networkIdleMs?: number }
166
+
138
167
  /**
139
- * `toHaveURL` is a polling assertion: it passes on the FIRST sample that matches. A client-side
140
- * router pushes the destination URL before the route guard or the API has answered, so a user
141
- * with no permission is "on" `/items/detail` for a few hundred milliseconds — long enough for
142
- * the assertion to sample it, pass, and end the test green — and is then bounced to `/forbidden`
143
- * with no assertion left to see it. A real project shipped a permission matrix on that oracle;
144
- * the 403s were found by hand (#662).
168
+ * A URL that MATCHES is not a page the user is still on. A client-side router pushes the
169
+ * destination URL before the route guard or the API has answered, so a user with no permission is
170
+ * "on" `/items/detail` for a few hundred milliseconds — long enough for a polling assertion to
171
+ * sample it and pass, or for a `waitForURL` to resolve — and is then bounced to `/forbidden` with
172
+ * nothing left watching. A real project shipped a permission matrix on that oracle; the 403s were
173
+ * found by hand (#662).
145
174
  *
146
- * `expectPage` is the page oracle `see [X] page` compiles to now. Arriving is not enough: the URL
147
- * has to be STILL matching once the page has settled.
148
- * 1. arrive — the same predicate `toHaveURL` always used;
149
- * 2. settle — wait for network to go quiet (bounded: an app that polls forever must not hang
150
- * the test, so a timeout here is not a failure);
151
- * 3. hold — sample the URL over a settle window; the moment it stops matching, fail and
152
- * NAME where it went, because that destination is the finding;
153
- * 4. re-assert — the arrival predicate, once more, on the settled page.
175
+ * So both URL steps go through this: having arrived,
176
+ * 1. settle — wait for network to go quiet (bounded: an app that polls forever must not hang
177
+ * the test, so a timeout here is not a failure);
178
+ * 2. hold — sample the URL over a settle window; the moment it stops matching, fail and
179
+ * NAME where it went, because that destination is the finding.
180
+ * The caller decides what arriving meant and what to do afterwards.
154
181
  *
155
182
  * `SUNGEN_URL_SETTLE_MS` (default 1000) is the hold window; `SUNGEN_URL_NETWORKIDLE_MS` (default
156
- * 3000) bounds step 2. Both are runtime knobs, so a slow environment is a config change, not a
183
+ * 3000) bounds step 1. Both are runtime knobs, so a slow environment is a config change, not a
157
184
  * regenerate.
158
185
  */
159
- export async function expectPage(
160
- page: { url(): string; waitForLoadState(state: 'networkidle', opts?: { timeout?: number }): Promise<void> },
161
- expectation: UrlExpectation,
162
- opts: { settleMs?: number; networkIdleMs?: number; assertUrl?: (matches: (u: URL) => boolean) => Promise<void> } = {},
186
+ async function settleAndHold(
187
+ page: SettleablePage,
188
+ matches: (u: URL) => boolean,
189
+ opts: SettleOpts,
190
+ label: string,
191
+ consequence: string,
163
192
  ): Promise<void> {
164
193
  const settleMs = opts.settleMs ?? Number(process.env.SUNGEN_URL_SETTLE_MS ?? 1000);
165
194
  const networkIdleMs = opts.networkIdleMs ?? Number(process.env.SUNGEN_URL_NETWORKIDLE_MS ?? 3000);
166
- const matches = urlMatches(expectation);
167
- const assertUrl = opts.assertUrl ?? (async (m: (u: URL) => boolean) => { await expect(page as unknown as Page).toHaveURL(m); });
168
-
169
- await assertUrl(matches);
170
195
  const arrivedAt = page.url();
171
196
 
172
197
  await page.waitForLoadState('networkidle', { timeout: networkIdleMs }).catch(() => undefined);
@@ -176,13 +201,61 @@ export async function expectPage(
176
201
  const now = page.url();
177
202
  if (!matches(new URL(now))) {
178
203
  throw new Error(
179
- `page assertion: reached ${arrivedAt} and then left for ${now} after ${Date.now() - started}ms — `
204
+ `${label}: reached ${arrivedAt} and then left for ${now} after ${Date.now() - started}ms — `
180
205
  + 'a route guard, an error boundary or a redirect moved the user away after the URL had already '
181
- + 'matched. The page the test asserted is not the page the user ended on.',
206
+ + `matched. ${consequence}`,
182
207
  );
183
208
  }
184
209
  await new Promise((r) => setTimeout(r, 50));
185
210
  }
211
+ }
212
+
213
+ /**
214
+ * `expectPage` is the page oracle `see [X] page` / `is on [X] page` compile to. Arriving is not
215
+ * enough: the URL has to be STILL matching once the page has settled.
216
+ * 1. arrive — the same predicate `toHaveURL` always used;
217
+ * 2. settle + hold — see `settleAndHold`;
218
+ * 3. re-assert — the arrival predicate, once more, on the settled page.
219
+ */
220
+ export async function expectPage(
221
+ page: SettleablePage,
222
+ expectation: UrlExpectation,
223
+ opts: SettleOpts & { assertUrl?: (matches: (u: URL) => boolean) => Promise<void> } = {},
224
+ ): Promise<void> {
225
+ const matches = urlMatches(expectation);
226
+ const assertUrl = opts.assertUrl ?? (async (m: (u: URL) => boolean) => { await expect(page as unknown as Page).toHaveURL(m); });
186
227
 
187
228
  await assertUrl(matches);
229
+ await settleAndHold(page, matches, opts, 'page assertion',
230
+ 'The page the test asserted is not the page the user ended on.');
231
+ await assertUrl(matches);
232
+ }
233
+
234
+ /**
235
+ * `waitForPage` is what `When User wait for [X] page` compiles to (#677).
236
+ *
237
+ * A bare `page.waitForURL(/…/)` resolves on the first URL sample that matches — the transient URL
238
+ * the router pushed before its guard decided. The wait then returned, and every step AFTER it ran
239
+ * against a page mid-redirect, so their failures pointed at the wrong thing. It was also the third
240
+ * hand-rolled copy of "does this URL match", drifting from the pair #662 consolidated here.
241
+ *
242
+ * Where this deliberately DIFFERS from `expectPage`: the query is not constrained. A Then-side page
243
+ * assertion is an oracle, so a bare page must be bare — `/vi/search` fails on `/vi/search?x=1`. A
244
+ * wait is not an oracle: `wait for [Results] page` after submitting a search legitimately lands on
245
+ * `/results?q=abc`, and reddening there would assert something the step never said. Prove the query
246
+ * with the `Then User see [Results] page with {{v}}` that follows.
247
+ */
248
+ export async function waitForPage(
249
+ page: SettleablePage & { waitForURL(url: (u: URL) => boolean, opts?: { timeout?: number }): Promise<void> },
250
+ expectation: UrlExpectation,
251
+ opts: SettleOpts & { timeout?: number; waitUrl?: (matches: (u: URL) => boolean) => Promise<void> } = {},
252
+ ): Promise<void> {
253
+ const { pathname, hostname } = expectation;
254
+ const matches = (u: URL): boolean => locationMatches(pathname, hostname, u);
255
+ // Playwright's own navigation timeout governs the arrival; only the hold is sungen's.
256
+ const arrive = opts.waitUrl ?? (async (m: (u: URL) => boolean) => { await page.waitForURL(m, { timeout: opts.timeout }); });
257
+
258
+ await arrive(matches);
259
+ await settleAndHold(page, matches, opts, 'page wait',
260
+ 'The steps after this wait would have run against a page the user is already being moved off.');
188
261
  }
@@ -1,7 +1,49 @@
1
1
  /**
2
2
  * Valid selector types used across scaffold generator, selector resolver, and validator.
3
3
  * Single source of truth — import this instead of hardcoding.
4
+ *
5
+ * The list is partitioned deliberately. A type is either a LOCATOR STRATEGY (the resolver's
6
+ * strategy switch turns it into a Playwright/WDIO locator) or a NON-STRATEGY declaration (the
7
+ * entry describes something other than how to find an element, and a reader downstream of the
8
+ * resolver consumes it). A type that is in neither set but is still accepted as valid is drift:
9
+ * it reaches the switch's `default:` and resolves as `getByPlaceholder('<whatever value held>')`,
10
+ * a selector that cannot match. `upload` sat in exactly that gap — valid, documented, and
11
+ * silently mis-resolved (issue #690).
12
+ *
13
+ * Keep the partition total. `tests/codegen/selector-type-dispatch.run.ts` fails if a new member
14
+ * of VALID_SELECTOR_TYPES is classified into neither half, or if a strategy type stops being
15
+ * dispatched and starts resolving as a placeholder.
4
16
  */
5
- export declare const VALID_SELECTOR_TYPES: readonly ["placeholder", "role", "testid", "label", "text", "page", "column", "locator", "id", "upload", "frame", "table", "accessibility-id", "xpath", "android-uiautomator", "ios-predicate", "css"];
17
+ /**
18
+ * Types the resolver turns into a locator. Every member MUST be dispatched in
19
+ * `SelectorResolver.resolveFromEntry` — either by a `case` in its strategy switch or by an
20
+ * early return above it (`locator` and `upload` share one). The contract is on the RESOLVED
21
+ * STRATEGY, not on the syntax used to produce it.
22
+ */
23
+ export declare const STRATEGY_SELECTOR_TYPES: readonly ["placeholder", "role", "testid", "label", "text", "locator", "id", "upload", "accessibility-id", "xpath", "android-uiautomator", "ios-predicate", "css"];
24
+ /**
25
+ * Types that declare something OTHER than a locator strategy, so they have no `case` by design.
26
+ * Each is read after resolution via `ResolvedSelector.selectorType`, or by the pattern that owns
27
+ * the step, which reads the entry's fields directly:
28
+ *
29
+ * page — `value` is a URL path, not a locator. The page assertion reads `selectorType`
30
+ * AFTER resolving (see StepMapper.checkPageStepFallback), so this type must
31
+ * pass through the switch rather than throw.
32
+ * table — the table patterns read `columns` (StepMapper's column-index lookup), `rows` (the
33
+ * row templates, backed by SG-W016) and `pagination` (the paginate step, #712) off
34
+ * the entry. `pagination` was the NOTE that used to sit here: documented, plumbed,
35
+ * and read by nothing — the same drift shape as #690, one level down. It has a
36
+ * consumer now, and SG-W004 is the gate that makes the next such field fail a check
37
+ * rather than wait to be grepped.
38
+ * column — addressed through its table's `columns` map, never resolved standalone.
39
+ * frame — consumed by the frame-enter/exit steps, which need the raw iframe selector.
40
+ *
41
+ * Reaching the strategy switch is legitimate for these; the placeholder fallback there is inert
42
+ * because no consumer reads `strategy` for them.
43
+ */
44
+ export declare const NON_STRATEGY_SELECTOR_TYPES: readonly ["page", "column", "frame", "table"];
45
+ export declare const VALID_SELECTOR_TYPES: readonly ["placeholder", "role", "testid", "label", "text", "locator", "id", "upload", "accessibility-id", "xpath", "android-uiautomator", "ios-predicate", "css", "page", "column", "frame", "table"];
46
+ export type StrategySelectorType = (typeof STRATEGY_SELECTOR_TYPES)[number];
47
+ export type NonStrategySelectorType = (typeof NON_STRATEGY_SELECTOR_TYPES)[number];
6
48
  export type SelectorType = (typeof VALID_SELECTOR_TYPES)[number];
7
49
  //# sourceMappingURL=selector-types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"selector-types.d.ts","sourceRoot":"","sources":["../../src/utils/selector-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,oBAAoB,wMAmBvB,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC"}
1
+ {"version":3,"file":"selector-types.d.ts","sourceRoot":"","sources":["../../src/utils/selector-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,oKAe1B,CAAC;AAEX;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,2BAA2B,+CAK9B,CAAC;AAEX,eAAO,MAAM,oBAAoB,wMAGvB,CAAC;AAEX,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,CAAC,CAAC;AAC5E,MAAM,MAAM,uBAAuB,GAAG,CAAC,OAAO,2BAA2B,CAAC,CAAC,MAAM,CAAC,CAAC;AACnF,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC"}
@@ -1,23 +1,37 @@
1
1
  "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.VALID_SELECTOR_TYPES = void 0;
4
2
  /**
5
3
  * Valid selector types used across scaffold generator, selector resolver, and validator.
6
4
  * Single source of truth — import this instead of hardcoding.
5
+ *
6
+ * The list is partitioned deliberately. A type is either a LOCATOR STRATEGY (the resolver's
7
+ * strategy switch turns it into a Playwright/WDIO locator) or a NON-STRATEGY declaration (the
8
+ * entry describes something other than how to find an element, and a reader downstream of the
9
+ * resolver consumes it). A type that is in neither set but is still accepted as valid is drift:
10
+ * it reaches the switch's `default:` and resolves as `getByPlaceholder('<whatever value held>')`,
11
+ * a selector that cannot match. `upload` sat in exactly that gap — valid, documented, and
12
+ * silently mis-resolved (issue #690).
13
+ *
14
+ * Keep the partition total. `tests/codegen/selector-type-dispatch.run.ts` fails if a new member
15
+ * of VALID_SELECTOR_TYPES is classified into neither half, or if a strategy type stops being
16
+ * dispatched and starts resolving as a placeholder.
7
17
  */
8
- exports.VALID_SELECTOR_TYPES = [
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.VALID_SELECTOR_TYPES = exports.NON_STRATEGY_SELECTOR_TYPES = exports.STRATEGY_SELECTOR_TYPES = void 0;
20
+ /**
21
+ * Types the resolver turns into a locator. Every member MUST be dispatched in
22
+ * `SelectorResolver.resolveFromEntry` — either by a `case` in its strategy switch or by an
23
+ * early return above it (`locator` and `upload` share one). The contract is on the RESOLVED
24
+ * STRATEGY, not on the syntax used to produce it.
25
+ */
26
+ exports.STRATEGY_SELECTOR_TYPES = [
9
27
  'placeholder',
10
28
  'role',
11
29
  'testid',
12
30
  'label',
13
31
  'text',
14
- 'page',
15
- 'column',
16
32
  'locator',
17
33
  'id',
18
34
  'upload',
19
- 'frame',
20
- 'table',
21
35
  // === Mobile (Appium) strategies — resolved by the appium adapter (MOB-3) ===
22
36
  'accessibility-id', // cross-platform — Android content-desc / iOS accessibilityIdentifier
23
37
  'xpath', // cross-platform, last resort
@@ -25,4 +39,34 @@ exports.VALID_SELECTOR_TYPES = [
25
39
  'ios-predicate', // iOS only — NSPredicate strings
26
40
  'css', // hybrid WebView only — CSS inside a WEBVIEW context (after `switch to [X] frame`)
27
41
  ];
42
+ /**
43
+ * Types that declare something OTHER than a locator strategy, so they have no `case` by design.
44
+ * Each is read after resolution via `ResolvedSelector.selectorType`, or by the pattern that owns
45
+ * the step, which reads the entry's fields directly:
46
+ *
47
+ * page — `value` is a URL path, not a locator. The page assertion reads `selectorType`
48
+ * AFTER resolving (see StepMapper.checkPageStepFallback), so this type must
49
+ * pass through the switch rather than throw.
50
+ * table — the table patterns read `columns` (StepMapper's column-index lookup), `rows` (the
51
+ * row templates, backed by SG-W016) and `pagination` (the paginate step, #712) off
52
+ * the entry. `pagination` was the NOTE that used to sit here: documented, plumbed,
53
+ * and read by nothing — the same drift shape as #690, one level down. It has a
54
+ * consumer now, and SG-W004 is the gate that makes the next such field fail a check
55
+ * rather than wait to be grepped.
56
+ * column — addressed through its table's `columns` map, never resolved standalone.
57
+ * frame — consumed by the frame-enter/exit steps, which need the raw iframe selector.
58
+ *
59
+ * Reaching the strategy switch is legitimate for these; the placeholder fallback there is inert
60
+ * because no consumer reads `strategy` for them.
61
+ */
62
+ exports.NON_STRATEGY_SELECTOR_TYPES = [
63
+ 'page',
64
+ 'column',
65
+ 'frame',
66
+ 'table',
67
+ ];
68
+ exports.VALID_SELECTOR_TYPES = [
69
+ ...exports.STRATEGY_SELECTOR_TYPES,
70
+ ...exports.NON_STRATEGY_SELECTOR_TYPES,
71
+ ];
28
72
  //# sourceMappingURL=selector-types.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"selector-types.js","sourceRoot":"","sources":["../../src/utils/selector-types.ts"],"names":[],"mappings":";;;AAAA;;;GAGG;AACU,QAAA,oBAAoB,GAAG;IAClC,aAAa;IACb,MAAM;IACN,QAAQ;IACR,OAAO;IACP,MAAM;IACN,MAAM;IACN,QAAQ;IACR,SAAS;IACT,IAAI;IACJ,QAAQ;IACR,OAAO;IACP,OAAO;IACP,8EAA8E;IAC9E,kBAAkB,EAAK,sEAAsE;IAC7F,OAAO,EAAgB,8BAA8B;IACrD,qBAAqB,EAAE,wCAAwC;IAC/D,eAAe,EAAQ,iCAAiC;IACxD,KAAK,EAAkB,mFAAmF;CAClG,CAAC"}
1
+ {"version":3,"file":"selector-types.js","sourceRoot":"","sources":["../../src/utils/selector-types.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;GAeG;;;AAEH;;;;;GAKG;AACU,QAAA,uBAAuB,GAAG;IACrC,aAAa;IACb,MAAM;IACN,QAAQ;IACR,OAAO;IACP,MAAM;IACN,SAAS;IACT,IAAI;IACJ,QAAQ;IACR,8EAA8E;IAC9E,kBAAkB,EAAK,sEAAsE;IAC7F,OAAO,EAAgB,8BAA8B;IACrD,qBAAqB,EAAE,wCAAwC;IAC/D,eAAe,EAAQ,iCAAiC;IACxD,KAAK,EAAkB,mFAAmF;CAClG,CAAC;AAEX;;;;;;;;;;;;;;;;;;;GAmBG;AACU,QAAA,2BAA2B,GAAG;IACzC,MAAM;IACN,QAAQ;IACR,OAAO;IACP,OAAO;CACC,CAAC;AAEE,QAAA,oBAAoB,GAAG;IAClC,GAAG,+BAAuB;IAC1B,GAAG,mCAA2B;CACtB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sun-asterisk/sungen",
3
- "version": "3.2.29",
3
+ "version": "3.2.30",
4
4
  "description": "Deterministic E2E Test Compiler - Gherkin + Selectors → Playwright tests",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -40,8 +40,8 @@
40
40
  "@babel/types": "^7.28.5",
41
41
  "@cucumber/gherkin": "^37.0.0",
42
42
  "@cucumber/messages": "^31.0.0",
43
- "@sungen/driver-data-factory": "3.2.29",
44
- "@sungen/driver-ui": "3.2.29",
43
+ "@sungen/driver-data-factory": "3.2.30",
44
+ "@sungen/driver-ui": "3.2.30",
45
45
  "chalk": "^5.6.2",
46
46
  "commander": "^14.0.2",
47
47
  "dotenv": "^17.2.3",
@@ -39,6 +39,7 @@ import {
39
39
  hasReview,
40
40
  } from '../../exporters/matrix/export';
41
41
  import { MatrixFinding } from '../../exporters/matrix/types';
42
+ import { formatTestOutcome, resultTotals } from '../../exporters/matrix/result-totals';
42
43
 
43
44
  const COLOR = {
44
45
  reset: '\x1b[0m',
@@ -703,7 +704,7 @@ async function exportMatrixTarget(
703
704
  return null;
704
705
  }
705
706
 
706
- log(`${COLOR.bold}${paths.label}${COLOR.reset} — ${model.manifest.itemCount} delivery item(s), ${model.manifest.variantCount} coverage variant(s), state: ${model.manifest.approvalState}`);
707
+ log(`${COLOR.bold}${paths.label}${COLOR.reset} — ${model.manifest.itemCount} delivery item(s), ${model.manifest.variantCount} coverage variant(s), review: ${model.manifest.approvalState} (design), test outcome: ${formatTestOutcome(resultTotals(model.items))}`);
707
708
  {
708
709
  const mocked = model.items.flatMap((it) => it.variants.filter((v) => v.mocked));
709
710
  if (mocked.length > 0) {
@@ -238,8 +238,11 @@ export function registerGenerateCommand(program: Command): void {
238
238
  const advisorySensors = capabilityRegistry.sensors('advisory');
239
239
  const ddWarnings = scanDirs.flatMap((d) => advisorySensors.flatMap((s) => s.run({ dir: d, cwd })));
240
240
  if (ddWarnings.length) {
241
- console.log(`\n⚠️ Data-driven lint (@cases / @query) — review:`);
242
- for (const w of ddWarnings.slice(0, 20)) console.log(` ${w.scenario ? w.scenario + ': ' : ''}${w.message}`);
241
+ console.log(`\n⚠️ Data-driven lint (@cases / @query / dead test-data) — advisory, review:`);
242
+ const verbose = !!program.opts().verbose;
243
+ const shown = verbose ? ddWarnings : ddWarnings.slice(0, 20);
244
+ for (const w of shown) console.log(` ${w.scenario ? w.scenario + ': ' : ''}${w.message}`);
245
+ if (shown.length < ddWarnings.length) console.log(` … and ${ddWarnings.length - shown.length} more (run with -v to list all)`);
243
246
  }
244
247
 
245
248
  const nextStep = platform === 'mobile' ? 'npm run test:mobile' : 'npx playwright test --ui';
@@ -63,7 +63,7 @@ export function inspectProject(cwd: string): ProjectInspection {
63
63
  const perfPass = model.perf ? model.perf.pass : null;
64
64
 
65
65
  const reasons: string[] = [];
66
- if (!approved) reasons.push('delivery map not approved');
66
+ if (!approved) reasons.push('delivery map not approved (design review state — not a test outcome)');
67
67
  if (!frozen) reasons.push('sources drifted since approval (not frozen — Gate E)');
68
68
  if (failed.length) reasons.push(`${failed.length} golden variant(s) FAILED`);
69
69
  if (pending.length) reasons.push(`${pending.length} golden variant(s) have no run result`);
@@ -9,6 +9,8 @@ import * as fs from 'fs';
9
9
  import * as path from 'path';
10
10
  import { Command } from 'commander';
11
11
  import { mergeResultsReports, findGhostTitles } from '../../exporters/results-merge';
12
+ import { detectReportRoot, sanitizeReportPaths } from '../../exporters/report-path-sanitizer';
13
+ import { registerResultsSanitizeCommand } from './results-sanitize';
12
14
  import { resolveResultsPath } from '../../exporters/playwright-report-parser';
13
15
  import { parseSpecFile } from '../../exporters/spec-parser';
14
16
 
@@ -95,8 +97,12 @@ export function runResultsMerge(baseArg: string | undefined, newArg: string | un
95
97
  throw new Error('results merge: expected <base.json> <new.json>, or --screen <name> <new.json>.');
96
98
  }
97
99
 
98
- const base = readReport(baseFile, 'base');
99
- const fresh = readReport(newFile, 'new');
100
+ // Each report relativizes against ITS OWN root before the merge: `config` is copied from the base
101
+ // wholesale, so a base produced on another machine would otherwise carry that machine's paths
102
+ // into the merged file for good.
103
+ const sanitize = (doc: any): any => sanitizeReportPaths(doc, { root: detectReportRoot(doc) ?? cwd }).doc;
104
+ const base = sanitize(readReport(baseFile, 'base'));
105
+ const fresh = sanitize(readReport(newFile, 'new'));
100
106
  const { merged, replaced, added, kept } = mergeResultsReports(base, fresh);
101
107
 
102
108
  if (specTitles) {
@@ -136,6 +142,7 @@ export function registerResultsCommand(program: Command): void {
136
142
  const results = program
137
143
  .command('results')
138
144
  .description('Operate on per-target Playwright result files');
145
+ registerResultsSanitizeCommand(results);
139
146
 
140
147
  results
141
148
  .command('merge [base] [new]')
@@ -0,0 +1,98 @@
1
+ /**
2
+ * `sungen results sanitize [files…]` — rewrite the operator's machine paths out of result/report JSON.
3
+ *
4
+ * Playwright's JSON reporter and sungen's own reports record absolute paths, and teams commit those
5
+ * files next to the spec (delivery reads them). The reporter registered in `playwright.config.ts`
6
+ * sanitizes a fresh run's report on exit; this command is the safety net that reaches every file a
7
+ * project already has — and every project whose config predates the reporter. Idempotent: a clean
8
+ * file is left untouched (no rewrite, no timestamp churn).
9
+ *
10
+ * Each report relativizes against ITS OWN root (`dirname(config.configFile)` when it carries one), so
11
+ * a file produced on another machine comes out right too; a report that does not say falls back to
12
+ * the working directory.
13
+ */
14
+ import { Command } from 'commander';
15
+ import * as fs from 'fs';
16
+ import * as path from 'path';
17
+ import fg from 'fast-glob';
18
+ import { detectReportRoot, sanitizeReportPaths } from '../../exporters/report-path-sanitizer';
19
+
20
+ /** Where a project keeps the JSON files that carry paths. */
21
+ export const DEFAULT_REPORT_GLOBS = [
22
+ 'specs/generated/**/*-test-result*.json',
23
+ 'test-result.json',
24
+ '*-test-result*.json',
25
+ '.sungen/reports/*-script-check.json', // the one sungen report that records a path (`specPath`)
26
+ ];
27
+
28
+ export interface SanitizedFile {
29
+ file: string; // relative to cwd
30
+ rewritten: number; // string values changed (0 = already clean)
31
+ }
32
+
33
+ /** Sanitize the given files (or the project's default report locations). Returns one row per file. */
34
+ export function runResultsSanitize(files: string[] | undefined, cwd: string = process.cwd()): SanitizedFile[] {
35
+ const targets = files && files.length
36
+ ? files.map((f) => path.resolve(cwd, f))
37
+ : fg.sync(DEFAULT_REPORT_GLOBS, { cwd, absolute: true, onlyFiles: true, dot: true });
38
+ const out: SanitizedFile[] = [];
39
+ for (const file of targets.sort()) {
40
+ const raw = fs.readFileSync(file, 'utf-8');
41
+ let doc: unknown;
42
+ try {
43
+ doc = JSON.parse(raw);
44
+ } catch (err) {
45
+ throw new Error(`results sanitize: ${path.relative(cwd, file)} is not JSON (${err instanceof Error ? err.message : String(err)})`);
46
+ }
47
+ // A report that does not describe its root belongs to the project it sits in — the nearest
48
+ // ancestor holding a playwright.config.ts or .sungen/ — not to wherever the command was run from
49
+ // (a monorepo root would otherwise turn `specs/generated/x/x.spec.ts` into a bare basename).
50
+ const root = detectReportRoot(doc) ?? nearestProjectRoot(path.dirname(file)) ?? cwd;
51
+ const { doc: clean, rewritten } = sanitizeReportPaths(doc, { root });
52
+ if (rewritten > 0) {
53
+ // Atomic write, same as `results merge`: a crash mid-write must never leave a truncated report.
54
+ const tmp = `${file}.tmp-${process.pid}`;
55
+ try {
56
+ fs.writeFileSync(tmp, JSON.stringify(clean, null, 2) + (raw.endsWith('\n') ? '\n' : ''));
57
+ fs.renameSync(tmp, file);
58
+ } catch (err) {
59
+ try { fs.rmSync(tmp, { force: true }); } catch { /* best-effort cleanup */ }
60
+ throw err;
61
+ }
62
+ }
63
+ out.push({ file: path.relative(cwd, file), rewritten });
64
+ }
65
+ return out;
66
+ }
67
+
68
+ export function registerResultsSanitizeCommand(results: Command): void {
69
+ results
70
+ .command('sanitize [files...]')
71
+ .description('Rewrite absolute machine paths in result/report JSON to project-relative ones (default: every *-test-result*.json under specs/generated and .sungen/reports/*.json)')
72
+ .action((files: string[]) => {
73
+ try {
74
+ const rows = runResultsSanitize(files.length ? files : undefined);
75
+ if (rows.length === 0) {
76
+ console.log('results sanitize: no report files found.');
77
+ return;
78
+ }
79
+ const changed = rows.filter((r) => r.rewritten > 0);
80
+ for (const r of changed) console.log(`✓ ${r.file} — ${r.rewritten} path(s) made project-relative`);
81
+ console.log(`results sanitize: ${changed.length} of ${rows.length} file(s) rewritten${changed.length === 0 ? ' (all already clean)' : ''}.`);
82
+ } catch (err) {
83
+ console.error(err instanceof Error ? err.message : String(err));
84
+ process.exitCode = 1;
85
+ }
86
+ });
87
+ }
88
+
89
+ /** Nearest ancestor directory that looks like a sungen/Playwright project root. */
90
+ function nearestProjectRoot(from: string): string | undefined {
91
+ let dir = from;
92
+ for (;;) {
93
+ if (fs.existsSync(path.join(dir, 'playwright.config.ts')) || fs.existsSync(path.join(dir, '.sungen'))) return dir;
94
+ const parent = path.dirname(dir);
95
+ if (parent === dir) return undefined;
96
+ dir = parent;
97
+ }
98
+ }
@@ -3,6 +3,7 @@ import * as path from 'path';
3
3
  import * as fs from 'fs';
4
4
  import { runScriptCheck } from '../../harness/script-check';
5
5
  import { reportSlug } from '../../harness/unit-paths';
6
+ import { sanitizeReportPaths } from '../../exporters/report-path-sanitizer';
6
7
 
7
8
  export function registerScriptCheckCommand(program: Command): void {
8
9
  program
@@ -23,7 +24,8 @@ export function registerScriptCheckCommand(program: Command): void {
23
24
  const dir = kind === 'screen' ? screen : kind === 'flow' ? flow : kind === 'api' ? api : null;
24
25
  if (!dir || !kind) throw new Error(`Not found: qa/screens|flows|api/${name}`);
25
26
 
26
- const r = await runScriptCheck(dir, name, kind);
27
+ // The report is committed next to the spec — keep the operator's machine paths out of it.
28
+ const r = sanitizeReportPaths(await runScriptCheck(dir, name, kind), { root: process.cwd() }).doc;
27
29
 
28
30
  const outDir = path.join(process.cwd(), '.sungen', 'reports');
29
31
  fs.mkdirSync(outDir, { recursive: true });
@@ -100,6 +100,9 @@ export function registerUpdateCommand(program: Command): void {
100
100
  const { AIRulesUpdater } = require('../../orchestrator/ai-rules-updater');
101
101
  const updater = new AIRulesUpdater(process.cwd());
102
102
  await updater.update(options.dryRun ?? false, { agents: resolveAssistants(options.assistant), force: options.force ?? false });
103
+ // playwright.config.ts is written once at init and never re-synced — patch the reporter in.
104
+ const { installReportSanitizeReporter } = require('../../orchestrator/report-sanitize-reporter-install');
105
+ installReportSanitizeReporter(process.cwd(), options.dryRun ?? false);
103
106
  } catch (error) {
104
107
  console.error('❌ Update failed:', error);
105
108
  process.exit(1);
@@ -16,6 +16,7 @@
16
16
  */
17
17
 
18
18
  import { ApiCatalogEntry } from './types';
19
+ import { SENSITIVE_NAME, isSensitiveKey } from './sensitive-key';
19
20
 
20
21
  /** A single `@api:<name>(k=v,…)` invocation parsed from a scenario's tags. */
21
22
  export interface ApiCall {
@@ -372,12 +373,6 @@ function formatBodyFile(bodyFile: unknown, args: Record<string, string>, localiz
372
373
  return `${p} (${spec.mimeType || 'application/octet-stream'})`;
373
374
  }
374
375
 
375
- /**
376
- * Credential name matcher (substring, not anchored) for both body-field keys and
377
- * test-data keys — catches variants like confirmPassword / new_password /
378
- * password_confirmation / access_token / client_secret / otp / passcode.
379
- */
380
- export const SENSITIVE_NAME = /pass(word|code)?|passwd|pwd|secret|token|otp|api[-_]?key|apikey|credential|authorization|signature/i;
381
376
  function isSensitiveField(key: string): boolean {
382
377
  return SENSITIVE_NAME.test(key);
383
378
  }
@@ -387,11 +382,14 @@ function isSensitiveField(key: string): boolean {
387
382
  * over the finished Steps/Expected cells, closing the paths key-based masking misses
388
383
  * (nested request bodies, query-string credentials, and the Expected assertion cell).
389
384
  * Values shorter than 3 chars are skipped to avoid masking trivial/coincidental text.
385
+ * Sensitivity is judged by the key segment that NAMES the value (see isSensitiveKey): the
386
+ * test-data map also carries per-index array leaves (`tokens.0.label`), and matching the
387
+ * whole key would redact every field under a list whose name happens to be a credential word.
390
388
  */
391
389
  export function collectSecretValues(testData: Record<string, string>): string[] {
392
390
  const secrets: string[] = [];
393
391
  for (const [k, v] of Object.entries(testData)) {
394
- if (typeof v === 'string' && v.length >= 3 && SENSITIVE_NAME.test(k)) secrets.push(v);
392
+ if (typeof v === 'string' && v.length >= 3 && isSensitiveKey(k)) secrets.push(v);
395
393
  }
396
394
  // Longest-first so a longer secret is redacted before any shorter substring of it.
397
395
  return [...new Set(secrets)].sort((a, b) => b.length - a.length);
@@ -18,6 +18,7 @@ import {
18
18
  } from '../feature-parser';
19
19
  import { getCasesDatasetRows, resolveResultVariants } from '../result-variants';
20
20
  import { renderDynamicTokens } from '../test-data-resolver';
21
+ import { maskCredentialValue } from '../sensitive-key';
21
22
  import { classifyManualComments, renderAction, renderExpected, renderPrecondition, renderSetupInstruction, runtimeVarsOf } from './wording';
22
23
  import { scenarioFingerprint, combinedFingerprint, mapContentFingerprint } from './fingerprint';
23
24
  import {
@@ -100,12 +101,13 @@ function resolveDataPairs(
100
101
  if (row) {
101
102
  for (const [k, v] of Object.entries(row)) {
102
103
  if (k.startsWith('__') || k === 'case' || k === 'name' || k === 'label') continue;
103
- pairs.push(`${k}: ${displayValue(String(v))}`);
104
+ pairs.push(`${k}: ${displayValue(maskCredentialValue(k, String(v)))}`);
104
105
  }
105
106
  }
106
107
  for (const v of vars) {
107
108
  const val = row && v in row ? undefined : testData?.[v]; // row columns already listed
108
- if (val !== undefined) pairs.push(`${v}: ${displayValue(val)}`);
109
+ // Credential-named refs (`accounts.0.password`) render `***` — this column is read outside the run.
110
+ if (val !== undefined) pairs.push(`${v}: ${displayValue(maskCredentialValue(v, val))}`);
109
111
  }
110
112
  return pairs;
111
113
  }
@@ -13,6 +13,7 @@ import * as ExcelJS from 'exceljs';
13
13
  import { renderReportHeaderBand } from '../xlsx-report-builder';
14
14
  import { variantState } from './build';
15
15
  import { MatrixModel, MapCategory } from './types';
16
+ import { formatTestOutcome, resultTotals } from './result-totals';
16
17
  import { GAP_FILL, SOFT_BLUE, dataCell, headerCell, draftBanner } from './xlsx-cells';
17
18
 
18
19
  const CATEGORY_ORDER: MapCategory[] = ['normal', 'abnormal', 'security', 'nfr'];
@@ -151,7 +152,8 @@ export function addCoverageSheet(wb: ExcelJS.Workbook, model: MatrixModel, sunge
151
152
  const m = model.manifest;
152
153
  const manifestRows: Array<[string, string]> = [
153
154
  ['Unit', m.unit],
154
- ['Approval state', m.approvalState],
155
+ ['Review state (design approval)', m.approvalState],
156
+ ['Test outcome', formatTestOutcome(resultTotals(model.items))],
155
157
  ['Delivery items', String(m.itemCount)],
156
158
  ['Coverage variants', String(m.variantCount)],
157
159
  ['Source fingerprint', m.sourceFingerprint],