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
@@ -0,0 +1,917 @@
1
+ import prisma from '@/config/db-config'
2
+ import type { TestRun as TestRunFormValue } from '@/constants/form-opts/test-run-form-opts'
3
+ import { ServiceError } from '@/services/shared/errors'
4
+ import { RECENT_PERIOD_DAYS } from '@/services/shared/constants'
5
+ import {
6
+ TestRunStatus,
7
+ TestRunResult,
8
+ TestRunTestCaseStatus,
9
+ TestRunTestCaseResult,
10
+ Tag,
11
+ Environment,
12
+ } from '@prisma/client'
13
+ import { localExecutorAdapter } from '@/lib/executor/local-executor-adapter'
14
+ import { formatLogsForStorage, parseLogsFromStorage, type LogEntry } from '@/lib/test-run/log-formatter'
15
+ import { processManager } from '@/lib/test-run/process-manager'
16
+ import { createTestRunLogger, closeLogger, getLogFilePath } from '@/lib/test-run/winston-logger'
17
+ import { promises as fs } from 'fs'
18
+ import { updateTestCaseMetrics, updateMetricsForTestRun } from '@/lib/metrics/metric-calculator'
19
+ import { getAutomationReportRunDir, resolveStoredPath } from '@/lib/automation/automation-path-roots'
20
+ import { ensureTestSuiteIdentifierTags } from '@/lib/test-suite-identifier-service'
21
+ import { getIdentifierTagByPrefix } from '@/lib/tag-utils'
22
+ import { findMatchingTestRunTestCase } from '@/lib/test-run/matching'
23
+ import { storeReportFromFileService } from '@/services/report/report-service'
24
+ import {
25
+ buildOrExpression,
26
+ buildTestRunsWhereClause,
27
+ isCancelledOrCancellingStatus,
28
+ normalizeSuiteSelection,
29
+ } from '@/services/test-run/test-run-helpers'
30
+
31
+ export {
32
+ buildOrExpression,
33
+ buildTestRunsWhereClause,
34
+ isCancelledOrCancellingStatus,
35
+ normalizeSuiteSelection,
36
+ } from '@/services/test-run/test-run-helpers'
37
+
38
+ export async function isTestRunNameTaken(name: string, excludeId?: string): Promise<boolean> {
39
+ const existing = await prisma.testRun.findFirst({
40
+ where: {
41
+ name,
42
+ ...(excludeId && { id: { not: excludeId } }),
43
+ },
44
+ })
45
+ return !!existing
46
+ }
47
+
48
+ export async function listTestRuns(filter?: string) {
49
+ const whereClause = buildTestRunsWhereClause(filter)
50
+
51
+ return prisma.testRun.findMany({
52
+ where: whereClause,
53
+ include: {
54
+ testCases: true,
55
+ tags: true,
56
+ environment: true,
57
+ },
58
+ })
59
+ }
60
+
61
+ export async function getTestRunByIdOrThrow(id: string) {
62
+ const testRun = await prisma.testRun.findUnique({
63
+ where: { id },
64
+ include: {
65
+ testCases: {
66
+ include: {
67
+ testCase: true,
68
+ testSuite: true,
69
+ },
70
+ },
71
+ tags: true,
72
+ environment: true,
73
+ reports: true,
74
+ },
75
+ })
76
+
77
+ if (!testRun) {
78
+ throw new ServiceError('Test run not found', 'NOT_FOUND', 404)
79
+ }
80
+
81
+ return testRun
82
+ }
83
+
84
+ export async function listTestSuiteTestCases() {
85
+ await ensureTestSuiteIdentifierTags()
86
+
87
+ return prisma.testSuite.findMany({
88
+ include: {
89
+ module: true,
90
+ tags: true,
91
+ testCases: {
92
+ include: {
93
+ steps: true,
94
+ tags: true,
95
+ },
96
+ },
97
+ },
98
+ })
99
+ }
100
+
101
+ type TestRunTestCaseLink = { testCaseId: string; testSuiteId?: string | null }
102
+
103
+ async function resolveTagExpressionAndTestCases(value: TestRunFormValue): Promise<{
104
+ tagExpression: string
105
+ tags: Tag[]
106
+ testRunTestCases: TestRunTestCaseLink[]
107
+ environment: Environment
108
+ }> {
109
+ const environment = await prisma.environment.findUnique({
110
+ where: { id: value.environmentId },
111
+ })
112
+
113
+ if (!environment) {
114
+ throw new ServiceError('Environment not found', 'VALIDATION', 400)
115
+ }
116
+
117
+ const isFilteringByTags = value.tags.length > 0
118
+ const isFilteringByTestSuites = value.testSuites.length > 0 && value.tags.length === 0
119
+
120
+ if (!isFilteringByTags && !isFilteringByTestSuites) {
121
+ throw new ServiceError(
122
+ 'Either tags or test suites must be provided to filter the test run.',
123
+ 'VALIDATION',
124
+ 400,
125
+ )
126
+ }
127
+
128
+ let tags: Tag[] = []
129
+ let tagExpression: string | null = null
130
+ let testRunTestCases: TestRunTestCaseLink[] = []
131
+
132
+ if (isFilteringByTags) {
133
+ tags = await prisma.tag.findMany({
134
+ where: { id: { in: value.tags } },
135
+ })
136
+
137
+ tagExpression = buildOrExpression(tags.map(tag => `(${tag.tagExpression})`))
138
+
139
+ const tagFilteredTestCases = await prisma.testCase.findMany({
140
+ where: {
141
+ OR: [
142
+ {
143
+ tags: {
144
+ some: { id: { in: value.tags } },
145
+ },
146
+ },
147
+ {
148
+ TestSuite: {
149
+ some: {
150
+ tags: {
151
+ some: { id: { in: value.tags } },
152
+ },
153
+ },
154
+ },
155
+ },
156
+ ],
157
+ },
158
+ })
159
+
160
+ testRunTestCases = tagFilteredTestCases.map(tc => ({
161
+ testCaseId: tc.id,
162
+ testSuiteId: null,
163
+ }))
164
+ } else if (isFilteringByTestSuites) {
165
+ await ensureTestSuiteIdentifierTags(value.testSuites.map(testSuite => testSuite.testSuiteId))
166
+
167
+ const selectedSuites = await prisma.testSuite.findMany({
168
+ where: {
169
+ id: {
170
+ in: value.testSuites.map(testSuite => testSuite.testSuiteId),
171
+ },
172
+ },
173
+ include: {
174
+ tags: true,
175
+ testCases: {
176
+ include: {
177
+ tags: true,
178
+ },
179
+ },
180
+ },
181
+ })
182
+
183
+ if (selectedSuites.length !== value.testSuites.length) {
184
+ throw new ServiceError('One or more selected test suites could not be found.', 'VALIDATION', 400)
185
+ }
186
+
187
+ const selectedSuiteById = new Map(selectedSuites.map(testSuite => [testSuite.id, testSuite]))
188
+ const suiteClauses: string[] = []
189
+
190
+ for (const suiteSelection of value.testSuites) {
191
+ const selectedSuite = selectedSuiteById.get(suiteSelection.testSuiteId)
192
+ if (!selectedSuite) {
193
+ continue
194
+ }
195
+
196
+ if (selectedSuite.testCases.length === 0) {
197
+ continue
198
+ }
199
+
200
+ const normalizedSelection = normalizeSuiteSelection(
201
+ suiteSelection,
202
+ selectedSuite.testCases.map(testCase => testCase.id),
203
+ )
204
+
205
+ if (!normalizedSelection) {
206
+ continue
207
+ }
208
+
209
+ const suiteIdentifierTag = getIdentifierTagByPrefix(selectedSuite.tags, 'ts_')
210
+ if (!suiteIdentifierTag) {
211
+ throw new ServiceError(
212
+ `Test suite "${selectedSuite.name}" does not have an identifier tag.`,
213
+ 'VALIDATION',
214
+ 400,
215
+ )
216
+ }
217
+
218
+ if (normalizedSelection.runAll) {
219
+ suiteClauses.push(`(${suiteIdentifierTag.tagExpression})`)
220
+ testRunTestCases.push(
221
+ ...selectedSuite.testCases.map(testCase => ({
222
+ testCaseId: testCase.id,
223
+ testSuiteId: selectedSuite.id,
224
+ })),
225
+ )
226
+ continue
227
+ }
228
+
229
+ const selectedTestCases = selectedSuite.testCases.filter(testCase =>
230
+ normalizedSelection.testCaseIds.includes(testCase.id),
231
+ )
232
+
233
+ if (selectedTestCases.length === 0) {
234
+ throw new ServiceError(
235
+ `Test suite "${selectedSuite.name}" requires at least one selected test case.`,
236
+ 'VALIDATION',
237
+ 400,
238
+ )
239
+ }
240
+
241
+ const missingIdentifierTestCase = selectedTestCases.find(
242
+ testCase => !getIdentifierTagByPrefix(testCase.tags, 'tc_'),
243
+ )
244
+ if (missingIdentifierTestCase) {
245
+ throw new ServiceError(
246
+ `Test case "${missingIdentifierTestCase.title}" does not have an identifier tag.`,
247
+ 'VALIDATION',
248
+ 400,
249
+ )
250
+ }
251
+
252
+ const testCaseTagExpressions = selectedTestCases.map(testCase => {
253
+ const identifierTag = getIdentifierTagByPrefix(testCase.tags, 'tc_')
254
+ return identifierTag!.tagExpression
255
+ })
256
+
257
+ suiteClauses.push(
258
+ `(${suiteIdentifierTag.tagExpression}) and (${testCaseTagExpressions.map(tag => `(${tag})`).join(' or ')})`,
259
+ )
260
+ testRunTestCases.push(
261
+ ...selectedTestCases.map(testCase => ({
262
+ testCaseId: testCase.id,
263
+ testSuiteId: selectedSuite.id,
264
+ })),
265
+ )
266
+ }
267
+
268
+ tagExpression = buildOrExpression(suiteClauses.map(clause => `(${clause})`))
269
+ }
270
+
271
+ if (!tagExpression) {
272
+ throw new ServiceError('No executable tests were resolved from the selected filters.', 'VALIDATION', 400)
273
+ }
274
+
275
+ return { tagExpression, tags, testRunTestCases, environment }
276
+ }
277
+
278
+ export type UpdateScenarioStatusResult =
279
+ | { kind: 'updated' }
280
+ | { kind: 'no_match'; message: string }
281
+ | { kind: 'test_run_not_found' }
282
+
283
+ export async function updateTestRunTestCaseStatusFromScenario(
284
+ testRunId: string,
285
+ scenario: {
286
+ scenarioName: string
287
+ status: 'passed' | 'failed' | 'skipped' | 'unknown'
288
+ tracePath?: string
289
+ featureName?: string
290
+ scenarioTags?: string[]
291
+ },
292
+ ): Promise<UpdateScenarioStatusResult> {
293
+ const testRun = await prisma.testRun.findUnique({
294
+ where: { runId: testRunId },
295
+ include: {
296
+ testCases: {
297
+ include: {
298
+ testCase: {
299
+ include: {
300
+ tags: true,
301
+ },
302
+ },
303
+ testSuite: {
304
+ include: {
305
+ tags: true,
306
+ },
307
+ },
308
+ },
309
+ },
310
+ },
311
+ })
312
+
313
+ if (!testRun) {
314
+ return { kind: 'test_run_not_found' }
315
+ }
316
+
317
+ const matchingTestCase = findMatchingTestRunTestCase(testRun.testCases, {
318
+ scenarioName: scenario.scenarioName,
319
+ scenarioTags: scenario.scenarioTags,
320
+ })
321
+
322
+ if (!matchingTestCase) {
323
+ console.log(
324
+ `[TestRunService] No matching test case found for scenario: ${scenario.scenarioName}. This is expected when scenarios run without corresponding test cases in this test run.`,
325
+ )
326
+ return {
327
+ kind: 'no_match',
328
+ message: `Scenario "${scenario.scenarioName}" completed but has no corresponding test case in this test run`,
329
+ }
330
+ }
331
+
332
+ const testCaseStatus: TestRunTestCaseStatus = TestRunTestCaseStatus.COMPLETED
333
+ let testCaseResult: TestRunTestCaseResult
334
+
335
+ switch (scenario.status) {
336
+ case 'passed':
337
+ testCaseResult = TestRunTestCaseResult.PASSED
338
+ break
339
+ case 'failed':
340
+ testCaseResult = TestRunTestCaseResult.FAILED
341
+ break
342
+ case 'skipped':
343
+ testCaseResult = TestRunTestCaseResult.UNTESTED
344
+ break
345
+ default:
346
+ testCaseResult = TestRunTestCaseResult.UNTESTED
347
+ }
348
+
349
+ await prisma.testRunTestCase.update({
350
+ where: { id: matchingTestCase.id },
351
+ data: {
352
+ status: testCaseStatus,
353
+ result: testCaseResult,
354
+ tracePath: scenario.tracePath || null,
355
+ },
356
+ })
357
+
358
+ try {
359
+ await updateTestCaseMetrics(
360
+ matchingTestCase.testCaseId,
361
+ testCaseResult,
362
+ testRun.completedAt || testRun.startedAt || new Date(),
363
+ )
364
+ } catch (error) {
365
+ console.error(`[TestRunService] Error updating metrics for test case ${matchingTestCase.testCaseId}:`, error)
366
+ }
367
+
368
+ return { kind: 'updated' }
369
+ }
370
+
371
+ async function persistLogsAndUpdateRunStatus(args: {
372
+ testRunDbId: string
373
+ runId: string
374
+ logEntries: LogEntry[]
375
+ logger: Awaited<ReturnType<typeof createTestRunLogger>>
376
+ exitCode: number
377
+ }): Promise<void> {
378
+ const { testRunDbId, runId, logEntries, logger, exitCode } = args
379
+
380
+ await storeTestRunLogsService(runId, logEntries)
381
+ await closeLogger(logger)
382
+
383
+ const currentTestRun = await prisma.testRun.findUnique({
384
+ where: { id: testRunDbId },
385
+ select: { status: true, result: true },
386
+ })
387
+
388
+ if (currentTestRun && !isCancelledOrCancellingStatus(currentTestRun.status)) {
389
+ const result = exitCode === 0 ? TestRunResult.PASSED : TestRunResult.FAILED
390
+
391
+ await prisma.testRun.update({
392
+ where: { id: testRunDbId },
393
+ data: {
394
+ status: TestRunStatus.COMPLETED,
395
+ result,
396
+ completedAt: new Date(),
397
+ },
398
+ })
399
+
400
+ try {
401
+ await updateMetricsForTestRun(testRunDbId)
402
+ } catch (error) {
403
+ console.error(`[TestRunService] Error updating metrics for test run ${testRunDbId}:`, error)
404
+ }
405
+ } else if (currentTestRun && !currentTestRun.result) {
406
+ await prisma.testRun.update({
407
+ where: { id: testRunDbId },
408
+ data: {
409
+ completedAt: new Date(),
410
+ },
411
+ })
412
+ }
413
+ }
414
+
415
+ async function storeReportAfterRunIfNeeded(testRunDbId: string, runId: string, reportPath: string | null | undefined): Promise<void> {
416
+ const finalTestRunStatus = await prisma.testRun.findUnique({
417
+ where: { id: testRunDbId },
418
+ select: { status: true },
419
+ })
420
+
421
+ if (finalTestRunStatus && isCancelledOrCancellingStatus(finalTestRunStatus.status)) {
422
+ console.log(`[TestRunService] Skipping report generation for testRunId: ${runId} - test run was cancelled`)
423
+ } else if (reportPath) {
424
+ try {
425
+ const reportOutcome = await storeReportFromFileService(runId, reportPath)
426
+ if (reportOutcome.success) {
427
+ console.log(`[TestRunService] Report stored successfully for testRunId: ${runId}`)
428
+ } else {
429
+ console.warn(`[TestRunService] Failed to store report for testRunId: ${runId}: ${reportOutcome.message}`)
430
+ }
431
+ } catch (error) {
432
+ console.error(`[TestRunService] Error storing report for testRunId: ${runId}:`, error)
433
+ }
434
+ } else {
435
+ console.warn(`[TestRunService] No report path available for testRunId: ${runId}`)
436
+ }
437
+ }
438
+
439
+ async function scheduleTestRunCompletion(args: {
440
+ testRun: { id: string; runId: string }
441
+ environment: Environment
442
+ tagExpression: string
443
+ value: TestRunFormValue
444
+ logger: Awaited<ReturnType<typeof createTestRunLogger>>
445
+ }): Promise<void> {
446
+ const { testRun, environment, tagExpression, value, logger } = args
447
+
448
+ try {
449
+ const { process: spawnedProcess, reportPath } = await localExecutorAdapter.executeTestRun({
450
+ testRunId: testRun.runId,
451
+ environment,
452
+ tagExpression,
453
+ testWorkersCount: value.testWorkersCount || 1,
454
+ browserEngine: value.browserEngine,
455
+ headless: true,
456
+ })
457
+
458
+ await prisma.testRun.update({
459
+ where: { id: testRun.id },
460
+ data: { reportPath },
461
+ })
462
+
463
+ const onScenarioEnd = async (eventData: {
464
+ testRunId: string
465
+ scenarioName: string
466
+ status: string
467
+ tracePath?: string
468
+ featureName?: string
469
+ scenarioTags?: string[]
470
+ }) => {
471
+ if (eventData.testRunId !== testRun.runId) {
472
+ return
473
+ }
474
+ console.log(
475
+ `[TestRunService] Server-side scenario::end event for testRunId: ${testRun.runId}, scenario: ${eventData.scenarioName}, status: ${eventData.status}${eventData.tracePath ? `, tracePath: ${eventData.tracePath}` : ''}`,
476
+ )
477
+ const statusMap: Record<string, 'passed' | 'failed' | 'skipped' | 'unknown'> = {
478
+ passed: 'passed',
479
+ failed: 'failed',
480
+ skipped: 'skipped',
481
+ }
482
+ const mappedStatus = statusMap[eventData.status] || 'unknown'
483
+ await updateTestRunTestCaseStatusFromScenario(testRun.runId, {
484
+ scenarioName: eventData.scenarioName,
485
+ status: mappedStatus,
486
+ tracePath: eventData.tracePath,
487
+ featureName: eventData.featureName,
488
+ scenarioTags: eventData.scenarioTags,
489
+ })
490
+ }
491
+
492
+ processManager.on('scenario::end', onScenarioEnd)
493
+ console.log(`[TestRunService] Registered server-side scenario::end listener for testRunId: ${testRun.runId}`)
494
+
495
+ const cleanupListener = () => {
496
+ processManager.removeListener('scenario::end', onScenarioEnd)
497
+ console.log(`[TestRunService] Removed server-side scenario::end listener for testRunId: ${testRun.runId}`)
498
+ }
499
+
500
+ const executePromise = Promise.resolve(spawnedProcess)
501
+
502
+ executePromise
503
+ .then(async proc => {
504
+ const exitCodeRaw = await localExecutorAdapter.waitForProcess(proc.name)
505
+ const exitCode = exitCodeRaw ?? 1
506
+
507
+ const logEntries: LogEntry[] = []
508
+
509
+ if (proc.output.stdout.length > 0) {
510
+ const stdoutText = proc.output.stdout.join('')
511
+ const stdoutLines = stdoutText.split('\n').filter(line => line.trim() !== '')
512
+ stdoutLines.forEach((line, index) => {
513
+ const timestamp = new Date(proc.startTime.getTime() + index * 10)
514
+ logEntries.push({
515
+ type: 'stdout',
516
+ message: line,
517
+ timestamp,
518
+ })
519
+ logger.info(line)
520
+ })
521
+ }
522
+
523
+ if (proc.output.stderr.length > 0) {
524
+ const stderrText = proc.output.stderr.join('')
525
+ const stderrLines = stderrText.split('\n').filter(line => line.trim() !== '')
526
+ const stdoutCount = logEntries.filter(e => e.type === 'stdout').length
527
+ stderrLines.forEach((line, index) => {
528
+ const timestamp = new Date(proc.startTime.getTime() + stdoutCount * 10 + index * 10)
529
+ logEntries.push({
530
+ type: 'stderr',
531
+ message: line,
532
+ timestamp,
533
+ })
534
+ logger.error(line)
535
+ })
536
+ }
537
+
538
+ const exitMessage = `Process exited with code ${exitCode}`
539
+ logEntries.push({
540
+ type: 'status',
541
+ message: exitMessage,
542
+ timestamp: proc.endTime || new Date(),
543
+ })
544
+ logger.info(exitMessage)
545
+
546
+ await persistLogsAndUpdateRunStatus({
547
+ testRunDbId: testRun.id,
548
+ runId: testRun.runId,
549
+ logEntries,
550
+ logger,
551
+ exitCode,
552
+ })
553
+
554
+ cleanupListener()
555
+
556
+ await storeReportAfterRunIfNeeded(testRun.id, testRun.runId, reportPath)
557
+ })
558
+ .catch(async error => {
559
+ console.error(`[TestRunService] Error executing test run for testRunId: ${testRun.runId}:`, error)
560
+
561
+ logger.error(`Error executing test run: ${error instanceof Error ? error.message : String(error)}`)
562
+ if (error instanceof Error && error.stack) {
563
+ logger.error(error.stack)
564
+ }
565
+
566
+ await closeLogger(logger).catch(err => {
567
+ console.error(`[TestRunService] Error closing logger for testRunId: ${testRun.runId}:`, err)
568
+ })
569
+
570
+ const currentTestRun = await prisma.testRun.findUnique({
571
+ where: { id: testRun.id },
572
+ select: { status: true, result: true },
573
+ })
574
+
575
+ if (currentTestRun && !isCancelledOrCancellingStatus(currentTestRun.status)) {
576
+ await prisma.testRun.update({
577
+ where: { id: testRun.id },
578
+ data: {
579
+ status: TestRunStatus.COMPLETED,
580
+ result: TestRunResult.FAILED,
581
+ completedAt: new Date(),
582
+ },
583
+ })
584
+ } else if (currentTestRun && !currentTestRun.result) {
585
+ await prisma.testRun.update({
586
+ where: { id: testRun.id },
587
+ data: {
588
+ completedAt: new Date(),
589
+ },
590
+ })
591
+ }
592
+
593
+ cleanupListener()
594
+ })
595
+ } catch (error) {
596
+ console.error(`[TestRunService] Synchronous error calling executeTestRun for testRunId: ${testRun.runId}:`, error)
597
+ console.error(`[TestRunService] Error stack:`, error instanceof Error ? error.stack : 'No stack trace')
598
+ }
599
+ }
600
+
601
+ export async function createTestRunFromValidatedValue(value: TestRunFormValue): Promise<{ runId: string; id: string }> {
602
+ const nameTaken = await isTestRunNameTaken(value.name)
603
+ if (nameTaken) {
604
+ throw new ServiceError(
605
+ 'A test run with this name already exists. Please choose a different name.',
606
+ 'VALIDATION',
607
+ 400,
608
+ )
609
+ }
610
+
611
+ const { tagExpression, tags, testRunTestCases, environment } = await resolveTagExpressionAndTestCases(value)
612
+
613
+ const testRun = await prisma.testRun.create({
614
+ data: {
615
+ name: value.name,
616
+ environmentId: value.environmentId,
617
+ testWorkersCount: value.testWorkersCount || 1,
618
+ browserEngine: value.browserEngine,
619
+ status: TestRunStatus.RUNNING,
620
+ result: TestRunResult.PENDING,
621
+ tags: {
622
+ connect: tags.map(tag => ({ id: tag.id })),
623
+ },
624
+ testCases: {
625
+ create: testRunTestCases.map(tc => ({
626
+ testCaseId: tc.testCaseId,
627
+ testSuiteId: tc.testSuiteId ?? null,
628
+ })),
629
+ },
630
+ },
631
+ })
632
+
633
+ const logger = await createTestRunLogger(testRun.runId)
634
+ const logFilePath = getLogFilePath(testRun.runId)
635
+
636
+ await prisma.testRun.update({
637
+ where: { id: testRun.id },
638
+ data: {
639
+ logPath: logFilePath,
640
+ },
641
+ })
642
+
643
+ await scheduleTestRunCompletion({
644
+ testRun,
645
+ environment,
646
+ tagExpression,
647
+ value,
648
+ logger,
649
+ })
650
+
651
+ return { runId: testRun.runId, id: testRun.id }
652
+ }
653
+
654
+ export async function deleteTestRunsByIds(ids: string[]): Promise<void> {
655
+ const testRuns = await prisma.testRun.findMany({
656
+ where: { id: { in: ids } },
657
+ select: {
658
+ runId: true,
659
+ logPath: true,
660
+ reportPath: true,
661
+ testCases: {
662
+ select: {
663
+ tracePath: true,
664
+ },
665
+ },
666
+ },
667
+ })
668
+
669
+ for (const testRun of testRuns) {
670
+ await fs.rm(getAutomationReportRunDir(testRun.runId), { recursive: true, force: true })
671
+
672
+ const legacyArtifactPaths = [
673
+ testRun.logPath,
674
+ testRun.reportPath,
675
+ ...testRun.testCases.map(testCase => testCase.tracePath),
676
+ ].filter((artifactPath): artifactPath is string => Boolean(artifactPath))
677
+
678
+ for (const artifactPath of legacyArtifactPaths) {
679
+ await fs.rm(resolveStoredPath(artifactPath), { force: true }).catch(() => {})
680
+ }
681
+ }
682
+
683
+ await prisma.testRun.deleteMany({
684
+ where: { id: { in: ids } },
685
+ })
686
+
687
+ const { recalculateMetricsForTestCases, updateDashboardMetrics } = await import('@/lib/metrics/metric-calculator')
688
+
689
+ const recentPeriodDate = new Date()
690
+ recentPeriodDate.setDate(recentPeriodDate.getDate() - RECENT_PERIOD_DAYS)
691
+
692
+ const allRecentTestRunTestCases = await prisma.testRunTestCase.findMany({
693
+ where: {
694
+ status: TestRunTestCaseStatus.COMPLETED,
695
+ testRun: {
696
+ completedAt: {
697
+ gte: recentPeriodDate,
698
+ },
699
+ },
700
+ },
701
+ select: {
702
+ testCaseId: true,
703
+ },
704
+ })
705
+
706
+ const allAffectedTestCaseIds = [...new Set(allRecentTestRunTestCases.map(trtc => trtc.testCaseId))]
707
+
708
+ if (allAffectedTestCaseIds.length > 0) {
709
+ await recalculateMetricsForTestCases(allAffectedTestCaseIds)
710
+ }
711
+
712
+ await updateDashboardMetrics()
713
+ }
714
+
715
+ export async function storeTestRunLogsService(testRunId: string, logs: LogEntry[]): Promise<void> {
716
+ if (logs.length === 0) {
717
+ return
718
+ }
719
+ const formattedLogs = formatLogsForStorage(logs)
720
+ await prisma.testRunLog.upsert({
721
+ where: { testRunId },
722
+ create: {
723
+ testRunId,
724
+ logs: formattedLogs,
725
+ },
726
+ update: {
727
+ logs: formattedLogs,
728
+ },
729
+ })
730
+ }
731
+
732
+ export async function getTestRunLogsService(testRunId: string): Promise<LogEntry[]> {
733
+ const testRunLog = await prisma.testRunLog.findUnique({
734
+ where: { testRunId },
735
+ })
736
+ if (!testRunLog) {
737
+ return []
738
+ }
739
+ return parseLogsFromStorage(testRunLog.logs)
740
+ }
741
+
742
+ export type CancelTestRunOutcome =
743
+ | { kind: 'not_found' }
744
+ | { kind: 'invalid_state'; message: string }
745
+ | { kind: 'already_cancelling' }
746
+ | { kind: 'cancelled_no_process' }
747
+ | { kind: 'stopped' }
748
+
749
+ export async function cancelTestRunService(testRunId: string): Promise<CancelTestRunOutcome> {
750
+ const testRun = await prisma.testRun.findUnique({
751
+ where: { runId: testRunId },
752
+ })
753
+ if (!testRun) {
754
+ return { kind: 'not_found' }
755
+ }
756
+
757
+ if (
758
+ testRun.status !== TestRunStatus.RUNNING &&
759
+ testRun.status !== TestRunStatus.QUEUED &&
760
+ testRun.status !== TestRunStatus.CANCELLING
761
+ ) {
762
+ return {
763
+ kind: 'invalid_state',
764
+ message: 'Test run is not running, queued, or already being cancelled',
765
+ }
766
+ }
767
+
768
+ if (testRun.status === TestRunStatus.CANCELLING) {
769
+ return { kind: 'already_cancelling' }
770
+ }
771
+
772
+ await prisma.testRun.update({
773
+ where: { id: testRun.id },
774
+ data: {
775
+ status: TestRunStatus.CANCELLING,
776
+ },
777
+ })
778
+
779
+ const process = processManager.get(testRunId)
780
+ console.log(`[TestRunService] Process: ${JSON.stringify(process)}`)
781
+
782
+ if (!process) {
783
+ console.warn(`[TestRunService] No process found for testRunId: ${testRunId}`)
784
+ await prisma.testRun.update({
785
+ where: { id: testRun.id },
786
+ data: {
787
+ status: TestRunStatus.CANCELLED,
788
+ result: TestRunResult.CANCELLED,
789
+ completedAt: new Date(),
790
+ },
791
+ })
792
+ return { kind: 'cancelled_no_process' }
793
+ }
794
+
795
+ const killed = localExecutorAdapter.killProcess(process.name, 'SIGTERM')
796
+ console.log(`[TestRunService] Killed: ${killed}`)
797
+ if (!killed) {
798
+ const forceKilled = localExecutorAdapter.killProcess(process.name, 'SIGKILL')
799
+ if (!forceKilled) {
800
+ console.warn(`[TestRunService] Failed to force kill process for testRunId: ${testRunId}`)
801
+ }
802
+ }
803
+
804
+ await prisma.testRun.update({
805
+ where: { id: testRun.id },
806
+ data: {
807
+ status: TestRunStatus.CANCELLED,
808
+ result: TestRunResult.CANCELLED,
809
+ completedAt: new Date(),
810
+ },
811
+ })
812
+
813
+ await prisma.testRunTestCase.updateMany({
814
+ where: {
815
+ testRunId: testRun.id,
816
+ status: {
817
+ in: [TestRunTestCaseStatus.PENDING, TestRunTestCaseStatus.RUNNING],
818
+ },
819
+ },
820
+ data: {
821
+ status: TestRunTestCaseStatus.CANCELLED,
822
+ result: TestRunTestCaseResult.UNTESTED,
823
+ },
824
+ })
825
+
826
+ return { kind: 'stopped' }
827
+ }
828
+
829
+ export async function checkTraceViewerStatusService(
830
+ testRunId: string,
831
+ testCaseId: string,
832
+ ): Promise<
833
+ | { kind: 'ok'; isRunning: boolean; processName: string | null }
834
+ | { kind: 'test_run_not_found' }
835
+ | { kind: 'test_case_not_in_run' }
836
+ > {
837
+ const testRun = await prisma.testRun.findUnique({
838
+ where: { runId: testRunId },
839
+ include: {
840
+ testCases: {
841
+ where: { id: testCaseId },
842
+ },
843
+ },
844
+ })
845
+
846
+ if (!testRun) {
847
+ return { kind: 'test_run_not_found' }
848
+ }
849
+
850
+ const testRunTestCase = testRun.testCases.find(tc => tc.id === testCaseId)
851
+ if (!testRunTestCase) {
852
+ return { kind: 'test_case_not_in_run' }
853
+ }
854
+
855
+ const processName = `trace-viewer-${testCaseId}`
856
+ const proc = localExecutorAdapter.getProcess(processName)
857
+ const isRunning = proc?.isRunning ?? false
858
+
859
+ return {
860
+ kind: 'ok',
861
+ isRunning,
862
+ processName: isRunning ? processName : null,
863
+ }
864
+ }
865
+
866
+ export async function spawnTraceViewerService(
867
+ testRunId: string,
868
+ testCaseId: string,
869
+ ): Promise<
870
+ | { kind: 'ok'; processName: string }
871
+ | { kind: 'test_run_not_found' }
872
+ | { kind: 'test_case_not_in_run' }
873
+ | { kind: 'no_trace_path' }
874
+ | { kind: 'trace_file_missing'; path: string }
875
+ > {
876
+ const testRun = await prisma.testRun.findUnique({
877
+ where: { runId: testRunId },
878
+ include: {
879
+ testCases: {
880
+ where: { id: testCaseId },
881
+ include: {
882
+ testCase: true,
883
+ },
884
+ },
885
+ },
886
+ })
887
+
888
+ if (!testRun) {
889
+ return { kind: 'test_run_not_found' }
890
+ }
891
+
892
+ const testRunTestCase = testRun.testCases.find(tc => tc.id === testCaseId)
893
+ if (!testRunTestCase) {
894
+ return { kind: 'test_case_not_in_run' }
895
+ }
896
+
897
+ const tracePath = testRunTestCase.tracePath
898
+ if (!tracePath) {
899
+ return { kind: 'no_trace_path' }
900
+ }
901
+
902
+ const absoluteTracePath = resolveStoredPath(tracePath)
903
+
904
+ try {
905
+ await fs.access(absoluteTracePath)
906
+ } catch {
907
+ return { kind: 'trace_file_missing', path: tracePath }
908
+ }
909
+
910
+ const spawnedProcess = await localExecutorAdapter.spawnTraceViewer(testCaseId, absoluteTracePath)
911
+
912
+ console.log(
913
+ `[TestRunService] Spawned trace viewer process for testCaseId: ${testCaseId}, tracePath: ${absoluteTracePath}`,
914
+ )
915
+
916
+ return { kind: 'ok', processName: spawnedProcess.name }
917
+ }