@sun-asterisk/sungen 3.2.24-beta.6 → 3.2.25

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/capabilities/context-router.d.ts.map +1 -1
  2. package/dist/capabilities/context-router.js +15 -7
  3. package/dist/capabilities/context-router.js.map +1 -1
  4. package/dist/cli/commands/capability.d.ts.map +1 -1
  5. package/dist/cli/commands/capability.js +6 -48
  6. package/dist/cli/commands/capability.js.map +1 -1
  7. package/dist/cli/commands/changed.d.ts +14 -0
  8. package/dist/cli/commands/changed.d.ts.map +1 -0
  9. package/dist/cli/commands/changed.js +128 -0
  10. package/dist/cli/commands/changed.js.map +1 -0
  11. package/dist/cli/commands/delivery.d.ts.map +1 -1
  12. package/dist/cli/commands/delivery.js +25 -1
  13. package/dist/cli/commands/delivery.js.map +1 -1
  14. package/dist/cli/commands/generate.js +1 -1
  15. package/dist/cli/commands/generate.js.map +1 -1
  16. package/dist/cli/commands/results-merge.d.ts +19 -0
  17. package/dist/cli/commands/results-merge.d.ts.map +1 -0
  18. package/dist/cli/commands/results-merge.js +191 -0
  19. package/dist/cli/commands/results-merge.js.map +1 -0
  20. package/dist/cli/commands/update.d.ts.map +1 -1
  21. package/dist/cli/commands/update.js +100 -12
  22. package/dist/cli/commands/update.js.map +1 -1
  23. package/dist/cli/index.js +4 -0
  24. package/dist/cli/index.js.map +1 -1
  25. package/dist/exporters/api-catalog-loader.d.ts.map +1 -1
  26. package/dist/exporters/api-catalog-loader.js +10 -3
  27. package/dist/exporters/api-catalog-loader.js.map +1 -1
  28. package/dist/exporters/api-testcase-formatter.d.ts +9 -0
  29. package/dist/exporters/api-testcase-formatter.d.ts.map +1 -1
  30. package/dist/exporters/api-testcase-formatter.js +55 -3
  31. package/dist/exporters/api-testcase-formatter.js.map +1 -1
  32. package/dist/exporters/csv-exporter.d.ts.map +1 -1
  33. package/dist/exporters/csv-exporter.js +10 -1
  34. package/dist/exporters/csv-exporter.js.map +1 -1
  35. package/dist/exporters/json-exporter.d.ts.map +1 -1
  36. package/dist/exporters/json-exporter.js +6 -1
  37. package/dist/exporters/json-exporter.js.map +1 -1
  38. package/dist/exporters/matrix/build.d.ts.map +1 -1
  39. package/dist/exporters/matrix/build.js +10 -0
  40. package/dist/exporters/matrix/build.js.map +1 -1
  41. package/dist/exporters/matrix/export.js +1 -1
  42. package/dist/exporters/matrix/export.js.map +1 -1
  43. package/dist/exporters/matrix/render-csv.d.ts.map +1 -1
  44. package/dist/exporters/matrix/render-csv.js +5 -1
  45. package/dist/exporters/matrix/render-csv.js.map +1 -1
  46. package/dist/exporters/matrix/render-xlsx.d.ts.map +1 -1
  47. package/dist/exporters/matrix/render-xlsx.js +4 -1
  48. package/dist/exporters/matrix/render-xlsx.js.map +1 -1
  49. package/dist/exporters/matrix/types.d.ts +7 -0
  50. package/dist/exporters/matrix/types.d.ts.map +1 -1
  51. package/dist/exporters/matrix/types.js.map +1 -1
  52. package/dist/exporters/playwright-report-parser.d.ts +17 -2
  53. package/dist/exporters/playwright-report-parser.d.ts.map +1 -1
  54. package/dist/exporters/playwright-report-parser.js +68 -8
  55. package/dist/exporters/playwright-report-parser.js.map +1 -1
  56. package/dist/exporters/results-merge.d.ts +46 -0
  57. package/dist/exporters/results-merge.d.ts.map +1 -0
  58. package/dist/exporters/results-merge.js +148 -0
  59. package/dist/exporters/results-merge.js.map +1 -0
  60. package/dist/exporters/selector-key-resolver.d.ts +4 -0
  61. package/dist/exporters/selector-key-resolver.d.ts.map +1 -1
  62. package/dist/exporters/selector-key-resolver.js +101 -1
  63. package/dist/exporters/selector-key-resolver.js.map +1 -1
  64. package/dist/exporters/types.d.ts +8 -1
  65. package/dist/exporters/types.d.ts.map +1 -1
  66. package/dist/exporters/xlsx-report-builder.d.ts.map +1 -1
  67. package/dist/exporters/xlsx-report-builder.js +5 -4
  68. package/dist/exporters/xlsx-report-builder.js.map +1 -1
  69. package/dist/generators/gherkin-parser/index.d.ts +2 -0
  70. package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
  71. package/dist/generators/gherkin-parser/index.js +52 -1
  72. package/dist/generators/gherkin-parser/index.js.map +1 -1
  73. package/dist/generators/test-generator/adapters/adapter-interface.d.ts +20 -10
  74. package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
  75. package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts +2 -11
  76. package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts.map +1 -1
  77. package/dist/generators/test-generator/adapters/appium/appium-adapter.js.map +1 -1
  78. package/dist/generators/test-generator/adapters/appium/templates/imports.hbs +3 -0
  79. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/biometric-action.hbs +9 -0
  80. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/check-action.hbs +11 -6
  81. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/date-picker-unsupported.hbs +4 -0
  82. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/deep-link-action.hbs +8 -0
  83. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/lifecycle-missing-app-id.hbs +5 -0
  84. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/location-services-action.hbs +11 -0
  85. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/reinstall-app-action.hbs +21 -0
  86. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/restart-app-action.hbs +16 -0
  87. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/set-connectivity-action.hbs +11 -0
  88. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/uncheck-action.hbs +9 -5
  89. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/checked-assertion.hbs +10 -4
  90. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/device-log-assertion.hbs +16 -0
  91. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/disappears-within-assertion.hbs +5 -0
  92. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/matches-format-assertion.hbs +15 -0
  93. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/not-checked-assertion.hbs +6 -4
  94. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/state-with-filter-assertion.hbs +4 -3
  95. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/still-visible-after-assertion.hbs +4 -0
  96. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-filtered-assertion.hbs +5 -2
  97. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/pull-to-refresh-action.hbs +3 -0
  98. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/swipe-action.hbs +3 -0
  99. package/dist/generators/test-generator/adapters/appium/templates/steps/partials/appium-selector.hbs +7 -1
  100. package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +302 -29
  101. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +2 -11
  102. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
  103. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
  104. package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  105. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/select-action.hbs +17 -1
  106. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/attribute-assertion.hbs +6 -1
  107. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/have-value-assertion.hbs +18 -1
  108. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-filter-assertion.hbs +5 -1
  109. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/matches-format-assertion.hbs +5 -0
  110. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/state-with-filter-assertion.hbs +4 -2
  111. package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-for-element-with-text.hbs +4 -1
  112. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-expect-text.hbs +1 -1
  113. package/dist/generators/test-generator/code-generator.d.ts +6 -0
  114. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  115. package/dist/generators/test-generator/code-generator.js +80 -7
  116. package/dist/generators/test-generator/code-generator.js.map +1 -1
  117. package/dist/generators/test-generator/diagnostics.d.ts +60 -6
  118. package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
  119. package/dist/generators/test-generator/diagnostics.js +59 -5
  120. package/dist/generators/test-generator/diagnostics.js.map +1 -1
  121. package/dist/generators/test-generator/patterns/expect-patterns.d.ts +7 -0
  122. package/dist/generators/test-generator/patterns/expect-patterns.d.ts.map +1 -1
  123. package/dist/generators/test-generator/patterns/expect-patterns.js +36 -2
  124. package/dist/generators/test-generator/patterns/expect-patterns.js.map +1 -1
  125. package/dist/generators/test-generator/patterns/index.d.ts +19 -12
  126. package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
  127. package/dist/generators/test-generator/patterns/index.js +58 -38
  128. package/dist/generators/test-generator/patterns/index.js.map +1 -1
  129. package/dist/generators/test-generator/patterns/types.d.ts +23 -0
  130. package/dist/generators/test-generator/patterns/types.d.ts.map +1 -1
  131. package/dist/generators/test-generator/patterns/unsupported-step-code.d.ts +18 -0
  132. package/dist/generators/test-generator/patterns/unsupported-step-code.d.ts.map +1 -0
  133. package/dist/generators/test-generator/patterns/unsupported-step-code.js +23 -0
  134. package/dist/generators/test-generator/patterns/unsupported-step-code.js.map +1 -0
  135. package/dist/generators/test-generator/recipe-expander.js +9 -1
  136. package/dist/generators/test-generator/recipe-expander.js.map +1 -1
  137. package/dist/generators/test-generator/reset-tier.d.ts +68 -0
  138. package/dist/generators/test-generator/reset-tier.d.ts.map +1 -0
  139. package/dist/generators/test-generator/reset-tier.js +94 -0
  140. package/dist/generators/test-generator/reset-tier.js.map +1 -0
  141. package/dist/generators/test-generator/step-mapper.d.ts +39 -0
  142. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  143. package/dist/generators/test-generator/step-mapper.js +144 -5
  144. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  145. package/dist/generators/test-generator/template-engine.d.ts +2 -10
  146. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  147. package/dist/generators/test-generator/template-engine.js +19 -2
  148. package/dist/generators/test-generator/template-engine.js.map +1 -1
  149. package/dist/generators/test-generator/utils/format-pattern.d.ts +35 -0
  150. package/dist/generators/test-generator/utils/format-pattern.d.ts.map +1 -0
  151. package/dist/generators/test-generator/utils/format-pattern.js +82 -0
  152. package/dist/generators/test-generator/utils/format-pattern.js.map +1 -0
  153. package/dist/generators/test-generator/utils/js-literal-scanner.d.ts +42 -0
  154. package/dist/generators/test-generator/utils/js-literal-scanner.d.ts.map +1 -0
  155. package/dist/generators/test-generator/utils/js-literal-scanner.js +142 -0
  156. package/dist/generators/test-generator/utils/js-literal-scanner.js.map +1 -0
  157. package/dist/generators/test-generator/utils/mask-refs-for-matching.d.ts +18 -0
  158. package/dist/generators/test-generator/utils/mask-refs-for-matching.d.ts.map +1 -0
  159. package/dist/generators/test-generator/utils/mask-refs-for-matching.js +25 -0
  160. package/dist/generators/test-generator/utils/mask-refs-for-matching.js.map +1 -0
  161. package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts +9 -1
  162. package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts.map +1 -1
  163. package/dist/generators/test-generator/utils/runtime-data-transformer.js +124 -63
  164. package/dist/generators/test-generator/utils/runtime-data-transformer.js.map +1 -1
  165. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
  166. package/dist/generators/test-generator/utils/selector-resolver.js +24 -1
  167. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
  168. package/dist/generators/test-generator/utils/spec-syntax-check.d.ts +14 -0
  169. package/dist/generators/test-generator/utils/spec-syntax-check.d.ts.map +1 -0
  170. package/dist/generators/test-generator/utils/spec-syntax-check.js +26 -0
  171. package/dist/generators/test-generator/utils/spec-syntax-check.js.map +1 -0
  172. package/dist/harness/annotation-overrides.d.ts +0 -12
  173. package/dist/harness/annotation-overrides.d.ts.map +1 -1
  174. package/dist/harness/annotation-overrides.js +9 -5
  175. package/dist/harness/annotation-overrides.js.map +1 -1
  176. package/dist/harness/audit.d.ts.map +1 -1
  177. package/dist/harness/audit.js +7 -5
  178. package/dist/harness/audit.js.map +1 -1
  179. package/dist/harness/capability-plan.d.ts +2 -1
  180. package/dist/harness/capability-plan.d.ts.map +1 -1
  181. package/dist/harness/capability-plan.js +14 -4
  182. package/dist/harness/capability-plan.js.map +1 -1
  183. package/dist/harness/capability.d.ts +11 -0
  184. package/dist/harness/capability.d.ts.map +1 -1
  185. package/dist/harness/capability.js +8 -0
  186. package/dist/harness/capability.js.map +1 -1
  187. package/dist/harness/catalog/drivers.yaml +5 -0
  188. package/dist/harness/parse.d.ts +1 -0
  189. package/dist/harness/parse.d.ts.map +1 -1
  190. package/dist/harness/parse.js +6 -1
  191. package/dist/harness/parse.js.map +1 -1
  192. package/dist/harness/run-manifest.d.ts +58 -0
  193. package/dist/harness/run-manifest.d.ts.map +1 -0
  194. package/dist/harness/run-manifest.js +264 -0
  195. package/dist/harness/run-manifest.js.map +1 -0
  196. package/dist/harness/script-check.d.ts.map +1 -1
  197. package/dist/harness/script-check.js +6 -2
  198. package/dist/harness/script-check.js.map +1 -1
  199. package/dist/harness/sensors.d.ts.map +1 -1
  200. package/dist/harness/sensors.js +5 -1
  201. package/dist/harness/sensors.js.map +1 -1
  202. package/dist/harness/viewpoint-ledger.d.ts +1 -1
  203. package/dist/harness/viewpoint-ledger.d.ts.map +1 -1
  204. package/dist/harness/viewpoint-ledger.js +25 -4
  205. package/dist/harness/viewpoint-ledger.js.map +1 -1
  206. package/dist/index.d.ts +1 -0
  207. package/dist/index.d.ts.map +1 -1
  208. package/dist/index.js +5 -1
  209. package/dist/index.js.map +1 -1
  210. package/dist/orchestrator/lockstep-version.d.ts +22 -0
  211. package/dist/orchestrator/lockstep-version.d.ts.map +1 -0
  212. package/dist/orchestrator/lockstep-version.js +58 -0
  213. package/dist/orchestrator/lockstep-version.js.map +1 -0
  214. package/dist/orchestrator/mobile-runtime-scaffolder.d.ts.map +1 -1
  215. package/dist/orchestrator/mobile-runtime-scaffolder.js +5 -1
  216. package/dist/orchestrator/mobile-runtime-scaffolder.js.map +1 -1
  217. package/dist/orchestrator/project-deps-sync.d.ts +39 -0
  218. package/dist/orchestrator/project-deps-sync.d.ts.map +1 -0
  219. package/dist/orchestrator/project-deps-sync.js +175 -0
  220. package/dist/orchestrator/project-deps-sync.js.map +1 -0
  221. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  222. package/dist/orchestrator/project-initializer.js +5 -2
  223. package/dist/orchestrator/project-initializer.js.map +1 -1
  224. package/dist/orchestrator/templates/ai-src/commands/run-test.md +109 -0
  225. package/dist/orchestrator/templates/ai-src/config/claude.md +1 -1
  226. package/dist/orchestrator/templates/ai-src/config/copilot.md +1 -1
  227. package/dist/orchestrator/templates/ai-src/skills/sungen-api-design/SKILL.md +49 -0
  228. package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +14 -3
  229. package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +120 -10
  230. package/dist/orchestrator/templates/ai-src/skills/sungen-locale/SKILL.md +1 -1
  231. package/dist/orchestrator/templates/ai-src/skills/sungen-mobile-gestures/SKILL.md +31 -0
  232. package/dist/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +1 -1
  233. package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +9 -0
  234. package/dist/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +15 -0
  235. package/dist/orchestrator/templates/env.appium.example +21 -0
  236. package/dist/orchestrator/templates/specs-api.d.ts +4 -2
  237. package/dist/orchestrator/templates/specs-api.d.ts.map +1 -1
  238. package/dist/orchestrator/templates/specs-api.js +233 -10
  239. package/dist/orchestrator/templates/specs-api.js.map +1 -1
  240. package/dist/orchestrator/templates/specs-api.ts +214 -11
  241. package/dist/orchestrator/templates/specs-db.d.ts.map +1 -1
  242. package/dist/orchestrator/templates/specs-db.js +79 -6
  243. package/dist/orchestrator/templates/specs-db.js.map +1 -1
  244. package/dist/orchestrator/templates/specs-db.ts +70 -7
  245. package/dist/orchestrator/templates/specs-regex-escape.d.ts +13 -0
  246. package/dist/orchestrator/templates/specs-regex-escape.d.ts.map +1 -0
  247. package/dist/orchestrator/templates/specs-regex-escape.js +19 -0
  248. package/dist/orchestrator/templates/specs-regex-escape.js.map +1 -0
  249. package/dist/orchestrator/templates/specs-regex-escape.ts +16 -0
  250. package/dist/orchestrator/templates/specs-test-data.ts +53 -6
  251. package/dist/orchestrator/templates/wdio.conf.ts +126 -10
  252. package/dist/utils/selector-types.d.ts +1 -1
  253. package/dist/utils/selector-types.d.ts.map +1 -1
  254. package/dist/utils/selector-types.js +1 -0
  255. package/dist/utils/selector-types.js.map +1 -1
  256. package/package.json +8 -6
  257. package/src/capabilities/context-router.ts +13 -5
  258. package/src/cli/commands/capability.ts +4 -38
  259. package/src/cli/commands/changed.ts +104 -0
  260. package/src/cli/commands/delivery.ts +27 -2
  261. package/src/cli/commands/generate.ts +1 -1
  262. package/src/cli/commands/results-merge.ts +153 -0
  263. package/src/cli/commands/update.ts +72 -13
  264. package/src/cli/index.ts +4 -0
  265. package/src/exporters/api-catalog-loader.ts +10 -2
  266. package/src/exporters/api-testcase-formatter.ts +59 -3
  267. package/src/exporters/csv-exporter.ts +11 -2
  268. package/src/exporters/json-exporter.ts +7 -2
  269. package/src/exporters/matrix/build.ts +9 -0
  270. package/src/exporters/matrix/export.ts +1 -1
  271. package/src/exporters/matrix/render-csv.ts +5 -1
  272. package/src/exporters/matrix/render-xlsx.ts +4 -1
  273. package/src/exporters/matrix/types.ts +7 -0
  274. package/src/exporters/playwright-report-parser.ts +68 -9
  275. package/src/exporters/results-merge.ts +150 -0
  276. package/src/exporters/selector-key-resolver.ts +81 -1
  277. package/src/exporters/types.ts +8 -1
  278. package/src/exporters/xlsx-report-builder.ts +5 -4
  279. package/src/generators/gherkin-parser/index.ts +55 -1
  280. package/src/generators/test-generator/adapters/adapter-interface.ts +23 -1
  281. package/src/generators/test-generator/adapters/appium/appium-adapter.ts +2 -2
  282. package/src/generators/test-generator/adapters/appium/templates/imports.hbs +3 -0
  283. package/src/generators/test-generator/adapters/appium/templates/steps/actions/biometric-action.hbs +9 -0
  284. package/src/generators/test-generator/adapters/appium/templates/steps/actions/check-action.hbs +11 -6
  285. package/src/generators/test-generator/adapters/appium/templates/steps/actions/date-picker-unsupported.hbs +4 -0
  286. package/src/generators/test-generator/adapters/appium/templates/steps/actions/deep-link-action.hbs +8 -0
  287. package/src/generators/test-generator/adapters/appium/templates/steps/actions/lifecycle-missing-app-id.hbs +5 -0
  288. package/src/generators/test-generator/adapters/appium/templates/steps/actions/location-services-action.hbs +11 -0
  289. package/src/generators/test-generator/adapters/appium/templates/steps/actions/reinstall-app-action.hbs +21 -0
  290. package/src/generators/test-generator/adapters/appium/templates/steps/actions/restart-app-action.hbs +16 -0
  291. package/src/generators/test-generator/adapters/appium/templates/steps/actions/set-connectivity-action.hbs +11 -0
  292. package/src/generators/test-generator/adapters/appium/templates/steps/actions/uncheck-action.hbs +9 -5
  293. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/checked-assertion.hbs +10 -4
  294. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/device-log-assertion.hbs +16 -0
  295. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/disappears-within-assertion.hbs +5 -0
  296. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/matches-format-assertion.hbs +15 -0
  297. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/not-checked-assertion.hbs +6 -4
  298. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/state-with-filter-assertion.hbs +4 -3
  299. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/still-visible-after-assertion.hbs +4 -0
  300. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-filtered-assertion.hbs +5 -2
  301. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/pull-to-refresh-action.hbs +3 -0
  302. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/swipe-action.hbs +3 -0
  303. package/src/generators/test-generator/adapters/appium/templates/steps/partials/appium-selector.hbs +7 -1
  304. package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +302 -29
  305. package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +2 -2
  306. package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  307. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/select-action.hbs +17 -1
  308. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/attribute-assertion.hbs +6 -1
  309. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/have-value-assertion.hbs +18 -1
  310. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-filter-assertion.hbs +5 -1
  311. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/matches-format-assertion.hbs +5 -0
  312. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/state-with-filter-assertion.hbs +4 -2
  313. package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-for-element-with-text.hbs +4 -1
  314. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-expect-text.hbs +1 -1
  315. package/src/generators/test-generator/code-generator.ts +86 -7
  316. package/src/generators/test-generator/diagnostics.ts +60 -6
  317. package/src/generators/test-generator/patterns/expect-patterns.ts +37 -2
  318. package/src/generators/test-generator/patterns/index.ts +58 -33
  319. package/src/generators/test-generator/patterns/types.ts +36 -0
  320. package/src/generators/test-generator/patterns/unsupported-step-code.ts +19 -0
  321. package/src/generators/test-generator/recipe-expander.ts +9 -1
  322. package/src/generators/test-generator/reset-tier.ts +93 -0
  323. package/src/generators/test-generator/step-mapper.ts +149 -6
  324. package/src/generators/test-generator/template-engine.ts +22 -3
  325. package/src/generators/test-generator/utils/format-pattern.ts +79 -0
  326. package/src/generators/test-generator/utils/js-literal-scanner.ts +132 -0
  327. package/src/generators/test-generator/utils/mask-refs-for-matching.ts +21 -0
  328. package/src/generators/test-generator/utils/runtime-data-transformer.ts +126 -76
  329. package/src/generators/test-generator/utils/selector-resolver.ts +26 -1
  330. package/src/generators/test-generator/utils/spec-syntax-check.ts +22 -0
  331. package/src/harness/annotation-overrides.ts +10 -5
  332. package/src/harness/audit.ts +7 -5
  333. package/src/harness/capability-plan.ts +13 -6
  334. package/src/harness/capability.ts +16 -0
  335. package/src/harness/catalog/drivers.yaml +5 -0
  336. package/src/harness/parse.ts +7 -1
  337. package/src/harness/run-manifest.ts +254 -0
  338. package/src/harness/script-check.ts +6 -2
  339. package/src/harness/sensors.ts +5 -1
  340. package/src/harness/viewpoint-ledger.ts +26 -5
  341. package/src/index.ts +1 -0
  342. package/src/orchestrator/lockstep-version.ts +46 -0
  343. package/src/orchestrator/mobile-runtime-scaffolder.ts +5 -1
  344. package/src/orchestrator/project-deps-sync.ts +175 -0
  345. package/src/orchestrator/project-initializer.ts +5 -2
  346. package/src/orchestrator/templates/ai-src/commands/run-test.md +109 -0
  347. package/src/orchestrator/templates/ai-src/config/claude.md +1 -1
  348. package/src/orchestrator/templates/ai-src/config/copilot.md +1 -1
  349. package/src/orchestrator/templates/ai-src/skills/sungen-api-design/SKILL.md +49 -0
  350. package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +14 -3
  351. package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +120 -10
  352. package/src/orchestrator/templates/ai-src/skills/sungen-locale/SKILL.md +1 -1
  353. package/src/orchestrator/templates/ai-src/skills/sungen-mobile-gestures/SKILL.md +31 -0
  354. package/src/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +1 -1
  355. package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +9 -0
  356. package/src/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +15 -0
  357. package/src/orchestrator/templates/env.appium.example +21 -0
  358. package/src/orchestrator/templates/specs-api.ts +214 -11
  359. package/src/orchestrator/templates/specs-db.ts +70 -7
  360. package/src/orchestrator/templates/specs-regex-escape.ts +16 -0
  361. package/src/orchestrator/templates/specs-test-data.ts +53 -6
  362. package/src/orchestrator/templates/wdio.conf.ts +126 -10
  363. package/src/utils/selector-types.ts +1 -0
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Format placeholders → a regex source, for `see [T] <type> matches "<format>"`.
3
+ *
4
+ * QA specs state a label's contract as a SHAPE, not a value: "Format: yyyy/mm/dd ~ yyyy/mm/dd",
5
+ * "Format: 残りX個". Neither existing text assertion can express that — an exact-value assert
6
+ * pins data that changes daily (red while the app is right), and `contains` proves almost
7
+ * nothing ("abc ~ def" passes). This translator keeps the spec's own vocabulary in the Gherkin
8
+ * so the step reads like the requirement, and no regex reaches a file QA has to review.
9
+ *
10
+ * Tokens (everything else is a literal, regex-escaped):
11
+ * yyyy → a 4-digit year mm | dd | hh | ss → a zero-padded 2-digit part
12
+ * X → an integer, thousand separators allowed (the app renders prices as "2,800円")
13
+ * * → any run of characters, for a label that carries surrounding content
14
+ *
15
+ * A token only counts when it is not glued to an ASCII letter, so a literal English prefix
16
+ * survives ("Comment" keeps its "mm"; "yyyy/mm/dd" does not). Japanese/symbol literals are
17
+ * unaffected either way.
18
+ */
19
+ /**
20
+ * Translate a format string into an ANCHORED regex source (no delimiters, no flags).
21
+ * Anchored because the spec means the whole label: "Format: X件" is not satisfied by "12件です".
22
+ * Use the `*` token where the label legitimately carries more (a fused mobile node).
23
+ */
24
+ export declare function formatToRegexSource(format: string): string;
25
+ /** True when the string carries at least one token — i.e. it is a FORMAT, not a plain value. */
26
+ export declare function hasFormatToken(format: string): boolean;
27
+ /**
28
+ * The same pattern as a JS **regex literal**, ready to paste into generated code.
29
+ *
30
+ * Not a string: `new RegExp('^\d{4}$')` is a trap — in a single-quoted JS literal `\d` collapses
31
+ * to `d`, so the compiled test would silently match the letter instead of a digit. A regex literal
32
+ * carries the backslashes verbatim; only `/` needs escaping, and date formats are full of them.
33
+ */
34
+ export declare function formatToRegexLiteral(format: string): string;
35
+ //# sourceMappingURL=format-pattern.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format-pattern.d.ts","sourceRoot":"","sources":["../../../../src/generators/test-generator/utils/format-pattern.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAgBH;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAwB1D;AAED,gGAAgG;AAChG,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE3D"}
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ /**
3
+ * Format placeholders → a regex source, for `see [T] <type> matches "<format>"`.
4
+ *
5
+ * QA specs state a label's contract as a SHAPE, not a value: "Format: yyyy/mm/dd ~ yyyy/mm/dd",
6
+ * "Format: 残りX個". Neither existing text assertion can express that — an exact-value assert
7
+ * pins data that changes daily (red while the app is right), and `contains` proves almost
8
+ * nothing ("abc ~ def" passes). This translator keeps the spec's own vocabulary in the Gherkin
9
+ * so the step reads like the requirement, and no regex reaches a file QA has to review.
10
+ *
11
+ * Tokens (everything else is a literal, regex-escaped):
12
+ * yyyy → a 4-digit year mm | dd | hh | ss → a zero-padded 2-digit part
13
+ * X → an integer, thousand separators allowed (the app renders prices as "2,800円")
14
+ * * → any run of characters, for a label that carries surrounding content
15
+ *
16
+ * A token only counts when it is not glued to an ASCII letter, so a literal English prefix
17
+ * survives ("Comment" keeps its "mm"; "yyyy/mm/dd" does not). Japanese/symbol literals are
18
+ * unaffected either way.
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.formatToRegexSource = formatToRegexSource;
22
+ exports.hasFormatToken = hasFormatToken;
23
+ exports.formatToRegexLiteral = formatToRegexLiteral;
24
+ /** Longest-first: `yyyy` must win over a bare `yy`-style prefix of another token. */
25
+ const TOKENS = [
26
+ ['yyyy', '\\d{4}'],
27
+ ['mm', '\\d{2}'],
28
+ ['dd', '\\d{2}'],
29
+ ['hh', '\\d{2}'],
30
+ ['ss', '\\d{2}'],
31
+ ['X', '\\d+(?:,\\d{3})*'],
32
+ ['*', '[\\s\\S]*'],
33
+ ];
34
+ const isAsciiLetter = (ch) => !!ch && /[A-Za-z]/.test(ch);
35
+ const escapeLiteral = (ch) => ch.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
36
+ /**
37
+ * Translate a format string into an ANCHORED regex source (no delimiters, no flags).
38
+ * Anchored because the spec means the whole label: "Format: X件" is not satisfied by "12件です".
39
+ * Use the `*` token where the label legitimately carries more (a fused mobile node).
40
+ */
41
+ function formatToRegexSource(format) {
42
+ let out = '';
43
+ let i = 0;
44
+ while (i < format.length) {
45
+ const prev = i > 0 ? format[i - 1] : undefined;
46
+ let matched = false;
47
+ for (const [token, regex] of TOKENS) {
48
+ if (!format.startsWith(token, i))
49
+ continue;
50
+ // `*` is punctuation — the letter-adjacency rule protects word-like tokens only.
51
+ if (token !== '*') {
52
+ const next = format[i + token.length];
53
+ if (isAsciiLetter(prev) || isAsciiLetter(next))
54
+ continue;
55
+ }
56
+ out += regex;
57
+ i += token.length;
58
+ matched = true;
59
+ break;
60
+ }
61
+ if (!matched) {
62
+ out += escapeLiteral(format[i]);
63
+ i += 1;
64
+ }
65
+ }
66
+ return `^${out}$`;
67
+ }
68
+ /** True when the string carries at least one token — i.e. it is a FORMAT, not a plain value. */
69
+ function hasFormatToken(format) {
70
+ return formatToRegexSource(format) !== `^${format.split('').map(escapeLiteral).join('')}$`;
71
+ }
72
+ /**
73
+ * The same pattern as a JS **regex literal**, ready to paste into generated code.
74
+ *
75
+ * Not a string: `new RegExp('^\d{4}$')` is a trap — in a single-quoted JS literal `\d` collapses
76
+ * to `d`, so the compiled test would silently match the letter instead of a digit. A regex literal
77
+ * carries the backslashes verbatim; only `/` needs escaping, and date formats are full of them.
78
+ */
79
+ function formatToRegexLiteral(format) {
80
+ return `/${formatToRegexSource(format).replace(/\//g, '\\/')}/`;
81
+ }
82
+ //# sourceMappingURL=format-pattern.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format-pattern.js","sourceRoot":"","sources":["../../../../src/generators/test-generator/utils/format-pattern.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;AAqBH,kDAwBC;AAGD,wCAEC;AASD,oDAEC;AA3DD,qFAAqF;AACrF,MAAM,MAAM,GAA4B;IACtC,CAAC,MAAM,EAAE,QAAQ,CAAC;IAClB,CAAC,IAAI,EAAE,QAAQ,CAAC;IAChB,CAAC,IAAI,EAAE,QAAQ,CAAC;IAChB,CAAC,IAAI,EAAE,QAAQ,CAAC;IAChB,CAAC,IAAI,EAAE,QAAQ,CAAC;IAChB,CAAC,GAAG,EAAE,kBAAkB,CAAC;IACzB,CAAC,GAAG,EAAE,WAAW,CAAC;CACnB,CAAC;AAEF,MAAM,aAAa,GAAG,CAAC,EAAsB,EAAW,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACvF,MAAM,aAAa,GAAG,CAAC,EAAU,EAAU,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;AAExF;;;;GAIG;AACH,SAAgB,mBAAmB,CAAC,MAAc;IAChD,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC/C,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,MAAM,EAAE,CAAC;YACpC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC,CAAC;gBAAE,SAAS;YAC3C,iFAAiF;YACjF,IAAI,KAAK,KAAK,GAAG,EAAE,CAAC;gBAClB,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;gBACtC,IAAI,aAAa,CAAC,IAAI,CAAC,IAAI,aAAa,CAAC,IAAI,CAAC;oBAAE,SAAS;YAC3D,CAAC;YACD,GAAG,IAAI,KAAK,CAAC;YACb,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC;YAClB,OAAO,GAAG,IAAI,CAAC;YACf,MAAM;QACR,CAAC;QACD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,GAAG,IAAI,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;YAChC,CAAC,IAAI,CAAC,CAAC;QACT,CAAC;IACH,CAAC;IACD,OAAO,IAAI,GAAG,GAAG,CAAC;AACpB,CAAC;AAED,gGAAgG;AAChG,SAAgB,cAAc,CAAC,MAAc;IAC3C,OAAO,mBAAmB,CAAC,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC;AAC7F,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,oBAAoB,CAAC,MAAc;IACjD,OAAO,IAAI,mBAAmB,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC;AAClE,CAAC"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Minimal left-to-right lexer that segments generated JS into the four contexts a
3
+ * post-processor must tell apart: string/template literals, regex literals,
4
+ * comments, and everything else.
5
+ *
6
+ * WHY this exists instead of a regex pass per context: independent global regexes
7
+ * have no idea where a literal actually STARTS. `String.replace` retries at every
8
+ * offset, so a pattern that wants "quote … marker … quote" happily pairs the
9
+ * CLOSING quote of one literal with the OPENING quote of the next and rewrites the
10
+ * code in between. Field report (3.2.23, every version 2.0.0+):
11
+ *
12
+ * await page.getByText('Tìm thấy').filter({ hasText: /^§marker§$/ }).waitFor({ state: 'visible' });
13
+ *
14
+ * The marker sits in a REGEX literal, but the string-literal pass matched from the
15
+ * `'` after `thấy` to the `'` before `visible` and emitted
16
+ * `getByText('Tìm thấy`)` … `state: `visible' })` — the whole .spec.ts stopped
17
+ * parsing. Scanning once, in order, makes that class of fault unrepresentable: a
18
+ * quote can only ever open a literal when the scanner is not already inside one.
19
+ *
20
+ * Deliberately NOT a full JS parser — it only needs to find literal boundaries:
21
+ * - `${…}` inside a template literal is body text, not nested code (matches what
22
+ * the previous passes did, and generated templates never nest a literal there).
23
+ * - A `'`/`"` literal must close on its own line; an unterminated one is treated
24
+ * as ordinary code so a stray apostrophe (prose in a comment) cannot desync the
25
+ * rest of the file. Backticks may span lines, but an unterminated one to EOF is
26
+ * likewise demoted to code.
27
+ * - A `/` opens a regex only in expression position and only when it closes on the
28
+ * same line. Both guards keep a division (or a stray slash) from swallowing the
29
+ * literals after it.
30
+ */
31
+ export type SegmentKind = 'code' | 'string' | 'regex' | 'comment';
32
+ export interface Segment {
33
+ kind: SegmentKind;
34
+ /** Verbatim source slice, INCLUDING delimiters (quotes, slashes, `//`). */
35
+ text: string;
36
+ }
37
+ /**
38
+ * Split `code` into ordered segments. Concatenating every `text` reproduces the
39
+ * input byte for byte — callers rewrite only the segments they care about.
40
+ */
41
+ export declare function scanSegments(code: string): Segment[];
42
+ //# sourceMappingURL=js-literal-scanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"js-literal-scanner.d.ts","sourceRoot":"","sources":["../../../../src/generators/test-generator/utils/js-literal-scanner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;AAElE,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,WAAW,CAAC;IAClB,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;CACd;AAyCD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAiDpD"}
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+ /**
3
+ * Minimal left-to-right lexer that segments generated JS into the four contexts a
4
+ * post-processor must tell apart: string/template literals, regex literals,
5
+ * comments, and everything else.
6
+ *
7
+ * WHY this exists instead of a regex pass per context: independent global regexes
8
+ * have no idea where a literal actually STARTS. `String.replace` retries at every
9
+ * offset, so a pattern that wants "quote … marker … quote" happily pairs the
10
+ * CLOSING quote of one literal with the OPENING quote of the next and rewrites the
11
+ * code in between. Field report (3.2.23, every version 2.0.0+):
12
+ *
13
+ * await page.getByText('Tìm thấy').filter({ hasText: /^§marker§$/ }).waitFor({ state: 'visible' });
14
+ *
15
+ * The marker sits in a REGEX literal, but the string-literal pass matched from the
16
+ * `'` after `thấy` to the `'` before `visible` and emitted
17
+ * `getByText('Tìm thấy`)` … `state: `visible' })` — the whole .spec.ts stopped
18
+ * parsing. Scanning once, in order, makes that class of fault unrepresentable: a
19
+ * quote can only ever open a literal when the scanner is not already inside one.
20
+ *
21
+ * Deliberately NOT a full JS parser — it only needs to find literal boundaries:
22
+ * - `${…}` inside a template literal is body text, not nested code (matches what
23
+ * the previous passes did, and generated templates never nest a literal there).
24
+ * - A `'`/`"` literal must close on its own line; an unterminated one is treated
25
+ * as ordinary code so a stray apostrophe (prose in a comment) cannot desync the
26
+ * rest of the file. Backticks may span lines, but an unterminated one to EOF is
27
+ * likewise demoted to code.
28
+ * - A `/` opens a regex only in expression position and only when it closes on the
29
+ * same line. Both guards keep a division (or a stray slash) from swallowing the
30
+ * literals after it.
31
+ */
32
+ Object.defineProperty(exports, "__esModule", { value: true });
33
+ exports.scanSegments = scanSegments;
34
+ /** Chars that, immediately before a `/`, make it division or a member access — never a regex. */
35
+ const NOT_REGEX_PREFIX = /[\w$)\]]/;
36
+ const REGEX_FLAGS = /[dgimsuvy]/;
37
+ /** Index just past the closing quote, or -1 when the literal never closes (see module note). */
38
+ function scanQuoted(code, start, quote) {
39
+ const spansLines = quote === '`';
40
+ for (let i = start + 1; i < code.length; i++) {
41
+ const c = code[i];
42
+ if (c === '\\') {
43
+ i++;
44
+ continue;
45
+ }
46
+ if (c === quote)
47
+ return i + 1;
48
+ if (c === '\n' && !spansLines)
49
+ return -1;
50
+ }
51
+ return -1;
52
+ }
53
+ /** Index just past the regex's flags, or -1 when it is not a single-line regex literal. */
54
+ function scanRegex(code, start) {
55
+ // Expression-position guard: skip back over spaces/tabs to the last significant char.
56
+ let p = start - 1;
57
+ while (p >= 0 && (code[p] === ' ' || code[p] === '\t'))
58
+ p--;
59
+ if (p >= 0 && NOT_REGEX_PREFIX.test(code[p]))
60
+ return -1;
61
+ let inClass = false;
62
+ for (let i = start + 1; i < code.length; i++) {
63
+ const c = code[i];
64
+ if (c === '\n')
65
+ return -1;
66
+ if (c === '\\') {
67
+ i++;
68
+ continue;
69
+ }
70
+ if (inClass) {
71
+ if (c === ']')
72
+ inClass = false;
73
+ continue;
74
+ }
75
+ if (c === '[') {
76
+ inClass = true;
77
+ continue;
78
+ }
79
+ if (c === '/') {
80
+ let j = i + 1;
81
+ while (j < code.length && REGEX_FLAGS.test(code[j]))
82
+ j++;
83
+ return j;
84
+ }
85
+ }
86
+ return -1;
87
+ }
88
+ /**
89
+ * Split `code` into ordered segments. Concatenating every `text` reproduces the
90
+ * input byte for byte — callers rewrite only the segments they care about.
91
+ */
92
+ function scanSegments(code) {
93
+ const out = [];
94
+ let codeStart = 0;
95
+ let i = 0;
96
+ const flushCode = (end) => {
97
+ if (end > codeStart)
98
+ out.push({ kind: 'code', text: code.slice(codeStart, end) });
99
+ };
100
+ const take = (kind, end) => {
101
+ flushCode(i);
102
+ out.push({ kind, text: code.slice(i, end) });
103
+ i = codeStart = end;
104
+ };
105
+ while (i < code.length) {
106
+ const c = code[i];
107
+ // Comments first: a `//` or `/*` reached in code position can never be a regex,
108
+ // and consuming them keeps an apostrophe in prose from opening a literal.
109
+ if (c === '/' && code[i + 1] === '/') {
110
+ const nl = code.indexOf('\n', i);
111
+ take('comment', nl === -1 ? code.length : nl);
112
+ continue;
113
+ }
114
+ if (c === '/' && code[i + 1] === '*') {
115
+ const close = code.indexOf('*/', i + 2);
116
+ take('comment', close === -1 ? code.length : close + 2);
117
+ continue;
118
+ }
119
+ if (c === '\'' || c === '"' || c === '`') {
120
+ const end = scanQuoted(code, i, c);
121
+ if (end !== -1) {
122
+ take('string', end);
123
+ continue;
124
+ }
125
+ i++; // unterminated → ordinary code
126
+ continue;
127
+ }
128
+ if (c === '/') {
129
+ const end = scanRegex(code, i);
130
+ if (end !== -1) {
131
+ take('regex', end);
132
+ continue;
133
+ }
134
+ i++; // division or stray slash → ordinary code
135
+ continue;
136
+ }
137
+ i++;
138
+ }
139
+ flushCode(code.length);
140
+ return out;
141
+ }
142
+ //# sourceMappingURL=js-literal-scanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"js-literal-scanner.js","sourceRoot":"","sources":["../../../../src/generators/test-generator/utils/js-literal-scanner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Mask the CONTENT of selector refs `[...]` and data placeholders `{{...}}` before verb matching,
3
+ * keeping the delimiters. A step's verb/keywords come from the SENTENCE — never from a selector or
4
+ * variable NAME — but matchers scan `step.text` with substring checks (e.g. `includes('click')`),
5
+ * so a data ref like `{{double_click_guard_account.email}}` would spuriously satisfy the click
6
+ * matcher and pre-empt fill. The content is replaced with an inert placeholder (`~` — carries no
7
+ * verb/keyword/digit substring) rather than emptied, so structural matchers that require a NON-EMPTY
8
+ * ref survive too — e.g. `expect {{a}} is {{b}}` matches `\{\{[^}]+\}\}`, and `in [X] table` /
9
+ * `/\bto\s+\[/` keep their delimiters. The resolver/generator always receives the ORIGINAL step, so
10
+ * value extraction and codegen are unaffected.
11
+ *
12
+ * Lives in `utils/` rather than in `patterns/index.ts` so the Gherkin parser can reuse it for
13
+ * state-clause extraction (SG-E021) without importing the pattern registry — which would pull the
14
+ * whole capability graph into the parser and close a require cycle. `patterns/index.ts` re-exports
15
+ * it, so its historical import path still resolves.
16
+ */
17
+ export declare function maskRefsForMatching(text: string): string;
18
+ //# sourceMappingURL=mask-refs-for-matching.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mask-refs-for-matching.d.ts","sourceRoot":"","sources":["../../../../src/generators/test-generator/utils/mask-refs-for-matching.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIxD"}
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.maskRefsForMatching = maskRefsForMatching;
4
+ /**
5
+ * Mask the CONTENT of selector refs `[...]` and data placeholders `{{...}}` before verb matching,
6
+ * keeping the delimiters. A step's verb/keywords come from the SENTENCE — never from a selector or
7
+ * variable NAME — but matchers scan `step.text` with substring checks (e.g. `includes('click')`),
8
+ * so a data ref like `{{double_click_guard_account.email}}` would spuriously satisfy the click
9
+ * matcher and pre-empt fill. The content is replaced with an inert placeholder (`~` — carries no
10
+ * verb/keyword/digit substring) rather than emptied, so structural matchers that require a NON-EMPTY
11
+ * ref survive too — e.g. `expect {{a}} is {{b}}` matches `\{\{[^}]+\}\}`, and `in [X] table` /
12
+ * `/\bto\s+\[/` keep their delimiters. The resolver/generator always receives the ORIGINAL step, so
13
+ * value extraction and codegen are unaffected.
14
+ *
15
+ * Lives in `utils/` rather than in `patterns/index.ts` so the Gherkin parser can reuse it for
16
+ * state-clause extraction (SG-E021) without importing the pattern registry — which would pull the
17
+ * whole capability graph into the parser and close a require cycle. `patterns/index.ts` re-exports
18
+ * it, so its historical import path still resolves.
19
+ */
20
+ function maskRefsForMatching(text) {
21
+ return text
22
+ .replace(/\{\{[^}]*\}\}/g, '{{~}}')
23
+ .replace(/\[[^\]]*\]/g, '[~]');
24
+ }
25
+ //# sourceMappingURL=mask-refs-for-matching.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mask-refs-for-matching.js","sourceRoot":"","sources":["../../../../src/generators/test-generator/utils/mask-refs-for-matching.ts"],"names":[],"mappings":";;AAgBA,kDAIC;AApBD;;;;;;;;;;;;;;;GAeG;AACH,SAAgB,mBAAmB,CAAC,IAAY;IAC9C,OAAO,IAAI;SACR,OAAO,CAAC,gBAAgB,EAAE,OAAO,CAAC;SAClC,OAAO,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC;AACnC,CAAC"}
@@ -1,6 +1,14 @@
1
1
  /**
2
2
  * Replace __SUNGEN_TD$…$__ markers with testData.get() calls in generated code.
3
- * Four passes: comments, string literals, regex literals, then bare identifiers.
3
+ *
4
+ * The code is scanned ONCE into literal/regex/comment/code segments and each
5
+ * segment is rewritten for the context it is actually in. This replaced four
6
+ * independent global-regex passes whose ordering was the defect: the string pass
7
+ * ran first and, finding no marker inside a genuine literal, re-matched from that
8
+ * literal's CLOSING quote — pairing it with the OPENING quote of the next literal
9
+ * and rewriting the regex literal between them into a broken backtick template
10
+ * (`getByText('Tìm thấy`)` … `state: `visible' })`, a .spec.ts that would not
11
+ * parse at all). See js-literal-scanner.ts for the full field report.
4
12
  */
