create-appraisejs 0.2.0 → 0.3.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (485) hide show
  1. package/README.md +12 -1
  2. package/package.json +1 -1
  3. package/templates/default/.appraise-template-meta.json +2 -2
  4. package/templates/default/.env.example +2 -2
  5. package/templates/default/README.md +57 -53
  6. package/templates/default/automation/steps/actions/click.step.ts +58 -58
  7. package/templates/default/automation/steps/actions/hover.step.ts +27 -27
  8. package/templates/default/automation/steps/actions/navigation.step.ts +70 -70
  9. package/templates/default/automation/steps/actions/random_data.step.ts +142 -142
  10. package/templates/default/automation/steps/actions/store.step.ts +86 -86
  11. package/templates/default/automation/steps/actions/wait.step.ts +110 -90
  12. package/templates/default/automation/steps/validations/active_state_assertion.step.ts +30 -30
  13. package/templates/default/automation/steps/validations/navigation_assertion.step.ts +22 -22
  14. package/templates/default/automation/steps/validations/text_assertion.step.ts +107 -107
  15. package/templates/default/automation/steps/validations/visibility_assertion.step.ts +26 -26
  16. package/templates/default/components.json +24 -24
  17. package/templates/default/cucumber.mjs +16 -16
  18. package/templates/default/eslint.config.mjs +20 -16
  19. package/templates/default/next-env.d.ts +6 -6
  20. package/templates/default/next.config.ts +20 -11
  21. package/templates/default/package-lock.json +1775 -74
  22. package/templates/default/package.json +8 -1
  23. package/templates/default/packages/cucumber-runtime/package.json +13 -13
  24. package/templates/default/packages/cucumber-runtime/src/cache.util.ts +93 -93
  25. package/templates/default/packages/cucumber-runtime/src/cli.ts +68 -68
  26. package/templates/default/packages/cucumber-runtime/src/environment.util.ts +21 -21
  27. package/templates/default/packages/cucumber-runtime/src/executor.ts +32 -32
  28. package/templates/default/packages/cucumber-runtime/src/index.ts +17 -17
  29. package/templates/default/packages/cucumber-runtime/src/locator.util.ts +234 -234
  30. package/templates/default/packages/cucumber-runtime/src/parameter-types.ts +7 -7
  31. package/templates/default/packages/cucumber-runtime/src/random-data.util.ts +35 -35
  32. package/templates/default/packages/cucumber-runtime/src/types.ts +13 -13
  33. package/templates/default/packages/cucumber-runtime/src/world.ts +44 -44
  34. package/templates/default/packages/cucumber-runtime/tsconfig.json +11 -11
  35. package/templates/default/postcss.config.mjs +8 -8
  36. package/templates/default/prisma/dev.db +0 -0
  37. package/templates/default/prisma/migrations/20251104113456_add_type_for_template_step_groups/migration.sql +16 -16
  38. package/templates/default/prisma/migrations/20251104170946_add_tags_to_test_suite_and_test_case/migration.sql +27 -27
  39. package/templates/default/prisma/migrations/20251112190024_add_cascade_delete_to_test_run_test_case/migration.sql +17 -17
  40. package/templates/default/prisma/migrations/20251113181100_add_test_run_log/migration.sql +12 -12
  41. package/templates/default/prisma/migrations/20251119191838_add_tag_type/migration.sql +28 -28
  42. package/templates/default/prisma/migrations/20251121164059_add_conflict_resolution/migration.sql +12 -12
  43. package/templates/default/prisma/migrations/20251223183400_add_report_model_to_db_schema/migration.sql +10 -10
  44. package/templates/default/prisma/migrations/20251223183637_add_report_test_case_entity_for_storing_test_results_for_individual_test_cases/migration.sql +10 -10
  45. package/templates/default/prisma/migrations/20251224083549_add_comprehensive_report_storage/migration.sql +108 -108
  46. package/templates/default/prisma/migrations/20251229194422_migrate_duration_to_string/migration.sql +55 -55
  47. package/templates/default/prisma/migrations/20251230124637_add_unique_constraint_to_test_run_name/migration.sql +27 -27
  48. package/templates/default/prisma/migrations/20260115094436_add_dashboard_metrics/migration.sql +59 -59
  49. package/templates/default/prisma/migrations/20260127172022_add_cascade_delete_to_step_parameters/migration.sql +34 -34
  50. package/templates/default/prisma/migrations/20260313093000_add_report_step_screenshot_path/migration.sql +1 -1
  51. package/templates/default/scripts/build-step-registry.ts +33 -0
  52. package/templates/default/scripts/install-playwright.ts +17 -8
  53. package/templates/default/scripts/install-template-step.ts +128 -0
  54. package/templates/default/scripts/lib/filename-utils.test.ts +24 -0
  55. package/templates/default/scripts/lib/filename-utils.ts +24 -0
  56. package/templates/default/scripts/lib/jsdoc-parser.test.ts +63 -0
  57. package/templates/default/scripts/lib/jsdoc-parser.ts +88 -0
  58. package/templates/default/scripts/lib/step-file-parser.test.ts +71 -0
  59. package/templates/default/scripts/lib/step-file-parser.ts +315 -0
  60. package/templates/default/scripts/lib/step-matcher.test.ts +86 -0
  61. package/templates/default/scripts/lib/step-matcher.ts +120 -0
  62. package/templates/default/scripts/lib/sync-script-runner.ts +23 -0
  63. package/templates/default/scripts/lib/sync-summary.ts +29 -0
  64. package/templates/default/scripts/lib/tag-parsing.test.ts +20 -0
  65. package/templates/default/scripts/lib/tag-parsing.ts +10 -0
  66. package/templates/default/scripts/lib/template-step-installer.test.ts +225 -0
  67. package/templates/default/scripts/lib/template-step-installer.ts +404 -0
  68. package/templates/default/scripts/lib/template-step-registry.test.ts +118 -0
  69. package/templates/default/scripts/lib/template-step-registry.ts +137 -0
  70. package/templates/default/scripts/protect-seeded-files.ts +51 -38
  71. package/templates/default/scripts/regenerate-features.ts +98 -94
  72. package/templates/default/scripts/run-vitest.ts +59 -0
  73. package/templates/default/scripts/setup-env.ts +26 -19
  74. package/templates/default/scripts/sync-all.ts +44 -54
  75. package/templates/default/scripts/sync-appraise-base-template.ts +44 -16
  76. package/templates/default/scripts/sync-environments.ts +22 -66
  77. package/templates/default/scripts/sync-locator-groups.ts +358 -410
  78. package/templates/default/scripts/sync-locators.ts +348 -398
  79. package/templates/default/scripts/sync-modules.ts +302 -341
  80. package/templates/default/scripts/sync-tags.ts +29 -65
  81. package/templates/default/scripts/sync-template-step-groups.ts +24 -182
  82. package/templates/default/scripts/sync-template-steps.ts +36 -493
  83. package/templates/default/scripts/sync-test-cases.ts +296 -539
  84. package/templates/default/scripts/sync-test-suites.ts +32 -79
  85. package/templates/default/src/actions/dashboard/dashboard-actions.ts +70 -241
  86. package/templates/default/src/actions/environments/environment-actions.ts +102 -188
  87. package/templates/default/src/actions/locator/locator-actions.ts +77 -490
  88. package/templates/default/src/actions/locator-groups/locator-group-actions.ts +34 -212
  89. package/templates/default/src/actions/locator-picker/locator-picker-actions.test.ts +81 -0
  90. package/templates/default/src/actions/locator-picker/locator-picker-actions.ts +20 -161
  91. package/templates/default/src/actions/modules/module-actions.ts +99 -135
  92. package/templates/default/src/actions/reports/report-actions.ts +28 -565
  93. package/templates/default/src/actions/settings/sync-actions.test.ts +58 -0
  94. package/templates/default/src/actions/tags/tag-actions.ts +99 -107
  95. package/templates/default/src/actions/template-step/template-step-actions.ts +33 -194
  96. package/templates/default/src/actions/template-step-group/template-step-group-actions.ts +35 -92
  97. package/templates/default/src/actions/template-test-case/template-test-case-actions.ts +98 -238
  98. package/templates/default/src/actions/test-case/test-case-actions.ts +108 -356
  99. package/templates/default/src/actions/test-run/test-run-actions.ts +74 -1081
  100. package/templates/default/src/actions/test-suite/test-suite-actions.ts +35 -202
  101. package/templates/default/src/app/(base)/environments/create/page.tsx +28 -28
  102. package/templates/default/src/app/(base)/environments/environment-form.test.tsx +92 -0
  103. package/templates/default/src/app/(base)/environments/environment-form.tsx +228 -219
  104. package/templates/default/src/app/(base)/environments/environment-helpers.ts +58 -0
  105. package/templates/default/src/app/(base)/environments/environment-table-columns.tsx +96 -96
  106. package/templates/default/src/app/(base)/environments/environment-table.tsx +25 -24
  107. package/templates/default/src/app/(base)/environments/modify/[id]/page.tsx +49 -46
  108. package/templates/default/src/app/(base)/environments/page.tsx +59 -59
  109. package/templates/default/src/app/(base)/layout.tsx +10 -10
  110. package/templates/default/src/app/(base)/locator-groups/create/page.tsx +44 -44
  111. package/templates/default/src/app/(base)/locator-groups/locator-group-form.tsx +215 -215
  112. package/templates/default/src/app/(base)/locator-groups/locator-group-table-columns.tsx +77 -77
  113. package/templates/default/src/app/(base)/locator-groups/locator-group-table.tsx +28 -28
  114. package/templates/default/src/app/(base)/locator-groups/modify/[id]/page.tsx +46 -46
  115. package/templates/default/src/app/(base)/locators/create/create-locator-workspace-helpers.test.ts +71 -0
  116. package/templates/default/src/app/(base)/locators/create/create-locator-workspace-helpers.ts +333 -0
  117. package/templates/default/src/app/(base)/locators/create/create-locator-workspace.test.tsx +125 -0
  118. package/templates/default/src/app/(base)/locators/create/create-locator-workspace.tsx +56 -274
  119. package/templates/default/src/app/(base)/locators/create/page.tsx +8 -4
  120. package/templates/default/src/app/(base)/locators/create/use-locator-workspace.ts +183 -0
  121. package/templates/default/src/app/(base)/locators/locator-helpers.test.ts +28 -0
  122. package/templates/default/src/app/(base)/locators/locator-helpers.ts +59 -0
  123. package/templates/default/src/app/(base)/locators/locator-table-columns.tsx +74 -73
  124. package/templates/default/src/app/(base)/locators/locator-table.tsx +30 -28
  125. package/templates/default/src/app/(base)/locators/modify/[id]/page.tsx +20 -8
  126. package/templates/default/src/app/(base)/locators/page.tsx +3 -6
  127. package/templates/default/src/app/(base)/locators/sync-locators-button.tsx +67 -66
  128. package/templates/default/src/app/(base)/modules/create/page.tsx +33 -34
  129. package/templates/default/src/app/(base)/modules/modify/[id]/page.tsx +43 -46
  130. package/templates/default/src/app/(base)/modules/module-form.test.tsx +84 -0
  131. package/templates/default/src/app/(base)/modules/module-form.tsx +159 -126
  132. package/templates/default/src/app/(base)/modules/module-helpers.ts +64 -0
  133. package/templates/default/src/app/(base)/modules/module-table-columns.tsx +81 -85
  134. package/templates/default/src/app/(base)/modules/module-table.tsx +25 -24
  135. package/templates/default/src/app/(base)/modules/page.tsx +59 -59
  136. package/templates/default/src/app/(base)/reports/[id]/page.tsx +20 -260
  137. package/templates/default/src/app/(base)/reports/duration-chart.tsx +33 -33
  138. package/templates/default/src/app/(base)/reports/feature-chart.tsx +79 -78
  139. package/templates/default/src/app/(base)/reports/overview-chart.tsx +49 -49
  140. package/templates/default/src/app/(base)/reports/page.tsx +98 -98
  141. package/templates/default/src/app/(base)/reports/report-detail-helpers.test.ts +109 -0
  142. package/templates/default/src/app/(base)/reports/report-detail-helpers.ts +247 -0
  143. package/templates/default/src/app/(base)/reports/report-metric-card.tsx +78 -78
  144. package/templates/default/src/app/(base)/reports/report-table-columns.tsx +189 -189
  145. package/templates/default/src/app/(base)/reports/report-table.tsx +72 -72
  146. package/templates/default/src/app/(base)/reports/test-cases/page.tsx +40 -40
  147. package/templates/default/src/app/(base)/reports/test-cases/test-cases-metric-table-columns.tsx +115 -115
  148. package/templates/default/src/app/(base)/reports/test-cases/test-cases-metric-table.tsx +27 -27
  149. package/templates/default/src/app/(base)/reports/test-suites/page.tsx +42 -42
  150. package/templates/default/src/app/(base)/reports/test-suites/test-suites-metric-table-columns.tsx +79 -79
  151. package/templates/default/src/app/(base)/reports/test-suites/test-suites-metric-table.tsx +27 -27
  152. package/templates/default/src/app/(base)/reports/view-logs-button.tsx +58 -58
  153. package/templates/default/src/app/(base)/settings/settings-sync-panel-helpers.test.tsx +40 -0
  154. package/templates/default/src/app/(base)/settings/settings-sync-panel-helpers.tsx +110 -0
  155. package/templates/default/src/app/(base)/settings/settings-sync-panel.test.tsx +127 -0
  156. package/templates/default/src/app/(base)/settings/settings-sync-panel.tsx +19 -134
  157. package/templates/default/src/app/(base)/settings/use-settings-sync.ts +66 -0
  158. package/templates/default/src/app/(base)/tags/create/page.tsx +39 -39
  159. package/templates/default/src/app/(base)/tags/modify/[id]/page.tsx +50 -50
  160. package/templates/default/src/app/(base)/tags/page.tsx +58 -58
  161. package/templates/default/src/app/(base)/tags/tag-form-helpers.ts +13 -0
  162. package/templates/default/src/app/(base)/tags/tag-form.test.tsx +83 -0
  163. package/templates/default/src/app/(base)/tags/tag-form.tsx +143 -147
  164. package/templates/default/src/app/(base)/tags/tag-table-columns.tsx +63 -63
  165. package/templates/default/src/app/(base)/tags/tag-table.tsx +29 -29
  166. package/templates/default/src/app/(base)/template-step-groups/create/page.tsx +28 -28
  167. package/templates/default/src/app/(base)/template-step-groups/modify/[id]/page.tsx +43 -45
  168. package/templates/default/src/app/(base)/template-step-groups/page.tsx +60 -60
  169. package/templates/default/src/app/(base)/template-step-groups/template-step-group-form.test.tsx +82 -0
  170. package/templates/default/src/app/(base)/template-step-groups/template-step-group-form.tsx +181 -167
  171. package/templates/default/src/app/(base)/template-step-groups/template-step-group-helpers.ts +54 -0
  172. package/templates/default/src/app/(base)/template-step-groups/template-step-group-table-columns.tsx +89 -89
  173. package/templates/default/src/app/(base)/template-step-groups/template-step-group-table.tsx +34 -32
  174. package/templates/default/src/app/(base)/template-steps/create/page.tsx +40 -37
  175. package/templates/default/src/app/(base)/template-steps/modify/[id]/page.tsx +54 -49
  176. package/templates/default/src/app/(base)/template-steps/page.tsx +59 -58
  177. package/templates/default/src/app/(base)/template-steps/paramChip.tsx +233 -213
  178. package/templates/default/src/app/(base)/template-steps/template-step-form.test.tsx +132 -0
  179. package/templates/default/src/app/(base)/template-steps/template-step-form.tsx +342 -384
  180. package/templates/default/src/app/(base)/template-steps/template-step-helpers.test.ts +99 -0
  181. package/templates/default/src/app/(base)/template-steps/template-step-helpers.ts +176 -0
  182. package/templates/default/src/app/(base)/template-steps/template-step-table-columns.tsx +153 -158
  183. package/templates/default/src/app/(base)/template-steps/template-step-table.tsx +26 -24
  184. package/templates/default/src/app/(base)/template-test-cases/create/page.tsx +56 -56
  185. package/templates/default/src/app/(base)/template-test-cases/modify/[id]/page.tsx +89 -89
  186. package/templates/default/src/app/(base)/template-test-cases/page.tsx +58 -58
  187. package/templates/default/src/app/(base)/template-test-cases/template-test-case-flow.test.tsx +109 -0
  188. package/templates/default/src/app/(base)/template-test-cases/template-test-case-flow.tsx +45 -84
  189. package/templates/default/src/app/(base)/template-test-cases/template-test-case-form.test.tsx +140 -0
  190. package/templates/default/src/app/(base)/template-test-cases/template-test-case-form.tsx +154 -262
  191. package/templates/default/src/app/(base)/template-test-cases/template-test-case-table-columns.tsx +76 -76
  192. package/templates/default/src/app/(base)/template-test-cases/template-test-case-table.tsx +32 -32
  193. package/templates/default/src/app/(base)/test-cases/create/page.tsx +90 -76
  194. package/templates/default/src/app/(base)/test-cases/create-from-template/create-from-template-helpers.test.ts +94 -0
  195. package/templates/default/src/app/(base)/test-cases/create-from-template/create-from-template-helpers.ts +171 -0
  196. package/templates/default/src/app/(base)/test-cases/create-from-template/generate/[id]/page.tsx +105 -96
  197. package/templates/default/src/app/(base)/test-cases/create-from-template/page.tsx +40 -38
  198. package/templates/default/src/app/(base)/test-cases/create-from-template/template-selection-form.test.tsx +87 -0
  199. package/templates/default/src/app/(base)/test-cases/create-from-template/template-selection-form.tsx +83 -73
  200. package/templates/default/src/app/(base)/test-cases/modify/[id]/page.tsx +106 -106
  201. package/templates/default/src/app/(base)/test-cases/page.tsx +3 -2
  202. package/templates/default/src/app/(base)/test-cases/test-case-flow.test.tsx +108 -0
  203. package/templates/default/src/app/(base)/test-cases/test-case-flow.tsx +43 -82
  204. package/templates/default/src/app/(base)/test-cases/test-case-form.test.tsx +202 -0
  205. package/templates/default/src/app/(base)/test-cases/test-case-form.tsx +263 -395
  206. package/templates/default/src/app/(base)/test-cases/test-case-route-helpers.test.ts +95 -0
  207. package/templates/default/src/app/(base)/test-cases/test-case-route-helpers.ts +147 -0
  208. package/templates/default/src/app/(base)/test-cases/test-case-table.tsx +4 -2
  209. package/templates/default/src/app/(base)/test-runs/[id]/page.tsx +11 -10
  210. package/templates/default/src/app/(base)/test-runs/create/page.tsx +4 -5
  211. package/templates/default/src/app/(base)/test-runs/page.tsx +60 -60
  212. package/templates/default/src/app/(base)/test-runs/test-run-form-helpers.test.ts +50 -0
  213. package/templates/default/src/app/(base)/test-runs/test-run-form-helpers.ts +168 -0
  214. package/templates/default/src/app/(base)/test-runs/test-run-form.test.tsx +138 -0
  215. package/templates/default/src/app/(base)/test-runs/test-run-form.tsx +111 -256
  216. package/templates/default/src/app/(base)/test-runs/test-run-table-columns.tsx +229 -229
  217. package/templates/default/src/app/(base)/test-runs/test-run-table.tsx +127 -127
  218. package/templates/default/src/app/(base)/test-runs/use-test-run-name-validation.ts +74 -0
  219. package/templates/default/src/app/(base)/test-suites/create/page.tsx +17 -12
  220. package/templates/default/src/app/(base)/test-suites/modify/[id]/page.tsx +22 -19
  221. package/templates/default/src/app/(base)/test-suites/page.tsx +14 -56
  222. package/templates/default/src/app/(base)/test-suites/test-suite-form.test.tsx +127 -0
  223. package/templates/default/src/app/(base)/test-suites/test-suite-form.tsx +45 -64
  224. package/templates/default/src/app/(base)/test-suites/test-suite-helpers.test.ts +67 -0
  225. package/templates/default/src/app/(base)/test-suites/test-suite-helpers.ts +215 -0
  226. package/templates/default/src/app/(base)/test-suites/test-suite-table.tsx +32 -29
  227. package/templates/default/src/app/(dashboard-components)/app-drawer.tsx +187 -187
  228. package/templates/default/src/app/(dashboard-components)/data-card-grid.tsx +12 -12
  229. package/templates/default/src/app/(dashboard-components)/data-card.tsx +26 -26
  230. package/templates/default/src/app/(dashboard-components)/execution-health-panel.tsx +56 -56
  231. package/templates/default/src/app/(dashboard-components)/ongoing-test-runs-card.tsx +87 -87
  232. package/templates/default/src/app/(dashboard-components)/quick-actions-drawer.tsx +44 -44
  233. package/templates/default/src/app/api/reports/steps/[stepId]/screenshot/route.test.ts +83 -0
  234. package/templates/default/src/app/api/reports/steps/[stepId]/screenshot/route.ts +52 -52
  235. package/templates/default/src/app/api/test-runs/[runId]/download/route.test.ts +169 -0
  236. package/templates/default/src/app/api/test-runs/[runId]/download/route.ts +1 -1
  237. package/templates/default/src/app/api/test-runs/[runId]/trace/[testCaseId]/route.test.ts +135 -0
  238. package/templates/default/src/app/api/test-runs/[runId]/trace/[testCaseId]/route.ts +146 -146
  239. package/templates/default/src/app/globals.css +147 -147
  240. package/templates/default/src/app/page.tsx +1 -1
  241. package/templates/default/src/assets/icons/empty-tube.tsx +23 -23
  242. package/templates/default/src/assets/icons/tube-plus.tsx +29 -29
  243. package/templates/default/src/components/base-node.tsx +21 -21
  244. package/templates/default/src/components/chart/pie-chart.tsx +73 -73
  245. package/templates/default/src/components/data-extraction/locator-inspector-helpers.test.ts +32 -0
  246. package/templates/default/src/components/data-extraction/locator-inspector-helpers.ts +183 -0
  247. package/templates/default/src/components/data-extraction/locator-inspector.tsx +349 -460
  248. package/templates/default/src/components/data-state/empty-state.tsx +40 -40
  249. package/templates/default/src/components/data-visualization/info-card.tsx +70 -70
  250. package/templates/default/src/components/data-visualization/info-grid.tsx +22 -22
  251. package/templates/default/src/components/diagram/button-edge.tsx +54 -54
  252. package/templates/default/src/components/diagram/dynamic-parameters-helpers.test.ts +83 -0
  253. package/templates/default/src/components/diagram/dynamic-parameters-helpers.ts +158 -0
  254. package/templates/default/src/components/diagram/dynamic-parameters.tsx +350 -474
  255. package/templates/default/src/components/diagram/edit-header-option.tsx +36 -36
  256. package/templates/default/src/components/diagram/flow-diagram-helpers.test.ts +117 -0
  257. package/templates/default/src/components/diagram/flow-diagram-helpers.ts +251 -0
  258. package/templates/default/src/components/diagram/flow-diagram.tsx +247 -470
  259. package/templates/default/src/components/diagram/flow-host-helpers.test.ts +74 -0
  260. package/templates/default/src/components/diagram/flow-host-helpers.ts +51 -0
  261. package/templates/default/src/components/diagram/node-form-helpers.test.ts +92 -0
  262. package/templates/default/src/components/diagram/node-form-helpers.ts +100 -0
  263. package/templates/default/src/components/diagram/node-form.test.tsx +168 -0
  264. package/templates/default/src/components/diagram/node-form.tsx +199 -262
  265. package/templates/default/src/components/diagram/options-header-node.tsx +57 -57
  266. package/templates/default/src/components/diagram/template-step-combobox.tsx +155 -155
  267. package/templates/default/src/components/diagram/use-flow-node-order.ts +49 -0
  268. package/templates/default/src/components/form/error-message.tsx +7 -7
  269. package/templates/default/src/components/kokonutui/smooth-tab.tsx +453 -453
  270. package/templates/default/src/components/loading-skeleton/data-table/data-table-skeleton.tsx +30 -30
  271. package/templates/default/src/components/loading-skeleton/form/button-skeleton.tsx +8 -8
  272. package/templates/default/src/components/loading-skeleton/form/icon-button-skeleton.tsx +8 -8
  273. package/templates/default/src/components/loading-skeleton/form/text-input-skeleton.tsx +8 -8
  274. package/templates/default/src/components/loading-skeleton/visualization/table-skeleton.tsx +14 -14
  275. package/templates/default/src/components/navigation/command-badge.tsx +34 -34
  276. package/templates/default/src/components/navigation/command-chain-input.tsx +51 -51
  277. package/templates/default/src/components/navigation/entity-search-command.tsx +118 -116
  278. package/templates/default/src/components/navigation/nav-card.tsx +31 -31
  279. package/templates/default/src/components/navigation/nav-command-helpers.ts +122 -0
  280. package/templates/default/src/components/navigation/nav-command-search.tsx +125 -0
  281. package/templates/default/src/components/navigation/nav-command.test.tsx +106 -0
  282. package/templates/default/src/components/navigation/nav-command.tsx +49 -472
  283. package/templates/default/src/components/navigation/nav-link.tsx +60 -60
  284. package/templates/default/src/components/navigation/nav-menu-card-deck.tsx +112 -112
  285. package/templates/default/src/components/navigation/use-nav-command.ts +58 -0
  286. package/templates/default/src/components/node-header.tsx +159 -159
  287. package/templates/default/src/components/reports/test-case-logs-modal.tsx +310 -310
  288. package/templates/default/src/components/table/table-actions.tsx +174 -172
  289. package/templates/default/src/components/test-case/test-case-form-helpers.test.ts +100 -0
  290. package/templates/default/src/components/test-case/test-case-form-helpers.ts +140 -0
  291. package/templates/default/src/components/test-case/test-case-picker-helpers.test.ts +40 -0
  292. package/templates/default/src/components/test-case/test-case-picker-helpers.ts +41 -0
  293. package/templates/default/src/components/test-case/test-case-picker.test.tsx +44 -0
  294. package/templates/default/src/components/test-case/test-case-picker.tsx +16 -35
  295. package/templates/default/src/components/test-case/test-scenario-preview.tsx +34 -0
  296. package/templates/default/src/components/test-run/download-logs-button.tsx +92 -92
  297. package/templates/default/src/components/test-run/log-viewer-helpers.test.ts +37 -0
  298. package/templates/default/src/components/test-run/log-viewer-helpers.ts +80 -0
  299. package/templates/default/src/components/test-run/log-viewer.test.tsx +118 -0
  300. package/templates/default/src/components/test-run/log-viewer.tsx +51 -350
  301. package/templates/default/src/components/test-run/test-run-details-helpers.test.ts +31 -0
  302. package/templates/default/src/components/test-run/test-run-details-helpers.ts +208 -0
  303. package/templates/default/src/components/test-run/test-run-details.test.tsx +174 -0
  304. package/templates/default/src/components/test-run/test-run-details.tsx +155 -457
  305. package/templates/default/src/components/test-run/test-run-header-helpers.test.ts +31 -0
  306. package/templates/default/src/components/test-run/test-run-header-helpers.ts +23 -0
  307. package/templates/default/src/components/test-run/test-run-header.test.tsx +103 -0
  308. package/templates/default/src/components/test-run/test-run-header.tsx +27 -149
  309. package/templates/default/src/components/test-run/use-log-viewer.ts +213 -0
  310. package/templates/default/src/components/test-run/use-test-run-details.ts +184 -0
  311. package/templates/default/src/components/test-run/use-test-run-header.ts +89 -0
  312. package/templates/default/src/components/test-run/view-report-button.tsx +102 -102
  313. package/templates/default/src/components/test-suite/test-suite-picker-helpers.test.ts +68 -0
  314. package/templates/default/src/components/test-suite/test-suite-picker-helpers.ts +76 -0
  315. package/templates/default/src/components/test-suite/test-suite-picker.test.tsx +65 -0
  316. package/templates/default/src/components/test-suite/test-suite-picker.tsx +4 -72
  317. package/templates/default/src/components/theme/mode-toggle.tsx +54 -54
  318. package/templates/default/src/components/theme/theme-provider.tsx +8 -8
  319. package/templates/default/src/components/typography/page-header-subtitle.tsx +7 -7
  320. package/templates/default/src/components/typography/page-header.tsx +7 -7
  321. package/templates/default/src/components/ui/alert-dialog.tsx +106 -106
  322. package/templates/default/src/components/ui/alert.tsx +43 -43
  323. package/templates/default/src/components/ui/avatar.tsx +40 -40
  324. package/templates/default/src/components/ui/badge.tsx +29 -29
  325. package/templates/default/src/components/ui/button.tsx +47 -47
  326. package/templates/default/src/components/ui/calendar.tsx +158 -158
  327. package/templates/default/src/components/ui/card.tsx +43 -43
  328. package/templates/default/src/components/ui/checkbox.tsx +28 -28
  329. package/templates/default/src/components/ui/command.tsx +135 -135
  330. package/templates/default/src/components/ui/data-table-column-header.tsx +61 -61
  331. package/templates/default/src/components/ui/data-table-pagination.tsx +87 -87
  332. package/templates/default/src/components/ui/data-table-view-options.tsx +50 -50
  333. package/templates/default/src/components/ui/data-table.test.tsx +122 -0
  334. package/templates/default/src/components/ui/data-table.tsx +298 -261
  335. package/templates/default/src/components/ui/dialog.tsx +97 -97
  336. package/templates/default/src/components/ui/dropdown-menu.tsx +182 -182
  337. package/templates/default/src/components/ui/input.tsx +22 -22
  338. package/templates/default/src/components/ui/kbd.tsx +28 -28
  339. package/templates/default/src/components/ui/label.tsx +19 -19
  340. package/templates/default/src/components/ui/loading.tsx +12 -12
  341. package/templates/default/src/components/ui/multi-select-with-preview.tsx +116 -116
  342. package/templates/default/src/components/ui/multi-select.test.tsx +45 -0
  343. package/templates/default/src/components/ui/multi-select.tsx +158 -142
  344. package/templates/default/src/components/ui/navigation-menu.tsx +120 -120
  345. package/templates/default/src/components/ui/popover.tsx +33 -33
  346. package/templates/default/src/components/ui/progress.tsx +25 -25
  347. package/templates/default/src/components/ui/radio-group.tsx +44 -44
  348. package/templates/default/src/components/ui/scroll-area.tsx +40 -40
  349. package/templates/default/src/components/ui/select.tsx +151 -151
  350. package/templates/default/src/components/ui/separator.tsx +22 -22
  351. package/templates/default/src/components/ui/skeleton.tsx +7 -7
  352. package/templates/default/src/components/ui/table.tsx +76 -76
  353. package/templates/default/src/components/ui/tabs.tsx +55 -55
  354. package/templates/default/src/components/ui/textarea.tsx +21 -21
  355. package/templates/default/src/components/ui/toast.tsx +113 -113
  356. package/templates/default/src/components/ui/toaster.tsx +26 -26
  357. package/templates/default/src/components/user-prompt/delete-prompt.test.tsx +60 -0
  358. package/templates/default/src/components/user-prompt/delete-prompt.tsx +118 -87
  359. package/templates/default/src/constants/form-opts/diagram/node-form.ts +30 -30
  360. package/templates/default/src/constants/form-opts/environment-form-opts.ts +24 -24
  361. package/templates/default/src/constants/form-opts/locator-group-form-opts.ts +28 -28
  362. package/templates/default/src/constants/form-opts/module-form-opts.ts +21 -21
  363. package/templates/default/src/constants/form-opts/tag-form-opts.ts +42 -42
  364. package/templates/default/src/constants/form-opts/template-selection-form-opts.ts +16 -16
  365. package/templates/default/src/constants/form-opts/template-step-group-form-opts.ts +24 -24
  366. package/templates/default/src/constants/form-opts/template-test-case-form-opts.ts +39 -39
  367. package/templates/default/src/constants/form-opts/template-test-step-form-opts.ts +36 -36
  368. package/templates/default/src/constants/form-opts/test-case-form-opts.ts +43 -43
  369. package/templates/default/src/constants/form-opts/test-suite-form-opts.ts +24 -24
  370. package/templates/default/src/hooks/use-toast.ts +187 -187
  371. package/templates/default/src/lib/automation/automation-path-roots.ts +95 -0
  372. package/templates/default/src/lib/automation/automation-workspace.ts +147 -0
  373. package/templates/default/src/lib/automation/paths.ts +6 -211
  374. package/templates/default/src/lib/bidirectional-sync.ts +432 -432
  375. package/templates/default/src/lib/environment-file-utils.ts +2 -1
  376. package/templates/default/src/lib/executor/local-executor-adapter.ts +2 -5
  377. package/templates/default/src/lib/feature-file-generator.ts +2 -1
  378. package/templates/default/src/lib/gherkin-parser.ts +0 -2
  379. package/templates/default/src/lib/locator-group-file-utils.ts +304 -307
  380. package/templates/default/src/lib/locator-picker/session-manager.ts +0 -21
  381. package/templates/default/src/lib/locator-picker/suggestions.ts +4 -2
  382. package/templates/default/src/lib/metrics/metric-calculator.ts +2 -6
  383. package/templates/default/src/lib/module-hierarchy-builder.ts +205 -205
  384. package/templates/default/src/lib/path-helpers/module-path.ts +71 -71
  385. package/templates/default/src/lib/sync/sync-executor.test.ts +76 -0
  386. package/templates/default/src/lib/sync/sync-pending-counts.test.ts +227 -226
  387. package/templates/default/src/lib/sync/sync-pending-counts.ts +2 -5
  388. package/templates/default/src/lib/template-sync-utils.d.ts +6 -6
  389. package/templates/default/src/lib/template-sync-utils.js +46 -46
  390. package/templates/default/src/lib/template-sync-utils.ts +63 -63
  391. package/templates/default/src/lib/test-case-utils.ts +6 -6
  392. package/templates/default/src/lib/test-run/log-formatter.ts +83 -83
  393. package/templates/default/src/lib/test-run/report-parser.ts +352 -352
  394. package/templates/default/src/lib/test-run/test-run-executor.ts +13 -13
  395. package/templates/default/src/lib/test-run/winston-logger.ts +65 -64
  396. package/templates/default/src/lib/transformers/gherkin-converter.ts +42 -42
  397. package/templates/default/src/lib/transformers/key-to-icon-transformer.tsx +95 -95
  398. package/templates/default/src/lib/transformers/template-test-case-converter.ts +160 -160
  399. package/templates/default/src/lib/utils/node-param-validation.ts +81 -81
  400. package/templates/default/src/lib/utils/template-step-file-generator.ts +2 -2
  401. package/templates/default/src/lib/utils/template-step-file-manager.ts +166 -166
  402. package/templates/default/src/lib/utils.ts +31 -31
  403. package/templates/default/src/services/dashboard/dashboard-service.test.ts +106 -0
  404. package/templates/default/src/services/dashboard/dashboard-service.ts +173 -0
  405. package/templates/default/src/services/environment/environment-service.test.ts +137 -0
  406. package/templates/default/src/services/environment/environment-service.ts +96 -0
  407. package/templates/default/src/services/locator/locator-path-utils.test.ts +14 -0
  408. package/templates/default/src/services/locator/locator-path-utils.ts +14 -0
  409. package/templates/default/src/services/locator/locator-service.test.ts +63 -0
  410. package/templates/default/src/services/locator/locator-service.ts +479 -0
  411. package/templates/default/src/services/locator/locator-sync-utils.test.ts +19 -0
  412. package/templates/default/src/services/locator/locator-sync-utils.ts +21 -0
  413. package/templates/default/src/services/locator-group/locator-group-service.test.ts +123 -0
  414. package/templates/default/src/services/locator-group/locator-group-service.ts +180 -0
  415. package/templates/default/src/services/module/module-service.test.ts +89 -0
  416. package/templates/default/src/services/module/module-service.ts +66 -0
  417. package/templates/default/src/services/report/report-service.test.ts +244 -0
  418. package/templates/default/src/services/report/report-service.ts +438 -0
  419. package/templates/default/src/services/shared/constants.ts +2 -0
  420. package/templates/default/src/services/shared/errors.test.ts +38 -0
  421. package/templates/default/src/services/shared/errors.ts +44 -0
  422. package/templates/default/src/services/shared/index.ts +7 -0
  423. package/templates/default/src/services/tag/tag-service.test.ts +22 -0
  424. package/templates/default/src/services/tag/tag-service.ts +41 -0
  425. package/templates/default/src/services/template-step/template-step-service.test.ts +22 -0
  426. package/templates/default/src/services/template-step/template-step-service.ts +171 -0
  427. package/templates/default/src/services/template-step-group/template-step-group-service.test.ts +22 -0
  428. package/templates/default/src/services/template-step-group/template-step-group-service.ts +81 -0
  429. package/templates/default/src/services/template-test-case/template-test-case-service.test.ts +22 -0
  430. package/templates/default/src/services/template-test-case/template-test-case-service.ts +128 -0
  431. package/templates/default/src/services/test-case/test-case-service.test.ts +175 -0
  432. package/templates/default/src/services/test-case/test-case-service.ts +298 -0
  433. package/templates/default/src/services/test-run/test-run-helpers.ts +61 -0
  434. package/templates/default/src/services/test-run/test-run-service.test.ts +647 -0
  435. package/templates/default/src/services/test-run/test-run-service.ts +917 -0
  436. package/templates/default/src/services/test-suite/test-suite-service.test.ts +127 -0
  437. package/templates/default/src/services/test-suite/test-suite-service.ts +197 -0
  438. package/templates/default/src/types/diagram/diagram.ts +34 -34
  439. package/templates/default/src/types/diagram/template-step.ts +11 -11
  440. package/templates/default/src/types/executor/browser.type.ts +1 -1
  441. package/templates/default/src/types/form/actionHandler.ts +19 -6
  442. package/templates/default/src/types/locator/locator.type.ts +11 -11
  443. package/templates/default/src/types/step/step.type.ts +1 -1
  444. package/templates/default/src/types/table/data-table.ts +6 -6
  445. package/templates/default/tailwind.config.ts +62 -62
  446. package/templates/default/tsconfig.json +1 -1
  447. package/dist/cli.e2e.test.d.ts +0 -2
  448. package/dist/cli.e2e.test.d.ts.map +0 -1
  449. package/dist/cli.e2e.test.js +0 -73
  450. package/dist/cli.e2e.test.js.map +0 -1
  451. package/dist/config.test.d.ts +0 -2
  452. package/dist/config.test.d.ts.map +0 -1
  453. package/dist/config.test.js +0 -65
  454. package/dist/config.test.js.map +0 -1
  455. package/dist/copy-template.test.d.ts +0 -2
  456. package/dist/copy-template.test.d.ts.map +0 -1
  457. package/dist/copy-template.test.js +0 -71
  458. package/dist/copy-template.test.js.map +0 -1
  459. package/dist/download-repo.test.d.ts +0 -2
  460. package/dist/download-repo.test.d.ts.map +0 -1
  461. package/dist/download-repo.test.js +0 -14
  462. package/dist/download-repo.test.js.map +0 -1
  463. package/dist/install.test.d.ts +0 -2
  464. package/dist/install.test.d.ts.map +0 -1
  465. package/dist/install.test.js +0 -119
  466. package/dist/install.test.js.map +0 -1
  467. package/dist/prompts.test.d.ts +0 -2
  468. package/dist/prompts.test.d.ts.map +0 -1
  469. package/dist/prompts.test.js +0 -58
  470. package/dist/prompts.test.js.map +0 -1
  471. package/templates/default/src/actions/conflict/conflict.action.ts +0 -33
  472. package/templates/default/src/actions/review/review-actions.ts +0 -147
  473. package/templates/default/src/actions/user/user-actions.ts +0 -13
  474. package/templates/default/src/app/(base)/locators/locator-form.tsx +0 -163
  475. package/templates/default/src/app/(base)/reviews/create/page.tsx +0 -26
  476. package/templates/default/src/app/(base)/reviews/created-reviews-table.tsx +0 -15
  477. package/templates/default/src/app/(base)/reviews/modify/[id]/page.tsx +0 -26
  478. package/templates/default/src/app/(base)/reviews/page.tsx +0 -26
  479. package/templates/default/src/app/(base)/reviews/review/[id]/page.tsx +0 -26
  480. package/templates/default/src/app/(base)/reviews/review-form.tsx +0 -11
  481. package/templates/default/src/app/(base)/reviews/review-table-by-creator-columns.tsx +0 -9
  482. package/templates/default/src/app/(base)/reviews/review-table-by-reviewer-columns.tsx +0 -9
  483. package/templates/default/src/app/(base)/reviews/reviewer-reviews-table.tsx +0 -15
  484. package/templates/default/src/constants/form-opts/locator-form-opts.ts +0 -20
  485. package/templates/default/src/constants/form-opts/review-form-opts.ts +0 -23
