@sun-asterisk/sungen 3.2.28 → 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 (486) hide show
  1. package/dist/capabilities/discover.d.ts +29 -0
  2. package/dist/capabilities/discover.d.ts.map +1 -1
  3. package/dist/capabilities/discover.js +144 -10
  4. package/dist/capabilities/discover.js.map +1 -1
  5. package/dist/capabilities/registry.d.ts +18 -0
  6. package/dist/capabilities/registry.d.ts.map +1 -1
  7. package/dist/capabilities/registry.js +25 -0
  8. package/dist/capabilities/registry.js.map +1 -1
  9. package/dist/cli/commands/audit.d.ts.map +1 -1
  10. package/dist/cli/commands/audit.js +9 -0
  11. package/dist/cli/commands/audit.js.map +1 -1
  12. package/dist/cli/commands/capability.js +1 -1
  13. package/dist/cli/commands/capability.js.map +1 -1
  14. package/dist/cli/commands/delivery.d.ts.map +1 -1
  15. package/dist/cli/commands/delivery.js +2 -1
  16. package/dist/cli/commands/delivery.js.map +1 -1
  17. package/dist/cli/commands/generate.d.ts.map +1 -1
  18. package/dist/cli/commands/generate.js +6 -2
  19. package/dist/cli/commands/generate.js.map +1 -1
  20. package/dist/cli/commands/inspect.js +1 -1
  21. package/dist/cli/commands/inspect.js.map +1 -1
  22. package/dist/cli/commands/results-merge.d.ts.map +1 -1
  23. package/dist/cli/commands/results-merge.js +9 -2
  24. package/dist/cli/commands/results-merge.js.map +1 -1
  25. package/dist/cli/commands/results-sanitize.d.ts +24 -0
  26. package/dist/cli/commands/results-sanitize.d.ts.map +1 -0
  27. package/dist/cli/commands/results-sanitize.js +126 -0
  28. package/dist/cli/commands/results-sanitize.js.map +1 -0
  29. package/dist/cli/commands/script-check.d.ts.map +1 -1
  30. package/dist/cli/commands/script-check.js +3 -1
  31. package/dist/cli/commands/script-check.js.map +1 -1
  32. package/dist/cli/commands/update.d.ts.map +1 -1
  33. package/dist/cli/commands/update.js +3 -0
  34. package/dist/cli/commands/update.js.map +1 -1
  35. package/dist/dashboard/templates/index.html +1 -1
  36. package/dist/exporters/api-testcase-formatter.d.ts +3 -6
  37. package/dist/exporters/api-testcase-formatter.d.ts.map +1 -1
  38. package/dist/exporters/api-testcase-formatter.js +7 -10
  39. package/dist/exporters/api-testcase-formatter.js.map +1 -1
  40. package/dist/exporters/feature-parser.d.ts +1 -1
  41. package/dist/exporters/feature-parser.js +1 -1
  42. package/dist/exporters/matrix/build.d.ts.map +1 -1
  43. package/dist/exporters/matrix/build.js +18 -3
  44. package/dist/exporters/matrix/build.js.map +1 -1
  45. package/dist/exporters/matrix/item-labels.d.ts +35 -0
  46. package/dist/exporters/matrix/item-labels.d.ts.map +1 -0
  47. package/dist/exporters/matrix/item-labels.js +90 -0
  48. package/dist/exporters/matrix/item-labels.js.map +1 -0
  49. package/dist/exporters/matrix/render-coverage-sheet.d.ts +14 -0
  50. package/dist/exporters/matrix/render-coverage-sheet.d.ts.map +1 -0
  51. package/dist/exporters/matrix/render-coverage-sheet.js +162 -0
  52. package/dist/exporters/matrix/render-coverage-sheet.js.map +1 -0
  53. package/dist/exporters/matrix/render-csv.d.ts.map +1 -1
  54. package/dist/exporters/matrix/render-csv.js +3 -1
  55. package/dist/exporters/matrix/render-csv.js.map +1 -1
  56. package/dist/exporters/matrix/render-overview-sheet.d.ts +20 -0
  57. package/dist/exporters/matrix/render-overview-sheet.d.ts.map +1 -0
  58. package/dist/exporters/matrix/render-overview-sheet.js +32 -0
  59. package/dist/exporters/matrix/render-overview-sheet.js.map +1 -0
  60. package/dist/exporters/matrix/render-testcase-sheet.d.ts +30 -0
  61. package/dist/exporters/matrix/render-testcase-sheet.d.ts.map +1 -0
  62. package/dist/exporters/matrix/render-testcase-sheet.js +156 -0
  63. package/dist/exporters/matrix/render-testcase-sheet.js.map +1 -0
  64. package/dist/exporters/matrix/render-xlsx.d.ts +41 -31
  65. package/dist/exporters/matrix/render-xlsx.d.ts.map +1 -1
  66. package/dist/exporters/matrix/render-xlsx.js +72 -425
  67. package/dist/exporters/matrix/render-xlsx.js.map +1 -1
  68. package/dist/exporters/matrix/result-totals.d.ts +23 -0
  69. package/dist/exporters/matrix/result-totals.d.ts.map +1 -0
  70. package/dist/exporters/matrix/result-totals.js +21 -0
  71. package/dist/exporters/matrix/result-totals.js.map +1 -0
  72. package/dist/exporters/matrix/testcase-summary-band.d.ts +30 -0
  73. package/dist/exporters/matrix/testcase-summary-band.d.ts.map +1 -0
  74. package/dist/exporters/matrix/testcase-summary-band.js +71 -0
  75. package/dist/exporters/matrix/testcase-summary-band.js.map +1 -0
  76. package/dist/exporters/matrix/wording.d.ts.map +1 -1
  77. package/dist/exporters/matrix/wording.js +13 -0
  78. package/dist/exporters/matrix/wording.js.map +1 -1
  79. package/dist/exporters/matrix/xlsx-cells.d.ts +38 -0
  80. package/dist/exporters/matrix/xlsx-cells.d.ts.map +1 -0
  81. package/dist/exporters/matrix/xlsx-cells.js +52 -0
  82. package/dist/exporters/matrix/xlsx-cells.js.map +1 -0
  83. package/dist/exporters/qa-env-secret-denylist.js +2 -2
  84. package/dist/exporters/qa-env-secret-denylist.js.map +1 -1
  85. package/dist/exporters/report-path-sanitizer.d.ts +38 -0
  86. package/dist/exporters/report-path-sanitizer.d.ts.map +1 -0
  87. package/dist/exporters/report-path-sanitizer.js +137 -0
  88. package/dist/exporters/report-path-sanitizer.js.map +1 -0
  89. package/dist/exporters/result-variants.d.ts.map +1 -1
  90. package/dist/exporters/result-variants.js +2 -1
  91. package/dist/exporters/result-variants.js.map +1 -1
  92. package/dist/exporters/sensitive-key.d.ts +31 -0
  93. package/dist/exporters/sensitive-key.d.ts.map +1 -0
  94. package/dist/exporters/sensitive-key.js +50 -0
  95. package/dist/exporters/sensitive-key.js.map +1 -0
  96. package/dist/exporters/test-data-resolver.d.ts +4 -1
  97. package/dist/exporters/test-data-resolver.d.ts.map +1 -1
  98. package/dist/exporters/test-data-resolver.js +12 -2
  99. package/dist/exporters/test-data-resolver.js.map +1 -1
  100. package/dist/exporters/xlsx-report-builder.d.ts +12 -1
  101. package/dist/exporters/xlsx-report-builder.d.ts.map +1 -1
  102. package/dist/exporters/xlsx-report-builder.js +10 -2
  103. package/dist/exporters/xlsx-report-builder.js.map +1 -1
  104. package/dist/generators/gherkin-parser/index.d.ts +10 -0
  105. package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
  106. package/dist/generators/gherkin-parser/index.js +20 -12
  107. package/dist/generators/gherkin-parser/index.js.map +1 -1
  108. package/dist/generators/gherkin-parser/parent-scope-clause.d.ts +54 -0
  109. package/dist/generators/gherkin-parser/parent-scope-clause.d.ts.map +1 -0
  110. package/dist/generators/gherkin-parser/parent-scope-clause.js +109 -0
  111. package/dist/generators/gherkin-parser/parent-scope-clause.js.map +1 -0
  112. package/dist/generators/test-generator/adapters/adapter-interface.d.ts +23 -18
  113. package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
  114. package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts +4 -19
  115. package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts.map +1 -1
  116. package/dist/generators/test-generator/adapters/appium/appium-adapter.js.map +1 -1
  117. package/dist/generators/test-generator/adapters/appium/os-support.d.ts +53 -0
  118. package/dist/generators/test-generator/adapters/appium/os-support.d.ts.map +1 -0
  119. package/dist/generators/test-generator/adapters/appium/os-support.js +200 -0
  120. package/dist/generators/test-generator/adapters/appium/os-support.js.map +1 -0
  121. package/dist/generators/test-generator/adapters/appium/templates/after-all.hbs +18 -0
  122. package/dist/generators/test-generator/adapters/appium/templates/after-each.hbs +18 -0
  123. package/dist/generators/test-generator/adapters/appium/templates/before-all.hbs +18 -0
  124. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/alert-fill-action.hbs +9 -2
  125. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/capture-variable.hbs +24 -1
  126. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/click-at-position-action.hbs +1 -0
  127. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/click-element-with-text.hbs +1 -1
  128. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/double-click-action.hbs +7 -0
  129. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/frame-enter-action.hbs +3 -6
  130. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/hover-action.hbs +6 -2
  131. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/hover-element-with-text.hbs +4 -3
  132. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/table-paginate-action.hbs +3 -0
  133. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/unknown-element-action.hbs +9 -0
  134. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/have-value-assertion.hbs +34 -1
  135. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +14 -6
  136. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-assertion.hbs +1 -1
  137. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/go-back-action.hbs +21 -0
  138. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/long-press-action.hbs +4 -0
  139. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/pinch-zoom-action.hbs +4 -0
  140. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/set-geolocation-action.hbs +16 -0
  141. package/dist/generators/test-generator/adapters/appium/templates/steps/partials/locator.hbs +9 -1
  142. package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +62 -23
  143. package/dist/generators/test-generator/adapters/index.d.ts +1 -1
  144. package/dist/generators/test-generator/adapters/index.d.ts.map +1 -1
  145. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +4 -19
  146. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
  147. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
  148. package/dist/generators/test-generator/adapters/playwright/templates/after-all.hbs +19 -1
  149. package/dist/generators/test-generator/adapters/playwright/templates/after-each.hbs +18 -0
  150. package/dist/generators/test-generator/adapters/playwright/templates/before-all.hbs +19 -1
  151. package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  152. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/check-action.hbs +6 -1
  153. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/click-at-position-action.hbs +23 -0
  154. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/expand-action.hbs +8 -8
  155. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/fill-editor-action.hbs +7 -2
  156. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row-nth.hbs +7 -2
  157. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +7 -2
  158. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-paginate-action.hbs +5 -0
  159. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/uncheck-action.hbs +3 -1
  160. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/unknown-element-action.hbs +1 -1
  161. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/wait-for-page.hbs +18 -1
  162. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-assertion.hbs +32 -4
  163. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-element.hbs +5 -2
  164. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/checked-assertion.hbs +5 -1
  165. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/column-cell-assertion.hbs +13 -2
  166. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/contain-text-assertion.hbs +1 -1
  167. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +2 -2
  168. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-variable-assertion.hbs +1 -1
  169. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/have-text-assertion.hbs +1 -1
  170. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +2 -2
  171. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-variable-assertion.hbs +1 -1
  172. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/not-checked-assertion.hbs +2 -1
  173. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/selected-assertion.hbs +21 -3
  174. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/state-with-filter-assertion.hbs +20 -2
  175. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-empty.hbs +4 -1
  176. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-match-data.hbs +4 -1
  177. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-count.hbs +4 -1
  178. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-exists.hbs +12 -4
  179. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-not-exists.hbs +14 -2
  180. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +3 -3
  181. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-value-assertion.hbs +1 -1
  182. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-variable-assertion.hbs +1 -1
  183. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/checked-opts.hbs +8 -0
  184. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +8 -8
  185. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-nth.hbs +1 -1
  186. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/id.hbs +1 -1
  187. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/testid.hbs +1 -1
  188. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +8 -8
  189. package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +10 -8
  190. package/dist/generators/test-generator/code-generator.d.ts +37 -0
  191. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  192. package/dist/generators/test-generator/code-generator.js +165 -40
  193. package/dist/generators/test-generator/code-generator.js.map +1 -1
  194. package/dist/generators/test-generator/diagnostics.d.ts +116 -6
  195. package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
  196. package/dist/generators/test-generator/diagnostics.js +115 -5
  197. package/dist/generators/test-generator/diagnostics.js.map +1 -1
  198. package/dist/generators/test-generator/patterns/index.d.ts +57 -11
  199. package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
  200. package/dist/generators/test-generator/patterns/index.js +163 -21
  201. package/dist/generators/test-generator/patterns/index.js.map +1 -1
  202. package/dist/generators/test-generator/patterns/types.d.ts +8 -0
  203. package/dist/generators/test-generator/patterns/types.d.ts.map +1 -1
  204. package/dist/generators/test-generator/patterns/with-parent-scope.d.ts +18 -0
  205. package/dist/generators/test-generator/patterns/with-parent-scope.d.ts.map +1 -0
  206. package/dist/generators/test-generator/patterns/with-parent-scope.js +40 -0
  207. package/dist/generators/test-generator/patterns/with-parent-scope.js.map +1 -0
  208. package/dist/generators/test-generator/step-mapper.d.ts +103 -10
  209. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  210. package/dist/generators/test-generator/step-mapper.js +266 -45
  211. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  212. package/dist/generators/test-generator/template-engine.d.ts +4 -19
  213. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  214. package/dist/generators/test-generator/template-engine.js +17 -12
  215. package/dist/generators/test-generator/template-engine.js.map +1 -1
  216. package/dist/generators/test-generator/utils/data-resolver.d.ts +7 -0
  217. package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
  218. package/dist/generators/test-generator/utils/data-resolver.js +19 -2
  219. package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
  220. package/dist/generators/test-generator/utils/dialog-scope-identity.d.ts +41 -0
  221. package/dist/generators/test-generator/utils/dialog-scope-identity.d.ts.map +1 -0
  222. package/dist/generators/test-generator/utils/dialog-scope-identity.js +61 -0
  223. package/dist/generators/test-generator/utils/dialog-scope-identity.js.map +1 -0
  224. package/dist/generators/test-generator/utils/escape-js-string.d.ts +17 -0
  225. package/dist/generators/test-generator/utils/escape-js-string.d.ts.map +1 -0
  226. package/dist/generators/test-generator/utils/escape-js-string.js +29 -0
  227. package/dist/generators/test-generator/utils/escape-js-string.js.map +1 -0
  228. package/dist/generators/test-generator/utils/nearest-match.d.ts +24 -0
  229. package/dist/generators/test-generator/utils/nearest-match.d.ts.map +1 -0
  230. package/dist/generators/test-generator/utils/nearest-match.js +56 -0
  231. package/dist/generators/test-generator/utils/nearest-match.js.map +1 -0
  232. package/dist/generators/test-generator/utils/selector-entry-fields.d.ts +58 -0
  233. package/dist/generators/test-generator/utils/selector-entry-fields.d.ts.map +1 -0
  234. package/dist/generators/test-generator/utils/selector-entry-fields.js +119 -0
  235. package/dist/generators/test-generator/utils/selector-entry-fields.js.map +1 -0
  236. package/dist/generators/test-generator/utils/selector-entry-walk.d.ts +25 -0
  237. package/dist/generators/test-generator/utils/selector-entry-walk.d.ts.map +1 -0
  238. package/dist/generators/test-generator/utils/selector-entry-walk.js +54 -0
  239. package/dist/generators/test-generator/utils/selector-entry-walk.js.map +1 -0
  240. package/dist/generators/test-generator/utils/selector-nth-validation.d.ts +59 -0
  241. package/dist/generators/test-generator/utils/selector-nth-validation.d.ts.map +1 -0
  242. package/dist/generators/test-generator/utils/selector-nth-validation.js +91 -0
  243. package/dist/generators/test-generator/utils/selector-nth-validation.js.map +1 -0
  244. package/dist/generators/test-generator/utils/selector-resolver.d.ts +53 -2
  245. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
  246. package/dist/generators/test-generator/utils/selector-resolver.js +273 -27
  247. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
  248. package/dist/generators/test-generator/utils/selector-type-validation.d.ts +51 -0
  249. package/dist/generators/test-generator/utils/selector-type-validation.d.ts.map +1 -0
  250. package/dist/generators/test-generator/utils/selector-type-validation.js +77 -0
  251. package/dist/generators/test-generator/utils/selector-type-validation.js.map +1 -0
  252. package/dist/harness/audit.d.ts +3 -1
  253. package/dist/harness/audit.d.ts.map +1 -1
  254. package/dist/harness/audit.js +51 -28
  255. package/dist/harness/audit.js.map +1 -1
  256. package/dist/harness/capability-plan.d.ts +4 -0
  257. package/dist/harness/capability-plan.d.ts.map +1 -1
  258. package/dist/harness/capability-plan.js +32 -2
  259. package/dist/harness/capability-plan.js.map +1 -1
  260. package/dist/harness/catalog/drivers.yaml +1 -1
  261. package/dist/harness/catalog/universal-viewpoints.yaml +145 -0
  262. package/dist/harness/data-driven-lint.d.ts.map +1 -1
  263. package/dist/harness/data-driven-lint.js +217 -33
  264. package/dist/harness/data-driven-lint.js.map +1 -1
  265. package/dist/harness/parse.d.ts +6 -0
  266. package/dist/harness/parse.d.ts.map +1 -1
  267. package/dist/harness/parse.js +1 -1
  268. package/dist/harness/parse.js.map +1 -1
  269. package/dist/harness/repair.d.ts.map +1 -1
  270. package/dist/harness/repair.js +8 -1
  271. package/dist/harness/repair.js.map +1 -1
  272. package/dist/harness/script-check.d.ts +2 -2
  273. package/dist/harness/script-check.d.ts.map +1 -1
  274. package/dist/harness/script-check.js +186 -25
  275. package/dist/harness/script-check.js.map +1 -1
  276. package/dist/harness/sensors.d.ts +48 -0
  277. package/dist/harness/sensors.d.ts.map +1 -1
  278. package/dist/harness/sensors.js +26 -1
  279. package/dist/harness/sensors.js.map +1 -1
  280. package/dist/harness/viewpoint-ledger.d.ts +10 -0
  281. package/dist/harness/viewpoint-ledger.d.ts.map +1 -1
  282. package/dist/harness/viewpoint-ledger.js +23 -2
  283. package/dist/harness/viewpoint-ledger.js.map +1 -1
  284. package/dist/index.d.ts +2 -0
  285. package/dist/index.d.ts.map +1 -1
  286. package/dist/index.js +10 -1
  287. package/dist/index.js.map +1 -1
  288. package/dist/orchestrator/project-initializer.d.ts +6 -0
  289. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  290. package/dist/orchestrator/project-initializer.js +16 -0
  291. package/dist/orchestrator/project-initializer.js.map +1 -1
  292. package/dist/orchestrator/report-sanitize-reporter-install.d.ts +24 -0
  293. package/dist/orchestrator/report-sanitize-reporter-install.d.ts.map +1 -0
  294. package/dist/orchestrator/report-sanitize-reporter-install.js +161 -0
  295. package/dist/orchestrator/report-sanitize-reporter-install.js.map +1 -0
  296. package/dist/orchestrator/templates/ai-src/commands/delivery.md +4 -3
  297. package/dist/orchestrator/templates/ai-src/commands/run-test.md +9 -1
  298. package/dist/orchestrator/templates/ai-src/skills/sungen-api-coverage-model/SKILL.md +10 -0
  299. package/dist/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +14 -5
  300. package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +62 -3
  301. package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +126 -11
  302. package/dist/orchestrator/templates/ai-src/skills/sungen-mobile-gestures/SKILL.md +16 -1
  303. package/dist/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +12 -0
  304. package/dist/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +19 -5
  305. package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +88 -2
  306. package/dist/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +62 -5
  307. package/dist/orchestrator/templates/ai-src/skills/sungen-test-design-techniques/SKILL.md +13 -0
  308. package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/SKILL.md +59 -10
  309. package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/group-f-mobile-native.md +419 -0
  310. package/dist/orchestrator/templates/playwright.config.ts +7 -0
  311. package/dist/orchestrator/templates/readme.md +1 -1
  312. package/dist/orchestrator/templates/specs-checked-state.d.ts +83 -0
  313. package/dist/orchestrator/templates/specs-checked-state.d.ts.map +1 -0
  314. package/dist/orchestrator/templates/specs-checked-state.js +261 -0
  315. package/dist/orchestrator/templates/specs-checked-state.js.map +1 -0
  316. package/dist/orchestrator/templates/specs-checked-state.ts +324 -0
  317. package/dist/orchestrator/templates/specs-pw-shape-reporter.ts +0 -0
  318. package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.d.ts +36 -0
  319. package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.d.ts.map +1 -0
  320. package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.js +155 -0
  321. package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.js.map +1 -0
  322. package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.ts +142 -0
  323. package/dist/orchestrator/templates/specs-url-assert.d.ts +50 -25
  324. package/dist/orchestrator/templates/specs-url-assert.d.ts.map +1 -1
  325. package/dist/orchestrator/templates/specs-url-assert.js +65 -24
  326. package/dist/orchestrator/templates/specs-url-assert.js.map +1 -1
  327. package/dist/orchestrator/templates/specs-url-assert.ts +101 -28
  328. package/dist/orchestrator/templates/wdio.conf.ts +105 -1
  329. package/dist/utils/selector-types.d.ts +43 -1
  330. package/dist/utils/selector-types.d.ts.map +1 -1
  331. package/dist/utils/selector-types.js +51 -7
  332. package/dist/utils/selector-types.js.map +1 -1
  333. package/package.json +3 -3
  334. package/src/capabilities/discover.ts +146 -10
  335. package/src/capabilities/registry.ts +30 -0
  336. package/src/cli/commands/audit.ts +9 -0
  337. package/src/cli/commands/capability.ts +1 -1
  338. package/src/cli/commands/delivery.ts +2 -1
  339. package/src/cli/commands/generate.ts +5 -2
  340. package/src/cli/commands/inspect.ts +1 -1
  341. package/src/cli/commands/results-merge.ts +9 -2
  342. package/src/cli/commands/results-sanitize.ts +98 -0
  343. package/src/cli/commands/script-check.ts +3 -1
  344. package/src/cli/commands/update.ts +3 -0
  345. package/src/dashboard/templates/index.html +1 -1
  346. package/src/exporters/api-testcase-formatter.ts +5 -7
  347. package/src/exporters/feature-parser.ts +1 -1
  348. package/src/exporters/matrix/build.ts +17 -3
  349. package/src/exporters/matrix/item-labels.ts +78 -0
  350. package/src/exporters/matrix/render-coverage-sheet.ts +170 -0
  351. package/src/exporters/matrix/render-csv.ts +3 -1
  352. package/src/exporters/matrix/render-overview-sheet.ts +51 -0
  353. package/src/exporters/matrix/render-testcase-sheet.ts +178 -0
  354. package/src/exporters/matrix/render-xlsx.ts +67 -448
  355. package/src/exporters/matrix/result-totals.ts +37 -0
  356. package/src/exporters/matrix/testcase-summary-band.ts +74 -0
  357. package/src/exporters/matrix/wording.ts +13 -0
  358. package/src/exporters/matrix/xlsx-cells.ts +56 -0
  359. package/src/exporters/qa-env-secret-denylist.ts +1 -1
  360. package/src/exporters/report-path-sanitizer.ts +144 -0
  361. package/src/exporters/result-variants.ts +2 -1
  362. package/src/exporters/sensitive-key.ts +48 -0
  363. package/src/exporters/test-data-resolver.ts +12 -2
  364. package/src/exporters/xlsx-report-builder.ts +16 -2
  365. package/src/generators/gherkin-parser/index.ts +22 -11
  366. package/src/generators/gherkin-parser/parent-scope-clause.ts +130 -0
  367. package/src/generators/test-generator/adapters/adapter-interface.ts +25 -3
  368. package/src/generators/test-generator/adapters/appium/appium-adapter.ts +4 -4
  369. package/src/generators/test-generator/adapters/appium/os-support.ts +176 -0
  370. package/src/generators/test-generator/adapters/appium/templates/after-all.hbs +18 -0
  371. package/src/generators/test-generator/adapters/appium/templates/after-each.hbs +18 -0
  372. package/src/generators/test-generator/adapters/appium/templates/before-all.hbs +18 -0
  373. package/src/generators/test-generator/adapters/appium/templates/steps/actions/alert-fill-action.hbs +9 -2
  374. package/src/generators/test-generator/adapters/appium/templates/steps/actions/capture-variable.hbs +24 -1
  375. package/src/generators/test-generator/adapters/appium/templates/steps/actions/click-at-position-action.hbs +1 -0
  376. package/src/generators/test-generator/adapters/appium/templates/steps/actions/click-element-with-text.hbs +1 -1
  377. package/src/generators/test-generator/adapters/appium/templates/steps/actions/double-click-action.hbs +7 -0
  378. package/src/generators/test-generator/adapters/appium/templates/steps/actions/frame-enter-action.hbs +3 -6
  379. package/src/generators/test-generator/adapters/appium/templates/steps/actions/hover-action.hbs +6 -2
  380. package/src/generators/test-generator/adapters/appium/templates/steps/actions/hover-element-with-text.hbs +4 -3
  381. package/src/generators/test-generator/adapters/appium/templates/steps/actions/table-paginate-action.hbs +3 -0
  382. package/src/generators/test-generator/adapters/appium/templates/steps/actions/unknown-element-action.hbs +9 -0
  383. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/have-value-assertion.hbs +34 -1
  384. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +14 -6
  385. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-assertion.hbs +1 -1
  386. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/go-back-action.hbs +21 -0
  387. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/long-press-action.hbs +4 -0
  388. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/pinch-zoom-action.hbs +4 -0
  389. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/set-geolocation-action.hbs +16 -0
  390. package/src/generators/test-generator/adapters/appium/templates/steps/partials/locator.hbs +9 -1
  391. package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +62 -23
  392. package/src/generators/test-generator/adapters/index.ts +1 -1
  393. package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +4 -4
  394. package/src/generators/test-generator/adapters/playwright/templates/after-all.hbs +19 -1
  395. package/src/generators/test-generator/adapters/playwright/templates/after-each.hbs +18 -0
  396. package/src/generators/test-generator/adapters/playwright/templates/before-all.hbs +19 -1
  397. package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  398. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/check-action.hbs +6 -1
  399. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/click-at-position-action.hbs +23 -0
  400. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/expand-action.hbs +8 -8
  401. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/fill-editor-action.hbs +7 -2
  402. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row-nth.hbs +7 -2
  403. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +7 -2
  404. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-paginate-action.hbs +5 -0
  405. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/uncheck-action.hbs +3 -1
  406. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/unknown-element-action.hbs +1 -1
  407. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/wait-for-page.hbs +18 -1
  408. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-assertion.hbs +32 -4
  409. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-element.hbs +5 -2
  410. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/checked-assertion.hbs +5 -1
  411. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/column-cell-assertion.hbs +13 -2
  412. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/contain-text-assertion.hbs +1 -1
  413. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +2 -2
  414. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-variable-assertion.hbs +1 -1
  415. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/have-text-assertion.hbs +1 -1
  416. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +2 -2
  417. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-variable-assertion.hbs +1 -1
  418. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/not-checked-assertion.hbs +2 -1
  419. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/selected-assertion.hbs +21 -3
  420. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/state-with-filter-assertion.hbs +20 -2
  421. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-empty.hbs +4 -1
  422. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-match-data.hbs +4 -1
  423. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-count.hbs +4 -1
  424. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-exists.hbs +12 -4
  425. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-not-exists.hbs +14 -2
  426. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +3 -3
  427. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-value-assertion.hbs +1 -1
  428. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-variable-assertion.hbs +1 -1
  429. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/checked-opts.hbs +8 -0
  430. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +8 -8
  431. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-nth.hbs +1 -1
  432. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/id.hbs +1 -1
  433. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/testid.hbs +1 -1
  434. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +8 -8
  435. package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +10 -8
  436. package/src/generators/test-generator/code-generator.ts +181 -46
  437. package/src/generators/test-generator/diagnostics.ts +117 -6
  438. package/src/generators/test-generator/patterns/index.ts +175 -24
  439. package/src/generators/test-generator/patterns/types.ts +8 -0
  440. package/src/generators/test-generator/patterns/with-parent-scope.ts +41 -0
  441. package/src/generators/test-generator/step-mapper.ts +277 -43
  442. package/src/generators/test-generator/template-engine.ts +21 -21
  443. package/src/generators/test-generator/utils/data-resolver.ts +23 -2
  444. package/src/generators/test-generator/utils/dialog-scope-identity.ts +71 -0
  445. package/src/generators/test-generator/utils/escape-js-string.ts +24 -0
  446. package/src/generators/test-generator/utils/nearest-match.ts +55 -0
  447. package/src/generators/test-generator/utils/selector-entry-fields.ts +131 -0
  448. package/src/generators/test-generator/utils/selector-entry-walk.ts +58 -0
  449. package/src/generators/test-generator/utils/selector-nth-validation.ts +99 -0
  450. package/src/generators/test-generator/utils/selector-resolver.ts +333 -28
  451. package/src/generators/test-generator/utils/selector-type-validation.ts +87 -0
  452. package/src/harness/audit.ts +55 -30
  453. package/src/harness/capability-plan.ts +33 -1
  454. package/src/harness/catalog/drivers.yaml +1 -1
  455. package/src/harness/catalog/universal-viewpoints.yaml +145 -0
  456. package/src/harness/data-driven-lint.ts +192 -32
  457. package/src/harness/parse.ts +7 -1
  458. package/src/harness/repair.ts +8 -1
  459. package/src/harness/script-check.ts +186 -21
  460. package/src/harness/sensors.ts +75 -1
  461. package/src/harness/viewpoint-ledger.ts +26 -5
  462. package/src/index.ts +7 -0
  463. package/src/orchestrator/project-initializer.ts +17 -0
  464. package/src/orchestrator/report-sanitize-reporter-install.ts +109 -0
  465. package/src/orchestrator/templates/ai-src/commands/delivery.md +4 -3
  466. package/src/orchestrator/templates/ai-src/commands/run-test.md +9 -1
  467. package/src/orchestrator/templates/ai-src/skills/sungen-api-coverage-model/SKILL.md +10 -0
  468. package/src/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +14 -5
  469. package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +62 -3
  470. package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +126 -11
  471. package/src/orchestrator/templates/ai-src/skills/sungen-mobile-gestures/SKILL.md +16 -1
  472. package/src/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +12 -0
  473. package/src/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +19 -5
  474. package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +88 -2
  475. package/src/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +62 -5
  476. package/src/orchestrator/templates/ai-src/skills/sungen-test-design-techniques/SKILL.md +13 -0
  477. package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/SKILL.md +59 -10
  478. package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/group-f-mobile-native.md +419 -0
  479. package/src/orchestrator/templates/playwright.config.ts +7 -0
  480. package/src/orchestrator/templates/readme.md +1 -1
  481. package/src/orchestrator/templates/specs-checked-state.ts +324 -0
  482. package/src/orchestrator/templates/specs-pw-shape-reporter.ts +0 -0
  483. package/src/orchestrator/templates/specs-report-path-sanitize-reporter.ts +142 -0
  484. package/src/orchestrator/templates/specs-url-assert.ts +101 -28
  485. package/src/orchestrator/templates/wdio.conf.ts +105 -1
  486. package/src/utils/selector-types.ts +54 -5
