@sun-asterisk/sungen 3.2.29 → 3.2.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (335) hide show
  1. package/dist/cli/commands/delivery.d.ts.map +1 -1
  2. package/dist/cli/commands/delivery.js +2 -1
  3. package/dist/cli/commands/delivery.js.map +1 -1
  4. package/dist/cli/commands/generate.d.ts.map +1 -1
  5. package/dist/cli/commands/generate.js +6 -2
  6. package/dist/cli/commands/generate.js.map +1 -1
  7. package/dist/cli/commands/inspect.js +1 -1
  8. package/dist/cli/commands/inspect.js.map +1 -1
  9. package/dist/cli/commands/results-merge.d.ts.map +1 -1
  10. package/dist/cli/commands/results-merge.js +9 -2
  11. package/dist/cli/commands/results-merge.js.map +1 -1
  12. package/dist/cli/commands/results-sanitize.d.ts +24 -0
  13. package/dist/cli/commands/results-sanitize.d.ts.map +1 -0
  14. package/dist/cli/commands/results-sanitize.js +126 -0
  15. package/dist/cli/commands/results-sanitize.js.map +1 -0
  16. package/dist/cli/commands/script-check.d.ts.map +1 -1
  17. package/dist/cli/commands/script-check.js +3 -1
  18. package/dist/cli/commands/script-check.js.map +1 -1
  19. package/dist/cli/commands/update.d.ts.map +1 -1
  20. package/dist/cli/commands/update.js +3 -0
  21. package/dist/cli/commands/update.js.map +1 -1
  22. package/dist/exporters/api-testcase-formatter.d.ts +3 -6
  23. package/dist/exporters/api-testcase-formatter.d.ts.map +1 -1
  24. package/dist/exporters/api-testcase-formatter.js +7 -10
  25. package/dist/exporters/api-testcase-formatter.js.map +1 -1
  26. package/dist/exporters/matrix/build.d.ts.map +1 -1
  27. package/dist/exporters/matrix/build.js +4 -2
  28. package/dist/exporters/matrix/build.js.map +1 -1
  29. package/dist/exporters/matrix/render-coverage-sheet.d.ts.map +1 -1
  30. package/dist/exporters/matrix/render-coverage-sheet.js +3 -1
  31. package/dist/exporters/matrix/render-coverage-sheet.js.map +1 -1
  32. package/dist/exporters/matrix/render-csv.d.ts.map +1 -1
  33. package/dist/exporters/matrix/render-csv.js +3 -1
  34. package/dist/exporters/matrix/render-csv.js.map +1 -1
  35. package/dist/exporters/matrix/render-overview-sheet.d.ts.map +1 -1
  36. package/dist/exporters/matrix/render-overview-sheet.js +5 -9
  37. package/dist/exporters/matrix/render-overview-sheet.js.map +1 -1
  38. package/dist/exporters/matrix/result-totals.d.ts +23 -0
  39. package/dist/exporters/matrix/result-totals.d.ts.map +1 -0
  40. package/dist/exporters/matrix/result-totals.js +21 -0
  41. package/dist/exporters/matrix/result-totals.js.map +1 -0
  42. package/dist/exporters/matrix/testcase-summary-band.d.ts.map +1 -1
  43. package/dist/exporters/matrix/testcase-summary-band.js +2 -6
  44. package/dist/exporters/matrix/testcase-summary-band.js.map +1 -1
  45. package/dist/exporters/qa-env-secret-denylist.js +2 -2
  46. package/dist/exporters/qa-env-secret-denylist.js.map +1 -1
  47. package/dist/exporters/report-path-sanitizer.d.ts +38 -0
  48. package/dist/exporters/report-path-sanitizer.d.ts.map +1 -0
  49. package/dist/exporters/report-path-sanitizer.js +137 -0
  50. package/dist/exporters/report-path-sanitizer.js.map +1 -0
  51. package/dist/exporters/result-variants.d.ts.map +1 -1
  52. package/dist/exporters/result-variants.js +2 -1
  53. package/dist/exporters/result-variants.js.map +1 -1
  54. package/dist/exporters/sensitive-key.d.ts +31 -0
  55. package/dist/exporters/sensitive-key.d.ts.map +1 -0
  56. package/dist/exporters/sensitive-key.js +50 -0
  57. package/dist/exporters/sensitive-key.js.map +1 -0
  58. package/dist/exporters/test-data-resolver.d.ts +4 -1
  59. package/dist/exporters/test-data-resolver.d.ts.map +1 -1
  60. package/dist/exporters/test-data-resolver.js +12 -2
  61. package/dist/exporters/test-data-resolver.js.map +1 -1
  62. package/dist/generators/gherkin-parser/index.d.ts +3 -0
  63. package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
  64. package/dist/generators/gherkin-parser/index.js +17 -9
  65. package/dist/generators/gherkin-parser/index.js.map +1 -1
  66. package/dist/generators/gherkin-parser/parent-scope-clause.d.ts +54 -0
  67. package/dist/generators/gherkin-parser/parent-scope-clause.d.ts.map +1 -0
  68. package/dist/generators/gherkin-parser/parent-scope-clause.js +109 -0
  69. package/dist/generators/gherkin-parser/parent-scope-clause.js.map +1 -0
  70. package/dist/generators/test-generator/adapters/adapter-interface.d.ts +23 -18
  71. package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
  72. package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts +4 -19
  73. package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts.map +1 -1
  74. package/dist/generators/test-generator/adapters/appium/appium-adapter.js.map +1 -1
  75. package/dist/generators/test-generator/adapters/appium/templates/after-all.hbs +18 -0
  76. package/dist/generators/test-generator/adapters/appium/templates/after-each.hbs +18 -0
  77. package/dist/generators/test-generator/adapters/appium/templates/before-all.hbs +18 -0
  78. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/click-at-position-action.hbs +1 -0
  79. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/click-element-with-text.hbs +1 -1
  80. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/double-click-action.hbs +7 -0
  81. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/frame-enter-action.hbs +3 -6
  82. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/table-paginate-action.hbs +3 -0
  83. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-assertion.hbs +1 -1
  84. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/long-press-action.hbs +4 -0
  85. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/pinch-zoom-action.hbs +4 -0
  86. package/dist/generators/test-generator/adapters/appium/templates/steps/partials/locator.hbs +9 -1
  87. package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +54 -23
  88. package/dist/generators/test-generator/adapters/index.d.ts +1 -1
  89. package/dist/generators/test-generator/adapters/index.d.ts.map +1 -1
  90. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +4 -19
  91. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
  92. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
  93. package/dist/generators/test-generator/adapters/playwright/templates/after-all.hbs +19 -1
  94. package/dist/generators/test-generator/adapters/playwright/templates/after-each.hbs +18 -0
  95. package/dist/generators/test-generator/adapters/playwright/templates/before-all.hbs +19 -1
  96. package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  97. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/check-action.hbs +6 -1
  98. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/click-at-position-action.hbs +23 -0
  99. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/expand-action.hbs +8 -8
  100. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/fill-editor-action.hbs +7 -2
  101. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row-nth.hbs +3 -1
  102. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +3 -1
  103. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-paginate-action.hbs +5 -0
  104. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/uncheck-action.hbs +3 -1
  105. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/unknown-element-action.hbs +1 -1
  106. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/wait-for-page.hbs +18 -1
  107. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-assertion.hbs +32 -4
  108. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-element.hbs +5 -2
  109. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/checked-assertion.hbs +5 -1
  110. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/column-cell-assertion.hbs +13 -2
  111. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/contain-text-assertion.hbs +1 -1
  112. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +2 -2
  113. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-variable-assertion.hbs +1 -1
  114. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/have-text-assertion.hbs +1 -1
  115. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +2 -2
  116. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-variable-assertion.hbs +1 -1
  117. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/not-checked-assertion.hbs +2 -1
  118. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/selected-assertion.hbs +14 -3
  119. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/state-with-filter-assertion.hbs +13 -2
  120. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +3 -3
  121. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-value-assertion.hbs +1 -1
  122. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-variable-assertion.hbs +1 -1
  123. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/checked-opts.hbs +8 -0
  124. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-nth.hbs +1 -1
  125. package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +10 -8
  126. package/dist/generators/test-generator/code-generator.d.ts +37 -0
  127. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  128. package/dist/generators/test-generator/code-generator.js +160 -39
  129. package/dist/generators/test-generator/code-generator.js.map +1 -1
  130. package/dist/generators/test-generator/diagnostics.d.ts +79 -5
  131. package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
  132. package/dist/generators/test-generator/diagnostics.js +78 -4
  133. package/dist/generators/test-generator/diagnostics.js.map +1 -1
  134. package/dist/generators/test-generator/patterns/index.d.ts +24 -5
  135. package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
  136. package/dist/generators/test-generator/patterns/index.js +92 -14
  137. package/dist/generators/test-generator/patterns/index.js.map +1 -1
  138. package/dist/generators/test-generator/patterns/with-parent-scope.d.ts +18 -0
  139. package/dist/generators/test-generator/patterns/with-parent-scope.d.ts.map +1 -0
  140. package/dist/generators/test-generator/patterns/with-parent-scope.js +40 -0
  141. package/dist/generators/test-generator/patterns/with-parent-scope.js.map +1 -0
  142. package/dist/generators/test-generator/step-mapper.d.ts +63 -9
  143. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  144. package/dist/generators/test-generator/step-mapper.js +166 -28
  145. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  146. package/dist/generators/test-generator/template-engine.d.ts +4 -19
  147. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  148. package/dist/generators/test-generator/template-engine.js +17 -12
  149. package/dist/generators/test-generator/template-engine.js.map +1 -1
  150. package/dist/generators/test-generator/utils/data-resolver.d.ts +7 -0
  151. package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
  152. package/dist/generators/test-generator/utils/data-resolver.js +19 -2
  153. package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
  154. package/dist/generators/test-generator/utils/dialog-scope-identity.d.ts +41 -0
  155. package/dist/generators/test-generator/utils/dialog-scope-identity.d.ts.map +1 -0
  156. package/dist/generators/test-generator/utils/dialog-scope-identity.js +61 -0
  157. package/dist/generators/test-generator/utils/dialog-scope-identity.js.map +1 -0
  158. package/dist/generators/test-generator/utils/escape-js-string.d.ts +17 -0
  159. package/dist/generators/test-generator/utils/escape-js-string.d.ts.map +1 -0
  160. package/dist/generators/test-generator/utils/escape-js-string.js +29 -0
  161. package/dist/generators/test-generator/utils/escape-js-string.js.map +1 -0
  162. package/dist/generators/test-generator/utils/nearest-match.d.ts +24 -0
  163. package/dist/generators/test-generator/utils/nearest-match.d.ts.map +1 -0
  164. package/dist/generators/test-generator/utils/nearest-match.js +56 -0
  165. package/dist/generators/test-generator/utils/nearest-match.js.map +1 -0
  166. package/dist/generators/test-generator/utils/selector-entry-fields.d.ts +58 -0
  167. package/dist/generators/test-generator/utils/selector-entry-fields.d.ts.map +1 -0
  168. package/dist/generators/test-generator/utils/selector-entry-fields.js +119 -0
  169. package/dist/generators/test-generator/utils/selector-entry-fields.js.map +1 -0
  170. package/dist/generators/test-generator/utils/selector-entry-walk.d.ts +25 -0
  171. package/dist/generators/test-generator/utils/selector-entry-walk.d.ts.map +1 -0
  172. package/dist/generators/test-generator/utils/selector-entry-walk.js +54 -0
  173. package/dist/generators/test-generator/utils/selector-entry-walk.js.map +1 -0
  174. package/dist/generators/test-generator/utils/selector-nth-validation.d.ts +59 -0
  175. package/dist/generators/test-generator/utils/selector-nth-validation.d.ts.map +1 -0
  176. package/dist/generators/test-generator/utils/selector-nth-validation.js +91 -0
  177. package/dist/generators/test-generator/utils/selector-nth-validation.js.map +1 -0
  178. package/dist/generators/test-generator/utils/selector-resolver.d.ts +30 -1
  179. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
  180. package/dist/generators/test-generator/utils/selector-resolver.js +195 -15
  181. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
  182. package/dist/generators/test-generator/utils/selector-type-validation.d.ts +51 -0
  183. package/dist/generators/test-generator/utils/selector-type-validation.d.ts.map +1 -0
  184. package/dist/generators/test-generator/utils/selector-type-validation.js +77 -0
  185. package/dist/generators/test-generator/utils/selector-type-validation.js.map +1 -0
  186. package/dist/harness/data-driven-lint.d.ts.map +1 -1
  187. package/dist/harness/data-driven-lint.js +217 -33
  188. package/dist/harness/data-driven-lint.js.map +1 -1
  189. package/dist/harness/script-check.d.ts +2 -2
  190. package/dist/harness/script-check.d.ts.map +1 -1
  191. package/dist/harness/script-check.js +186 -25
  192. package/dist/harness/script-check.js.map +1 -1
  193. package/dist/index.d.ts +2 -0
  194. package/dist/index.d.ts.map +1 -1
  195. package/dist/index.js +10 -1
  196. package/dist/index.js.map +1 -1
  197. package/dist/orchestrator/project-initializer.d.ts +6 -0
  198. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  199. package/dist/orchestrator/project-initializer.js +16 -0
  200. package/dist/orchestrator/project-initializer.js.map +1 -1
  201. package/dist/orchestrator/report-sanitize-reporter-install.d.ts +24 -0
  202. package/dist/orchestrator/report-sanitize-reporter-install.d.ts.map +1 -0
  203. package/dist/orchestrator/report-sanitize-reporter-install.js +161 -0
  204. package/dist/orchestrator/report-sanitize-reporter-install.js.map +1 -0
  205. package/dist/orchestrator/templates/ai-src/commands/run-test.md +9 -1
  206. package/dist/orchestrator/templates/ai-src/skills/sungen-api-coverage-model/SKILL.md +10 -0
  207. package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +14 -2
  208. package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +108 -8
  209. package/dist/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +1 -0
  210. package/dist/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +9 -2
  211. package/dist/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +4 -4
  212. package/dist/orchestrator/templates/playwright.config.ts +7 -0
  213. package/dist/orchestrator/templates/specs-checked-state.d.ts +83 -0
  214. package/dist/orchestrator/templates/specs-checked-state.d.ts.map +1 -0
  215. package/dist/orchestrator/templates/specs-checked-state.js +261 -0
  216. package/dist/orchestrator/templates/specs-checked-state.js.map +1 -0
  217. package/dist/orchestrator/templates/specs-checked-state.ts +324 -0
  218. package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.d.ts +36 -0
  219. package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.d.ts.map +1 -0
  220. package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.js +155 -0
  221. package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.js.map +1 -0
  222. package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.ts +142 -0
  223. package/dist/orchestrator/templates/specs-url-assert.d.ts +50 -25
  224. package/dist/orchestrator/templates/specs-url-assert.d.ts.map +1 -1
  225. package/dist/orchestrator/templates/specs-url-assert.js +65 -24
  226. package/dist/orchestrator/templates/specs-url-assert.js.map +1 -1
  227. package/dist/orchestrator/templates/specs-url-assert.ts +101 -28
  228. package/dist/utils/selector-types.d.ts +43 -1
  229. package/dist/utils/selector-types.d.ts.map +1 -1
  230. package/dist/utils/selector-types.js +51 -7
  231. package/dist/utils/selector-types.js.map +1 -1
  232. package/package.json +3 -3
  233. package/src/cli/commands/delivery.ts +2 -1
  234. package/src/cli/commands/generate.ts +5 -2
  235. package/src/cli/commands/inspect.ts +1 -1
  236. package/src/cli/commands/results-merge.ts +9 -2
  237. package/src/cli/commands/results-sanitize.ts +98 -0
  238. package/src/cli/commands/script-check.ts +3 -1
  239. package/src/cli/commands/update.ts +3 -0
  240. package/src/exporters/api-testcase-formatter.ts +5 -7
  241. package/src/exporters/matrix/build.ts +4 -2
  242. package/src/exporters/matrix/render-coverage-sheet.ts +3 -1
  243. package/src/exporters/matrix/render-csv.ts +3 -1
  244. package/src/exporters/matrix/render-overview-sheet.ts +5 -10
  245. package/src/exporters/matrix/result-totals.ts +37 -0
  246. package/src/exporters/matrix/testcase-summary-band.ts +2 -7
  247. package/src/exporters/qa-env-secret-denylist.ts +1 -1
  248. package/src/exporters/report-path-sanitizer.ts +144 -0
  249. package/src/exporters/result-variants.ts +2 -1
  250. package/src/exporters/sensitive-key.ts +48 -0
  251. package/src/exporters/test-data-resolver.ts +12 -2
  252. package/src/generators/gherkin-parser/index.ts +21 -10
  253. package/src/generators/gherkin-parser/parent-scope-clause.ts +130 -0
  254. package/src/generators/test-generator/adapters/adapter-interface.ts +25 -3
  255. package/src/generators/test-generator/adapters/appium/appium-adapter.ts +4 -4
  256. package/src/generators/test-generator/adapters/appium/templates/after-all.hbs +18 -0
  257. package/src/generators/test-generator/adapters/appium/templates/after-each.hbs +18 -0
  258. package/src/generators/test-generator/adapters/appium/templates/before-all.hbs +18 -0
  259. package/src/generators/test-generator/adapters/appium/templates/steps/actions/click-at-position-action.hbs +1 -0
  260. package/src/generators/test-generator/adapters/appium/templates/steps/actions/click-element-with-text.hbs +1 -1
  261. package/src/generators/test-generator/adapters/appium/templates/steps/actions/double-click-action.hbs +7 -0
  262. package/src/generators/test-generator/adapters/appium/templates/steps/actions/frame-enter-action.hbs +3 -6
  263. package/src/generators/test-generator/adapters/appium/templates/steps/actions/table-paginate-action.hbs +3 -0
  264. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-assertion.hbs +1 -1
  265. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/long-press-action.hbs +4 -0
  266. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/pinch-zoom-action.hbs +4 -0
  267. package/src/generators/test-generator/adapters/appium/templates/steps/partials/locator.hbs +9 -1
  268. package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +54 -23
  269. package/src/generators/test-generator/adapters/index.ts +1 -1
  270. package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +4 -4
  271. package/src/generators/test-generator/adapters/playwright/templates/after-all.hbs +19 -1
  272. package/src/generators/test-generator/adapters/playwright/templates/after-each.hbs +18 -0
  273. package/src/generators/test-generator/adapters/playwright/templates/before-all.hbs +19 -1
  274. package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  275. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/check-action.hbs +6 -1
  276. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/click-at-position-action.hbs +23 -0
  277. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/expand-action.hbs +8 -8
  278. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/fill-editor-action.hbs +7 -2
  279. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row-nth.hbs +3 -1
  280. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +3 -1
  281. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-paginate-action.hbs +5 -0
  282. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/uncheck-action.hbs +3 -1
  283. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/unknown-element-action.hbs +1 -1
  284. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/wait-for-page.hbs +18 -1
  285. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-assertion.hbs +32 -4
  286. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-element.hbs +5 -2
  287. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/checked-assertion.hbs +5 -1
  288. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/column-cell-assertion.hbs +13 -2
  289. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/contain-text-assertion.hbs +1 -1
  290. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +2 -2
  291. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-variable-assertion.hbs +1 -1
  292. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/have-text-assertion.hbs +1 -1
  293. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +2 -2
  294. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-variable-assertion.hbs +1 -1
  295. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/not-checked-assertion.hbs +2 -1
  296. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/selected-assertion.hbs +14 -3
  297. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/state-with-filter-assertion.hbs +13 -2
  298. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +3 -3
  299. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-value-assertion.hbs +1 -1
  300. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-variable-assertion.hbs +1 -1
  301. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/checked-opts.hbs +8 -0
  302. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-nth.hbs +1 -1
  303. package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +10 -8
  304. package/src/generators/test-generator/code-generator.ts +176 -45
  305. package/src/generators/test-generator/diagnostics.ts +79 -5
  306. package/src/generators/test-generator/patterns/index.ts +97 -16
  307. package/src/generators/test-generator/patterns/with-parent-scope.ts +41 -0
  308. package/src/generators/test-generator/step-mapper.ts +170 -26
  309. package/src/generators/test-generator/template-engine.ts +21 -21
  310. package/src/generators/test-generator/utils/data-resolver.ts +23 -2
  311. package/src/generators/test-generator/utils/dialog-scope-identity.ts +71 -0
  312. package/src/generators/test-generator/utils/escape-js-string.ts +24 -0
  313. package/src/generators/test-generator/utils/nearest-match.ts +55 -0
  314. package/src/generators/test-generator/utils/selector-entry-fields.ts +131 -0
  315. package/src/generators/test-generator/utils/selector-entry-walk.ts +58 -0
  316. package/src/generators/test-generator/utils/selector-nth-validation.ts +99 -0
  317. package/src/generators/test-generator/utils/selector-resolver.ts +245 -16
  318. package/src/generators/test-generator/utils/selector-type-validation.ts +87 -0
  319. package/src/harness/data-driven-lint.ts +192 -32
  320. package/src/harness/script-check.ts +186 -21
  321. package/src/index.ts +7 -0
  322. package/src/orchestrator/project-initializer.ts +17 -0
  323. package/src/orchestrator/report-sanitize-reporter-install.ts +109 -0
  324. package/src/orchestrator/templates/ai-src/commands/run-test.md +9 -1
  325. package/src/orchestrator/templates/ai-src/skills/sungen-api-coverage-model/SKILL.md +10 -0
  326. package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +14 -2
  327. package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +108 -8
  328. package/src/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +1 -0
  329. package/src/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +9 -2
  330. package/src/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +4 -4
  331. package/src/orchestrator/templates/playwright.config.ts +7 -0
  332. package/src/orchestrator/templates/specs-checked-state.ts +324 -0
  333. package/src/orchestrator/templates/specs-report-path-sanitize-reporter.ts +142 -0
  334. package/src/orchestrator/templates/specs-url-assert.ts +101 -28
  335. package/src/utils/selector-types.ts +54 -5