@@ -1,345 +1,93 @@
1
1
  'use server'
2
2
 
3
- import prisma from '@/config/db-config'
4
3
  import { testRunSchema } from '@/constants/form-opts/test-run-form-opts'
5
4
  import { ActionResponse } from '@/types/form/actionHandler'
6
5
  import { z } from 'zod'
7
- import {
8
- TestRunStatus,
9
- TestRunResult,
10
- TestRunTestCaseStatus,
11
- TestRunTestCaseResult,
12
- Tag,
13
- } from '@prisma/client'
14
- import { localExecutorAdapter } from '@/lib/executor/local-executor-adapter'
15
6
  import { revalidatePath } from 'next/cache'
16
- import { formatLogsForStorage, parseLogsFromStorage, type LogEntry } from '@/lib/test-run/log-formatter'
17
- import { processManager } from '@/lib/test-run/process-manager'
18
- import { createTestRunLogger, closeLogger, getLogFilePath } from '@/lib/test-run/winston-logger'
19
- import { promises as fs } from 'fs'
20
- import { Prisma } from '@prisma/client'
21
- import { updateTestCaseMetrics, updateMetricsForTestRun } from '@/lib/metrics/metric-calculator'
22
- import { getAutomationReportRunDir, resolveStoredPath } from '@/lib/automation/paths'
23
- import { ensureTestSuiteIdentifierTags } from '@/lib/test-suite-identifier-service'
24
- import { getIdentifierTagByPrefix } from '@/lib/tag-utils'
25
- import { findMatchingTestRunTestCase } from '@/lib/test-run/matching'
26
-
27
- /**
28
- * Check if a test run name already exists
29
- */
30
- async function checkUniqueName(name: string, excludeId?: string): Promise<boolean> {
31
- const existing = await prisma.testRun.findFirst({
32
- where: {
33
- name: name,
34
- ...(excludeId && { id: { not: excludeId } }),
35
- },
36
- })
37
- return !!existing
38
- }
39
-
40
- function buildOrExpression(expressions: string[]): string | null {
41
- if (expressions.length === 0) {
42
- return null
43
- }
44
-
45
- if (expressions.length === 1) {
46
- return expressions[0]
47
- }
48
-
49
- return expressions.join(' or ')
50
- }
51
-
52
- function normalizeSuiteSelection(
53
- selection: z.infer<typeof testRunSchema>['testSuites'][number],
54
- availableTestCaseIds: string[],
55
- ) {
56
- if (selection.runAll) {
57
- return {
58
- testSuiteId: selection.testSuiteId,
59
- runAll: true,
60
- testCaseIds: [] as string[],
61
- }
62
- }
63
-
64
- const selectedTestCaseIds = selection.testCaseIds.filter(testCaseId => availableTestCaseIds.includes(testCaseId))
65
-
66
- if (selectedTestCaseIds.length === availableTestCaseIds.length) {
67
- return {
68
- testSuiteId: selection.testSuiteId,
69
- runAll: true,
70
- testCaseIds: [] as string[],
71
- }
72
- }
73
-
74
- return {
75
- testSuiteId: selection.testSuiteId,
76
- runAll: false,
77
- testCaseIds: selectedTestCaseIds,
78
- }
79
- }
7
+ import {
8
+ cancelTestRunService,
9
+ checkTraceViewerStatusService,
10
+ createTestRunFromValidatedValue,
11
+ deleteTestRunsByIds,
12
+ getTestRunLogsService,
13
+ getTestRunByIdOrThrow,
14
+ isTestRunNameTaken,
15
+ listTestRuns,
16
+ listTestSuiteTestCases,
17
+ spawnTraceViewerService,
18
+ } from '@/services/test-run/test-run-service'
19
+ import { ServiceError, serviceErrorToActionResponse, unknownErrorToActionResponse } from '@/services/shared/errors'
80
20
 
