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
@@ -1,46 +1,46 @@
1
- import { getLocatorGroupByIdAction } from '@/actions/locator-groups/locator-group-actions'
2
- import { LocatorGroup, Module } from '@prisma/client'
3
- import React from 'react'
4
- import LocatorGroupForm from '../../locator-group-form'
5
- import { updateLocatorGroupAction } from '@/actions/locator-groups/locator-group-actions'
6
- import { getAllModulesAction } from '@/actions/modules/module-actions'
7
- import { Metadata } from 'next'
8
-
9
- export const metadata: Metadata = {
10
- title: 'Appraise | Modify Locator Group',
11
- description: 'Update locator group configuration',
12
- }
13
-
14
- const ModifyLocator = async ({ params }: { params: Promise<{ id: string }> }) => {
15
- const { id } = await params
16
- const { data, error } = await getLocatorGroupByIdAction(id)
17
-
18
- if (error) {
19
- return <div>Error: {error}</div>
20
- }
21
-
22
- const locatorGroup = data as LocatorGroup
23
-
24
- const { data: moduleList, error: moduleListError } = await getAllModulesAction()
25
-
26
- if (moduleListError) {
27
- return <div>Error: {moduleListError}</div>
28
- }
29
-
30
- return (
31
- <LocatorGroupForm
32
- defaultValues={{
33
- name: locatorGroup.name ?? '',
34
- moduleId: locatorGroup.moduleId ?? '',
35
- route: locatorGroup.route ?? '',
36
- }}
37
- successTitle="Locator Group updated"
38
- successMessage="Locator Group updated successfully"
39
- onSubmitAction={updateLocatorGroupAction}
40
- id={id}
41
- moduleList={moduleList as Module[]}
42
- />
43
- )
44
- }
45
-
46
- export default ModifyLocator
1
+ import { getLocatorGroupByIdAction } from '@/actions/locator-groups/locator-group-actions'
2
+ import { LocatorGroup, Module } from '@prisma/client'
3
+ import React from 'react'
4
+ import LocatorGroupForm from '../../locator-group-form'
5
+ import { updateLocatorGroupAction } from '@/actions/locator-groups/locator-group-actions'
6
+ import { getAllModulesAction } from '@/actions/modules/module-actions'
7
+ import { Metadata } from 'next'
8
+
9
+ export const metadata: Metadata = {
10
+ title: 'Appraise | Modify Locator Group',
11
+ description: 'Update locator group configuration',
12
+ }
13
+
14
+ const ModifyLocator = async ({ params }: { params: Promise<{ id: string }> }) => {
15
+ const { id } = await params
16
+ const { data, error } = await getLocatorGroupByIdAction(id)
17
+
18
+ if (error) {
19
+ return <div>Error: {error}</div>
20
+ }
21
+
22
+ const locatorGroup = data as LocatorGroup
23
+
24
+ const { data: moduleList, error: moduleListError } = await getAllModulesAction()
25
+
26
+ if (moduleListError) {
27
+ return <div>Error: {moduleListError}</div>
28
+ }
29
+
30
+ return (
31
+ <LocatorGroupForm
32
+ defaultValues={{
33
+ name: locatorGroup.name ?? '',
34
+ moduleId: locatorGroup.moduleId ?? '',
35
+ route: locatorGroup.route ?? '',
36
+ }}
37
+ successTitle="Locator Group updated"
38
+ successMessage="Locator Group updated successfully"
39
+ onSubmitAction={updateLocatorGroupAction}
40
+ id={id}
41
+ moduleList={moduleList as Module[]}
42
+ />
43
+ )
44
+ }
45
+
46
+ export default ModifyLocator
@@ -1,7 +1,7 @@
1
1
  import PageHeader from '@/components/typography/page-header'
2
2
  import HeaderSubtitle from '@/components/typography/page-header-subtitle'
3
3
  import React from 'react'
4
- import { Group } from 'lucide-react'
4
+ import { Group, Plus } from 'lucide-react'
5
5
  import LocatorGroupTable from './locator-group-table'
6
6
  import { Suspense } from 'react'
