@sun-asterisk/sungen 3.2.29 → 3.2.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (335) hide show
  1. package/dist/cli/commands/delivery.d.ts.map +1 -1
  2. package/dist/cli/commands/delivery.js +2 -1
  3. package/dist/cli/commands/delivery.js.map +1 -1
  4. package/dist/cli/commands/generate.d.ts.map +1 -1
  5. package/dist/cli/commands/generate.js +6 -2
  6. package/dist/cli/commands/generate.js.map +1 -1
  7. package/dist/cli/commands/inspect.js +1 -1
  8. package/dist/cli/commands/inspect.js.map +1 -1
  9. package/dist/cli/commands/results-merge.d.ts.map +1 -1
  10. package/dist/cli/commands/results-merge.js +9 -2
  11. package/dist/cli/commands/results-merge.js.map +1 -1
  12. package/dist/cli/commands/results-sanitize.d.ts +24 -0
  13. package/dist/cli/commands/results-sanitize.d.ts.map +1 -0
  14. package/dist/cli/commands/results-sanitize.js +126 -0
  15. package/dist/cli/commands/results-sanitize.js.map +1 -0
  16. package/dist/cli/commands/script-check.d.ts.map +1 -1
  17. package/dist/cli/commands/script-check.js +3 -1
  18. package/dist/cli/commands/script-check.js.map +1 -1
  19. package/dist/cli/commands/update.d.ts.map +1 -1
  20. package/dist/cli/commands/update.js +3 -0
  21. package/dist/cli/commands/update.js.map +1 -1
  22. package/dist/exporters/api-testcase-formatter.d.ts +3 -6
  23. package/dist/exporters/api-testcase-formatter.d.ts.map +1 -1
  24. package/dist/exporters/api-testcase-formatter.js +7 -10
  25. package/dist/exporters/api-testcase-formatter.js.map +1 -1
  26. package/dist/exporters/matrix/build.d.ts.map +1 -1
  27. package/dist/exporters/matrix/build.js +4 -2
  28. package/dist/exporters/matrix/build.js.map +1 -1
  29. package/dist/exporters/matrix/render-coverage-sheet.d.ts.map +1 -1
  30. package/dist/exporters/matrix/render-coverage-sheet.js +3 -1
  31. package/dist/exporters/matrix/render-coverage-sheet.js.map +1 -1
  32. package/dist/exporters/matrix/render-csv.d.ts.map +1 -1
  33. package/dist/exporters/matrix/render-csv.js +3 -1
  34. package/dist/exporters/matrix/render-csv.js.map +1 -1
  35. package/dist/exporters/matrix/render-overview-sheet.d.ts.map +1 -1
  36. package/dist/exporters/matrix/render-overview-sheet.js +5 -9
  37. package/dist/exporters/matrix/render-overview-sheet.js.map +1 -1
  38. package/dist/exporters/matrix/result-totals.d.ts +23 -0
  39. package/dist/exporters/matrix/result-totals.d.ts.map +1 -0
  40. package/dist/exporters/matrix/result-totals.js +21 -0
  41. package/dist/exporters/matrix/result-totals.js.map +1 -0
  42. package/dist/exporters/matrix/testcase-summary-band.d.ts.map +1 -1
  43. package/dist/exporters/matrix/testcase-summary-band.js +2 -6
  44. package/dist/exporters/matrix/testcase-summary-band.js.map +1 -1
  45. package/dist/exporters/qa-env-secret-denylist.js +2 -2
  46. package/dist/exporters/qa-env-secret-denylist.js.map +1 -1
  47. package/dist/exporters/report-path-sanitizer.d.ts +38 -0
  48. package/dist/exporters/report-path-sanitizer.d.ts.map +1 -0
  49. package/dist/exporters/report-path-sanitizer.js +137 -0
  50. package/dist/exporters/report-path-sanitizer.js.map +1 -0
  51. package/dist/exporters/result-variants.d.ts.map +1 -1
  52. package/dist/exporters/result-variants.js +2 -1
  53. package/dist/exporters/result-variants.js.map +1 -1
  54. package/dist/exporters/sensitive-key.d.ts +31 -0
  55. package/dist/exporters/sensitive-key.d.ts.map +1 -0
  56. package/dist/exporters/sensitive-key.js +50 -0
  57. package/dist/exporters/sensitive-key.js.map +1 -0
  58. package/dist/exporters/test-data-resolver.d.ts +4 -1
  59. package/dist/exporters/test-data-resolver.d.ts.map +1 -1
  60. package/dist/exporters/test-data-resolver.js +12 -2
  61. package/dist/exporters/test-data-resolver.js.map +1 -1
  62. package/dist/generators/gherkin-parser/index.d.ts +3 -0
  63. package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
  64. package/dist/generators/gherkin-parser/index.js +17 -9
  65. package/dist/generators/gherkin-parser/index.js.map +1 -1
  66. package/dist/generators/gherkin-parser/parent-scope-clause.d.ts +54 -0
  67. package/dist/generators/gherkin-parser/parent-scope-clause.d.ts.map +1 -0
  68. package/dist/generators/gherkin-parser/parent-scope-clause.js +109 -0
  69. package/dist/generators/gherkin-parser/parent-scope-clause.js.map +1 -0
  70. package/dist/generators/test-generator/adapters/adapter-interface.d.ts +23 -18
  71. package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
  72. package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts +4 -19
  73. package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts.map +1 -1
  74. package/dist/generators/test-generator/adapters/appium/appium-adapter.js.map +1 -1
  75. package/dist/generators/test-generator/adapters/appium/templates/after-all.hbs +18 -0
  76. package/dist/generators/test-generator/adapters/appium/templates/after-each.hbs +18 -0
  77. package/dist/generators/test-generator/adapters/appium/templates/before-all.hbs +18 -0
  78. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/click-at-position-action.hbs +1 -0
  79. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/click-element-with-text.hbs +1 -1
  80. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/double-click-action.hbs +7 -0
  81. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/frame-enter-action.hbs +3 -6
  82. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/table-paginate-action.hbs +3 -0
  83. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-assertion.hbs +1 -1
  84. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/long-press-action.hbs +4 -0
  85. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/pinch-zoom-action.hbs +4 -0
  86. package/dist/generators/test-generator/adapters/appium/templates/steps/partials/locator.hbs +9 -1
  87. package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +54 -23
  88. package/dist/generators/test-generator/adapters/index.d.ts +1 -1
  89. package/dist/generators/test-generator/adapters/index.d.ts.map +1 -1
  90. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +4 -19
  91. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
  92. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
  93. package/dist/generators/test-generator/adapters/playwright/templates/after-all.hbs +19 -1
  94. package/dist/generators/test-generator/adapters/playwright/templates/after-each.hbs +18 -0
  95. package/dist/generators/test-generator/adapters/playwright/templates/before-all.hbs +19 -1
  96. package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  97. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/check-action.hbs +6 -1
  98. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/click-at-position-action.hbs +23 -0
  99. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/expand-action.hbs +8 -8
  100. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/fill-editor-action.hbs +7 -2
  101. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row-nth.hbs +3 -1
  102. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +3 -1
  103. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-paginate-action.hbs +5 -0
  104. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/uncheck-action.hbs +3 -1
  105. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/unknown-element-action.hbs +1 -1
  106. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/wait-for-page.hbs +18 -1
  107. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-assertion.hbs +32 -4
  108. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-element.hbs +5 -2
  109. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/checked-assertion.hbs +5 -1
  110. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/column-cell-assertion.hbs +13 -2
  111. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/contain-text-assertion.hbs +1 -1
  112. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +2 -2
  113. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-variable-assertion.hbs +1 -1
  114. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/have-text-assertion.hbs +1 -1
  115. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +2 -2
  116. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-variable-assertion.hbs +1 -1
  117. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/not-checked-assertion.hbs +2 -1
  118. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/selected-assertion.hbs +14 -3
  119. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/state-with-filter-assertion.hbs +13 -2
  120. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +3 -3
  121. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-value-assertion.hbs +1 -1
  122. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-variable-assertion.hbs +1 -1
  123. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/checked-opts.hbs +8 -0
  124. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-nth.hbs +1 -1
  125. package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +10 -8
  126. package/dist/generators/test-generator/code-generator.d.ts +37 -0
  127. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  128. package/dist/generators/test-generator/code-generator.js +160 -39
  129. package/dist/generators/test-generator/code-generator.js.map +1 -1
  130. package/dist/generators/test-generator/diagnostics.d.ts +79 -5
  131. package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
  132. package/dist/generators/test-generator/diagnostics.js +78 -4
  133. package/dist/generators/test-generator/diagnostics.js.map +1 -1
  134. package/dist/generators/test-generator/patterns/index.d.ts +24 -5
  135. package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
  136. package/dist/generators/test-generator/patterns/index.js +92 -14
  137. package/dist/generators/test-generator/patterns/index.js.map +1 -1
  138. package/dist/generators/test-generator/patterns/with-parent-scope.d.ts +18 -0
  139. package/dist/generators/test-generator/patterns/with-parent-scope.d.ts.map +1 -0
  140. package/dist/generators/test-generator/patterns/with-parent-scope.js +40 -0
  141. package/dist/generators/test-generator/patterns/with-parent-scope.js.map +1 -0
  142. package/dist/generators/test-generator/step-mapper.d.ts +63 -9
  143. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  144. package/dist/generators/test-generator/step-mapper.js +166 -28
  145. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  146. package/dist/generators/test-generator/template-engine.d.ts +4 -19
  147. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  148. package/dist/generators/test-generator/template-engine.js +17 -12
  149. package/dist/generators/test-generator/template-engine.js.map +1 -1
  150. package/dist/generators/test-generator/utils/data-resolver.d.ts +7 -0
  151. package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
  152. package/dist/generators/test-generator/utils/data-resolver.js +19 -2
  153. package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
  154. package/dist/generators/test-generator/utils/dialog-scope-identity.d.ts +41 -0
  155. package/dist/generators/test-generator/utils/dialog-scope-identity.d.ts.map +1 -0
  156. package/dist/generators/test-generator/utils/dialog-scope-identity.js +61 -0
  157. package/dist/generators/test-generator/utils/dialog-scope-identity.js.map +1 -0
  158. package/dist/generators/test-generator/utils/escape-js-string.d.ts +17 -0
  159. package/dist/generators/test-generator/utils/escape-js-string.d.ts.map +1 -0
  160. package/dist/generators/test-generator/utils/escape-js-string.js +29 -0
  161. package/dist/generators/test-generator/utils/escape-js-string.js.map +1 -0
  162. package/dist/generators/test-generator/utils/nearest-match.d.ts +24 -0
  163. package/dist/generators/test-generator/utils/nearest-match.d.ts.map +1 -0
  164. package/dist/generators/test-generator/utils/nearest-match.js +56 -0
  165. package/dist/generators/test-generator/utils/nearest-match.js.map +1 -0
  166. package/dist/generators/test-generator/utils/selector-entry-fields.d.ts +58 -0
  167. package/dist/generators/test-generator/utils/selector-entry-fields.d.ts.map +1 -0
  168. package/dist/generators/test-generator/utils/selector-entry-fields.js +119 -0
  169. package/dist/generators/test-generator/utils/selector-entry-fields.js.map +1 -0
  170. package/dist/generators/test-generator/utils/selector-entry-walk.d.ts +25 -0
  171. package/dist/generators/test-generator/utils/selector-entry-walk.d.ts.map +1 -0
  172. package/dist/generators/test-generator/utils/selector-entry-walk.js +54 -0
  173. package/dist/generators/test-generator/utils/selector-entry-walk.js.map +1 -0
  174. package/dist/generators/test-generator/utils/selector-nth-validation.d.ts +59 -0
  175. package/dist/generators/test-generator/utils/selector-nth-validation.d.ts.map +1 -0
  176. package/dist/generators/test-generator/utils/selector-nth-validation.js +91 -0
  177. package/dist/generators/test-generator/utils/selector-nth-validation.js.map +1 -0
  178. package/dist/generators/test-generator/utils/selector-resolver.d.ts +30 -1
  179. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
  180. package/dist/generators/test-generator/utils/selector-resolver.js +195 -15
  181. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
  182. package/dist/generators/test-generator/utils/selector-type-validation.d.ts +51 -0
  183. package/dist/generators/test-generator/utils/selector-type-validation.d.ts.map +1 -0
  184. package/dist/generators/test-generator/utils/selector-type-validation.js +77 -0
  185. package/dist/generators/test-generator/utils/selector-type-validation.js.map +1 -0
  186. package/dist/harness/data-driven-lint.d.ts.map +1 -1
  187. package/dist/harness/data-driven-lint.js +217 -33
  188. package/dist/harness/data-driven-lint.js.map +1 -1
  189. package/dist/harness/script-check.d.ts +2 -2
  190. package/dist/harness/script-check.d.ts.map +1 -1
  191. package/dist/harness/script-check.js +186 -25
  192. package/dist/harness/script-check.js.map +1 -1
  193. package/dist/index.d.ts +2 -0
  194. package/dist/index.d.ts.map +1 -1
  195. package/dist/index.js +10 -1
  196. package/dist/index.js.map +1 -1
  197. package/dist/orchestrator/project-initializer.d.ts +6 -0
  198. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  199. package/dist/orchestrator/project-initializer.js +16 -0
  200. package/dist/orchestrator/project-initializer.js.map +1 -1
  201. package/dist/orchestrator/report-sanitize-reporter-install.d.ts +24 -0
  202. package/dist/orchestrator/report-sanitize-reporter-install.d.ts.map +1 -0
  203. package/dist/orchestrator/report-sanitize-reporter-install.js +161 -0
  204. package/dist/orchestrator/report-sanitize-reporter-install.js.map +1 -0
  205. package/dist/orchestrator/templates/ai-src/commands/run-test.md +9 -1
  206. package/dist/orchestrator/templates/ai-src/skills/sungen-api-coverage-model/SKILL.md +10 -0
  207. package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +14 -2
  208. package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +108 -8
  209. package/dist/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +1 -0
  210. package/dist/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +9 -2
  211. package/dist/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +4 -4
  212. package/dist/orchestrator/templates/playwright.config.ts +7 -0
  213. package/dist/orchestrator/templates/specs-checked-state.d.ts +83 -0
  214. package/dist/orchestrator/templates/specs-checked-state.d.ts.map +1 -0
  215. package/dist/orchestrator/templates/specs-checked-state.js +261 -0
  216. package/dist/orchestrator/templates/specs-checked-state.js.map +1 -0
  217. package/dist/orchestrator/templates/specs-checked-state.ts +324 -0
  218. package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.d.ts +36 -0
  219. package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.d.ts.map +1 -0
  220. package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.js +155 -0
  221. package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.js.map +1 -0
  222. package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.ts +142 -0
  223. package/dist/orchestrator/templates/specs-url-assert.d.ts +50 -25
  224. package/dist/orchestrator/templates/specs-url-assert.d.ts.map +1 -1
  225. package/dist/orchestrator/templates/specs-url-assert.js +65 -24
  226. package/dist/orchestrator/templates/specs-url-assert.js.map +1 -1
  227. package/dist/orchestrator/templates/specs-url-assert.ts +101 -28
  228. package/dist/utils/selector-types.d.ts +43 -1
  229. package/dist/utils/selector-types.d.ts.map +1 -1
  230. package/dist/utils/selector-types.js +51 -7
  231. package/dist/utils/selector-types.js.map +1 -1
  232. package/package.json +3 -3
  233. package/src/cli/commands/delivery.ts +2 -1
  234. package/src/cli/commands/generate.ts +5 -2
  235. package/src/cli/commands/inspect.ts +1 -1
  236. package/src/cli/commands/results-merge.ts +9 -2
  237. package/src/cli/commands/results-sanitize.ts +98 -0
  238. package/src/cli/commands/script-check.ts +3 -1
  239. package/src/cli/commands/update.ts +3 -0
  240. package/src/exporters/api-testcase-formatter.ts +5 -7
  241. package/src/exporters/matrix/build.ts +4 -2
  242. package/src/exporters/matrix/render-coverage-sheet.ts +3 -1
  243. package/src/exporters/matrix/render-csv.ts +3 -1
  244. package/src/exporters/matrix/render-overview-sheet.ts +5 -10
  245. package/src/exporters/matrix/result-totals.ts +37 -0
  246. package/src/exporters/matrix/testcase-summary-band.ts +2 -7
  247. package/src/exporters/qa-env-secret-denylist.ts +1 -1
  248. package/src/exporters/report-path-sanitizer.ts +144 -0
  249. package/src/exporters/result-variants.ts +2 -1
  250. package/src/exporters/sensitive-key.ts +48 -0
  251. package/src/exporters/test-data-resolver.ts +12 -2
  252. package/src/generators/gherkin-parser/index.ts +21 -10
  253. package/src/generators/gherkin-parser/parent-scope-clause.ts +130 -0
  254. package/src/generators/test-generator/adapters/adapter-interface.ts +25 -3
  255. package/src/generators/test-generator/adapters/appium/appium-adapter.ts +4 -4
  256. package/src/generators/test-generator/adapters/appium/templates/after-all.hbs +18 -0
  257. package/src/generators/test-generator/adapters/appium/templates/after-each.hbs +18 -0
  258. package/src/generators/test-generator/adapters/appium/templates/before-all.hbs +18 -0
  259. package/src/generators/test-generator/adapters/appium/templates/steps/actions/click-at-position-action.hbs +1 -0
  260. package/src/generators/test-generator/adapters/appium/templates/steps/actions/click-element-with-text.hbs +1 -1
  261. package/src/generators/test-generator/adapters/appium/templates/steps/actions/double-click-action.hbs +7 -0
  262. package/src/generators/test-generator/adapters/appium/templates/steps/actions/frame-enter-action.hbs +3 -6
  263. package/src/generators/test-generator/adapters/appium/templates/steps/actions/table-paginate-action.hbs +3 -0
  264. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-assertion.hbs +1 -1
  265. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/long-press-action.hbs +4 -0
  266. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/pinch-zoom-action.hbs +4 -0
  267. package/src/generators/test-generator/adapters/appium/templates/steps/partials/locator.hbs +9 -1
  268. package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +54 -23
  269. package/src/generators/test-generator/adapters/index.ts +1 -1
  270. package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +4 -4
  271. package/src/generators/test-generator/adapters/playwright/templates/after-all.hbs +19 -1
  272. package/src/generators/test-generator/adapters/playwright/templates/after-each.hbs +18 -0
  273. package/src/generators/test-generator/adapters/playwright/templates/before-all.hbs +19 -1
  274. package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  275. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/check-action.hbs +6 -1
  276. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/click-at-position-action.hbs +23 -0
  277. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/expand-action.hbs +8 -8
  278. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/fill-editor-action.hbs +7 -2
  279. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row-nth.hbs +3 -1
  280. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +3 -1
  281. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-paginate-action.hbs +5 -0
  282. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/uncheck-action.hbs +3 -1
  283. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/unknown-element-action.hbs +1 -1
  284. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/wait-for-page.hbs +18 -1
  285. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-assertion.hbs +32 -4
  286. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-element.hbs +5 -2
  287. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/checked-assertion.hbs +5 -1
  288. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/column-cell-assertion.hbs +13 -2
  289. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/contain-text-assertion.hbs +1 -1
  290. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +2 -2
  291. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-variable-assertion.hbs +1 -1
  292. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/have-text-assertion.hbs +1 -1
  293. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +2 -2
  294. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-variable-assertion.hbs +1 -1
  295. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/not-checked-assertion.hbs +2 -1
  296. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/selected-assertion.hbs +14 -3
  297. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/state-with-filter-assertion.hbs +13 -2
  298. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +3 -3
  299. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-value-assertion.hbs +1 -1
  300. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-variable-assertion.hbs +1 -1
  301. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/checked-opts.hbs +8 -0
  302. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-nth.hbs +1 -1
  303. package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +10 -8
  304. package/src/generators/test-generator/code-generator.ts +176 -45
  305. package/src/generators/test-generator/diagnostics.ts +79 -5
  306. package/src/generators/test-generator/patterns/index.ts +97 -16
  307. package/src/generators/test-generator/patterns/with-parent-scope.ts +41 -0
  308. package/src/generators/test-generator/step-mapper.ts +170 -26
  309. package/src/generators/test-generator/template-engine.ts +21 -21
  310. package/src/generators/test-generator/utils/data-resolver.ts +23 -2
  311. package/src/generators/test-generator/utils/dialog-scope-identity.ts +71 -0
  312. package/src/generators/test-generator/utils/escape-js-string.ts +24 -0
  313. package/src/generators/test-generator/utils/nearest-match.ts +55 -0
  314. package/src/generators/test-generator/utils/selector-entry-fields.ts +131 -0
  315. package/src/generators/test-generator/utils/selector-entry-walk.ts +58 -0
  316. package/src/generators/test-generator/utils/selector-nth-validation.ts +99 -0
  317. package/src/generators/test-generator/utils/selector-resolver.ts +245 -16
  318. package/src/generators/test-generator/utils/selector-type-validation.ts +87 -0
  319. package/src/harness/data-driven-lint.ts +192 -32
  320. package/src/harness/script-check.ts +186 -21
  321. package/src/index.ts +7 -0
  322. package/src/orchestrator/project-initializer.ts +17 -0
  323. package/src/orchestrator/report-sanitize-reporter-install.ts +109 -0
  324. package/src/orchestrator/templates/ai-src/commands/run-test.md +9 -1
  325. package/src/orchestrator/templates/ai-src/skills/sungen-api-coverage-model/SKILL.md +10 -0
  326. package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +14 -2
  327. package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +108 -8
  328. package/src/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +1 -0
  329. package/src/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +9 -2
  330. package/src/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +4 -4
  331. package/src/orchestrator/templates/playwright.config.ts +7 -0
  332. package/src/orchestrator/templates/specs-checked-state.ts +324 -0
  333. package/src/orchestrator/templates/specs-report-path-sanitize-reporter.ts +142 -0
  334. package/src/orchestrator/templates/specs-url-assert.ts +101 -28
  335. package/src/utils/selector-types.ts +54 -5
