@sun-asterisk/sungen 3.2.29 → 3.2.31

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,4 +1,4 @@
1
- import { TestGeneratorAdapter, TestFileData, ScenarioData, RenderImportsOptions } from '../adapter-interface';
1
+ import { TestGeneratorAdapter, TestFileData, ScenarioData, RenderImportsOptions, HookRenderData } from '../adapter-interface';
2
2
  /**
3
3
  * PlaywrightAdapter
4
4
  * Generates Playwright TypeScript test code using Handlebars templates
@@ -18,24 +18,9 @@ export declare class PlaywrightAdapter implements TestGeneratorAdapter {
18
18
  code: string;
19
19
  }>;
20
20
  }): string;
21
- renderBeforeAll(data: {
22
- steps: Array<{
23
- comment?: string;
24
- code: string;
25
- }>;
26
- }): string;
27
- renderAfterEach(data: {
28
- steps: Array<{
29
- comment?: string;
30
- code: string;
31
- }>;
32
- }): string;
33
- renderAfterAll(data: {
34
- steps: Array<{
35
- comment?: string;
36
- code: string;
37
- }>;
38
- }): string;
21
+ renderBeforeAll(data: HookRenderData): string;
22
+ renderAfterEach(data: HookRenderData): string;
23
+ renderAfterAll(data: HookRenderData): string;
39
24
  renderStep(templateName: string, data: any): string;
40
25
  }
41
26
  //# sourceMappingURL=playwright-adapter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"playwright-adapter.d.ts","sourceRoot":"","sources":["../../../../../src/generators/test-generator/adapters/playwright/playwright-adapter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAG9G;;;GAGG;AACH,qBAAa,iBAAkB,YAAW,oBAAoB;IAC5D,QAAQ,CAAC,aAAa,gBAAgB;IACtC,QAAQ,CAAC,aAAa,cAAc;IACpC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,OAAO,CAAC,cAAc,CAAiB;;IAQvC,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM;IAI1C,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM;IAI1C,aAAa,CAAC,OAAO,CAAC,EAAE,oBAAoB,GAAG,MAAM;IAIrD,gBAAgB,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,KAAK,CAAC;YAAE,OAAO,CAAC,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,GAAG,MAAM;IAIpF,eAAe,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,KAAK,CAAC;YAAE,OAAO,CAAC,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,GAAG,MAAM;IAInF,eAAe,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,KAAK,CAAC;YAAE,OAAO,CAAC,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,GAAG,MAAM;IAInF,cAAc,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,KAAK,CAAC;YAAE,OAAO,CAAC,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,GAAG,MAAM;IAIlF,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,MAAM;CAGpD"}
1
+ {"version":3,"file":"playwright-adapter.d.ts","sourceRoot":"","sources":["../../../../../src/generators/test-generator/adapters/playwright/playwright-adapter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,YAAY,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAG9H;;;GAGG;AACH,qBAAa,iBAAkB,YAAW,oBAAoB;IAC5D,QAAQ,CAAC,aAAa,gBAAgB;IACtC,QAAQ,CAAC,aAAa,cAAc;IACpC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,OAAO,CAAC,cAAc,CAAiB;;IAQvC,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM;IAI1C,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM;IAI1C,aAAa,CAAC,OAAO,CAAC,EAAE,oBAAoB,GAAG,MAAM;IAIrD,gBAAgB,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,KAAK,CAAC;YAAE,OAAO,CAAC,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,GAAG,MAAM;IAIpF,eAAe,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM;IAI7C,eAAe,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM;IAI7C,cAAc,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM;IAI5C,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,MAAM;CAGpD"}
@@ -1 +1 @@
1
- {"version":3,"file":"playwright-adapter.js","sourceRoot":"","sources":["../../../../../src/generators/test-generator/adapters/playwright/playwright-adapter.ts"],"names":[],"mappings":";;;;;;AAAA,gDAAwB;AAExB,2DAAuD;AAEvD;;;GAGG;AACH,MAAa,iBAAiB;IAM5B;QALS,kBAAa,GAAG,YAAY,CAAC;QAC7B,kBAAa,GAAG,UAAU,CAAC;QAKlC,2CAA2C;QAC3C,IAAI,CAAC,YAAY,GAAG,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;QACtD,IAAI,CAAC,cAAc,GAAG,IAAI,gCAAc,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC9D,CAAC;IAED,cAAc,CAAC,IAAkB;QAC/B,OAAO,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;IAClD,CAAC;IAED,cAAc,CAAC,IAAkB;QAC/B,OAAO,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;IAClD,CAAC;IAED,aAAa,CAAC,OAA8B;QAC1C,OAAO,IAAI,CAAC,cAAc,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IACpD,CAAC;IAED,gBAAgB,CAAC,IAA0D;QACzE,OAAO,IAAI,CAAC,cAAc,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACpD,CAAC;IAED,eAAe,CAAC,IAA0D;QACxE,OAAO,IAAI,CAAC,cAAc,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;IACnD,CAAC;IAED,eAAe,CAAC,IAA0D;QACxE,OAAO,IAAI,CAAC,cAAc,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;IACnD,CAAC;IAED,cAAc,CAAC,IAA0D;QACvE,OAAO,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;IAClD,CAAC;IAED,UAAU,CAAC,YAAoB,EAAE,IAAS;QACxC,OAAO,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;IAC5D,CAAC;CACF;AA3CD,8CA2CC"}
1
+ {"version":3,"file":"playwright-adapter.js","sourceRoot":"","sources":["../../../../../src/generators/test-generator/adapters/playwright/playwright-adapter.ts"],"names":[],"mappings":";;;;;;AAAA,gDAAwB;AAExB,2DAAuD;AAEvD;;;GAGG;AACH,MAAa,iBAAiB;IAM5B;QALS,kBAAa,GAAG,YAAY,CAAC;QAC7B,kBAAa,GAAG,UAAU,CAAC;QAKlC,2CAA2C;QAC3C,IAAI,CAAC,YAAY,GAAG,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;QACtD,IAAI,CAAC,cAAc,GAAG,IAAI,gCAAc,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC9D,CAAC;IAED,cAAc,CAAC,IAAkB;QAC/B,OAAO,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;IAClD,CAAC;IAED,cAAc,CAAC,IAAkB;QAC/B,OAAO,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;IAClD,CAAC;IAED,aAAa,CAAC,OAA8B;QAC1C,OAAO,IAAI,CAAC,cAAc,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IACpD,CAAC;IAED,gBAAgB,CAAC,IAA0D;QACzE,OAAO,IAAI,CAAC,cAAc,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACpD,CAAC;IAED,eAAe,CAAC,IAAoB;QAClC,OAAO,IAAI,CAAC,cAAc,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;IACnD,CAAC;IAED,eAAe,CAAC,IAAoB;QAClC,OAAO,IAAI,CAAC,cAAc,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;IACnD,CAAC;IAED,cAAc,CAAC,IAAoB;QACjC,OAAO,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;IAClD,CAAC;IAED,UAAU,CAAC,YAAoB,EAAE,IAAS;QACxC,OAAO,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;IAC5D,CAAC;CACF;AA3CD,8CA2CC"}
@@ -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}});
@@ -1,3 +1,14 @@
1
- await expect({{> locator}}.getByRole('columnheader', { name: '{{columnName}}', includeHidden: true })).toBeVisible();
1
+ {{!-- Rooted at `page`, like the two lines below it. This line used to render `{{> locator}}` from
2
+ step data that carries no strategy/value at all, so it emitted `page.locator('')` — an empty
3
+ CSS selector, which Playwright refuses to parse. The step could not run.
4
+
5
+ Block-scoped: the column-index const is named after the [Reference], so two steps naming the
6
+ same column — or two non-ASCII references, which sanitize down to the same identifier — would
7
+ redeclare it in one scenario and the spec would not compile. The braces make each step's
8
+ binding its own.
9
+
10
+ Not dialog/frame scoped: this step form carries no selector entry, so there is nothing for
11
+ `pageRoot` to read. A column assertion inside a modal needs the row-scoped form. --~}}{ await expect(page.getByRole('columnheader', { name: '{{columnName}}', includeHidden: true })).toBeVisible();
2
12
  const {{columnIndexVar}} = (await page.getByRole('columnheader', { includeHidden: true }).filter({ visible: true }).allTextContents()).findIndex(h => h.includes('{{columnName}}'));
3
- await expect(page.getByRole('row', { includeHidden: true }).filter({ visible: true }).nth({{rowNth}}).getByRole('cell', { includeHidden: true }).filter({ visible: true }).nth({{columnIndexVar}})).toHaveText('{{dataValue}}');
13
+ expect({{columnIndexVar}}, 'Column "{{escapeQuotes columnName}}" is not among the visible headers').toBeGreaterThanOrEqual(0);
14
+ await expect(page.getByRole('row', { includeHidden: true }).filter({ visible: true }).nth({{rowNth}}).getByRole('cell', { includeHidden: true }).filter({ visible: true }).nth({{columnIndexVar}})).toHaveText('{{dataValue}}'); }
@@ -1 +1 @@
1
- await expect({{> locator}}).toContainText('{{expectedText}}');
1
+ await expect({{> locator}}).toContainText('{{escapeQuotes expectedText}}');
@@ -1,5 +1,5 @@
1
1
  {{#if name}}
2
- await expect(page.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeDisabled();
2
+ await expect(page.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{> locator-nth}}).toBeDisabled();
3
3
  {{else}}
4
- await expect(page.getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeDisabled();
4
+ await expect(page.getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{> locator-nth}}).toBeDisabled();
5
5
  {{/if}}
@@ -1 +1 @@
1
- await expect(page.getByText('{{escapeQuotes selectorRef}}').filter({ hasText: {{#if (eq selectorValue "")}}/.*{{escapeRegex dataValue}}$/{{else}}'{{escapeQuotes dataValue}}'{{/if}} }){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeDisabled();
1
+ await expect(page.getByText('{{escapeQuotes selectorRef}}').filter({ hasText: {{#if (eq selectorValue "")}}/.*{{escapeRegex dataValue}}$/{{else}}'{{escapeQuotes dataValue}}'{{/if}} }){{> locator-nth}}).toBeDisabled();
@@ -1 +1 @@
1
- await expect({{> locator}}).toHaveText('{{expectedText}}');
1
+ await expect({{> locator}}).toHaveText('{{escapeQuotes expectedText}}');
@@ -1,5 +1,5 @@
1
1
  {{#if name}}
2
- await expect(page.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeHidden();
2
+ await expect(page.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{> locator-nth}}).toBeHidden();
3
3
  {{else}}
4
- await expect(page.getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeHidden();
4
+ await expect(page.getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{> locator-nth}}).toBeHidden();
5
5
  {{/if}}
@@ -1 +1 @@
1
- await expect(page.getByText('{{escapeQuotes selectorRef}}').filter({ hasText: {{#if (eq selectorValue "")}}/.*{{escapeRegex dataValue}}$/{{else}}'{{escapeQuotes dataValue}}'{{/if}} }){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeHidden();
1
+ await expect(page.getByText('{{escapeQuotes selectorRef}}').filter({ hasText: {{#if (eq selectorValue "")}}/.*{{escapeRegex dataValue}}$/{{else}}'{{escapeQuotes dataValue}}'{{/if}} }){{> locator-nth}}).toBeHidden();
@@ -1 +1,2 @@
1
- await expect({{> locator}}).toBeChecked({ checked: false });
1
+ {{!-- Inverse of checked-assertion.hbs — see there (#675). --}}
2
+ await expectCheckedState({{> locator}}, false, '{{escapeQuotes selectorRef}}'{{> checked-opts cls=checkedClass attr=checkedAttribute}});
@@ -1,7 +1,16 @@
1
- {{#if (or (eq role 'checkbox') (eq role 'radio') (eq role 'switch'))}}
1
+ {{#if (or checkedClass checkedAttribute)}}
2
+ {{!-- A DECLARED signal (issue #730) short-circuits the whole ARIA probe below: the author
3
+ reached for `selected:`/`checked:` precisely because this component publishes no aria-selected,
4
+ no aria-current and no checked state — a Tailwind card/tab picker. Probing for them first would
5
+ just spend the budget confirming they are absent. --}}
6
+ await expectCheckedState({{> locator}}, true, '{{escapeQuotes selectorRef}}'{{> checked-opts cls=checkedClass attr=checkedAttribute}});
7
+ {{else if (or (eq role 'checkbox') (eq role 'radio') (eq role 'switch'))}}
2
8
  {{!-- Form control: "selected" is the checked state, never aria-selected. A native checkbox/radio
3
9
  exposes no aria-selected attribute — its state lives in the `checked` property. --}}
4
- await expect({{> locator}}).toBeChecked();
10
+ {{!-- "selected" on a form control IS the checked state — routed through expectCheckedState so an
11
+ aria-pressed toggle does not throw "Not a checkbox or radio button" (#675), and so the selector
12
+ that declares role: switch while the element publishes aria-pressed still reads correctly. --}}
13
+ await expectCheckedState({{> locator}}, true, '{{escapeQuotes selectorRef}}');
5
14
  {{else}}
6
15
  {{!-- Other/unknown role (tab, option, or a text/testid/css selector): probe at runtime. An ARIA
7
16
  single-select widget exposes aria-selected → assert it; anything without it falls back to the
@@ -17,7 +26,9 @@ await expect(el).toHaveAttribute('aria-selected', 'true');
17
26
  role=tab owns the stronger signal. --}}
18
27
  await expect(el).toHaveAttribute('aria-current', /^(?!false$)./);
19
28
  } else {
20
- await expect(el).toBeChecked();
29
+ {{!-- Last resort, same as the -with-filter twin: neither aria-selected nor aria-current, so read the
30
+ checked/pressed state through the #675-safe path rather than toBeChecked(). --}}
31
+ await expectCheckedState(el, true, '{{escapeQuotes selectorRef}}');
21
32
  }
22
33
  }
23
34
  {{/if}}
@@ -1,6 +1,12 @@
1
1
  {{!-- Filter contract shared with the visible/hidden/contains siblings (has-text-value):
2
2
  substring by default, `exact: true` in the selector YAML anchors. --}}
3
3
  {{#if selectedProbe}}
4
+ {{#if (or checkedClass checkedAttribute)}}
5
+ {{!-- Declared class signal (issue #730) — short-circuits the ARIA probe, exactly as the unfiltered
6
+ twin selected-assertion.hbs does. Without this branch the filtered form stayed broken while the
7
+ unfiltered one worked, which is the inconsistency #730's second field report ran into. --}}
8
+ await expectCheckedState({{> locator-base}}.filter({ hasText: {{> has-text-value dataValue=dataValue exact=exact}} }){{> locator-nth}}, true, '{{escapeQuotes selectorRef}}'{{> checked-opts cls=checkedClass attr=checkedAttribute}});
9
+ {{else}}
4
10
  { const el = {{> locator-base}}.filter({ hasText: {{> has-text-value dataValue=dataValue exact=exact}} }){{> locator-nth}};
5
11
  if ((await el.getAttribute('aria-selected')) !== null) {
6
12
  await expect(el).toHaveAttribute('aria-selected', 'true');
@@ -12,9 +18,14 @@ await expect(el).toHaveAttribute('aria-selected', 'true');
12
18
  role=tab owns the stronger signal. --}}
13
19
  await expect(el).toHaveAttribute('aria-current', /^(?!false$)./);
14
20
  } else {
15
- await expect(el).toBeChecked();
21
+ {{!-- Last resort: the element publishes neither aria-selected nor aria-current. toBeChecked() used
22
+ to sit here and threw "Not a checkbox or radio button" on an aria-pressed toggle (#675). --}}
23
+ await expectCheckedState(el, true, '{{escapeQuotes selectorRef}}');
16
24
  }
17
25
  }
26
+ {{/if}}
27
+ {{else}}{{#if isCheckedAssertion}}
28
+ await expectCheckedState({{> locator-base}}.filter({ hasText: {{> has-text-value dataValue=dataValue exact=exact}} }){{> locator-nth}}, {{checkedWant}}, '{{escapeQuotes selectorRef}}'{{> checked-opts cls=checkedClass attr=checkedAttribute}});
18
29
  {{else}}
19
30
  await expect({{> locator-base}}.filter({ hasText: {{> has-text-value dataValue=dataValue exact=exact}} }){{> locator-nth}}).{{{stateAssertion}}};
20
- {{/if}}
31
+ {{/if}}{{/if}}
@@ -1,9 +1,9 @@
1
1
  {{#if name}}
2
2
  {{#if dataValue}}
3
- await expect(page.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeVisible();
3
+ await expect(page.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{> locator-nth}}).toBeVisible();
4
4
  {{else}}
5
- await expect(page.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeVisible();
5
+ await expect(page.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }){{> locator-nth}}).toBeVisible();
6
6
  {{/if}}
7
7
  {{else}}
8
- await expect(page.getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeVisible();
8
+ await expect(page.getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{> locator-nth}}).toBeVisible();
9
9
  {{/if}}
@@ -1 +1 @@
1
- await expect(page.getByText('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeVisible();
1
+ await expect(page.getByText('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{> locator-nth}}).toBeVisible();
@@ -1 +1 @@
1
- await expect(page.getByText('{{escapeQuotes selectorValue}}').filter({ hasText: /.*{{escapeRegex dataValue}}$/ }){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeVisible();
1
+ await expect(page.getByText('{{escapeQuotes selectorValue}}').filter({ hasText: /.*{{escapeRegex dataValue}}$/ }){{> locator-nth}}).toBeVisible();
@@ -0,0 +1,8 @@
1
+ {{!-- The `checked:`/`selected:` signal from selectors.yaml, appended as the options argument of
2
+ setCheckedState/expectCheckedState (issue #730). One partial so all nine call sites carry it
3
+ identically, and so a site that declares nothing stays BYTE-IDENTICAL to before — the whole
4
+ argument disappears rather than becoming an empty object. Takes `cls`/`attr` so a caller can
5
+ pass a signal resolved from a DIFFERENT entry than the one it spread (table-action-in-row
6
+ spreads the TABLE but acts on the row's own control). A selector declares one or the other;
7
+ the resolver refuses both, so the two branches never collide here. --~}}
8
+ {{~#if cls}}, { checkedClass: '{{escapeQuotes cls}}' }{{else if attr}}, { checkedAttribute: '{{escapeQuotes attr}}' }{{/if~}}
@@ -1 +1 @@
1
- {{#if (gte nth 0)}}.nth({{nth}}){{/if}}
1
+ {{#if (gte nth 0)}}.nth({{nth}}){{else if (eq nth -1)}}.last(){{/if}}
@@ -98,6 +98,16 @@ test.describe.serial('{{featureName}}', () => {
98
98
  });
99
99
  {{/if}}
100
100
 
101
+ {{#if beforeAll}}
102
+ {{beforeAll}}
103
+
104
+ {{/if}}
105
+ {{#if afterAll}}
106
+ // User @afterAll runs BEFORE the built-in close below (Playwright runs afterAll hooks in registration
107
+ // order), so the shared page is still open for its cleanup steps.
108
+ {{afterAll}}
109
+
110
+ {{/if}}
101
111
  test.afterAll(async () => {
102
112
  await page.close();
103
113
  await context.close();
@@ -112,18 +122,10 @@ test.describe.serial('{{featureName}}', () => {
112
122
  {{#if screenshotOnFailure}}
113
123
  test.use({ screenshotOnFailure: true });
114
124
 
115
- {{/if}}
116
- {{#if beforeAll}}
117
- {{beforeAll}}
118
-
119
125
  {{/if}}
120
126
  {{#if afterEach}}
121
127
  {{afterEach}}
122
128
 
123
- {{/if}}
124
- {{#if afterAll}}
125
- {{afterAll}}
126
-
127
129
  {{/if}}
128
130
  {{#if authGroups}}
129
131
  {{#each authGroups}}
@@ -75,6 +75,23 @@ export declare class CodeGenerator {
75
75
  private generateTestCode;
76
76
  private generateBackgroundSteps;
77
77
  private generateBeforeEach;
78
+ /**
79
+ * Hook block (`@beforeAll` / `@afterEach` / `@afterAll`). Shares prepare/finalize with ordinary
80
+ * scenarios, so an `@api:` / `@query:` precondition on the hook runs INSIDE it (bind {{name}}) and
81
+ * the hook's own `{{name.*}}` refs resolve at runtime — a hook used to map only its steps, so
82
+ * `@beforeAll @api:login` compiled to a beforeAll with no api.call and a testData.get() of nothing.
83
+ * Not applied to hooks: `@cases` (a hook has no dataset rows) and `@extend` (no base scenario) —
84
+ * warned and ignored; `@auth:` grouping and the mock epilogue are scenario-only. `@concurrent:N`
85
+ * IS valid: the api capability reads it off the same tags.
86
+ *
87
+ * Failure collection: a failing hook skips every test in its describe, so one run must report
88
+ * EVERY failed check, not just the first. The hook template wraps each mapped step in try/catch:
89
+ * an assertion step (effective keyword Then) records its failure and lets the next step run; an
90
+ * action step (Given/When) records it and fails the hook right there, since the steps after it
91
+ * would run on state the failed action never produced. The block ends with a guard that fails once
92
+ * with the full list. `expect.soft` is not an option here — a soft failure marks the owning test,
93
+ * and a hook has none.
94
+ */
78
95
  private generateHook;