@@ -8,6 +8,9 @@ import { SelectorType } from '../../../utils/selector-types';
8
8
  import { DataResolver } from './data-resolver';
9
9
  import { DiagnosticsCollector } from '../diagnostics';
10
10
  import { DEFAULT_CONTENT_NAMED_ROLES } from '../app-profile';
11
+ import { reportUnknownEntryFields } from './selector-entry-fields';
12
+ import { reportUnknownSelectorTypes } from './selector-type-validation';
13
+ import { normalizeNth, reportInvalidNth } from './selector-nth-validation';
11
14
 
12
15
  // Structured selector format v2
13
16
  interface SelectorEntry {
@@ -15,7 +18,8 @@ interface SelectorEntry {
15
18
  type?: SelectorType; // Locator strategy
16
19
  value?: string; // Natural language value for the locator (e.g., 'button', 'link' for role)
17
20
  name?: string; // Accessible name for role-based selectors (e.g., 'Login', 'Submit')
18
- nth?: number; // Element index (undefined = no .nth(), 0+ = .nth(N))
21
+ nth?: number; // Element index: undefined = no positional filter, 0+ = that index,
22
+ // -1 = the last match. Anything else is dropped + SG-W018 (#720)
19
23
  exact?: boolean; // Whether to use exact matching (default: false)
20
24
  inputMethod?: string; // 'pressSequentially' for contenteditable/rich-text elements
21
25
 
@@ -53,6 +57,22 @@ interface SelectorEntry {
53
57
  // entry, which positions ONE row (`tbody tr:first-child`) — a shape is a property of the table.
54
58
  rows?: string;
55
59
 
60
+ // === Checked/selected state signal (issue #730) ===
61
+ // How a control publishes its on/off state when it publishes NO ARIA. A Tailwind checkbox or
62
+ // card picker — a <div> with a class swap and no <input>, no role, no aria-checked/aria-pressed —
63
+ // has no state for `check`/`uncheck` to drive or for `is checked`/`is selected` to read, so every
64
+ // such step failed with "it has no on/off state to set" no matter how right the selector was.
65
+ // `checked: { class: 'is-checked' }` names the class that marks the ON state.
66
+ //
67
+ // `selected:` is an ALIAS for the same signal, not a second one: a card picker declaring
68
+ // `checked:` reads wrong, and `is selected` on a non-ARIA element already falls through to the
69
+ // checked family (selected-assertion.hbs). Declaring both is a conflict — see resolveFromEntry.
70
+ // `checked.attribute` is the same signal published as a boolean attribute ('true'/'false')
71
+ // instead of a class, for a control that swaps `data-checked` rather than a class token.
72
+ // Declare ONE of the two per entry: class and attribute are two ways to read one state.
73
+ checked?: { class?: string; attribute?: string };
74
+ selected?: { class?: string; attribute?: string };
75
+
56
76
  // === v2: Assertion Helpers ===
57
77
  attribute?: string; // Attribute to check (for "has" assertions)
58
78
  pattern?: string; // Regex for attribute value
@@ -123,7 +143,7 @@ export interface ResolvedSelector {
123
143
  role?: string; // For role-based selectors (e.g., 'button', 'link')
124
144
  name?: string; // For accessible name in role selectors
125
145
  locator?: string; // CSS locator for fallback
126
- nth?: number; // Index for .nth() if multiple matches exist
146
+ nth?: number; // Index for .nth() if multiple matches exist (0+, or -1 = last match)
127
147
  exact?: boolean; // Whether to use exact matching (default: false)
128
148
  inputMethod?: string; // 'pressSequentially' for contenteditable/rich-text elements
129
149
 
@@ -140,6 +160,11 @@ export interface ResolvedSelector {
140
160
  attribute?: string; // Attribute to check
141
161
  pattern?: string; // Regex for attribute value
142
162
  expanded?: { class?: string; attribute?: string; state?: 'none' }; // Expand/collapse state signal
163
+ // Class / boolean attribute marking the ON state, normalised from `checked:`/`selected:`
164
+ // (issue #730). Flat rather than an object because ONE resolved signal is what every consumer
165
+ // needs — the two YAML spellings are an authoring convenience, not two different things.
166
+ checkedClass?: string;
167
+ checkedAttribute?: string;
143
168
  anchor?: ResolvedSelector; // page entries only — the rendered-content proof behind `see [X] page` (#662)
144
169
  }
145
170
 
@@ -188,7 +213,7 @@ export class SelectorResolver {
188
213
  return this.platform === 'mobile' && !!r && r.strategy === 'role' && !r.name;
189
214
  }
190
215
 
191
- /** Attach the compile-diagnostics collector (SG-W003) */
216
+ /** Attach the compile-diagnostics collector (SG-W003, SG-W016, SG-W030) */
192
217
  setDiagnostics(collector: DiagnosticsCollector): void {
193
218
  this.diagnostics = collector;
194
219
  }
@@ -595,9 +620,137 @@ export class SelectorResolver {
595
620
  });
596
621
  }
597
622
 
623
+ /** Encode `{{var}}` references inside a table's `columns: { <col>: { header } }` map. */
624
+ private static resolveColumnHeaders(
625
+ columns: NonNullable<SelectorEntry['columns']>,
626
+ ): NonNullable<SelectorEntry['columns']> {
627
+ const out: NonNullable<SelectorEntry['columns']> = {};
628
+ for (const [key, col] of Object.entries(columns)) {
629
+ out[key] = col && typeof col.header === 'string'
630
+ ? { ...col, header: SelectorResolver.resolveTemplateVars(col.header) }
631
+ : col;
632
+ }
633
+ return out;
634
+ }
635
+
636
+ /**
637
+ * Normalise `checked:` / `selected:` into the one signal consumers read (issue #730). The two
638
+ * spellings are an authoring convenience for the same thing — `checked:` on a toggle, `selected:`
639
+ * on a card/tab picker — so they collapse here rather than at every call site. Each spelling can
640
+ * publish the state as a class token or as a boolean attribute; one entry declares one of them.
641
+ *
642
+ * Validated at CODEGEN because every failure mode here is silent at run time: the class token
643
+ * lands inside `[class~="..."]`, where a quote breaks the selector and whitespace can never match
644
+ * (the `~=` operator matches ONE whitespace-separated token, so 'a b' matches nothing at all and
645
+ * the step would just time out claiming the control never changed state); an attribute name has
646
+ * the same exposure inside `[name="true"]`. Refused rather than warned-and-dropped, unlike
647
+ * `expanded:`: dropping this signal leaves the step with nothing to read, so it would fail again
648
+ * with the original "no on/off state" error and hide the real cause.
649
+ */
650
+ private resolveCheckedSignal(
651
+ entry: SelectorEntry,
652
+ label: string,
653
+ ): { class?: string; attribute?: string } | undefined {
654
+ const signal: { class?: string; attribute?: string } = {};
655
+ // Recorded BEFORE it is thrown, every time. A pattern that resolves its selector inside a
656
+ // `catch {}` — several do, to fall back when a `[Ref]` simply is not in the file — would
657
+ // otherwise swallow the refusal whole and emit a step against an unresolved locator, with
658
+ // nothing anywhere naming the entry. The throw is what stops the file; the diagnostic is what
659
+ // survives a caller that decided not to be stopped, and what `--strict` counts.
660
+ const refuse = (message: string): never => {
661
+ this.diagnostics?.add('SG-E031', message, { feature: this.featureName });
662
+ throw new Error(message);
663
+ };
664
+
665
+ for (const key of ['class', 'attribute'] as const) {
666
+ const fromChecked = entry.checked?.[key]?.trim();
667
+ const fromSelected = entry.selected?.[key]?.trim();
668
+
669
+ if (fromChecked && fromSelected && fromChecked !== fromSelected) {
670
+ refuse(
671
+ `Selector "[${label}]" declares both \`checked.${key}\` ("${fromChecked}") and ` +
672
+ `\`selected.${key}\` ("${fromSelected}") with different values. They are two spellings of ONE ` +
673
+ `signal — how the control marks its ON state — so only one can be right. Keep \`checked:\` for ` +
674
+ `a toggle/checkbox or \`selected:\` for a card/tab picker, and delete the other.`
675
+ );
676
+ }
677
+
678
+ const value = fromChecked || fromSelected;
679
+ if (!value) continue;
680
+
681
+ if (/\s/.test(value)) {
682
+ refuse(
683
+ `Selector "[${label}]" declares checked/selected ${key} "${value}", which contains whitespace. ` +
684
+ `This must be ONE ${key === 'class' ? 'class token, not a class list' : 'attribute name'}: the ` +
685
+ `generated matcher tests a single token, so a multi-token value can never match and the step ` +
686
+ `would time out rather than fail clearly. Pick the one ${key} that marks the ON state ` +
687
+ `(e.g. '${key === 'class' ? 'border-link' : 'data-checked'}', not ` +
688
+ `'${key === 'class' ? 'card border-link' : 'data-checked aria-checked'}').`
689
+ );
690
+ }
691
+ if (value.includes('"') || value.includes('\\')) {
692
+ refuse(
693
+ `Selector "[${label}]" declares checked/selected ${key} "${value}", which contains a quote or ` +
694
+ `backslash. The value is embedded in a [${key === 'class' ? 'class~="..."' : 'name="true"'}] CSS ` +
695
+ `selector, which those characters would break. A ${key} carrying them is almost certainly a typo.`
696
+ );
697
+ }
698
+ signal[key] = value;
699
+ }
700
+
701
+ if (signal.class && signal.attribute) {
702
+ refuse(
703
+ `Selector "[${label}]" declares both a checked/selected \`class\` ("${signal.class}") and ` +
704
+ `\`attribute\` ("${signal.attribute}"). One control publishes its ON state one way — declaring ` +
705
+ `two lets an action drive one signal while the assertion after it reads the other. Keep the one ` +
706
+ `the component actually swaps.`
707
+ );
708
+ }
709
+
710
+ return signal.class || signal.attribute ? signal : undefined;
711
+ }
712
+
598
713
  /**
599
714
  * Resolve from new structured entry format
600
715
  */
716
+ /**
717
+ * An `expanded:` signal's `class` / `attribute` value is pasted into the generated spec — into a
718
+ * string literal, a regex, a `classList.contains()` argument. selectors.yaml is often generated
719
+ * from the page under test, so a value carrying a quote, a `${`, whitespace or any other character
720
+ * a class or attribute name cannot legitimately contain is refused (diagnostic + the field is
721
+ * dropped) rather than compiled into code. Dropping it leaves a working step — codegen falls back
722
+ * to driving/asserting `aria-expanded` — which is why this warns where the checked/selected signal,
723
+ * whose fallback is the failure it exists to fix, refuses outright.
724
+ */
725
+ private validatedStateSignal(
726
+ signal: { class?: string; attribute?: string },
727
+ field: string,
728
+ label: string,
729
+ ): { class?: string; attribute?: string } | undefined {
730
+ // Letters, digits and the punctuation real class/attribute names use — including Tailwind
731
+ // arbitrary values (`bg-[#eff6ff]`, `!mt-0`, `@md:flex`, `w-[calc(100%-2rem)]`). Excluded on
732
+ // purpose: whitespace, quotes, backticks, backslash, `$`/`{`/`}` — the characters that could
733
+ // close a literal or open an interpolation in the generated code.
734
+ const SAFE = /^[A-Za-z0-9_\-:./[\]%#!@(),&+=*~|?]+$/;
735
+ const out: { class?: string; attribute?: string } = {};
736
+ for (const key of ['class', 'attribute'] as const) {
737
+ const value = signal[key];
738
+ if (value === undefined || value === null) continue;
739
+ const text = String(value);
740
+ if (SAFE.test(text)) { out[key] = text; continue; }
741
+ const warnKey = `w030:${label}:${field}.${key}`;
742
+ if (!this.warnedContentNamed.has(warnKey)) { // once per entry+field, not once per step that uses it
743
+ this.warnedContentNamed.add(warnKey);
744
+ this.diagnostics?.add(
745
+ 'SG-W030',
746
+ `selector "${label}": ${field}.${key} ${JSON.stringify(text)} is not accepted as a ${key} name (no whitespace, quotes, backslash, \`$\`, \`{\` or \`}\`) — the value is ignored and the step compiles to its default form`,
747
+ { feature: this.featureName },
748
+ );
749
+ }
750
+ }
751
+ return out.class || out.attribute ? out : undefined;
752
+ }
753
+
601
754
  private resolveFromEntry(entry: SelectorEntry, originalLabel: string): ResolvedSelector {
602
755
  // Default values
603
756
  const locator = entry.locator || '';
@@ -619,9 +772,22 @@ export class SelectorResolver {
619
772
  : (entry.name ?? '');
620
773
  const value = SelectorResolver.resolveTemplateVars(rawValue);
621
774
  const name = SelectorResolver.resolveTemplateVars(rawName);
622
- const nth = entry.nth !== undefined && entry.nth !== null ? entry.nth : undefined;
775
+ // `nth:` is the one field whose VALUE nothing used to check (#720): anything non-null was
776
+ // handed to the adapters, which disagreed about it — web dropped `-1` silently, mobile emitted
777
+ // `$$(...)[-1]`. Normalised to a real index (integer >= -1) or `undefined` HERE, so no template
778
+ // ever renders a value it cannot express; the out-of-contract write is reported as SG-W018 at
779
+ // the file load point below.
780
+ const nth = normalizeNth(entry.nth);
623
781
  const exact = entry.exact === true || entry.match === 'exact';
624
- const inputMethod = entry.inputMethod;
782
+ // `contenteditable: true` is the MANUAL override for a rich-text editor (#712). It was a
783
+ // documented, plumbed field that nothing read: the rich-text decision lives in driver-ui's
784
+ // fill pattern, which gates on `inputMethod` to pick `fill-editor-action`, so an author who
785
+ // followed the selector reference and wrote `contenteditable: true` got a plain `.fill()` and
786
+ // no diagnostic. Forcing the input method HERE rather than adding a second condition in the
787
+ // pattern keeps ONE rich-text signal on ResolvedSelector, so every consumer — current and
788
+ // future — reads the intent off a single field. An explicit `inputMethod` is more specific and
789
+ // still wins, so the two may be mixed without surprise.
790
+ const inputMethod = entry.inputMethod ?? (entry.contenteditable ? 'pressSequentially' : undefined);
625
791
 
626
792
  // v2 fields
627
793
  const v2Fields: Partial<ResolvedSelector> = {};
@@ -631,12 +797,21 @@ export class SelectorResolver {
631
797
  if (entry.trigger) v2Fields.trigger = entry.trigger;
632
798
  if (entry.frame) v2Fields.frame = entry.frame;
633
799
  if (entry.contenteditable) v2Fields.contenteditable = true;
634
- if (entry.columns) v2Fields.columns = entry.columns;
800
+ // A column's `header` is DOM text like any other selector field, so a `{{var}}` in it is a
801
+ // runtime reference — the same i18n escape hatch `name:`/`value:` have. Encoded here rather
802
+ // than at the read site so every consumer of the map gets it (issue #674).
803
+ if (entry.columns) v2Fields.columns = SelectorResolver.resolveColumnHeaders(entry.columns);
635
804
  if (entry.pagination) v2Fields.pagination = entry.pagination;
636
805
  if (typeof entry.rows === 'string' && entry.rows.trim()) v2Fields.rows = entry.rows.trim();
637
806
  if (entry.attribute) v2Fields.attribute = entry.attribute;
638
807
  if (entry.pattern) v2Fields.pattern = entry.pattern;
639
- if (entry.expanded) v2Fields.expanded = entry.expanded;
808
+ if (entry.expanded) {
809
+ const expanded = this.validatedStateSignal(entry.expanded, 'expanded', originalLabel);
810
+ if (expanded || entry.expanded.state) v2Fields.expanded = { ...expanded, ...(entry.expanded.state ? { state: entry.expanded.state } : {}) };
811
+ }
812
+ const checked = this.resolveCheckedSignal(entry, originalLabel);
813
+ if (checked?.class) v2Fields.checkedClass = checked.class;
814
+ if (checked?.attribute) v2Fields.checkedAttribute = checked.attribute;
640
815
  // The anchor is a selector in its own right; it must not inherit the PAGE's label as its
641
816
  // accessible name, so it resolves against its own name (or nothing).
642
817
  if (entry.anchor) v2Fields.anchor = this.resolveFromEntry(entry.anchor, entry.anchor.name ?? '');
@@ -672,7 +847,26 @@ export class SelectorResolver {
672
847
  // If type is 'locator', use locator field (or value as fallback) as the CSS locator directly.
673
848
  // `exact` travels along: a locator LIST filtered by a test-data value (hasText) matches by
674
849
  // substring by default, and `exact: true` in the YAML opts into the anchored full-text match.
675
- if (type === 'locator') {
850
+ //
851
+ // `upload` rides the SAME branch rather than getting a case of its own. A file input is
852
+ // addressed by CSS in practice (`input[type=file]`, an id, a testid wrapper), and the selector
853
+ // reference has always documented `upload` as `page.locator(value).setInputFiles()` — so the
854
+ // two spellings must resolve identically or teams migrating off the workaround get a silent
855
+ // behaviour change. It had no case at all before, so it fell through to `default:` and
856
+ // compiled to `getByPlaceholder('<the CSS string>')` — a locator hunting an input whose
857
+ // placeholder ATTRIBUTE is the literal text `input[type=file]`, which no page has. It failed
858
+ // at run time with no diagnostic, and the only way out was to work out for oneself that
859
+ // `type: locator` had to be used instead, which no page of the reference says (issue #690).
860
+ //
861
+ // Handled HERE, above the generic `locator:`-field branch below, because that branch returns
862
+ // strategy 'css' and drops `exact` — putting `upload` in the switch left the two spellings
863
+ // diverging for any entry that sets `locator:`. `withExtras` still stamps the declared
864
+ // `selectorType`, so a consumer that needs to tell an upload from a plain locator still can.
865
+ //
866
+ // `exact` is inert for the upload action itself (`setInputFiles` takes no text filter), but it
867
+ // is NOT dead: the same key reused by a filtered step (`… with {{v}}`) reaches has-text-value,
868
+ // which reads `exact` regardless of strategy.
869
+ if (type === 'locator' || type === 'upload') {
676
870
  const locatorValue = locator && locator.trim() ? locator : value;
677
871
  return withExtras({
678
872
  strategy: 'locator',
@@ -798,7 +992,14 @@ export class SelectorResolver {
798
992
  });
799
993
 
800
994
  default:
801
- // Fallback to placeholder
995
+ // Reached by the NON_STRATEGY_SELECTOR_TYPES (`page`/`table`/`column`/`frame`, whose
996
+ // consumers read the entry or `selectorType` rather than `strategy`) and by a type this
997
+ // compiler does not know at all — YAML is untyped at run time, so a misspelling lands
998
+ // here too. The placeholder result is inert for the first group and wrong for the second,
999
+ // but it MUST NOT throw: `page` alone reaches this branch on every page assertion.
1000
+ // What must never arrive here is a STRATEGY type — that is the #690 defect, and
1001
+ // tests/codegen/selector-type-dispatch.run.ts fails the build if one does (it checks the
1002
+ // resolved strategy, so an early return above satisfies it as well as a `case` here).
802
1003
  return withExtras({
803
1004
  strategy: 'placeholder',
804
1005
  value,
@@ -813,13 +1014,18 @@ export class SelectorResolver {
813
1014
  * Returns metadata only - no framework-specific code
814
1015
  */
815
1016
  private resolveByPriority(element: LegacySelectorElement): ResolvedSelector {
1017
+ // The legacy format never passes through the SG-W018 file check (that walks new-format
1018
+ // entries), so it is normalised here instead — a template must never see an index it cannot
1019
+ // express, whichever door the entry came through.
1020
+ const nth = normalizeNth(element.nth);
1021
+
816
1022
  // Extract data-testid from locator if present
817
1023
  const testIdMatch = element.locator.match(/\[data-testid=["']([^"']+)["']\]/);
818
1024
  if (testIdMatch) {
819
1025
  return {
820
1026
  strategy: 'testid',
821
1027
  value: testIdMatch[1],
822
- nth: element.nth,
1028
+ nth,
823
1029
  };
824
1030
  }
825
1031
 
@@ -830,7 +1036,7 @@ export class SelectorResolver {
830
1036
  role: 'button',
831
1037
  name: element.value,
832
1038
  value: element.value,
833
- nth: element.nth,
1039
+ nth,
834
1040
  };
835
1041
  }
836
1042
 
@@ -838,7 +1044,7 @@ export class SelectorResolver {
838
1044
  return {
839
1045
  strategy: 'placeholder',
840
1046
  value: element.value,
841
- nth: element.nth,
1047
+ nth,
842
1048
  };
843
1049
  }
844
1050
 
@@ -846,7 +1052,7 @@ export class SelectorResolver {
846
1052
  return {
847
1053
  strategy: 'label',
848
1054
  value: element.value,
849
- nth: element.nth,
1055
+ nth,
850
1056
  };
851
1057
  }
852
1058
 
@@ -854,7 +1060,7 @@ export class SelectorResolver {
854
1060
  return {
855
1061
  strategy: 'text',
856
1062
  value: element.value,
857
- nth: element.nth,
1063
+ nth,
858
1064
  };
859
1065
  }
860
1066
 
@@ -865,7 +1071,7 @@ export class SelectorResolver {
865
1071
  strategy: 'id',
866
1072
  value: idMatch[1],
867
1073
  locator: `#${idMatch[1]}`,
868
- nth: element.nth,
1074
+ nth,
869
1075
  };
870
1076
  }
871
1077
 
@@ -874,7 +1080,7 @@ export class SelectorResolver {
874
1080
  strategy: 'css',
875
1081
  value: element.locator,
876
1082
  locator: element.locator,
877
- nth: element.nth,
1083
+ nth,
878
1084
  };
879
1085
  }
880
1086
 
@@ -924,6 +1130,29 @@ export class SelectorResolver {
924
1130
  }
925
1131
  }
926
1132
 
1133
+ // SG-W004 — a field nothing reads (#712). Reported HERE, at the one cached load point, so a
1134
+ // table named by twenty row steps warns once rather than twenty times.
1135
+ reportUnknownEntryFields(selectors as Record<string, unknown>, {
1136
+ feature: featureName,
1137
+ diagnostics: this.diagnostics,
1138
+ });
1139
+
1140
+ // SG-E027 — a `type:` nothing dispatches (#711). Same load point and same once-per-file
1141
+ // reason as SG-W004 above: the two checks are the same question one level apart (an unknown
1142
+ // KEY vs an unknown VALUE for a known key), and both want the file in hand to name the entry.
1143
+ reportUnknownSelectorTypes(selectors as Record<string, unknown>, {
1144
+ feature: featureName,
1145
+ diagnostics: this.diagnostics,
1146
+ });
1147
+
1148
+ // SG-W018 — an `nth:` value outside the contract (#720). Third of the same family at the same
1149
+ // load point: SG-W004 asks whether anything READS the key, SG-E027 whether the `type:` VALUE
1150
+ // dispatches, this one whether the `nth:` VALUE is an index the adapters can express.
1151
+ reportInvalidNth(selectors as Record<string, unknown>, {
1152
+ feature: featureName,
1153
+ diagnostics: this.diagnostics,
1154
+ });
1155
+
927
1156
  this.selectorCache.set(cacheKey, selectors);
928
1157
  return selectors;
929
1158
  }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * SG-E027 — an entry `type:` the compiler does not know (issue #711).
3
+ *
4
+ * `selectors.yaml` is YAML, so `type:` is an arbitrary string at run time. `VALID_SELECTOR_TYPES`
5
+ * constrained it only through the `SelectorType` TypeScript union, and nothing imported that list
6
+ * for a runtime check — so any value the resolver did not recognise fell to the strategy switch's
7
+ * `default:` branch, which returns `{ strategy: 'placeholder' }`, and the entry's `value:` was
8
+ * emitted as literal placeholder text:
9
+ *
10
+ * save:
11
+ * type: 'testId' # note the capital I
12
+ * value: 'btn-save'
13
+ * → await page.getByPlaceholder('btn-save').click();
14
+ *
15
+ * Same family as #690 (a valid type with no dispatch) and #712 (a documented field with no
16
+ * consumer): the DECLARED surface was wider than the IMPLEMENTED one and nothing compared the two.
17
+ * Here the gap was the widest of the three, because an unknown type is not a fixed list of
18
+ * oversights — it is every possible misspelling.
19
+ *
20
+ * Checked HERE, at the selector-file load, rather than in the strategy switch. The switch CANNOT
21
+ * throw: `type: page` reaches its `default:` on every page assertion (its `value` is a URL, not a
22
+ * locator) and `StepMapper.checkPageStepFallback` resolves inside a try/catch that would read
23
+ * a throw as "not a page" and emit a false SG-W013. Validating at the load point also buys a
24
+ * better message — the file, the entry key and the nearest valid spelling — and reports once per
25
+ * entry rather than once per step that names it.
26
+ */
27
+
28
+ import type { DiagnosticsCollector } from '../diagnostics';
29
+ import { VALID_SELECTOR_TYPES } from '../../../utils/selector-types';
30
+ import { nearestMatch } from './nearest-match';
31
+ import { walkSelectorEntries } from './selector-entry-walk';
32
+
33
+ export interface UnknownSelectorType {
34
+ /** Dotted path to the offending entry, e.g. `save` or `greeting.ios`. */
35
+ path: string;
36
+ /** The unrecognised `type:` value exactly as written. */
37
+ type: string;
38
+ /** A near-miss valid type, when one is close enough to be worth naming. */
39
+ suggestion?: string;
40
+ }
41
+
42
+ /** Every entry in `file` whose `type:` is not a member of `VALID_SELECTOR_TYPES`. */
43
+ export function collectUnknownSelectorTypes(file: Record<string, unknown>): UnknownSelectorType[] {
44
+ const out: UnknownSelectorType[] = [];
45
+ walkSelectorEntries(file, {
46
+ entry(entry, path) {
47
+ const type = entry.type;
48
+ // Only a STRING type is judged here. A non-string `type:` (a YAML nesting slip) is a
49
+ // different defect with a different fix, and SG-W004's field walk is not the owner of it
50
+ // either — naming it as an unknown type would print `[object Object]` at the author.
51
+ if (typeof type !== 'string') return;
52
+ if ((VALID_SELECTOR_TYPES as readonly string[]).includes(type)) return;
53
+ out.push({ path, type, suggestion: nearestMatch(type, VALID_SELECTOR_TYPES) });
54
+ },
55
+ });
56
+ return out;
57
+ }
58
+
59
+ /** True when this entry kind is one the compiler knows how to resolve. */
60
+ export function isKnownSelectorType(type: unknown): boolean {
61
+ return typeof type === 'string' && (VALID_SELECTOR_TYPES as readonly string[]).includes(type);
62
+ }
63
+
64
+ /**
65
+ * SG-E027 — report every entry whose `type:` nothing dispatches.
66
+ *
67
+ * Runs once per selector FILE (the resolver's load is cached), so an entry named by twenty steps
68
+ * is reported once. Silent when there is no diagnostics channel attached — `generate` must not
69
+ * depend on diagnostics being wired.
70
+ */
71
+ export function reportUnknownSelectorTypes(
72
+ file: Record<string, unknown>,
73
+ opts: { feature?: string; diagnostics?: DiagnosticsCollector },
74
+ ): void {
75
+ const sink = opts.diagnostics;
76
+ if (!sink) return;
77
+ for (const { path, type, suggestion } of collectUnknownSelectorTypes(file)) {
78
+ const hint = suggestion
79
+ ? ` Did you mean "${suggestion}"?`
80
+ : ` Valid types: ${VALID_SELECTOR_TYPES.join(', ')}.`;
81
+ sink.add('SG-E027',
82
+ `selectors.yaml entry "${path}" declares type: "${type}", which is not a selector type — `
83
+ + `the entry resolves to getByPlaceholder("<its value>"), a locator that cannot match, so `
84
+ + `every step naming it now throws instead of failing later as a mystery timeout.${hint}`,
85
+ { feature: opts.feature });
86
+ }
87
+ }