5
13
  export declare function transformToRuntimeData(code: string, accessor?: string): string;
6
14
  export declare function hasRuntimeDataMarkers(code: string): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-data-transformer.d.ts","sourceRoot":"","sources":["../../../../src/generators/test-generator/utils/runtime-data-transformer.ts"],"names":[],"mappings":"AAoDA;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAmB,GAAG,MAAM,CAwD1F;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE3D"}
1
+ {"version":3,"file":"runtime-data-transformer.d.ts","sourceRoot":"","sources":["../../../../src/generators/test-generator/utils/runtime-data-transformer.ts"],"names":[],"mappings":"AA0CA;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAmB,GAAG,MAAM,CAG1F;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE3D"}
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.transformToRuntimeData = transformToRuntimeData;
4
4
  exports.hasRuntimeDataMarkers = hasRuntimeDataMarkers;
5
+ const js_literal_scanner_1 = require("./js-literal-scanner");
5
6
  /**
6
7
  * Wire format of a runtime test-data marker: `__SUNGEN_TD$<key>$__`.
7
8
  *
@@ -18,12 +19,12 @@ exports.hasRuntimeDataMarkers = hasRuntimeDataMarkers;
18
19
  * swallowed one `_` of the `___` run and decoding produced `get('code_')` plus
19
20
  * a detached `ACCESS_TOKEN`. `$` is also identifier-safe — the whole marker
20
21
  * still parses as ONE bare JS token, so it survives the bare-identifier
21
- * (numeric) positions Pass 3 handles.
22
+ * (numeric) positions the code context handles.
22
23
  *
23
- * Single source of truth: every pass and every whole-literal probe below is
24
- * built from these constants. Invariant: every marker producer goes through
25
- * DataResolver.encodeMarker and every consumer through this module — a
26
- * drifting hand-copied char class is what let the suffix bug hide.
24
+ * Single source of truth: every context below is built from these constants.
25
+ * Invariant: every marker producer goes through DataResolver.encodeMarker and
26
+ * every consumer through this module — a drifting hand-copied char class is what
27
+ * let the suffix bug hide.
27
28
  */