@@ -1,8 +1,26 @@
1
1
  after(async () => {
2
+ {{#if collect}}
3
+ // Hook checks are collected: every failed step is recorded and the hook fails ONCE with the full list
4
+ const __hookFailures: string[] = [];
5
+ {{/if}}
2
6
  {{#each steps}}
3
7
  {{#if comment}}
4
8
  // {{comment}}
5
9
  {{/if}}
10
+ {{#if wrap}}
11
+ try {
12
+ {{code}}
13
+ } catch (e) {
14
+ __hookFailures.push({{label}} + ': ' + String((e as Error).message).split('\n')[0]);
15
+ {{#if stopOnFailure}}
16
+ if (__hookFailures.length) throw new Error('[sungen] hook preconditions not met:\n' + __hookFailures.join('\n'));
17
+ {{/if}}
18
+ }
19
+ {{else}}
6
20
  {{code}}
21
+ {{/if}}
7
22
  {{/each}}
23
+ {{#if collect}}
24
+ if (__hookFailures.length) throw new Error('[sungen] hook preconditions not met:\n' + __hookFailures.join('\n'));
25
+ {{/if}}
8
26
  });
@@ -1,8 +1,26 @@
1
1
  afterEach(async () => {
2
+ {{#if collect}}
3
+ // Hook checks are collected: every failed step is recorded and the hook fails ONCE with the full list
4
+ const __hookFailures: string[] = [];
5
+ {{/if}}
2
6
  {{#each steps}}
3
7
  {{#if comment}}
4
8
  // {{comment}}
5
9
  {{/if}}
10
+ {{#if wrap}}
11
+ try {
12
+ {{code}}
13
+ } catch (e) {
14
+ __hookFailures.push({{label}} + ': ' + String((e as Error).message).split('\n')[0]);
15
+ {{#if stopOnFailure}}
16
+ if (__hookFailures.length) throw new Error('[sungen] hook preconditions not met:\n' + __hookFailures.join('\n'));
17
+ {{/if}}
18
+ }
19
+ {{else}}
6
20
  {{code}}
21
+ {{/if}}
7
22
  {{/each}}
23
+ {{#if collect}}
24
+ if (__hookFailures.length) throw new Error('[sungen] hook preconditions not met:\n' + __hookFailures.join('\n'));
25
+ {{/if}}
8
26
  });
@@ -1,8 +1,26 @@
1
1
  before(async () => {
2
+ {{#if collect}}
3
+ // Hook checks are collected: every failed step is recorded and the hook fails ONCE with the full list
4
+ const __hookFailures: string[] = [];
5
+ {{/if}}
2
6
  {{#each steps}}
3
7
  {{#if comment}}
4
8
  // {{comment}}
5
9
  {{/if}}
10
+ {{#if wrap}}
11
+ try {
12
+ {{code}}
13
+ } catch (e) {
14
+ __hookFailures.push({{label}} + ': ' + String((e as Error).message).split('\n')[0]);
15
+ {{#if stopOnFailure}}
16
+ if (__hookFailures.length) throw new Error('[sungen] hook preconditions not met:\n' + __hookFailures.join('\n'));
17
+ {{/if}}
18
+ }
19
+ {{else}}
6
20
  {{code}}
21
+ {{/if}}
7
22
  {{/each}}
23
+ {{#if collect}}
24
+ if (__hookFailures.length) throw new Error('[sungen] hook preconditions not met:\n' + __hookFailures.join('\n'));
25
+ {{/if}}
8
26
  });
@@ -0,0 +1 @@
1
+ throw new Error('[sungen] "click … at position/offset" is web-only — tap the control the position targets directly ("User tap [X] button"), or tag the scenario @manual');
@@ -1,2 +1,2 @@
1
1
  {{!-- Tap an element by its visible text / content-desc (dynamic text match). --}}
2
- {{#if nth}}await $$('//*[contains(@content-desc,"{{escapeQuotes dataValue}}") or contains(@text,"{{escapeQuotes dataValue}}")]')[{{nth}}].click();{{else}}await $('//*[contains(@content-desc,"{{escapeQuotes dataValue}}") or contains(@text,"{{escapeQuotes dataValue}}")]').click();{{/if}}
2
+ {{#if (gte nth -1)}}await (await $$('//*[contains(@content-desc,"{{escapeQuotes dataValue}}") or contains(@text,"{{escapeQuotes dataValue}}")]')).at({{nth}}).click();{{else}}await $('//*[contains(@content-desc,"{{escapeQuotes dataValue}}") or contains(@text,"{{escapeQuotes dataValue}}")]').click();{{/if}}
@@ -1,6 +1,13 @@
1
1
  {{!-- Double-tap. Use the platform gesture (element.doubleClick is web-oriented). --}}
2
2
  {
3
3
  const __el = await {{> locator}};
4
+ // Fail loud with the REAL cause when the element never resolved. `driver.execute` does NOT do
5
+ // WDIO's implicit existence wait (every other action template gets that for free by going
6
+ // through .click()/.setValue()/expect), and the locator partial returns $(selector) even for a
7
+ // never-found element by design. So an undefined elementId reaches Appium, which falls through
8
+ // to its coordinates branch and reports "Double click offset coordinates must be provided if
9
+ // element is not set" — a message about offsets for what is really a not-found element (#668).
10
+ if (!__el.elementId) throw new Error((__el as any).error?.message || `Element not found: ${__el.selector}`);
4
11
  if (driver.isIOS) await driver.execute('mobile: doubleTap', { elementId: __el.elementId });
5
12
  else await driver.execute('mobile: doubleClickGesture', { elementId: __el.elementId });
6
13
  }
@@ -1,6 +1,3 @@
1
- {{!-- Hybrid app: switch into the webview context (mobile equivalent of entering an iframe). --}}
2
- {
3
- const __ctxs = await driver.getContexts();
4
- const __web = __ctxs.find(c => /WEBVIEW|CHROMIUM/i.test(typeof c === 'string' ? c : c.id));
5
- if (__web) await driver.switchContext(typeof __web === 'string' ? __web : __web.id);
6
- }
1
+ {{!-- Hybrid app: switch into the webview context (mobile equivalent of entering an iframe).
2
+ Polls for the context and throws when it never appears — see __enterWebContext (#705). --}}
3
+ await __enterWebContext('{{escapeQuotes frameRef}}');
@@ -0,0 +1,3 @@
1
+ {{!-- An HTML paginator is a web control: a native list pages by scrolling, not by a next/prev
2
+ button pair, so there is nothing honest to emit here. --}}
3
+ throw new Error("[sungen] 'go to next/prev page in table' drives an HTML paginator and is not supported on the appium adapter. Scroll the list instead, tap the pager control as a plain tap step, or tag this scenario @manual.");
@@ -8,7 +8,7 @@
8
8
  await expect({{> locator}}).toBeDisplayed();
9
9
  {{~else if scope~}}
10
10
  await expect({{> locator}}).toBeDisplayed();
11
- {{~else if nth~}}
11
+ {{~else if (gte nth -1)~}}
12
12
  await expect({{> locator}}).toBeDisplayed();
13
13
  {{~else~}}
14
14
  await __assertVisible({{> appium-selector-expr}});
@@ -1,4 +1,8 @@
1
1
  await (async () => { const e = await {{> locator}};
2
+ // Fail loud on a missing target — `driver.execute` skips WDIO's implicit existence wait, so an
3
+ // undefined elementId surfaces as an opaque coordinates/offset error instead of the not-found
4
+ // that actually happened (#668).
5
+ if (!e.elementId) throw new Error((e as any).error?.message || `Element not found: ${e.selector}`);
2
6
  // XCUITest `mobile: touchAndHold` takes SECONDS; UiAutomator2 `mobile: longClickGesture` takes ms.
3
7
  if (driver.isIOS) { await driver.execute('mobile: touchAndHold', { elementId: e.elementId, duration: {{durationMs}} / 1000 }); }
4
8
  else { await driver.execute('mobile: longClickGesture', { elementId: e.elementId, duration: {{durationMs}} }); }
@@ -1,4 +1,8 @@
1
1
  await (async () => { const e = await {{> locator}};
2
+ // Fail loud on a missing target — `driver.execute` skips WDIO's implicit existence wait, so an
3
+ // undefined elementId surfaces as an opaque coordinates/offset error instead of the not-found
4
+ // that actually happened (#668).
5
+ if (!e.elementId) throw new Error((e as any).error?.message || `Element not found: ${e.selector}`);
2
6
  // XCUITest `mobile: pinch`: scale >1 zooms in, <1 zooms out (velocity sign must match).
3
7
  if (driver.isIOS) { await driver.execute('mobile: pinch', { elementId: e.elementId, scale: {{#if zoomOut}}0.5{{else}}2{{/if}}, velocity: {{#if zoomOut}}-1{{else}}1{{/if}} }); }
4
8
  else { await driver.execute('mobile: {{#if zoomOut}}pinchCloseGesture{{else}}pinchOpenGesture{{/if}}', { elementId: e.elementId, percent: 0.75 }); }
@@ -9,7 +9,15 @@
9
9
  scroll-into-view, so a below-fold / nav-occluded element is scrolled into the safe band before any
10
10
  find/tap/assert (Appium has no auto-scroll). Parent/scope/nth chains keep the plain `$()` — those
11
11
  resolve within a container / by index where a blind scroll doesn't apply.
12
+ `nth` is an INDEX, gated numerically (`gte nth -1`), not on truthiness (#720): `{{#if nth}}` read
13
+ `nth: 0` — the index every doc page uses as its example — as "no index declared" and sent it down
14
+ the __ensureVisible branch, while the web adapter indexed it, so the two adapters disagreed on the
15
+ one value authors actually write. It renders `(await $$(expr)).at(n)`: `$$` is a
16
+ ChainablePromiseArray in WDIO v9, so the old `$$(expr)[n]` indexed a promise and was `undefined`
17
+ for EVERY n (the awaited form is what the mobile helpers elsewhere in the emitted spec already
18
+ use), and `.at()` carries `-1` = last match with no second branch. The resolver has already
19
+ normalised anything outside `>= -1` to undefined and reported it as SG-W018.
12
20
  `plain=true` (passed by is-hidden-assertion) also forces the bare `$()`: an element asserted HIDDEN
13
21
  is expected to be absent, so scroll-hunting for it is both pointless and harmful — the 12x down-scroll
14
22
  would drag the screen to the bottom and strand later same-scenario steps that sit above. --}}
15
- {{~#if parentLocator~}}{{parentLocator}}.{{#if nth}}$$({{> appium-selector-expr}})[{{nth}}]{{else}}$({{> appium-selector-expr}}){{/if}}{{~else~}}{{#if scope}}$('~{{escapeQuotes scope}}').{{#if nth}}$$({{> appium-selector-expr}})[{{nth}}]{{else}}$({{> appium-selector-expr}}){{/if}}{{else}}{{#if nth}}$$({{> appium-selector-expr}})[{{nth}}]{{else}}{{#if plain}}$({{> appium-selector-expr}}){{else}}(await __ensureVisible({{> appium-selector-expr}})){{/if}}{{/if}}{{/if}}{{~/if~}}
23
+ {{~#if parentLocator~}}{{#if (gte nth -1)}}(await {{parentLocator}}.$$({{> appium-selector-expr}})).at({{nth}}){{else}}{{parentLocator}}.$({{> appium-selector-expr}}){{/if}}{{~else~}}{{#if scope}}{{#if (gte nth -1)}}(await $('~{{escapeQuotes scope}}').$$({{> appium-selector-expr}})).at({{nth}}){{else}}$('~{{escapeQuotes scope}}').$({{> appium-selector-expr}}){{/if}}{{else}}{{#if (gte nth -1)}}(await $$({{> appium-selector-expr}})).at({{nth}}){{else}}{{#if plain}}$({{> appium-selector-expr}}){{else}}(await __ensureVisible({{> appium-selector-expr}})){{/if}}{{/if}}{{/if}}{{~/if~}}
@@ -210,6 +210,36 @@ async function __assertCheckedState(el: WebdriverIO.Element, expected: boolean,
210
210
  }
211
211
  throw new Error(`Expect ${label} to be ${want} — read [${signals.map(s => `${s.source} → ${s.value}`).join(', ')}]`);
212
212
  }
213
+
214
+ // Entering a "frame" on a native driver means switching into the WebView CONTEXT — there is no
215
+ // iframe to locate. That context does not exist until the WebView runtime has created it, and the
216
+ // tap that kicks off the navigation returns long before that, so a ONE-SHOT getContexts() reads a
217
+ // list that holds only NATIVE_APP. Skipping the switch on that reading is the worst possible
218
+ // answer: every later step then searches the native tree, where a hybrid WebView is a SINGLE
219
+ // opaque leaf with no children — the target can never appear there, so no downstream wait can
220
+ // recover it and the run dies with a misleading "element still not displayed" (#705). Poll for the
221
+ // context instead, and throw when it never arrives: a step that reads "switch to X frame" must
222
+ // never quietly not switch.
223
+ async function __enterWebContext(label: string): Promise<void> {
224
+ const budget = Number((driver as any)?.options?.waitforTimeout) || 30000;
225
+ const deadline = Date.now() + budget;
226
+ let seen: any[] = [];
227
+ for (;;) {
228
+ seen = await driver.getContexts();
229
+ const web = seen.find(c => /WEBVIEW|CHROMIUM/i.test(typeof c === 'string' ? c : c.id));
230
+ if (web) { await driver.switchContext(typeof web === 'string' ? web : web.id); return; }
231
+ if (Date.now() >= deadline) break;
232
+ await driver.pause(500);
233
+ }
234
+ throw new Error(
235
+ `[sungen] "switch to ${label} frame" — no WEBVIEW/CHROMIUM context appeared within ${budget}ms ` +
236
+ `(contexts seen: ${JSON.stringify(seen)}). The driver is still in NATIVE_APP, where a hybrid WebView ` +
237
+ `is one opaque node with no children, so every later step would fail as "element not displayed" ` +
238
+ `while the real cause is this switch. If the transition is genuinely slower than ${budget}ms, raise ` +
239
+ `waitforTimeout in wdio.conf.ts; if the screen has no WebView, drop the frame step and assert the ` +
240
+ `native markers instead.`,
241
+ );
242
+ }
213
243
  {{#if singleAuthRole}}
214
244
  import * as fs from 'node:fs';
215
245
  import * as path from 'node:path';
@@ -340,23 +370,25 @@ describe('{{featureName}}', () => {
340
370
  // @auth:{{singleAuthRole}} — authenticate ONCE before any scenario; the session persists across
341
371
  // the per-scenario relaunch below (noReset:true). Throws (blocks the spec) if login can't be done.
342
372
  before(async () => {
343
- {{#if appPackage}}
344
373
  // Relaunch to a known launch screen first — the session may attach to the app in any state.
345
374
  // Platform-aware id (NOT the Android literal): on iOS a @platform:mobile feature's app is the
346
375
  // dual-id bundle id — a literal appPackage here would throw and block the whole spec.
347
- // (__resolveAppId is a hoisted function declaration in this describe scope.)
376
+ // (__resolveAppId is a hoisted function declaration in this describe scope. It is no longer
377
+ // gated on a `Path:`-derived package (#669): it resolves the session capability too, and throws
378
+ // a named error when nothing supplies an id at all.)
348
379
  const __AUTH_APP__ = __resolveAppId();
349
380
  try { await driver.terminateApp(__AUTH_APP__); } catch { /* not running */ }
350
381
  await driver.activateApp(__AUTH_APP__);
351
- {{/if}}
352
382
  await __ensureAuth('{{singleAuthRole}}');
353
383
  });
354
384
 
355
385
  {{/if}}
356
- {{#if appPackage}}
357
386
  // Multi-app: this feature's app (from the `Path:` line). Reset to its launch screen before each
358
387
  // scenario so one `wdio run` can host screens from different apps — each spec launches its own.
359
388
  // The `Path:` holds the ANDROID package (or, for an @platform:ios feature, the iOS bundle id).
389
+ // EMPTY when the feature declared no usable `Path:` (#669) — the session capability then supplies
390
+ // the id (see __resolveAppId). Emitted unconditionally so that a feature without a `Path:` still
391
+ // gets per-scenario reset + Background instead of silently dropping to a once-per-file before().
360
392
  const __APP_PKG_DEFAULT__ = '{{appPackage}}';
361
393
  {{#if iosBundleId}}
362
394
  // Per-feature iOS bundle id from the dual-id `Path: <androidPkg>/<activity> | <iosBundleId>` —
@@ -366,13 +398,26 @@ describe('{{featureName}}', () => {
366
398
  {{/if}}
367
399
  // Platform-aware app id: an @platform:mobile spec runs on BOTH OSes. On iOS prefer the feature's own
368
400
  // bundle id (dual-id Path), then the session cap (IOS_BUNDLE_ID in wdio.conf), then the Path value;
369
- // on Android (or any non-iOS session) use the Path package unchanged.
401
+ // on Android (or any non-iOS session) prefer the Path package, then the session cap.
402
+ // The session-capability fallback (#669) is what lets a feature with no app-package `Path:` keep a
403
+ // real per-scenario reset: wdio.conf sets `appium:appPackage` for the run, so the id the compiler
404
+ // could not read off the feature file is almost always present here. Throwing when NOTHING supplies
405
+ // it is deliberate — the alternative was the silent once-per-file before() this issue is about,
406
+ // where scenarios leak tab/scroll/login state into each other and the suite lies about why it
407
+ // failed. SG-W029 already named this at compile time, so this throw is never the first warning.
370
408
  function __resolveAppId(): string {
371
- if (typeof driver !== 'undefined' && driver.isIOS) {
372
- const caps = (driver.requestedCapabilities || {}) as Record<string, string>;
373
- return {{#if iosBundleId}}__IOS_BUNDLE__ || {{/if}}caps['appium:bundleId'] || caps['bundleId'] || __APP_PKG_DEFAULT__;
409
+ const caps = (typeof driver !== 'undefined' ? (driver.requestedCapabilities || {}) : {}) as Record<string, string>;
410
+ const resolved = (typeof driver !== 'undefined' && driver.isIOS)
411
+ ? ({{#if iosBundleId}}__IOS_BUNDLE__ || {{/if}}caps['appium:bundleId'] || caps['bundleId'] || __APP_PKG_DEFAULT__ || caps['appium:appPackage'] || caps['appPackage'])
412
+ : (__APP_PKG_DEFAULT__ || caps['appium:appPackage'] || caps['appPackage'] || caps['appium:bundleId'] || caps['bundleId']);
413
+ if (!resolved) {
414
+ throw new Error(
415
+ `[sungen] cannot resolve this app's id, so no per-scenario reset is possible (SG-W029). ` +
416
+ `Add an app-package \`Path:\` to the feature — \`Path: com.example.app/.MainActivity\` — ` +
417
+ `or set APP_PACKAGE (Android) / IOS_BUNDLE_ID (iOS) so wdio.conf puts it on the session.`,
418
+ );
374
419
  }
375
- return __APP_PKG_DEFAULT__;
420
+ return resolved;
376
421
  }
377
422
  // ── Reset tier ────────────────────────────────────────────────────────────────────────────────
378
423
  // How much state each scenario starts from (`@reset:` on the Feature; default `soft`):
@@ -583,20 +628,6 @@ describe('{{featureName}}', () => {
583
628
  {{/if}}
584
629
  });
585
630
 
586
- {{/if}}
587
- {{#unless appPackage}}
588
- {{#if backgroundSteps}}
589
- before(async () => {
590
- {{#each backgroundSteps}}
591
- {{#if comment}}
592
- // {{comment}}
593
- {{/if}}
594
- {{code}}
595
- {{/each}}
596
- });
597
-
598
- {{/if}}
599
- {{/unless}}
600
631
  {{#if beforeAll}}
601
632
  {{beforeAll}}
602
633
 
@@ -1,4 +1,4 @@
1
- export { TestGeneratorAdapter, TestFileData, ScenarioData, StepTemplateData, LocatorExpression } from './adapter-interface';
1
+ export { TestGeneratorAdapter, TestFileData, ScenarioData, StepTemplateData, LocatorExpression, HookStep, HookRenderData } from './adapter-interface';
2
2
  export { AdapterRegistry, adapterRegistry, DriverNotInstalledError } from './adapter-registry';
3
3
  export { PlaywrightAdapter } from './playwright/playwright-adapter';
4
4
  export { AppiumAdapter } from './appium/appium-adapter';
@@ -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
  /**
@@ -34,15 +34,15 @@ export class PlaywrightAdapter implements TestGeneratorAdapter {
34
34
  return this.templateEngine.renderBeforeEach(data);
35
35
  }
36
36
 
37
- renderBeforeAll(data: { steps: Array<{ comment?: string; code: string }> }): string {
37
+ renderBeforeAll(data: HookRenderData): string {
38
38
  return this.templateEngine.renderBeforeAll(data);
39
39
  }
40
40
 
41
- renderAfterEach(data: { steps: Array<{ comment?: string; code: string }> }): string {
41
+ renderAfterEach(data: HookRenderData): string {
42
42
  return this.templateEngine.renderAfterEach(data);
43
43
  }
44
44
 
45
- renderAfterAll(data: { steps: Array<{ comment?: string; code: string }> }): string {
45
+ renderAfterAll(data: HookRenderData): string {
46
46
  return this.templateEngine.renderAfterAll(data);
47
47
  }
48
48
 
@@ -1,8 +1,26 @@
1
- test.afterAll(async ({ request }) => {
1
+ test.afterAll(async () => {
2
+ {{#if collect}}
3
+ // Hook checks are collected: every failed step is recorded and the hook fails ONCE with the full list
4
+ const __hookFailures: string[] = [];
5
+ {{/if}}
2
6
  {{#each steps}}
3
7
  {{#if comment}}
4
8
  // {{comment}}
5
9
  {{/if}}
10
+ {{#if wrap}}
11
+ try {
12
+ {{code}}
13
+ } catch (e) {
14
+ __hookFailures.push({{label}} + ': ' + String((e as Error).message).split('\n')[0]);
15
+ {{#if stopOnFailure}}
16
+ expect(__hookFailures, 'Hook preconditions not met').toEqual([]);
17
+ {{/if}}
18
+ }
19
+ {{else}}
6
20
  {{code}}
21
+ {{/if}}
7
22
  {{/each}}
23
+ {{#if collect}}
24
+ expect(__hookFailures, 'Hook preconditions not met').toEqual([]);
25
+ {{/if}}
8
26
  });
@@ -1,8 +1,26 @@
1
1
  test.afterEach(async ({ page }) => {
2
+ {{#if collect}}
3
+ // Hook checks are collected: every failed step is recorded and the hook fails ONCE with the full list
4
+ const __hookFailures: string[] = [];
5
+ {{/if}}
2
6
  {{#each steps}}
3
7
  {{#if comment}}
4
8
  // {{comment}}
5
9
  {{/if}}
10
+ {{#if wrap}}
11
+ try {
12
+ {{code}}
13
+ } catch (e) {
14
+ __hookFailures.push({{label}} + ': ' + String((e as Error).message).split('\n')[0]);
15
+ {{#if stopOnFailure}}
16
+ expect(__hookFailures, 'Hook preconditions not met').toEqual([]);
17
+ {{/if}}
18
+ }
19
+ {{else}}
6
20
  {{code}}
21
+ {{/if}}
7
22
  {{/each}}
23
+ {{#if collect}}
24
+ expect(__hookFailures, 'Hook preconditions not met').toEqual([]);
25
+ {{/if}}
8
26
  });
@@ -1,8 +1,26 @@
1
- test.beforeAll(async ({ request }) => {
1
+ test.beforeAll(async () => {
2
+ {{#if collect}}
3
+ // Hook checks are collected: every failed step is recorded and the hook fails ONCE with the full list
4
+ const __hookFailures: string[] = [];
5
+ {{/if}}
2
6
  {{#each steps}}
3
7
  {{#if comment}}
4
8
  // {{comment}}
5
9
  {{/if}}
10
+ {{#if wrap}}
11
+ try {
12
+ {{code}}
13
+ } catch (e) {
14
+ __hookFailures.push({{label}} + ': ' + String((e as Error).message).split('\n')[0]);
15
+ {{#if stopOnFailure}}
16
+ expect(__hookFailures, 'Hook preconditions not met').toEqual([]);
17
+ {{/if}}
18
+ }
19
+ {{else}}
6
20
  {{code}}
21
+ {{/if}}
7
22
  {{/each}}
23
+ {{#if collect}}
24
+ expect(__hookFailures, 'Hook preconditions not met').toEqual([]);
25
+ {{/if}}
8
26
  });
@@ -18,6 +18,9 @@ import { {{urlAssertImports}} } from '{{basePath}}/url-assert';
18
18
  {{#if regexEscapeImport}}
19
19
  import { escapeRegExp } from '{{basePath}}/regex-escape';
20
20
  {{/if}}
21
+ {{#if checkedStateImports}}
22
+ import { {{checkedStateImports}} } from '{{basePath}}/checked-state';
23
+ {{/if}}
21
24
 
22
25
  // This file is auto-generated from Gherkin feature files
23
26
  // DO NOT EDIT MANUALLY - changes will be overwritten
@@ -1 +1,6 @@
1
- await {{> locator}}.check();
1
+ {{!-- Playwright's .check() accepts ONLY native checkbox/radio and role=checkbox|radio|switch —
2
+ a `<button aria-pressed>` toggle threw "Not a checkbox or radio button" (#675). The branch
3
+ cannot be decided here: a testid/CSS selector carries no role, so the element is inspected at
4
+ run time by setCheckedState (specs/checked-state.ts), which also keeps the step IDEMPOTENT —
5
+ the reason `check` exists rather than `click`. --}}
6
+ await setCheckedState({{> locator}}, true, '{{escapeQuotes selectorRef}}'{{> checked-opts cls=checkedClass attr=checkedAttribute}});
@@ -0,0 +1,23 @@
1
+ {{#if clickPosition}}
2
+ {{!-- Positioned click: coordinates are relative to the element's PADDING box (clientWidth/Height),
3
+ the same box `click({ position })` measures from — boundingBox() would be the border box and
4
+ drift by the border width. Read once, checked, then clicked, so an offset past the real size
5
+ fails naming that size instead of landing on a neighbour. --}}
6
+ { const el = {{> locator}};
7
+ const __label = {{{label}}};
8
+ await el.waitFor({ state: 'visible' }); // the box is read once — not before the element has laid out
9
+ {{!-- padding box = bounding rect minus borders: clientWidth/Height would read 0 on an inline element (a link). --}}
10
+ const box = await el.evaluate((n) => {
11
+ const r = n.getBoundingClientRect(); const cs = getComputedStyle(n); const b = (p: string) => parseFloat(cs.getPropertyValue(p)) || 0;
12
+ return { w: r.width - b('border-left-width') - b('border-right-width'), h: r.height - b('border-top-width') - b('border-bottom-width') };
13
+ });
14
+ if (box.w <= 0 || box.h <= 0) throw new Error('[sungen] ' + __label + ': element has no layout box (hidden, detached or zero-sized)');
15
+ const x = {{#if (eq clickPosition.xUnit '%')}}box.w * {{clickPosition.xRatio}}{{else}}{{clickPosition.x}}{{/if}};
16
+ const y = {{#if (eq clickPosition.yUnit '%')}}box.h * {{clickPosition.yRatio}}{{else}}{{clickPosition.y}}{{/if}};
17
+ if (x >= box.w || y >= box.h) throw new Error('[sungen] ' + __label + ': click position (' + Math.round(x) + ', ' + Math.round(y) + ') lies outside the element box ' + Math.round(box.w) + 'x' + Math.round(box.h));
18
+ await el.click({ position: { x, y } });
19
+ }
20
+ {{else}}
21
+ {{!-- Unreachable through the compiler (SG-E026 replaces the step) — kept so a stray render fails loud. --}}
22
+ throw new Error('[sungen] click position could not be parsed');
23
+ {{/if}}
@@ -5,20 +5,20 @@ await el.click();
5
5
  {{else if expanded.class}}
6
6
  {{!-- State signalled by a class on a descendant (e.g. an accordion chevron icon). --}}
7
7
  {{#if (eq direction 'expand')}}
8
- if (!(await el.locator('.{{expanded.class}}').count())) { await el.click(); }
9
- await expect(el.locator('.{{expanded.class}}')).toBeVisible();
8
+ if (!(await el.locator('.{{escapeQuotes expanded.class}}').count())) { await el.click(); }
9
+ await expect(el.locator('.{{escapeQuotes expanded.class}}')).toBeVisible();
10
10
  {{else}}
11
- if (await el.locator('.{{expanded.class}}').count()) { await el.click(); }
12
- await expect(el.locator('.{{expanded.class}}')).toHaveCount(0);
11
+ if (await el.locator('.{{escapeQuotes expanded.class}}').count()) { await el.click(); }
12
+ await expect(el.locator('.{{escapeQuotes expanded.class}}')).toHaveCount(0);
13
13
  {{/if}}
14
14
  {{else if expanded.attribute}}
15
15
  {{!-- Explicit boolean attribute ('true'/'false'), custom or aria-expanded. --}}
16
16
  {{#if (eq direction 'expand')}}
17
- if (await el.getAttribute('{{expanded.attribute}}') !== 'true') { await el.click(); }
18
- await expect(el).toHaveAttribute('{{expanded.attribute}}', 'true');
17
+ if (await el.getAttribute('{{escapeQuotes expanded.attribute}}') !== 'true') { await el.click(); }
18
+ await expect(el).toHaveAttribute('{{escapeQuotes expanded.attribute}}', 'true');
19
19
  {{else}}
20
- if (await el.getAttribute('{{expanded.attribute}}') !== 'false') { await el.click(); }
21
- await expect(el).toHaveAttribute('{{expanded.attribute}}', 'false');
20
+ if (await el.getAttribute('{{escapeQuotes expanded.attribute}}') !== 'false') { await el.click(); }
21
+ await expect(el).toHaveAttribute('{{escapeQuotes expanded.attribute}}', 'false');
22
22
  {{/if}}
23
23
  {{else}}
24
24
  {{!-- Default (zero-config): drive by aria-expanded, but assert it ONLY when the element exposes it. --}}
@@ -1,3 +1,8 @@
1
- const editorLocator = {{> locator}};
1
+ {{!-- Block-scoped, like the appium counterpart: `const editorLocator` at statement level made a
2
+ SECOND editor fill in the same scenario a duplicate declaration, and Playwright fails the
3
+ WHOLE file with one SyntaxError — every scenario in it never runs. Unreachable while
4
+ `contenteditable:` was dead and nothing set `inputMethod` (#712); a form with two rich-text
5
+ fields hits it immediately once the field works. SG-E022 is the gate that caught it. --}}
6
+ { const editorLocator = {{> locator}};
2
7
  await editorLocator.click();
3
- await editorLocator.pressSequentially('{{escapeQuotes fillValue}}');
8
+ await editorLocator.pressSequentially('{{escapeQuotes fillValue}}'); }
@@ -2,4 +2,6 @@
2
2
  role=row, so the ARIA chain below is 0 elements and this assertion would be vacuous, issue #688)
3
3
  wins; otherwise the historical ARIA chain, byte-identical. --}}
4
4
  { const tableRow = {{> locator}}{{#if rows}}.locator('{{escapeQuotes rows}}'){{else}}.getByRole('row'){{/if}}.nth({{{rowIndexExpr}}});
5
- await {{{actionLocator}}}.{{action}}(); }
5
+ {{!-- `check`/`uncheck` must not emit a bare .check() here either (#675) — the row control is just
6
+ as likely to be an aria-pressed toggle as a standalone one. `click` stays a plain click. --}}
7
+ {{#if (eq action 'click')}}await {{{actionLocator}}}.click();{{else}}await setCheckedState({{{actionLocator}}}, {{#if (eq action 'check')}}true{{else}}false{{/if}}, '{{escapeQuotes elementName}}'{{> checked-opts cls=actionCheckedClass attr=actionCheckedAttribute}});{{/if}} }
@@ -2,4 +2,6 @@
2
2
  role=row, so the ARIA chain below is 0 elements and this assertion would be vacuous, issue #688)
3
3
  wins; otherwise the historical ARIA chain, byte-identical. --}}
4
4
  { const tableRow = {{> locator}}{{#if rows}}.locator('{{escapeQuotes rows}}'){{else}}.getByRole('row'){{/if}}.filter({ hasText: '{{escapeQuotes filterValue}}' });
5
- await {{{actionLocator}}}.{{action}}(); }
5
+ {{!-- `check`/`uncheck` must not emit a bare .check() here either (#675) — the row control is just
6
+ as likely to be an aria-pressed toggle as a standalone one. `click` stays a plain click. --}}
7
+ {{#if (eq action 'click')}}await {{{actionLocator}}}.click();{{else}}await setCheckedState({{{actionLocator}}}, {{#if (eq action 'check')}}true{{else}}false{{/if}}, '{{escapeQuotes elementName}}'{{> checked-opts cls=actionCheckedClass attr=actionCheckedAttribute}});{{/if}} }
@@ -0,0 +1,5 @@
1
+ {{!-- The paginator is located from `page`, NOT from inside the table locator: antd/MUI/Radix all
2
+ render the control as a SIBLING of the <table>, so scoping it to the table would resolve 0
3
+ elements (issue #712). No implicit wait — the round-trip belongs to
4
+ `User wait for [X] table to refresh`. A disabled control fails loud: click waits for enabled. --}}
5
+ await page.locator('{{escapeQuotes paginationSelector}}').click();
@@ -1 +1,3 @@
1
- await {{> locator}}.uncheck();
1
+ {{!-- Inverse of check-action.hbs — see there and specs/checked-state.ts for why the control is
2
+ inspected at run time rather than driven by a bare .uncheck() (#675). --}}
3
+ await setCheckedState({{> locator}}, false, '{{escapeQuotes selectorRef}}'{{> checked-opts cls=checkedClass attr=checkedAttribute}});
@@ -1 +1 @@
1
- await page.getByText('{{escapeQuotes selectorValue}}').filter({ hasText: {{> has-text-value dataValue=dataValue exact=exact legacyTail=(eq selectorValue "")}} }){{#if (gte nth 0)}}.nth({{nth}}){{/if}}.{{action}}();
1
+ await page.getByText('{{escapeQuotes selectorValue}}').filter({ hasText: {{> has-text-value dataValue=dataValue exact=exact legacyTail=(eq selectorValue "")}} }){{> locator-nth}}.{{action}}();
@@ -1 +1,18 @@
1
- await page.waitForURL(/{{pathRegex}}/);
1
+ {{!-- `wait for [T] page` is a WAIT, not an oracle — but a bare `page.waitForURL(/…/)` resolves on
2
+ the FIRST URL sample that matches, which for a client-side router is the URL it pushed before
3
+ the route guard had answered. Every step after the wait then ran against a page mid-redirect,
4
+ so their failures pointed at the wrong thing (#677). The unanchored regex also matched a
5
+ neighbour: `/vi/search` resolved on `/vi/search-results`.
6
+
7
+ `waitForPage` is the third caller of the settle-and-hold core in specs/url-assert.ts, after
8
+ the `see [T] page` / `is on [T] page` pair #662 routed there (partials/url-assertion.hbs):
9
+ arrive, let the page settle, HOLD the URL, and fail naming where the user actually ended up.
10
+
11
+ Where it deliberately DIFFERS from the Then-side `expectPage`: the query is not constrained.
12
+ A bare page ASSERTION means a bare page; a wait for a results page legitimately lands on
13
+ `?q=…`, and reddening there would assert something the step never said. The helper's docstring
14
+ carries the full argument.
15
+
16
+ `hostname` rides separately and only for an absolute page value: it is read off `u.hostname`,
17
+ which never appears in `u.pathname`, so one concatenated regex cannot carry both. --}}
18
+ await waitForPage(page, { pathname: /^{{pathRegex}}\/?$/{{#if hostRegex}}, hostname: /^{{hostRegex}}$/{{/if}} });
@@ -1,7 +1,35 @@
1
- {
2
- const __items_{{stepCounter}} = await {{> locator}}.allInnerTexts();
3
- expect(__items_{{stepCounter}}.length, 'Expected at least one [{{selectorRef}}]').toBeGreaterThan(0);
1
+ {{!-- All-contain: the assertion has to SURVIVE whatever async action precedes it.
2
+
3
+ `locator.allInnerTexts()` is a one-shot DOM read — no auto-wait, no retry — and wrapping its
4
+ RESULT in expect() adds no waiting either, because by then it is a plain array of strings.
5
+ Playwright's auto-waiting only applies when the LOCATOR is handed to expect(). Placed after a
6
+ search click or a filter change, the old shape asserted against the PRE-action DOM and failed
7
+ deterministically, naming real-looking values that read as a product defect rather than as a
8
+ generator bug (#672). A `toBeGreaterThan(0)` guard does not help: the stale list is non-empty,
9
+ so the length check passes and the CONTENT check is what races.
10
+
11
+ Playwright has no locator-level "every match contains X" — `filter({ hasNotText })` matches
12
+ case-insensitively on normalized whitespace, which is not the `toContain` semantics this step
13
+ promises — so the wait is expressed with expect.poll, which retries for `expect.timeout` from
14
+ playwright.config.ts. Two details carry the fix:
15
+
16
+ - The loop asserts on the snapshot the poll SETTLED on. Re-reading the locator afterwards
17
+ reopens the very race being closed — a late render pass between the poll settling and the
18
+ re-read swapped the rows out again, which is how the reporter's first patch passed in
19
+ isolation and failed in the full suite.
20
+ - The poll's own rejection is swallowed: on its own it can only report
21
+ `toBe(true) received false`. The per-item expect() below names expected vs received, which
22
+ is the message a QA has to act on. --~}}{
23
+ const __expected_{{stepCounter}} = '{{escapeQuotes expectedText}}';
24
+ let __items_{{stepCounter}}: string[] = [];
25
+ try {
26
+ await expect.poll(async () => {
27
+ __items_{{stepCounter}} = await {{> locator}}.allInnerTexts();
28
+ return __items_{{stepCounter}}.length > 0 && __items_{{stepCounter}}.every((__s) => __s.includes(__expected_{{stepCounter}}));
29
+ }).toBe(true);
30
+ } catch { /* the assertions below report expected-vs-received on the settled snapshot */ }
31
+ expect(__items_{{stepCounter}}.length, 'Expected at least one [{{escapeQuotes selectorRef}}]').toBeGreaterThan(0);
4
32
  for (const __t_{{stepCounter}} of __items_{{stepCounter}}) {
5
- expect(__t_{{stepCounter}}).toContain('{{expectedText}}');
33
+ expect(__t_{{stepCounter}}).toContain(__expected_{{stepCounter}});
6
34
  }
7
35
  }
@@ -1,7 +1,10 @@
1
- {
1
+ {{!-- The count() below is a one-shot read, same class as #672: after a search or filter click the
2
+ container list may not have rendered yet, and a bare `toBeGreaterThan(0)` on a stale 0 fails
3
+ instantly with no retry. `not.toHaveCount(0)` IS locator-based, so it auto-waits for the list
4
+ to appear; count() then reads a list that is already known to exist. --~}}{
2
5
  const {{containerVar}} = {{> locator}};
6
+ await expect({{containerVar}}, 'Expected at least one [{{escapeQuotes selectorRef}}]').not.toHaveCount(0);
3
7
  const __n_{{stepCounter}} = await {{containerVar}}.count();
4
- expect(__n_{{stepCounter}}, 'Expected at least one [{{escapeQuotes selectorRef}}]').toBeGreaterThan(0);
5
8
  for (let {{containerIndexVar}} = 0; {{containerIndexVar}} < __n_{{stepCounter}}; {{containerIndexVar}}++) {
6
9
  await expect({{#with child}}{{> locator parentLocator=(add (add (add ../containerVar ".nth(") ../containerIndexVar) ")")}}{{/with}}, 'Every [{{escapeQuotes selectorRef}}] must contain [{{escapeQuotes childRef}}]').toBeVisible();
7
10
  }
@@ -1 +1,5 @@
1
- await expect({{> locator}}).toBeChecked();
1
+ {{!-- `expect(locator).toBeChecked()` throws the same "Not a checkbox or radio button" (#675) on a
2
+ `<button aria-pressed>` as the action side did, so driving a toggle worked while ASSERTING it
3
+ did not. expectCheckedState reads state the same way setCheckedState drives it — an action and
4
+ the assertion after it can never disagree about what the control reports. --}}
5
+ await expectCheckedState({{> locator}}, true, '{{escapeQuotes selectorRef}}'{{> checked-opts cls=checkedClass attr=checkedAttribute}});