@sun-asterisk/sungen 3.2.31 → 3.2.33

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 (363) hide show
  1. package/dist/cli/commands/audit.d.ts +1 -0
  2. package/dist/cli/commands/audit.d.ts.map +1 -1
  3. package/dist/cli/commands/audit.js +13 -2
  4. package/dist/cli/commands/audit.js.map +1 -1
  5. package/dist/cli/commands/capability.d.ts.map +1 -1
  6. package/dist/cli/commands/capability.js +46 -17
  7. package/dist/cli/commands/capability.js.map +1 -1
  8. package/dist/cli/commands/delivery.d.ts.map +1 -1
  9. package/dist/cli/commands/delivery.js +39 -18
  10. package/dist/cli/commands/delivery.js.map +1 -1
  11. package/dist/cli/commands/generate.d.ts.map +1 -1
  12. package/dist/cli/commands/generate.js +14 -7
  13. package/dist/cli/commands/generate.js.map +1 -1
  14. package/dist/cli/commands/init.d.ts.map +1 -1
  15. package/dist/cli/commands/init.js +2 -0
  16. package/dist/cli/commands/init.js.map +1 -1
  17. package/dist/cli/commands/results-merge.d.ts.map +1 -1
  18. package/dist/cli/commands/results-merge.js +2 -12
  19. package/dist/cli/commands/results-merge.js.map +1 -1
  20. package/dist/cli/commands/results-sanitize.d.ts.map +1 -1
  21. package/dist/cli/commands/results-sanitize.js +2 -13
  22. package/dist/cli/commands/results-sanitize.js.map +1 -1
  23. package/dist/cli/commands/setup-mobile-env.d.ts +15 -0
  24. package/dist/cli/commands/setup-mobile-env.d.ts.map +1 -0
  25. package/dist/cli/commands/setup-mobile-env.js +65 -0
  26. package/dist/cli/commands/setup-mobile-env.js.map +1 -0
  27. package/dist/cli/index.js +2 -0
  28. package/dist/cli/index.js.map +1 -1
  29. package/dist/dashboard/snapshot-builder.d.ts.map +1 -1
  30. package/dist/dashboard/snapshot-builder.js +6 -10
  31. package/dist/dashboard/snapshot-builder.js.map +1 -1
  32. package/dist/exporters/feature-parser.d.ts +21 -0
  33. package/dist/exporters/feature-parser.d.ts.map +1 -1
  34. package/dist/exporters/feature-parser.js +32 -1
  35. package/dist/exporters/feature-parser.js.map +1 -1
  36. package/dist/exporters/locale-variants.d.ts +7 -1
  37. package/dist/exporters/locale-variants.d.ts.map +1 -1
  38. package/dist/exporters/locale-variants.js +23 -16
  39. package/dist/exporters/locale-variants.js.map +1 -1
  40. package/dist/exporters/matrix/build.d.ts.map +1 -1
  41. package/dist/exporters/matrix/build.js +13 -7
  42. package/dist/exporters/matrix/build.js.map +1 -1
  43. package/dist/exporters/matrix/carry-manual-results.d.ts +70 -0
  44. package/dist/exporters/matrix/carry-manual-results.d.ts.map +1 -0
  45. package/dist/exporters/matrix/carry-manual-results.js +232 -0
  46. package/dist/exporters/matrix/carry-manual-results.js.map +1 -0
  47. package/dist/exporters/matrix/export.d.ts +2 -0
  48. package/dist/exporters/matrix/export.d.ts.map +1 -1
  49. package/dist/exporters/matrix/export.js +7 -1
  50. package/dist/exporters/matrix/export.js.map +1 -1
  51. package/dist/exporters/matrix/wording.d.ts +7 -0
  52. package/dist/exporters/matrix/wording.d.ts.map +1 -1
  53. package/dist/exporters/matrix/wording.js +27 -1
  54. package/dist/exporters/matrix/wording.js.map +1 -1
  55. package/dist/exporters/xlsx-exporter.d.ts +2 -0
  56. package/dist/exporters/xlsx-exporter.d.ts.map +1 -1
  57. package/dist/exporters/xlsx-exporter.js +7 -2
  58. package/dist/exporters/xlsx-exporter.js.map +1 -1
  59. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/reinstall-app-action.hbs +2 -2
  60. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/all-contain-assertion.hbs +1 -2
  61. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/all-contain-element.hbs +8 -5
  62. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/count-assertion.hbs +5 -1
  63. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-with-filter-assertion.hbs +1 -1
  64. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +1 -1
  65. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-with-filter-assertion.hbs +1 -1
  66. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +1 -1
  67. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +8 -2
  68. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/list-item-count-assertion.hbs +5 -2
  69. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/state-with-filter-assertion.hbs +1 -1
  70. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-filtered-assertion.hbs +1 -1
  71. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-with-role-variable-assertion.hbs +7 -3
  72. package/dist/generators/test-generator/adapters/appium/templates/steps/partials/await-elements.hbs +12 -0
  73. package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +94 -4
  74. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/capture-variable.hbs +7 -1
  75. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/label-value-assertion.hbs +7 -1
  76. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/row-scoped-column-assertion.hbs +23 -1
  77. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +8 -4
  78. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +4 -4
  79. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/default.hbs +1 -1
  80. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/id.hbs +1 -1
  81. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +4 -4
  82. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/url-assertion.hbs +17 -2
  83. package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +14 -0
  84. package/dist/generators/test-generator/code-generator.d.ts +2 -1
  85. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  86. package/dist/generators/test-generator/code-generator.js +122 -15
  87. package/dist/generators/test-generator/code-generator.js.map +1 -1
  88. package/dist/generators/test-generator/diagnostics.d.ts +70 -4
  89. package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
  90. package/dist/generators/test-generator/diagnostics.js +69 -3
  91. package/dist/generators/test-generator/diagnostics.js.map +1 -1
  92. package/dist/generators/test-generator/patterns/index.d.ts +13 -3
  93. package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
  94. package/dist/generators/test-generator/patterns/index.js +16 -10
  95. package/dist/generators/test-generator/patterns/index.js.map +1 -1
  96. package/dist/generators/test-generator/patterns/types.d.ts +15 -0
  97. package/dist/generators/test-generator/patterns/types.d.ts.map +1 -1
  98. package/dist/generators/test-generator/patterns/with-parent-scope.d.ts +5 -0
  99. package/dist/generators/test-generator/patterns/with-parent-scope.d.ts.map +1 -1
  100. package/dist/generators/test-generator/patterns/with-parent-scope.js +6 -0
  101. package/dist/generators/test-generator/patterns/with-parent-scope.js.map +1 -1
  102. package/dist/generators/test-generator/step-mapper.d.ts +81 -0
  103. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  104. package/dist/generators/test-generator/step-mapper.js +187 -13
  105. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  106. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  107. package/dist/generators/test-generator/template-engine.js +18 -0
  108. package/dist/generators/test-generator/template-engine.js.map +1 -1
  109. package/dist/generators/test-generator/utils/data-resolver.d.ts +25 -0
  110. package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
  111. package/dist/generators/test-generator/utils/data-resolver.js +39 -8
  112. package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
  113. package/dist/generators/test-generator/utils/selector-entry-fields.d.ts.map +1 -1
  114. package/dist/generators/test-generator/utils/selector-entry-fields.js +1 -0
  115. package/dist/generators/test-generator/utils/selector-entry-fields.js.map +1 -1
  116. package/dist/generators/test-generator/utils/selector-resolver.d.ts +84 -4
  117. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
  118. package/dist/generators/test-generator/utils/selector-resolver.js +229 -82
  119. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
  120. package/dist/harness/audit.d.ts.map +1 -1
  121. package/dist/harness/audit.js +35 -8
  122. package/dist/harness/audit.js.map +1 -1
  123. package/dist/harness/capability.d.ts +7 -1
  124. package/dist/harness/capability.d.ts.map +1 -1
  125. package/dist/harness/capability.js +15 -12
  126. package/dist/harness/capability.js.map +1 -1
  127. package/dist/harness/catalog/universal-viewpoints.yaml +5 -5
  128. package/dist/harness/config-file.d.ts +17 -0
  129. package/dist/harness/config-file.d.ts.map +1 -0
  130. package/dist/harness/config-file.js +100 -0
  131. package/dist/harness/config-file.js.map +1 -0
  132. package/dist/harness/depth-lint.d.ts.map +1 -1
  133. package/dist/harness/depth-lint.js +2 -1
  134. package/dist/harness/depth-lint.js.map +1 -1
  135. package/dist/harness/flow-check.d.ts.map +1 -1
  136. package/dist/harness/flow-check.js +6 -2
  137. package/dist/harness/flow-check.js.map +1 -1
  138. package/dist/harness/keyword-match.d.ts +59 -0
  139. package/dist/harness/keyword-match.d.ts.map +1 -0
  140. package/dist/harness/keyword-match.js +86 -0
  141. package/dist/harness/keyword-match.js.map +1 -0
  142. package/dist/harness/next-step.d.ts.map +1 -1
  143. package/dist/harness/next-step.js +2 -1
  144. package/dist/harness/next-step.js.map +1 -1
  145. package/dist/harness/parse.d.ts.map +1 -1
  146. package/dist/harness/parse.js +42 -7
  147. package/dist/harness/parse.js.map +1 -1
  148. package/dist/harness/perf.d.ts +7 -1
  149. package/dist/harness/perf.d.ts.map +1 -1
  150. package/dist/harness/perf.js +13 -10
  151. package/dist/harness/perf.js.map +1 -1
  152. package/dist/harness/quality-gates.d.ts +9 -2
  153. package/dist/harness/quality-gates.d.ts.map +1 -1
  154. package/dist/harness/quality-gates.js +16 -4
  155. package/dist/harness/quality-gates.js.map +1 -1
  156. package/dist/harness/query-catalog.js +0 -0
  157. package/dist/harness/query-catalog.js.map +1 -1
  158. package/dist/harness/read-text.d.ts +6 -0
  159. package/dist/harness/read-text.d.ts.map +1 -1
  160. package/dist/harness/read-text.js +7 -1
  161. package/dist/harness/read-text.js.map +1 -1
  162. package/dist/harness/secret-scan.d.ts +27 -1
  163. package/dist/harness/secret-scan.d.ts.map +1 -1
  164. package/dist/harness/secret-scan.js +57 -4
  165. package/dist/harness/secret-scan.js.map +1 -1
  166. package/dist/harness/sensors.d.ts +12 -0
  167. package/dist/harness/sensors.d.ts.map +1 -1
  168. package/dist/harness/sensors.js +41 -18
  169. package/dist/harness/sensors.js.map +1 -1
  170. package/dist/harness/spec-branches.d.ts +7 -20
  171. package/dist/harness/spec-branches.d.ts.map +1 -1
  172. package/dist/harness/spec-branches.js +85 -13
  173. package/dist/harness/spec-branches.js.map +1 -1
  174. package/dist/harness/spec-coverage.d.ts +16 -0
  175. package/dist/harness/spec-coverage.d.ts.map +1 -1
  176. package/dist/harness/spec-coverage.js +131 -12
  177. package/dist/harness/spec-coverage.js.map +1 -1
  178. package/dist/harness/viewpoint-ledger.d.ts.map +1 -1
  179. package/dist/harness/viewpoint-ledger.js +2 -1
  180. package/dist/harness/viewpoint-ledger.js.map +1 -1
  181. package/dist/harness/write-atomic.d.ts +2 -0
  182. package/dist/harness/write-atomic.d.ts.map +1 -0
  183. package/dist/harness/write-atomic.js +62 -0
  184. package/dist/harness/write-atomic.js.map +1 -0
  185. package/dist/orchestrator/ai-skills/writer.d.ts.map +1 -1
  186. package/dist/orchestrator/ai-skills/writer.js +15 -7
  187. package/dist/orchestrator/ai-skills/writer.js.map +1 -1
  188. package/dist/orchestrator/appium-environment-setup.d.ts +24 -0
  189. package/dist/orchestrator/appium-environment-setup.d.ts.map +1 -0
  190. package/dist/orchestrator/appium-environment-setup.js +157 -0
  191. package/dist/orchestrator/appium-environment-setup.js.map +1 -0
  192. package/dist/orchestrator/assets-drift.d.ts.map +1 -1
  193. package/dist/orchestrator/assets-drift.js +11 -6
  194. package/dist/orchestrator/assets-drift.js.map +1 -1
  195. package/dist/orchestrator/mobile-runtime-scaffolder.d.ts +2 -0
  196. package/dist/orchestrator/mobile-runtime-scaffolder.d.ts.map +1 -1
  197. package/dist/orchestrator/mobile-runtime-scaffolder.js.map +1 -1
  198. package/dist/orchestrator/project-deps-sync.d.ts.map +1 -1
  199. package/dist/orchestrator/project-deps-sync.js +22 -1
  200. package/dist/orchestrator/project-deps-sync.js.map +1 -1
  201. package/dist/orchestrator/project-initializer.d.ts +9 -0
  202. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  203. package/dist/orchestrator/project-initializer.js +63 -7
  204. package/dist/orchestrator/project-initializer.js.map +1 -1
  205. package/dist/orchestrator/security-overrides-catalog.d.ts +39 -0
  206. package/dist/orchestrator/security-overrides-catalog.d.ts.map +1 -0
  207. package/dist/orchestrator/security-overrides-catalog.js +63 -0
  208. package/dist/orchestrator/security-overrides-catalog.js.map +1 -0
  209. package/dist/orchestrator/security-overrides.d.ts +51 -0
  210. package/dist/orchestrator/security-overrides.d.ts.map +1 -0
  211. package/dist/orchestrator/security-overrides.js +165 -0
  212. package/dist/orchestrator/security-overrides.js.map +1 -0
  213. package/dist/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +14 -0
  214. package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +8 -2
  215. package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +64 -2
  216. package/dist/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +13 -1
  217. package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +20 -5
  218. package/dist/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +3 -3
  219. package/dist/orchestrator/templates/appium-environment/README.md +83 -0
  220. package/dist/orchestrator/templates/appium-environment/scripts/common/install-appium.ps1 +44 -0
  221. package/dist/orchestrator/templates/appium-environment/scripts/common/install-appium.sh +50 -0
  222. package/dist/orchestrator/templates/appium-environment/scripts/common/lib.ps1 +108 -0
  223. package/dist/orchestrator/templates/appium-environment/scripts/common/lib.sh +141 -0
  224. package/dist/orchestrator/templates/appium-environment/scripts/common/verify-appium.ps1 +39 -0
  225. package/dist/orchestrator/templates/appium-environment/scripts/common/verify-appium.sh +43 -0
  226. package/dist/orchestrator/templates/appium-environment/scripts/common/verify-node.ps1 +13 -0
  227. package/dist/orchestrator/templates/appium-environment/scripts/common/verify-node.sh +16 -0
  228. package/dist/orchestrator/templates/appium-environment/scripts/macos/install-android.sh +110 -0
  229. package/dist/orchestrator/templates/appium-environment/scripts/macos/install-ios.sh +32 -0
  230. package/dist/orchestrator/templates/appium-environment/scripts/macos/verify-android.sh +32 -0
  231. package/dist/orchestrator/templates/appium-environment/scripts/macos/verify-ios.sh +15 -0
  232. package/dist/orchestrator/templates/appium-environment/scripts/windows/install-android.ps1 +153 -0
  233. package/dist/orchestrator/templates/appium-environment/scripts/windows/verify-android.ps1 +26 -0
  234. package/dist/orchestrator/templates/appium-environment/setup.ps1 +58 -0
  235. package/dist/orchestrator/templates/appium-environment/setup.sh +88 -0
  236. package/dist/orchestrator/templates/specs-checked-state.d.ts +1 -1
  237. package/dist/orchestrator/templates/specs-checked-state.d.ts.map +1 -1
  238. package/dist/orchestrator/templates/specs-checked-state.js +26 -3
  239. package/dist/orchestrator/templates/specs-checked-state.js.map +1 -1
  240. package/dist/orchestrator/templates/specs-checked-state.ts +31 -6
  241. package/dist/orchestrator/templates/specs-locale-fixture.ts +21 -9
  242. package/dist/orchestrator/templates/specs-mock.d.ts +2 -1
  243. package/dist/orchestrator/templates/specs-mock.d.ts.map +1 -1
  244. package/dist/orchestrator/templates/specs-mock.js +10 -3
  245. package/dist/orchestrator/templates/specs-mock.js.map +1 -1
  246. package/dist/orchestrator/templates/specs-mock.ts +12 -4
  247. package/dist/orchestrator/templates/specs-pw-shape-reporter.ts +0 -0
  248. package/dist/orchestrator/templates/specs-url-assert.d.ts +17 -1
  249. package/dist/orchestrator/templates/specs-url-assert.d.ts.map +1 -1
  250. package/dist/orchestrator/templates/specs-url-assert.js +14 -2
  251. package/dist/orchestrator/templates/specs-url-assert.js.map +1 -1
  252. package/dist/orchestrator/templates/specs-url-assert.ts +24 -2
  253. package/dist/orchestrator/templates/wdio.conf.ts +24 -1
  254. package/package.json +3 -3
  255. package/src/cli/commands/audit.ts +12 -3
  256. package/src/cli/commands/capability.ts +63 -19
  257. package/src/cli/commands/delivery.ts +38 -16
  258. package/src/cli/commands/generate.ts +15 -7
  259. package/src/cli/commands/init.ts +3 -0
  260. package/src/cli/commands/results-merge.ts +2 -8
  261. package/src/cli/commands/results-sanitize.ts +2 -9
  262. package/src/cli/commands/setup-mobile-env.ts +64 -0
  263. package/src/cli/index.ts +2 -0
  264. package/src/dashboard/snapshot-builder.ts +6 -8
  265. package/src/exporters/feature-parser.ts +32 -2
  266. package/src/exporters/locale-variants.ts +23 -17
  267. package/src/exporters/matrix/build.ts +14 -8
  268. package/src/exporters/matrix/carry-manual-results.ts +229 -0
  269. package/src/exporters/matrix/export.ts +10 -4
  270. package/src/exporters/matrix/wording.ts +26 -1
  271. package/src/exporters/xlsx-exporter.ts +7 -2
  272. package/src/generators/test-generator/adapters/adapter-interface.ts +2 -2
  273. package/src/generators/test-generator/adapters/appium/templates/steps/actions/reinstall-app-action.hbs +2 -2
  274. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/all-contain-assertion.hbs +1 -2
  275. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/all-contain-element.hbs +8 -5
  276. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/count-assertion.hbs +5 -1
  277. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-with-filter-assertion.hbs +1 -1
  278. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +1 -1
  279. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-with-filter-assertion.hbs +1 -1
  280. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +1 -1
  281. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +8 -2
  282. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/list-item-count-assertion.hbs +5 -2
  283. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/state-with-filter-assertion.hbs +1 -1
  284. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-filtered-assertion.hbs +1 -1
  285. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-with-role-variable-assertion.hbs +7 -3
  286. package/src/generators/test-generator/adapters/appium/templates/steps/partials/await-elements.hbs +12 -0
  287. package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +94 -4
  288. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/capture-variable.hbs +7 -1
  289. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/label-value-assertion.hbs +7 -1
  290. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/row-scoped-column-assertion.hbs +23 -1
  291. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +8 -4
  292. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +4 -4
  293. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/default.hbs +1 -1
  294. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/id.hbs +1 -1
  295. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +4 -4
  296. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/url-assertion.hbs +17 -2
  297. package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +14 -0
  298. package/src/generators/test-generator/code-generator.ts +119 -19
  299. package/src/generators/test-generator/diagnostics.ts +70 -4
  300. package/src/generators/test-generator/patterns/index.ts +16 -9
  301. package/src/generators/test-generator/patterns/types.ts +15 -0
  302. package/src/generators/test-generator/patterns/with-parent-scope.ts +6 -0
  303. package/src/generators/test-generator/step-mapper.ts +196 -13
  304. package/src/generators/test-generator/template-engine.ts +18 -0
  305. package/src/generators/test-generator/utils/data-resolver.ts +41 -12
  306. package/src/generators/test-generator/utils/selector-entry-fields.ts +1 -0
  307. package/src/generators/test-generator/utils/selector-resolver.ts +256 -85
  308. package/src/harness/audit.ts +41 -12
  309. package/src/harness/capability.ts +23 -12
  310. package/src/harness/catalog/universal-viewpoints.yaml +5 -5
  311. package/src/harness/config-file.ts +66 -0
  312. package/src/harness/depth-lint.ts +2 -1
  313. package/src/harness/flow-check.ts +6 -2
  314. package/src/harness/keyword-match.ts +85 -0
  315. package/src/harness/next-step.ts +2 -1
  316. package/src/harness/parse.ts +42 -7
  317. package/src/harness/perf.ts +15 -7
  318. package/src/harness/quality-gates.ts +16 -4
  319. package/src/harness/query-catalog.ts +0 -0
  320. package/src/harness/read-text.ts +7 -1
  321. package/src/harness/secret-scan.ts +60 -4
  322. package/src/harness/sensors.ts +50 -19
  323. package/src/harness/spec-branches.ts +72 -14
  324. package/src/harness/spec-coverage.ts +133 -13
  325. package/src/harness/viewpoint-ledger.ts +2 -1
  326. package/src/harness/write-atomic.ts +22 -0
  327. package/src/orchestrator/ai-skills/writer.ts +18 -3
  328. package/src/orchestrator/appium-environment-setup.ts +127 -0
  329. package/src/orchestrator/assets-drift.ts +10 -5
  330. package/src/orchestrator/mobile-runtime-scaffolder.ts +2 -0
  331. package/src/orchestrator/project-deps-sync.ts +23 -1
  332. package/src/orchestrator/project-initializer.ts +69 -7
  333. package/src/orchestrator/security-overrides-catalog.ts +69 -0
  334. package/src/orchestrator/security-overrides.ts +149 -0
  335. package/src/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +14 -0
  336. package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +8 -2
  337. package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +64 -2
  338. package/src/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +13 -1
  339. package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +20 -5
  340. package/src/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +3 -3
  341. package/src/orchestrator/templates/appium-environment/README.md +83 -0
  342. package/src/orchestrator/templates/appium-environment/scripts/common/install-appium.ps1 +44 -0
  343. package/src/orchestrator/templates/appium-environment/scripts/common/install-appium.sh +50 -0
  344. package/src/orchestrator/templates/appium-environment/scripts/common/lib.ps1 +108 -0
  345. package/src/orchestrator/templates/appium-environment/scripts/common/lib.sh +141 -0
  346. package/src/orchestrator/templates/appium-environment/scripts/common/verify-appium.ps1 +39 -0
  347. package/src/orchestrator/templates/appium-environment/scripts/common/verify-appium.sh +43 -0
  348. package/src/orchestrator/templates/appium-environment/scripts/common/verify-node.ps1 +13 -0
  349. package/src/orchestrator/templates/appium-environment/scripts/common/verify-node.sh +16 -0
  350. package/src/orchestrator/templates/appium-environment/scripts/macos/install-android.sh +110 -0
  351. package/src/orchestrator/templates/appium-environment/scripts/macos/install-ios.sh +32 -0
  352. package/src/orchestrator/templates/appium-environment/scripts/macos/verify-android.sh +32 -0
  353. package/src/orchestrator/templates/appium-environment/scripts/macos/verify-ios.sh +15 -0
  354. package/src/orchestrator/templates/appium-environment/scripts/windows/install-android.ps1 +153 -0
  355. package/src/orchestrator/templates/appium-environment/scripts/windows/verify-android.ps1 +26 -0
  356. package/src/orchestrator/templates/appium-environment/setup.ps1 +58 -0
  357. package/src/orchestrator/templates/appium-environment/setup.sh +88 -0
  358. package/src/orchestrator/templates/specs-checked-state.ts +31 -6
  359. package/src/orchestrator/templates/specs-locale-fixture.ts +21 -9
  360. package/src/orchestrator/templates/specs-mock.ts +12 -4
  361. package/src/orchestrator/templates/specs-pw-shape-reporter.ts +0 -0
  362. package/src/orchestrator/templates/specs-url-assert.ts +24 -2
  363. package/src/orchestrator/templates/wdio.conf.ts +24 -1