@@ -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}}'); }
@@ -1,2 +1,7 @@
1
- { const tableRow = {{> locator}}.getByRole('row').nth({{{rowIndexExpr}}});
2
- await {{{actionLocator}}}.{{action}}(); }
1
+ {{!-- Row universe: `rows:` on the table entry (what EVERY row matches — a <div> list has no
2
+ role=row, so the ARIA chain below is 0 elements and this assertion would be vacuous, issue #688)
3
+ wins; otherwise the historical ARIA chain, byte-identical. --}}
4
+ { const tableRow = {{> locator}}{{#if rows}}.locator('{{escapeQuotes rows}}'){{else}}.getByRole('row'){{/if}}.nth({{{rowIndexExpr}}});
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}} }
@@ -1,2 +1,7 @@
1
- { const tableRow = {{> locator}}.getByRole('row').filter({ hasText: '{{escapeQuotes filterValue}}' });
2
- await {{{actionLocator}}}.{{action}}(); }
1
+ {{!-- Row universe: `rows:` on the table entry (what EVERY row matches — a <div> list has no
2
+ role=row, so the ARIA chain below is 0 elements and this assertion would be vacuous, issue #688)
3
+ wins; otherwise the historical ARIA chain, byte-identical. --}}
4
+ { const tableRow = {{> locator}}{{#if rows}}.locator('{{escapeQuotes rows}}'){{else}}.getByRole('row'){{/if}}.filter({ hasText: '{{escapeQuotes filterValue}}' });
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
@@ -9,8 +18,17 @@ await expect({{> locator}}).toBeChecked();
9
18
  { const el = {{> locator}};
10
19
  if ((await el.getAttribute('aria-selected')) !== null) {
11
20
  await expect(el).toHaveAttribute('aria-selected', 'true');
21
+ } else if ((await el.getAttribute('aria-current')) !== null) {
22
+ {{!-- aria-current marks the CURRENT item of a navigation set (WAI-ARIA 1.1): a Next.js tab strip
23
+ of plain <a> links carries aria-current="page" and has neither aria-selected nor a checked
24
+ state, so toBeChecked() threw "Not a checkbox or radio button" on a correct step. Any value
25
+ but "false" ("page", "step", "true", …) means current. Probed AFTER aria-selected: a real
26
+ role=tab owns the stronger signal. --}}
27
+ await expect(el).toHaveAttribute('aria-current', /^(?!false$)./);
12
28
  } else {
13
- 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}}');
14
32
  }
15
33
  }
16
34
  {{/if}}
@@ -1,13 +1,31 @@
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');
13
+ } else if ((await el.getAttribute('aria-current')) !== null) {
14
+ {{!-- aria-current marks the CURRENT item of a navigation set (WAI-ARIA 1.1): a Next.js tab strip
15
+ of plain <a> links carries aria-current="page" and has neither aria-selected nor a checked
16
+ state, so toBeChecked() threw "Not a checkbox or radio button" on a correct step. Any value
17
+ but "false" ("page", "step", "true", …) means current. Probed AFTER aria-selected: a real
18
+ role=tab owns the stronger signal. --}}
19
+ await expect(el).toHaveAttribute('aria-current', /^(?!false$)./);
7
20
  } else {
8
- 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}}');
9
24
  }
