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,490 +1,490 @@
1
- 'use server'
2
-
3
- import prisma from '@/config/db-config'
4
- import { locatorSchema } from '@/constants/form-opts/locator-form-opts'
5
- import { automationProjectionService } from '@/lib/automation/projection-service'
6
- import { getAutomationLocatorsDir } from '@/lib/automation/paths'
7
- import { getLocatorGroupFilePath } from '@/lib/locator-group-file-utils'
8
- import { buildModuleHierarchy } from '@/lib/module-hierarchy-builder'
9
- import { ActionResponse } from '@/types/form/actionHandler'
10
- import { revalidatePath } from 'next/cache'
11
- import { z } from 'zod'
12
- import { promises as fs } from 'fs'
13
- import path from 'path'
14
- import { glob } from 'glob'
15
-
16
- async function updateLocatorGroupFile(locatorGroupId: string | null): Promise<void> {
17
- if (!locatorGroupId) {
18
- return
19
- }
20
-
21
- try {
22
- await automationProjectionService.syncLocatorGroup(locatorGroupId)
23
- } catch (error) {
24
- console.error('Error updating locator group file:', error)
25
- }
26
- }
27
-
28
- export async function getAllLocatorsAction(): Promise<ActionResponse> {
29
- try {
30
- const locators = await prisma.locator.findMany({
31
- include: {
32
- locatorGroup: {
33
- select: {
34
- name: true,
35
- },
36
- },
37
- conflicts: {
38
- where: {
39
- resolved: false,
40
- },
41
- },
42
- },
43
- })
44
- return {
45
- status: 200,
46
- data: locators,
47
- }
48
- } catch (error) {
49
- return {
50
- status: 500,
51
- error: `Server error occurred: ${error}`,
52
- }
53
- }
54
- }
55
-
56
- export async function deleteLocatorAction(ids: string[]): Promise<ActionResponse> {
57
- try {
58
- const locatorsToDelete = await prisma.locator.findMany({
59
- where: { id: { in: ids } },
60
- select: { locatorGroupId: true },
61
- })
62
-
63
- const locatorGroupIds = [...new Set(locatorsToDelete.map(locator => locator.locatorGroupId).filter(Boolean))]
64
-
65
- const locator = await prisma.locator.deleteMany({
66
- where: { id: { in: ids } },
67
- })
68
-
69
- await Promise.all(locatorGroupIds.map(groupId => updateLocatorGroupFile(groupId)))
70
-
71
- revalidatePath('/locators')
72
- return {
73
- status: 200,
74
- data: locator,
75
- }
76
- } catch (error) {
77
- return {
78
- status: 500,
79
- error: `Server error occurred: ${error}`,
80
- }
81
- }
82
- }
83
-
84
- export async function createLocatorAction(
85
- _prev: unknown,
86
- value: z.infer<typeof locatorSchema>,
87
- ): Promise<ActionResponse> {
88
- try {
89
- locatorSchema.parse(value)
90
-
91
- const newLocator = await prisma.locator.create({
92
- data: {
93
- name: value.name,
94
- value: value.value,
95
- locatorGroupId: value.locatorGroupId,
96
- },
97
- include: {
98
- locatorGroup: {
99
- select: {
100
- name: true,
101
- },
102
- },
103
- },
104
- })
105
-
106
- if (value.locatorGroupId) {
107
- await updateLocatorGroupFile(value.locatorGroupId)
108
- }
109
-
110
- revalidatePath('/locators')
111
- return {
112
- status: 200,
113
- data: newLocator,
114
- message: 'Locator created successfully',
115
- }
116
- } catch (error) {
117
- return {
118
- status: 500,
119
- error: `Server error occurred: ${error}`,
120
- }
121
- }
122
- }
123
-
124
- export async function updateLocatorAction(
125
- _prev: unknown,
126
- value: z.infer<typeof locatorSchema>,
127
- id?: string,
128
- ): Promise<ActionResponse> {
129
- try {
130
- const currentLocator = await prisma.locator.findUnique({
131
- where: { id },
132
- select: { locatorGroupId: true },
133
- })
134
-
135
- const updatedLocator = await prisma.locator.update({
136
- where: { id },
137
- data: {
138
- name: value.name,
139
- value: value.value,
140
- locatorGroupId: value.locatorGroupId,
141
- },
142
- include: {
143
- locatorGroup: {
144
- select: {
145
- name: true,
146
- },
147
- },
148
- },
149
- })
150
-
151
- const groupsToUpdate = new Set<string>()
152
-
153
- if (currentLocator?.locatorGroupId !== value.locatorGroupId) {
154
- if (currentLocator?.locatorGroupId) {
155
- groupsToUpdate.add(currentLocator.locatorGroupId)
156
- }
157
- if (value.locatorGroupId) {
158
- groupsToUpdate.add(value.locatorGroupId)
159
- }
160
- } else if (value.locatorGroupId) {
161
- groupsToUpdate.add(value.locatorGroupId)
162
- }
163
-
164
- await Promise.all(Array.from(groupsToUpdate).map(groupId => updateLocatorGroupFile(groupId)))
165
-
166
- revalidatePath('/locators')
167
- return {
168
- status: 200,
169
- data: updatedLocator,
170
- message: 'Locator updated successfully',
171
- }
172
- } catch (error) {
173
- return {
174
- status: 500,
175
- error: `Server error occurred: ${error}`,
176
- }
177
- }
178
- }
179
-
180
- export async function getLocatorByIdAction(id: string): Promise<ActionResponse> {
181
- try {
182
- const locator = await prisma.locator.findUnique({
183
- where: { id },
184
- include: {
185
- locatorGroup: {
186
- select: {
187
- name: true,
188
- },
189
- },
190
- },
191
- })
192
- return {
193
- status: 200,
194
- data: locator,
195
- }
196
- } catch (error) {
197
- return {
198
- status: 500,
199
- error: `Server error occurred: ${error}`,
200
- }
201
- }
202
- }
203
-
204
- export async function getUngroupedLocatorsAction(): Promise<ActionResponse> {
205
- try {
206
- const locators = await prisma.locator.findMany({
207
- where: {
208
- locatorGroupId: null,
209
- },
210
- select: {
211
- id: true,
212
- name: true,
213
- value: true,
214
- },
215
- })
216
- return {
217
- status: 200,
218
- data: locators,
219
- }
220
- } catch (error) {
221
- return {
222
- status: 500,
223
- error: `Server error occurred: ${error}`,
224
- }
225
- }
226
- }
227
-
228
- function extractModulePathFromLocatorFile(filePath: string): string {
229
- const locatorsDir = getAutomationLocatorsDir()
230
- const relativePath = path.relative(locatorsDir, filePath)
231
- const pathParts = relativePath.split(/[/\\]/).filter(part => part)
232
- const moduleParts = pathParts.slice(0, -1)
233
- return moduleParts.length > 0 ? `/${moduleParts.join('/')}` : '/'
234
- }
235
-
236
- function extractLocatorGroupName(filePath: string): string {
237
- return path.basename(filePath, '.json')
238
- }
239
-
240
- async function detectAndCreateConflicts(
241
- locatorId: string,
242
- locatorName: string,
243
- locatorValue: string,
244
- locatorGroupId: string,
245
- ): Promise<number> {
246
- let conflictCount = 0
247
-
248
- const existingLocators = await prisma.locator.findMany({
249
- where: {
250
- locatorGroupId,
251
- id: { not: locatorId },
252
- },
253
- })
254
-
255
- for (const existingLocator of existingLocators) {
256
- if (existingLocator.name === locatorName) {
257
- const existingConflict = await prisma.conflictResolution.findFirst({
258
- where: {
259
- entityType: 'LOCATOR',
260
- entityId: locatorId,
261
- conflictType: 'DUPLICATE_NAME',
262
- conflictingEntityId: existingLocator.id,
263
- resolved: false,
264
- },
265
- })
266
-
267
- if (!existingConflict) {
268
- await prisma.conflictResolution.create({
269
- data: {
270
- entityType: 'LOCATOR',
271
- entityId: locatorId,
272
- conflictType: 'DUPLICATE_NAME',
273
- conflictingEntityId: existingLocator.id,
274
- resolved: false,
275
- },
276
- })
277
- await prisma.conflictResolution.create({
278
- data: {
279
- entityType: 'LOCATOR',
280
- entityId: existingLocator.id,
281
- conflictType: 'DUPLICATE_NAME',
282
- conflictingEntityId: locatorId,
283
- resolved: false,
284
- },
285
- })
286
- conflictCount++
287
- }
288
- } else if (existingLocator.value === locatorValue && existingLocator.name !== locatorName) {
289
- const existingConflict = await prisma.conflictResolution.findFirst({
290
- where: {
291
- entityType: 'LOCATOR',
292
- entityId: locatorId,
293
- conflictType: 'DUPLICATE_VALUE',
294
- conflictingEntityId: existingLocator.id,
295
- resolved: false,
296
- },
297
- })
298
-
299
- if (!existingConflict) {
300
- await prisma.conflictResolution.create({
301
- data: {
302
- entityType: 'LOCATOR',
303
- entityId: locatorId,
304
- conflictType: 'DUPLICATE_VALUE',
305
- conflictingEntityId: existingLocator.id,
306
- resolved: false,
307
- },
308
- })
309
- await prisma.conflictResolution.create({
310
- data: {
311
- entityType: 'LOCATOR',
312
- entityId: existingLocator.id,
313
- conflictType: 'DUPLICATE_VALUE',
314
- conflictingEntityId: locatorId,
315
- resolved: false,
316
- },
317
- })
318
- conflictCount++
319
- }
320
- }
321
- }
322
-
323
- return conflictCount
324
- }
325
-
326
- export async function syncLocatorsFromFilesAction(): Promise<ActionResponse> {
327
- try {
328
- const pattern = 'automation/locators/**/*.json'
329
- const relativeFiles = await glob(pattern, {
330
- cwd: process.cwd(),
331
- })
332
- const files = relativeFiles.map(file => path.resolve(process.cwd(), file))
333
-
334
- let synced = 0
335
- let totalConflicts = 0
336
- const errors: string[] = []
337
- const affectedLocatorGroupIds = new Set<string>()
338
-
339
- for (const filePath of files) {
340
- try {
341
- const content = await fs.readFile(filePath, 'utf-8')
342
- const locators = JSON.parse(content) as Record<string, string>
343
-
344
- const modulePath = extractModulePathFromLocatorFile(filePath)
345
- const moduleId = await buildModuleHierarchy(modulePath)
346
- const groupName = extractLocatorGroupName(filePath)
347
-
348
- let locatorGroup = await prisma.locatorGroup.findFirst({
349
- where: {
350
- name: groupName,
351
- moduleId,
352
- },
353
- })
354
-
355
- if (!locatorGroup) {
356
- locatorGroup = await prisma.locatorGroup.create({
357
- data: {
358
- name: groupName,
359
- route: `/${groupName}`,
360
- moduleId,
361
- },
362
- })
363
- }
364
-
365
- affectedLocatorGroupIds.add(locatorGroup.id)
366
-
367
- for (const [locatorName, locatorValue] of Object.entries(locators)) {
368
- const existingLocator = await prisma.locator.findFirst({
369
- where: {
370
- name: locatorName,
371
- locatorGroupId: locatorGroup.id,
372
- },
373
- })
374
-
375
- let locatorId: string
376
-
377
- if (existingLocator) {
378
- if (existingLocator.value !== locatorValue) {
379
- await prisma.locator.update({
380
- where: { id: existingLocator.id },
381
- data: { value: locatorValue },
382
- })
383
- }
384
- locatorId = existingLocator.id
385
- } else {
386
- const newLocator = await prisma.locator.create({
387
- data: {
388
- name: locatorName,
389
- value: locatorValue,
390
- locatorGroupId: locatorGroup.id,
391
- },
392
- })
393
- locatorId = newLocator.id
394
- synced++
395
- }
396
-
397
- totalConflicts += await detectAndCreateConflicts(locatorId, locatorName, locatorValue, locatorGroup.id)
398
- }
399
-
400
- const dbLocators = await prisma.locator.findMany({
401
- where: { locatorGroupId: locatorGroup.id },
402
- select: { name: true, value: true },
403
- })
404
-
405
- const mergedLocators: Record<string, string> = { ...locators }
406
- for (const dbLocator of dbLocators) {
407
- if (!(dbLocator.name in mergedLocators)) {
408
- mergedLocators[dbLocator.name] = dbLocator.value
409
- synced++
410
- }
411
- }
412
-
413
- await fs.writeFile(filePath, JSON.stringify(mergedLocators, null, 2) + '\n', 'utf-8')
414
- } catch (error) {
415
- const errorMessage = `Error syncing locator file ${filePath}: ${error}`
416
- console.error(errorMessage)
417
- errors.push(errorMessage)
418
- }
419
- }
420
-
421
- try {
422
- const allLocatorGroups = await prisma.locatorGroup.findMany({
423
- include: {
424
- locators: {
425
- select: { name: true, value: true },
426
- },
427
- },
428
- })
429
-
430
- for (const locatorGroup of allLocatorGroups) {
431
- if (affectedLocatorGroupIds.has(locatorGroup.id)) {
432
- continue
433
- }
434
-
435
- try {
436
- const filePath = await getLocatorGroupFilePath(locatorGroup.id)
437
- if (!filePath) {
438
- continue
439
- }
440
-
441
- try {
442
- await fs.access(filePath)
443
- const fileContent = await fs.readFile(filePath, 'utf-8')
444
- const fileLocators = JSON.parse(fileContent) as Record<string, string>
445
- const mergedLocators: Record<string, string> = { ...fileLocators }
446
-
447
- for (const dbLocator of locatorGroup.locators) {
448
- if (!(dbLocator.name in mergedLocators)) {
449
- mergedLocators[dbLocator.name] = dbLocator.value
450
- }
451
- }
452
-
453
- await fs.writeFile(filePath, JSON.stringify(mergedLocators, null, 2) + '\n', 'utf-8')
454
- } catch {
455
- await fs.mkdir(path.dirname(filePath), { recursive: true })
456
- const dbLocators: Record<string, string> = Object.fromEntries(
457
- locatorGroup.locators.map(locator => [locator.name, locator.value]),
458
- )
459
- await fs.writeFile(filePath, JSON.stringify(dbLocators, null, 2) + '\n', 'utf-8')
460
- }
461
- } catch (error) {
462
- const errorMessage = `Error syncing locator group ${locatorGroup.id} to file: ${error}`
463
- console.error(errorMessage)
464
- errors.push(errorMessage)
465
- }
466
- }
467
- } catch (error) {
468
- const errorMessage = `Error syncing database locators to files: ${error}`
469
- console.error(errorMessage)
470
- errors.push(errorMessage)
471
- }
472
-
473
- revalidatePath('/locators')
474
-
475
- return {
476
- status: 200,
477
- data: {
478
- synced,
479
- conflicts: totalConflicts,
480
- errors,
481
- },
482
- message: `Synced ${synced} locators, ${totalConflicts} conflicts detected`,
483
- }
484
- } catch (error) {
485
- return {
486
- status: 500,
487
- error: `Server error occurred: ${error}`,
488
- }
489
- }
490
- }
1
+ 'use server'
2
+
3
+ import prisma from '@/config/db-config'
4
+ import { locatorSchema } from '@/constants/form-opts/locator-form-opts'
5
+ import { automationProjectionService } from '@/lib/automation/projection-service'
6
+ import { getAutomationLocatorsDir } from '@/lib/automation/paths'
7
+ import { getLocatorGroupFilePath } from '@/lib/locator-group-file-utils'
8
+ import { buildModuleHierarchy } from '@/lib/module-hierarchy-builder'
9
+ import { ActionResponse } from '@/types/form/actionHandler'
10
+ import { revalidatePath } from 'next/cache'
11
+ import { z } from 'zod'
12
+ import { promises as fs } from 'fs'
13
+ import path from 'path'
14
+ import { glob } from 'glob'
15
+
16
+ async function updateLocatorGroupFile(locatorGroupId: string | null): Promise<void> {
17
+ if (!locatorGroupId) {
18
+ return
19
+ }
20
+
21
+ try {
22
+ await automationProjectionService.syncLocatorGroup(locatorGroupId)
23
+ } catch (error) {
24
+ console.error('Error updating locator group file:', error)
25
+ }
26
+ }
27
+
28
+ export async function getAllLocatorsAction(): Promise<ActionResponse> {
29
+ try {
30
+ const locators = await prisma.locator.findMany({
31
+ include: {
32
+ locatorGroup: {
33
+ select: {
34
+ name: true,
35
+ },
36
+ },
37
+ conflicts: {
38
+ where: {
39
+ resolved: false,
40
+ },
41
+ },
42
+ },
43
+ })
44
+ return {
45
+ status: 200,
46
+ data: locators,
47
+ }
48
+ } catch (error) {
49
+ return {
50
+ status: 500,
51
+ error: `Server error occurred: ${error}`,
52
+ }
53
+ }
54
+ }
55
+
56
+ export async function deleteLocatorAction(ids: string[]): Promise<ActionResponse> {
57
+ try {
58
+ const locatorsToDelete = await prisma.locator.findMany({
59
+ where: { id: { in: ids } },
60
+ select: { locatorGroupId: true },
61
+ })
62
+
63
+ const locatorGroupIds = [...new Set(locatorsToDelete.map(locator => locator.locatorGroupId).filter(Boolean))]
64
+
65
+ const locator = await prisma.locator.deleteMany({
66
+ where: { id: { in: ids } },
67
+ })
68
+
69
+ await Promise.all(locatorGroupIds.map(groupId => updateLocatorGroupFile(groupId)))
70
+
71
+ revalidatePath('/locators')
72
+ return {
73
+ status: 200,
74
+ data: locator,
75
+ }
76
+ } catch (error) {
77
+ return {
78
+ status: 500,
79
+ error: `Server error occurred: ${error}`,
80
+ }
81
+ }
82
+ }
83
+
84
+ export async function createLocatorAction(
85
+ _prev: unknown,
86
+ value: z.infer<typeof locatorSchema>,
87
+ ): Promise<ActionResponse> {
88
+ try {
89
+ locatorSchema.parse(value)
90
+
91
+ const newLocator = await prisma.locator.create({
92
+ data: {
93
+ name: value.name,
94
+ value: value.value,
95
+ locatorGroupId: value.locatorGroupId,
96
+ },
97
+ include: {
98
+ locatorGroup: {
99
+ select: {
100
+ name: true,
101
+ },
102
+ },
103
+ },
104
+ })
105
+
106
+ if (value.locatorGroupId) {
107
+ await updateLocatorGroupFile(value.locatorGroupId)
108
+ }
109
+
110
+ revalidatePath('/locators')
111
+ return {
112
+ status: 200,
113
+ data: newLocator,
114
+ message: 'Locator created successfully',
115
+ }
116
+ } catch (error) {
117
+ return {
118
+ status: 500,
119
+ error: `Server error occurred: ${error}`,
120
+ }
121
+ }
122
+ }
123
+
124
+ export async function updateLocatorAction(
125
+ _prev: unknown,
126
+ value: z.infer<typeof locatorSchema>,
127
+ id?: string,
128
+ ): Promise<ActionResponse> {
129
+ try {
130
+ const currentLocator = await prisma.locator.findUnique({
131
+ where: { id },
132
+ select: { locatorGroupId: true },
133
+ })
134
+
135
+ const updatedLocator = await prisma.locator.update({
136
+ where: { id },
137
+ data: {
138
+ name: value.name,
139
+ value: value.value,
140
+ locatorGroupId: value.locatorGroupId,
141
+ },
142
+ include: {
143
+ locatorGroup: {
144
+ select: {
145
+ name: true,
146
+ },
147
+ },
148
+ },
149
+ })
150
+
151
+ const groupsToUpdate = new Set<string>()
152
+
153
+ if (currentLocator?.locatorGroupId !== value.locatorGroupId) {
154
+ if (currentLocator?.locatorGroupId) {
155
+ groupsToUpdate.add(currentLocator.locatorGroupId)
156
+ }
157
+ if (value.locatorGroupId) {
158
+ groupsToUpdate.add(value.locatorGroupId)
159
+ }
160
+ } else if (value.locatorGroupId) {
161
+ groupsToUpdate.add(value.locatorGroupId)
162
+ }
163
+
164
+ await Promise.all(Array.from(groupsToUpdate).map(groupId => updateLocatorGroupFile(groupId)))
165
+
166
+ revalidatePath('/locators')
167
+ return {
168
+ status: 200,
169
+ data: updatedLocator,
170
+ message: 'Locator updated successfully',
171
+ }
172
+ } catch (error) {
173
+ return {
174
+ status: 500,
175
+ error: `Server error occurred: ${error}`,
176
+ }
177
+ }
178
+ }
179
+
180
+ export async function getLocatorByIdAction(id: string): Promise<ActionResponse> {
181
+ try {
182
+ const locator = await prisma.locator.findUnique({
183
+ where: { id },
184
+ include: {
185
+ locatorGroup: {
186
+ select: {
187
+ name: true,
188
+ },
189
+ },
190
+ },
191
+ })
192
+ return {
193
+ status: 200,
194
+ data: locator,
195
+ }
196
+ } catch (error) {
197
+ return {
198
+ status: 500,
199
+ error: `Server error occurred: ${error}`,
200
+ }
201
+ }
202
+ }
203
+
204
+ export async function getUngroupedLocatorsAction(): Promise<ActionResponse> {
205
+ try {
206
+ const locators = await prisma.locator.findMany({
207
+ where: {
208
+ locatorGroupId: null,
209
+ },
210
+ select: {
211
+ id: true,
212
+ name: true,
213
+ value: true,
214
+ },
215
+ })
216
+ return {
217
+ status: 200,
218
+ data: locators,
219
+ }
220
+ } catch (error) {
221
+ return {
222
+ status: 500,
223
+ error: `Server error occurred: ${error}`,
224
+ }
225
+ }
226
+ }
227
+
228
+ function extractModulePathFromLocatorFile(filePath: string): string {
229
+ const locatorsDir = getAutomationLocatorsDir()
230
+ const relativePath = path.relative(locatorsDir, filePath)
231
+ const pathParts = relativePath.split(/[/\\]/).filter(part => part)
232
+ const moduleParts = pathParts.slice(0, -1)
233
+ return moduleParts.length > 0 ? `/${moduleParts.join('/')}` : '/'
234
+ }
235
+
236
+ function extractLocatorGroupName(filePath: string): string {
237
+ return path.basename(filePath, '.json')
238
+ }
239
+
240
+ async function detectAndCreateConflicts(
241
+ locatorId: string,
242
+ locatorName: string,
243
+ locatorValue: string,
244
+ locatorGroupId: string,
245
+ ): Promise<number> {
246
+ let conflictCount = 0
247
+
248
+ const existingLocators = await prisma.locator.findMany({
249
+ where: {
250
+ locatorGroupId,
251
+ id: { not: locatorId },
252
+ },
253
+ })
254
+
255
+ for (const existingLocator of existingLocators) {
256
+ if (existingLocator.name === locatorName) {
257
+ const existingConflict = await prisma.conflictResolution.findFirst({
258
+ where: {
259
+ entityType: 'LOCATOR',
260
+ entityId: locatorId,
261
+ conflictType: 'DUPLICATE_NAME',
262
+ conflictingEntityId: existingLocator.id,
263
+ resolved: false,
264
+ },
265
+ })
266
+
267
+ if (!existingConflict) {
268
+ await prisma.conflictResolution.create({
269
+ data: {
270
+ entityType: 'LOCATOR',
271
+ entityId: locatorId,
272
+ conflictType: 'DUPLICATE_NAME',
273
+ conflictingEntityId: existingLocator.id,
274
+ resolved: false,
275
+ },
276
+ })
277
+ await prisma.conflictResolution.create({
278
+ data: {
279
+ entityType: 'LOCATOR',
280
+ entityId: existingLocator.id,
281
+ conflictType: 'DUPLICATE_NAME',
282
+ conflictingEntityId: locatorId,
283
+ resolved: false,
284
+ },
285
+ })
286
+ conflictCount++
287
+ }
288
+ } else if (existingLocator.value === locatorValue && existingLocator.name !== locatorName) {
289
+ const existingConflict = await prisma.conflictResolution.findFirst({
290
+ where: {
291
+ entityType: 'LOCATOR',
292
+ entityId: locatorId,
293
+ conflictType: 'DUPLICATE_VALUE',
294
+ conflictingEntityId: existingLocator.id,
295
+ resolved: false,
296
+ },
297
+ })
298
+
299
+ if (!existingConflict) {
300
+ await prisma.conflictResolution.create({
301
+ data: {
302
+ entityType: 'LOCATOR',
303
+ entityId: locatorId,
304
+ conflictType: 'DUPLICATE_VALUE',
305
+ conflictingEntityId: existingLocator.id,
306
+ resolved: false,
307
+ },
308
+ })
309
+ await prisma.conflictResolution.create({
310
+ data: {
311
+ entityType: 'LOCATOR',
312
+ entityId: existingLocator.id,
313
+ conflictType: 'DUPLICATE_VALUE',
314
+ conflictingEntityId: locatorId,
315
+ resolved: false,
316
+ },
317
+ })
318
+ conflictCount++
319
+ }
320
+ }
321
+ }
322
+
323
+ return conflictCount
324
+ }
325
+
326
+ export async function syncLocatorsFromFilesAction(): Promise<ActionResponse> {
327
+ try {
328
+ const pattern = 'automation/locators/**/*.json'
329
+ const relativeFiles = await glob(pattern, {
330
+ cwd: process.cwd(),
331
+ })
332
+ const files = relativeFiles.map(file => path.resolve(process.cwd(), file))
333
+
334
+ let synced = 0
335
+ let totalConflicts = 0
336
+ const errors: string[] = []
337
+ const affectedLocatorGroupIds = new Set<string>()
338
+
339
+ for (const filePath of files) {
340
+ try {
341
+ const content = await fs.readFile(filePath, 'utf-8')
342
+ const locators = JSON.parse(content) as Record<string, string>
343
+
344
+ const modulePath = extractModulePathFromLocatorFile(filePath)
345
+ const moduleId = await buildModuleHierarchy(modulePath)
346
+ const groupName = extractLocatorGroupName(filePath)
347
+
348
+ let locatorGroup = await prisma.locatorGroup.findFirst({
349
+ where: {
350
+ name: groupName,
351
+ moduleId,
352
+ },
353
+ })
354
+
355
+ if (!locatorGroup) {
356
+ locatorGroup = await prisma.locatorGroup.create({
357
+ data: {
358
+ name: groupName,
359
+ route: `/${groupName}`,
360
+ moduleId,
361
+ },
362
+ })
363
+ }
364
+
365
+ affectedLocatorGroupIds.add(locatorGroup.id)
366
+
367
+ for (const [locatorName, locatorValue] of Object.entries(locators)) {
368
+ const existingLocator = await prisma.locator.findFirst({
369
+ where: {
370
+ name: locatorName,
371
+ locatorGroupId: locatorGroup.id,
372
+ },
373
+ })
374
+
375
+ let locatorId: string
376
+
377
+ if (existingLocator) {
378
+ if (existingLocator.value !== locatorValue) {
379
+ await prisma.locator.update({
380
+ where: { id: existingLocator.id },
381
+ data: { value: locatorValue },
382
+ })
383
+ }
384
+ locatorId = existingLocator.id
385
+ } else {
386
+ const newLocator = await prisma.locator.create({
387
+ data: {
388
+ name: locatorName,
389
+ value: locatorValue,
390
+ locatorGroupId: locatorGroup.id,
391
+ },
392
+ })
393
+ locatorId = newLocator.id
394
+ synced++
395
+ }
396
+
397
+ totalConflicts += await detectAndCreateConflicts(locatorId, locatorName, locatorValue, locatorGroup.id)
398
+ }
399
+
400
+ const dbLocators = await prisma.locator.findMany({
401
+ where: { locatorGroupId: locatorGroup.id },
402
+ select: { name: true, value: true },
403
+ })
404
+
405
+ const mergedLocators: Record<string, string> = { ...locators }
406
+ for (const dbLocator of dbLocators) {
407
+ if (!(dbLocator.name in mergedLocators)) {
408
+ mergedLocators[dbLocator.name] = dbLocator.value
409
+ synced++
410
+ }
411
+ }
412
+
413
+ await fs.writeFile(filePath, JSON.stringify(mergedLocators, null, 2) + '\n', 'utf-8')
414
+ } catch (error) {
415
+ const errorMessage = `Error syncing locator file ${filePath}: ${error}`
416
+ console.error(errorMessage)
417
+ errors.push(errorMessage)
418
+ }
419
+ }
420
+
421
+ try {
422
+ const allLocatorGroups = await prisma.locatorGroup.findMany({
423
+ include: {
424
+ locators: {
425
+ select: { name: true, value: true },
426
+ },
427
+ },
428
+ })
429
+
430
+ for (const locatorGroup of allLocatorGroups) {
431
+ if (affectedLocatorGroupIds.has(locatorGroup.id)) {
432
+ continue
433
+ }
434
+
435
+ try {
436
+ const filePath = await getLocatorGroupFilePath(locatorGroup.id)
437
+ if (!filePath) {
438
+ continue
439
+ }
440
+
441
+ try {
442
+ await fs.access(filePath)
443
+ const fileContent = await fs.readFile(filePath, 'utf-8')
444
+ const fileLocators = JSON.parse(fileContent) as Record<string, string>
445
+ const mergedLocators: Record<string, string> = { ...fileLocators }
446
+
447
+ for (const dbLocator of locatorGroup.locators) {
448
+ if (!(dbLocator.name in mergedLocators)) {
449
+ mergedLocators[dbLocator.name] = dbLocator.value
450
+ }
451
+ }
452
+
453
+ await fs.writeFile(filePath, JSON.stringify(mergedLocators, null, 2) + '\n', 'utf-8')
454
+ } catch {
455
+ await fs.mkdir(path.dirname(filePath), { recursive: true })
456
+ const dbLocators: Record<string, string> = Object.fromEntries(
457
+ locatorGroup.locators.map(locator => [locator.name, locator.value]),
458
+ )
459
+ await fs.writeFile(filePath, JSON.stringify(dbLocators, null, 2) + '\n', 'utf-8')
460
+ }
461
+ } catch (error) {
462
+ const errorMessage = `Error syncing locator group ${locatorGroup.id} to file: ${error}`
463
+ console.error(errorMessage)
464
+ errors.push(errorMessage)
465
+ }
466
+ }
467
+ } catch (error) {
468
+ const errorMessage = `Error syncing database locators to files: ${error}`
469
+ console.error(errorMessage)
470
+ errors.push(errorMessage)
471
+ }
472
+
473
+ revalidatePath('/locators')
474
+
475
+ return {
476
+ status: 200,
477
+ data: {
478
+ synced,
479
+ conflicts: totalConflicts,
480
+ errors,
481
+ },
482
+ message: `Synced ${synced} locators, ${totalConflicts} conflicts detected`,
483
+ }
484
+ } catch (error) {
485
+ return {
486
+ status: 500,
487
+ error: `Server error occurred: ${error}`,
488
+ }
489
+ }
490
+ }