create-appraisejs 0.1.6 → 0.1.8-alpha

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 (320) hide show
  1. package/README.md +45 -45
  2. package/dist/cli.js +4 -4
  3. package/dist/cli.js.map +1 -1
  4. package/dist/copy-template.d.ts +2 -1
  5. package/dist/copy-template.d.ts.map +1 -1
  6. package/dist/copy-template.js +10 -7
  7. package/dist/copy-template.js.map +1 -1
  8. package/dist/copy-template.test.js +19 -0
  9. package/dist/copy-template.test.js.map +1 -1
  10. package/package.json +69 -67
  11. package/templates/default/.vscode/settings.json +5 -0
  12. package/templates/default/appraisejs.config.json +1 -1
  13. package/templates/default/components.json +24 -24
  14. package/templates/default/cucumber.mjs +16 -0
  15. package/templates/default/eslint.config.mjs +15 -15
  16. package/templates/default/next.config.ts +13 -7
  17. package/templates/default/package-lock.json +13732 -14321
  18. package/templates/default/package.json +11 -9
  19. package/templates/default/postcss.config.mjs +8 -8
  20. package/templates/default/prisma/migrations/20251104113456_add_type_for_template_step_groups/migration.sql +16 -16
  21. package/templates/default/prisma/migrations/20251104170946_add_tags_to_test_suite_and_test_case/migration.sql +27 -27
  22. package/templates/default/prisma/migrations/20251112190024_add_cascade_delete_to_test_run_test_case/migration.sql +17 -17
  23. package/templates/default/prisma/migrations/20251113181100_add_test_run_log/migration.sql +12 -12
  24. package/templates/default/prisma/migrations/20251119191838_add_tag_type/migration.sql +28 -28
  25. package/templates/default/prisma/migrations/20251121164059_add_conflict_resolution/migration.sql +12 -12
  26. package/templates/default/prisma/migrations/20251223183400_add_report_model_to_db_schema/migration.sql +10 -10
  27. package/templates/default/prisma/migrations/20251223183637_add_report_test_case_entity_for_storing_test_results_for_individual_test_cases/migration.sql +10 -10
  28. package/templates/default/prisma/migrations/20251224083549_add_comprehensive_report_storage/migration.sql +108 -108
  29. package/templates/default/prisma/migrations/20251229194422_migrate_duration_to_string/migration.sql +55 -55
  30. package/templates/default/prisma/migrations/20251230124637_add_unique_constraint_to_test_run_name/migration.sql +27 -27
  31. package/templates/default/prisma/migrations/20260115094436_add_dashboard_metrics/migration.sql +59 -59
  32. package/templates/default/prisma/migrations/20260127172022_add_cascade_delete_to_step_parameters/migration.sql +34 -34
  33. package/templates/default/prisma/schema.prisma +554 -554
  34. package/templates/default/scripts/regenerate-features.ts +94 -94
  35. package/templates/default/scripts/setup-env.ts +19 -19
  36. package/templates/default/scripts/sync-all.ts +341 -341
  37. package/templates/default/scripts/sync-appraise-base-template.ts +52 -2
  38. package/templates/default/scripts/sync-environments.ts +323 -323
  39. package/templates/default/scripts/sync-locator-groups.ts +413 -413
  40. package/templates/default/scripts/sync-locators.ts +402 -402
  41. package/templates/default/scripts/sync-modules.ts +349 -349
  42. package/templates/default/scripts/sync-tags.ts +292 -292
  43. package/templates/default/scripts/sync-template-step-groups.ts +399 -399
  44. package/templates/default/scripts/sync-template-steps.ts +806 -806
  45. package/templates/default/scripts/sync-test-cases.ts +905 -905
  46. package/templates/default/scripts/sync-test-suites.ts +411 -411
  47. package/templates/default/src/actions/conflict/conflict.action.ts +33 -33
  48. package/templates/default/src/actions/dashboard/dashboard-actions.ts +240 -240
  49. package/templates/default/src/actions/environments/environment-actions.ts +205 -205
  50. package/templates/default/src/actions/locator/locator-actions.ts +547 -547
  51. package/templates/default/src/actions/locator-groups/locator-group-actions.ts +344 -344
  52. package/templates/default/src/actions/modules/module-actions.ts +133 -133
  53. package/templates/default/src/actions/reports/report-actions.ts +613 -613
  54. package/templates/default/src/actions/review/review-actions.ts +147 -147
  55. package/templates/default/src/actions/tags/tag-actions.ts +104 -104
  56. package/templates/default/src/actions/template-step/template-step-actions.ts +332 -332
  57. package/templates/default/src/actions/template-step-group/template-step-group-actions.ts +278 -278
  58. package/templates/default/src/actions/template-test-case/template-test-case-actions.ts +238 -238
  59. package/templates/default/src/actions/test-case/test-case-actions.ts +419 -419
  60. package/templates/default/src/actions/test-run/test-run-actions.ts +1185 -1185
  61. package/templates/default/src/actions/test-suite/test-suite-actions.ts +253 -253
  62. package/templates/default/src/actions/user/user-actions.ts +13 -13
  63. package/templates/default/src/app/(base)/environments/create/page.tsx +28 -28
  64. package/templates/default/src/app/(base)/environments/environment-form.tsx +219 -219
  65. package/templates/default/src/app/(base)/environments/environment-table-columns.tsx +96 -96
  66. package/templates/default/src/app/(base)/environments/environment-table.tsx +24 -24
  67. package/templates/default/src/app/(base)/environments/modify/[id]/page.tsx +46 -46
  68. package/templates/default/src/app/(base)/environments/page.tsx +59 -59
  69. package/templates/default/src/app/(base)/layout.tsx +10 -10
  70. package/templates/default/src/app/(base)/locator-groups/create/page.tsx +44 -44
  71. package/templates/default/src/app/(base)/locator-groups/locator-group-form.tsx +215 -215
  72. package/templates/default/src/app/(base)/locator-groups/locator-group-table-columns.tsx +77 -77
  73. package/templates/default/src/app/(base)/locator-groups/locator-group-table.tsx +28 -28
  74. package/templates/default/src/app/(base)/locator-groups/modify/[id]/page.tsx +46 -46
  75. package/templates/default/src/app/(base)/locator-groups/page.tsx +61 -61
  76. package/templates/default/src/app/(base)/locators/create/page.tsx +38 -38
  77. package/templates/default/src/app/(base)/locators/locator-form.tsx +163 -163
  78. package/templates/default/src/app/(base)/locators/locator-table-columns.tsx +73 -90
  79. package/templates/default/src/app/(base)/locators/locator-table.tsx +28 -28
  80. package/templates/default/src/app/(base)/locators/modify/[id]/page.tsx +45 -45
  81. package/templates/default/src/app/(base)/locators/page.tsx +65 -65
  82. package/templates/default/src/app/(base)/locators/sync-locators-button.tsx +66 -66
  83. package/templates/default/src/app/(base)/modules/create/page.tsx +34 -34
  84. package/templates/default/src/app/(base)/modules/modify/[id]/page.tsx +46 -46
  85. package/templates/default/src/app/(base)/modules/module-form.tsx +126 -126
  86. package/templates/default/src/app/(base)/modules/module-table-columns.tsx +85 -85
  87. package/templates/default/src/app/(base)/modules/module-table.tsx +24 -24
  88. package/templates/default/src/app/(base)/modules/page.tsx +59 -59
  89. package/templates/default/src/app/(base)/reports/[id]/page.tsx +517 -517
  90. package/templates/default/src/app/(base)/reports/duration-chart.tsx +33 -33
  91. package/templates/default/src/app/(base)/reports/feature-chart.tsx +78 -78
  92. package/templates/default/src/app/(base)/reports/overview-chart.tsx +46 -46
  93. package/templates/default/src/app/(base)/reports/page.tsx +98 -98
  94. package/templates/default/src/app/(base)/reports/report-metric-card.tsx +16 -16
  95. package/templates/default/src/app/(base)/reports/report-table-columns.tsx +189 -189
  96. package/templates/default/src/app/(base)/reports/report-table.tsx +72 -72
  97. package/templates/default/src/app/(base)/reports/report-view-table-columns.tsx +131 -131
  98. package/templates/default/src/app/(base)/reports/report-view-table.tsx +82 -82
  99. package/templates/default/src/app/(base)/reports/test-cases/page.tsx +42 -42
  100. package/templates/default/src/app/(base)/reports/test-cases/test-cases-metric-table-columns.tsx +115 -115
  101. package/templates/default/src/app/(base)/reports/test-cases/test-cases-metric-table.tsx +27 -27
  102. package/templates/default/src/app/(base)/reports/test-suites/page.tsx +42 -42
  103. package/templates/default/src/app/(base)/reports/test-suites/test-suites-metric-table-columns.tsx +79 -79
  104. package/templates/default/src/app/(base)/reports/test-suites/test-suites-metric-table.tsx +27 -27
  105. package/templates/default/src/app/(base)/reports/view-logs-button.tsx +60 -60
  106. package/templates/default/src/app/(base)/reviews/create/page.tsx +26 -26
  107. package/templates/default/src/app/(base)/reviews/created-reviews-table.tsx +15 -15
  108. package/templates/default/src/app/(base)/reviews/modify/[id]/page.tsx +26 -26
  109. package/templates/default/src/app/(base)/reviews/page.tsx +26 -26
  110. package/templates/default/src/app/(base)/reviews/review/[id]/page.tsx +26 -26
  111. package/templates/default/src/app/(base)/reviews/review-form.tsx +11 -11
  112. package/templates/default/src/app/(base)/reviews/review-table-by-creator-columns.tsx +9 -9
  113. package/templates/default/src/app/(base)/reviews/review-table-by-reviewer-columns.tsx +9 -9
  114. package/templates/default/src/app/(base)/reviews/reviewer-reviews-table.tsx +15 -15
  115. package/templates/default/src/app/(base)/tags/create/page.tsx +39 -39
  116. package/templates/default/src/app/(base)/tags/modify/[id]/page.tsx +50 -50
  117. package/templates/default/src/app/(base)/tags/page.tsx +58 -58
  118. package/templates/default/src/app/(base)/tags/tag-form.tsx +147 -147
  119. package/templates/default/src/app/(base)/tags/tag-table-columns.tsx +63 -63
  120. package/templates/default/src/app/(base)/tags/tag-table.tsx +29 -29
  121. package/templates/default/src/app/(base)/template-step-groups/create/page.tsx +28 -28
  122. package/templates/default/src/app/(base)/template-step-groups/modify/[id]/page.tsx +45 -45
  123. package/templates/default/src/app/(base)/template-step-groups/page.tsx +60 -60
  124. package/templates/default/src/app/(base)/template-step-groups/template-step-group-form.tsx +167 -167
  125. package/templates/default/src/app/(base)/template-step-groups/template-step-group-table-columns.tsx +89 -89
  126. package/templates/default/src/app/(base)/template-step-groups/template-step-group-table.tsx +32 -32
  127. package/templates/default/src/app/(base)/template-steps/create/page.tsx +37 -37
  128. package/templates/default/src/app/(base)/template-steps/modify/[id]/page.tsx +49 -49
  129. package/templates/default/src/app/(base)/template-steps/page.tsx +59 -59
  130. package/templates/default/src/app/(base)/template-steps/paramChip.tsx +213 -213
  131. package/templates/default/src/app/(base)/template-steps/template-step-form.tsx +384 -384
  132. package/templates/default/src/app/(base)/template-steps/template-step-table-columns.tsx +158 -158
  133. package/templates/default/src/app/(base)/template-steps/template-step-table.tsx +24 -24
  134. package/templates/default/src/app/(base)/template-test-cases/create/page.tsx +56 -56
  135. package/templates/default/src/app/(base)/template-test-cases/modify/[id]/page.tsx +89 -89
  136. package/templates/default/src/app/(base)/template-test-cases/page.tsx +58 -58
  137. package/templates/default/src/app/(base)/template-test-cases/template-test-case-flow.tsx +84 -84
  138. package/templates/default/src/app/(base)/template-test-cases/template-test-case-form.tsx +262 -262
  139. package/templates/default/src/app/(base)/template-test-cases/template-test-case-table-columns.tsx +76 -76
  140. package/templates/default/src/app/(base)/template-test-cases/template-test-case-table.tsx +32 -32
  141. package/templates/default/src/app/(base)/test-cases/create/page.tsx +76 -76
  142. package/templates/default/src/app/(base)/test-cases/create-from-template/generate/[id]/page.tsx +96 -96
  143. package/templates/default/src/app/(base)/test-cases/create-from-template/page.tsx +38 -38
  144. package/templates/default/src/app/(base)/test-cases/create-from-template/template-selection-form.tsx +73 -73
  145. package/templates/default/src/app/(base)/test-cases/modify/[id]/page.tsx +106 -106
  146. package/templates/default/src/app/(base)/test-cases/page.tsx +60 -60
  147. package/templates/default/src/app/(base)/test-cases/test-case-flow.tsx +82 -82
  148. package/templates/default/src/app/(base)/test-cases/test-case-form.tsx +395 -395
  149. package/templates/default/src/app/(base)/test-cases/test-case-table-columns.tsx +90 -90
  150. package/templates/default/src/app/(base)/test-cases/test-case-table.tsx +35 -35
  151. package/templates/default/src/app/(base)/test-runs/[id]/page.tsx +56 -56
  152. package/templates/default/src/app/(base)/test-runs/create/page.tsx +47 -47
  153. package/templates/default/src/app/(base)/test-runs/page.tsx +60 -60
  154. package/templates/default/src/app/(base)/test-runs/test-run-form.tsx +508 -512
  155. package/templates/default/src/app/(base)/test-runs/test-run-table-columns.tsx +229 -229
  156. package/templates/default/src/app/(base)/test-runs/test-run-table.tsx +127 -127
  157. package/templates/default/src/app/(base)/test-suites/create/page.tsx +45 -45
  158. package/templates/default/src/app/(base)/test-suites/modify/[id]/page.tsx +55 -55
  159. package/templates/default/src/app/(base)/test-suites/page.tsx +82 -82
  160. package/templates/default/src/app/(base)/test-suites/test-suite-form.tsx +269 -269
  161. package/templates/default/src/app/(base)/test-suites/test-suite-table-columns.tsx +97 -97
  162. package/templates/default/src/app/(base)/test-suites/test-suite-table.tsx +29 -29
  163. package/templates/default/src/app/(dashboard-components)/app-drawer.tsx +187 -187
  164. package/templates/default/src/app/(dashboard-components)/data-card-grid.tsx +12 -12
  165. package/templates/default/src/app/(dashboard-components)/data-card.tsx +26 -26
  166. package/templates/default/src/app/(dashboard-components)/execution-health-panel.tsx +56 -56
  167. package/templates/default/src/app/(dashboard-components)/ongoing-test-runs-card.tsx +87 -87
  168. package/templates/default/src/app/(dashboard-components)/quick-actions-drawer.tsx +44 -44
  169. package/templates/default/src/app/api/test-runs/[runId]/download/route.ts +133 -133
  170. package/templates/default/src/app/api/test-runs/[runId]/logs/route.ts +420 -420
  171. package/templates/default/src/app/api/test-runs/[runId]/trace/[testCaseId]/route.ts +146 -146
  172. package/templates/default/src/app/globals.css +147 -147
  173. package/templates/default/src/app/layout.tsx +171 -171
  174. package/templates/default/src/app/page.tsx +64 -64
  175. package/templates/default/src/assets/icons/empty-tube.tsx +23 -23
  176. package/templates/default/src/assets/icons/tube-plus.tsx +29 -29
  177. package/templates/default/src/components/base-node.tsx +21 -21
  178. package/templates/default/src/components/chart/pie-chart.tsx +73 -73
  179. package/templates/default/src/components/data-extraction/locator-inspector.tsx +460 -460
  180. package/templates/default/src/components/data-state/empty-state.tsx +40 -40
  181. package/templates/default/src/components/data-visualization/info-card.tsx +70 -70
  182. package/templates/default/src/components/data-visualization/info-grid.tsx +22 -22
  183. package/templates/default/src/components/devtools/providers.tsx +19 -13
  184. package/templates/default/src/components/diagram/button-edge.tsx +54 -54
  185. package/templates/default/src/components/diagram/dynamic-parameters.tsx +438 -438
  186. package/templates/default/src/components/diagram/edit-header-option.tsx +36 -36
  187. package/templates/default/src/components/diagram/flow-diagram.tsx +470 -470
  188. package/templates/default/src/components/diagram/node-form.tsx +262 -262
  189. package/templates/default/src/components/diagram/options-header-node.tsx +57 -57
  190. package/templates/default/src/components/diagram/template-step-combobox.tsx +155 -155
  191. package/templates/default/src/components/form/error-message.tsx +7 -7
  192. package/templates/default/src/components/kokonutui/smooth-tab.tsx +453 -453
  193. package/templates/default/src/components/loading-skeleton/data-table/data-table-skeleton.tsx +30 -30
  194. package/templates/default/src/components/loading-skeleton/form/button-skeleton.tsx +8 -8
  195. package/templates/default/src/components/loading-skeleton/form/icon-button-skeleton.tsx +8 -8
  196. package/templates/default/src/components/loading-skeleton/form/text-input-skeleton.tsx +8 -8
  197. package/templates/default/src/components/loading-skeleton/visualization/table-skeleton.tsx +14 -14
  198. package/templates/default/src/components/logo.tsx +15 -15
  199. package/templates/default/src/components/navigation/command-badge.tsx +34 -34
  200. package/templates/default/src/components/navigation/command-chain-input.tsx +51 -51
  201. package/templates/default/src/components/navigation/entity-search-command.tsx +116 -116
  202. package/templates/default/src/components/navigation/nav-card.tsx +31 -31
  203. package/templates/default/src/components/navigation/nav-command.tsx +508 -508
  204. package/templates/default/src/components/navigation/nav-link.tsx +60 -60
  205. package/templates/default/src/components/navigation/nav-menu-card-deck.tsx +112 -112
  206. package/templates/default/src/components/node-header.tsx +159 -159
  207. package/templates/default/src/components/reports/test-case-logs-modal.tsx +253 -253
  208. package/templates/default/src/components/table/table-actions.tsx +172 -172
  209. package/templates/default/src/components/test-run/download-logs-button.tsx +99 -99
  210. package/templates/default/src/components/test-run/log-viewer.tsx +445 -445
  211. package/templates/default/src/components/test-run/test-run-details.tsx +611 -611
  212. package/templates/default/src/components/test-run/test-run-header.tsx +149 -149
  213. package/templates/default/src/components/test-run/view-report-button.tsx +102 -102
  214. package/templates/default/src/components/theme/mode-toggle.tsx +54 -54
  215. package/templates/default/src/components/theme/theme-provider.tsx +8 -8
  216. package/templates/default/src/components/typography/page-header-subtitle.tsx +7 -7
  217. package/templates/default/src/components/typography/page-header.tsx +7 -7
  218. package/templates/default/src/components/ui/alert-dialog.tsx +106 -106
  219. package/templates/default/src/components/ui/alert.tsx +43 -43
  220. package/templates/default/src/components/ui/avatar.tsx +40 -40
  221. package/templates/default/src/components/ui/badge.tsx +29 -29
  222. package/templates/default/src/components/ui/button.tsx +47 -47
  223. package/templates/default/src/components/ui/calendar.tsx +158 -158
  224. package/templates/default/src/components/ui/card.tsx +43 -43
  225. package/templates/default/src/components/ui/checkbox.tsx +28 -28
  226. package/templates/default/src/components/ui/command.tsx +135 -135
  227. package/templates/default/src/components/ui/data-table-column-header.tsx +61 -61
  228. package/templates/default/src/components/ui/data-table-pagination.tsx +87 -87
  229. package/templates/default/src/components/ui/data-table-view-options.tsx +50 -50
  230. package/templates/default/src/components/ui/data-table.tsx +267 -267
  231. package/templates/default/src/components/ui/dialog.tsx +97 -97
  232. package/templates/default/src/components/ui/dropdown-menu.tsx +182 -182
  233. package/templates/default/src/components/ui/input.tsx +22 -22
  234. package/templates/default/src/components/ui/kbd.tsx +28 -28
  235. package/templates/default/src/components/ui/label.tsx +19 -19
  236. package/templates/default/src/components/ui/loading.tsx +12 -12
  237. package/templates/default/src/components/ui/multi-select-with-preview.tsx +116 -116
  238. package/templates/default/src/components/ui/multi-select.tsx +142 -142
  239. package/templates/default/src/components/ui/navigation-menu.tsx +120 -120
  240. package/templates/default/src/components/ui/popover.tsx +33 -33
  241. package/templates/default/src/components/ui/progress.tsx +25 -25
  242. package/templates/default/src/components/ui/radio-group.tsx +44 -44
  243. package/templates/default/src/components/ui/scroll-area.tsx +40 -40
  244. package/templates/default/src/components/ui/select.tsx +151 -144
  245. package/templates/default/src/components/ui/separator.tsx +22 -22
  246. package/templates/default/src/components/ui/skeleton.tsx +7 -7
  247. package/templates/default/src/components/ui/table.tsx +76 -76
  248. package/templates/default/src/components/ui/tabs.tsx +55 -55
  249. package/templates/default/src/components/ui/textarea.tsx +21 -21
  250. package/templates/default/src/components/ui/toast.tsx +113 -113
  251. package/templates/default/src/components/ui/toaster.tsx +26 -26
  252. package/templates/default/src/components/user-prompt/delete-prompt.tsx +87 -87
  253. package/templates/default/src/config/db-config.ts +10 -10
  254. package/templates/default/src/constants/form-opts/diagram/node-form.ts +30 -30
  255. package/templates/default/src/constants/form-opts/environment-form-opts.ts +24 -24
  256. package/templates/default/src/constants/form-opts/locator-form-opts.ts +20 -20
  257. package/templates/default/src/constants/form-opts/locator-group-form-opts.ts +28 -28
  258. package/templates/default/src/constants/form-opts/module-form-opts.ts +21 -21
  259. package/templates/default/src/constants/form-opts/review-form-opts.ts +23 -23
  260. package/templates/default/src/constants/form-opts/tag-form-opts.ts +42 -42
  261. package/templates/default/src/constants/form-opts/template-selection-form-opts.ts +16 -16
  262. package/templates/default/src/constants/form-opts/template-step-group-form-opts.ts +24 -24
  263. package/templates/default/src/constants/form-opts/template-test-case-form-opts.ts +39 -39
  264. package/templates/default/src/constants/form-opts/template-test-step-form-opts.ts +36 -36
  265. package/templates/default/src/constants/form-opts/test-case-form-opts.ts +43 -43
  266. package/templates/default/src/constants/form-opts/test-run-form-opts.ts +31 -31
  267. package/templates/default/src/constants/form-opts/test-suite-form-opts.ts +24 -24
  268. package/templates/default/src/hooks/use-toast.ts +187 -187
  269. package/templates/default/src/lib/bidirectional-sync.ts +432 -432
  270. package/templates/default/src/lib/database-sync.ts +531 -531
  271. package/templates/default/src/lib/environment-file-utils.ts +221 -221
  272. package/templates/default/src/lib/feature-file-generator.ts +411 -411
  273. package/templates/default/src/lib/gherkin-parser.ts +259 -259
  274. package/templates/default/src/lib/locator-group-file-utils.ts +370 -370
  275. package/templates/default/src/lib/metrics/metric-calculator.ts +613 -613
  276. package/templates/default/src/lib/module-hierarchy-builder.ts +205 -205
  277. package/templates/default/src/lib/path-helpers/module-path.ts +71 -71
  278. package/templates/default/src/lib/test-case-utils.ts +6 -6
  279. package/templates/default/src/lib/test-run/log-formatter.ts +83 -83
  280. package/templates/default/src/lib/test-run/process-manager.ts +191 -191
  281. package/templates/default/src/lib/test-run/report-parser.ts +316 -316
  282. package/templates/default/src/lib/test-run/test-run-executor.ts +144 -144
  283. package/templates/default/src/lib/test-run/winston-logger.ts +95 -95
  284. package/templates/default/src/lib/transformers/gherkin-converter.ts +42 -42
  285. package/templates/default/src/lib/transformers/key-to-icon-transformer.tsx +95 -95
  286. package/templates/default/src/lib/transformers/template-test-case-converter.ts +160 -160
  287. package/templates/default/src/lib/utils/node-param-validation.ts +81 -81
  288. package/templates/default/src/lib/utils/template-step-file-generator.ts +167 -167
  289. package/templates/default/src/lib/utils/template-step-file-manager-intelligent.ts +723 -723
  290. package/templates/default/src/lib/utils/template-step-file-manager.ts +166 -166
  291. package/templates/default/src/lib/utils.ts +31 -31
  292. package/templates/default/src/tests/config/executor/world.ts +41 -41
  293. package/templates/default/src/tests/executor.ts +80 -80
  294. package/templates/default/src/tests/hooks/hooks.ts +99 -99
  295. package/templates/default/src/tests/mapping/locator-map.json +1 -1
  296. package/templates/default/src/tests/steps/actions/click.step.ts +62 -62
  297. package/templates/default/src/tests/steps/actions/navigation.step.ts +73 -72
  298. package/templates/default/src/tests/steps/validations/active_state_assertion.step.ts +34 -34
  299. package/templates/default/src/tests/steps/validations/navigation_assertion.step.ts +24 -23
  300. package/templates/default/src/tests/steps/validations/text_assertion.step.ts +111 -111
  301. package/templates/default/src/tests/steps/validations/visibility_assertion.step.ts +30 -30
  302. package/templates/default/src/tests/support/parameter-types.ts +12 -12
  303. package/templates/default/src/tests/utils/cache.util.ts +260 -260
  304. package/templates/default/src/tests/utils/cli.util.ts +177 -177
  305. package/templates/default/src/tests/utils/environment.util.ts +65 -65
  306. package/templates/default/src/tests/utils/locator.util.ts +248 -248
  307. package/templates/default/src/tests/utils/random-data.util.ts +44 -44
  308. package/templates/default/src/tests/utils/spawner.util.ts +617 -617
  309. package/templates/default/src/types/diagram/diagram.ts +34 -34
  310. package/templates/default/src/types/diagram/template-step.ts +11 -11
  311. package/templates/default/src/types/executor/browser.type.ts +1 -1
  312. package/templates/default/src/types/form/actionHandler.ts +6 -6
  313. package/templates/default/src/types/locator/locator.type.ts +11 -11
  314. package/templates/default/src/types/step/step.type.ts +1 -1
  315. package/templates/default/src/types/table/data-table.ts +6 -6
  316. package/templates/default/tailwind.config.ts +62 -62
  317. package/templates/default/.env +0 -2
  318. package/templates/default/next-env.d.ts +0 -6
  319. package/templates/default/prisma/prisma/dev.db +0 -0
  320. package/templates/default/src/tests/config/environments/environments.json +0 -14
