create-appraisejs 0.1.10-alpha.8 → 0.2.0-alpha.1

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 (414) hide show
  1. package/README.md +52 -52
  2. package/dist/cli.e2e.test.d.ts +2 -0
  3. package/dist/cli.e2e.test.d.ts.map +1 -0
  4. package/dist/cli.e2e.test.js +73 -0
  5. package/dist/cli.e2e.test.js.map +1 -0
  6. package/dist/config.test.d.ts +2 -0
  7. package/dist/config.test.d.ts.map +1 -0
  8. package/dist/config.test.js +65 -0
  9. package/dist/config.test.js.map +1 -0
  10. package/dist/copy-template.test.d.ts +2 -0
  11. package/dist/copy-template.test.d.ts.map +1 -0
  12. package/dist/copy-template.test.js +71 -0
  13. package/dist/copy-template.test.js.map +1 -0
  14. package/dist/download-repo.test.d.ts +2 -0
  15. package/dist/download-repo.test.d.ts.map +1 -0
  16. package/dist/download-repo.test.js +14 -0
  17. package/dist/download-repo.test.js.map +1 -0
  18. package/dist/install.test.d.ts +2 -0
  19. package/dist/install.test.d.ts.map +1 -0
  20. package/dist/install.test.js +119 -0
  21. package/dist/install.test.js.map +1 -0
  22. package/dist/prepare-template-utils.d.ts.map +1 -1
  23. package/dist/prepare-template-utils.js +5 -0
  24. package/dist/prepare-template-utils.js.map +1 -1
  25. package/dist/prompts.test.d.ts +2 -0
  26. package/dist/prompts.test.d.ts.map +1 -0
  27. package/dist/prompts.test.js +58 -0
  28. package/dist/prompts.test.js.map +1 -0
  29. package/dist/scaffold-gitignore.d.ts +1 -0
  30. package/dist/scaffold-gitignore.d.ts.map +1 -1
  31. package/dist/scaffold-gitignore.js +3 -0
  32. package/dist/scaffold-gitignore.js.map +1 -1
  33. package/package.json +3 -2
  34. package/templates/default/.appraise-template-meta.json +2 -2
  35. package/templates/default/.env.example +2 -2
  36. package/templates/default/.vscode/settings.json +2 -1
  37. package/templates/default/README.md +53 -53
  38. package/templates/default/automation/mapping/locator-map.json +1 -14
  39. package/templates/default/automation/steps/actions/click.step.ts +58 -58
  40. package/templates/default/automation/steps/actions/hover.step.ts +27 -27
  41. package/templates/default/automation/steps/actions/input.step.ts +84 -84
  42. package/templates/default/automation/steps/actions/navigation.step.ts +70 -70
  43. package/templates/default/automation/steps/actions/random_data.step.ts +142 -142
  44. package/templates/default/automation/steps/actions/store.step.ts +86 -86
  45. package/templates/default/automation/steps/actions/wait.step.ts +90 -90
  46. package/templates/default/automation/steps/validations/active_state_assertion.step.ts +30 -30
  47. package/templates/default/automation/steps/validations/navigation_assertion.step.ts +22 -22
  48. package/templates/default/automation/steps/validations/text_assertion.step.ts +107 -107
  49. package/templates/default/automation/steps/validations/visibility_assertion.step.ts +26 -26
  50. package/templates/default/components.json +24 -24
  51. package/templates/default/cucumber.mjs +16 -16
  52. package/templates/default/eslint.config.mjs +16 -16
  53. package/templates/default/gitignore +3 -0
  54. package/templates/default/next-env.d.ts +6 -6
  55. package/templates/default/next.config.ts +11 -11
  56. package/templates/default/package-lock.json +146 -75
  57. package/templates/default/package.json +2 -1
  58. package/templates/default/packages/cucumber-runtime/package.json +13 -13
  59. package/templates/default/packages/cucumber-runtime/src/cache.util.ts +93 -93
  60. package/templates/default/packages/cucumber-runtime/src/cli.ts +68 -68
  61. package/templates/default/packages/cucumber-runtime/src/environment.util.ts +21 -21
  62. package/templates/default/packages/cucumber-runtime/src/executor.ts +32 -32
  63. package/templates/default/packages/cucumber-runtime/src/hooks.ts +25 -3
  64. package/templates/default/packages/cucumber-runtime/src/index.ts +17 -17
  65. package/templates/default/packages/cucumber-runtime/src/locator.util.ts +234 -234
  66. package/templates/default/packages/cucumber-runtime/src/parameter-types.ts +7 -7
  67. package/templates/default/packages/cucumber-runtime/src/paths.ts +66 -1
  68. package/templates/default/packages/cucumber-runtime/src/random-data.util.ts +35 -35
  69. package/templates/default/packages/cucumber-runtime/src/types.ts +13 -13
  70. package/templates/default/packages/cucumber-runtime/src/world.ts +44 -44
  71. package/templates/default/packages/cucumber-runtime/tsconfig.json +11 -11
  72. package/templates/default/packages/locator-picker-companion/dist/cli.d.ts +1 -0
  73. package/templates/default/packages/locator-picker-companion/dist/cli.js +302 -0
  74. package/templates/default/packages/locator-picker-companion/dist/index.d.ts +3 -0
  75. package/templates/default/packages/locator-picker-companion/dist/index.js +3 -0
  76. package/templates/default/packages/locator-picker-companion/dist/injected-picker-script.d.ts +1 -0
  77. package/templates/default/packages/locator-picker-companion/dist/injected-picker-script.js +615 -0
  78. package/templates/default/packages/locator-picker-companion/dist/launcher.d.ts +11 -0
  79. package/templates/default/packages/locator-picker-companion/dist/launcher.js +59 -0
  80. package/templates/default/packages/locator-picker-companion/dist/selector-generator.d.ts +3 -0
  81. package/templates/default/packages/locator-picker-companion/dist/selector-generator.js +257 -0
  82. package/templates/default/packages/locator-picker-companion/dist/session-file.d.ts +22 -0
  83. package/templates/default/packages/locator-picker-companion/dist/session-file.js +150 -0
  84. package/templates/default/packages/locator-picker-companion/dist/types.d.ts +31 -0
  85. package/templates/default/packages/locator-picker-companion/dist/types.js +1 -0
  86. package/templates/default/packages/locator-picker-companion/package.json +17 -0
  87. package/templates/default/packages/locator-picker-companion/src/cli.ts +428 -0
  88. package/templates/default/packages/locator-picker-companion/src/index.ts +3 -0
  89. package/templates/default/packages/locator-picker-companion/src/injected-picker-script.ts +771 -0
  90. package/templates/default/packages/locator-picker-companion/src/launcher.ts +77 -0
  91. package/templates/default/packages/locator-picker-companion/src/selector-generator.ts +339 -0
  92. package/templates/default/packages/locator-picker-companion/src/session-file.ts +226 -0
  93. package/templates/default/packages/locator-picker-companion/src/types.ts +42 -0
  94. package/templates/default/packages/locator-picker-companion/tsconfig.json +14 -0
  95. package/templates/default/postcss.config.mjs +8 -8
  96. package/templates/default/prisma/dev.db +0 -0
  97. package/templates/default/prisma/migrations/20251104113456_add_type_for_template_step_groups/migration.sql +16 -16
  98. package/templates/default/prisma/migrations/20251104170946_add_tags_to_test_suite_and_test_case/migration.sql +27 -27
  99. package/templates/default/prisma/migrations/20251112190024_add_cascade_delete_to_test_run_test_case/migration.sql +17 -17
  100. package/templates/default/prisma/migrations/20251113181100_add_test_run_log/migration.sql +12 -12
  101. package/templates/default/prisma/migrations/20251119191838_add_tag_type/migration.sql +28 -28
  102. package/templates/default/prisma/migrations/20251121164059_add_conflict_resolution/migration.sql +12 -12
  103. package/templates/default/prisma/migrations/20251223183400_add_report_model_to_db_schema/migration.sql +10 -10
  104. package/templates/default/prisma/migrations/20251223183637_add_report_test_case_entity_for_storing_test_results_for_individual_test_cases/migration.sql +10 -10
  105. package/templates/default/prisma/migrations/20251224083549_add_comprehensive_report_storage/migration.sql +108 -108
  106. package/templates/default/prisma/migrations/20251229194422_migrate_duration_to_string/migration.sql +55 -55
  107. package/templates/default/prisma/migrations/20251230124637_add_unique_constraint_to_test_run_name/migration.sql +27 -27
  108. package/templates/default/prisma/migrations/20260115094436_add_dashboard_metrics/migration.sql +59 -59
  109. package/templates/default/prisma/migrations/20260127172022_add_cascade_delete_to_step_parameters/migration.sql +34 -34
  110. package/templates/default/prisma/migrations/20260313093000_add_report_step_screenshot_path/migration.sql +1 -0
  111. package/templates/default/prisma/migrations/20260318120000_add_test_suite_context_to_test_run_test_case/migration.sql +20 -0
  112. package/templates/default/prisma/migrations/20260318173512_add_support_of_test_suite_level_runs/migration.sql +2 -0
  113. package/templates/default/prisma/schema.prisma +4 -0
  114. package/templates/default/scripts/install-playwright.ts +20 -0
  115. package/templates/default/scripts/protect-seeded-files.ts +38 -38
  116. package/templates/default/scripts/regenerate-features.ts +94 -94
  117. package/templates/default/scripts/setup-env.ts +19 -19
  118. package/templates/default/scripts/sync-all.ts +22 -66
  119. package/templates/default/scripts/sync-appraise-base-template.ts +57 -19
  120. package/templates/default/scripts/sync-environments.ts +40 -21
  121. package/templates/default/scripts/sync-locator-groups.ts +410 -410
  122. package/templates/default/scripts/sync-locators.ts +398 -398
  123. package/templates/default/scripts/sync-modules.ts +341 -341
  124. package/templates/default/scripts/sync-tags.ts +2 -3
  125. package/templates/default/scripts/sync-template-step-groups.ts +0 -3
  126. package/templates/default/scripts/sync-template-steps.ts +37 -11
  127. package/templates/default/scripts/sync-test-cases.ts +68 -16
  128. package/templates/default/scripts/sync-test-suites.ts +7 -5
  129. package/templates/default/src/actions/conflict/conflict.action.ts +33 -33
  130. package/templates/default/src/actions/dashboard/dashboard-actions.ts +240 -240
  131. package/templates/default/src/actions/environments/environment-actions.ts +188 -188
  132. package/templates/default/src/actions/locator/locator-actions.ts +490 -490
  133. package/templates/default/src/actions/locator-groups/locator-group-actions.ts +6 -3
  134. package/templates/default/src/actions/locator-picker/locator-picker-actions.ts +243 -0
  135. package/templates/default/src/actions/modules/module-actions.ts +135 -135
  136. package/templates/default/src/actions/reports/report-actions.ts +51 -49
  137. package/templates/default/src/actions/review/review-actions.ts +147 -147
  138. package/templates/default/src/actions/settings/sync-actions.ts +46 -0
  139. package/templates/default/src/actions/tags/tag-actions.ts +107 -107
  140. package/templates/default/src/actions/template-step/template-step-actions.ts +38 -4
  141. package/templates/default/src/actions/template-step-group/template-step-group-actions.ts +10 -3
  142. package/templates/default/src/actions/template-test-case/template-test-case-actions.ts +238 -238
  143. package/templates/default/src/actions/test-case/test-case-actions.ts +356 -356
  144. package/templates/default/src/actions/test-run/test-run-actions.ts +222 -129
  145. package/templates/default/src/actions/test-suite/test-suite-actions.ts +95 -17
  146. package/templates/default/src/actions/user/user-actions.ts +13 -13
  147. package/templates/default/src/app/(base)/environments/create/page.tsx +28 -28
  148. package/templates/default/src/app/(base)/environments/environment-form.tsx +219 -219
  149. package/templates/default/src/app/(base)/environments/environment-table-columns.tsx +96 -96
  150. package/templates/default/src/app/(base)/environments/environment-table.tsx +24 -24
  151. package/templates/default/src/app/(base)/environments/modify/[id]/page.tsx +46 -46
  152. package/templates/default/src/app/(base)/environments/page.tsx +59 -59
  153. package/templates/default/src/app/(base)/layout.tsx +10 -10
  154. package/templates/default/src/app/(base)/locator-groups/create/page.tsx +44 -44
  155. package/templates/default/src/app/(base)/locator-groups/locator-group-form.tsx +215 -215
  156. package/templates/default/src/app/(base)/locator-groups/locator-group-table-columns.tsx +77 -77
  157. package/templates/default/src/app/(base)/locator-groups/locator-group-table.tsx +28 -28
  158. package/templates/default/src/app/(base)/locator-groups/modify/[id]/page.tsx +46 -46
  159. package/templates/default/src/app/(base)/locator-groups/page.tsx +20 -8
  160. package/templates/default/src/app/(base)/locators/create/create-locator-workspace.tsx +542 -0
  161. package/templates/default/src/app/(base)/locators/create/page.tsx +32 -19
  162. package/templates/default/src/app/(base)/locators/locator-form.tsx +163 -163
  163. package/templates/default/src/app/(base)/locators/locator-table-columns.tsx +73 -73
  164. package/templates/default/src/app/(base)/locators/locator-table.tsx +28 -28
  165. package/templates/default/src/app/(base)/locators/modify/[id]/page.tsx +56 -27
  166. package/templates/default/src/app/(base)/locators/page.tsx +13 -13
  167. package/templates/default/src/app/(base)/locators/picker/page.tsx +7 -0
  168. package/templates/default/src/app/(base)/locators/sync-locators-button.tsx +66 -66
  169. package/templates/default/src/app/(base)/modules/create/page.tsx +34 -34
  170. package/templates/default/src/app/(base)/modules/modify/[id]/page.tsx +46 -46
  171. package/templates/default/src/app/(base)/modules/module-form.tsx +126 -126
  172. package/templates/default/src/app/(base)/modules/module-table-columns.tsx +85 -85
  173. package/templates/default/src/app/(base)/modules/module-table.tsx +24 -24
  174. package/templates/default/src/app/(base)/modules/page.tsx +59 -59
  175. package/templates/default/src/app/(base)/reports/[id]/page.tsx +40 -33
  176. package/templates/default/src/app/(base)/reports/duration-chart.tsx +33 -33
  177. package/templates/default/src/app/(base)/reports/feature-chart.tsx +78 -78
  178. package/templates/default/src/app/(base)/reports/overview-chart.tsx +49 -46
  179. package/templates/default/src/app/(base)/reports/page.tsx +98 -98
  180. package/templates/default/src/app/(base)/reports/report-metric-card.tsx +78 -16
  181. package/templates/default/src/app/(base)/reports/report-table-columns.tsx +189 -189
  182. package/templates/default/src/app/(base)/reports/report-table.tsx +72 -72
  183. package/templates/default/src/app/(base)/reports/report-view-table-columns.tsx +12 -1
  184. package/templates/default/src/app/(base)/reports/report-view-table.tsx +1 -0
  185. package/templates/default/src/app/(base)/reports/test-cases/page.tsx +40 -42
  186. package/templates/default/src/app/(base)/reports/test-cases/test-cases-metric-table-columns.tsx +115 -115
  187. package/templates/default/src/app/(base)/reports/test-cases/test-cases-metric-table.tsx +27 -27
  188. package/templates/default/src/app/(base)/reports/test-suites/page.tsx +42 -42
  189. package/templates/default/src/app/(base)/reports/test-suites/test-suites-metric-table-columns.tsx +79 -79
  190. package/templates/default/src/app/(base)/reports/test-suites/test-suites-metric-table.tsx +27 -27
  191. package/templates/default/src/app/(base)/reports/view-logs-button.tsx +58 -60
  192. package/templates/default/src/app/(base)/reviews/create/page.tsx +26 -26
  193. package/templates/default/src/app/(base)/reviews/created-reviews-table.tsx +15 -15
  194. package/templates/default/src/app/(base)/reviews/modify/[id]/page.tsx +26 -26
  195. package/templates/default/src/app/(base)/reviews/page.tsx +26 -26
  196. package/templates/default/src/app/(base)/reviews/review/[id]/page.tsx +26 -26
  197. package/templates/default/src/app/(base)/reviews/review-form.tsx +11 -11
  198. package/templates/default/src/app/(base)/reviews/review-table-by-creator-columns.tsx +9 -9
  199. package/templates/default/src/app/(base)/reviews/review-table-by-reviewer-columns.tsx +9 -9
  200. package/templates/default/src/app/(base)/reviews/reviewer-reviews-table.tsx +15 -15
  201. package/templates/default/src/app/(base)/settings/page.tsx +30 -0
  202. package/templates/default/src/app/(base)/settings/settings-sync-panel.tsx +261 -0
  203. package/templates/default/src/app/(base)/tags/create/page.tsx +39 -39
  204. package/templates/default/src/app/(base)/tags/modify/[id]/page.tsx +50 -50
  205. package/templates/default/src/app/(base)/tags/page.tsx +58 -58
  206. package/templates/default/src/app/(base)/tags/tag-form.tsx +147 -147
  207. package/templates/default/src/app/(base)/tags/tag-table-columns.tsx +63 -63
  208. package/templates/default/src/app/(base)/tags/tag-table.tsx +29 -29
  209. package/templates/default/src/app/(base)/template-step-groups/create/page.tsx +28 -28
  210. package/templates/default/src/app/(base)/template-step-groups/modify/[id]/page.tsx +45 -45
  211. package/templates/default/src/app/(base)/template-step-groups/page.tsx +60 -60
  212. package/templates/default/src/app/(base)/template-step-groups/template-step-group-form.tsx +167 -167
  213. package/templates/default/src/app/(base)/template-step-groups/template-step-group-table-columns.tsx +89 -89
  214. package/templates/default/src/app/(base)/template-step-groups/template-step-group-table.tsx +32 -32
  215. package/templates/default/src/app/(base)/template-steps/create/page.tsx +37 -37
  216. package/templates/default/src/app/(base)/template-steps/modify/[id]/page.tsx +49 -49
  217. package/templates/default/src/app/(base)/template-steps/page.tsx +58 -58
  218. package/templates/default/src/app/(base)/template-steps/paramChip.tsx +213 -213
  219. package/templates/default/src/app/(base)/template-steps/template-step-form.tsx +384 -384
  220. package/templates/default/src/app/(base)/template-steps/template-step-table-columns.tsx +158 -158
  221. package/templates/default/src/app/(base)/template-steps/template-step-table.tsx +24 -24
  222. package/templates/default/src/app/(base)/template-test-cases/create/page.tsx +56 -56
  223. package/templates/default/src/app/(base)/template-test-cases/modify/[id]/page.tsx +89 -89
  224. package/templates/default/src/app/(base)/template-test-cases/page.tsx +58 -58
  225. package/templates/default/src/app/(base)/template-test-cases/template-test-case-flow.tsx +84 -84
  226. package/templates/default/src/app/(base)/template-test-cases/template-test-case-form.tsx +262 -262
  227. package/templates/default/src/app/(base)/template-test-cases/template-test-case-table-columns.tsx +76 -76
  228. package/templates/default/src/app/(base)/template-test-cases/template-test-case-table.tsx +32 -32
  229. package/templates/default/src/app/(base)/test-cases/create/page.tsx +76 -76
  230. package/templates/default/src/app/(base)/test-cases/create-from-template/generate/[id]/page.tsx +96 -96
  231. package/templates/default/src/app/(base)/test-cases/create-from-template/page.tsx +38 -38
  232. package/templates/default/src/app/(base)/test-cases/create-from-template/template-selection-form.tsx +73 -73
  233. package/templates/default/src/app/(base)/test-cases/modify/[id]/page.tsx +106 -106
  234. package/templates/default/src/app/(base)/test-cases/page.tsx +2 -2
  235. package/templates/default/src/app/(base)/test-cases/test-case-flow.tsx +82 -82
  236. package/templates/default/src/app/(base)/test-cases/test-case-form.tsx +395 -395
  237. package/templates/default/src/app/(base)/test-cases/test-case-table-columns.tsx +6 -71
  238. package/templates/default/src/app/(base)/test-cases/test-case-table.tsx +2 -2
  239. package/templates/default/src/app/(base)/test-runs/[id]/page.tsx +4 -1
  240. package/templates/default/src/app/(base)/test-runs/create/page.tsx +11 -9
  241. package/templates/default/src/app/(base)/test-runs/page.tsx +60 -60
  242. package/templates/default/src/app/(base)/test-runs/test-run-form.tsx +37 -44
  243. package/templates/default/src/app/(base)/test-runs/test-run-table-columns.tsx +229 -229
  244. package/templates/default/src/app/(base)/test-runs/test-run-table.tsx +127 -127
  245. package/templates/default/src/app/(base)/test-suites/create/page.tsx +3 -2
  246. package/templates/default/src/app/(base)/test-suites/modify/[id]/page.tsx +2 -1
  247. package/templates/default/src/app/(base)/test-suites/page.tsx +57 -21
  248. package/templates/default/src/app/(base)/test-suites/test-suite-form.tsx +16 -17
  249. package/templates/default/src/app/(base)/test-suites/test-suite-table-columns.tsx +2 -1
  250. package/templates/default/src/app/(base)/test-suites/test-suite-table.tsx +29 -29
  251. package/templates/default/src/app/(dashboard-components)/app-drawer.tsx +187 -187
  252. package/templates/default/src/app/(dashboard-components)/data-card-grid.tsx +12 -12
  253. package/templates/default/src/app/(dashboard-components)/data-card.tsx +26 -26
  254. package/templates/default/src/app/(dashboard-components)/execution-health-panel.tsx +56 -56
  255. package/templates/default/src/app/(dashboard-components)/ongoing-test-runs-card.tsx +87 -87
  256. package/templates/default/src/app/(dashboard-components)/quick-actions-drawer.tsx +44 -44
  257. package/templates/default/src/app/api/reports/steps/[stepId]/screenshot/route.ts +52 -0
  258. package/templates/default/src/app/api/test-runs/[runId]/download/route.ts +97 -34
  259. package/templates/default/src/app/api/test-runs/[runId]/logs/route.ts +4 -1
  260. package/templates/default/src/app/api/test-runs/[runId]/trace/[testCaseId]/route.ts +146 -147
  261. package/templates/default/src/app/globals.css +147 -147
  262. package/templates/default/src/app/layout.tsx +4 -0
  263. package/templates/default/src/app/page.tsx +2 -0
  264. package/templates/default/src/assets/icons/empty-tube.tsx +23 -23
  265. package/templates/default/src/assets/icons/tube-plus.tsx +29 -29
  266. package/templates/default/src/components/base-node.tsx +21 -21
  267. package/templates/default/src/components/chart/pie-chart.tsx +73 -73
  268. package/templates/default/src/components/data-extraction/locator-inspector.tsx +460 -460
  269. package/templates/default/src/components/data-state/empty-state.tsx +40 -40
  270. package/templates/default/src/components/data-visualization/info-card.tsx +70 -70
  271. package/templates/default/src/components/data-visualization/info-grid.tsx +22 -22
  272. package/templates/default/src/components/diagram/button-edge.tsx +54 -54
  273. package/templates/default/src/components/diagram/dynamic-parameters.tsx +474 -474
  274. package/templates/default/src/components/diagram/edit-header-option.tsx +36 -36
  275. package/templates/default/src/components/diagram/flow-diagram.tsx +470 -470
  276. package/templates/default/src/components/diagram/node-form.tsx +262 -262
  277. package/templates/default/src/components/diagram/options-header-node.tsx +57 -57
  278. package/templates/default/src/components/diagram/template-step-combobox.tsx +155 -155
  279. package/templates/default/src/components/form/error-message.tsx +7 -7
  280. package/templates/default/src/components/kokonutui/smooth-tab.tsx +453 -453
  281. package/templates/default/src/components/loading-skeleton/data-table/data-table-skeleton.tsx +30 -30
  282. package/templates/default/src/components/loading-skeleton/form/button-skeleton.tsx +8 -8
  283. package/templates/default/src/components/loading-skeleton/form/icon-button-skeleton.tsx +8 -8
  284. package/templates/default/src/components/loading-skeleton/form/text-input-skeleton.tsx +8 -8
  285. package/templates/default/src/components/loading-skeleton/visualization/table-skeleton.tsx +14 -14
  286. package/templates/default/src/components/logo.tsx +15 -15
  287. package/templates/default/src/components/navigation/command-badge.tsx +34 -34
  288. package/templates/default/src/components/navigation/command-chain-input.tsx +51 -51
  289. package/templates/default/src/components/navigation/entity-search-command.tsx +116 -116
  290. package/templates/default/src/components/navigation/nav-card.tsx +31 -31
  291. package/templates/default/src/components/navigation/nav-command.tsx +13 -0
  292. package/templates/default/src/components/navigation/nav-link.tsx +60 -60
  293. package/templates/default/src/components/navigation/nav-menu-card-deck.tsx +112 -112
  294. package/templates/default/src/components/node-header.tsx +159 -159
  295. package/templates/default/src/components/reports/test-case-logs-modal.tsx +310 -253
  296. package/templates/default/src/components/table/table-actions.tsx +172 -172
  297. package/templates/default/src/components/test-case/test-case-columns.tsx +76 -0
  298. package/templates/default/src/components/test-case/test-case-picker.tsx +244 -0
  299. package/templates/default/src/components/test-run/download-logs-button.tsx +92 -99
  300. package/templates/default/src/components/test-run/log-viewer.tsx +1 -14
  301. package/templates/default/src/components/test-run/test-run-details.tsx +15 -2
  302. package/templates/default/src/components/test-run/test-run-header.tsx +149 -149
  303. package/templates/default/src/components/test-run/view-report-button.tsx +102 -102
  304. package/templates/default/src/components/test-suite/test-suite-picker.tsx +444 -0
  305. package/templates/default/src/components/theme/mode-toggle.tsx +54 -54
  306. package/templates/default/src/components/theme/theme-provider.tsx +8 -8
  307. package/templates/default/src/components/typography/page-header-subtitle.tsx +7 -7
  308. package/templates/default/src/components/typography/page-header.tsx +7 -7
  309. package/templates/default/src/components/ui/alert-dialog.tsx +106 -106
  310. package/templates/default/src/components/ui/alert.tsx +43 -43
  311. package/templates/default/src/components/ui/avatar.tsx +40 -40
  312. package/templates/default/src/components/ui/badge.tsx +29 -29
  313. package/templates/default/src/components/ui/button.tsx +47 -47
  314. package/templates/default/src/components/ui/calendar.tsx +158 -158
  315. package/templates/default/src/components/ui/card.tsx +43 -43
  316. package/templates/default/src/components/ui/checkbox.tsx +28 -28
  317. package/templates/default/src/components/ui/command.tsx +135 -135
  318. package/templates/default/src/components/ui/data-table-column-header.tsx +61 -61
  319. package/templates/default/src/components/ui/data-table-pagination.tsx +87 -87
  320. package/templates/default/src/components/ui/data-table-view-options.tsx +50 -50
  321. package/templates/default/src/components/ui/data-table.tsx +261 -261
  322. package/templates/default/src/components/ui/dialog.tsx +97 -97
  323. package/templates/default/src/components/ui/dropdown-menu.tsx +182 -182
  324. package/templates/default/src/components/ui/input.tsx +22 -22
  325. package/templates/default/src/components/ui/kbd.tsx +28 -28
  326. package/templates/default/src/components/ui/label.tsx +19 -19
  327. package/templates/default/src/components/ui/loading.tsx +12 -12
  328. package/templates/default/src/components/ui/multi-select-with-preview.tsx +116 -116
  329. package/templates/default/src/components/ui/multi-select.tsx +142 -142
  330. package/templates/default/src/components/ui/navigation-menu.tsx +120 -120
  331. package/templates/default/src/components/ui/popover.tsx +33 -33
  332. package/templates/default/src/components/ui/progress.tsx +25 -25
  333. package/templates/default/src/components/ui/radio-group.tsx +44 -44
  334. package/templates/default/src/components/ui/scroll-area.tsx +40 -40
  335. package/templates/default/src/components/ui/select.tsx +151 -151
  336. package/templates/default/src/components/ui/separator.tsx +22 -22
  337. package/templates/default/src/components/ui/skeleton.tsx +7 -7
  338. package/templates/default/src/components/ui/table.tsx +76 -76
  339. package/templates/default/src/components/ui/tabs.tsx +55 -55
  340. package/templates/default/src/components/ui/textarea.tsx +21 -21
  341. package/templates/default/src/components/ui/toast.tsx +113 -113
  342. package/templates/default/src/components/ui/toaster.tsx +26 -26
  343. package/templates/default/src/components/user-prompt/delete-prompt.tsx +87 -87
  344. package/templates/default/src/config/db-config.ts +10 -10
  345. package/templates/default/src/constants/form-opts/diagram/node-form.ts +30 -30
  346. package/templates/default/src/constants/form-opts/environment-form-opts.ts +24 -24
  347. package/templates/default/src/constants/form-opts/locator-form-opts.ts +20 -20
  348. package/templates/default/src/constants/form-opts/locator-group-form-opts.ts +28 -28
  349. package/templates/default/src/constants/form-opts/module-form-opts.ts +21 -21
  350. package/templates/default/src/constants/form-opts/review-form-opts.ts +23 -23
  351. package/templates/default/src/constants/form-opts/tag-form-opts.ts +42 -42
  352. package/templates/default/src/constants/form-opts/template-selection-form-opts.ts +16 -16
  353. package/templates/default/src/constants/form-opts/template-step-group-form-opts.ts +24 -24
  354. package/templates/default/src/constants/form-opts/template-test-case-form-opts.ts +39 -39
  355. package/templates/default/src/constants/form-opts/template-test-step-form-opts.ts +36 -36
  356. package/templates/default/src/constants/form-opts/test-case-form-opts.ts +43 -43
  357. package/templates/default/src/constants/form-opts/test-run-form-opts.ts +5 -3
  358. package/templates/default/src/constants/form-opts/test-suite-form-opts.ts +24 -24
  359. package/templates/default/src/hooks/use-toast.ts +187 -187
  360. package/templates/default/src/lib/automation/paths.ts +42 -12
  361. package/templates/default/src/lib/automation/projection-service.ts +2 -1
  362. package/templates/default/src/lib/bidirectional-sync.ts +432 -432
  363. package/templates/default/src/lib/database-sync.ts +2 -7
  364. package/templates/default/src/lib/environment-file-utils.ts +184 -184
  365. package/templates/default/src/lib/executor/local-executor-adapter.ts +24 -25
  366. package/templates/default/src/lib/executor/types.ts +2 -2
  367. package/templates/default/src/lib/feature-file-generator.ts +5 -66
  368. package/templates/default/src/lib/gherkin-parser.ts +259 -259
  369. package/templates/default/src/lib/locator-group-file-utils.ts +307 -307
  370. package/templates/default/src/lib/locator-picker/session-manager.ts +452 -0
  371. package/templates/default/src/lib/locator-picker/suggestions.ts +124 -0
  372. package/templates/default/src/lib/metrics/metric-calculator.ts +5 -0
  373. package/templates/default/src/lib/module-hierarchy-builder.ts +205 -205
  374. package/templates/default/src/lib/path-helpers/module-path.ts +71 -71
  375. package/templates/default/src/lib/process/task-spawner.ts +39 -2
  376. package/templates/default/src/lib/sync/projected-feature-utils.ts +113 -0
  377. package/templates/default/src/lib/sync/sync-executor.ts +130 -0
  378. package/templates/default/src/lib/sync/sync-pending-counts.test.ts +239 -0
  379. package/templates/default/src/lib/sync/sync-pending-counts.ts +1358 -0
  380. package/templates/default/src/lib/sync/sync-registry.ts +149 -0
  381. package/templates/default/src/lib/tag-utils.ts +28 -0
  382. package/templates/default/src/lib/template-sync-utils.d.ts +6 -6
  383. package/templates/default/src/lib/template-sync-utils.js +46 -46
  384. package/templates/default/src/lib/template-sync-utils.ts +63 -63
  385. package/templates/default/src/lib/test-case-utils.ts +6 -6
  386. package/templates/default/src/lib/test-run/log-formatter.ts +83 -83
  387. package/templates/default/src/lib/test-run/matching.ts +117 -0
  388. package/templates/default/src/lib/test-run/process-manager.ts +2 -0
  389. package/templates/default/src/lib/test-run/report-parser.ts +352 -316
  390. package/templates/default/src/lib/test-run/test-run-executor.ts +13 -15
  391. package/templates/default/src/lib/test-run/winston-logger.ts +64 -66
  392. package/templates/default/src/lib/test-suite-identifier-service.ts +76 -0
  393. package/templates/default/src/lib/test-suite-utils.ts +6 -0
  394. package/templates/default/src/lib/transformers/gherkin-converter.ts +42 -42
  395. package/templates/default/src/lib/transformers/key-to-icon-transformer.tsx +95 -95
  396. package/templates/default/src/lib/transformers/template-test-case-converter.ts +160 -160
  397. package/templates/default/src/lib/utils/node-param-validation.ts +81 -81
  398. package/templates/default/src/lib/utils/template-step-file-generator.ts +104 -104
  399. package/templates/default/src/lib/utils/template-step-file-manager-intelligent.ts +0 -8
  400. package/templates/default/src/lib/utils/template-step-file-manager.ts +166 -166
  401. package/templates/default/src/lib/utils.ts +31 -31
  402. package/templates/default/src/types/diagram/diagram.ts +34 -34
  403. package/templates/default/src/types/diagram/template-step.ts +11 -11
  404. package/templates/default/src/types/executor/browser.type.ts +1 -1
  405. package/templates/default/src/types/form/actionHandler.ts +6 -6
  406. package/templates/default/src/types/locator/locator.type.ts +11 -11
  407. package/templates/default/src/types/locator-picker.ts +65 -0
  408. package/templates/default/src/types/step/step.type.ts +1 -1
  409. package/templates/default/src/types/table/data-table.ts +6 -6
  410. package/templates/default/src/types/test-case-picker.ts +6 -0
  411. package/templates/default/src/types/test-suite-picker.ts +18 -0
  412. package/templates/default/tailwind.config.ts +62 -62
  413. package/templates/default/tsconfig.json +4 -1
  414. package/templates/default/appraisejs.config.json +0 -1
