@sun-asterisk/sungen 1.0.24 → 2.0.0

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 (362) hide show
  1. package/README.md +198 -74
  2. package/dist/cli/commands/add.d.ts.map +1 -1
  3. package/dist/cli/commands/add.js +5 -3
  4. package/dist/cli/commands/add.js.map +1 -1
  5. package/dist/cli/commands/generate.d.ts.map +1 -1
  6. package/dist/cli/commands/generate.js +110 -35
  7. package/dist/cli/commands/generate.js.map +1 -1
  8. package/dist/cli/index.d.ts +2 -2
  9. package/dist/cli/index.js +5 -16
  10. package/dist/cli/index.js.map +1 -1
  11. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/frame-enter-action.hbs +1 -0
  12. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/frame-exit-action.hbs +1 -0
  13. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/keyboard-global-action.hbs +1 -0
  14. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/scroll-action.hbs +1 -0
  15. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +2 -0
  16. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-cell-by-filter.hbs +3 -0
  17. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-cell-by-index.hbs +3 -0
  18. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-column-exists.hbs +2 -0
  19. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-empty.hbs +2 -0
  20. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-count.hbs +2 -0
  21. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-exists.hbs +2 -0
  22. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-not-exists.hbs +2 -0
  23. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-value-assertion.hbs +1 -1
  24. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +11 -2
  25. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +11 -2
  26. package/dist/generators/test-generator/code-generator.d.ts +0 -1
  27. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  28. package/dist/generators/test-generator/code-generator.js +0 -47
  29. package/dist/generators/test-generator/code-generator.js.map +1 -1
  30. package/dist/generators/test-generator/patterns/assertion-patterns.d.ts.map +1 -1
  31. package/dist/generators/test-generator/patterns/assertion-patterns.js +2 -0
  32. package/dist/generators/test-generator/patterns/assertion-patterns.js.map +1 -1
  33. package/dist/generators/test-generator/patterns/index.d.ts +4 -1
  34. package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
  35. package/dist/generators/test-generator/patterns/index.js +17 -5
  36. package/dist/generators/test-generator/patterns/index.js.map +1 -1
  37. package/dist/generators/test-generator/patterns/interaction-patterns.js +1 -1
  38. package/dist/generators/test-generator/patterns/interaction-patterns.js.map +1 -1
  39. package/dist/generators/test-generator/patterns/keyboard-patterns.d.ts +7 -0
  40. package/dist/generators/test-generator/patterns/keyboard-patterns.d.ts.map +1 -0
  41. package/dist/generators/test-generator/patterns/keyboard-patterns.js +47 -0
  42. package/dist/generators/test-generator/patterns/keyboard-patterns.js.map +1 -0
  43. package/dist/generators/test-generator/patterns/scope-patterns.d.ts +7 -0
  44. package/dist/generators/test-generator/patterns/scope-patterns.d.ts.map +1 -0
  45. package/dist/generators/test-generator/patterns/scope-patterns.js +36 -0
  46. package/dist/generators/test-generator/patterns/scope-patterns.js.map +1 -0
  47. package/dist/generators/test-generator/patterns/scroll-patterns.d.ts +7 -0
  48. package/dist/generators/test-generator/patterns/scroll-patterns.d.ts.map +1 -0
  49. package/dist/generators/test-generator/patterns/scroll-patterns.js +25 -0
  50. package/dist/generators/test-generator/patterns/scroll-patterns.js.map +1 -0
  51. package/dist/generators/test-generator/patterns/table-patterns.d.ts +7 -0
  52. package/dist/generators/test-generator/patterns/table-patterns.d.ts.map +1 -0
  53. package/dist/generators/test-generator/patterns/table-patterns.js +192 -0
  54. package/dist/generators/test-generator/patterns/table-patterns.js.map +1 -0
  55. package/dist/generators/test-generator/step-mapper.d.ts +1 -3
  56. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  57. package/dist/generators/test-generator/step-mapper.js +30 -27
  58. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  59. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  60. package/dist/generators/test-generator/template-engine.js +4 -1
  61. package/dist/generators/test-generator/template-engine.js.map +1 -1
  62. package/dist/generators/test-generator/types.d.ts +7 -24
  63. package/dist/generators/test-generator/types.d.ts.map +1 -1
  64. package/dist/generators/test-generator/types.js +2 -101
  65. package/dist/generators/test-generator/types.js.map +1 -1
  66. package/dist/generators/test-generator/utils/selector-resolver.d.ts +14 -0
  67. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
  68. package/dist/generators/test-generator/utils/selector-resolver.js +37 -11
  69. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
  70. package/dist/orchestrator/project-initializer.d.ts +8 -0
  71. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  72. package/dist/orchestrator/project-initializer.js +343 -32
  73. package/dist/orchestrator/project-initializer.js.map +1 -1
  74. package/dist/orchestrator/screen-manager.d.ts +1 -32
  75. package/dist/orchestrator/screen-manager.d.ts.map +1 -1
  76. package/dist/orchestrator/screen-manager.js +55 -216
  77. package/dist/orchestrator/screen-manager.js.map +1 -1
  78. package/dist/utils/selector-types.d.ts +1 -1
  79. package/dist/utils/selector-types.d.ts.map +1 -1
  80. package/dist/utils/selector-types.js +3 -0
  81. package/dist/utils/selector-types.js.map +1 -1
  82. package/package.json +2 -2
  83. package/src/cli/commands/add.ts +5 -3
  84. package/src/cli/commands/generate.ts +90 -38
  85. package/src/cli/index.ts +5 -16
  86. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/frame-enter-action.hbs +1 -0
  87. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/frame-exit-action.hbs +1 -0
  88. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/keyboard-global-action.hbs +1 -0
  89. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/scroll-action.hbs +1 -0
  90. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +2 -0
  91. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-cell-by-filter.hbs +3 -0
  92. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-cell-by-index.hbs +3 -0
  93. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-column-exists.hbs +2 -0
  94. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-empty.hbs +2 -0
  95. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-count.hbs +2 -0
  96. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-exists.hbs +2 -0
  97. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-not-exists.hbs +2 -0
  98. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-value-assertion.hbs +1 -1
  99. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +11 -2
  100. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +11 -2
  101. package/src/generators/test-generator/code-generator.ts +0 -59
  102. package/src/generators/test-generator/patterns/assertion-patterns.ts +2 -0
  103. package/src/generators/test-generator/patterns/index.ts +12 -3
  104. package/src/generators/test-generator/patterns/interaction-patterns.ts +1 -1
  105. package/src/generators/test-generator/patterns/keyboard-patterns.ts +51 -0
  106. package/src/generators/test-generator/patterns/scope-patterns.ts +40 -0
  107. package/src/generators/test-generator/patterns/scroll-patterns.ts +27 -0
  108. package/src/generators/test-generator/patterns/table-patterns.ts +232 -0
  109. package/src/generators/test-generator/step-mapper.ts +33 -27
  110. package/src/generators/test-generator/template-engine.ts +3 -1
  111. package/src/generators/test-generator/types.ts +7 -112
  112. package/src/generators/test-generator/utils/selector-resolver.ts +70 -25
  113. package/src/orchestrator/project-initializer.ts +345 -32
  114. package/src/orchestrator/screen-manager.ts +61 -233
  115. package/src/utils/selector-types.ts +3 -0
  116. package/dist/cli/commands/cache-clear.d.ts +0 -3
  117. package/dist/cli/commands/cache-clear.d.ts.map +0 -1
  118. package/dist/cli/commands/cache-clear.js +0 -24
  119. package/dist/cli/commands/cache-clear.js.map +0 -1
  120. package/dist/cli/commands/full.d.ts +0 -3
  121. package/dist/cli/commands/full.d.ts.map +0 -1
  122. package/dist/cli/commands/full.js +0 -37
  123. package/dist/cli/commands/full.js.map +0 -1
  124. package/dist/cli/commands/live-scan.d.ts +0 -3
  125. package/dist/cli/commands/live-scan.d.ts.map +0 -1
  126. package/dist/cli/commands/live-scan.js +0 -78
  127. package/dist/cli/commands/live-scan.js.map +0 -1
  128. package/dist/cli/commands/map.d.ts +0 -3
  129. package/dist/cli/commands/map.d.ts.map +0 -1
  130. package/dist/cli/commands/map.js +0 -93
  131. package/dist/cli/commands/map.js.map +0 -1
  132. package/dist/cli/commands/validate.d.ts +0 -3
  133. package/dist/cli/commands/validate.d.ts.map +0 -1
  134. package/dist/cli/commands/validate.js +0 -43
  135. package/dist/cli/commands/validate.js.map +0 -1
  136. package/dist/cli/utils.d.ts +0 -6
  137. package/dist/cli/utils.d.ts.map +0 -1
  138. package/dist/cli/utils.js +0 -101
  139. package/dist/cli/utils.js.map +0 -1
  140. package/dist/config/config-loader.d.ts +0 -51
  141. package/dist/config/config-loader.d.ts.map +0 -1
  142. package/dist/config/config-loader.js +0 -216
  143. package/dist/config/config-loader.js.map +0 -1
  144. package/dist/config/config-schema.d.ts +0 -121
  145. package/dist/config/config-schema.d.ts.map +0 -1
  146. package/dist/config/config-schema.js +0 -7
  147. package/dist/config/config-schema.js.map +0 -1
  148. package/dist/core/live-scanner/config-reader.d.ts +0 -10
  149. package/dist/core/live-scanner/config-reader.d.ts.map +0 -1
  150. package/dist/core/live-scanner/config-reader.js +0 -87
  151. package/dist/core/live-scanner/config-reader.js.map +0 -1
  152. package/dist/core/live-scanner/element-finder.d.ts +0 -20
  153. package/dist/core/live-scanner/element-finder.d.ts.map +0 -1
  154. package/dist/core/live-scanner/element-finder.js +0 -481
  155. package/dist/core/live-scanner/element-finder.js.map +0 -1
  156. package/dist/core/live-scanner/index.d.ts +0 -8
  157. package/dist/core/live-scanner/index.d.ts.map +0 -1
  158. package/dist/core/live-scanner/index.js +0 -33
  159. package/dist/core/live-scanner/index.js.map +0 -1
  160. package/dist/core/live-scanner/matrix-reader.d.ts +0 -17
  161. package/dist/core/live-scanner/matrix-reader.d.ts.map +0 -1
  162. package/dist/core/live-scanner/matrix-reader.js +0 -60
  163. package/dist/core/live-scanner/matrix-reader.js.map +0 -1
  164. package/dist/core/live-scanner/matrix-writer.d.ts +0 -7
  165. package/dist/core/live-scanner/matrix-writer.d.ts.map +0 -1
  166. package/dist/core/live-scanner/matrix-writer.js +0 -103
  167. package/dist/core/live-scanner/matrix-writer.js.map +0 -1
  168. package/dist/core/live-scanner/role-fallback.d.ts +0 -15
  169. package/dist/core/live-scanner/role-fallback.d.ts.map +0 -1
  170. package/dist/core/live-scanner/role-fallback.js +0 -46
  171. package/dist/core/live-scanner/role-fallback.js.map +0 -1
  172. package/dist/core/live-scanner/scanner.d.ts +0 -22
  173. package/dist/core/live-scanner/scanner.d.ts.map +0 -1
  174. package/dist/core/live-scanner/scanner.js +0 -303
  175. package/dist/core/live-scanner/scanner.js.map +0 -1
  176. package/dist/core/live-scanner/step-replayer.d.ts +0 -26
  177. package/dist/core/live-scanner/step-replayer.d.ts.map +0 -1
  178. package/dist/core/live-scanner/step-replayer.js +0 -473
  179. package/dist/core/live-scanner/step-replayer.js.map +0 -1
  180. package/dist/core/live-scanner/types.d.ts +0 -52
  181. package/dist/core/live-scanner/types.d.ts.map +0 -1
  182. package/dist/core/live-scanner/types.js +0 -14
  183. package/dist/core/live-scanner/types.js.map +0 -1
  184. package/dist/core/selector-base/annotation-handler.d.ts +0 -45
  185. package/dist/core/selector-base/annotation-handler.d.ts.map +0 -1
  186. package/dist/core/selector-base/annotation-handler.js +0 -102
  187. package/dist/core/selector-base/annotation-handler.js.map +0 -1
  188. package/dist/core/selector-base/base-generator.d.ts +0 -49
  189. package/dist/core/selector-base/base-generator.d.ts.map +0 -1
  190. package/dist/core/selector-base/base-generator.js +0 -214
  191. package/dist/core/selector-base/base-generator.js.map +0 -1
  192. package/dist/core/selector-base/gherkin-parser.d.ts +0 -24
  193. package/dist/core/selector-base/gherkin-parser.d.ts.map +0 -1
  194. package/dist/core/selector-base/gherkin-parser.js +0 -42
  195. package/dist/core/selector-base/gherkin-parser.js.map +0 -1
  196. package/dist/core/selector-mapper/priority-mapper.d.ts +0 -74
  197. package/dist/core/selector-mapper/priority-mapper.d.ts.map +0 -1
  198. package/dist/core/selector-mapper/priority-mapper.js +0 -477
  199. package/dist/core/selector-mapper/priority-mapper.js.map +0 -1
  200. package/dist/core/ui-scanner/heuristics/base-heuristic.d.ts +0 -91
  201. package/dist/core/ui-scanner/heuristics/base-heuristic.d.ts.map +0 -1
  202. package/dist/core/ui-scanner/heuristics/base-heuristic.js +0 -175
  203. package/dist/core/ui-scanner/heuristics/base-heuristic.js.map +0 -1
  204. package/dist/core/ui-scanner/react-scanner.d.ts +0 -32
  205. package/dist/core/ui-scanner/react-scanner.d.ts.map +0 -1
  206. package/dist/core/ui-scanner/react-scanner.js +0 -163
  207. package/dist/core/ui-scanner/react-scanner.js.map +0 -1
  208. package/dist/core/ui-scanner/scanner-interface.d.ts +0 -94
  209. package/dist/core/ui-scanner/scanner-interface.d.ts.map +0 -1
  210. package/dist/core/ui-scanner/scanner-interface.js +0 -33
  211. package/dist/core/ui-scanner/scanner-interface.js.map +0 -1
  212. package/dist/core/ui-scanner/strict-scanner.d.ts +0 -81
  213. package/dist/core/ui-scanner/strict-scanner.d.ts.map +0 -1
  214. package/dist/core/ui-scanner/strict-scanner.js +0 -511
  215. package/dist/core/ui-scanner/strict-scanner.js.map +0 -1
  216. package/dist/core/validator/data-validator.d.ts +0 -38
  217. package/dist/core/validator/data-validator.d.ts.map +0 -1
  218. package/dist/core/validator/data-validator.js +0 -212
  219. package/dist/core/validator/data-validator.js.map +0 -1
  220. package/dist/core/validator/feature-validator.d.ts +0 -27
  221. package/dist/core/validator/feature-validator.d.ts.map +0 -1
  222. package/dist/core/validator/feature-validator.js +0 -182
  223. package/dist/core/validator/feature-validator.js.map +0 -1
  224. package/dist/core/validator/index.d.ts +0 -46
  225. package/dist/core/validator/index.d.ts.map +0 -1
  226. package/dist/core/validator/index.js +0 -17
  227. package/dist/core/validator/index.js.map +0 -1
  228. package/dist/core/validator/screen-validator.d.ts +0 -35
  229. package/dist/core/validator/screen-validator.d.ts.map +0 -1
  230. package/dist/core/validator/screen-validator.js +0 -195
  231. package/dist/core/validator/screen-validator.js.map +0 -1
  232. package/dist/core/validator/selector-validator.d.ts +0 -36
  233. package/dist/core/validator/selector-validator.d.ts.map +0 -1
  234. package/dist/core/validator/selector-validator.js +0 -210
  235. package/dist/core/validator/selector-validator.js.map +0 -1
  236. package/dist/external/ai-provider.d.ts +0 -60
  237. package/dist/external/ai-provider.d.ts.map +0 -1
  238. package/dist/external/ai-provider.js +0 -30
  239. package/dist/external/ai-provider.js.map +0 -1
  240. package/dist/external/anthropic-provider.d.ts +0 -29
  241. package/dist/external/anthropic-provider.d.ts.map +0 -1
  242. package/dist/external/anthropic-provider.js +0 -85
  243. package/dist/external/anthropic-provider.js.map +0 -1
  244. package/dist/generators/cache/cache-manager.d.ts +0 -66
  245. package/dist/generators/cache/cache-manager.d.ts.map +0 -1
  246. package/dist/generators/cache/cache-manager.js +0 -286
  247. package/dist/generators/cache/cache-manager.js.map +0 -1
  248. package/dist/generators/dsl-writer/index.d.ts +0 -33
  249. package/dist/generators/dsl-writer/index.d.ts.map +0 -1
  250. package/dist/generators/dsl-writer/index.js +0 -226
  251. package/dist/generators/dsl-writer/index.js.map +0 -1
  252. package/dist/generators/scaffold-generator/index.d.ts +0 -162
  253. package/dist/generators/scaffold-generator/index.d.ts.map +0 -1
  254. package/dist/generators/scaffold-generator/index.js +0 -877
  255. package/dist/generators/scaffold-generator/index.js.map +0 -1
  256. package/dist/generators/selector-mapper/ai-mapper.d.ts +0 -56
  257. package/dist/generators/selector-mapper/ai-mapper.d.ts.map +0 -1
  258. package/dist/generators/selector-mapper/ai-mapper.js +0 -457
  259. package/dist/generators/selector-mapper/ai-mapper.js.map +0 -1
  260. package/dist/generators/selector-mapper/hybrid-mapper.d.ts +0 -67
  261. package/dist/generators/selector-mapper/hybrid-mapper.d.ts.map +0 -1
  262. package/dist/generators/selector-mapper/hybrid-mapper.js +0 -349
  263. package/dist/generators/selector-mapper/hybrid-mapper.js.map +0 -1
  264. package/dist/generators/selector-mapper/index.d.ts +0 -8
  265. package/dist/generators/selector-mapper/index.d.ts.map +0 -1
  266. package/dist/generators/selector-mapper/index.js +0 -12
  267. package/dist/generators/selector-mapper/index.js.map +0 -1
  268. package/dist/generators/selector-mapper/intelligent-mapper.d.ts +0 -125
  269. package/dist/generators/selector-mapper/intelligent-mapper.d.ts.map +0 -1
  270. package/dist/generators/selector-mapper/intelligent-mapper.js +0 -391
  271. package/dist/generators/selector-mapper/intelligent-mapper.js.map +0 -1
  272. package/dist/generators/test-generator/ai-step-mapper.d.ts +0 -27
  273. package/dist/generators/test-generator/ai-step-mapper.d.ts.map +0 -1
  274. package/dist/generators/test-generator/ai-step-mapper.js +0 -204
  275. package/dist/generators/test-generator/ai-step-mapper.js.map +0 -1
  276. package/dist/generators/test-generator/auth-setup-generator.d.ts +0 -18
  277. package/dist/generators/test-generator/auth-setup-generator.d.ts.map +0 -1
  278. package/dist/generators/test-generator/auth-setup-generator.js +0 -82
  279. package/dist/generators/test-generator/auth-setup-generator.js.map +0 -1
  280. package/dist/generators/test-generator/patterns/legacy-patterns.d.ts +0 -7
  281. package/dist/generators/test-generator/patterns/legacy-patterns.d.ts.map +0 -1
  282. package/dist/generators/test-generator/patterns/legacy-patterns.js +0 -98
  283. package/dist/generators/test-generator/patterns/legacy-patterns.js.map +0 -1
  284. package/dist/generators/test-generator/templates/auth-setup.ts.hbs +0 -36
  285. package/dist/generators/ui-model-builder/deep-scanner.d.ts +0 -121
  286. package/dist/generators/ui-model-builder/deep-scanner.d.ts.map +0 -1
  287. package/dist/generators/ui-model-builder/deep-scanner.js +0 -1113
  288. package/dist/generators/ui-model-builder/deep-scanner.js.map +0 -1
  289. package/dist/generators/ui-model-builder/enhanced-deep-scanner.d.ts +0 -110
  290. package/dist/generators/ui-model-builder/enhanced-deep-scanner.d.ts.map +0 -1
  291. package/dist/generators/ui-model-builder/enhanced-deep-scanner.js +0 -608
  292. package/dist/generators/ui-model-builder/enhanced-deep-scanner.js.map +0 -1
  293. package/dist/generators/ui-model-builder/react-scanner.d.ts +0 -107
  294. package/dist/generators/ui-model-builder/react-scanner.d.ts.map +0 -1
  295. package/dist/generators/ui-model-builder/react-scanner.js +0 -797
  296. package/dist/generators/ui-model-builder/react-scanner.js.map +0 -1
  297. package/dist/orchestrator/cache-manager.d.ts +0 -15
  298. package/dist/orchestrator/cache-manager.d.ts.map +0 -1
  299. package/dist/orchestrator/cache-manager.js +0 -62
  300. package/dist/orchestrator/cache-manager.js.map +0 -1
  301. package/dist/orchestrator/pipeline.d.ts +0 -56
  302. package/dist/orchestrator/pipeline.d.ts.map +0 -1
  303. package/dist/orchestrator/pipeline.js +0 -298
  304. package/dist/orchestrator/pipeline.js.map +0 -1
  305. package/dist/orchestrator/reporter.d.ts +0 -15
  306. package/dist/orchestrator/reporter.d.ts.map +0 -1
  307. package/dist/orchestrator/reporter.js +0 -30
  308. package/dist/orchestrator/reporter.js.map +0 -1
  309. package/src/cli/commands/cache-clear.ts +0 -22
  310. package/src/cli/commands/full.ts +0 -35
  311. package/src/cli/commands/live-scan.ts +0 -82
  312. package/src/cli/commands/map.ts +0 -97
  313. package/src/cli/commands/validate.ts +0 -43
  314. package/src/cli/utils.ts +0 -106
  315. package/src/config/ai-providers.yaml +0 -56
  316. package/src/config/config-loader.ts +0 -248
  317. package/src/config/config-schema.ts +0 -148
  318. package/src/config/default.config.yaml +0 -107
  319. package/src/config/framework.config.yaml +0 -52
  320. package/src/config/routes.yaml +0 -31
  321. package/src/core/live-scanner/config-reader.ts +0 -57
  322. package/src/core/live-scanner/element-finder.ts +0 -534
  323. package/src/core/live-scanner/index.ts +0 -7
  324. package/src/core/live-scanner/matrix-reader.ts +0 -65
  325. package/src/core/live-scanner/matrix-writer.ts +0 -77
  326. package/src/core/live-scanner/role-fallback.ts +0 -44
  327. package/src/core/live-scanner/scanner.ts +0 -321
  328. package/src/core/live-scanner/step-replayer.ts +0 -503
  329. package/src/core/live-scanner/types.ts +0 -58
  330. package/src/core/selector-base/annotation-handler.ts +0 -127
  331. package/src/core/selector-base/base-generator.ts +0 -234
  332. package/src/core/selector-base/gherkin-parser.ts +0 -57
  333. package/src/core/selector-mapper/priority-mapper.ts +0 -607
  334. package/src/core/ui-scanner/heuristics/base-heuristic.ts +0 -216
  335. package/src/core/ui-scanner/react-scanner.ts +0 -156
  336. package/src/core/ui-scanner/scanner-interface.ts +0 -133
  337. package/src/core/ui-scanner/strict-scanner.ts +0 -629
  338. package/src/core/validator/data-validator.ts +0 -202
  339. package/src/core/validator/feature-validator.ts +0 -176
  340. package/src/core/validator/index.ts +0 -57
  341. package/src/core/validator/screen-validator.ts +0 -209
  342. package/src/core/validator/selector-validator.ts +0 -209
  343. package/src/external/ai-provider.ts +0 -90
  344. package/src/external/anthropic-provider.ts +0 -114
  345. package/src/generators/README.md +0 -410
  346. package/src/generators/cache/cache-manager.ts +0 -322
  347. package/src/generators/dsl-writer/index.ts +0 -253
  348. package/src/generators/scaffold-generator/index.ts +0 -1029
  349. package/src/generators/selector-mapper/ai-mapper.ts +0 -528
  350. package/src/generators/selector-mapper/hybrid-mapper.ts +0 -427
  351. package/src/generators/selector-mapper/index.ts +0 -10
  352. package/src/generators/selector-mapper/intelligent-mapper.ts +0 -530
  353. package/src/generators/test-generator/ai-step-mapper.ts +0 -224
  354. package/src/generators/test-generator/auth-setup-generator.ts +0 -59
  355. package/src/generators/test-generator/patterns/legacy-patterns.ts +0 -104
  356. package/src/generators/test-generator/templates/auth-setup.ts.hbs +0 -36
  357. package/src/generators/ui-model-builder/deep-scanner.ts +0 -1244
  358. package/src/generators/ui-model-builder/enhanced-deep-scanner.ts +0 -731
  359. package/src/generators/ui-model-builder/react-scanner.ts +0 -959
  360. package/src/orchestrator/cache-manager.ts +0 -32
  361. package/src/orchestrator/pipeline.ts +0 -354
  362. package/src/orchestrator/reporter.ts +0 -36