28
29
  const MARKER_KEY = '[A-Za-z0-9_-]+';
29
30
  // Templates that embed a value into a regex literal run it through the escapeRegex
@@ -31,82 +32,142 @@ const MARKER_KEY = '[A-Za-z0-9_-]+';
31
32
  // arrive here spelled `__SUNGEN_TD\$key\$__`. `\\\\?` accepts that optional backslash
32
33
  // before each delimiter; the replacement consumes it, so no escape leaks to output.
33
34
  const DELIM = '\\\\?\\$';
34
- /** Marker with no capture group — for "contains a marker" positions inside bigger regexes. */
35
+ /** Marker with no capture group — for "contains a marker" probes. */
35
36
  const MARKER = `__SUNGEN_TD${DELIM}${MARKER_KEY}${DELIM}__`;
36
37
  /** Marker capturing the encoded key. */
37
38
  const MARKER_CAP = `__SUNGEN_TD${DELIM}(${MARKER_KEY})${DELIM}__`;
38
39
  const MARKER_PATTERN = new RegExp(MARKER);
39
- // Pass 0: full-line // comments carrying a marker (anchored see pass comment).
40
- const RE_COMMENT_LINE = new RegExp(`^(\\s*)\\/\\/(.*)${MARKER_CAP}(.*)$`, 'gm');
41
- // Pass 1: a quoted string literal containing at least one marker. Covers ', " and
42
- // backtick containers; `\\.` lets an ESCAPED quote (`'it\'s {{x}}'`) sit inside the
43
- // body instead of terminating the literal early. The single-char branch excludes `\`
44
- // so every backslash is consumed by exactly ONE branch — an ambiguous `(?!\1).`
45
- // alternative enumerates 2^n segmentations when a marker-less escape-heavy literal
46
- // (escapeQuotes output: multi-line Flutter content-desc) fails to match, hanging
47
- // the generate run. `\n` exclusion just makes the dot's no-newline behavior explicit.
48
- const RE_STRING_LITERAL = new RegExp('([\'"`])((?:\\\\.|(?!\\1)[^\\\\\\n])*?' + MARKER + '(?:\\\\.|(?!\\1)[^\\\\\\n])*)\\1', 'g');
49
- // Pass 2: a single-line regex literal containing at least one marker.
50
- const RE_REGEX_LITERAL = new RegExp(`\\/((?:[^/\\\\\\n]|\\\\.)*?${MARKER}(?:[^/\\\\\\n]|\\\\.)*)\\/([gimsuy]*)`, 'g');
51
- // Whole-literal probe: the body is EXACTLY one marker (bare accessor call, no template).
40
+ /** Whole-literal probe: the body is EXACTLY one marker (bare accessor call, no template). */
52
41
  const RE_WHOLE_MARKER = new RegExp(`^${MARKER_CAP}$`);