79
96
  /**
80
97
  * TQ-11 — a `@requires:<cap>` scenario whose capability is NOT enabled. Emits a real `test()`
@@ -83,6 +100,26 @@ export declare class CodeGenerator {
83
100
  * scenario compiles to its full automated body instead.
84
101
  */
85
102
  private generateRequiresSkipStub;
103
+ /**
104
+ * Scenario-body preparation shared by ordinary scenarios and hook blocks — runs BEFORE the steps
105
+ * are mapped (the step mapper reads what it sets): scenario context, `@cases` row-column capture,
106
+ * and the capability preconditions (`@api:<name>` / `@query:<name>` → bind {{name}}) whose bound
107
+ * `{{name.*}}` refs exist only at runtime, so they are registered as captured and compile to a
108
+ * runtime get() instead of a compile-time YAML lookup that would fail. Pair with
109
+ * finalizeScenarioBody(), which prepends the preconditions once the steps are mapped.
110
+ */
111
+ private prepareScenarioBody;
112
+ /**
113
+ * Capability preconditions run BEFORE the body's own steps — prepend them (indented) once the
114
+ * steps are mapped. Second half of the prepareScenarioBody() pair.
115
+ */
116
+ private finalizeScenarioBody;
117
+ /**
118
+ * Step-boundary trace (#673): scenario name → the ordered step-comment texts emitted for it.
119
+ * Read by script-check to attribute emitted code to steps structurally; see the populating
120
+ * site in generateScenario for why comment adjacency cannot do the job.
121
+ */
122
+ readonly stepTrace: Map<string, string[]>;
86
123
  private generateScenario;