10
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}});
11
29
  {{else}}
12
30
  await expect({{> locator-base}}.filter({ hasText: {{> has-text-value dataValue=dataValue exact=exact}} }){{> locator-nth}}).{{{stateAssertion}}};
13
- {{/if}}
31
+ {{/if}}{{/if}}
@@ -1 +1,4 @@
1
- await expect({{> locator}}.locator('tbody').getByRole('row', { includeHidden: true }).filter({ visible: true })).toHaveCount(0);
1
+ {{!-- Row universe: `rows:` on the table entry (what EVERY row matches — a <div> list has no
2
+ role=row, so the ARIA chain below is 0 elements and this assertion would be vacuous, issue #688)
3
+ wins; otherwise the historical ARIA chain, byte-identical. --}}
4
+ await expect({{> locator}}{{#if rows}}.locator('{{escapeQuotes rows}}'){{else}}.locator('tbody').getByRole('row', { includeHidden: true }){{/if}}.filter({ visible: true })).toHaveCount(0);
@@ -1,6 +1,9 @@
1
1
  {
2
2
  const table = {{> locator}};
3
- const rows = table.locator('tbody').getByRole('row', { includeHidden: true }).filter({ visible: true });
3
+ {{!-- Row universe: `rows:` on the table entry (what EVERY row matches — a <div> list has no
4
+ role=row, so the ARIA chain below is 0 elements and this assertion would be vacuous, issue #688)
5
+ wins; otherwise the historical ARIA chain, byte-identical. --}}
6
+ const rows = table{{#if rows}}.locator('{{escapeQuotes rows}}'){{else}}.locator('tbody').getByRole('row', { includeHidden: true }){{/if}}.filter({ visible: true });
4
7
  {{#each assertions}}
5
8
  {{{this}}}
6
9
  {{/each~}}
@@ -1 +1,4 @@
1
- await expect({{> locator}}.locator('tbody').getByRole('row', { includeHidden: true }).filter({ visible: true })).toHaveCount({{expectedCount}});
1
+ {{!-- Row universe: `rows:` on the table entry (what EVERY row matches — a <div> list has no
2
+ role=row, so the ARIA chain below is 0 elements and this assertion would be vacuous, issue #688)
3
+ wins; otherwise the historical ARIA chain, byte-identical. --}}
4
+ await expect({{> locator}}{{#if rows}}.locator('{{escapeQuotes rows}}'){{else}}.locator('tbody').getByRole('row', { includeHidden: true }){{/if}}.filter({ visible: true })).toHaveCount({{expectedCount}});
@@ -1,17 +1,25 @@
1
1
  {{~#if rowSelector}}
2
+ {{!-- Declared row LABEL = one positioned row. The toHaveCount(1) guard is what turns "I used the
3
+ row label as the row SHAPE" (div.job-row, issue #688) into a named error instead of a
4
+ strict-mode message pointing at the wrong line. --}}
2
5
  {{~#if isGiven}}
3
- const tableRow = {{> locator}}.locator('{{rowSelector}}');
6
+ const tableRow = {{> locator}}.locator('{{escapeQuotes rowSelector}}');
7
+ await expect(tableRow, 'row label "[{{escapeQuotes rowLabel}}]" must position exactly ONE row (its selectors.yaml entry is relative to the table) — if this selector describes what EVERY row looks like, declare it as `rows:` on the TABLE entry instead').toHaveCount(1);
4
8
  await tableRow.filter({ hasText: '{{escapeQuotes filterValue}}' }).waitFor();
5
9
  {{~else}}
6
- const tableRow = {{> locator}}.locator('{{rowSelector}}');
10
+ const tableRow = {{> locator}}.locator('{{escapeQuotes rowSelector}}');
11
+ await expect(tableRow, 'row label "[{{escapeQuotes rowLabel}}]" must position exactly ONE row (its selectors.yaml entry is relative to the table) — if this selector describes what EVERY row looks like, declare it as `rows:` on the TABLE entry instead').toHaveCount(1);
7
12
  await expect(tableRow).toContainText('{{escapeQuotes filterValue}}');
8
13
  {{~/if}}
9
14
  {{~else}}
15
+ {{!-- Row universe: `rows:` on the table entry (what EVERY row matches — a <div> list has no
16
+ role=row, so the ARIA chain below is 0 elements and this assertion would be vacuous, issue #688)
17
+ wins; otherwise the historical ARIA chain, byte-identical. --}}
10
18
  {{~#if isGiven}}
11
- const tableRow = {{> locator}}.getByRole('row', { includeHidden: true }).filter({ hasText: '{{escapeQuotes filterValue}}' }).filter({ visible: true });
19
+ const tableRow = {{> locator}}{{#if rows}}.locator('{{escapeQuotes rows}}'){{else}}.getByRole('row', { includeHidden: true }){{/if}}.filter({ hasText: '{{escapeQuotes filterValue}}' }).filter({ visible: true });
12
20
  await tableRow.waitFor();
13
21
  {{~else}}
14
- const tableRow = {{> locator}}.getByRole('row', { includeHidden: true }).filter({ hasText: '{{escapeQuotes filterValue}}' }).filter({ visible: true });
22
+ const tableRow = {{> locator}}{{#if rows}}.locator('{{escapeQuotes rows}}'){{else}}.getByRole('row', { includeHidden: true }){{/if}}.filter({ hasText: '{{escapeQuotes filterValue}}' }).filter({ visible: true });
15
23
  await expect(tableRow).toBeVisible();
16
24
  {{~/if}}
17
25
  {{~/if}}
@@ -1,5 +1,17 @@
1
+ {{~#if rowSelector}}
2
+ {{!-- Declared row LABEL = one positioned row (same contract as the exists twin). The row must not
3
+ hold the value. Block-scoped: this step does not enter row scope, so it must not redeclare
4
+ the function-level tableRow the exists step binds. --}}
5
+ { const tableRow = {{> locator}}.locator('{{escapeQuotes rowSelector}}');
6
+ await expect(tableRow, 'row label "[{{escapeQuotes rowLabel}}]" must position exactly ONE row (its selectors.yaml entry is relative to the table) — if this selector describes what EVERY row looks like, declare it as `rows:` on the TABLE entry instead').toHaveCount(1);
7
+ await expect(tableRow).not.toContainText('{{escapeQuotes filterValue}}'); }
8
+ {{~else}}
9
+ {{!-- Row universe: `rows:` on the table entry (what EVERY row matches — a <div> list has no
10
+ role=row, so the ARIA chain below is 0 elements and this assertion would be vacuous, issue #688)
11
+ wins; otherwise the historical ARIA chain, byte-identical. --}}
1
12
  {{~#if isGiven}}
2
- await {{> locator}}.getByRole('row', { includeHidden: true }).filter({ hasText: '{{escapeQuotes filterValue}}' }).filter({ visible: true }).waitFor({ state: 'hidden' });
13
+ await {{> locator}}{{#if rows}}.locator('{{escapeQuotes rows}}'){{else}}.getByRole('row', { includeHidden: true }){{/if}}.filter({ hasText: '{{escapeQuotes filterValue}}' }).filter({ visible: true }).waitFor({ state: 'hidden' });
3
14
  {{~else}}
4
- await expect({{> locator}}.getByRole('row', { includeHidden: true }).filter({ hasText: '{{escapeQuotes filterValue}}' }).filter({ visible: true })).toHaveCount(0);
15
+ await expect({{> locator}}{{#if rows}}.locator('{{escapeQuotes rows}}'){{else}}.getByRole('row', { includeHidden: true }){{/if}}.filter({ hasText: '{{escapeQuotes filterValue}}' }).filter({ visible: true })).toHaveCount(0);
16
+ {{~/if}}
5
17
  {{~/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,22 +1,22 @@
1
1
  {{#if parentLocator}}{{parentLocator}}.{{#switch strategy~}}
2
- {{~#case 'testid'}}getByTestId('{{value}}'){{/case~}}
2
+ {{~#case 'testid'}}getByTestId('{{escapeQuotes value}}'){{/case~}}
3
3
  {{~#case 'role'}}{{#if name}}getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }){{else}}getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}){{/if}}{{/case~}}
4
4
  {{~#case 'placeholder'}}getByPlaceholder('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
5
5
  {{~#case 'label'}}getByLabel('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
6
6
  {{~#case 'text'}}getByText('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
7
- {{~#case 'locator'}}locator('{{value}}'){{/case~}}
8
- {{~#case 'id'}}locator('{{value}}'){{/case~}}
9
- {{~#default}}locator('{{value}}'){{/default~}}
7
+ {{~#case 'locator'}}locator('{{escapeQuotes value}}'){{/case~}}
8
+ {{~#case 'id'}}locator('{{escapeQuotes value}}'){{/case~}}
9
+ {{~#default}}locator('{{escapeQuotes value}}'){{/default~}}
10
10
  {{~/switch}}{{else~}}
11
11
  {{#if scope}}{{#if (eq scope "dialog")}}{{pageRoot}}.{{else}}{{pageRoot}}.getByLabel('{{escapeQuotes scope}}').{{/if}}{{#switch strategy~}}
12
- {{~#case 'testid'}}getByTestId('{{value}}'){{/case~}}
12
+ {{~#case 'testid'}}getByTestId('{{escapeQuotes value}}'){{/case~}}
13
13
  {{~#case 'role'}}{{#if name}}getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }){{else}}getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}){{/if}}{{/case~}}
14
14
  {{~#case 'placeholder'}}getByPlaceholder('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
15
15
  {{~#case 'label'}}getByLabel('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
16
16
  {{~#case 'text'}}getByText('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
17
- {{~#case 'locator'}}locator('{{value}}'){{/case~}}
18
- {{~#case 'id'}}locator('{{value}}'){{/case~}}
19
- {{~#default}}locator('{{value}}'){{/default~}}
17
+ {{~#case 'locator'}}locator('{{escapeQuotes value}}'){{/case~}}
18
+ {{~#case 'id'}}locator('{{escapeQuotes value}}'){{/case~}}
19
+ {{~#default}}locator('{{escapeQuotes value}}'){{/default~}}
20
20
  {{~/switch}}{{else}}{{#switch strategy~}}
21
21
  {{~#case 'testid'}}{{> locator-strategies/testid}}{{/case~}}
22
22
  {{~#case 'role'}}{{> locator-strategies/role}}{{/case~}}
@@ -1 +1 @@
1
- {{#if (gte nth 0)}}.nth({{nth}}){{/if}}
1
+ {{#if (gte nth 0)}}.nth({{nth}}){{else if (eq nth -1)}}.last(){{/if}}
@@ -1 +1 @@
1
- page.locator('{{selector}}')
1
+ page.locator('{{escapeQuotes selector}}')
@@ -1 +1 @@
1
- {{pageRoot}}.getByTestId('{{value}}')
1
+ {{pageRoot}}.getByTestId('{{escapeQuotes value}}')
@@ -1,22 +1,22 @@
1
1
  {{#if parentLocator}}{{parentLocator}}.{{#switch strategy~}}
2
- {{~#case 'testid'}}getByTestId('{{value}}'){{/case~}}
2
+ {{~#case 'testid'}}getByTestId('{{escapeQuotes value}}'){{/case~}}
3
3
  {{~#case 'role'}}{{#if name}}getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }){{else}}getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}){{/if}}{{/case~}}
4
4
  {{~#case 'placeholder'}}getByPlaceholder('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
5
5
  {{~#case 'label'}}getByLabel('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
6
6
  {{~#case 'text'}}getByText('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
7
- {{~#case 'locator'}}locator('{{value}}'){{/case~}}
8
- {{~#case 'id'}}locator('{{value}}'){{/case~}}
9
- {{~#default}}locator('{{value}}'){{/default~}}
7
+ {{~#case 'locator'}}locator('{{escapeQuotes value}}'){{/case~}}
8
+ {{~#case 'id'}}locator('{{escapeQuotes value}}'){{/case~}}
9
+ {{~#default}}locator('{{escapeQuotes value}}'){{/default~}}
10
10
  {{~/switch}}{{> locator-nth}}{{else~}}
11
11
  {{#if scope}}{{#if (eq scope "dialog")}}{{pageRoot}}.{{else}}{{pageRoot}}.getByLabel('{{escapeQuotes scope}}').{{/if}}{{#switch strategy~}}
12
- {{~#case 'testid'}}getByTestId('{{value}}'){{/case~}}
12
+ {{~#case 'testid'}}getByTestId('{{escapeQuotes value}}'){{/case~}}
13
13
  {{~#case 'role'}}{{#if name}}getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }){{else}}getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}){{/if}}{{/case~}}
14
14
  {{~#case 'placeholder'}}getByPlaceholder('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
15
15
  {{~#case 'label'}}getByLabel('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
16
16
  {{~#case 'text'}}getByText('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
17
- {{~#case 'locator'}}locator('{{value}}'){{/case~}}
18
- {{~#case 'id'}}locator('{{value}}'){{/case~}}
19
- {{~#default}}locator('{{value}}'){{/default~}}
17
+ {{~#case 'locator'}}locator('{{escapeQuotes value}}'){{/case~}}
18
+ {{~#case 'id'}}locator('{{escapeQuotes value}}'){{/case~}}
19
+ {{~#default}}locator('{{escapeQuotes value}}'){{/default~}}
20
20
  {{~/switch}}{{> locator-nth}}{{else}}{{#switch strategy~}}
21
21
  {{~#case 'testid'}}{{> locator-strategies/testid}}{{/case~}}
22
22
  {{~#case 'role'}}{{> locator-strategies/role}}{{/case~}}
@@ -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}}