53
- // Every marker occurrence, capturing the key (Pass 3 + interpolation).
42
+ /** Every marker occurrence, capturing the key. */
54
43
  const RE_MARKER_G = new RegExp(MARKER_CAP, 'g');
55
44
  /**
56
45
  * Replace __SUNGEN_TD$…$__ markers with testData.get() calls in generated code.
57
- * Four passes: comments, string literals, regex literals, then bare identifiers.
46
+ *
47
+ * The code is scanned ONCE into literal/regex/comment/code segments and each
48
+ * segment is rewritten for the context it is actually in. This replaced four
49
+ * independent global-regex passes whose ordering was the defect: the string pass
50
+ * ran first and, finding no marker inside a genuine literal, re-matched from that
51
+ * literal's CLOSING quote — pairing it with the OPENING quote of the next literal
52
+ * and rewriting the regex literal between them into a broken backtick template
53
+ * (`getByText('Tìm thấy`)` … `state: `visible' })`, a .spec.ts that would not
54
+ * parse at all). See js-literal-scanner.ts for the full field report.
58
55
  */
59
56
  function transformToRuntimeData(code, accessor = 'testData') {
60
- // Pass 0: Comments — replace markers in // comments with decoded key name.
61
- // Prevents Pass 2 from misinterpreting // comment markers as regex delimiters.
62
- // ANCHORED to line start (^\s*//) so a `//` INSIDE a string literal on a code line
63
- // — e.g. a URL `https://careerforum.aki-inc.jp/` in a selector name — is NOT mistaken
64
- // for a comment. Before this anchor, such a URL preceding a marker on the same line
65
- // made Pass 0 strip the marker to its bare key, so Pass 1 never wrapped it in
66
- // testData.get() (silent: `toHaveAttribute('href', 'official_site_url')`). Generated
67
- // comments are always full-line, so anchoring loses nothing.
68
- code = code.replace(RE_COMMENT_LINE, (_, indent, before, enc, after) => `${indent}//${before}${decodeKey(enc)}${after}`);
69
- // Pass 1: String literal context — a literal may carry SEVERAL markers
70
- // ('/{{code}}/car-search?{{prefill}}' compiles to two markers in ONE literal).
71
- // Match the whole quoted literal that contains at least one marker, then
72
- // interpolate EVERY marker inside it. The old shape (one marker + lazy
73
- // prefix/suffix) swallowed trailing markers into its suffix verbatim; they
74
- // escaped the quote context (the literal had become a backtick template) and
75
- // Pass 3's numeric catch-all emitted them as literal
76
- // `Number(testData.get(...))` TEXT inside the template — a compiling spec
77
- // navigating to a garbage URL.
78
- // 'marker' → testData.get('key')
79
- // 'a__m1__b__m2__c' → `a${testData.get('k1')}b${testData.get('k2')}c`
80
- code = code.replace(RE_STRING_LITERAL, (_, _quote, body) => {
81
- const whole = RE_WHOLE_MARKER.exec(body);
82
- if (whole)
83
- return `${accessor}.get('${decodeKey(whole[1])}')`;
84
- return `\`${interpolateMarkers(body, accessor)}\``;
85
- });
86
- // Pass 2: Regex literal context — /…$marker$…/ → new RegExp(`…`). Same
87
- // several-markers-per-literal rule as Pass 1 (/{{min}}-{{max}} items/).
88
- // Only matches within a single line (no newlines in the body).
89
- code = code.replace(RE_REGEX_LITERAL, (_, body, flags) => {
90
- const flagStr = flags ? `, '${flags}'` : '';
91
- const whole = RE_WHOLE_MARKER.exec(body);
92
- if (whole)
93
- return `new RegExp(${accessor}.get('${decodeKey(whole[1])}')${flagStr})`;
94
- return `new RegExp(\`${interpolateMarkers(body, accessor)}\`${flagStr})`;
95
- });
96
- // Pass 3: Bare-identifier context — any marker left after the string/regex passes
97
- // sits as a bare token in a numeric position (e.g. `toHaveCount(__marker__)` from the
98
- // table/list count templates). testData.get() returns a string, so coerce with Number().
99
- code = code.replace(RE_MARKER_G, (_, enc) => `Number(${accessor}.get('${decodeKey(enc)}'))`);
100
- return code;
57
+ if (!MARKER_PATTERN.test(code))
58
+ return code;
59
+ return (0, js_literal_scanner_1.scanSegments)(code).map((seg) => transformSegment(seg, accessor)).join('');
101
60
  }