@@ -0,0 +1,452 @@
1
+ import prisma from '@/config/db-config'
2
+ import { normalizeRoute } from '@/lib/locator-picker/suggestions'
3
+ import { removeTask, spawnTask } from '@/lib/process/task-spawner'
4
+ import type {
5
+ LocatorPickerSession,
6
+ PickedLocatorPayload,
7
+ StartLocatorPickerSessionRequest,
8
+ } from '@/types/locator-picker'
9
+ import { resolveLocatorPickerCompanionInvocation } from '@locator-picker-companion/launcher'
10
+ import {
11
+ appendLocatorPickerCrashLog,
12
+ clearLocatorPickerCrashLogs,
13
+ createLocatorPickerCrashLog,
14
+ ensureLocatorPickerDirectories,
15
+ getLocatorPickerRuntimeEnv,
16
+ getLocatorPickerCrashLogPath,
17
+ removeLocatorPickerProfileDir,
18
+ removeLocatorPickerSessionFile,
19
+ getLocatorPickerSessionsDir,
20
+ getLocatorPickerSessionFilePath,
21
+ patchLocatorPickerSessionFile,
22
+ readLocatorPickerSessionFile,
23
+ writeLocatorPickerSessionFile,
24
+ } from '@locator-picker-companion/session-file'
25
+ import type { CompanionSessionFile } from '@locator-picker-companion/types'
26
+ import { randomUUID } from 'crypto'
27
+ import { execa } from 'execa'
28
+ import { readdir } from 'fs/promises'
29
+ import path from 'path'
30
+
31
+ const ACTIVE_SESSION_STALE_MS = 2 * 60 * 1000
32
+ const COMPANION_EXIT_POLL_MS = 100
33
+ const COMPANION_EXIT_TIMEOUT_MS = 5 * 1000
34
+ const TERMINAL_SESSION_RETENTION_MS = 30 * 60 * 1000
35
+
36
+ function safeUrlParts(url: string): { currentUrl: string; pathname: string } {
37
+ if (!url) {
38
+ return { currentUrl: '', pathname: '/' }
39
+ }
40
+
41
+ try {
42
+ const parsed = new URL(url)
43
+ return {
44
+ currentUrl: parsed.toString(),
45
+ pathname: parsed.pathname || '/',
46
+ }
47
+ } catch {
48
+ return {
49
+ currentUrl: url,
50
+ pathname: normalizeRoute(url),
51
+ }
52
+ }
53
+ }
54
+
55
+ function delay(ms: number): Promise<void> {
56
+ return new Promise(resolve => setTimeout(resolve, ms))
57
+ }
58
+
59
+ function isMissingProcessError(error: unknown): boolean {
60
+ return Boolean(error instanceof Error && 'code' in error && error.code === 'ESRCH')
61
+ }
62
+
63
+ async function processExists(pid: number): Promise<boolean> {
64
+ try {
65
+ process.kill(pid, 0)
66
+ return true
67
+ } catch (error) {
68
+ if (isMissingProcessError(error)) {
69
+ return false
70
+ }
71
+
72
+ return true
73
+ }
74
+ }
75
+
76
+ async function waitForProcessExit(pid: number, timeoutMs = COMPANION_EXIT_TIMEOUT_MS): Promise<boolean> {
77
+ const deadline = Date.now() + timeoutMs
78
+
79
+ while (Date.now() < deadline) {
80
+ if (!(await processExists(pid))) {
81
+ return true
82
+ }
83
+
84
+ await delay(COMPANION_EXIT_POLL_MS)
85
+ }
86
+
87
+ return !(await processExists(pid))
88
+ }
89
+
90
+ async function terminateProcessByPid(pid: number, force = false): Promise<void> {
91
+ if (process.platform === 'win32') {
92
+ const args = ['/PID', String(pid), '/T']
93
+ if (force) {
94
+ args.push('/F')
95
+ }
96
+
97
+ await execa('taskkill', args)
98
+ return
99
+ }
100
+
101
+ process.kill(pid, force ? 'SIGKILL' : 'SIGTERM')
102
+ }
103
+
104
+ async function shutdownCompanionProcess(pid: number): Promise<void> {
105
+ await terminateProcessByPid(pid).catch(() => undefined)
106
+
107
+ if (await waitForProcessExit(pid)) {
108
+ return
109
+ }
110
+
111
+ await terminateProcessByPid(pid, true).catch(() => undefined)
112
+ await waitForProcessExit(pid)
113
+ }
114
+
115
+ function getSessionAgeMs(updatedAt: string, now: number): number {
116
+ const updatedAtMs = Date.parse(updatedAt)
117
+ return Number.isFinite(updatedAtMs) ? Math.max(0, now - updatedAtMs) : Number.POSITIVE_INFINITY
118
+ }
119
+
120
+ function isTerminalStatus(status: CompanionSessionFile['status']): boolean {
121
+ return status === 'picked' || status === 'closed' || status === 'error'
122
+ }
123
+
124
+ async function cleanupLingeringCompanionSessions(currentSessionId?: string): Promise<void> {
125
+ await ensureLocatorPickerDirectories()
126
+
127
+ const sessionsDir = getLocatorPickerSessionsDir()
128
+ const entries = await readdir(sessionsDir).catch(() => [])
129
+ const now = Date.now()
130
+
131
+ for (const entry of entries) {
132
+ if (!entry.endsWith('.json')) {
133
+ continue
134
+ }
135
+
136
+ const sessionFilePath = path.join(sessionsDir, entry)
137
+ const session = await readLocatorPickerSessionFile(sessionFilePath)
138
+ if (!session || session.sessionId === currentSessionId) {
139
+ continue
140
+ }
141
+
142
+ const ageMs = getSessionAgeMs(session.updatedAt, now)
143
+ const isTerminal = isTerminalStatus(session.status)
144
+ const shouldTerminate = Boolean(session.companionPid)
145
+ const shouldCloseStaleSession = !session.companionPid && !isTerminal && ageMs > ACTIVE_SESSION_STALE_MS
146
+ const shouldPruneSessionFile =
147
+ (isTerminal && ageMs > TERMINAL_SESSION_RETENTION_MS) || shouldCloseStaleSession
148
+
149
+ if (shouldTerminate && session.companionPid) {
150
+ await shutdownCompanionProcess(session.companionPid).catch(() => undefined)
151
+ await patchLocatorPickerSessionFile(sessionFilePath, {
152
+ companionPid: null,
153
+ status: session.status === 'picked' ? 'picked' : 'closed',
154
+ }).catch(() => undefined)
155
+ }
156
+
157
+ if (shouldCloseStaleSession) {
158
+ await patchLocatorPickerSessionFile(sessionFilePath, {
159
+ companionPid: null,
160
+ status: 'closed',
161
+ }).catch(() => undefined)
162
+ }
163
+
164
+ if (shouldTerminate || isTerminal || shouldCloseStaleSession) {
165
+ await removeLocatorPickerProfileDir(session.sessionId).catch(() => undefined)
166
+ }
167
+
168
+ if (shouldPruneSessionFile) {
169
+ await removeLocatorPickerSessionFile(session.sessionId).catch(() => undefined)
170
+ }
171
+ }
172
+ }
173
+
174
+ function toPickedLocatorPayload(
175
+ sessionId: string,
176
+ payload?: CompanionSessionFile['pickedLocator'],
177
+ ): PickedLocatorPayload | undefined {
178
+ if (!payload) {
179
+ return undefined
180
+ }
181
+
182
+ return {
183
+ sessionId,
184
+ selector: payload.selector,
185
+ currentUrl: payload.currentUrl,
186
+ pathname: payload.pathname,
187
+ pageTitle: payload.pageTitle,
188
+ tagName: payload.tagName,
189
+ text: payload.text,
190
+ accessibleName: payload.accessibleName,
191
+ strategy: payload.strategy,
192
+ }
193
+ }
194
+
195
+ function toSessionSnapshot(record: CompanionSessionFile): LocatorPickerSession {
196
+ return {
197
+ sessionId: record.sessionId,
198
+ launchSource: record.launchSource,
199
+ browserName: 'chromium',
200
+ status: record.status,
201
+ currentUrl: record.currentUrl,
202
+ currentPathname: record.currentPathname,
203
+ pageTitle: record.pageTitle,
204
+ companionPid: record.companionPid,
205
+ crashLogPath: record.crashLogPath,
206
+ pickedLocator: toPickedLocatorPayload(record.sessionId, record.pickedLocator),
207
+ startedAt: record.startedAt,
208
+ updatedAt: record.updatedAt,
209
+ error: record.error,
210
+ }
211
+ }
212
+
213
+ async function markSessionLaunchFailure(
214
+ sessionFilePath: string,
215
+ message: string,
216
+ companionPid: number | null | undefined,
217
+ ): Promise<void> {
218
+ await patchLocatorPickerSessionFile(sessionFilePath, current => {
219
+ if (current.status === 'picked' || current.status === 'closed') {
220
+ return current
221
+ }
222
+
223
+ return {
224
+ status: 'error',
225
+ error: message,
226
+ companionPid: companionPid ?? current.companionPid,
227
+ }
228
+ })
229
+ }
230
+
231
+ class LocatorPickerSessionManager {
232
+ private constructor() {}
233
+
234
+ static getInstance(): LocatorPickerSessionManager {
235
+ const globalForLocatorPicker = global as unknown as {
236
+ locatorPickerSessionManager?: LocatorPickerSessionManager
237
+ }
238
+
239
+ if (!globalForLocatorPicker.locatorPickerSessionManager) {
240
+ globalForLocatorPicker.locatorPickerSessionManager = new LocatorPickerSessionManager()
241
+ }
242
+
243
+ return globalForLocatorPicker.locatorPickerSessionManager
244
+ }
245
+
246
+ async startSession(request: StartLocatorPickerSessionRequest): Promise<LocatorPickerSession> {
247
+ await clearLocatorPickerCrashLogs()
248
+ await cleanupLingeringCompanionSessions()
249
+
250
+ const environment = request.environmentId
251
+ ? await prisma.environment.findUnique({
252
+ where: {
253
+ id: request.environmentId,
254
+ },
255
+ })
256
+ : null
257
+
258
+ const launchUrl = request.url?.trim() || environment?.baseUrl || ''
259
+ if (!launchUrl) {
260
+ throw new Error('Choose an environment or provide a URL before launching Chromium.')
261
+ }
262
+
263
+ const normalizedUrl = /^https?:\/\//i.test(launchUrl) ? launchUrl : `https://${launchUrl}`
264
+ const sessionId = randomUUID()
265
+ const sessionFilePath = getLocatorPickerSessionFilePath(sessionId)
266
+ const crashLogPath = getLocatorPickerCrashLogPath(sessionId)
267
+ const { currentUrl, pathname } = safeUrlParts(normalizedUrl)
268
+
269
+ await ensureLocatorPickerDirectories()
270
+
271
+ const initialRecord: CompanionSessionFile = {
272
+ sessionId,
273
+ status: 'starting',
274
+ launchSource: {
275
+ environmentId: environment?.id,
276
+ environmentName: environment?.name,
277
+ url: normalizedUrl,
278
+ },
279
+ currentUrl,
280
+ currentPathname: pathname,
281
+ pageTitle: '',
282
+ companionPid: null,
283
+ crashLogPath,
284
+ startedAt: new Date().toISOString(),
285
+ updatedAt: new Date().toISOString(),
286
+ }
287
+
288
+ await writeLocatorPickerSessionFile(sessionFilePath, initialRecord)
289
+ await createLocatorPickerCrashLog(crashLogPath)
290
+ await appendLocatorPickerCrashLog(
291
+ crashLogPath,
292
+ `Session created for ${normalizedUrl}. Waiting for companion launch.`,
293
+ ).catch(() => undefined)
294
+
295
+ const companionEnv = getLocatorPickerRuntimeEnv(process.cwd(), process.env)
296
+
297
+ try {
298
+ const { command, args } = await resolveLocatorPickerCompanionInvocation(
299
+ ['--session-id', sessionId, '--session-file', sessionFilePath, '--target-url', normalizedUrl],
300
+ process.cwd(),
301
+ )
302
+
303
+ const spawnedProcess = await spawnTask(command, args, {
304
+ cwd: process.cwd(),
305
+ env: companionEnv,
306
+ streamLogs: false,
307
+ captureOutput: true,
308
+ logPrefix: `locator-picker-companion-${sessionId}`,
309
+ retainProcessRecord: false,
310
+ })
311
+
312
+ spawnedProcess.process.on('error', error => {
313
+ void markSessionLaunchFailure(
314
+ sessionFilePath,
315
+ error.message || 'Locator picker companion failed to start.',
316
+ spawnedProcess.pid,
317
+ )
318
+ void appendLocatorPickerCrashLog(
319
+ crashLogPath,
320
+ `Companion process error: ${error.message || 'Unknown process error.'}`,
321
+ ).catch(() => undefined)
322
+ removeTask(spawnedProcess.name)
323
+ })
324
+
325
+ spawnedProcess.process.on('exit', code => {
326
+ const stderr = spawnedProcess.output.stderr.join('').trim()
327
+ const stdout = spawnedProcess.output.stdout.join('').trim()
328
+ const logLines = [`Companion process exited with code ${code}.`]
329
+
330
+ if (stdout) {
331
+ logLines.push(`Captured stdout:\n${stdout}`)
332
+ }
333
+
334
+ if (stderr) {
335
+ logLines.push(`Captured stderr:\n${stderr}`)
336
+ }
337
+
338
+ void patchLocatorPickerSessionFile(sessionFilePath, current => {
339
+ const basePatch: Partial<CompanionSessionFile> = {
340
+ companionPid: null,
341
+ }
342
+
343
+ if (code === 0) {
344
+ if (current.status === 'picked') {
345
+ return basePatch
346
+ }
347
+
348
+ return {
349
+ ...basePatch,
350
+ status: 'closed',
351
+ }
352
+ }
353
+
354
+ const details = current.error?.trim() || stderr || stdout
355
+ const message = current.error?.trim()
356
+ ? current.error.trim()
357
+ : details
358
+ ? `Locator picker companion exited early: ${details}`
359
+ : `Locator picker companion exited early with code ${code}.`
360
+
361
+ if (current.status === 'picked' || current.status === 'closed') {
362
+ return basePatch
363
+ }
364
+
365
+ return {
366
+ ...basePatch,
367
+ status: 'error',
368
+ error: message,
369
+ }
370
+ })
371
+ .catch(() => undefined)
372
+ .finally(() => {
373
+ void appendLocatorPickerCrashLog(crashLogPath, logLines.join('\n\n')).catch(() => undefined)
374
+ removeTask(spawnedProcess.name)
375
+ return removeLocatorPickerProfileDir(sessionId).catch(() => undefined)
376
+ })
377
+ })
378
+
379
+ await patchLocatorPickerSessionFile(sessionFilePath, current => ({
380
+ companionPid: spawnedProcess.pid ?? current.companionPid,
381
+ error: undefined,
382
+ }))
383
+ } catch (error) {
384
+ await appendLocatorPickerCrashLog(
385
+ crashLogPath,
386
+ `Failed to spawn companion: ${error instanceof Error ? error.message : 'Unknown launch failure.'}`,
387
+ ).catch(() => undefined)
388
+ await removeLocatorPickerProfileDir(sessionId).catch(() => undefined)
389
+ await patchLocatorPickerSessionFile(sessionFilePath, {
390
+ status: 'error',
391
+ error: error instanceof Error ? error.message : 'Failed to start the locator picker companion.',
392
+ })
393
+ }
394
+
395
+ const record = await readLocatorPickerSessionFile(sessionFilePath)
396
+ if (!record) {
397
+ throw new Error('Locator picker session could not be initialized.')
398
+ }
399
+
400
+ return toSessionSnapshot(record)
401
+ }
402
+
403
+ async getSession(sessionId: string): Promise<LocatorPickerSession | null> {
404
+ const record = await readLocatorPickerSessionFile(getLocatorPickerSessionFilePath(sessionId))
405
+ return record ? toSessionSnapshot(record) : null
406
+ }
407
+
408
+ async markSaving(sessionId?: string): Promise<void> {
409
+ if (!sessionId) {
410
+ return
411
+ }
412
+
413
+ await patchLocatorPickerSessionFile(getLocatorPickerSessionFilePath(sessionId), {
414
+ status: 'saving',
415
+ error: undefined,
416
+ })
417
+ }
418
+
419
+ async markReadyAfterSave(sessionId?: string): Promise<void> {
420
+ if (!sessionId) {
421
+ return
422
+ }
423
+
424
+ await patchLocatorPickerSessionFile(getLocatorPickerSessionFilePath(sessionId), current => ({
425
+ status: current.pickedLocator ? 'picked' : current.companionPid ? 'ready' : 'closed',
426
+ error: undefined,
427
+ }))
428
+ }
429
+
430
+ async closeSession(sessionId: string): Promise<LocatorPickerSession | null> {
431
+ const sessionFilePath = getLocatorPickerSessionFilePath(sessionId)
432
+ const current = await readLocatorPickerSessionFile(sessionFilePath)
433
+ if (!current) {
434
+ return null
435
+ }
436
+
437
+ if (current.companionPid) {
438
+ await shutdownCompanionProcess(current.companionPid).catch(() => undefined)
439
+ }
440
+
441
+ const nextRecord = await patchLocatorPickerSessionFile(sessionFilePath, {
442
+ status: 'closed',
443
+ companionPid: null,
444
+ })
445
+
446
+ await removeLocatorPickerProfileDir(sessionId).catch(() => undefined)
447
+
448
+ return nextRecord ? toSessionSnapshot(nextRecord) : null
449
+ }
450
+ }
451
+
452
+ export const locatorPickerSessionManager = LocatorPickerSessionManager.getInstance()
@@ -0,0 +1,124 @@
1
+ import type { LocatorPickerGroupSuggestion, PickedLocatorPayload } from '@/types/locator-picker'
2
+ import type { LocatorGroup, Module } from '@prisma/client'
3
+
4
+ function normalizeText(value: string | null | undefined): string {
5
+ return (value ?? '').replace(/\s+/g, ' ').trim()
6
+ }
7
+
8
+ function humanizeSegment(value: string): string {
9
+ return value
10
+ .replace(/[-_]+/g, ' ')
11
+ .replace(/\s+/g, ' ')
12
+ .trim()
13
+ .replace(/\b\w/g, match => match.toUpperCase())
14
+ }
15
+
16
+ export function normalizeRoute(value: string | null | undefined): string {
17
+ if (!value || value.trim() === '') {
18
+ return '/'
19
+ }
20
+
21
+ try {
22
+ const parsed = new URL(value)
23
+ return parsed.pathname || '/'
24
+ } catch {
25
+ const route = value.trim()
26
+ if (route === '') {
27
+ return '/'
28
+ }
29
+
30
+ return route.startsWith('/') ? route : `/${route}`
31
+ }
32
+ }
33
+
34
+ function buildModulePathMap(modules: Module[]): Map<string, string> {
35
+ const moduleById = new Map(modules.map(module => [module.id, module]))
36
+ const pathByModuleId = new Map<string, string>()
37
+
38
+ const buildPath = (module: Module): string => {
39
+ const cached = pathByModuleId.get(module.id)
40
+ if (cached) {
41
+ return cached
42
+ }
43
+
44
+ const parent = module.parentId ? moduleById.get(module.parentId) : null
45
+ const pathValue = parent ? `${buildPath(parent)}/${module.name}` : `/${module.name}`
46
+
47
+ pathByModuleId.set(module.id, pathValue.replace(/\/{2,}/g, '/'))
48
+ return pathByModuleId.get(module.id)!
49
+ }
50
+
51
+ for (const module of modules) {
52
+ buildPath(module)
53
+ }
54
+
55
+ return pathByModuleId
56
+ }
57
+
58
+ export function suggestLocatorName(payload?: PickedLocatorPayload): string {
59
+ if (!payload) {
60
+ return ''
61
+ }
62
+
63
+ const candidates = [payload.accessibleName, payload.text]
64
+
65
+ for (const candidate of candidates) {
66
+ const normalized = normalizeText(candidate)
67
+ if (normalized) {
68
+ return normalized.slice(0, 80)
69
+ }
70
+ }
71
+
72
+ return humanizeSegment(payload.tagName || 'element')
73
+ }
74
+
75
+ export function inferGroupSuggestion(
76
+ routeValue: string,
77
+ pageTitle: string,
78
+ locatorGroups: LocatorGroup[],
79
+ modules: Module[],
80
+ ): LocatorPickerGroupSuggestion {
81
+ const route = normalizeRoute(routeValue)
82
+ const exactMatch = locatorGroups.find(group => normalizeRoute(group.route) === route)
83
+
84
+ if (exactMatch) {
85
+ return {
86
+ mode: 'existing',
87
+ route,
88
+ existingLocatorGroupId: exactMatch.id,
89
+ existingLocatorGroupName: exactMatch.name,
90
+ suggestedGroupName: exactMatch.name,
91
+ suggestedModuleId: exactMatch.moduleId,
92
+ requiresModuleSelection: false,
93
+ }
94
+ }
95
+
96
+ const pathSegments = route.split('/').filter(Boolean)
97
+ const baseName =
98
+ normalizeText(pageTitle) ||
99
+ (pathSegments.length > 0 ? humanizeSegment(pathSegments[pathSegments.length - 1]) : 'Home')
100
+
101
+ const existingNames = new Set(locatorGroups.map(group => group.name.toLowerCase()))
102
+ let suggestedGroupName = baseName
103
+ let suffix = 2
104
+
105
+ while (existingNames.has(suggestedGroupName.toLowerCase())) {
106
+ suggestedGroupName = `${baseName} ${suffix}`
107
+ suffix += 1
108
+ }
109
+
110
+ const modulePathMap = buildModulePathMap(modules)
111
+ const matchedModule = Array.from(modulePathMap.entries())
112
+ .map(([moduleId, modulePath]) => ({ moduleId, modulePath }))
113
+ .sort((left, right) => right.modulePath.length - left.modulePath.length)
114
+ .find(({ modulePath }) => route === modulePath || route.startsWith(`${modulePath}/`))
115
+
116
+ return {
117
+ mode: 'create',
118
+ route,
119
+ suggestedGroupName,
120
+ suggestedModuleId: matchedModule?.moduleId,
121
+ suggestedModulePath: matchedModule?.modulePath,
122
+ requiresModuleSelection: !matchedModule?.moduleId,
123
+ }
124
+ }
@@ -190,6 +190,11 @@ export async function updateTestSuitesForTestRun(
190
190
  // Extract unique test suite IDs
191
191
  const testSuiteIds = new Set<string>()
192
192
  testRunTestCases.forEach(trtc => {
193
+ if (trtc.testSuiteId) {
194
+ testSuiteIds.add(trtc.testSuiteId)
195
+ return
196
+ }
197
+
193
198
  if (trtc.testCase && trtc.testCase.TestSuite) {
194
199
  trtc.testCase.TestSuite.forEach(suite => {
195
200
  testSuiteIds.add(suite.id)