@@ -186,12 +186,29 @@
186
186
  * build names itself at generate time instead of one step further downstream
187
187
  *
188
188
  * SG-W028 — a step's trailing scope clause was DROPPED, so the step runs against more of the page
189
- * than its sentence names. Two causes, one code: the `<word>` after `in [X]` is not a
189
+ * than its sentence names. Three causes, one code: the `<word>` after `in [X]` is not a
190
190
  * container type (a typo, or a leaf element used as a parent), or the by-content form
191
191
  * `in [X] <type> with {{v}}` was written for the mobile adapter, which chains a parent
192
192
  * element and has no content filter. Before this, both compiled clean and silently
193
193
  * widened the assertion to the whole screen — the failure mode that only a diff of the
194
- * generated spec against the Gherkin could reveal (#667)
194
+ * generated spec against the Gherkin could reveal (#667).
195
+ * The third is the RENDER-time half #667 could not reach: the clause parsed and the
196
+ * container resolved, but the step form that won the sentence emits a locator built
197
+ * inline and never applies it. Judged on the emitted code — every path that honours a
198
+ * scope emits the resolved container locator verbatim, so its absence IS the drop —
199
+ * which makes the check total rather than a list of templates to keep in sync. Found
200
+ * via `see [学科] label with {{v}} in [Education Card 6] card` compiling to a page-wide
201
+ * `getByText` on a screen repeating ten identical cards (#747)
202
+ * SG-W037 — a `type: page` entry says something about the QUERY that the compiler cannot use.
203
+ * Two causes, one code, and the same root confusion behind both: a bare `see [X] page`
204
+ * asserts the URL carries NO query (#464), which is unsatisfiable for a page keyed by
205
+ * one (`/admins/detail?id=5`, where the id comes from the row just clicked). Either the
206
+ * entry's `value` carries a `?`/`#` — dropped, because a page value is the path only and
207
+ * the pattern is matched against `u.pathname`, which never holds a query — or `query:`
208
+ * declares something other than `any`, which is ignored rather than read as permissive,
209
+ * since a typo must never quietly switch off the #464 assertion. Both used to be silent,
210
+ * and the first is how #748 was misdiagnosed in the field as a regex-escaping bug: the
211
+ * obvious workaround produced byte-identical output and no hint why
195
212
  * SG-W029 — a mobile (appium) feature compiled with NO usable app-package `Path:` — either the line
196
213
  * is missing outright, or it holds a web-style route (`Path: /applied-jobs`) that the
197
214
  * package guard rejects. Both used to be silent, and both cost the same two things: the
@@ -218,9 +235,58 @@
218
235
  * signal (SG-W030) it is not dropped-and-warned: with no signal the step is back on the
219
236
  * failure the declaration exists to fix. Recorded here AND thrown, so a pattern that
220
237
  * resolves selectors inside a `catch` cannot swallow it silently
238
+ * SG-W032 — a step carries the row filter `in [T] table with {{v}}` but the pattern that won it
239
+ * dropped the clause, so the generated code runs against the WHOLE table. Reproduced
240
+ * live: `Then User see [Status] text in [Job Table] table with {{v}}` asserts on every
241
+ * `.status` in the table and passes on a row the sentence never named. Judged on the
242
+ * emitted code — row scoping compiles to `.filter(` on every path that honours it — so
243
+ * the `table-*` family (which resolves the container itself, positioned rows included)
244
+ * and steps already refused elsewhere are excluded, as is the two-`with` form, which
245
+ * binds its tail to the parent and is the rewrite this message points at. A step that
246
+ * WRITES is excluded too: a write needs a value, so its `with {{v}}` IS that value and
247
+ * cannot also be a row filter — `fill [Search] field in [T] table with {{v}}` is an
248
+ * ordinary container-scoped write
249
+ * SG-W034 — a `see [X] column with {{v}}` step runs inside a table whose rows are declared with a
250
+ * `rows:` SHAPE, so the row is not an ARIA row and holds no ARIA cells — yet the
251
+ * assertion's only other way to reach a cell is `getByRole('cell')`, which matches
252
+ * nothing there. `columns.<col>.index` cannot rescue it either: it indexes that same
253
+ * empty set. Silent before this, and silent in the worst way — `toBeVisible()` on an
254
+ * empty locator fails as a timeout blaming the app. The message names the
255
+ * `columns.<col>.elements.cell` declaration that resolves the cell directly. Scoped to
256
+ * the `rows:` case on purpose: on a real <table> the role fallback is correct, and a
257
+ * diagnostic that fires on the correct spelling gets muted along with the real ones
258
+ * SG-E035 — `table match data:` on a table whose rows are declared with a `rows:` SHAPE and whose
259
+ * columns are not declared (or do not cover every column of the DataTable). The
260
+ * assertion reads the header row from `thead tr` and addresses cells as `td, th`;
261
+ * a list built from <div>s has neither, forever — not a render race — so the step can
262
+ * only wait out every timeout and fail on an empty header row. Refused at compile time
263
+ * instead, naming the `columns:` entries it needs. Declaring them replaces the runtime
264
+ * header read entirely: the index is known at compile time, and a column that names
265
+ * its own `elements.cell` addresses the cell without any table role at all
266
+ * SG-W036 — a `remember [X] … as {{v}}` capture whose entry selects by SHAPE (`locator`/`css`)
267
+ * and pins no `nth:`. A capture reads ONE element and the template takes the first
268
+ * match — the right default, and exactly what the documented `nth: 0` workaround asked
269
+ * for after Playwright's strict mode turned a list-shaped selector into a hard failure
270
+ * naming the count instead of the step. But "the first of many" is a decision: if the
271
+ * selector describes every item in a list, the captured value is the first row's rather
272
+ * than the one the scenario means, and nothing else would ever say so. Not reported for
273
+ * testid/label/placeholder/role+name/text entries, which name a particular thing —
274
+ * warning there would fire on nearly every capture in a suite
275
+ * SG-E038 — a `{{var}}` the compiler cannot resolve: no test-data file for the feature, no such
276
+ * key, or a key holding a list/map where a value was needed. Twenty-four step patterns
277
+ * used to catch this and emit the PLACEHOLDER TEXT as the value — `fill [ID] with
278
+ * {{valid_partner_id}}` compiled to `fill('${valid_partner_id}')`, generate exited 0,
279
+ * and the literal 20 characters were typed into the app. The field under test applied
280
+ * no format validation, so the string was accepted, the server saw an unknown user, and
281
+ * the scenario failed at the assertion it was written to fail at, for the reason it was
282
+ * written to fail: a suite that looked exactly right while feeding the system garbage.
283
+ * Worse than one wrong value — every run shared the SAME literal id, so a scenario
284
+ * needing a fresh user silently reused one and its state leaked between runs (#781).
285
+ * Note a feature resolves `{{var}}` against a data file named after ITSELF, so the
286
+ * second `.feature` in a screen directory starts life with no data file at all
221
287
  * Diagnostics never change the EXIT CODE by themselves; `sungen generate --strict` turns any
222
288
  * collected diagnostic into a non-zero exit. They are not all inert, though: SG-W001, SG-W011,
223
- * SG-E020, SG-E021, SG-W020, SG-E023, SG-E024, SG-E025, SG-E026 and SG-E027 additionally compile
289
+ * SG-E020, SG-E021, SG-W020, SG-E023, SG-E024, SG-E025, SG-E026, SG-E027, SG-E035 and SG-E038 additionally compile
224
290
  * the offending step to a runtime `throw` — a step that reads as a check and silently checks
225
291
  * nothing is worse than one that fails loudly — and SG-E031 refuses the file outright. (SG-E022 is
226
292
  * the one exception to the "blame the Gherkin" rule and throws nothing: the file is still written
@@ -228,7 +294,7 @@
228
294
  * Generation always COMPLETES — the rest of the scenario and the rest of the file still compile.
229
295
  */
230
296
 
231
- export type DiagnosticCode = 'SG-W001' | 'SG-W002' | 'SG-W003' | 'SG-W004' | 'SG-W005' | 'SG-W006' | 'SG-W007' | 'SG-W008' | 'SG-W009' | 'SG-W010' | 'SG-W011' | 'SG-W012' | 'SG-W013' | 'SG-W014' | 'SG-W015' | 'SG-W016' | 'SG-W017' | 'SG-W018' | 'SG-E020' | 'SG-E021' | 'SG-W020' | 'SG-W021' | 'SG-W022' | 'SG-W023' | 'SG-W024' | 'SG-W026' | 'SG-E025' | 'SG-E026' | 'SG-E022' | 'SG-E023' | 'SG-E024' | 'SG-E027' | 'SG-W028' | 'SG-W029' | 'SG-W030' | 'SG-E031';
297
+ export type DiagnosticCode = 'SG-W001' | 'SG-W002' | 'SG-W003' | 'SG-W004' | 'SG-W005' | 'SG-W006' | 'SG-W007' | 'SG-W008' | 'SG-W009' | 'SG-W010' | 'SG-W011' | 'SG-W012' | 'SG-W013' | 'SG-W014' | 'SG-W015' | 'SG-W016' | 'SG-W017' | 'SG-W018' | 'SG-E020' | 'SG-E021' | 'SG-W020' | 'SG-W021' | 'SG-W022' | 'SG-W023' | 'SG-W024' | 'SG-W026' | 'SG-E025' | 'SG-E026' | 'SG-E022' | 'SG-E023' | 'SG-E024' | 'SG-E027' | 'SG-W028' | 'SG-W029' | 'SG-W030' | 'SG-E031' | 'SG-W032' | 'SG-W034' | 'SG-E035' | 'SG-W036' | 'SG-W037' | 'SG-E038';
232
298
 
233
299
  export interface Diagnostic {
234
300
  code: DiagnosticCode;
@@ -48,7 +48,7 @@ const TAP_GESTURE_FORMS = /\bdouble[-\s]?tap|\btap(?:s)?\b[^.]*\b(?:top of|at th
48
48
  * one of the two mobile patterns that must stay UNDECLARED.
49
49
  * 2. Both vocabularies then see their own verb, and ordinary PRIORITY decides the winner —
50
50
  * no matcher has to learn about the other. `tap … and accept alert` reaches
51
- * `click-with-alert` (21); `tap … in [T] table row 2` reaches `table-action-in-row-nth` (19);
51
+ * `click-with-alert` (21); `tap … in [T] table row 2` reaches `table-action-in-row-nth` (18);
52
52
  * a plain `tap` still lands on `mobile-tap` (15) ahead of `click-element` (10).
53
53
  *
54
54
  * Safe because every click-keyed matcher in the vocabulary is a word-boundary test (`\bclick(s)?\b`
@@ -193,6 +193,7 @@ export class PatternRegistry {
193
193
  comment: resolved.comment,
194
194
  pattern: pattern.name,
195
195
  templateData: resolved.data,
196
+ parentLocator,
196
197
  };
197
198
  }
198
199
 
@@ -202,7 +203,7 @@ export class PatternRegistry {
202
203
  if (generatorStateMiss) return generatorStateMiss;
203
204
  // A generator returning nothing still falls through to the no-pattern path.
204
205
  const generated = pattern.generator(step, scoped);
205
- return generated ? { ...generated, pattern: pattern.name } : null;
206
+ return generated ? { ...generated, pattern: pattern.name, parentLocator } : null;
206
207
  }
207
208
 
208
209
  return null;
@@ -447,17 +448,23 @@ export class PatternRegistry {
447
448
 
448
449
  /**
449
450
  * Resolve a `{{var}}` written where literal text is expected, leaving anything else untouched.
450
- * An unknown variable stays as written rather than throwing — the locator is then wrong in a way
451
- * the author can read, which beats losing the whole step.
451
+ *
452
+ * An unresolvable ref is NOT kept as written. That was the last of the #781 swallows, and the
453
+ * hardest to see, because the old rationale — "the locator is then wrong in a way the author can
454
+ * read, which beats losing the whole step" — is true of a locator NAME and false here. The second
455
+ * `with` in `see [Tag] header with {{label}} in [Job Card] card with {{job_title}}` binds to the
456
+ * PARENT scope, so the value lands in `.filter({ hasText: '{{job_title}}' })`: a scope that can
457
+ * never match, compiled with ZERO diagnostics and `generate` exiting 0. The author then reads a
458
+ * "element not found" timeout that says nothing about the missing test-data key.
459
+ *
460
+ * Letting it throw routes it to StepMapper.mapStep like every other ref, so the step names the
461
+ * ref and the file it wanted. Only a string that IS exactly `{{ref}}` is ever resolved here;
462
+ * anything else is returned untouched, so ordinary literal text is unaffected.
452
463
  */
453
464
  private static resolveInlineData(text: string, context: PatternContext): string {
454
465
  const match = text.match(/^\{\{([a-z0-9\-._]+)\}\}$/i);
455
466
  if (!match) return text;
456
- try {
457
- return context.dataResolver.resolveData(match[1], context.featureName);
458
- } catch {
459
- return text;
460
- }
467
+ return context.dataResolver.resolveData(match[1], context.featureName);
461
468
  }
462
469
 
463
470
  /**
@@ -96,6 +96,21 @@ export interface PatternContext {
96
96
  adapterName?: string;
97
97
  /** Compile-diagnostics channel, so the pattern layer can report instead of throwing. */
98
98
  diagnostics?: DiagnosticsSink;
99
+ /**
100
+ * The container locator the step's `in [Parent] <type>` clause resolved to, already rendered —
101
+ * set by `withParentScope`, absent when the step carries no scope clause.
102
+ *
103
+ * `withParentScope` also stamps this onto everything the pattern RESOLVES, which is the seam that
104
+ * serves the ordinary case. It is not enough on its own: a pattern whose locator is not built from
105
+ * a selectors.yaml entry never has a `resolved` object to carry it. `see [学科] label with {{v}}`
106
+ * is exactly that — the ref names on-screen CAPTION text, so the common case has no entry at all,
107
+ * `resolveSelector` throws, and the branch renders from `{}`. The scope was then dropped with no
108
+ * diagnostic (#747), which is the silence #667 set out to end.
109
+ *
110
+ * Read it ONLY where the template's locator is built inline rather than from `resolved`; every
111
+ * other pattern gets the scope through `resolved` and must not apply it twice.
112
+ */
113
+ parentLocator?: string;
99
114
  /** App profile (qa/app.yaml merged over presets/defaults) — widget variants, recipes,
100
115
  * loading indicator, table row conventions. Typed loosely to keep the SPI decoupled;
101
116
  * the concrete shape is ResolvedAppProfile. */
@@ -14,6 +14,11 @@ import { PatternContext } from './types';
14
14
  * Only the target's own scope is stamped; anything a pattern resolves that ALREADY carries a
15
15
  * `parentLocator` is left alone, so a pattern that builds its own container chain (table rows) keeps
16
16
  * winning over the generic one.
17
+ *
18
+ * The scope also rides on the CONTEXT itself, not only on what the pattern resolves. A pattern whose
19
+ * locator is built inline — `see [X] label with {{v}}` matches on-screen caption text and usually
20
+ * has no selectors.yaml entry at all, so `resolveSelector` throws and the branch renders from `{}` —
21
+ * has no `resolved` object for the stamp to land on, and lost the scope silently (#747).
17
22
  */
18
23
  export function withParentScope(context: PatternContext, parentLocator: string): PatternContext {
19
24
  const scope = <T>(resolved: T): T =>
@@ -35,6 +40,7 @@ export function withParentScope(context: PatternContext, parentLocator: string):
35
40
 
36
41
  return {
37
42
  ...context,
43
+ parentLocator,
38
44
  selectorResolver,
39
45
  renderLocator: (resolved: any) => context.renderLocator(scope(resolved)),
40
46
  };
@@ -3,8 +3,8 @@ import { PARENT_CONTAINER_TYPES } from '../gherkin-parser/parent-scope-clause';
3
3
  import { TemplateEngine } from './template-engine';
4
4
  import { adapterRegistry } from './adapters';
5
5
  import { PatternRegistry, PatternContext, Platform } from './patterns';
6
- import { SelectorResolver } from './utils/selector-resolver';
7
- import { DataResolver } from './utils/data-resolver';
6
+ import { SelectorResolver, type ResolvedSelector } from './utils/selector-resolver';
7
+ import { DataResolver, DataRefError } from './utils/data-resolver';
8
8
  import { locatorIdentityKey } from './utils/dialog-scope-identity';
9
9
  import { DiagnosticsCollector } from './diagnostics';
10
10
  import { unsupportedStepCode } from './patterns/unsupported-step-code';
@@ -44,6 +44,9 @@ export interface MappedStep {
44
44
  imports?: string[]; // Required imports
45
45
  pattern?: string; // Name of the step form that produced the code (post-map guards read it)
46
46
  templateData?: Record<string, any>; // What the resolver handed the template (e.g. a recorded refusal)
47
+ /** The container locator the step's `in [Parent] <type>` clause resolved to, when it carried one.
48
+ * Recorded so a post-map guard can check the scope actually reached the code (SG-W028, #747). */
49
+ parentLocator?: string;
47
50
  }
48
51
 
49
52
  /**
@@ -226,7 +229,45 @@ export class StepMapper {
226
229
  * Map a Gherkin step to Playwright code
227
230
  * Uses pattern registry first, falls back to AI if enabled
228
231
  */
232
+ /**
233
+ * SG-E038 — the ONE place an unresolvable `{{var}}` is answered.
234
+ *
235
+ * Twenty-four step patterns used to catch the resolver's failure each in their own way and
236
+ * substitute the placeholder TEXT as the value, so `fill [ID] with {{valid_partner_id}}` shipped
237
+ * `fill('${valid_partner_id}')` — 20 literal characters typed into the app, `generate` exiting 0,
238
+ * and a scenario that failed exactly where it was written to fail, for the reason it was written
239
+ * to fail. Nothing told the run apart from a correct one (#781).
240
+ *
241
+ * The step now compiles to a throw naming the ref and the file, which is the house answer to
242
+ * "the compiler would otherwise invent something" (SG-E023/E024/E026 all work this way). A throw
243
+ * is preferred over letting the error kill the feature file, because one run then reports EVERY
244
+ * unresolved ref instead of the first; `--strict` turns the diagnostic into exit 1.
245
+ */
229
246
  mapStep(step: ParsedStep): MappedStep | Promise<MappedStep> {
247
+ try {
248
+ return this.mapStepResolving(step);
249
+ } catch (err) {
250
+ if (err instanceof DataRefError) return this.unresolvedDataRef(step, err);
251
+ throw err;
252
+ }
253
+ }
254
+
255
+ private unresolvedDataRef(step: ParsedStep, err: DataRefError): MappedStep {
256
+ const fix = err.file
257
+ ? `add a "${err.dataRef}" key to ${err.file}`
258
+ : `define "${err.dataRef}" in the feature's test-data file`;
259
+ const reason =
260
+ `step "${step.text}": {{${err.dataRef}}} does not resolve — ${err.message}. `
261
+ + `${fix}, or reference a key that exists. The step compiles to this throw rather than `
262
+ + `typing the literal text "\${${err.dataRef}}" into the app as if it were data.`;
263
+ this.diagnostics?.add('SG-E038', reason, { feature: this.featureName, step: step.text });
264
+ return {
265
+ code: unsupportedStepCode(reason),
266
+ comment: `Unresolved {{${err.dataRef}}} — see SG-E038: ${step.text}`,
267
+ };
268
+ }
269
+
270
+ private mapStepResolving(step: ParsedStep): MappedStep | Promise<MappedStep> {
230
271
  this.stepCounter++;
231
272
 
232
273
  // Track effective keyword: And/But inherit from previous Given/When/Then
@@ -377,12 +418,7 @@ export class StepMapper {
377
418
  // Case 3: [panel] dialog with {{value}}
378
419
  // Use dataRef as filter text — the dialog name is dynamic (from test-data),
379
420
  // so use .filter({ hasText }) instead of { name } for reliable matching
380
- let filterText = '';
381
- try {
382
- filterText = this.dataResolver.resolveData(step.dataRef, this.featureName);
383
- } catch {
384
- filterText = `\${${step.dataRef}}`;
385
- }
421
+ const filterText = this.dataResolver.resolveData(step.dataRef, this.featureName);
386
422
  contextVars.dialogFilterText = filterText;
387
423
  // The declared entry still owns the ROOT the filter applies to — but only when it
388
424
  // is a non-role strategy (locator/testid/css). A role entry keeps the bare
@@ -470,8 +506,14 @@ export class StepMapper {
470
506
  if (positionDropped) return positionDropped;
471
507
  const swallowed = this.checkAssertionClauseSwallowed(step, mappedStep);
472
508
  if (swallowed) return swallowed;
509
+ const rowFilterDropped = this.checkRowFilterClauseDropped(step, mappedStep);
510
+ if (rowFilterDropped) return rowFilterDropped;
473
511
  const badType = this.checkUnknownSelectorType(step, mappedStep);
474
512
  if (badType) return badType;
513
+ // LAST of the guards, and deliberately so: every check above can REPLACE the step with a
514
+ // refusal, and reporting a dropped scope against code that is about to be discarded is the
515
+ // "two contradictory messages about one step" this guard's own doc rules out.
516
+ this.checkParentScopeDropped(step, mappedStep);
475
517
  return mappedStep;
476
518
  }
477
519
 
@@ -680,6 +722,112 @@ export class StepMapper {
680
722
  };
681
723
  }
682
724
 
725
+ /**
726
+ * The `in [T] table with {{v}}` row filter is the sentence's narrowest claim — it names ONE row.
727
+ * A pattern that does not consume it leaves the step running against the WHOLE table, and the
728
+ * author cannot see that from the Gherkin.
729
+ *
730
+ * Judged on the emitted code, not on which pattern won: `.filter(` is what row scoping compiles
731
+ * to on every path that honours the clause, so its absence is the fact. Three exclusions, each
732
+ * measured rather than assumed:
733
+ * - the `table-*` family resolves the container from the step text itself; its positioned-row
734
+ * form emits `toHaveCount(1)` + `toContainText` with no filter at all and is CORRECT;
735
+ * - a step already refused elsewhere compiles to a `throw`, and a second opinion on top of the
736
+ * refusal (an off-platform table step already gets SG-W020) is two contradictory messages
737
+ * about one step;
738
+ * - the two-`with` form binds its tail to the PARENT, which does compile to a filter — it is
739
+ * the shape this diagnostic points people at, so it must never be flagged.
740
+ *
741
+ * A step that WRITES is exempt rather than escalated — see ROW_FILTER_WRITE_CALL. It stays a
742
+ * WARNING for the rest, in line with the repo's rule that a diagnostic which cries wolf gets muted
743
+ * wholesale: the single-`with` sentence is genuinely ambiguous, and reading the value as the
744
+ * expected text is a legitimate compilation of it, just not the one most authors mean.
745
+ */
746
+ private static readonly ROW_FILTER_CLAUSE = /\bin\s+\[[^\]]+\]\s+table\s+with\b/i;
747
+
748
+ /**
749
+ * Calls that WRITE through the scoped locator, as opposed to reading it. Both adapters are listed:
750
+ * the mobile spelling of a fill is `setValue`/`addValue`.
751
+ *
752
+ * A write is EXEMPT, not escalated. A step that writes needs a value, so its `with {{v}}` is that
753
+ * value and cannot also be a row filter — `fill [Search] field in [Job Table] table with {{v}}` is
754
+ * an ordinary container-scoped write, and a toolbar search box inside a table is exactly that. The
755
+ * danger an earlier reading claimed for it does not exist either: on a multi-row table a
756
+ * mis-scoped write is a loud strict-mode failure, and on a single-row table there is only one
757
+ * field to write to.
758
+ */
759
+ private static readonly ROW_FILTER_WRITE_CALL =
760
+ /\.(?:fill|type|setInputFiles|selectOption|clear|check|uncheck|press|setValue|addValue|clearValue)\(/;
761
+
762
+ private checkRowFilterClauseDropped(step: ParsedStep, mapped: MappedStep): MappedStep | null {
763
+ const code = mapped.code || '';
764
+ if (!StepMapper.ROW_FILTER_CLAUSE.test(step.text)) return null;
765
+ // Bound to the parent instead of the target — that IS the scoped form.
766
+ if (step.parentValueRef || step.parentValueText) return null;
767
+ if (mapped.pattern?.startsWith('table-')) return null;
768
+ if (code.startsWith('throw new Error(')) return null;
769
+ if (/\.filter\(/.test(code)) return null;
770
+
771
+ if (StepMapper.ROW_FILTER_WRITE_CALL.test(code)) return null;
772
+
773
+ const table = step.parentRef ?? 'Table';
774
+ const target = step.selectorRef ?? 'X';
775
+ const type = step.elementType ?? 'text';
776
+ const filter = step.dataRef ? `{{${step.dataRef}}}` : 'the row value';
777
+ const rewrite = `User see [${target}] ${type} with <expected> in [<Row Entry>] row with ${filter}`;
778
+ this.diagnostics?.add('SG-W032',
779
+ `step "${step.text}" reads "with ${filter}" as the EXPECTED VALUE, not as a row filter — the `
780
+ + `sentence names a row but the assertion runs against the whole [${table}] table, so it can `
781
+ + `pass on a row the sentence never named. To scope the row, use the two-"with" form: `
782
+ + `"${rewrite}". Point that second \`[…]\` at a ROW entry, not at the table entry — a filter `
783
+ + `applied to the table narrows nothing and the assertion stays as weak as it is now.`,
784
+ { feature: this.featureName, step: step.text });
785
+ return null;
786
+ }
787
+
788
+ /**
789
+ * The container scope parsed, resolved — and then never reached the emitted code.
790
+ *
791
+ * #667 closed the parse-time half of this: an `in [X] <word>` whose `<word>` is not a container
792
+ * type is reported (SG-W028) rather than truncated. It could not close the render-time half,
793
+ * because honouring the scope is per-template work: the registry stamps `parentLocator` on what a
794
+ * RESOLVER hands back and `withParentScope` stamps it on what any pattern RESOLVES, but a branch
795
+ * that builds its locator inline and renders from hand-picked fields receives neither. Three did,
796
+ * and `see [学科] label with {{v}} in [Education Card 6] card` compiled to a page-wide
797
+ * `getByText` with no diagnostic at all (#747) — a green test reading a card the sentence never
798
+ * names, findable only by diffing the generated spec against the Gherkin.
799
+ *
800
+ * So the fact is taken from the OUTPUT, exactly as SG-W032 takes the row filter from `.filter(`:
801
+ * every path that honours a container scope emits the resolved container locator verbatim, so its
802
+ * absence from the code IS the drop. That makes the guard total — it covers every template that
803
+ * exists now and every one added later, without a list to keep in sync.
804
+ *
805
+ * A step already refused elsewhere is excluded: it compiles to a `throw`, and a second opinion on
806
+ * top of a refusal is two contradictory messages about one step (SG-W032's reasoning, unchanged).
807
+ * The `throw` test covers a refusal the PATTERN layer recorded; a refusal one of the guards above
808
+ * substitutes is covered by call order instead — this one runs last.
809
+ *
810
+ * Returns `void`, not `MappedStep | null` like its siblings: it only ever REPORTS. Typed that way
811
+ * on purpose, so a future edit copying the sibling shape cannot assume a replacement step is being
812
+ * consumed when nothing is looking at one.
813
+ */
814
+ private checkParentScopeDropped(step: ParsedStep, mapped: MappedStep): void {
815
+ const parent = mapped.parentLocator;
816
+ if (!parent) return;
817
+ const code = mapped.code || '';
818
+ if (code.startsWith('throw new Error(')) return;
819
+ if (code.includes(parent)) return;
820
+
821
+ this.diagnostics?.add('SG-W028',
822
+ `step "${step.text}" names the container [${step.parentRef}] but the generated code does not `
823
+ + `scope to it — the step runs against the WHOLE page, so it fails where the target repeats and `
824
+ + `passes on a DIFFERENT [${step.parentRef}] where it does not. The "${mapped.pattern}" step `
825
+ + `form does not carry a container scope. Assert on an element the container owns (give it a `
826
+ + `selectors.yaml entry and use "has text"/"contains"), or drop the clause so the sentence `
827
+ + `matches what is actually checked.`,
828
+ { feature: this.featureName, step: step.text });
829
+ }
830
+
683
831
  private checkAssertionClauseSwallowed(step: ParsedStep, mapped: MappedStep): MappedStep | null {
684
832
  const code = mapped.code || '';
685
833
  // Bare visibility: nothing but a visible/displayed assertion on one locator.
@@ -766,7 +914,6 @@ export class StepMapper {
766
914
  // delimiters, so the code may carry `__SUNGEN_TD\$key\$__` instead of the plain form.
767
915
  const marker = DataResolver.encodeMarker(step.dataRef);
768
916
  if (code.includes(marker) || code.includes(marker.replace(/\$/g, '\\$'))) return;
769
- if (code.includes(`\${${step.dataRef}}`)) return; // unresolved fallback
770
917
  if (code.includes(step.dataRef)) return; // testData.set('ref'), comments
771
918
  try {
772
919
  const value = this.dataResolver.resolveData(step.dataRef, this.featureName);
@@ -804,21 +951,57 @@ export class StepMapper {
804
951
 
805
952
  // Try to find column index from table's columns config
806
953
  let columnIndex: number | undefined;
954
+ // The CELL, when the table declares one. `getByRole('cell')` below is the only other way this
955
+ // template can reach a cell, and a row shape built from <div>s has no cell role anywhere in it
956
+ // — the assertion then matches zero elements on a list that renders perfectly well. A
957
+ // `columns.<col>.elements.cell` entry names the cell directly and sidesteps the role entirely.
958
+ // `columns.<col>.index` cannot help there: it indexes the very getByRole('cell') set that is
959
+ // empty.
960
+ let cellLocator: string | undefined;
807
961
  if (this.rowScopeTable) {
962
+ // Only the table LOOKUP is guarded. Widening the catch to cover the cell resolution below
963
+ // would swallow a broken `elements.cell` declaration under a comment about a missing table,
964
+ // and silently serve the ARIA fallback with no diagnostic at all.
965
+ let tableResolved: ResolvedSelector | undefined;
808
966
  try {
809
- const tableResolved = this.selectorResolver.resolveSelector(
967
+ tableResolved = this.selectorResolver.resolveSelector(
810
968
  this.rowScopeTable, this.featureName, 'table', 0
811
969
  );
812
- const columnKey = columnRef.toLowerCase().normalize('NFC');
813
- columnIndex = tableResolved.columns?.[columnKey]?.index;
814
970
  } catch {
815
971
  // Table not found — fallback to filter
816
972
  }
973
+ if (tableResolved) {
974
+ const columnKey = columnRef.toLowerCase().normalize('NFC');
975
+ const column = tableResolved.columns?.[columnKey];
976
+ columnIndex = column?.index;
977
+ const cellEntry = column?.elements?.cell;
978
+ // A shape that still selects <tr> keeps its ARIA rows and cells, so the role fallback below
979
+ // serves it exactly as before. Only a shape built from something else loses them.
980
+ const shapeHidesTableRoles = !!tableResolved.rows
981
+ && !/(^|[\s>+~,(])tr\b/i.test(tableResolved.rows);
982
+ if (cellEntry) {
983
+ cellLocator = this.templateEngine.renderStep('locator', {
984
+ ...this.selectorResolver.resolveNestedEntry(cellEntry, columnRef),
985
+ parentLocator: 'tableRow',
986
+ });
987
+ } else if (shapeHidesTableRoles) {
988
+ // The rows are not ARIA rows, so their children are not ARIA cells either. Warned only in
989
+ // that case: on a real <table> — including one that pins its row universe with
990
+ // `rows: 'tbody tr'` — the role fallback is correct, and a diagnostic that fires on the
991
+ // correct spelling gets muted along with the real ones.
992
+ this.diagnostics?.add('SG-W034',
993
+ `column "[${columnRef}]" is asserted inside a table whose rows are declared with a "rows:" `
994
+ + `shape, so the row is not an ARIA row and holds no ARIA cells — this step falls back to `
995
+ + `getByRole('cell'), which matches nothing there. Name the cell on the column, e.g. `
996
+ + `"${this.rowScopeTable.toLowerCase()}:\\n columns:\\n ${columnKey}:\\n index: 0\\n header: '${columnRef}'\\n elements:\\n cell:\\n type: locator\\n value: '.your-cell-class'"`,
997
+ { feature: this.featureName, step: step.text });
998
+ }
999
+ }
817
1000
  }
818
1001
 
819
1002
  const isGiven = this.lastPrimaryKeyword === 'Given';
820
1003
  const code = this.templateEngine.renderStep('row-scoped-column-assertion', {
821
- columnIndex, cellValue, isGiven,
1004
+ columnIndex, cellValue, isGiven, cellLocator,
822
1005
  });
823
1006
 
824
1007
  return {
@@ -53,6 +53,24 @@ export class TemplateEngine {
53
53
  .replace(/\u2029/g, '\\u2029');
54
54
  });
55
55
 
56
+ // The CSS selector behind an `id` strategy, from whichever field carries it.
57
+ //
58
+ // Two shapes reach this with two different conventions. A new-format entry (`type: id`) keeps
59
+ // whatever the author wrote in `value`; the legacy `resolveByPriority` path STRIPS the hash,
60
+ // returning `value: 'submit-btn'` with the full `#submit-btn` in `locator`. Reading `value`
61
+ // alone would therefore compile `#submit-btn` down to `page.locator('submit-btn')` — a type
62
+ // selector that matches nothing, which an `is hidden` assertion then passes forever.
63
+ //
64
+ // A bare word gets its `#` back for the same reason: `id` is also a mobile strategy, where the
65
+ // convention IS bare ("bottom_bar"), so an entry copied across platforms would otherwise be a
66
+ // silent no-match on web. Anything already carrying selector syntax is passed through untouched.
67
+ Handlebars.registerHelper('cssIdSelector', function(locator: any, value: any) {
68
+ const raw = (typeof locator === 'string' && locator) ? locator : (value ?? '');
69
+ const text = String(raw);
70
+ if (!text) return '';
71
+ return /[#.\[\]:>~+ ]/.test(text) ? text : `#${text}`;
72
+ });
73
+
56
74
  Handlebars.registerHelper('includes', function(text: string, search: string) {
57
75
  return typeof text === 'string' && text.includes(search);
58
76
  });
@@ -1,6 +1,33 @@
1
1
  import * as path from 'path';
2
2
  import { readYamlIfExists } from '../../../utils/yaml-io';
3
3
 
4
+ /**
5
+ * A `{{var}}` the compiler could not resolve: missing data file, missing key, or a key holding
6
+ * something that is not a primitive.
7
+ *
8
+ * A TYPE rather than a message convention, because the one thing callers must never do with this
9
+ * failure is guess. Twenty-four step patterns used to catch it and substitute the placeholder text
10
+ * as the VALUE — `fill('${valid_partner_id}')` typed 20 literal characters into the app, `generate`
11
+ * exited 0, and the scenario failed at the assertion it was written to fail at, for the reason it
12
+ * was written to fail. Nothing distinguished that run from a correct one (#781).
13
+ *
14
+ * So the failure is now carried, not converted, and it is answered in exactly one place —
15
+ * `StepMapper.mapStep`, which turns it into SG-E038 plus a step that throws by name. Catching this
16
+ * anywhere else to produce a value is the defect, and the type is what makes that visible in review.
17
+ */
18
+ export class DataRefError extends Error {
19
+ constructor(
20
+ message: string,
21
+ /** The `{{ref}}` as written in the feature, e.g. `valid_partner_id` or `user.email`. */
22
+ readonly dataRef: string,
23
+ /** The test-data file consulted, relative to the project root. Absent when no file was reached. */
24
+ readonly file?: string,
25
+ ) {
26
+ super(message);
27
+ this.name = 'DataRefError';
28
+ }
29
+ }
30
+
4
31
  /**
5
32
  * DataResolver - Resolves data references to actual values
6
33
  *
@@ -106,16 +133,17 @@ export class DataResolver {
106
133
 
107
134
  private resolveValue(dataRef: string, featureName?: string): string {
108
135
  const { fileName, valuePath } = this.parseDataRef(dataRef, featureName);
109
- const data = this.loadDataFile(fileName);
136
+ const data = this.loadDataFile(fileName, dataRef);
137
+ const rel = this.screenName
138
+ ? `qa/${this.flowMode ? 'flows' : 'screens'}/${this.screenName}/test-data/${fileName}.yaml`
139
+ : undefined;
110
140
 
111
141
  let current = data;
112
142
  for (const key of valuePath) {
113
143
  if (current && typeof current === 'object' && key in current) {
114
144
  current = current[key];
115
145
  } else {
116
- throw new Error(
117
- `Data path not found: ${dataRef}. Failed at key: ${key}`
118
- );
146
+ throw new DataRefError(`Data path not found: ${dataRef}. Failed at key: ${key}`, dataRef, rel);
119
147
  }
120
148
  }
121
149
 
@@ -124,8 +152,10 @@ export class DataResolver {
124
152
  } else if (typeof current === 'number' || typeof current === 'boolean') {
125
153
  return String(current);
126
154
  } else {
127
- throw new Error(
128
- `Data reference ${dataRef} must resolve to a primitive value, got: ${typeof current}`
155
+ throw new DataRefError(
156
+ `Data reference ${dataRef} must resolve to a primitive value, got: ${typeof current}`,
157
+ dataRef,
158
+ rel,
129
159
  );
130
160
  }
131
161
  }
@@ -147,7 +177,7 @@ export class DataResolver {
147
177
  }
148
178
 
149
179
  if (parts.length < 2) {
150
- throw new Error(`Invalid data reference: ${dataRef}. Expected format: file.path.to.value or provide featureName`);
180
+ throw new DataRefError(`Invalid data reference: ${dataRef}. Expected format: file.path.to.value or provide featureName`, dataRef);
151
181
  }
152
182
  return { fileName: parts[0], valuePath: parts.slice(1) };
153
183
  }
@@ -168,24 +198,23 @@ export class DataResolver {
168
198
  * Load data file from disk (with caching)
169
199
  * Merges base + env-specific: {name}.yaml + {name}.{SUNGEN_ENV}.yaml
170
200
  */
171
- private loadDataFile(fileName: string): any {
201
+ private loadDataFile(fileName: string, dataRef: string): any {
172
202
  if (this.dataCache.has(fileName)) {
173
203
  return this.dataCache.get(fileName)!;
174
204
  }
175
205
 
176
206
  if (!this.screenName) {
177
- throw new Error(`Data resolution requires screen/flow context for "${fileName}"`);
207
+ throw new DataRefError(`Data resolution requires screen/flow context for "${fileName}"`, dataRef);
178
208
  }
179
209
 
180
210
  const subDir = this.flowMode ? 'flows' : 'screens';
181
211
  const baseDir = path.join(process.cwd(), 'qa', subDir, this.screenName, 'test-data');
182
212
  const basePath = path.join(baseDir, `${fileName}.yaml`);
213
+ const rel = `qa/${subDir}/${this.screenName}/test-data/${fileName}.yaml`;
183
214
  const data = readYamlIfExists(basePath);
184
215
 
185
216
  if (!data || typeof data !== 'object' || Object.keys(data).length === 0) {
186
- throw new Error(
187
- `Data file not found or empty: qa/${subDir}/${this.screenName}/test-data/${fileName}.yaml`
188
- );
217
+ throw new DataRefError(`Data file not found or empty: ${rel}`, dataRef, rel);
189
218
  }
190
219
 
191
220
  let merged = { ...data };
@@ -36,6 +36,7 @@ export const CONSUMED_ENTRY_FIELDS: ReadonlySet<string> = new Set([
36
36
  'scope', // dialog/landmark scoping in the locator partial
37
37
  'match', // collapses into `exact` for getByText
38
38
  'anchor', // page assertions — the rendered-content proof (#662)
39
+ 'query', // page assertions — `any` drops the bare-page no-query rule (#748)
39
40
  'android', // appium adapter → androidSelector (#392)
40
41
  'ios', // appium adapter → iosSelector (#392)
41
42
  'variant', // widget recipes / native select / upload subtype