87
124
  /**
88
125
  * Indent code lines with specified number of spaces
@@ -1 +1 @@
1
- {"version":3,"file":"code-generator.d.ts","sourceRoot":"","sources":["../../../src/generators/test-generator/code-generator.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAA8B,MAAM,mBAAmB,CAAC;AAI9E,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAuOrD;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,UAAU,CAAC,CAAS;IAC5B,OAAO,CAAC,OAAO,CAAM;IAErB,OAAO,CAAC,eAAe,CAAc;IAErC,OAAO,CAAC,aAAa,CAAqC;IAE1D,OAAO,CAAC,WAAW,CAA8B;gBAErC,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAO;IAiBtK,oFAAoF;IACpF,cAAc,IAAI,oBAAoB;IAItC;;OAEG;IACG,YAAY,CAChB,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,aAAa,CAAC;IA4FzB;;;;OAIG;IACH,OAAO,CAAC,eAAe;IAavB;;;OAGG;IACH,OAAO,CAAC,UAAU;IAclB;;OAEG;IACH,SAAS,CAAC,aAAa,EAAE,aAAa,GAAG,IAAI;IAS7C;;OAEG;IACH;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IA0C3B;;;;OAIG;IACH,OAAO,CAAC,uBAAuB;IAoB/B;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAY3B,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;YASzB,gBAAgB;YAuWhB,uBAAuB;YAYvB,kBAAkB;YAoBlB,YAAY;IAqB1B;;;;;OAKG;IACH,OAAO,CAAC,wBAAwB;YASlB,gBAAgB;IAoJ9B;;OAEG;IACH,OAAO,CAAC,UAAU;IAKlB;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAS7B;;OAEG;IACH;;;;;;;OAOG;IACH,OAAO,CAAC,eAAe,CAAqB;IAC5C,OAAO,CAAC,kBAAkB;IAyBpB,gBAAgB,CACpB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EAAE,GACrB,OAAO,CAAC,aAAa,EAAE,CAAC;CA+D5B"}
1
+ {"version":3,"file":"code-generator.d.ts","sourceRoot":"","sources":["../../../src/generators/test-generator/code-generator.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAA8B,MAAM,mBAAmB,CAAC;AAI9E,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AA0OrD;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,UAAU,CAAC,CAAS;IAC5B,OAAO,CAAC,OAAO,CAAM;IAErB,OAAO,CAAC,eAAe,CAAc;IAErC,OAAO,CAAC,aAAa,CAAqC;IAE1D,OAAO,CAAC,WAAW,CAA8B;gBAErC,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAO;IAiBtK,oFAAoF;IACpF,cAAc,IAAI,oBAAoB;IAItC;;OAEG;IACG,YAAY,CAChB,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,aAAa,CAAC;IAqGzB;;;;OAIG;IACH,OAAO,CAAC,eAAe;IAavB;;;OAGG;IACH,OAAO,CAAC,UAAU;IAclB;;OAEG;IACH,SAAS,CAAC,aAAa,EAAE,aAAa,GAAG,IAAI;IAS7C;;OAEG;IACH;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IA0C3B;;;;OAIG;IACH,OAAO,CAAC,uBAAuB;IAoB/B;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAY3B,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;YAczB,gBAAgB;YA4YhB,uBAAuB;YAYvB,kBAAkB;IAoBhC;;;;;;;;;;;;;;;;OAgBG;YACW,YAAY;IA+C1B;;;;;OAKG;IACH,OAAO,CAAC,wBAAwB;IAShC;;;;;;;OAOG;IACH,OAAO,CAAC,mBAAmB;IAgC3B;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAM5B;;;;OAIG;IACH,QAAQ,CAAC,SAAS,wBAA+B;YAEnC,gBAAgB;IA6H9B;;OAEG;IACH,OAAO,CAAC,UAAU;IAKlB;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAS7B;;OAEG;IACH;;;;;;;OAOG;IACH,OAAO,CAAC,eAAe,CAAqB;IAC5C,OAAO,CAAC,kBAAkB;IAyBpB,gBAAgB,CACpB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EAAE,GACrB,OAAO,CAAC,aAAa,EAAE,CAAC;CA+D5B"}