81
21
  export async function getAllTestRunsAction(filter?: string): Promise<ActionResponse> {
82
22
  try {
83
- // Build the where clause based on filter
84
- const whereClause: Prisma.TestRunWhereInput = {}
85
-
86
- if (filter === 'recentFailed') {
87
- // Calculate the date 7 days ago
88
- const sevenDaysAgo = new Date()
89
- sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7)
90
-
91
- whereClause.result = TestRunResult.FAILED
92
- whereClause.completedAt = {
93
- not: null,
94
- gte: sevenDaysAgo,
95
- }
96
- }
97
-
98
- const testRuns = await prisma.testRun.findMany({
99
- where: whereClause,
100
- include: {
101
- testCases: true,
102
- tags: true,
103
- environment: true,
104
- },
105
- })
23
+ const testRuns = await listTestRuns(filter)
106
24
  return {
107
25
  status: 200,
26
+ success: true,
108
27
  data: testRuns,
109
28
  }
110
29
  } catch (error) {
111
- return {
112
- status: 500,
113
- error: `Server error occurred: ${error}`,
114
- }
30
+ return unknownErrorToActionResponse(error)
115
31
  }
116
32
  }
117
33
 
118
34
  export async function getTestRunByIdAction(id: string): Promise<ActionResponse> {
119
35
  try {
120
- const testRun = await prisma.testRun.findUnique({
121
- where: { id },
122
- include: {
123
- testCases: {
124
- include: {
125
- testCase: true,
126
- testSuite: true,
127
- },
128
- },
129
- tags: true,
130
- environment: true,
131
- reports: true,
132
- },
133
- })
134
-
135
- if (!testRun) {
136
- return {
137
- status: 404,
138
- error: 'Test run not found',
139
- }
140
- }
141
-
36
+ const testRun = await getTestRunByIdOrThrow(id)
142
37
  return {
143
38
  status: 200,
39
+ success: true,
144
40
  data: testRun,
145
41
  }
146
42
  } catch (error) {
147
- return {
148
- status: 500,
149
- error: `Server error occurred: ${error}`,
43
+ if (error instanceof ServiceError) {
44
+ return serviceErrorToActionResponse(error)
150
45
  }
46
+ return unknownErrorToActionResponse(error)
151
47
  }
152
48
  }
153
49
 
154
50
  export async function deleteTestRunAction(id: string[]): Promise<ActionResponse> {
155
51
  try {
156
- const testRuns = await prisma.testRun.findMany({
157
- where: { id: { in: id } },
158
- select: {
159
- runId: true,
160
- logPath: true,
161
- reportPath: true,
162
- testCases: {
163
- select: {
164
- tracePath: true,
165
- },
166
- },
167
- },
168
- })
169
-
170
- for (const testRun of testRuns) {
171
- await fs.rm(getAutomationReportRunDir(testRun.runId), { recursive: true, force: true })
172
-
173
- const legacyArtifactPaths = [
174
- testRun.logPath,
175
- testRun.reportPath,
176
- ...testRun.testCases.map(testCase => testCase.tracePath),
177
- ].filter((artifactPath): artifactPath is string => Boolean(artifactPath))
178
-
179
- for (const artifactPath of legacyArtifactPaths) {
180
- await fs.rm(resolveStoredPath(artifactPath), { force: true }).catch(() => {})
181
- }
182
- }
183
-
184
- // delete the test runs
185
- await prisma.testRun.deleteMany({
186
- where: { id: { in: id } },
187
- })
188
-
189
- // Recalculate metrics for affected test cases and dashboard metrics
190
- // Note: We recalculate all test case metrics, not just affected ones, because
191
- // deleting a test run might affect consecutive failure counts for any test case
192
- // that had recent runs (e.g., if a test case had 3 consecutive failures and we
193
- // delete one of those failures, it might no longer be "repeatedly failing")
194
- const { recalculateMetricsForTestCases, updateDashboardMetrics } = await import('@/lib/metrics/metric-calculator')
195
-
196
- // Get all test case IDs that have recent test runs (last 7 days)
197
- // These are the ones that might be affected by the deletion
198
- // We recalculate all of them because deleting a test run might affect
199
- // consecutive failure counts for any test case
200
- const recentPeriodDate = new Date()
201
- recentPeriodDate.setDate(recentPeriodDate.getDate() - 7)
202
-
203
- const allRecentTestRunTestCases = await prisma.testRunTestCase.findMany({
204
- where: {
205
- status: TestRunTestCaseStatus.COMPLETED,
206
- testRun: {
207
- completedAt: {
208
- gte: recentPeriodDate,
209
- },
210
- },
211
- },
212
- select: {
213
- testCaseId: true,
214
- },
215
- })
216
-
217
- // Get unique test case IDs
218
- const allAffectedTestCaseIds = [...new Set(allRecentTestRunTestCases.map(trtc => trtc.testCaseId))]
219
-
220
- // Recalculate metrics for all test cases with recent runs
221
- if (allAffectedTestCaseIds.length > 0) {
222
- await recalculateMetricsForTestCases(allAffectedTestCaseIds)
223
- }
224
-
225
- // Always update dashboard metrics (e.g., failedRecentRunsCount might change)
226
- await updateDashboardMetrics()
52
+ await deleteTestRunsByIds(id)
227
53
 
228
- // Revalidate paths
229
54
  revalidatePath('/test-runs')
230
55
  revalidatePath('/')
231
56
  return {
232
57
  status: 200,
58
+ success: true,
233
59
  message: 'Test run(s) deleted successfully',
234
60
  }
235
61
  } catch (error) {
236
- return {
237
- status: 500,
238
- error: `Server error occurred: ${error}`,
239
- }
62
+ return unknownErrorToActionResponse(error)
240
63
  }
241
64
  }
242
65
 
243
66
  export async function getAllTestSuiteTestCasesAction(): Promise<ActionResponse> {
244
67
  try {
245
- await ensureTestSuiteIdentifierTags()
246
-
247
- const testSuiteTestCases = await prisma.testSuite.findMany({
248
- include: {
249
- module: true,
250
- tags: true,
251
- testCases: {
252
- include: {
253
- steps: true,
254
- tags: true,
255
- },
256
- },
257
- },
258
- })
68
+ const testSuiteTestCases = await listTestSuiteTestCases()
259
69
  return {
260
70
  status: 200,
71
+ success: true,
261
72
  data: testSuiteTestCases,
262
73
  }
263
74
  } catch (error) {
264
- return {
265
- status: 500,
266
- error: `Server error occurred: ${error}`,
267
- }
75
+ return unknownErrorToActionResponse(error)
268
76
  }
269
77
  }
270
78
 
271
- /**
272
- * Stores test run logs in the database
273
- * @param testRunId - The test run ID (runId, not id)
274
- * @param logs - Array of log entries to store
275
- */
276
- export async function storeTestRunLogsAction(testRunId: string, logs: LogEntry[]): Promise<ActionResponse> {
277
- try {
278
- if (logs.length === 0) {
279
- return {
280
- status: 200,
281
- message: 'No logs to store',
282
- }
283
- }
284
-
285
- // Format logs for storage
286
- const formattedLogs = formatLogsForStorage(logs)
287
-
288
- // Upsert logs in TestRunLog table
289
- await prisma.testRunLog.upsert({
290
- where: { testRunId },
291
- create: {
292
- testRunId,
293
- logs: formattedLogs,
294
- },
295
- update: {
296
- logs: formattedLogs,
297
- },
298
- })
299
-
300
- return {
301
- status: 200,
302
- message: 'Logs stored successfully',
303
- }
304
- } catch (error) {
305
- console.error(`[TestRunAction] Error storing logs for testRunId: ${testRunId}:`, error)
306
- return {
307
- status: 500,
308
- error: `Server error occurred: ${error instanceof Error ? error.message : 'Unknown error'}`,
309
- }
310
- }
311
- }
312
-
313
- /**
314
- * Retrieves test run logs from the database
315
- * @param testRunId - The test run ID (runId, not id)
316
- */
317
79
  export async function getTestRunLogsAction(testRunId: string): Promise<ActionResponse> {
318
80
  try {
319
- const testRunLog = await prisma.testRunLog.findUnique({
320
- where: { testRunId },
321
- })
322
-
323
- if (!testRunLog) {
324
- return {
325
- status: 200,
326
- data: [],
327
- }
328
- }
329
-
330
- // Parse logs from storage
331
- const logs = parseLogsFromStorage(testRunLog.logs)
81
+ const logs = await getTestRunLogsService(testRunId)
332
82
 
333
83
  return {
334
84
  status: 200,
85
+ success: true,
335
86
  data: logs,
336
87
  }
337
88
  } catch (error) {
338
89
  console.error(`[TestRunAction] Error retrieving logs for testRunId: ${testRunId}:`, error)
339
- return {
340
- status: 500,
341
- error: `Server error occurred: ${error instanceof Error ? error.message : 'Unknown error'}`,
342
- }
90
+ return unknownErrorToActionResponse(error)
343
91
  }
344
92
  }
345
93
 
@@ -348,669 +96,51 @@ export async function createTestRunAction(
348
96
  value: z.infer<typeof testRunSchema>,
349
97
  ): Promise<ActionResponse> {
350
98
  try {
351
- // Validate input
352
99
  testRunSchema.parse(value)
353
100
 
354
- // Check if name already exists
355
- const nameExists = await checkUniqueName(value.name)
356
- if (nameExists) {
357
- return {
358
- status: 400,
359
- error: 'A test run with this name already exists. Please choose a different name.',
360
- }
361
- }
362
-
363
- // Fetch environment and tags from database
364
- const environment = await prisma.environment.findUnique({
365
- where: { id: value.environmentId },
366
- })
367
-
368
- if (!environment) {
369
- return {
370
- status: 400,
371
- error: 'Environment not found',
372
- }
373
- }
374
-
375
- // Determine if we're filtering by tags or test suites
376
- const isFilteringByTags = value.tags.length > 0
377
- const isFilteringByTestSuites = value.testSuites.length > 0 && value.tags.length === 0
378
-
379
- // Validate that at least one filtering option is provided
380
- if (!isFilteringByTags && !isFilteringByTestSuites) {
381
- return {
382
- status: 400,
383
- error: 'Either tags or test suites must be provided to filter the test run.',
384
- }
385
- }
386
-
387
- let tags: Tag[] = []
388
- let tagExpression: string | null = null
389
- let testRunTestCases: Array<{ testCaseId: string; testSuiteId?: string | null }> = []
390
-
391
- if (isFilteringByTags) {
392
- tags = await prisma.tag.findMany({
393
- where: { id: { in: value.tags } },
394
- })
395
-
396
- tagExpression = buildOrExpression(tags.map(tag => `(${tag.tagExpression})`))
397
-
398
- // Find test cases that have tags directly OR belong to test suites with tags
399
- const tagFilteredTestCases = await prisma.testCase.findMany({
400
- where: {
401
- OR: [
402
- // Test cases with tags directly
403
- {
404
- tags: {
405
- some: { id: { in: value.tags } },
406
- },
407
- },
408
- // Test cases in test suites with tags
409
- {
410
- TestSuite: {
411
- some: {
412
- tags: {
413
- some: { id: { in: value.tags } },
414
- },
415
- },
416
- },
417
- },
418
- ],
419
- },
420
- })
421
-
422
- testRunTestCases = tagFilteredTestCases.map(tc => ({
423
- testCaseId: tc.id,
424
- testSuiteId: null,
425
- }))
426
- } else if (isFilteringByTestSuites) {
427
- await ensureTestSuiteIdentifierTags(value.testSuites.map(testSuite => testSuite.testSuiteId))
428
-
429
- const selectedSuites = await prisma.testSuite.findMany({
430
- where: {
431
- id: {
432
- in: value.testSuites.map(testSuite => testSuite.testSuiteId),
433
- },
434
- },
435
- include: {
436
- tags: true,
437
- testCases: {
438
- include: {
439
- tags: true,
440
- },
441
- },
442
- },
443
- })
444
-
445
- if (selectedSuites.length !== value.testSuites.length) {
446
- return {
447
- status: 400,
448
- error: 'One or more selected test suites could not be found.',
449
- }
450
- }
451
-
452
- const selectedSuiteById = new Map(selectedSuites.map(testSuite => [testSuite.id, testSuite]))
453
- const suiteClauses: string[] = []
454
-
455
- for (const suiteSelection of value.testSuites) {
456
- const selectedSuite = selectedSuiteById.get(suiteSelection.testSuiteId)
457
- if (!selectedSuite) {
458
- continue
459
- }
460
-
461
- if (selectedSuite.testCases.length === 0) {
462
- continue
463
- }
464
-
465
- const normalizedSelection = normalizeSuiteSelection(
466
- suiteSelection,
467
- selectedSuite.testCases.map(testCase => testCase.id),
468
- )
469
-
470
- if (!normalizedSelection) {
471
- continue
472
- }
473
-
474
- const suiteIdentifierTag = getIdentifierTagByPrefix(selectedSuite.tags, 'ts_')
475
- if (!suiteIdentifierTag) {
476
- return {
477
- status: 400,
478
- error: `Test suite "${selectedSuite.name}" does not have an identifier tag.`,
479
- }
480
- }
481
-
482
- if (normalizedSelection.runAll) {
483
- suiteClauses.push(`(${suiteIdentifierTag.tagExpression})`)
484
- testRunTestCases.push(
485
- ...selectedSuite.testCases.map(testCase => ({
486
- testCaseId: testCase.id,
487
- testSuiteId: selectedSuite.id,
488
- })),
489
- )
490
- continue
491
- }
492
-
493
- const selectedTestCases = selectedSuite.testCases.filter(testCase =>
494
- normalizedSelection.testCaseIds.includes(testCase.id),
495
- )
496
-
497
- if (selectedTestCases.length === 0) {
498
- return {
499
- status: 400,
500
- error: `Test suite "${selectedSuite.name}" requires at least one selected test case.`,
501
- }
502
- }
503
-
504
- const missingIdentifierTestCase = selectedTestCases.find(
505
- testCase => !getIdentifierTagByPrefix(testCase.tags, 'tc_'),
506
- )
507
- if (missingIdentifierTestCase) {
508
- return {
509
- status: 400,
510
- error: `Test case "${missingIdentifierTestCase.title}" does not have an identifier tag.`,
511
- }
512
- }
513
-
514
- const testCaseTagExpressions = selectedTestCases.map(testCase => {
515
- const identifierTag = getIdentifierTagByPrefix(testCase.tags, 'tc_')
516
- return identifierTag!.tagExpression
517
- })
518
-
519
- suiteClauses.push(
520
- `(${suiteIdentifierTag.tagExpression}) and (${testCaseTagExpressions.map(tag => `(${tag})`).join(' or ')})`,
521
- )
522
- testRunTestCases.push(
523
- ...selectedTestCases.map(testCase => ({
524
- testCaseId: testCase.id,
525
- testSuiteId: selectedSuite.id,
526
- })),
527
- )
528
- }
529
-
530
- tagExpression = buildOrExpression(suiteClauses.map(clause => `(${clause})`))
531
- }
532
-
533
- if (!tagExpression) {
534
- return {
535
- status: 400,
536
- error: 'No executable tests were resolved from the selected filters.',
537
- }
538
- }
539
-
540
- // Create TestRun record in database with RUNNING status
541
- const testRun = await prisma.testRun.create({
542
- data: {
543
- name: value.name,
544
- environmentId: value.environmentId,
545
- testWorkersCount: value.testWorkersCount || 1,
546
- browserEngine: value.browserEngine,
547
- status: TestRunStatus.RUNNING,
548
- result: TestRunResult.PENDING,
549
- tags: {
550
- connect: tags.map(tag => ({ id: tag.id })),
551
- },
552
- testCases: {
553
- create: testRunTestCases.map(tc => ({
554
- testCaseId: tc.testCaseId,
555
- testSuiteId: tc.testSuiteId ?? null,
556
- })),
557
- },
558
- },
559
- })
560
-
561
- // Initialize Winston logger for this test run
562
- const logger = await createTestRunLogger(testRun.runId)
563
- const logFilePath = getLogFilePath(testRun.runId)
564
-
565
- // Store log file path in database
566
- await prisma.testRun.update({
567
- where: { id: testRun.id },
568
- data: {
569
- logPath: logFilePath,
570
- },
571
- })
572
-
573
- // Execute test run asynchronously (don't await, let it run in background)
574
- try {
575
- const { process: spawnedProcess, reportPath } = await localExecutorAdapter.executeTestRun({
576
- testRunId: testRun.runId,
577
- environment,
578
- tagExpression,
579
- testWorkersCount: value.testWorkersCount || 1,
580
- browserEngine: value.browserEngine,
581
- headless: true, // Default to headless
582
- })
583
-
584
- // Store report path in TestRun record
585
- await prisma.testRun.update({
586
- where: { id: testRun.id },
587
- data: {
588
- reportPath,
589
- },
590
- })
591
-
592
- const executePromise = Promise.resolve(spawnedProcess)
593
-
594
- // Set up server-side listener for scenario::end events to update test case statuses
595
- // This ensures status updates happen even if no client is connected
596
- const onScenarioEnd = async (eventData: {
597
- testRunId: string
598
- scenarioName: string
599
- status: string
600
- tracePath?: string
601
- featureName?: string
602
- scenarioTags?: string[]
603
- }) => {
604
- // Only process events for this test run
605
- if (eventData.testRunId === testRun.runId) {
606
- console.log(
607
- `[TestRunAction] Server-side scenario::end event for testRunId: ${testRun.runId}, scenario: ${eventData.scenarioName}, status: ${eventData.status}${eventData.tracePath ? `, tracePath: ${eventData.tracePath}` : ''}`,
608
- )
609
- // Map the status string to the expected format
610
- const statusMap: Record<string, 'passed' | 'failed' | 'skipped' | 'unknown'> = {
611
- passed: 'passed',
612
- failed: 'failed',
613
- skipped: 'skipped',
614
- }
615
- const mappedStatus = statusMap[eventData.status] || 'unknown'
616
- // Update test case status in database
617
- await updateTestRunTestCaseStatusAction(testRun.runId, {
618
- scenarioName: eventData.scenarioName,
619
- status: mappedStatus,
620
- tracePath: eventData.tracePath,
621
- featureName: eventData.featureName,
622
- scenarioTags: eventData.scenarioTags,
623
- })
624
- }
625
- }
626
-
627
- // Register the server-side listener
628
- processManager.on('scenario::end', onScenarioEnd)
629
- console.log(`[TestRunAction] Registered server-side scenario::end listener for testRunId: ${testRun.runId}`)
630
-
631
- // Cleanup function to remove the listener
632
- const cleanupListener = () => {
633
- processManager.removeListener('scenario::end', onScenarioEnd)
634
- console.log(`[TestRunAction] Removed server-side scenario::end listener for testRunId: ${testRun.runId}`)
635
- }
636
-
637
- executePromise
638
- .then(async spawnedProcess => {
639
- // Wait for process to complete
640
- const exitCode = await localExecutorAdapter.waitForProcess(spawnedProcess.name)
641
-
642
- // Collect all logs from the process output
643
- const logEntries: LogEntry[] = []
644
-
645
- // Add stdout logs
646
- if (spawnedProcess.output.stdout.length > 0) {
647
- const stdoutText = spawnedProcess.output.stdout.join('')
648
- const stdoutLines = stdoutText.split('\n').filter(line => line.trim() !== '')
649
- stdoutLines.forEach((line, index) => {
650
- const timestamp = new Date(spawnedProcess.startTime.getTime() + index * 10)
651
- logEntries.push({
652
- type: 'stdout',
653
- message: line,
654
- timestamp,
655
- })
656
- // Log to Winston logger
657
- logger.info(line)
658
- })
659
- }
660
-
661
- // Add stderr logs
662
- if (spawnedProcess.output.stderr.length > 0) {
663
- const stderrText = spawnedProcess.output.stderr.join('')
664
- const stderrLines = stderrText.split('\n').filter(line => line.trim() !== '')
665
- const stdoutCount = logEntries.filter(e => e.type === 'stdout').length
666
- stderrLines.forEach((line, index) => {
667
- const timestamp = new Date(spawnedProcess.startTime.getTime() + stdoutCount * 10 + index * 10)
668
- logEntries.push({
669
- type: 'stderr',
670
- message: line,
671
- timestamp,
672
- })
673
- // Log to Winston logger
674
- logger.error(line)
675
- })
676
- }
677
-
678
- // Add exit status log
679
- const exitMessage = `Process exited with code ${exitCode}`
680
- logEntries.push({
681
- type: 'status',
682
- message: exitMessage,
683
- timestamp: spawnedProcess.endTime || new Date(),
684
- })
685
- // Log exit status to Winston logger
686
- logger.info(exitMessage)
687
-
688
- // Store logs in database
689
- await storeTestRunLogsAction(testRun.runId, logEntries)
690
-
691
- // Close Winston logger
692
- await closeLogger(logger)
693
-
694
- // Check current status before updating - preserve CANCELLED status if already set
695
- const currentTestRun = await prisma.testRun.findUnique({
696
- where: { id: testRun.id },
697
- select: { status: true, result: true },
698
- })
699
-
700
- // Only update to COMPLETED if not already CANCELLED or CANCELLING
701
- if (
702
- currentTestRun &&
703
- currentTestRun.status !== TestRunStatus.CANCELLED &&
704
- currentTestRun.status !== TestRunStatus.CANCELLING
705
- ) {
706
- // Update TestRun status based on exit code
707
- const status = exitCode === 0 ? TestRunStatus.COMPLETED : TestRunStatus.COMPLETED
708
- const result = exitCode === 0 ? TestRunResult.PASSED : TestRunResult.FAILED
709
-
710
- await prisma.testRun.update({
711
- where: { id: testRun.id },
712
- data: {
713
- status,
714
- result,
715
- completedAt: new Date(),
716
- },
717
- })
718
-
719
- // Update metrics for the completed test run
720
- try {
721
- await updateMetricsForTestRun(testRun.id)
722
- } catch (error) {
723
- console.error(`[TestRunAction] Error updating metrics for test run ${testRun.id}:`, error)
724
- // Don't fail the test run if metrics update fails
725
- }
726
- } else {
727
- // Status is already CANCELLED or CANCELLING, just update completedAt if not set
728
- if (currentTestRun && !currentTestRun.result) {
729
- await prisma.testRun.update({
730
- where: { id: testRun.id },
731
- data: {
732
- completedAt: new Date(),
733
- },
734
- })
735
- }
736
- }
737
-
738
- // Clean up the server-side event listener
739
- cleanupListener()
740
-
741
- // Store report in database if report path exists and test run is not cancelled
742
- // Check current status again to ensure we don't generate reports for cancelled runs
743
- const finalTestRunStatus = await prisma.testRun.findUnique({
744
- where: { id: testRun.id },
745
- select: { status: true },
746
- })
747
-
748
- if (
749
- finalTestRunStatus &&
750
- (finalTestRunStatus.status === TestRunStatus.CANCELLED ||
751
- finalTestRunStatus.status === TestRunStatus.CANCELLING)
752
- ) {
753
- console.log(
754
- `[TestRunAction] Skipping report generation for testRunId: ${testRun.runId} - test run was cancelled`,
755
- )
756
- } else if (reportPath) {
757
- try {
758
- const { storeReportFromFile } = await import('@/actions/reports/report-actions')
759
- const reportResult = await storeReportFromFile(testRun.runId, reportPath)
760
- if (reportResult.status === 200) {
761
- console.log(`[TestRunAction] Report stored successfully for testRunId: ${testRun.runId}`)
762
- } else {
763
- console.warn(
764
- `[TestRunAction] Failed to store report for testRunId: ${testRun.runId}: ${reportResult.error}`,
765
- )
766
- }
767
- } catch (error) {
768
- console.error(`[TestRunAction] Error storing report for testRunId: ${testRun.runId}:`, error)
769
- // Don't fail the test run if report storage fails
770
- }
771
- } else {
772
- console.warn(`[TestRunAction] No report path available for testRunId: ${testRun.runId}`)
773
- }
774
- })
775
- .catch(async error => {
776
- console.error(`[TestRunAction] Error executing test run for testRunId: ${testRun.runId}:`, error)
777
-
778
- // Log error to Winston logger
779
- logger.error(`Error executing test run: ${error instanceof Error ? error.message : String(error)}`)
780
- if (error instanceof Error && error.stack) {
781
- logger.error(error.stack)
782
- }
783
-
784
- // Close Winston logger
785
- await closeLogger(logger).catch(err => {
786
- console.error(`[TestRunAction] Error closing logger for testRunId: ${testRun.runId}:`, err)
787
- })
788
-
789
- // Check current status before updating - preserve CANCELLED status if already set
790
- const currentTestRun = await prisma.testRun.findUnique({
791
- where: { id: testRun.id },
792
- select: { status: true, result: true },
793
- })
794
-
795
- // Only update to COMPLETED if not already CANCELLED or CANCELLING
796
- if (
797
- currentTestRun &&
798
- currentTestRun.status !== TestRunStatus.CANCELLED &&
799
- currentTestRun.status !== TestRunStatus.CANCELLING
800
- ) {
801
- // Update TestRun status to indicate failure
802
- await prisma.testRun.update({
803
- where: { id: testRun.id },
804
- data: {
805
- status: TestRunStatus.COMPLETED,
806
- result: TestRunResult.FAILED,
807
- completedAt: new Date(),
808
- },
809
- })
810
- } else {
811
- // Status is already CANCELLED or CANCELLING, just update completedAt if not set
812
- if (currentTestRun && !currentTestRun.result) {
813
- await prisma.testRun.update({
814
- where: { id: testRun.id },
815
- data: {
816
- completedAt: new Date(),
817
- },
818
- })
819
- }
820
- }
821
-
822
- // Clean up the server-side event listener
823
- cleanupListener()
824
- })
825
- } catch (error) {
826
- // Catch any synchronous errors
827
- console.error(`[TestRunAction] Synchronous error calling executeTestRun for testRunId: ${testRun.runId}:`, error)
828
- console.error(`[TestRunAction] Error stack:`, error instanceof Error ? error.stack : 'No stack trace')
829
- // Note: If executeTestRun throws synchronously, the listener won't be set up, so no cleanup needed
830
- }
101
+ const result = await createTestRunFromValidatedValue(value)
831
102
 
832
103
  return {
833
104
  status: 200,
105
+ success: true,
834
106
  message: 'Test run created successfully',
835
- data: { testRunId: testRun.runId, id: testRun.id },
107
+ data: { testRunId: result.runId, id: result.id },
836
108
  }
837
109
  } catch (error) {
838
110
  console.error('Error creating test run:', error)
839
- // Handle Prisma unique constraint error
840
- if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === 'P2002') {
841
- return {
842
- status: 400,
843
- error: 'A test run with this name already exists. Please choose a different name.',
844
- }
845
- }
846
- return {
847
- status: 500,
848
- error: `Server error occurred: ${error instanceof Error ? error.message : 'Unknown error'}`,
849
- }
850
- }
851
- }
852
-
853
- /**
854
- * Updates a test case status and result in a test run based on scenario completion
855
- * @param testRunId - The test run ID (runId, not id)
856
- * @param scenarioName - The scenario name from cucumber (format: "[Test Case Title] Description")
857
- * @param status - The scenario status (passed, failed, skipped)
858
- * @param tracePath - Optional trace path for failed scenarios
859
- */
860
- export async function updateTestRunTestCaseStatusAction(
861
- testRunId: string,
862
- scenario: {
863
- scenarioName: string
864
- status: 'passed' | 'failed' | 'skipped' | 'unknown'
865
- tracePath?: string
866
- featureName?: string
867
- scenarioTags?: string[]
868
- },
869
- ): Promise<ActionResponse> {
870
- try {
871
- // Find the test run by runId
872
- const testRun = await prisma.testRun.findUnique({
873
- where: { runId: testRunId },
874
- include: {
875
- testCases: {
876
- include: {
877
- testCase: {
878
- include: {
879
- tags: true,
880
- },
881
- },
882
- testSuite: {
883
- include: {
884
- tags: true,
885
- },
886
- },
887
- },
888
- },
889
- },
890
- })
891
-
892
- if (!testRun) {
893
- return {
894
- status: 404,
895
- error: 'Test run not found',
896
- }
897
- }
898
-
899
- const matchingTestCase = findMatchingTestRunTestCase(testRun.testCases, {
900
- scenarioName: scenario.scenarioName,
901
- scenarioTags: scenario.scenarioTags,
902
- })
903
-
904
- if (!matchingTestCase) {
905
- console.log(
906
- `[TestRunAction] No matching test case found for scenario: ${scenario.scenarioName}. This is expected when scenarios run without corresponding test cases in this test run.`,
907
- )
908
- return {
909
- status: 200,
910
- message: `Scenario "${scenario.scenarioName}" completed but has no corresponding test case in this test run`,
911
- }
912
- }
913
-
914
- // Map status to TestRunTestCaseStatus and TestRunTestCaseResult
915
- const testCaseStatus: TestRunTestCaseStatus = TestRunTestCaseStatus.COMPLETED
916
- let testCaseResult: TestRunTestCaseResult
917
-
918
- switch (scenario.status) {
919
- case 'passed':
920
- testCaseResult = TestRunTestCaseResult.PASSED
921
- break
922
- case 'failed':
923
- testCaseResult = TestRunTestCaseResult.FAILED
924
- break
925
- case 'skipped':
926
- testCaseResult = TestRunTestCaseResult.UNTESTED // Skipped is treated as untested
927
- break
928
- default:
929
- testCaseResult = TestRunTestCaseResult.UNTESTED
930
- }
931
-
932
- // Update the TestRunTestCase
933
- await prisma.testRunTestCase.update({
934
- where: { id: matchingTestCase.id },
935
- data: {
936
- status: testCaseStatus,
937
- result: testCaseResult,
938
- tracePath: scenario.tracePath || null,
939
- },
940
- })
941
-
942
- // Update test case metrics
943
- try {
944
- await updateTestCaseMetrics(
945
- matchingTestCase.testCaseId,
946
- testCaseResult,
947
- testRun.completedAt || testRun.startedAt || new Date(),
948
- )
949
- } catch (error) {
950
- console.error(`[TestRunAction] Error updating metrics for test case ${matchingTestCase.testCaseId}:`, error)
951
- // Don't fail the action if metrics update fails
952
- }
953
-
954
- return {
955
- status: 200,
956
- message: 'Test case status updated successfully',
957
- }
958
- } catch (error) {
959
- console.error(
960
- `[TestRunAction] Error updating test case status for testRunId: ${testRunId}, scenario: ${scenario.scenarioName}:`,
961
- error,
962
- )
963
- return {
964
- status: 500,
965
- error: `Server error occurred: ${error instanceof Error ? error.message : 'Unknown error'}`,
111
+ if (error instanceof ServiceError) {
112
+ return serviceErrorToActionResponse(error)
966
113
  }
114
+ return unknownErrorToActionResponse(error)
967
115
  }
968
116
  }
969
117
 
970
- /**
971
- * Checks if a trace viewer is currently running for a test case
972
- * @param testRunId - The test run ID (runId, not id)
973
- * @param testCaseId - The test case ID (TestRunTestCase id, not TestCase id)
974
- * @returns ActionResponse with isRunning status
975
- */
976
118
  export async function checkTraceViewerStatusAction(testRunId: string, testCaseId: string): Promise<ActionResponse> {
977
119
  try {
978
- // Verify test run exists
979
- const testRun = await prisma.testRun.findUnique({
980
- where: { runId: testRunId },
981
- include: {
982
- testCases: {
983
- where: { id: testCaseId },
984
- },
985
- },
986
- })
120
+ const outcome = await checkTraceViewerStatusService(testRunId, testCaseId)
987
121
 
988
- if (!testRun) {
122
+ if (outcome.kind === 'test_run_not_found') {
989
123
  return {
990
124
  status: 404,
125
+ success: false,
991
126
  error: 'Test run not found',
992
127
  }
993
128
  }
994
129
 
995
- // Verify test case belongs to this test run
996
- const testRunTestCase = testRun.testCases.find(tc => tc.id === testCaseId)
997
- if (!testRunTestCase) {
130
+ if (outcome.kind === 'test_case_not_in_run') {
998
131
  return {
999
132
  status: 404,
133
+ success: false,
1000
134
  error: 'Test case not found in this test run',
1001
135
  }
1002
136
  }
1003
137
 
1004
- // Check if trace viewer process is running
1005
- const processName = `trace-viewer-${testCaseId}`
1006
- const process = localExecutorAdapter.getProcess(processName)
1007
- const isRunning = process?.isRunning ?? false
1008
-
1009
138
  return {
1010
139
  status: 200,
140
+ success: true,
1011
141
  data: {
1012
- isRunning,
1013
- processName: isRunning ? processName : null,
142
+ isRunning: outcome.isRunning,
143
+ processName: outcome.processName,
1014
144
  },
1015
145
  }
1016
146
  } catch (error) {
@@ -1018,84 +148,52 @@ export async function checkTraceViewerStatusAction(testRunId: string, testCaseId
1018
148
  `[TestRunAction] Error checking trace viewer status for testRunId: ${testRunId}, testCaseId: ${testCaseId}:`,
1019
149
  error,
1020
150
  )
1021
- return {
1022
- status: 500,
1023
- error: `Server error occurred: ${error instanceof Error ? error.message : 'Unknown error'}`,
1024
- }
151
+ return unknownErrorToActionResponse(error)
1025
152
  }
1026
153
  }
1027
154
 
1028
- /**
1029
- * Spawns Playwright trace viewer for a failed test case
1030
- * @param testRunId - The test run ID (runId, not id)
1031
- * @param testCaseId - The test case ID (TestRunTestCase id, not TestCase id)
1032
- * @returns ActionResponse indicating success or failure
1033
- */
1034
155
  export async function spawnTraceViewerAction(testRunId: string, testCaseId: string): Promise<ActionResponse> {
1035
156
  try {
1036
- // Verify test run exists
1037
- const testRun = await prisma.testRun.findUnique({
1038
- where: { runId: testRunId },
1039
- include: {
1040
- testCases: {
1041
- where: { id: testCaseId },
1042
- include: {
1043
- testCase: true,
1044
- },
1045
- },
1046
- },
1047
- })
157
+ const outcome = await spawnTraceViewerService(testRunId, testCaseId)
1048
158
 
1049
- if (!testRun) {
159
+ if (outcome.kind === 'test_run_not_found') {
1050
160
  return {
1051
161
  status: 404,
162
+ success: false,
1052
163
  error: 'Test run not found',
1053
164
  }
1054
165
  }
1055
166
 
1056
- // Verify test case belongs to this test run
1057
- const testRunTestCase = testRun.testCases.find(tc => tc.id === testCaseId)
1058
- if (!testRunTestCase) {
167
+ if (outcome.kind === 'test_case_not_in_run') {
1059
168
  return {
1060
169
  status: 404,
170
+ success: false,
1061
171
  error: 'Test case not found in this test run',
1062
172
  }
1063
173
  }
1064
174
 
1065
- // Get trace path from database
1066
- const tracePath = testRunTestCase.tracePath
1067
- if (!tracePath) {
175
+ if (outcome.kind === 'no_trace_path') {
1068
176
  return {
1069
177
  status: 400,
178
+ success: false,
1070
179
  error: 'No trace path available for this test case',
1071
180
  }
1072
181
  }
1073
182
 
1074
- const absoluteTracePath = resolveStoredPath(tracePath)
1075
-
1076
- // Validate trace file exists
1077
- try {
1078
- await fs.access(absoluteTracePath)
1079
- } catch {
183
+ if (outcome.kind === 'trace_file_missing') {
1080
184
  return {
1081
185
  status: 404,
1082
- error: `Trace file not found at path: ${tracePath}`,
186
+ success: false,
187
+ error: `Trace file not found at path: ${outcome.path}`,
1083
188
  }
1084
189
  }
1085
190
 
1086
- // Spawn playwright show-trace command
1087
- // The process is self-closing when the user closes the trace viewer
1088
- const spawnedProcess = await localExecutorAdapter.spawnTraceViewer(testCaseId, absoluteTracePath)
1089
-
1090
- console.log(
1091
- `[TestRunAction] Spawned trace viewer process for testCaseId: ${testCaseId}, tracePath: ${absoluteTracePath}`,
1092
- )
1093
-
1094
191
  return {
1095
192
  status: 200,
193
+ success: true,
1096
194
  message: 'Trace viewer launched successfully',
1097
195
  data: {
1098
- processName: spawnedProcess.name,
196
+ processName: outcome.processName,
1099
197
  },
1100
198
  }
1101
199
  } catch (error) {
@@ -1103,174 +201,69 @@ export async function spawnTraceViewerAction(testRunId: string, testCaseId: stri
1103
201
  `[TestRunAction] Error spawning trace viewer for testRunId: ${testRunId}, testCaseId: ${testCaseId}:`,
1104
202
  error,
1105
203
  )
1106
- return {
1107
- status: 500,
1108
- error: `Server error occurred: ${error instanceof Error ? error.message : 'Unknown error'}`,
1109
- }
204
+ return unknownErrorToActionResponse(error)
1110
205
  }
1111
206
  }
1112
207
 
1113
208
  export async function cancelTestRunAction(testRunId: string): Promise<ActionResponse> {
1114
209
  try {
1115
- const testRun = await prisma.testRun.findUnique({
1116
- where: { runId: testRunId },
1117
- })
1118
- if (!testRun) {
210
+ const outcome = await cancelTestRunService(testRunId)
211
+
212
+ if (outcome.kind === 'not_found') {
1119
213
  return {
1120
214
  status: 404,
215
+ success: false,
1121
216
  error: 'Test run not found',
1122
217
  }
1123
218
  }
1124
219
 
1125
- if (
1126
- testRun.status !== TestRunStatus.RUNNING &&
1127
- testRun.status !== TestRunStatus.QUEUED &&
1128
- testRun.status !== TestRunStatus.CANCELLING
1129
- ) {
220
+ if (outcome.kind === 'invalid_state') {
1130
221
  return {
1131
222
  status: 400,
1132
- error: 'Test run is not running, queued, or already being cancelled',
223
+ success: false,
224
+ error: outcome.message,
1133
225
  }
1134
226
  }
1135
227
 
1136
- // If already cancelling, don't proceed
1137
- if (testRun.status === TestRunStatus.CANCELLING) {
228
+ if (outcome.kind === 'already_cancelling') {
1138
229
  return {
1139
230
  status: 200,
231
+ success: true,
1140
232
  message: 'Test run cancellation is already in progress',
1141
233
  }
1142
234
  }
1143
235
 
1144
- // Set status to CANCELLING immediately
1145
- await prisma.testRun.update({
1146
- where: { id: testRun.id },
1147
- data: {
1148
- status: TestRunStatus.CANCELLING,
1149
- },
1150
- })
1151
-
1152
- const process = processManager.get(testRunId)
1153
- console.log(`[TestRunAction] Process: ${JSON.stringify(process)}`)
1154
-
1155
- if (!process) {
1156
- console.warn(`[TestRunAction] No process found for testRunId: ${testRunId}`)
1157
- await prisma.testRun.update({
1158
- where: { id: testRun.id },
1159
- data: {
1160
- status: TestRunStatus.CANCELLED,
1161
- result: TestRunResult.CANCELLED,
1162
- completedAt: new Date(),
1163
- },
1164
- })
236
+ if (outcome.kind === 'cancelled_no_process') {
1165
237
  return {
1166
238
  status: 200,
239
+ success: true,
1167
240
  message: 'Test run cancelled successfully',
1168
241
  }
1169
242
  }
1170
243
 
1171
- const killed = localExecutorAdapter.killProcess(process.name, 'SIGTERM')
1172
- console.log(`[TestRunAction] Killed: ${killed}`)
1173
- if (!killed) {
1174
- const forceKilled = localExecutorAdapter.killProcess(process.name, 'SIGKILL')
1175
- if (!forceKilled) {
1176
- console.warn(`[TestRunAction] Failed to force kill process for testRunId: ${testRunId}`)
1177
- }
1178
- }
1179
-
1180
- await prisma.testRun.update({
1181
- where: { id: testRun.id },
1182
- data: {
1183
- status: TestRunStatus.CANCELLED,
1184
- result: TestRunResult.CANCELLED,
1185
- completedAt: new Date(),
1186
- },
1187
- })
1188
-
1189
- await prisma.testRunTestCase.updateMany({
1190
- where: {
1191
- testRunId: testRun.id,
1192
- status: {
1193
- in: [TestRunTestCaseStatus.PENDING, TestRunTestCaseStatus.RUNNING],
1194
- },
1195
- },
1196
- data: {
1197
- status: TestRunTestCaseStatus.CANCELLED,
1198
- result: TestRunTestCaseResult.UNTESTED,
1199
- },
1200
- })
1201
-
1202
244
  revalidatePath('/test-runs')
1203
245
  revalidatePath(`/test-runs/${testRunId}`)
1204
246
 
1205
247
  return {
1206
248
  status: 200,
249
+ success: true,
1207
250
  message: 'Test run stopped successfully',
1208
251
  }
1209
252
  } catch (error) {
1210
253
  console.error(`[TestRunAction] Error stopping test run ${testRunId}:`, error)
1211
- return {
1212
- status: 500,
1213
- error: `Server error occurred: ${error instanceof Error ? error.message : 'Unknown error'}`,
1214
- }
1215
- }
1216
- }
1217
-
1218
- export async function getMostRecentTestRunAction(): Promise<ActionResponse> {
1219
- try {
1220
- const testRun = await prisma.testRun.findFirst({
1221
- orderBy: { completedAt: 'desc' },
1222
- where: {
1223
- completedAt: { not: null },
1224
- status: TestRunStatus.COMPLETED,
1225
- },
1226
- include: {
1227
- testCases: {
1228
- include: {
1229
- testCase: {
1230
- include: {
1231
- metrics: true, // Include metrics if needed
1232
- },
1233
- },
1234
- },
1235
- },
1236
- environment: true,
1237
- tags: true,
1238
- },
1239
- })
1240
-
1241
- if (!testRun) {
1242
- return {
1243
- status: 404,
1244
- error: 'No completed test run found',
1245
- }
1246
- }
1247
-
1248
- return {
1249
- status: 200,
1250
- data: testRun,
1251
- }
1252
- } catch (error) {
1253
- return {
1254
- status: 500,
1255
- error: `Server error occurred: ${error}`,
1256
- }
254
+ return unknownErrorToActionResponse(error)
1257
255
  }
1258
256
  }
1259
257
 
1260
- /**
1261
- * Check if a test run name is unique
1262
- */
1263
258
  export async function checkTestRunNameUniqueAction(name: string, excludeId?: string): Promise<ActionResponse> {
1264
259
  try {
1265
- const nameExists = await checkUniqueName(name, excludeId)
260
+ const nameExists = await isTestRunNameTaken(name, excludeId)
1266
261
  return {
1267
262
  status: 200,
263
+ success: true,
1268
264
  data: { isUnique: !nameExists },
1269
265
  }
1270
266
  } catch (error) {
1271
- return {
1272
- status: 500,
1273
- error: `Server error occurred: ${error}`,
1274
- }
267
+ return unknownErrorToActionResponse(error)
1275
268
  }
1276
269
  }