@@ -1,1029 +0,0 @@
1
- /**
2
- * Scaffold Generator
3
- * Parses Gherkin feature files and generates selector YAML scaffolds
4
- */
5
-
6
- import fs from 'fs';
7
- import path from 'path';
8
- import yaml from 'yaml';
9
- import { glob } from 'glob';
10
- import { SelectorResolver } from '../test-generator/utils/selector-resolver';
11
- import { SelectorType } from '../../utils/selector-types';
12
-
13
- export interface ScaffoldElement {
14
- locator: string;
15
- type: SelectorType;
16
- value: string;
17
- name?: string; // For role type (accessible name), or label text
18
- nth: number;
19
- exact?: boolean; // Whether to use exact matching (default: false)
20
- inputMethod?: string; // 'pressSequentially' for contenteditable/rich-text elements
21
- }
22
-
23
- export interface ScaffoldResult {
24
- [key: string]: ScaffoldElement;
25
- }
26
-
27
- export interface TestDataResult {
28
- [key: string]: string | TestDataResult;
29
- }
30
-
31
- export interface MapResult {
32
- featureFile: string;
33
- selectorsPath: string;
34
- testDataPath: string;
35
- elementCount: number;
36
- dataRefCount: number;
37
- selectorsSkipped?: boolean; // True if selectors file already existed (and force was not set)
38
- testDataMerged?: boolean; // True if test-data file existed and was smart-merged
39
- }
40
-
41
- interface ExtractedDataRef {
42
- rawRef: string; // Original ref e.g., "valid_login_email" or "valid.login_password"
43
- lineText: string; // Full line for context
44
- }
45
-
46
- interface ExtractedElement {
47
- rawText: string; // Original text in brackets e.g., "Email Address"
48
- key: string; // Converted key e.g., "email.address"
49
- action: string; // Action type: fill, click, see, open, select
50
- targetType: string; // Target type from Gherkin: button, link, field, radio, dropdown, text, etc.
51
- lineText: string; // Full line for context
52
- nth: number; // Index from "field 1", "button 2", etc. (0 = no index specified)
53
- featurePath?: string; // NEW: Feature path for page type (e.g., "/" or "/dashboard")
54
- }
55
-
56
- export class ScaffoldGenerator {
57
- /**
58
- * Generate scaffold YAML from a feature file
59
- */
60
- generateFromFile(featureFilePath: string): ScaffoldResult {
61
- const content = fs.readFileSync(featureFilePath, 'utf-8');
62
- return this.generateFromContent(content);
63
- }
64
-
65
- /**
66
- * Generate scaffold YAML from feature content
67
- */
68
- generateFromContent(content: string): ScaffoldResult {
69
- // Extract feature path from Gherkin metadata (e.g., "Path: login-guest?user=inactive")
70
- const featurePath = this.extractFeaturePath(content);
71
-
72
- const elements = this.extractElements(content, featurePath);
73
- return this.buildScaffold(elements);
74
- }
75
-
76
- /**
77
- * Extract elements from Gherkin content with their actions
78
- */
79
- private extractElements(content: string, featurePath?: string): ExtractedElement[] {
80
- const elements: ExtractedElement[] = [];
81
- const lines = content.split('\n');
82
-
83
- // Regex to match elements in brackets [...] with optional nth index
84
- // Captures: [Element] field 1, [Element] button 2, [Element] text 3, etc.
85
- const elementRegex = /\[([^\]]+)\]/g;
86
-
87
- for (const line of lines) {
88
- const trimmedLine = line.trim();
89
-
90
- // Skip comments and empty lines
91
- if (trimmedLine.startsWith('#') || !trimmedLine) {
92
- continue;
93
- }
94
-
95
- // Determine action from the line
96
- const action = this.detectAction(trimmedLine);
97
-
98
- let match: RegExpExecArray | null;
99
- while ((match = elementRegex.exec(trimmedLine)) !== null) {
100
- const rawText = match[1];
101
- const baseKey = SelectorResolver.generateKey(rawText);
102
-
103
- // Extract nth index and target type from text after the element reference
104
- // Matches patterns like: [Email] field 1, [Submit] button 2, [Name] 3
105
- const afterElement = trimmedLine.slice(match.index + match[0].length);
106
- const nth = SelectorResolver.extractNthFromStep(afterElement);
107
- const targetType = this.extractTargetType(afterElement, action);
108
-
109
- // Append nth to key so elements at different positions get distinct keys
110
- // e.g., [Hãy gửi lời cảm ơn] field 3 → "hay.gui.loi.cam.on--3"
111
- const key = nth > 0 ? `${baseKey}--${nth}` : baseKey;
112
-
113
- // Check if element starts with "target" (semantic indicator for dynamic content)
114
- // e.g., [target order], [Target Item] → element should have empty value
115
- const isTargetElement = /^target\s/i.test(rawText);
116
-
117
- elements.push({
118
- rawText: isTargetElement ? '' : rawText, // Empty value when element is a target element
119
- key,
120
- action,
121
- targetType,
122
- lineText: trimmedLine,
123
- nth,
124
- featurePath: targetType === 'page' ? featurePath : undefined,
125
- });
126
- }
127
- }
128
-
129
- return elements;
130
- }
131
-
132
- /**
133
- * Extract target type from text after element reference
134
- * e.g., " button" -> "button", " link 1" -> "link", " field with" -> "field"
135
- */
136
- private extractTargetType(text: string, action: string): string {
137
- const lowerText = text.toLowerCase().trim();
138
-
139
- // NEW: Check for 'page' keyword FIRST (highest priority)
140
- if (/^page\b/.test(lowerText) || /^\d*\s*page\b/.test(lowerText)) {
141
- return 'page';
142
- }
143
-
144
- // Check for specific target types in order of specificity
145
- if (/^link\b/.test(lowerText) || /^\d*\s*link\b/.test(lowerText)) {
146
- return 'link';
147
- }
148
- if (/^button\b/.test(lowerText) || /^\d*\s*button\b/.test(lowerText)) {
149
- return 'button';
150
- }
151
- if (/^radio\b/.test(lowerText) || /^\d*\s*radio\b/.test(lowerText)) {
152
- return 'radio';
153
- }
154
- if (/^dropdown\b/.test(lowerText) || /^\d*\s*dropdown\b/.test(lowerText) || /^select\b/.test(lowerText)) {
155
- return 'dropdown';
156
- }
157
- if (/^checkbox\b/.test(lowerText) || /^\d*\s*checkbox\b/.test(lowerText)) {
158
- return 'checkbox';
159
- }
160
- if (/^field\b/.test(lowerText) || /^\d*\s*field\b/.test(lowerText) || /^input\b/.test(lowerText)) {
161
- return 'field';
162
- }
163
- if (/^textbox\b/.test(lowerText) || /^\d*\s*textbox\b/.test(lowerText)) {
164
- return 'textbox';
165
- }
166
- if (/^textarea\b/.test(lowerText) || /^\d*\s*textarea\b/.test(lowerText)) {
167
- return 'textarea';
168
- }
169
- if (/^editor\b/.test(lowerText) || /^\d*\s*editor\b/.test(lowerText)) {
170
- return 'editor';
171
- }
172
- if (/^text\b/.test(lowerText) || /^\d*\s*text\b/.test(lowerText)) {
173
- return 'text';
174
- }
175
- if (/^label\b/.test(lowerText) || /^\d*\s*label\b/.test(lowerText)) {
176
- return 'label';
177
- }
178
- if (/^uploader\b/.test(lowerText) || /^\d*\s*uploader\b/.test(lowerText)) {
179
- return 'uploader';
180
- }
181
- if (/^element\b/.test(lowerText) || /^\d*\s*element\b/.test(lowerText)) {
182
- return 'element';
183
- }
184
- if (/^(column|columnheader)\b/.test(lowerText) || /^\d*\s*(column|columnheader)\b/.test(lowerText)) {
185
- return 'column';
186
- }
187
- if (/^(logo|image|img|icon)\b/.test(lowerText) || /^\d*\s*(logo|image|img|icon)\b/.test(lowerText)) {
188
- return 'img';
189
- }
190
- if (/^(option|item|listitem)\b/.test(lowerText) || /^\d*\s*(option|item|listitem)\b/.test(lowerText)) {
191
- return 'option';
192
- }
193
- if (/^(dialog|modal)\b/.test(lowerText) || /^\d*\s*(dialog|modal)\b/.test(lowerText)) {
194
- return 'dialog';
195
- }
196
- if (/^(heading|header)\b/.test(lowerText) || /^\d*\s*(heading|header)\b/.test(lowerText)) {
197
- return 'heading';
198
- }
199
- if (/^(title|caption|message)\b/.test(lowerText) || /^\d*\s*(title|caption|message)\b/.test(lowerText)) {
200
- return 'text';
201
- }
202
-
203
- // Default based on action
204
- switch (action) {
205
- case 'click':
206
- return 'text'; // Default click to text
207
- case 'fill':
208
- return 'field'; // Default fill to field
209
- case 'see':
210
- return 'text'; // Default see to text
211
- case 'select':
212
- return 'dropdown'; // Default select to dropdown
213
- default:
214
- return 'element';
215
- }
216
- }
217
-
218
- /**
219
- * Extract nth index from text after element reference
220
- * e.g., " field 1 with" -> 1, " button 2" -> 2, " row 1" -> 1, " text" -> 0
221
- */
222
- /**
223
- * Detect the action type from a Gherkin step line
224
- */
225
- private detectAction(line: string): string {
226
- const lowerLine = line.toLowerCase();
227
-
228
- // Check for specific action keywords
229
- // Order matters: check "see" before "fill" because text like "fill in all fields" may appear in assertions
230
- if (/\b(see|verify|check|expect|should see|visible|displayed)\b/.test(lowerLine)) {
231
- return 'see';
232
- }
233
- if (/\b(fill|enter|type|input)\b/.test(lowerLine)) {
234
- return 'fill';
235
- }
236
- if (/\b(click|press|tap|submit)\b/.test(lowerLine)) {
237
- return 'click';
238
- }
239
- if (/\b(open|navigate|go to|visit)\b/.test(lowerLine)) {
240
- return 'open';
241
- }
242
- if (/\b(select|choose|pick)\b/.test(lowerLine)) {
243
- return 'select';
244
- }
245
- if (/\b(wait|wait for|waits for)\b/.test(lowerLine)) {
246
- return 'wait';
247
- }
248
-
249
- // Default to placeholder for unknown actions
250
- return 'default';
251
- }
252
-
253
- /**
254
- * Extract feature path from Gherkin metadata
255
- * Looks for "Path: <value>" line in the feature file
256
- * e.g., "Path: login-guest?user=inactive" -> "login-guest?user=inactive"
257
- */
258
- private extractFeaturePath(content: string): string | undefined {
259
- const lines = content.split('\n');
260
-
261
- for (const line of lines) {
262
- const trimmedLine = line.trim();
263
-
264
- // Look for Path: pattern (case-insensitive)
265
- const match = trimmedLine.match(/^Path:\s*(.+?)$/i);
266
- if (match) {
267
- return match[1].trim();
268
- }
269
- }
270
-
271
- return undefined;
272
- }
273
-
274
- /**
275
- * Convert element text to YAML key format
276
- * "Email Address" -> "email.address"
277
- * "Your email or password is incorrect!" -> "your.email.or.password.is.incorrect"
278
- */
279
- /**
280
- * Get action priority (higher number = higher priority)
281
- */
282
- private getActionPriority(action: string): number {
283
- const priorities: { [key: string]: number } = {
284
- 'click': 4, // Highest priority - most specific interaction
285
- 'fill': 3, // Form input
286
- 'select': 3, // Form select
287
- 'see': 2, // Assertion
288
- 'open': 1, // Navigation - lowest priority
289
- 'default': 0,
290
- };
291
- return priorities[action] ?? 0;
292
- }
293
-
294
- /**
295
- * Build scaffold result from extracted elements
296
- */
297
- private buildScaffold(elements: ExtractedElement[]): ScaffoldResult {
298
- const result: ScaffoldResult = {};
299
-
300
- // Group elements by key to detect duplicates with different target types
301
- const elementsByKey: { [key: string]: ExtractedElement[] } = {};
302
- for (const element of elements) {
303
- if (!elementsByKey[element.key]) {
304
- elementsByKey[element.key] = [];
305
- }
306
- elementsByKey[element.key].push(element);
307
- }
308
-
309
- for (const key of Object.keys(elementsByKey)) {
310
- const group = elementsByKey[key];
311
-
312
- // Deduplicate by targetType, keeping highest priority action per type
313
- const byType: { [type: string]: ExtractedElement } = {};
314
- for (const element of group) {
315
- const existing = byType[element.targetType];
316
- if (!existing ||
317
- this.getActionPriority(element.action) > this.getActionPriority(existing.action)) {
318
- byType[element.targetType] = element;
319
- }
320
- }
321
-
322
- const uniqueTypes = Object.keys(byType);
323
-
324
- if (uniqueTypes.length <= 1) {
325
- // No type conflict — use base key as before
326
- const element = byType[uniqueTypes[0]];
327
- result[key] = this.createScaffoldElement(element);
328
- } else {
329
- // Multiple types for same name — generate type-suffixed keys
330
- for (const type of uniqueTypes) {
331
- const suffixedKey = `${key}--${type}`;
332
- result[suffixedKey] = this.createScaffoldElement(byType[type]);
333
- }
334
- }
335
- }
336
-
337
- return result;
338
- }
339
-
340
- /**
341
- * Create a scaffold element based on the action type and target type
342
- */
343
- private createScaffoldElement(element: ExtractedElement): ScaffoldElement {
344
- const { nth, targetType } = element;
345
-
346
- // NEW: Handle page type
347
- if (targetType === 'page') {
348
- return {
349
- locator: '',
350
- type: 'page',
351
- value: element.featurePath || '/',
352
- nth,
353
- };
354
- }
355
-
356
- switch (element.action) {
357
- case 'fill':
358
- // For fill action with uploader type, use label type (file input triggered by label)
359
- if (targetType === 'uploader') {
360
- return {
361
- locator: '',
362
- type: 'label',
363
- value: element.rawText,
364
- nth,
365
- };
366
- }
367
-
368
- // For fill action with label type, use label type
369
- if (targetType === 'label') {
370
- return {
371
- locator: '',
372
- type: 'label',
373
- value: element.rawText,
374
- nth,
375
- };
376
- }
377
-
378
- // For fill action with field/textbox/textarea types, use role-based locator
379
- return {
380
- locator: '',
381
- type: 'role',
382
- value: this.getFillRoleValue(targetType),
383
- name: element.rawText,
384
- nth,
385
- };
386
-
387
- case 'click':
388
- // Special case: label and text clicks use text locator
389
- if (targetType === 'label' || targetType === 'text') {
390
- return {
391
- locator: '',
392
- type: 'text',
393
- value: element.rawText,
394
- nth,
395
- };
396
- }
397
-
398
- // Regular click actions use role
399
- const clickRoleValue = this.getClickRoleValue(targetType);
400
- return {
401
- locator: '',
402
- type: 'role',
403
- value: clickRoleValue,
404
- name: element.rawText, // TODO: Update with actual accessible name from inspection
405
- nth,
406
- };
407
-
408
- case 'see':
409
- // Column type: used for table column cell assertions, no DOM selector needed
410
- if (targetType === 'column') {
411
- return {
412
- locator: '',
413
- type: 'column',
414
- value: element.rawText,
415
- nth,
416
- };
417
- }
418
-
419
- // For assertions - respect targetType for role-based elements
420
- if (targetType === 'img' || targetType === 'button' || targetType === 'link' ||
421
- targetType === 'checkbox' || targetType === 'radio' || targetType === 'heading' || targetType === 'dialog' || targetType === 'dropdown') {
422
- const seeRoleValue = this.getRoleValue(targetType);
423
- return {
424
- locator: '',
425
- type: 'role',
426
- value: seeRoleValue,
427
- name: element.rawText,
428
- nth,
429
- };
430
- }
431
- return {
432
- locator: '',
433
- type: 'text',
434
- value: element.rawText,
435
- nth,
436
- };
437
-
438
- case 'select':
439
- // Determine role based on target type (radio, dropdown/combobox, checkbox)
440
- const selectRoleValue = this.getSelectRoleValue(targetType);
441
- return {
442
- locator: '',
443
- type: 'role',
444
- value: selectRoleValue,
445
- name: element.rawText, // TODO: Update with actual accessible name from inspection
446
- nth,
447
- };
448
-
449
- case 'open':
450
- // For page navigation, we typically don't need a selector
451
- // But include it for reference
452
- return {
453
- locator: '',
454
- type: 'text',
455
- value: element.rawText,
456
- nth,
457
- };
458
-
459
- default:
460
- // Default to placeholder type
461
- return {
462
- locator: '',
463
- type: 'placeholder',
464
- value: element.rawText,
465
- nth,
466
- };
467
- }
468
- }
469
-
470
- /**
471
- * Get ARIA role value from target type (shared across actions)
472
- */
473
- private getRoleValue(targetType: string): string {
474
- switch (targetType) {
475
- case 'link':
476
- return 'link';
477
- case 'button':
478
- return 'button';
479
- case 'checkbox':
480
- return 'checkbox';
481
- case 'radio':
482
- return 'radio';
483
- case 'tab':
484
- return 'tab';
485
- case 'menuitem':
486
- return 'menuitem';
487
- case 'img':
488
- return 'img';
489
- case 'heading':
490
- return 'heading';
491
- case 'dialog':
492
- return 'dialog';
493
- case 'dropdown':
494
- return 'button';
495
- default:
496
- return 'button'; // Default to button
497
- }
498
- }
499
-
500
- /**
501
- * Get role value for click actions based on target type
502
- */
503
- private getClickRoleValue(targetType: string): string {
504
- return this.getRoleValue(targetType);
505
- }
506
-
507
- /**
508
- * Get role value for select actions based on target type
509
- */
510
- private getSelectRoleValue(targetType: string): string {
511
- switch (targetType) {
512
- case 'radio':
513
- return 'radio';
514
- case 'checkbox':
515
- return 'checkbox';
516
- case 'dropdown':
517
- case 'select':
518
- return 'combobox';
519
- case 'listbox':
520
- return 'listbox';
521
- default:
522
- return 'combobox'; // Default to combobox
523
- }
524
- }
525
-
526
- /**
527
- * Get ARIA role value for fill actions — all fillable inputs use 'textbox'
528
- */
529
- private getFillRoleValue(targetType: string): string {
530
- switch (targetType) {
531
- case 'field':
532
- case 'textbox':
533
- case 'textarea':
534
- default:
535
- return 'textbox';
536
- }
537
- }
538
-
539
- /**
540
- * Generate YAML content from scaffold result
541
- */
542
- generateYamlContent(scaffold: ScaffoldResult, screenName?: string): string {
543
- // For header, use name (for role type) or value (for other types)
544
- const getDisplayValue = (e: ScaffoldElement) => e.name || e.value;
545
-
546
- const header = screenName
547
- ? `# ${this.capitalizeFirst(screenName)} Screen Selectors (Auto-generated)
548
- # Reference in features: ${Object.values(scaffold).map(e => `[${getDisplayValue(e)}]`).join(', ')}
549
- # Directory: qa/selectors/screens/
550
-
551
- `
552
- : `# Screen Selectors (Auto-generated)
553
-
554
- `;
555
-
556
- // Convert to YAML with proper formatting
557
- const yamlContent = yaml.stringify(scaffold, {
558
- indent: 2,
559
- lineWidth: 100,
560
- defaultKeyType: 'PLAIN',
561
- defaultStringType: 'QUOTE_SINGLE',
562
- });
563
-
564
- return header + yamlContent;
565
- }
566
-
567
- /**
568
- * Write scaffold to file
569
- */
570
- writeScaffold(
571
- scaffold: ScaffoldResult,
572
- outputPath: string,
573
- screenName?: string
574
- ): void {
575
- const dir = path.dirname(outputPath);
576
- if (!fs.existsSync(dir)) {
577
- fs.mkdirSync(dir, { recursive: true });
578
- }
579
-
580
- const content = this.generateYamlContent(scaffold, screenName);
581
- fs.writeFileSync(outputPath, content, 'utf-8');
582
- }
583
-
584
- /**
585
- * Process a feature file and write scaffold to output
586
- */
587
- processFeatureFile(
588
- featureFilePath: string,
589
- outputDir: string,
590
- forceOverwrite: boolean = false
591
- ): { outputPath: string; elementCount: number } {
592
- // Extract screen name from feature file name
593
- const featureFileName = path.basename(featureFilePath, '.feature');
594
- const screenName = featureFileName;
595
-
596
- // Generate scaffold
597
- const scaffold = this.generateFromFile(featureFilePath);
598
- const elementCount = Object.keys(scaffold).length;
599
-
600
- // Write to output
601
- const outputPath = path.join(outputDir, `${screenName}.yaml`);
602
-
603
- // Check if file exists and skip if not forcing overwrite
604
- if (fs.existsSync(outputPath) && !forceOverwrite) {
605
- return { outputPath, elementCount };
606
- }
607
-
608
- this.writeScaffold(scaffold, outputPath, screenName);
609
-
610
- return { outputPath, elementCount };
611
- }
612
-
613
- /**
614
- * Process all feature files in a directory
615
- */
616
- processAllFeatures(
617
- featuresDir: string,
618
- outputDir: string
619
- ): Array<{ file: string; outputPath: string; elementCount: number }> {
620
- const featureFiles = glob.sync('**/*.feature', {
621
- cwd: featuresDir,
622
- absolute: true,
623
- });
624
-
625
- const results: Array<{ file: string; outputPath: string; elementCount: number }> = [];
626
-
627
- for (const featureFile of featureFiles) {
628
- const result = this.processFeatureFile(featureFile, outputDir);
629
- results.push({
630
- file: featureFile,
631
- ...result,
632
- });
633
- }
634
-
635
- return results;
636
- }
637
-
638
- /**
639
- * Enrich scaffold with live-scan data if a .live-scan.yaml file exists.
640
- * Uses the Playwright locator strategy detected by live-scan:
641
- * testid → type: testid, value: <testid>
642
- * role → type: role, value: <aria-role>, name: <accessible-name>
643
- * label → type: label, value: <label-text>
644
- * placeholder → type: placeholder, value: <placeholder-text>
645
- * text → type: text, value: <visible-text>
646
- */
647
- private enrichWithLiveScan(scaffold: ScaffoldResult, liveScanPath: string): ScaffoldResult {
648
- if (!fs.existsSync(liveScanPath)) {
649
- return scaffold;
650
- }
651
-
652
- let matrixData: any;
653
- try {
654
- const { readMatrix, mergeMatrixElements } = require('../../core/live-scanner/matrix-reader');
655
- matrixData = readMatrix(liveScanPath);
656
- if (!matrixData) return scaffold;
657
-
658
- const elements = mergeMatrixElements(matrixData);
659
- if (!elements || Object.keys(elements).length === 0) return scaffold;
660
-
661
- console.log(` 🔗 Using live-scan data from ${path.basename(liveScanPath)}`);
662
-
663
- let enrichedCount = 0;
664
- let warningCount = 0;
665
-
666
- const enriched: ScaffoldResult = { ...scaffold };
667
-
668
- for (const [key, scaffoldElement] of Object.entries(enriched)) {
669
- // Column-type elements use header text matching, skip live-scan enrichment
670
- if (scaffoldElement.type === 'column') continue;
671
- // Target elements (empty value) are matched by data value at runtime, skip enrichment
672
- if (!scaffoldElement.value && !scaffoldElement.name) continue;
673
-
674
- // Try exact key first, then base key without --N suffix or --type suffix
675
- let liveElement = elements[key];
676
- if (!liveElement && key.includes('--')) {
677
- const baseKey = key.replace(/--.*$/, '');
678
- liveElement = elements[baseKey];
679
- }
680
- if (!liveElement) {
681
- continue;
682
- }
683
-
684
- // If live-scan couldn't find the element, clear the identifying field so user must fill manually
685
- if (liveElement.matchMethod === 'unresolved') {
686
- if (scaffoldElement.name !== undefined) {
687
- enriched[key] = { ...scaffoldElement, name: '' };
688
- } else {
689
- enriched[key] = { ...scaffoldElement, value: '' };
690
- }
691
- continue;
692
- }
693
-
694
- // Use the Playwright locator strategy that live-scan detected
695
- const selectorType = liveElement.selectorType;
696
-
697
- // Only set exact when true (omit from YAML when false for cleaner output)
698
- const exactField = liveElement.exact ? { exact: true } : {};
699
-
700
- // Strip 'name' from scaffold base — only 'role' type uses name
701
- const { name: _scaffoldName, ...scaffoldBase } = scaffoldElement as any;
702
-
703
- if (selectorType === 'testid' && liveElement.testid) {
704
- // page.getByTestId('value')
705
- enriched[key] = {
706
- ...scaffoldBase,
707
- type: 'testid' as any,
708
- value: liveElement.testid,
709
- };
710
- } else if (selectorType === 'role' && liveElement.role) {
711
- // page.getByRole('button', { name: 'Submit' })
712
- // When name is empty (e.g. data-value fallback), keep it as '' —
713
- // the actual name comes from test-data at runtime
714
- enriched[key] = {
715
- ...scaffoldBase,
716
- type: 'role',
717
- value: liveElement.role,
718
- name: liveElement.name,
719
- ...exactField,
720
- };
721
- } else if (selectorType === 'label' && liveElement.label) {
722
- // page.getByLabel('Username')
723
- enriched[key] = {
724
- ...scaffoldBase,
725
- type: 'label',
726
- value: liveElement.label,
727
- ...exactField,
728
- };
729
- } else if (selectorType === 'placeholder' && (liveElement.placeholder || liveElement.selectorValue)) {
730
- // page.getByPlaceholder('Enter email')
731
- enriched[key] = {
732
- ...scaffoldBase,
733
- type: 'placeholder',
734
- value: liveElement.placeholder || liveElement.selectorValue,
735
- ...exactField,
736
- };
737
- } else if (selectorType === 'text') {
738
- // page.getByText('Welcome')
739
- // When name is empty (data-value fallback), keep value empty —
740
- // the actual text comes from test-data at runtime
741
- enriched[key] = {
742
- ...scaffoldBase,
743
- type: 'text',
744
- value: liveElement.name,
745
- ...exactField,
746
- };
747
- } else if (selectorType === 'locator' && liveElement.selectorValue) {
748
- // page.locator('[contenteditable="true"]') — CSS locator
749
- // Reset nth to 0: the original nth was for text matches, not this locator type
750
- enriched[key] = {
751
- ...scaffoldBase,
752
- type: 'locator' as any,
753
- value: liveElement.selectorValue,
754
- nth: 0,
755
- };
756
- } else {
757
- continue;
758
- }
759
-
760
- // Detect contenteditable div-based editors (rich text editors like TipTap, Quill, ProseMirror)
761
- // When the DOM element is contenteditable but NOT a native input/textarea,
762
- // mark it so the generator uses click + pressSequentially instead of .fill()
763
- if (liveElement.contenteditable && liveElement.tag !== 'textarea' && liveElement.tag !== 'input') {
764
- enriched[key] = { ...enriched[key], inputMethod: 'pressSequentially' };
765
- }
766
-
767
- enrichedCount++;
768
- if (liveElement.warning) {
769
- warningCount++;
770
- console.log(` ⚠️ ${key}: ${liveElement.warning}`);
771
- }
772
- }
773
-
774
- const textOnlyCount = Object.keys(enriched).length - enrichedCount;
775
- console.log(` 📊 Live-scan: ${enrichedCount} enriched, ${textOnlyCount} text-inferred${warningCount > 0 ? `, ${warningCount} warnings` : ''}`);
776
-
777
- return enriched;
778
- } catch (error: any) {
779
- console.warn(` ⚠️ Failed to read live-scan data: ${error.message}`);
780
- return scaffold;
781
- }
782
- }
783
-
784
- /**
785
- * Helper to capitalize first letter
786
- */
787
- private capitalizeFirst(str: string): string {
788
- return str.charAt(0).toUpperCase() + str.slice(1);
789
- }
790
-
791
- /**
792
- * Process a screen and generate both selectors and test-data YAML
793
- * Folder structure: qa/screens/<screen>/features/, selectors/, test-data/
794
- */
795
- processScreen(screenName: string, screensDir: string = 'qa/screens', forceOverwrite: boolean = false): MapResult[] {
796
- const screenDir = path.join(screensDir, screenName);
797
- const featuresDir = path.join(screenDir, 'features');
798
- const selectorsDir = path.join(screenDir, 'selectors');
799
- const testDataDir = path.join(screenDir, 'test-data');
800
-
801
- // Find all feature files in the screen's features directory
802
- const featureFiles = glob.sync('**/*.feature', {
803
- cwd: featuresDir,
804
- absolute: true,
805
- });
806
-
807
- if (featureFiles.length === 0) {
808
- throw new Error(`No feature files found in ${featuresDir}`);
809
- }
810
-
811
- // Ensure directories exist
812
- fs.mkdirSync(selectorsDir, { recursive: true });
813
- fs.mkdirSync(testDataDir, { recursive: true });
814
-
815
- const results: MapResult[] = [];
816
-
817
- // Process each feature file individually
818
- for (const featureFile of featureFiles) {
819
- const filename = path.basename(featureFile, '.feature');
820
- const content = fs.readFileSync(featureFile, 'utf-8');
821
-
822
- // Extract feature path from Gherkin metadata
823
- const featurePath = this.extractFeaturePath(content);
824
-
825
- // Extract elements and data refs from this file
826
- const elements = this.extractElements(content, featurePath);
827
- const dataRefs = this.extractDataRefs(content);
828
-
829
- // Build scaffolds from this file
830
- const selectorScaffold = this.buildScaffold(elements);
831
- const testDataScaffold = this.buildTestData(dataRefs);
832
-
833
- // Enrich scaffold with live-scan data if available
834
- const liveScanPath = path.join(selectorsDir, `${filename}.live-scan.yaml`);
835
- const liveScanEnriched = this.enrichWithLiveScan(selectorScaffold, liveScanPath);
836
-
837
- // Check if files exist
838
- const selectorsPath = path.join(selectorsDir, `${filename}.yaml`);
839
- const selectorsOverridePath = path.join(selectorsDir, `${filename}.override.yaml`);
840
- const selectorsExists = fs.existsSync(selectorsPath);
841
-
842
- const testDataPath = path.join(testDataDir, `${filename}.yaml`);
843
- const testDataOverridePath = path.join(testDataDir, `${filename}.override.yaml`);
844
- const testDataExists = fs.existsSync(testDataPath);
845
-
846
- // Write selectors YAML (BASE ONLY - never touch override files)
847
- if (!selectorsExists || forceOverwrite) {
848
- this.writeSelectorsYaml(liveScanEnriched, selectorsPath, filename);
849
- }
850
- // Override file is NEVER touched by map command
851
-
852
- // Write test-data YAML — always smart-merge regardless of --force:
853
- // • existing keys keep their current value
854
- // • new keys from features are added (empty string)
855
- // • keys no longer referenced in features are removed
856
- if (testDataExists) {
857
- const existingRaw = yaml.parse(fs.readFileSync(testDataPath, 'utf-8')) as TestDataResult || {};
858
- const merged = this.mergeTestData(existingRaw, testDataScaffold);
859
- this.writeTestDataYaml(merged, testDataPath, filename);
860
- } else {
861
- this.writeTestDataYaml(testDataScaffold, testDataPath, filename);
862
- }
863
- // Override file is NEVER touched by map command
864
-
865
- results.push({
866
- featureFile: filename,
867
- selectorsPath,
868
- testDataPath,
869
- elementCount: Object.keys(liveScanEnriched).length,
870
- dataRefCount: dataRefs.length,
871
- selectorsSkipped: selectorsExists && !forceOverwrite,
872
- testDataMerged: testDataExists,
873
- });
874
- }
875
-
876
- return results;
877
- }
878
-
879
- /**
880
- * Extract data references {{...}} from Gherkin content
881
- */
882
- private extractDataRefs(content: string): ExtractedDataRef[] {
883
- const dataRefs: ExtractedDataRef[] = [];
884
- const lines = content.split('\n');
885
- const seen = new Set<string>();
886
-
887
- // Regex to match data refs {{...}}
888
- const dataRefRegex = /\{\{([^}]+)\}\}/g;
889
-
890
- for (const line of lines) {
891
- const trimmedLine = line.trim();
892
-
893
- // Skip comments and empty lines
894
- if (trimmedLine.startsWith('#') || !trimmedLine) {
895
- continue;
896
- }
897
-
898
- let match: RegExpExecArray | null;
899
- while ((match = dataRefRegex.exec(trimmedLine)) !== null) {
900
- const rawRef = match[1].trim();
901
-
902
- // Skip duplicates
903
- if (seen.has(rawRef)) {
904
- continue;
905
- }
906
- seen.add(rawRef);
907
-
908
- dataRefs.push({
909
- rawRef,
910
- lineText: trimmedLine,
911
- });
912
- }
913
- }
914
-
915
- return dataRefs;
916
- }
917
-
918
- /**
919
- * Build test data structure from data refs
920
- * Supports nested structure: "valid.login_password" -> { valid: { login_password: '' } }
921
- */
922
- private buildTestData(dataRefs: ExtractedDataRef[]): TestDataResult {
923
- const result: TestDataResult = {};
924
-
925
- for (const dataRef of dataRefs) {
926
- const parts = dataRef.rawRef.split('.');
927
-
928
- if (parts.length === 1) {
929
- // Simple variable: "valid_login_email" -> { valid_login_email: '' }
930
- result[parts[0]] = '';
931
- } else {
932
- // Nested variable: "valid.login_password" -> { valid: { login_password: '' } }
933
- let current: TestDataResult = result;
934
- for (let i = 0; i < parts.length - 1; i++) {
935
- const part = parts[i];
936
- if (!current[part] || typeof current[part] === 'string') {
937
- current[part] = {};
938
- }
939
- current = current[part] as TestDataResult;
940
- }
941
- current[parts[parts.length - 1]] = '';
942
- }
943
- }
944
-
945
- return result;
946
- }
947
-
948
- /**
949
- * Write selectors YAML file
950
- */
951
- private writeSelectorsYaml(scaffold: ScaffoldResult, outputPath: string, screenName: string): void {
952
- const getDisplayValue = (e: ScaffoldElement) => e.name || e.value;
953
-
954
- // Deduplicate display values for header comment
955
- const displayValues = [...new Set(Object.values(scaffold).map(e => `[${getDisplayValue(e)}]`))];
956
- const header = `# ${this.capitalizeFirst(screenName)} Screen Selectors (Auto-generated)
957
- # ⚠️ AUTO-GENERATED - Do not edit directly
958
- # To override values, edit ${screenName}.override.yaml
959
- # Manual edits to this file will be lost on regeneration
960
- #
961
- # Reference in features: ${displayValues.join(', ')}
962
-
963
- `;
964
-
965
- const yamlContent = yaml.stringify(scaffold, {
966
- indent: 2,
967
- lineWidth: 100,
968
- defaultKeyType: 'PLAIN',
969
- defaultStringType: 'QUOTE_SINGLE',
970
- });
971
-
972
- fs.writeFileSync(outputPath, header + yamlContent, 'utf-8');
973
- }
974
-
975
- /**
976
- * Smart-merge test-data objects:
977
- * - Keys present in both: keep the existing value (user may have filled it in)
978
- * - Keys only in fresh (new refs in features): add with empty value
979
- * - Keys only in existing (removed from features): drop them
980
- */
981
- private mergeTestData(existing: TestDataResult, fresh: TestDataResult): TestDataResult {
982
- const merged: TestDataResult = {};
983
-
984
- for (const key of Object.keys(fresh)) {
985
- if (key in existing) {
986
- const existingVal = existing[key];
987
- const freshVal = fresh[key];
988
- if (typeof freshVal === 'object' && typeof existingVal === 'object') {
989
- // Both are nested — recurse
990
- merged[key] = this.mergeTestData(existingVal as TestDataResult, freshVal as TestDataResult);
991
- } else {
992
- // Keep the existing (user-supplied) value
993
- merged[key] = existingVal;
994
- }
995
- } else {
996
- // New key — add with the scaffold default (empty string or nested object)
997
- merged[key] = fresh[key];
998
- }
999
- // Keys present in existing but absent from fresh are intentionally dropped (no longer referenced)
1000
- }
1001
-
1002
- return merged;
1003
- }
1004
-
1005
- /**
1006
- * Write test-data YAML file
1007
- */
1008
- private writeTestDataYaml(testData: TestDataResult, outputPath: string, screenName: string): void {
1009
- const header = `# ${this.capitalizeFirst(screenName)} Screen Test Data
1010
- # Managed by: sungen map (smart-merge)
1011
- # • New {{variable}} refs are added automatically (empty value)
1012
- # • Removed refs are dropped on the next sungen map run
1013
- # • Your existing values are never overwritten
1014
- # To fix a value permanently, copy the key to ${screenName}.override.yaml
1015
- #
1016
- # Reference in features using {{variable}} syntax
1017
-
1018
- `;
1019
-
1020
- const yamlContent = yaml.stringify(testData, {
1021
- indent: 2,
1022
- lineWidth: 100,
1023
- defaultKeyType: 'PLAIN',
1024
- defaultStringType: 'QUOTE_SINGLE',
1025
- });
1026
-
1027
- fs.writeFileSync(outputPath, header + yamlContent, 'utf-8');
1028
- }
1029
- }