create-appraisejs 0.1.7 → 0.1.8

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 (316) hide show
  1. package/README.md +45 -45
  2. package/dist/cli.js +2 -2
  3. package/dist/cli.js.map +1 -1
  4. package/dist/copy-template.d.ts.map +1 -1
  5. package/dist/copy-template.js.map +1 -1
  6. package/package.json +69 -67
  7. package/templates/default/.vscode/settings.json +5 -0
  8. package/templates/default/appraisejs.config.json +1 -1
  9. package/templates/default/components.json +24 -24
  10. package/templates/default/cucumber.mjs +16 -0
  11. package/templates/default/eslint.config.mjs +15 -15
  12. package/templates/default/next.config.ts +13 -7
  13. package/templates/default/package-lock.json +13732 -14321
  14. package/templates/default/package.json +11 -9
  15. package/templates/default/postcss.config.mjs +8 -8
  16. package/templates/default/prisma/migrations/20251104113456_add_type_for_template_step_groups/migration.sql +16 -16
  17. package/templates/default/prisma/migrations/20251104170946_add_tags_to_test_suite_and_test_case/migration.sql +27 -27
  18. package/templates/default/prisma/migrations/20251112190024_add_cascade_delete_to_test_run_test_case/migration.sql +17 -17
  19. package/templates/default/prisma/migrations/20251113181100_add_test_run_log/migration.sql +12 -12
  20. package/templates/default/prisma/migrations/20251119191838_add_tag_type/migration.sql +28 -28
  21. package/templates/default/prisma/migrations/20251121164059_add_conflict_resolution/migration.sql +12 -12
  22. package/templates/default/prisma/migrations/20251223183400_add_report_model_to_db_schema/migration.sql +10 -10
  23. package/templates/default/prisma/migrations/20251223183637_add_report_test_case_entity_for_storing_test_results_for_individual_test_cases/migration.sql +10 -10
  24. package/templates/default/prisma/migrations/20251224083549_add_comprehensive_report_storage/migration.sql +108 -108
  25. package/templates/default/prisma/migrations/20251229194422_migrate_duration_to_string/migration.sql +55 -55
  26. package/templates/default/prisma/migrations/20251230124637_add_unique_constraint_to_test_run_name/migration.sql +27 -27
  27. package/templates/default/prisma/migrations/20260115094436_add_dashboard_metrics/migration.sql +59 -59
  28. package/templates/default/prisma/migrations/20260127172022_add_cascade_delete_to_step_parameters/migration.sql +34 -34
  29. package/templates/default/prisma/schema.prisma +554 -554
  30. package/templates/default/scripts/regenerate-features.ts +94 -94
  31. package/templates/default/scripts/setup-env.ts +19 -19
  32. package/templates/default/scripts/sync-all.ts +341 -341
  33. package/templates/default/scripts/sync-appraise-base-template.ts +52 -2
  34. package/templates/default/scripts/sync-environments.ts +323 -323
  35. package/templates/default/scripts/sync-locator-groups.ts +413 -413
  36. package/templates/default/scripts/sync-locators.ts +402 -402
  37. package/templates/default/scripts/sync-modules.ts +349 -349
  38. package/templates/default/scripts/sync-tags.ts +292 -292
  39. package/templates/default/scripts/sync-template-step-groups.ts +399 -399
  40. package/templates/default/scripts/sync-template-steps.ts +806 -806
  41. package/templates/default/scripts/sync-test-cases.ts +905 -905
  42. package/templates/default/scripts/sync-test-suites.ts +411 -411
  43. package/templates/default/src/actions/conflict/conflict.action.ts +33 -33
  44. package/templates/default/src/actions/dashboard/dashboard-actions.ts +240 -240
  45. package/templates/default/src/actions/environments/environment-actions.ts +205 -205
  46. package/templates/default/src/actions/locator/locator-actions.ts +547 -547
  47. package/templates/default/src/actions/locator-groups/locator-group-actions.ts +344 -344
  48. package/templates/default/src/actions/modules/module-actions.ts +133 -133
  49. package/templates/default/src/actions/reports/report-actions.ts +613 -613
  50. package/templates/default/src/actions/review/review-actions.ts +147 -147
  51. package/templates/default/src/actions/tags/tag-actions.ts +104 -104
  52. package/templates/default/src/actions/template-step/template-step-actions.ts +332 -332
  53. package/templates/default/src/actions/template-step-group/template-step-group-actions.ts +278 -278
  54. package/templates/default/src/actions/template-test-case/template-test-case-actions.ts +238 -238
  55. package/templates/default/src/actions/test-case/test-case-actions.ts +419 -419
  56. package/templates/default/src/actions/test-run/test-run-actions.ts +1185 -1185
  57. package/templates/default/src/actions/test-suite/test-suite-actions.ts +253 -253
  58. package/templates/default/src/actions/user/user-actions.ts +13 -13
  59. package/templates/default/src/app/(base)/environments/create/page.tsx +28 -28
  60. package/templates/default/src/app/(base)/environments/environment-form.tsx +219 -219
  61. package/templates/default/src/app/(base)/environments/environment-table-columns.tsx +96 -96
  62. package/templates/default/src/app/(base)/environments/environment-table.tsx +24 -24
  63. package/templates/default/src/app/(base)/environments/modify/[id]/page.tsx +46 -46
  64. package/templates/default/src/app/(base)/environments/page.tsx +59 -59
  65. package/templates/default/src/app/(base)/layout.tsx +10 -10
  66. package/templates/default/src/app/(base)/locator-groups/create/page.tsx +44 -44
  67. package/templates/default/src/app/(base)/locator-groups/locator-group-form.tsx +215 -215
  68. package/templates/default/src/app/(base)/locator-groups/locator-group-table-columns.tsx +77 -77
  69. package/templates/default/src/app/(base)/locator-groups/locator-group-table.tsx +28 -28
  70. package/templates/default/src/app/(base)/locator-groups/modify/[id]/page.tsx +46 -46
  71. package/templates/default/src/app/(base)/locator-groups/page.tsx +61 -61
  72. package/templates/default/src/app/(base)/locators/create/page.tsx +38 -38
  73. package/templates/default/src/app/(base)/locators/locator-form.tsx +163 -163
  74. package/templates/default/src/app/(base)/locators/locator-table-columns.tsx +73 -90
  75. package/templates/default/src/app/(base)/locators/locator-table.tsx +28 -28
  76. package/templates/default/src/app/(base)/locators/modify/[id]/page.tsx +45 -45
  77. package/templates/default/src/app/(base)/locators/page.tsx +65 -65
  78. package/templates/default/src/app/(base)/locators/sync-locators-button.tsx +66 -66
  79. package/templates/default/src/app/(base)/modules/create/page.tsx +34 -34
  80. package/templates/default/src/app/(base)/modules/modify/[id]/page.tsx +46 -46
  81. package/templates/default/src/app/(base)/modules/module-form.tsx +126 -126
  82. package/templates/default/src/app/(base)/modules/module-table-columns.tsx +85 -85
  83. package/templates/default/src/app/(base)/modules/module-table.tsx +24 -24
  84. package/templates/default/src/app/(base)/modules/page.tsx +59 -59
  85. package/templates/default/src/app/(base)/reports/[id]/page.tsx +517 -517
  86. package/templates/default/src/app/(base)/reports/duration-chart.tsx +33 -33
  87. package/templates/default/src/app/(base)/reports/feature-chart.tsx +78 -78
  88. package/templates/default/src/app/(base)/reports/overview-chart.tsx +46 -46
  89. package/templates/default/src/app/(base)/reports/page.tsx +98 -98
  90. package/templates/default/src/app/(base)/reports/report-metric-card.tsx +16 -16
  91. package/templates/default/src/app/(base)/reports/report-table-columns.tsx +189 -189
  92. package/templates/default/src/app/(base)/reports/report-table.tsx +72 -72
  93. package/templates/default/src/app/(base)/reports/report-view-table-columns.tsx +131 -131
  94. package/templates/default/src/app/(base)/reports/report-view-table.tsx +82 -82
  95. package/templates/default/src/app/(base)/reports/test-cases/page.tsx +42 -42
  96. package/templates/default/src/app/(base)/reports/test-cases/test-cases-metric-table-columns.tsx +115 -115
  97. package/templates/default/src/app/(base)/reports/test-cases/test-cases-metric-table.tsx +27 -27
  98. package/templates/default/src/app/(base)/reports/test-suites/page.tsx +42 -42
  99. package/templates/default/src/app/(base)/reports/test-suites/test-suites-metric-table-columns.tsx +79 -79
  100. package/templates/default/src/app/(base)/reports/test-suites/test-suites-metric-table.tsx +27 -27
  101. package/templates/default/src/app/(base)/reports/view-logs-button.tsx +60 -60
  102. package/templates/default/src/app/(base)/reviews/create/page.tsx +26 -26
  103. package/templates/default/src/app/(base)/reviews/created-reviews-table.tsx +15 -15
  104. package/templates/default/src/app/(base)/reviews/modify/[id]/page.tsx +26 -26
  105. package/templates/default/src/app/(base)/reviews/page.tsx +26 -26
  106. package/templates/default/src/app/(base)/reviews/review/[id]/page.tsx +26 -26
  107. package/templates/default/src/app/(base)/reviews/review-form.tsx +11 -11
  108. package/templates/default/src/app/(base)/reviews/review-table-by-creator-columns.tsx +9 -9
  109. package/templates/default/src/app/(base)/reviews/review-table-by-reviewer-columns.tsx +9 -9
  110. package/templates/default/src/app/(base)/reviews/reviewer-reviews-table.tsx +15 -15
  111. package/templates/default/src/app/(base)/tags/create/page.tsx +39 -39
  112. package/templates/default/src/app/(base)/tags/modify/[id]/page.tsx +50 -50
  113. package/templates/default/src/app/(base)/tags/page.tsx +58 -58
  114. package/templates/default/src/app/(base)/tags/tag-form.tsx +147 -147
  115. package/templates/default/src/app/(base)/tags/tag-table-columns.tsx +63 -63
  116. package/templates/default/src/app/(base)/tags/tag-table.tsx +29 -29
  117. package/templates/default/src/app/(base)/template-step-groups/create/page.tsx +28 -28
  118. package/templates/default/src/app/(base)/template-step-groups/modify/[id]/page.tsx +45 -45
  119. package/templates/default/src/app/(base)/template-step-groups/page.tsx +60 -60
  120. package/templates/default/src/app/(base)/template-step-groups/template-step-group-form.tsx +167 -167
  121. package/templates/default/src/app/(base)/template-step-groups/template-step-group-table-columns.tsx +89 -89
  122. package/templates/default/src/app/(base)/template-step-groups/template-step-group-table.tsx +32 -32
  123. package/templates/default/src/app/(base)/template-steps/create/page.tsx +37 -37
  124. package/templates/default/src/app/(base)/template-steps/modify/[id]/page.tsx +49 -49
  125. package/templates/default/src/app/(base)/template-steps/page.tsx +59 -59
  126. package/templates/default/src/app/(base)/template-steps/paramChip.tsx +213 -213
  127. package/templates/default/src/app/(base)/template-steps/template-step-form.tsx +384 -384
  128. package/templates/default/src/app/(base)/template-steps/template-step-table-columns.tsx +158 -158
  129. package/templates/default/src/app/(base)/template-steps/template-step-table.tsx +24 -24
  130. package/templates/default/src/app/(base)/template-test-cases/create/page.tsx +56 -56
  131. package/templates/default/src/app/(base)/template-test-cases/modify/[id]/page.tsx +89 -89
  132. package/templates/default/src/app/(base)/template-test-cases/page.tsx +58 -58
  133. package/templates/default/src/app/(base)/template-test-cases/template-test-case-flow.tsx +84 -84
  134. package/templates/default/src/app/(base)/template-test-cases/template-test-case-form.tsx +262 -262
  135. package/templates/default/src/app/(base)/template-test-cases/template-test-case-table-columns.tsx +76 -76
  136. package/templates/default/src/app/(base)/template-test-cases/template-test-case-table.tsx +32 -32
  137. package/templates/default/src/app/(base)/test-cases/create/page.tsx +76 -76
  138. package/templates/default/src/app/(base)/test-cases/create-from-template/generate/[id]/page.tsx +96 -96
  139. package/templates/default/src/app/(base)/test-cases/create-from-template/page.tsx +38 -38
  140. package/templates/default/src/app/(base)/test-cases/create-from-template/template-selection-form.tsx +73 -73
  141. package/templates/default/src/app/(base)/test-cases/modify/[id]/page.tsx +106 -106
  142. package/templates/default/src/app/(base)/test-cases/page.tsx +60 -60
  143. package/templates/default/src/app/(base)/test-cases/test-case-flow.tsx +82 -82
  144. package/templates/default/src/app/(base)/test-cases/test-case-form.tsx +395 -395
  145. package/templates/default/src/app/(base)/test-cases/test-case-table-columns.tsx +90 -90
  146. package/templates/default/src/app/(base)/test-cases/test-case-table.tsx +35 -35
  147. package/templates/default/src/app/(base)/test-runs/[id]/page.tsx +56 -56
  148. package/templates/default/src/app/(base)/test-runs/create/page.tsx +47 -47
  149. package/templates/default/src/app/(base)/test-runs/page.tsx +60 -60
  150. package/templates/default/src/app/(base)/test-runs/test-run-form.tsx +508 -512
  151. package/templates/default/src/app/(base)/test-runs/test-run-table-columns.tsx +229 -229
  152. package/templates/default/src/app/(base)/test-runs/test-run-table.tsx +127 -127
  153. package/templates/default/src/app/(base)/test-suites/create/page.tsx +45 -45
  154. package/templates/default/src/app/(base)/test-suites/modify/[id]/page.tsx +55 -55
  155. package/templates/default/src/app/(base)/test-suites/page.tsx +82 -82
  156. package/templates/default/src/app/(base)/test-suites/test-suite-form.tsx +269 -269
  157. package/templates/default/src/app/(base)/test-suites/test-suite-table-columns.tsx +97 -97
  158. package/templates/default/src/app/(base)/test-suites/test-suite-table.tsx +29 -29
  159. package/templates/default/src/app/(dashboard-components)/app-drawer.tsx +187 -187
  160. package/templates/default/src/app/(dashboard-components)/data-card-grid.tsx +12 -12
  161. package/templates/default/src/app/(dashboard-components)/data-card.tsx +26 -26
  162. package/templates/default/src/app/(dashboard-components)/execution-health-panel.tsx +56 -56
  163. package/templates/default/src/app/(dashboard-components)/ongoing-test-runs-card.tsx +87 -87
  164. package/templates/default/src/app/(dashboard-components)/quick-actions-drawer.tsx +44 -44
  165. package/templates/default/src/app/api/test-runs/[runId]/download/route.ts +133 -133
  166. package/templates/default/src/app/api/test-runs/[runId]/logs/route.ts +420 -420
  167. package/templates/default/src/app/api/test-runs/[runId]/trace/[testCaseId]/route.ts +146 -146
  168. package/templates/default/src/app/globals.css +147 -147
  169. package/templates/default/src/app/layout.tsx +171 -171
  170. package/templates/default/src/app/page.tsx +64 -64
  171. package/templates/default/src/assets/icons/empty-tube.tsx +23 -23
  172. package/templates/default/src/assets/icons/tube-plus.tsx +29 -29
  173. package/templates/default/src/components/base-node.tsx +21 -21
  174. package/templates/default/src/components/chart/pie-chart.tsx +73 -73
  175. package/templates/default/src/components/data-extraction/locator-inspector.tsx +460 -460
  176. package/templates/default/src/components/data-state/empty-state.tsx +40 -40
  177. package/templates/default/src/components/data-visualization/info-card.tsx +70 -70
  178. package/templates/default/src/components/data-visualization/info-grid.tsx +22 -22
  179. package/templates/default/src/components/devtools/providers.tsx +19 -13
  180. package/templates/default/src/components/diagram/button-edge.tsx +54 -54
  181. package/templates/default/src/components/diagram/dynamic-parameters.tsx +438 -438
  182. package/templates/default/src/components/diagram/edit-header-option.tsx +36 -36
  183. package/templates/default/src/components/diagram/flow-diagram.tsx +470 -470
  184. package/templates/default/src/components/diagram/node-form.tsx +262 -262
  185. package/templates/default/src/components/diagram/options-header-node.tsx +57 -57
  186. package/templates/default/src/components/diagram/template-step-combobox.tsx +155 -155
  187. package/templates/default/src/components/form/error-message.tsx +7 -7
  188. package/templates/default/src/components/kokonutui/smooth-tab.tsx +453 -453
  189. package/templates/default/src/components/loading-skeleton/data-table/data-table-skeleton.tsx +30 -30
  190. package/templates/default/src/components/loading-skeleton/form/button-skeleton.tsx +8 -8
  191. package/templates/default/src/components/loading-skeleton/form/icon-button-skeleton.tsx +8 -8
  192. package/templates/default/src/components/loading-skeleton/form/text-input-skeleton.tsx +8 -8
  193. package/templates/default/src/components/loading-skeleton/visualization/table-skeleton.tsx +14 -14
  194. package/templates/default/src/components/logo.tsx +15 -15
  195. package/templates/default/src/components/navigation/command-badge.tsx +34 -34
  196. package/templates/default/src/components/navigation/command-chain-input.tsx +51 -51
  197. package/templates/default/src/components/navigation/entity-search-command.tsx +116 -116
  198. package/templates/default/src/components/navigation/nav-card.tsx +31 -31
  199. package/templates/default/src/components/navigation/nav-command.tsx +508 -508
  200. package/templates/default/src/components/navigation/nav-link.tsx +60 -60
  201. package/templates/default/src/components/navigation/nav-menu-card-deck.tsx +112 -112
  202. package/templates/default/src/components/node-header.tsx +159 -159
  203. package/templates/default/src/components/reports/test-case-logs-modal.tsx +253 -253
  204. package/templates/default/src/components/table/table-actions.tsx +172 -172
  205. package/templates/default/src/components/test-run/download-logs-button.tsx +99 -99
  206. package/templates/default/src/components/test-run/log-viewer.tsx +445 -445
  207. package/templates/default/src/components/test-run/test-run-details.tsx +611 -611
  208. package/templates/default/src/components/test-run/test-run-header.tsx +149 -149
  209. package/templates/default/src/components/test-run/view-report-button.tsx +102 -102
  210. package/templates/default/src/components/theme/mode-toggle.tsx +54 -54
  211. package/templates/default/src/components/theme/theme-provider.tsx +8 -8
  212. package/templates/default/src/components/typography/page-header-subtitle.tsx +7 -7
  213. package/templates/default/src/components/typography/page-header.tsx +7 -7
  214. package/templates/default/src/components/ui/alert-dialog.tsx +106 -106
  215. package/templates/default/src/components/ui/alert.tsx +43 -43
  216. package/templates/default/src/components/ui/avatar.tsx +40 -40
  217. package/templates/default/src/components/ui/badge.tsx +29 -29
  218. package/templates/default/src/components/ui/button.tsx +47 -47
  219. package/templates/default/src/components/ui/calendar.tsx +158 -158
  220. package/templates/default/src/components/ui/card.tsx +43 -43
  221. package/templates/default/src/components/ui/checkbox.tsx +28 -28
  222. package/templates/default/src/components/ui/command.tsx +135 -135
  223. package/templates/default/src/components/ui/data-table-column-header.tsx +61 -61
  224. package/templates/default/src/components/ui/data-table-pagination.tsx +87 -87
  225. package/templates/default/src/components/ui/data-table-view-options.tsx +50 -50
  226. package/templates/default/src/components/ui/data-table.tsx +267 -267
  227. package/templates/default/src/components/ui/dialog.tsx +97 -97
  228. package/templates/default/src/components/ui/dropdown-menu.tsx +182 -182
  229. package/templates/default/src/components/ui/input.tsx +22 -22
  230. package/templates/default/src/components/ui/kbd.tsx +28 -28
  231. package/templates/default/src/components/ui/label.tsx +19 -19
  232. package/templates/default/src/components/ui/loading.tsx +12 -12
  233. package/templates/default/src/components/ui/multi-select-with-preview.tsx +116 -116
  234. package/templates/default/src/components/ui/multi-select.tsx +142 -142
  235. package/templates/default/src/components/ui/navigation-menu.tsx +120 -120
  236. package/templates/default/src/components/ui/popover.tsx +33 -33
  237. package/templates/default/src/components/ui/progress.tsx +25 -25
  238. package/templates/default/src/components/ui/radio-group.tsx +44 -44
  239. package/templates/default/src/components/ui/scroll-area.tsx +40 -40
  240. package/templates/default/src/components/ui/select.tsx +151 -144
  241. package/templates/default/src/components/ui/separator.tsx +22 -22
  242. package/templates/default/src/components/ui/skeleton.tsx +7 -7
  243. package/templates/default/src/components/ui/table.tsx +76 -76
  244. package/templates/default/src/components/ui/tabs.tsx +55 -55
  245. package/templates/default/src/components/ui/textarea.tsx +21 -21
  246. package/templates/default/src/components/ui/toast.tsx +113 -113
  247. package/templates/default/src/components/ui/toaster.tsx +26 -26
  248. package/templates/default/src/components/user-prompt/delete-prompt.tsx +87 -87
  249. package/templates/default/src/config/db-config.ts +10 -10
  250. package/templates/default/src/constants/form-opts/diagram/node-form.ts +30 -30
  251. package/templates/default/src/constants/form-opts/environment-form-opts.ts +24 -24
  252. package/templates/default/src/constants/form-opts/locator-form-opts.ts +20 -20
  253. package/templates/default/src/constants/form-opts/locator-group-form-opts.ts +28 -28
  254. package/templates/default/src/constants/form-opts/module-form-opts.ts +21 -21
  255. package/templates/default/src/constants/form-opts/review-form-opts.ts +23 -23
  256. package/templates/default/src/constants/form-opts/tag-form-opts.ts +42 -42
  257. package/templates/default/src/constants/form-opts/template-selection-form-opts.ts +16 -16
  258. package/templates/default/src/constants/form-opts/template-step-group-form-opts.ts +24 -24
  259. package/templates/default/src/constants/form-opts/template-test-case-form-opts.ts +39 -39
  260. package/templates/default/src/constants/form-opts/template-test-step-form-opts.ts +36 -36
  261. package/templates/default/src/constants/form-opts/test-case-form-opts.ts +43 -43
  262. package/templates/default/src/constants/form-opts/test-run-form-opts.ts +31 -31
  263. package/templates/default/src/constants/form-opts/test-suite-form-opts.ts +24 -24
  264. package/templates/default/src/hooks/use-toast.ts +187 -187
  265. package/templates/default/src/lib/bidirectional-sync.ts +432 -432
  266. package/templates/default/src/lib/database-sync.ts +531 -531
  267. package/templates/default/src/lib/environment-file-utils.ts +221 -221
  268. package/templates/default/src/lib/feature-file-generator.ts +411 -411
  269. package/templates/default/src/lib/gherkin-parser.ts +259 -259
  270. package/templates/default/src/lib/locator-group-file-utils.ts +370 -370
  271. package/templates/default/src/lib/metrics/metric-calculator.ts +613 -613
  272. package/templates/default/src/lib/module-hierarchy-builder.ts +205 -205
  273. package/templates/default/src/lib/path-helpers/module-path.ts +71 -71
  274. package/templates/default/src/lib/test-case-utils.ts +6 -6
  275. package/templates/default/src/lib/test-run/log-formatter.ts +83 -83
  276. package/templates/default/src/lib/test-run/process-manager.ts +191 -191
  277. package/templates/default/src/lib/test-run/report-parser.ts +316 -316
  278. package/templates/default/src/lib/test-run/test-run-executor.ts +144 -144
  279. package/templates/default/src/lib/test-run/winston-logger.ts +95 -95
  280. package/templates/default/src/lib/transformers/gherkin-converter.ts +42 -42
  281. package/templates/default/src/lib/transformers/key-to-icon-transformer.tsx +95 -95
  282. package/templates/default/src/lib/transformers/template-test-case-converter.ts +160 -160
  283. package/templates/default/src/lib/utils/node-param-validation.ts +81 -81
  284. package/templates/default/src/lib/utils/template-step-file-generator.ts +167 -167
  285. package/templates/default/src/lib/utils/template-step-file-manager-intelligent.ts +723 -723
  286. package/templates/default/src/lib/utils/template-step-file-manager.ts +166 -166
  287. package/templates/default/src/lib/utils.ts +31 -31
  288. package/templates/default/src/tests/config/executor/world.ts +41 -41
  289. package/templates/default/src/tests/executor.ts +80 -80
  290. package/templates/default/src/tests/hooks/hooks.ts +99 -99
  291. package/templates/default/src/tests/mapping/locator-map.json +1 -1
  292. package/templates/default/src/tests/steps/actions/click.step.ts +62 -62
  293. package/templates/default/src/tests/steps/actions/navigation.step.ts +73 -72
  294. package/templates/default/src/tests/steps/validations/active_state_assertion.step.ts +34 -34
  295. package/templates/default/src/tests/steps/validations/navigation_assertion.step.ts +24 -23
  296. package/templates/default/src/tests/steps/validations/text_assertion.step.ts +111 -111
  297. package/templates/default/src/tests/steps/validations/visibility_assertion.step.ts +30 -30
  298. package/templates/default/src/tests/support/parameter-types.ts +12 -12
  299. package/templates/default/src/tests/utils/cache.util.ts +260 -260
  300. package/templates/default/src/tests/utils/cli.util.ts +177 -177
  301. package/templates/default/src/tests/utils/environment.util.ts +65 -65
  302. package/templates/default/src/tests/utils/locator.util.ts +248 -248
  303. package/templates/default/src/tests/utils/random-data.util.ts +44 -44
  304. package/templates/default/src/tests/utils/spawner.util.ts +617 -617
  305. package/templates/default/src/types/diagram/diagram.ts +34 -34
  306. package/templates/default/src/types/diagram/template-step.ts +11 -11
  307. package/templates/default/src/types/executor/browser.type.ts +1 -1
  308. package/templates/default/src/types/form/actionHandler.ts +6 -6
  309. package/templates/default/src/types/locator/locator.type.ts +11 -11
  310. package/templates/default/src/types/step/step.type.ts +1 -1
  311. package/templates/default/src/types/table/data-table.ts +6 -6
  312. package/templates/default/tailwind.config.ts +62 -62
  313. package/templates/default/.env +0 -2
  314. package/templates/default/next-env.d.ts +0 -6
  315. package/templates/default/prisma/prisma/dev.db +0 -0
  316. 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
+ }