102
61
  function hasRuntimeDataMarkers(code) {
103
62
  return MARKER_PATTERN.test(code);
104
63
  }
64
+ function transformSegment(seg, accessor) {
65
+ if (!MARKER_PATTERN.test(seg.text))
66
+ return seg.text;
67
+ switch (seg.kind) {
68
+ // Decode to the plain key: a marker in a comment documents the ref, it is not code.
69
+ case 'comment':
70
+ return seg.text.replace(RE_MARKER_G, (_, enc) => decodeKey(enc));
71
+ case 'string':
72
+ return transformStringLiteral(seg.text, accessor);
73
+ case 'regex':
74
+ return transformRegexLiteral(seg.text, accessor);
75
+ // Bare-token position (e.g. `toHaveCount(__marker__)` from the table/list count
76
+ // templates). testData.get() returns a string, so coerce with Number().
77
+ case 'code':
78
+ return seg.text.replace(RE_MARKER_G, (_, enc) => `Number(${accessor}.get('${decodeKey(enc)}'))`);
79
+ }
80
+ }
81
+ /**
82
+ * 'marker' → testData.get('key')
83
+ * 'a__m1__b__m2__c' → `a${testData.get('k1')}b${testData.get('k2')}c`
84
+ *
85
+ * A literal may carry SEVERAL markers ('/{{code}}/car-search?{{prefill}}' compiles
86
+ * to two markers in ONE literal), so every marker inside it is interpolated.
87
+ */
88
+ function transformStringLiteral(text, accessor) {
89
+ const quote = text[0];
90
+ const body = text.slice(1, -1);
91
+ const whole = RE_WHOLE_MARKER.exec(body);
92
+ if (whole)
93
+ return `${accessor}.get('${decodeKey(whole[1])}')`;
94
+ return `\`${interpolateMarkers(escapeForTemplate(body, quote === '`'), accessor)}\``;
95
+ }
96
+ /**
97
+ * /…$marker$…/flags → new RegExp(`…`, 'flags'). Same several-markers rule as above.
98
+ *
99
+ * Two regex-only twists on top of the string path:
100
+ * - The runtime VALUE is literal text (test-data holds data, not patterns), so every
101
+ * accessor is wrapped in the runtime escapeRegExp helper — a value containing
102
+ * `( [ $ . * +` must not throw a SyntaxError or silently widen the match. The helper
103
+ * lives in the synced specs/regex-escape.ts; the generator imports it when the
104
+ * transformed body references it. Templates that intend PATTERN semantics (attribute
105
+ * `pattern:`) emit a string container instead of a regex literal, so they skip this.
106
+ * - Marker-less SEGMENTS get their backslashes doubled for the template context: regex
107
+ * source text like a hand-written `\s*` (or escapeRegex output `\)`) means one thing
108
+ * in a /…/ literal but loses its backslash when the template cooks one escape level —
109
+ * the constructed RegExp would read `s*` / a bare `)`. Segments are escaped AFTER the
110
+ * markers are cut out, so an escapeRegex-spelled marker delimiter (`\$`) is never
111
+ * double-escaped into something the marker regex can no longer see.
112
+ */
113
+ function transformRegexLiteral(text, accessor) {
114
+ // Flags never contain `/`, so the last slash always closes the pattern.
115
+ const close = text.lastIndexOf('/');
116
+ const body = text.slice(1, close);
117
+ const flags = text.slice(close + 1);
118
+ const flagStr = flags ? `, '${flags}'` : '';
119
+ const whole = RE_WHOLE_MARKER.exec(body);
120
+ if (whole)
121
+ return `new RegExp(escapeRegExp(${accessor}.get('${decodeKey(whole[1])}'))${flagStr})`;
122
+ return `new RegExp(\`${interpolateMarkersInRegexBody(body, accessor)}\`${flagStr})`;
123
+ }
105
124
  function decodeKey(encoded) {
106
125
  return encoded.replace(/_D_/g, '.');
107
126
  }