@@ -1,411 +1,411 @@
1
- import { promises as fs } from 'fs'
2
- import { join, dirname } from 'path'
3
- import prisma from '@/config/db-config'
4
- import { buildModulePath } from '@/lib/path-helpers/module-path'
5
-
6
- /**
7
- * Checks if a directory is empty (no files or subdirectories)
8
- * @param dirPath - Path to the directory to check
9
- * @returns Promise<boolean> - True if directory is empty, false otherwise
10
- */
11
- async function isDirectoryEmpty(dirPath: string): Promise<boolean> {
12
- try {
13
- const entries = await fs.readdir(dirPath)
14
- return entries.length === 0
15
- } catch (error) {
16
- // If directory doesn't exist or can't be read, consider it empty
17
- console.warn(`Could not read directory ${dirPath}:`, error)
18
- return true
19
- }
20
- }
21
-
22
- /**
23
- * Removes empty directories up the hierarchy until a non-empty directory is found
24
- * @param dirPath - Starting directory path to clean up
25
- * @param basePath - Base path to stop cleaning (e.g., features directory)
26
- * @returns Promise<void>
27
- */
28
- async function removeEmptyDirectoriesUp(dirPath: string, basePath: string): Promise<void> {
29
- let currentPath = dirPath
30
-
31
- // Keep going up the directory tree until we reach the base path
32
- while (currentPath !== basePath && currentPath !== dirname(currentPath)) {
33
- try {
34
- // Check if current directory is empty
35
- if (await isDirectoryEmpty(currentPath)) {
36
- await fs.rmdir(currentPath)
37
- console.log(`Removed empty directory: ${currentPath}`)
38
- // Move up one level
39
- currentPath = dirname(currentPath)
40
- } else {
41
- // Directory is not empty, stop cleaning
42
- break
43
- }
44
- } catch (error) {
45
- // If we can't remove the directory or it doesn't exist, stop
46
- console.warn(`Could not remove directory ${currentPath}:`, error)
47
- break
48
- }
49
- }
50
- }
51
-
52
- /**
53
- * Generates a Gherkin feature file for a test suite
54
- * @param testSuiteId - The ID of the test suite
55
- * @param testSuiteName - The name of the test suite
56
- * @param testSuiteDescription - The description of the test suite
57
- * @param moduleName - The name of the module the test suite belongs to
58
- * @returns Promise<string> - The path to the generated feature file
59
- */
60
- export async function generateFeatureFile(
61
- testSuiteId: string,
62
- testSuiteName: string,
63
- testSuiteDescription?: string,
64
- ): Promise<string> {
65
- try {
66
- // Fetch test suite with test cases, steps, tags, and all modules for path building
67
- const [testSuite, allModules] = await Promise.all([
68
- prisma.testSuite.findUnique({
69
- where: { id: testSuiteId },
70
- include: {
71
- testCases: {
72
- include: {
73
- steps: {
74
- include: {
75
- parameters: true,
76
- },
77
- orderBy: {
78
- order: 'asc',
79
- },
80
- },
81
- tags: true,
82
- },
83
- },
84
- module: true,
85
- tags: true,
86
- },
87
- }),
88
- prisma.module.findMany(), // Get all modules to build hierarchy path
89
- ])
90
-
91
- if (!testSuite) {
92
- throw new Error(`Test suite with ID ${testSuiteId} not found`)
93
- }
94
-
95
- // Build the module path for directory structure
96
- const modulePath = buildModulePath(allModules, testSuite.module)
97
-
98
- // Generate feature file content
99
- const featureContent = generateFeatureContent(
100
- testSuiteDescription || testSuiteName, // Use description as feature title, fallback to name
101
- testSuite.testCases,
102
- testSuite.tags,
103
- )
104
-
105
- // Create the features directory with module path
106
- const featuresBaseDir = join(process.cwd(), 'src', 'tests', 'features')
107
- const moduleDir = join(featuresBaseDir, modulePath.substring(1)) // Remove leading slash
108
- await fs.mkdir(moduleDir, { recursive: true })
109
-
110
- // Generate a safe filename from the test suite name only
111
- const safeFileName = generateSafeFileName(testSuiteName)
112
- const featureFilePath = join(moduleDir, `${safeFileName}.feature`)
113
-
114
- // Write the feature file
115
- await fs.writeFile(featureFilePath, featureContent, 'utf8')
116
-
117
- return featureFilePath
118
- } catch (error) {
119
- console.error('Error generating feature file:', error)
120
- throw error
121
- }
122
- }
123
-
124
- /**
125
- * Generates the content for a Gherkin feature file
126
- */
127
- function generateFeatureContent(
128
- featureTitle: string,
129
- testCases: Array<{
130
- title: string
131
- description: string
132
- steps: Array<{
133
- gherkinStep: string
134
- order: number
135
- }>
136
- tags?: Array<{
137
- tagExpression: string
138
- }>
139
- }>,
140
- testSuiteTags?: Array<{
141
- tagExpression: string
142
- }>,
143
- ): string {
144
- const lines: string[] = []
145
-
146
- // Warning header
147
- lines.push('# AUTO-GENERATED FILE - DO NOT EDIT MANUALLY')
148
- lines.push('# This file is automatically generated from Test Suite data.')
149
- lines.push('# Any manual changes will be overwritten when the Test Suite is updated.')
150
- lines.push('# To modify this feature, update the corresponding Test Suite in the application.')
151
- lines.push('')
152
-
153
- // Add feature-level tags (one tag per line)
154
- if (testSuiteTags && testSuiteTags.length > 0) {
155
- testSuiteTags.forEach(tag => {
156
- lines.push(tag.tagExpression)
157
- })
158
- }
159
-
160
- // Feature header - use description as feature title
161
- lines.push(`Feature: ${featureTitle}`)
162
- lines.push('')
163
-
164
- // Get test suite tag expressions for deduplication
165
- const testSuiteTagExpressions = new Set((testSuiteTags || []).map(tag => tag.tagExpression.toLowerCase()))
166
-
167
- // Generate scenarios for each test case that has steps
168
- let scenarioCount = 0
169
- testCases.forEach(testCase => {
170
- // Only generate scenario if test case has steps
171
- if (testCase.steps && testCase.steps.length > 0) {
172
- // Generate Gherkin steps from test case steps
173
- const gherkinSteps = generateGherkinStepsFromTestCase(testCase.steps)
174
-
175
- // Only add scenario if there are actual gherkin steps
176
- if (gherkinSteps.length > 0) {
177
- if (scenarioCount > 0) {
178
- lines.push('') // Add blank line between scenarios
179
- }
180
-
181
- // Add scenario-level tags (skip if already present at feature level)
182
- if (testCase.tags && testCase.tags.length > 0) {
183
- testCase.tags.forEach(tag => {
184
- // Only add tag if it's not already present at feature level
185
- if (!testSuiteTagExpressions.has(tag.tagExpression.toLowerCase())) {
186
- lines.push(` ${tag.tagExpression}`)
187
- }
188
- })
189
- }
190
-
191
- lines.push(` Scenario: [${testCase.title}] ${testCase.description}`)
192
-
193
- gherkinSteps.forEach(step => {
194
- lines.push(` ${step}`)
195
- })
196
-
197
- scenarioCount++
198
- }
199
- }
200
- })
201
-
202
- return lines.join('\n') + '\n'
203
- }
204
-
205
- /**
206
- * Generates Gherkin steps from test case steps using the same logic as the frontend
207
- */
208
- function generateGherkinStepsFromTestCase(
209
- steps: Array<{
210
- gherkinStep: string
211
- order: number
212
- }>,
213
- ): string[] {
214
- if (!steps || steps.length === 0) {
215
- return []
216
- }
217
-
218
- // Sort steps by order
219
- const sortedSteps = steps.sort((a, b) => a.order - b.order)
220
-
221
- let hasThenInPrevious = false
222
- let hasWhenInPrevious = false
223
-
224
- return sortedSteps.map((step, index) => {
225
- const gherkinStep = step.gherkinStep?.trim() || ''
226
- const firstWord = gherkinStep.split(' ')[0].toLowerCase()
227
- const hasGherkinKeyword = ['given', 'when', 'then', 'and', 'but'].includes(firstWord)
228
- const stepWithoutKeyword = hasGherkinKeyword ? gherkinStep.split(' ').slice(1).join(' ') : gherkinStep
229
-
230
- // First step always starts with Given
231
- if (index === 0) {
232
- return `Given ${stepWithoutKeyword}`
233
- }
234
-
235
- // Check if this step should be a Then statement
236
- const isThenStatement =
237
- firstWord === 'then' ||
238
- stepWithoutKeyword.toLowerCase().startsWith('should') ||
239
- stepWithoutKeyword.toLowerCase().startsWith('must') ||
240
- stepWithoutKeyword.toLowerCase().startsWith('will')
241
-
242
- // If we haven't seen a Then yet
243
- if (!hasThenInPrevious) {
244
- // If this is a Then statement
245
- if (isThenStatement) {
246
- hasThenInPrevious = true
247
- return `Then ${stepWithoutKeyword}`
248
- }
249
-
250
- // If we haven't seen a When yet, use When
251
- if (!hasWhenInPrevious) {
252
- hasWhenInPrevious = true
253
- return `When ${stepWithoutKeyword}`
254
- }
255
- // After When, use And
256
- return `And ${stepWithoutKeyword}`
257
- }
258
-
259
- // After Then
260
- if (isThenStatement) {
261
- // If it's another Then statement, use And
262
- return `And ${stepWithoutKeyword}`
263
- }
264
- // After Then, use When for new actions
265
- hasThenInPrevious = false
266
- hasWhenInPrevious = true
267
- return `When ${stepWithoutKeyword}`
268
- })
269
- }
270
-
271
- /**
272
- * Deletes a feature file for a test suite
273
- * @param testSuiteId - The ID of the test suite
274
- * @returns Promise<boolean> - True if file was deleted, false if file didn't exist
275
- */
276
- export async function deleteFeatureFile(testSuiteId: string): Promise<boolean> {
277
- try {
278
- // Fetch test suite and all modules to build the correct path
279
- const [testSuite, allModules] = await Promise.all([
280
- prisma.testSuite.findUnique({
281
- where: { id: testSuiteId },
282
- include: {
283
- module: true,
284
- },
285
- }),
286
- prisma.module.findMany(),
287
- ])
288
-
289
- if (!testSuite) {
290
- console.warn(`Test suite with ID ${testSuiteId} not found for feature file deletion`)
291
- return false
292
- }
293
-
294
- // Build the module path for directory structure
295
- const modulePath = buildModulePath(allModules, testSuite.module)
296
- const safeFileName = generateSafeFileName(testSuite.name)
297
-
298
- const featuresBaseDir = join(process.cwd(), 'src', 'tests', 'features')
299
- const moduleDir = join(featuresBaseDir, modulePath.substring(1)) // Remove leading slash
300
- const featureFilePath = join(moduleDir, `${safeFileName}.feature`)
301
-
302
- try {
303
- await fs.unlink(featureFilePath)
304
- console.log(`Feature file deleted: ${featureFilePath}`)
305
-
306
- // Clean up empty directories up the module hierarchy
307
- await removeEmptyDirectoriesUp(moduleDir, featuresBaseDir)
308
-
309
- return true
310
- } catch (error: unknown) {
311
- if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
312
- console.warn(`Feature file not found for deletion: ${featureFilePath}`)
313
- return false
314
- }
315
- throw error
316
- }
317
- } catch (error) {
318
- console.error('Error deleting feature file:', error)
319
- throw error
320
- }
321
- }
322
-
323
- /**
324
- * Regenerates all feature files from the current database state
325
- * This is useful after merging changes or database migrations to ensure sync
326
- * @returns Promise<string[]> - Array of generated feature file paths
327
- */
328
- export async function regenerateAllFeatureFiles(): Promise<string[]> {
329
- try {
330
- console.log('Starting regeneration of all feature files...')
331
-
332
- // Clear existing feature files directory
333
- const featuresBaseDir = join(process.cwd(), 'src', 'tests', 'features')
334
- try {
335
- await fs.rm(featuresBaseDir, { recursive: true, force: true })
336
- } catch (error) {
337
- console.warn('Could not clear features directory:', error)
338
- }
339
-
340
- // Fetch all test suites from database
341
- const testSuites = await prisma.testSuite.findMany({
342
- include: {
343
- testCases: {
344
- include: {
345
- steps: {
346
- include: {
347
- parameters: true,
348
- },
349
- orderBy: {
350
- order: 'asc',
351
- },
352
- },
353
- tags: true,
354
- },
355
- },
356
- module: true,
357
- tags: true,
358
- },
359
- })
360
-
361
- // Fetch all modules for path building
362
- const allModules = await prisma.module.findMany()
363
-
364
- const generatedFiles: string[] = []
365
-
366
- // Generate feature file for each test suite
367
- for (const testSuite of testSuites) {
368
- try {
369
- const modulePath = buildModulePath(allModules, testSuite.module)
370
- const featureContent = generateFeatureContent(
371
- testSuite.description || testSuite.name,
372
- testSuite.testCases,
373
- testSuite.tags,
374
- )
375
-
376
- // Create the features directory with module path
377
- const moduleDir = join(featuresBaseDir, modulePath.substring(1)) // Remove leading slash
378
- await fs.mkdir(moduleDir, { recursive: true })
379
-
380
- // Generate filename and write file
381
- const safeFileName = generateSafeFileName(testSuite.name)
382
- const featureFilePath = join(moduleDir, `${safeFileName}.feature`)
383
-
384
- await fs.writeFile(featureFilePath, featureContent, 'utf8')
385
- generatedFiles.push(featureFilePath)
386
-
387
- console.log(`Generated: ${featureFilePath}`)
388
- } catch (error) {
389
- console.error(`Error generating feature file for test suite ${testSuite.name}:`, error)
390
- }
391
- }
392
-
393
- console.log(`Regeneration complete. Generated ${generatedFiles.length} feature files.`)
394
- return generatedFiles
395
- } catch (error) {
396
- console.error('Error during feature files regeneration:', error)
397
- throw error
398
- }
399
- }
400
-
401
- /**
402
- * Generates a safe filename from test suite name
403
- */
404
- function generateSafeFileName(testSuiteName: string): string {
405
- // Convert to lowercase and replace spaces and special characters with hyphens
406
- return testSuiteName
407
- .toLowerCase()
408
- .replace(/[^a-z0-9]+/g, '-')
409
- .replace(/^-+|-+$/g, '') // Remove leading/trailing hyphens
410
- .replace(/-+/g, '-') // Replace multiple consecutive hyphens with single hyphen
411
- }
1
+ import { promises as fs } from 'fs'
2
+ import { join, dirname } from 'path'
3
+ import prisma from '@/config/db-config'
4
+ import { buildModulePath } from '@/lib/path-helpers/module-path'
5
+
6
+ /**
7
+ * Checks if a directory is empty (no files or subdirectories)
8
+ * @param dirPath - Path to the directory to check
9
+ * @returns Promise<boolean> - True if directory is empty, false otherwise
10
+ */
11
+ async function isDirectoryEmpty(dirPath: string): Promise<boolean> {
12
+ try {
13
+ const entries = await fs.readdir(dirPath)
14
+ return entries.length === 0
15
+ } catch (error) {
16
+ // If directory doesn't exist or can't be read, consider it empty
17
+ console.warn(`Could not read directory ${dirPath}:`, error)
18
+ return true
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Removes empty directories up the hierarchy until a non-empty directory is found
24
+ * @param dirPath - Starting directory path to clean up
25
+ * @param basePath - Base path to stop cleaning (e.g., features directory)
26
+ * @returns Promise<void>
27
+ */
28
+ async function removeEmptyDirectoriesUp(dirPath: string, basePath: string): Promise<void> {
29
+ let currentPath = dirPath
30
+
31
+ // Keep going up the directory tree until we reach the base path
32
+ while (currentPath !== basePath && currentPath !== dirname(currentPath)) {
33
+ try {
34
+ // Check if current directory is empty
35
+ if (await isDirectoryEmpty(currentPath)) {
36
+ await fs.rmdir(currentPath)
37
+ console.log(`Removed empty directory: ${currentPath}`)
38
+ // Move up one level
39
+ currentPath = dirname(currentPath)
40
+ } else {
41
+ // Directory is not empty, stop cleaning
42
+ break
43
+ }
44
+ } catch (error) {
45
+ // If we can't remove the directory or it doesn't exist, stop
46
+ console.warn(`Could not remove directory ${currentPath}:`, error)
47
+ break
48
+ }
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Generates a Gherkin feature file for a test suite
54
+ * @param testSuiteId - The ID of the test suite
55
+ * @param testSuiteName - The name of the test suite
56
+ * @param testSuiteDescription - The description of the test suite
57
+ * @param moduleName - The name of the module the test suite belongs to
58
+ * @returns Promise<string> - The path to the generated feature file
59
+ */
60
+ export async function generateFeatureFile(
61
+ testSuiteId: string,
62
+ testSuiteName: string,
63
+ testSuiteDescription?: string,
64
+ ): Promise<string> {
65
+ try {
66
+ // Fetch test suite with test cases, steps, tags, and all modules for path building
67
+ const [testSuite, allModules] = await Promise.all([
68
+ prisma.testSuite.findUnique({
69
+ where: { id: testSuiteId },
70
+ include: {
71
+ testCases: {
72
+ include: {
73
+ steps: {
74
+ include: {
75
+ parameters: true,
76
+ },
77
+ orderBy: {
78
+ order: 'asc',
79
+ },
80
+ },
81
+ tags: true,
82
+ },
83
+ },
84
+ module: true,
85
+ tags: true,
86
+ },
87
+ }),
88
+ prisma.module.findMany(), // Get all modules to build hierarchy path
89
+ ])
90
+
91
+ if (!testSuite) {
92
+ throw new Error(`Test suite with ID ${testSuiteId} not found`)
93
+ }
94
+
95
+ // Build the module path for directory structure
96
+ const modulePath = buildModulePath(allModules, testSuite.module)
97
+
98
+ // Generate feature file content
99
+ const featureContent = generateFeatureContent(
100
+ testSuiteDescription || testSuiteName, // Use description as feature title, fallback to name
101
+ testSuite.testCases,
102
+ testSuite.tags,
103
+ )
104
+
105
+ // Create the features directory with module path
106
+ const featuresBaseDir = join(process.cwd(), 'src', 'tests', 'features')
107
+ const moduleDir = join(featuresBaseDir, modulePath.substring(1)) // Remove leading slash
108
+ await fs.mkdir(moduleDir, { recursive: true })
109
+
110
+ // Generate a safe filename from the test suite name only
111
+ const safeFileName = generateSafeFileName(testSuiteName)
112
+ const featureFilePath = join(moduleDir, `${safeFileName}.feature`)
113
+
114
+ // Write the feature file
115
+ await fs.writeFile(featureFilePath, featureContent, 'utf8')
116
+
117
+ return featureFilePath
118
+ } catch (error) {
119
+ console.error('Error generating feature file:', error)
120
+ throw error
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Generates the content for a Gherkin feature file
126
+ */
127
+ function generateFeatureContent(
128
+ featureTitle: string,
129
+ testCases: Array<{
130
+ title: string
131
+ description: string
132
+ steps: Array<{
133
+ gherkinStep: string
134
+ order: number
135
+ }>
136
+ tags?: Array<{
137
+ tagExpression: string
138
+ }>
139
+ }>,
140
+ testSuiteTags?: Array<{
141
+ tagExpression: string
142
+ }>,
143
+ ): string {
144
+ const lines: string[] = []
145
+
146
+ // Warning header
147
+ lines.push('# AUTO-GENERATED FILE - DO NOT EDIT MANUALLY')
148
+ lines.push('# This file is automatically generated from Test Suite data.')
149
+ lines.push('# Any manual changes will be overwritten when the Test Suite is updated.')
150
+ lines.push('# To modify this feature, update the corresponding Test Suite in the application.')
151
+ lines.push('')
152
+
153
+ // Add feature-level tags (one tag per line)
154
+ if (testSuiteTags && testSuiteTags.length > 0) {
155
+ testSuiteTags.forEach(tag => {
156
+ lines.push(tag.tagExpression)
157
+ })
158
+ }
159
+
160
+ // Feature header - use description as feature title
161
+ lines.push(`Feature: ${featureTitle}`)
162
+ lines.push('')
163
+
164
+ // Get test suite tag expressions for deduplication
165
+ const testSuiteTagExpressions = new Set((testSuiteTags || []).map(tag => tag.tagExpression.toLowerCase()))
166
+
167
+ // Generate scenarios for each test case that has steps
168
+ let scenarioCount = 0
169
+ testCases.forEach(testCase => {
170
+ // Only generate scenario if test case has steps
171
+ if (testCase.steps && testCase.steps.length > 0) {
172
+ // Generate Gherkin steps from test case steps
173
+ const gherkinSteps = generateGherkinStepsFromTestCase(testCase.steps)
174
+
175
+ // Only add scenario if there are actual gherkin steps
176
+ if (gherkinSteps.length > 0) {
177
+ if (scenarioCount > 0) {
178
+ lines.push('') // Add blank line between scenarios
179
+ }
180
+
181
+ // Add scenario-level tags (skip if already present at feature level)
182
+ if (testCase.tags && testCase.tags.length > 0) {
183
+ testCase.tags.forEach(tag => {
184
+ // Only add tag if it's not already present at feature level
185
+ if (!testSuiteTagExpressions.has(tag.tagExpression.toLowerCase())) {
186
+ lines.push(` ${tag.tagExpression}`)
187
+ }
188
+ })
189
+ }
190
+
191
+ lines.push(` Scenario: [${testCase.title}] ${testCase.description}`)
192
+
193
+ gherkinSteps.forEach(step => {
194
+ lines.push(` ${step}`)
195
+ })
196
+
197
+ scenarioCount++
198
+ }
199
+ }
200
+ })
201
+
202
+ return lines.join('\n') + '\n'
203
+ }
204
+
205
+ /**
206
+ * Generates Gherkin steps from test case steps using the same logic as the frontend
207
+ */
208
+ function generateGherkinStepsFromTestCase(
209
+ steps: Array<{
210
+ gherkinStep: string
211
+ order: number
212
+ }>,
213
+ ): string[] {
214
+ if (!steps || steps.length === 0) {
215
+ return []
216
+ }
217
+
218
+ // Sort steps by order
219
+ const sortedSteps = steps.sort((a, b) => a.order - b.order)
220
+
221
+ let hasThenInPrevious = false
222
+ let hasWhenInPrevious = false
223
+
224
+ return sortedSteps.map((step, index) => {
225
+ const gherkinStep = step.gherkinStep?.trim() || ''
226
+ const firstWord = gherkinStep.split(' ')[0].toLowerCase()
227
+ const hasGherkinKeyword = ['given', 'when', 'then', 'and', 'but'].includes(firstWord)
228
+ const stepWithoutKeyword = hasGherkinKeyword ? gherkinStep.split(' ').slice(1).join(' ') : gherkinStep
229
+
230
+ // First step always starts with Given
231
+ if (index === 0) {
232
+ return `Given ${stepWithoutKeyword}`
233
+ }
234
+
235
+ // Check if this step should be a Then statement
236
+ const isThenStatement =
237
+ firstWord === 'then' ||
238
+ stepWithoutKeyword.toLowerCase().startsWith('should') ||
239
+ stepWithoutKeyword.toLowerCase().startsWith('must') ||
240
+ stepWithoutKeyword.toLowerCase().startsWith('will')
241
+
242
+ // If we haven't seen a Then yet
243
+ if (!hasThenInPrevious) {
244
+ // If this is a Then statement
245
+ if (isThenStatement) {
246
+ hasThenInPrevious = true
247
+ return `Then ${stepWithoutKeyword}`
248
+ }
249
+
250
+ // If we haven't seen a When yet, use When
251
+ if (!hasWhenInPrevious) {
252
+ hasWhenInPrevious = true
253
+ return `When ${stepWithoutKeyword}`
254
+ }
255
+ // After When, use And
256
+ return `And ${stepWithoutKeyword}`
257
+ }
258
+
259
+ // After Then
260
+ if (isThenStatement) {
261
+ // If it's another Then statement, use And
262
+ return `And ${stepWithoutKeyword}`
263
+ }
264
+ // After Then, use When for new actions
265
+ hasThenInPrevious = false
266
+ hasWhenInPrevious = true
267
+ return `When ${stepWithoutKeyword}`
268
+ })
269
+ }
270
+
271
+ /**
272
+ * Deletes a feature file for a test suite
273
+ * @param testSuiteId - The ID of the test suite
274
+ * @returns Promise<boolean> - True if file was deleted, false if file didn't exist
275
+ */
276
+ export async function deleteFeatureFile(testSuiteId: string): Promise<boolean> {
277
+ try {
278
+ // Fetch test suite and all modules to build the correct path
279
+ const [testSuite, allModules] = await Promise.all([
280
+ prisma.testSuite.findUnique({
281
+ where: { id: testSuiteId },
282
+ include: {
283
+ module: true,
284
+ },
285
+ }),
286
+ prisma.module.findMany(),
287
+ ])
288
+
289
+ if (!testSuite) {
290
+ console.warn(`Test suite with ID ${testSuiteId} not found for feature file deletion`)
291
+ return false
292
+ }
293
+
294
+ // Build the module path for directory structure
295
+ const modulePath = buildModulePath(allModules, testSuite.module)
296
+ const safeFileName = generateSafeFileName(testSuite.name)
297
+
298
+ const featuresBaseDir = join(process.cwd(), 'src', 'tests', 'features')
299
+ const moduleDir = join(featuresBaseDir, modulePath.substring(1)) // Remove leading slash
300
+ const featureFilePath = join(moduleDir, `${safeFileName}.feature`)
301
+
302
+ try {
303
+ await fs.unlink(featureFilePath)
304
+ console.log(`Feature file deleted: ${featureFilePath}`)
305
+
306
+ // Clean up empty directories up the module hierarchy
307
+ await removeEmptyDirectoriesUp(moduleDir, featuresBaseDir)
308
+
309
+ return true
310
+ } catch (error: unknown) {
311
+ if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
312
+ console.warn(`Feature file not found for deletion: ${featureFilePath}`)
313
+ return false
314
+ }
315
+ throw error
316
+ }
317
+ } catch (error) {
318
+ console.error('Error deleting feature file:', error)
319
+ throw error
320
+ }
321
+ }
322
+
323
+ /**
324
+ * Regenerates all feature files from the current database state
325
+ * This is useful after merging changes or database migrations to ensure sync
326
+ * @returns Promise<string[]> - Array of generated feature file paths
327
+ */
328
+ export async function regenerateAllFeatureFiles(): Promise<string[]> {
329
+ try {
330
+ console.log('Starting regeneration of all feature files...')
331
+
332
+ // Clear existing feature files directory
333
+ const featuresBaseDir = join(process.cwd(), 'src', 'tests', 'features')
334
+ try {
335
+ await fs.rm(featuresBaseDir, { recursive: true, force: true })
336
+ } catch (error) {
337
+ console.warn('Could not clear features directory:', error)
338
+ }
339
+
340
+ // Fetch all test suites from database
341
+ const testSuites = await prisma.testSuite.findMany({
342
+ include: {
343
+ testCases: {
344
+ include: {
345
+ steps: {
346
+ include: {
347
+ parameters: true,
348
+ },
349
+ orderBy: {
350
+ order: 'asc',
351
+ },
352
+ },
353
+ tags: true,
354
+ },
355
+ },
356
+ module: true,
357
+ tags: true,
358
+ },
359
+ })
360
+
361
+ // Fetch all modules for path building
362
+ const allModules = await prisma.module.findMany()
363
+
364
+ const generatedFiles: string[] = []
365
+
366
+ // Generate feature file for each test suite
367
+ for (const testSuite of testSuites) {
368
+ try {
369
+ const modulePath = buildModulePath(allModules, testSuite.module)
370
+ const featureContent = generateFeatureContent(
371
+ testSuite.description || testSuite.name,
372
+ testSuite.testCases,
373
+ testSuite.tags,
374
+ )
375
+
376
+ // Create the features directory with module path
377
+ const moduleDir = join(featuresBaseDir, modulePath.substring(1)) // Remove leading slash
378
+ await fs.mkdir(moduleDir, { recursive: true })
379
+
380
+ // Generate filename and write file
381
+ const safeFileName = generateSafeFileName(testSuite.name)
382
+ const featureFilePath = join(moduleDir, `${safeFileName}.feature`)
383
+
384
+ await fs.writeFile(featureFilePath, featureContent, 'utf8')
385
+ generatedFiles.push(featureFilePath)
386
+
387
+ console.log(`Generated: ${featureFilePath}`)
388
+ } catch (error) {
389
+ console.error(`Error generating feature file for test suite ${testSuite.name}:`, error)
390
+ }
391
+ }
392
+
393
+ console.log(`Regeneration complete. Generated ${generatedFiles.length} feature files.`)
394
+ return generatedFiles
395
+ } catch (error) {
396
+ console.error('Error during feature files regeneration:', error)
397
+ throw error
398
+ }
399
+ }
400
+
401
+ /**
402
+ * Generates a safe filename from test suite name
403
+ */
404
+ function generateSafeFileName(testSuiteName: string): string {
405
+ // Convert to lowercase and replace spaces and special characters with hyphens
406
+ return testSuiteName
407
+ .toLowerCase()
408
+ .replace(/[^a-z0-9]+/g, '-')
409
+ .replace(/^-+|-+$/g, '') // Remove leading/trailing hyphens
410
+ .replace(/-+/g, '-') // Replace multiple consecutive hyphens with single hyphen
411
+ }