7
7
  import DataTableSkeleton from '@/components/loading-skeleton/data-table/data-table-skeleton'
@@ -9,6 +9,8 @@ import { getAllLocatorGroupsAction } from '@/actions/locator-groups/locator-grou
9
9
  import EmptyState from '@/components/data-state/empty-state'
10
10
  import { LocatorGroup } from '@prisma/client'
11
11
  import { Metadata } from 'next'
12
+ import { Button } from '@/components/ui/button'
13
+ import Link from 'next/link'
12
14
 
13
15
  export const metadata: Metadata = {
14
16
  title: 'Appraise | Locator Groups',
@@ -41,13 +43,23 @@ const LocatorGroups = async () => {
41
43
  return (
42
44
  <>
43
45
  <div className="mb-8">
44
- <PageHeader>
45
- <span className="flex items-center">
46
- <Group className="mr-2 h-8 w-8" />
47
- Locator Groups
48
- </span>
49
- </PageHeader>
50
- <HeaderSubtitle>Organize locators for better maintainability and reusability</HeaderSubtitle>
46
+ <div className="flex items-center justify-between">
47
+ <div>
48
+ <PageHeader>
49
+ <span className="flex items-center">
50
+ <Group className="mr-2 h-8 w-8" />
51
+ Locator Groups
52
+ </span>
53
+ </PageHeader>
54
+ <HeaderSubtitle>Organize locators for better maintainability and reusability</HeaderSubtitle>
55
+ </div>
56
+ <Button variant="outline" asChild>
57
+ <Link href="/locators/create">
58
+ <Plus className="mr-2 h-4 w-4" />
59
+ Create Locator
60
+ </Link>
61
+ </Button>
62
+ </div>
51
63
  </div>
52
64
  <Suspense fallback={<DataTableSkeleton />}>
53
65
  <LocatorGroupTable />
@@ -0,0 +1,542 @@
1
+ 'use client'
2
+
3
+ import {
4
+ getLocatorPickerSessionAction,
5
+ savePickedLocatorAction,
6
+ startLocatorPickerSessionAction,
7
+ } from '@/actions/locator-picker/locator-picker-actions'
8
+ import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
9
+ import { Badge } from '@/components/ui/badge'
10
+ import { Button } from '@/components/ui/button'
11
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
12
+ import { Input } from '@/components/ui/input'
13
+ import { Label } from '@/components/ui/label'
14
+ import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
15
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
16
+ import { Separator } from '@/components/ui/separator'
17
+ import { Textarea } from '@/components/ui/textarea'
18
+ import { toast } from '@/hooks/use-toast'
19
+ import { inferGroupSuggestion, normalizeRoute, suggestLocatorName } from '@/lib/locator-picker/suggestions'
20
+ import type { LocatorPickerSession } from '@/types/locator-picker'
21
+ import type { Environment, LocatorGroup, Module } from '@prisma/client'
22
+ import { ExternalLink, Loader2, Save, Target } from 'lucide-react'
23
+ import { useRouter } from 'next/navigation'
24
+ import { useEffect, useRef, useState } from 'react'
25
+
26
+ interface CreateLocatorWorkspaceProps {
27
+ environments: Environment[]
28
+ locatorGroups: LocatorGroup[]
29
+ modules: Module[]
30
+ mode?: 'create' | 'modify'
31
+ locatorId?: string
32
+ initialValues?: {
33
+ locatorName?: string
34
+ selector?: string
35
+ resolutionMode?: 'existing' | 'create'
36
+ existingLocatorGroupId?: string
37
+ newLocatorGroupName?: string
38
+ route?: string
39
+ moduleId?: string
40
+ }
41
+ }
42
+
43
+ function statusTone(status: LocatorPickerSession['status']) {
44
+ switch (status) {
45
+ case 'picked':
46
+ return 'default'
47
+ case 'saving':
48
+ return 'secondary'
49
+ case 'closed':
50
+ return 'outline'
51
+ case 'error':
52
+ return 'destructive'
53
+ case 'ready':
54
+ return 'secondary'
55
+ case 'starting':
56
+ default:
57
+ return 'secondary'
58
+ }
59
+ }
60
+
61
+ function formatStatus(status: LocatorPickerSession['status']) {
62
+ if (status === 'picked') {
63
+ return 'Picked'
64
+ }
65
+
66
+ return status.charAt(0).toUpperCase() + status.slice(1)
67
+ }
68
+
69
+ export default function CreateLocatorWorkspace({
70
+ environments,
71
+ locatorGroups,
72
+ modules,
73
+ mode = 'create',
74
+ locatorId,
75
+ initialValues,
76
+ }: CreateLocatorWorkspaceProps) {
77
+ const router = useRouter()
78
+ const isModifyMode = mode === 'modify'
79
+ const [sourceType, setSourceType] = useState<'environment' | 'url'>(environments.length > 0 ? 'environment' : 'url')
80
+ const [environmentId, setEnvironmentId] = useState(environments[0]?.id ?? '')
81
+ const [url, setUrl] = useState('')
82
+ const [session, setSession] = useState<LocatorPickerSession | null>(null)
83
+ const [isStarting, setIsStarting] = useState(false)
84
+ const [isSaving, setIsSaving] = useState(false)
85
+ const payloadSignatureRef = useRef('')
86
+
87
+ const [locatorName, setLocatorName] = useState(initialValues?.locatorName ?? '')
88
+ const [selector, setSelector] = useState(initialValues?.selector ?? '')
89
+ const [resolutionMode, setResolutionMode] = useState<'existing' | 'create'>(initialValues?.resolutionMode ?? 'existing')
90
+ const [existingLocatorGroupId, setExistingLocatorGroupId] = useState(initialValues?.existingLocatorGroupId ?? '')
91
+ const [newLocatorGroupName, setNewLocatorGroupName] = useState(initialValues?.newLocatorGroupName ?? '')
92
+ const [route, setRoute] = useState(normalizeRoute(initialValues?.route ?? '/'))
93
+ const [moduleId, setModuleId] = useState(initialValues?.moduleId ?? '')
94
+ const [lastAutoLocatorName, setLastAutoLocatorName] = useState(initialValues?.locatorName ?? '')
95
+ const [lastAutoSelector, setLastAutoSelector] = useState(initialValues?.selector ?? '')
96
+ const [lastAutoExistingGroupId, setLastAutoExistingGroupId] = useState(initialValues?.existingLocatorGroupId ?? '')
97
+ const [lastAutoGroupName, setLastAutoGroupName] = useState(initialValues?.newLocatorGroupName ?? '')
98
+ const [lastAutoRoute, setLastAutoRoute] = useState(normalizeRoute(initialValues?.route ?? '/'))
99
+ const [lastAutoModuleId, setLastAutoModuleId] = useState(initialValues?.moduleId ?? '')
100
+
101
+ const loadSession = async (sessionId: string, silent = false) => {
102
+ const response = await getLocatorPickerSessionAction(sessionId)
103
+ if (response.status === 200) {
104
+ setSession(response.data as LocatorPickerSession)
105
+ } else if (!silent) {
106
+ toast({
107
+ title: 'Unable to refresh the picker session',
108
+ description: response.error,
109
+ variant: 'destructive',
110
+ })
111
+ }
112
+ }
113
+
114
+ useEffect(() => {
115
+ if (!session?.sessionId || session.status === 'closed' || !session.companionPid) {
116
+ return
117
+ }
118
+
119
+ const intervalId = window.setInterval(() => {
120
+ void loadSession(session.sessionId, true)
121
+ }, 1500)
122
+
123
+ return () => window.clearInterval(intervalId)
124
+ }, [session?.companionPid, session?.sessionId, session?.status])
125
+
126
+ useEffect(() => {
127
+ const pickedLocator = session?.pickedLocator
128
+ if (!pickedLocator) {
129
+ return
130
+ }
131
+
132
+ const nextPayloadSignature = `${session?.updatedAt}:${pickedLocator.currentUrl}:${pickedLocator.selector}`
133
+ if (nextPayloadSignature === payloadSignatureRef.current) {
134
+ return
135
+ }
136
+
137
+ payloadSignatureRef.current = nextPayloadSignature
138
+ const timeoutId = window.setTimeout(() => {
139
+ if (selector === '' || selector === lastAutoSelector) {
140
+ setSelector(pickedLocator.selector)
141
+ setLastAutoSelector(pickedLocator.selector)
142
+ }
143
+
144
+ const suggestedName = suggestLocatorName(pickedLocator)
145
+ if (suggestedName && (locatorName === '' || locatorName === lastAutoLocatorName)) {
146
+ setLocatorName(suggestedName)
147
+ setLastAutoLocatorName(suggestedName)
148
+ }
149
+
150
+ const suggestion = inferGroupSuggestion(pickedLocator.pathname, pickedLocator.pageTitle, locatorGroups, modules)
151
+
152
+ if (route === '/' || route === '' || route === lastAutoRoute) {
153
+ setRoute(suggestion.route)
154
+ setLastAutoRoute(suggestion.route)
155
+ }
156
+
157
+ if (suggestion.mode === 'existing') {
158
+ setResolutionMode(currentMode => (currentMode === 'create' ? 'existing' : currentMode))
159
+
160
+ const nextExistingGroupId = suggestion.existingLocatorGroupId ?? ''
161
+ if (existingLocatorGroupId === '' || existingLocatorGroupId === lastAutoExistingGroupId) {
162
+ setExistingLocatorGroupId(nextExistingGroupId)
163
+ setLastAutoExistingGroupId(nextExistingGroupId)
164
+ }
165
+ } else {
166
+ if (
167
+ resolutionMode === 'create' ||
168
+ existingLocatorGroupId === '' ||
169
+ existingLocatorGroupId === lastAutoExistingGroupId
170
+ ) {
171
+ setResolutionMode('create')
172
+ }
173
+
174
+ if (newLocatorGroupName === '' || newLocatorGroupName === lastAutoGroupName) {
175
+ setNewLocatorGroupName(suggestion.suggestedGroupName)
176
+ setLastAutoGroupName(suggestion.suggestedGroupName)
177
+ }
178
+
179
+ if (moduleId === '' || moduleId === lastAutoModuleId) {
180
+ const nextModuleId = suggestion.suggestedModuleId ?? ''
181
+ setModuleId(nextModuleId)
182
+ setLastAutoModuleId(nextModuleId)
183
+ }
184
+ }
185
+ }, 0)
186
+
187
+ return () => window.clearTimeout(timeoutId)
188
+ }, [
189
+ existingLocatorGroupId,
190
+ lastAutoExistingGroupId,
191
+ lastAutoGroupName,
192
+ lastAutoLocatorName,
193
+ lastAutoModuleId,
194
+ lastAutoRoute,
195
+ lastAutoSelector,
196
+ locatorGroups,
197
+ locatorName,
198
+ moduleId,
199
+ modules,
200
+ newLocatorGroupName,
201
+ resolutionMode,
202
+ route,
203
+ selector,
204
+ session,
205
+ ])
206
+
207
+ const handleStart = async () => {
208
+ setIsStarting(true)
209
+ const response = await startLocatorPickerSessionAction({
210
+ environmentId: sourceType === 'environment' ? environmentId : undefined,
211
+ url: sourceType === 'url' ? url : undefined,
212
+ })
213
+ setIsStarting(false)
214
+
215
+ if (response.status !== 200 || !response.data) {
216
+ toast({
217
+ title: 'Unable to launch Chromium',
218
+ description: response.error,
219
+ variant: 'destructive',
220
+ })
221
+ return
222
+ }
223
+
224
+ setSession(response.data as LocatorPickerSession)
225
+ payloadSignatureRef.current = ''
226
+ setLastAutoLocatorName(locatorName)
227
+ setLastAutoSelector(selector)
228
+ setLastAutoExistingGroupId(existingLocatorGroupId)
229
+ setLastAutoGroupName(newLocatorGroupName)
230
+ setLastAutoRoute(route)
231
+ setLastAutoModuleId(moduleId)
232
+
233
+ toast({
234
+ title: 'Chromium launched',
235
+ description: 'Use the in-browser Appraise picker panel to start picking, click one element, then confirm Use selector.',
236
+ })
237
+ }
238
+
239
+ const handleSave = async () => {
240
+ setIsSaving(true)
241
+
242
+ const response = await savePickedLocatorAction({
243
+ locatorId,
244
+ sessionId: session?.sessionId,
245
+ locatorName,
246
+ selector,
247
+ resolutionMode,
248
+ existingLocatorGroupId: resolutionMode === 'existing' ? existingLocatorGroupId : undefined,
249
+ newLocatorGroupName: resolutionMode === 'create' ? newLocatorGroupName : undefined,
250
+ route: resolutionMode === 'create' ? route : undefined,
251
+ moduleId: resolutionMode === 'create' ? moduleId : undefined,
252
+ })
253
+
254
+ setIsSaving(false)
255
+
256
+ if (response.status === 200) {
257
+ toast({
258
+ title: isModifyMode ? 'Locator updated' : 'Locator saved',
259
+ description: response.message,
260
+ })
261
+ router.push('/locators')
262
+ router.refresh()
263
+ return
264
+ }
265
+
266
+ toast({
267
+ title: 'Unable to save locator',
268
+ description: response.error,
269
+ variant: 'destructive',
270
+ })
271
+ }
272
+
273
+ const canSave =
274
+ locatorName.trim() !== '' &&
275
+ selector.trim() !== '' &&
276
+ ((resolutionMode === 'existing' && existingLocatorGroupId !== '') ||
277
+ (resolutionMode === 'create' && newLocatorGroupName.trim() !== '' && moduleId !== ''))
278
+
279
+ return (
280
+ <div className="space-y-6">
281
+ <div className="grid gap-6 xl:grid-cols-[minmax(0,24rem)_minmax(0,1fr)]">
282
+ <Card>
283
+ <CardHeader>
284
+ <CardTitle>Launch Chromium Picker</CardTitle>
285
+ <CardDescription>
286
+ Start a Chromium window from an environment or direct URL. The companion injects its own floating picker
287
+ panel into that browser, so no extension is required. Use this to repick the selector while editing or to
288
+ capture a new one from scratch.
289
+ </CardDescription>
290
+ </CardHeader>
291
+ <CardContent className="space-y-5">
292
+ <RadioGroup
293
+ value={sourceType}
294
+ onValueChange={value => setSourceType(value as 'environment' | 'url')}
295
+ className="grid gap-3"
296
+ >
297
+ <label className="flex items-center gap-3 rounded-lg border p-3">
298
+ <RadioGroupItem value="environment" id="source-environment" disabled={environments.length === 0} />
299
+ <div>
300
+ <div className="font-medium">Saved environment</div>
301
+ <div className="text-sm text-muted-foreground">Launch from an existing base URL.</div>
302
+ </div>
303
+ </label>
304
+ <label className="flex items-center gap-3 rounded-lg border p-3">
305
+ <RadioGroupItem value="url" id="source-url" />
306
+ <div>
307
+ <div className="font-medium">Direct URL</div>
308
+ <div className="text-sm text-muted-foreground">Open any reachable page directly in Chromium.</div>
309
+ </div>
310
+ </label>
311
+ </RadioGroup>
312
+
313
+ {sourceType === 'environment' ? (
314
+ <div className="space-y-2">
315
+ <Label>Environment</Label>
316
+ <Select value={environmentId} onValueChange={setEnvironmentId}>
317
+ <SelectTrigger>
318
+ <SelectValue placeholder="Select an environment" />
319
+ </SelectTrigger>
320
+ <SelectContent isEmpty={environments.length === 0}>
321
+ {environments.map(environment => (
322
+ <SelectItem key={environment.id} value={environment.id}>
323
+ {environment.name}
324
+ </SelectItem>
325
+ ))}
326
+ </SelectContent>
327
+ </Select>
328
+ </div>
329
+ ) : (
330
+ <div className="space-y-2">
331
+ <Label htmlFor="picker-url">URL</Label>
332
+ <Input
333
+ id="picker-url"
334
+ value={url}
335
+ onChange={event => setUrl(event.target.value)}
336
+ placeholder="https://example.com/login"
337
+ />
338
+ </div>
339
+ )}
340
+
341
+ <Button
342
+ type="button"
343
+ onClick={handleStart}
344
+ disabled={isStarting || (sourceType === 'environment' ? environmentId === '' : url.trim() === '')}
345
+ className="w-full"
346
+ >
347
+ {isStarting ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <Target className="mr-2 h-4 w-4" />}
348
+ Launch Chromium
349
+ </Button>
350
+
351
+ <Alert>
352
+ <AlertTitle>Inside Chromium</AlertTitle>
353
+ <AlertDescription>
354
+ Use the Appraise picker panel in the page itself: start picking, hover to inspect, click one element,
355
+ then confirm Use selector. The final selector returns here automatically.
356
+ </AlertDescription>
357
+ </Alert>
358
+
359
+ {session ? (
360
+ <>
361
+ <Separator />
362
+ <div className="space-y-4">
363
+ <div className="flex items-center justify-between gap-3">
364
+ <div>
365
+ <div className="text-sm font-medium">Session</div>
366
+ <div className="text-xs text-muted-foreground">
367
+ {session.launchSource.environmentName ?? session.launchSource.url}
368
+ </div>
369
+ {session.companionPid ? (
370
+ <div className="text-[11px] text-muted-foreground">PID {session.companionPid}</div>
371
+ ) : null}
372
+ </div>
373
+ <Badge variant={statusTone(session.status)}>{formatStatus(session.status)}</Badge>
374
+ </div>
375
+
376
+ <div className="space-y-2 text-sm">
377
+ <div className="font-medium">Current page</div>
378
+ <div className="break-all text-muted-foreground">{session.currentUrl || session.launchSource.url}</div>
379
+ <div className="text-muted-foreground">{session.pageTitle || 'Waiting for page metadata'}</div>
380
+ </div>
381
+
382
+ {session.error ? (
383
+ <Alert variant="destructive">
384
+ <AlertTitle>Picker error</AlertTitle>
385
+ <AlertDescription>{session.error}</AlertDescription>
386
+ </Alert>
387
+ ) : null}
388
+ </div>
389
+ </>
390
+ ) : null}
391
+ </CardContent>
392
+ </Card>
393
+
394
+ <Card>
395
+ <CardHeader>
396
+ <CardTitle>{isModifyMode ? 'Update Locator' : 'Finalize Locator'}</CardTitle>
397
+ <CardDescription>
398
+ Manual selector entry still works. When a picker result arrives, Appraise fills in the selector and route
399
+ defaults for you.
400
+ </CardDescription>
401
+ </CardHeader>
402
+ <CardContent className="space-y-5">
403
+ {session?.pickedLocator ? (
404
+ <div className="rounded-lg border bg-muted/30 p-4">
405
+ <div className="mb-2 flex items-center gap-2 text-sm font-medium">
406
+ <ExternalLink className="h-4 w-4" />
407
+ Picked from page
408
+ </div>
409
+ <div className="space-y-2 text-sm text-muted-foreground">
410
+ <div className="break-all">{session.pickedLocator.selector}</div>
411
+ <div>
412
+ {session.pickedLocator.tagName}
413
+ {session.pickedLocator.accessibleName ? ` • ${session.pickedLocator.accessibleName}` : ''}
414
+ </div>
415
+ <div className="break-all">{session.pickedLocator.currentUrl}</div>
416
+ </div>
417
+ </div>
418
+ ) : (
419
+ <Alert>
420
+ <AlertTitle>Waiting for a picked selector</AlertTitle>
421
+ <AlertDescription>
422
+ You can keep working manually below, or launch Chromium and pick one live element to populate these
423
+ fields.
424
+ </AlertDescription>
425
+ </Alert>
426
+ )}
427
+
428
+ <div className="grid gap-5 lg:grid-cols-2">
429
+ <div className="space-y-2">
430
+ <Label htmlFor="locator-name">Locator Name</Label>
431
+ <Input
432
+ id="locator-name"
433
+ value={locatorName}
434
+ onChange={event => setLocatorName(event.target.value)}
435
+ placeholder="Enter locator name"
436
+ />
437
+ </div>
438
+
439
+ <div className="space-y-2">
440
+ <Label>Group Resolution</Label>
441
+ <RadioGroup
442
+ value={resolutionMode}
443
+ onValueChange={value => setResolutionMode(value as 'existing' | 'create')}
444
+ className="grid gap-3"
445
+ >
446
+ <label className="flex items-center gap-3 rounded-lg border p-3">
447
+ <RadioGroupItem value="existing" id="group-existing" />
448
+ <div>
449
+ <div className="font-medium">Use existing group</div>
450
+ <div className="text-sm text-muted-foreground">Attach the locator to a saved route group.</div>
451
+ </div>
452
+ </label>
453
+ <label className="flex items-center gap-3 rounded-lg border p-3">
454
+ <RadioGroupItem value="create" id="group-create" />
455
+ <div>
456
+ <div className="font-medium">Create new group</div>
457
+ <div className="text-sm text-muted-foreground">Create and sync a new locator group on save.</div>
458
+ </div>
459
+ </label>
460
+ </RadioGroup>
461
+ </div>
462
+ </div>
463
+
464
+ <div className="space-y-2">
465
+ <Label htmlFor="locator-selector">Selector</Label>
466
+ <Textarea
467
+ id="locator-selector"
468
+ value={selector}
469
+ onChange={event => setSelector(event.target.value)}
470
+ placeholder="Playwright selector or XPath"
471
+ className="min-h-28"
472
+ />
473
+ </div>
474
+
475
+ {resolutionMode === 'existing' ? (
476
+ <div className="space-y-2">
477
+ <Label>Locator Group</Label>
478
+ <Select value={existingLocatorGroupId} onValueChange={setExistingLocatorGroupId}>
479
+ <SelectTrigger>
480
+ <SelectValue placeholder="Select a locator group" />
481
+ </SelectTrigger>
482
+ <SelectContent isEmpty={locatorGroups.length === 0}>
483
+ {locatorGroups.map(locatorGroup => (
484
+ <SelectItem key={locatorGroup.id} value={locatorGroup.id}>
485
+ {locatorGroup.name}
486
+ </SelectItem>
487
+ ))}
488
+ </SelectContent>
489
+ </Select>
490
+ </div>
491
+ ) : (
492
+ <div className="grid gap-5 lg:grid-cols-3">
493
+ <div className="space-y-2">
494
+ <Label htmlFor="new-group-name">Locator Group Name</Label>
495
+ <Input
496
+ id="new-group-name"
497
+ value={newLocatorGroupName}
498
+ onChange={event => setNewLocatorGroupName(event.target.value)}
499
+ placeholder="Enter group name"
500
+ />
501
+ </div>
502
+
503
+ <div className="space-y-2">
504
+ <Label htmlFor="new-group-route">Route</Label>
505
+ <Input
506
+ id="new-group-route"
507
+ value={route}
508
+ onChange={event => setRoute(normalizeRoute(event.target.value))}
509
+ placeholder="/account/settings"
510
+ />
511
+ </div>
512
+
513
+ <div className="space-y-2">
514
+ <Label>Module</Label>
515
+ <Select value={moduleId} onValueChange={setModuleId}>
516
+ <SelectTrigger>
517
+ <SelectValue placeholder="Select a module" />
518
+ </SelectTrigger>
519
+ <SelectContent isEmpty={modules.length === 0}>
520
+ {modules.map(module => (
521
+ <SelectItem key={module.id} value={module.id}>
522
+ {module.name}
523
+ </SelectItem>
524
+ ))}
525
+ </SelectContent>
526
+ </Select>
527
+ </div>
528
+ </div>
529
+ )}
530
+
531
+ <div className="flex justify-end">
532
+ <Button type="button" onClick={handleSave} disabled={!canSave || isSaving}>
533
+ {isSaving ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <Save className="mr-2 h-4 w-4" />}
534
+ {isModifyMode ? 'Update Locator' : 'Save Locator'}
535
+ </Button>
536
+ </div>
537
+ </CardContent>
538
+ </Card>
539
+ </div>
540
+ </div>
541
+ )
542
+ }