108
- /** Replace every marker in a literal's body with a `${accessor.get('key')}` interpolation. */
127
+ /** Replace every marker in a body with a `${accessor.get('key')}` interpolation. */
109
128
  function interpolateMarkers(body, accessor) {
110
129
  return body.replace(RE_MARKER_G, (_, enc) => `\${${accessor}.get('${decodeKey(enc)}')}`);
111
130
  }
131
+ /**
132
+ * Regex-body variant: markers are cut out FIRST, each accessor is wrapped in the
133
+ * runtime escapeRegExp helper, and only the marker-less segments between them are
134
+ * re-escaped for the template context (backslashes doubled, backtick/`${` neutralized).
135
+ */
136
+ function interpolateMarkersInRegexBody(body, accessor) {
137
+ let out = '';
138
+ let last = 0;
139
+ for (const m of body.matchAll(RE_MARKER_G)) {
140
+ out += escapeRegexSegmentForTemplate(body.slice(last, m.index)) +
141
+ `\${escapeRegExp(${accessor}.get('${decodeKey(m[1])}'))}`;
142
+ last = m.index + m[0].length;
143
+ }
144
+ return out + escapeRegexSegmentForTemplate(body.slice(last));
145
+ }
146
+ /**
147
+ * Regex pattern text moving into a template literal: the template layer cooks one
148
+ * level of escapes BEFORE RegExp ever sees the string, so `\)` arrives as a bare `)`
149
+ * (SyntaxError: unmatched paren) and `\.` as a match-anything dot. Double the
150
+ * backslashes and neutralize the template's own metacharacters so the constructed
151
+ * RegExp source equals the original pattern text.
152
+ */
153
+ const escapeRegexSegmentForTemplate = (segment) => segment.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
154
+ /**
155
+ * Both rewrites move a body into a backtick template, where a raw backtick or `${`
156
+ * would be read as syntax rather than text — a `'…'` selector value or a regex
157
+ * pattern is allowed to contain either (escapeQuotes/escapeRegex escape neither).
158
+ * Escape before interpolating so the `${…}` this module inserts is the only live one.
159
+ * An already-backtick container needs no pass: its body was valid template text.
160
+ *
161
+ * PRECONDITION: the body must already have its backslashes DOUBLED — which every caller
162
+ * satisfies, because every dynamic value reaches a literal through escapeQuotes or
163
+ * escapeRegex (template-engine.ts) and both double `\` before anything else. An unpaired
164
+ * backslash immediately before a backtick would make the pass below emit `\\\``, i.e. an
165
+ * escaped backslash followed by a LIVE backtick, ending the template early. If you
166
+ * hand-write a backslash into a .hbs template, keep it paired.
167
+ */
168
+ function escapeForTemplate(body, alreadyTemplate) {
169
+ if (alreadyTemplate)
170
+ return body;
171
+ return body.replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
172
+ }
112
173
  //# sourceMappingURL=runtime-data-transformer.js.map