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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (414) hide show
  1. package/README.md +52 -52
  2. package/dist/cli.e2e.test.d.ts +2 -0
  3. package/dist/cli.e2e.test.d.ts.map +1 -0
  4. package/dist/cli.e2e.test.js +73 -0
  5. package/dist/cli.e2e.test.js.map +1 -0
  6. package/dist/config.test.d.ts +2 -0
  7. package/dist/config.test.d.ts.map +1 -0
  8. package/dist/config.test.js +65 -0
  9. package/dist/config.test.js.map +1 -0
  10. package/dist/copy-template.test.d.ts +2 -0
  11. package/dist/copy-template.test.d.ts.map +1 -0
  12. package/dist/copy-template.test.js +71 -0
  13. package/dist/copy-template.test.js.map +1 -0
  14. package/dist/download-repo.test.d.ts +2 -0
  15. package/dist/download-repo.test.d.ts.map +1 -0
  16. package/dist/download-repo.test.js +14 -0
  17. package/dist/download-repo.test.js.map +1 -0
  18. package/dist/install.test.d.ts +2 -0
  19. package/dist/install.test.d.ts.map +1 -0
  20. package/dist/install.test.js +119 -0
  21. package/dist/install.test.js.map +1 -0
  22. package/dist/prepare-template-utils.d.ts.map +1 -1
  23. package/dist/prepare-template-utils.js +5 -0
  24. package/dist/prepare-template-utils.js.map +1 -1
  25. package/dist/prompts.test.d.ts +2 -0
  26. package/dist/prompts.test.d.ts.map +1 -0
  27. package/dist/prompts.test.js +58 -0
  28. package/dist/prompts.test.js.map +1 -0
  29. package/dist/scaffold-gitignore.d.ts +1 -0
  30. package/dist/scaffold-gitignore.d.ts.map +1 -1
  31. package/dist/scaffold-gitignore.js +3 -0
  32. package/dist/scaffold-gitignore.js.map +1 -1
  33. package/package.json +3 -2
  34. package/templates/default/.appraise-template-meta.json +2 -2
  35. package/templates/default/.env.example +2 -2
  36. package/templates/default/.vscode/settings.json +2 -1
  37. package/templates/default/README.md +53 -53
  38. package/templates/default/automation/mapping/locator-map.json +1 -14
  39. package/templates/default/automation/steps/actions/click.step.ts +58 -58
  40. package/templates/default/automation/steps/actions/hover.step.ts +27 -27
  41. package/templates/default/automation/steps/actions/input.step.ts +84 -84
  42. package/templates/default/automation/steps/actions/navigation.step.ts +70 -70
  43. package/templates/default/automation/steps/actions/random_data.step.ts +142 -142
  44. package/templates/default/automation/steps/actions/store.step.ts +86 -86
  45. package/templates/default/automation/steps/actions/wait.step.ts +90 -90
  46. package/templates/default/automation/steps/validations/active_state_assertion.step.ts +30 -30
  47. package/templates/default/automation/steps/validations/navigation_assertion.step.ts +22 -22
  48. package/templates/default/automation/steps/validations/text_assertion.step.ts +107 -107
  49. package/templates/default/automation/steps/validations/visibility_assertion.step.ts +26 -26
  50. package/templates/default/components.json +24 -24
  51. package/templates/default/cucumber.mjs +16 -16
  52. package/templates/default/eslint.config.mjs +16 -16
  53. package/templates/default/gitignore +3 -0
  54. package/templates/default/next-env.d.ts +6 -6
  55. package/templates/default/next.config.ts +11 -11
  56. package/templates/default/package-lock.json +146 -75
  57. package/templates/default/package.json +2 -1
  58. package/templates/default/packages/cucumber-runtime/package.json +13 -13
  59. package/templates/default/packages/cucumber-runtime/src/cache.util.ts +93 -93
  60. package/templates/default/packages/cucumber-runtime/src/cli.ts +68 -68
  61. package/templates/default/packages/cucumber-runtime/src/environment.util.ts +21 -21
  62. package/templates/default/packages/cucumber-runtime/src/executor.ts +32 -32
  63. package/templates/default/packages/cucumber-runtime/src/hooks.ts +25 -3
  64. package/templates/default/packages/cucumber-runtime/src/index.ts +17 -17
  65. package/templates/default/packages/cucumber-runtime/src/locator.util.ts +234 -234
  66. package/templates/default/packages/cucumber-runtime/src/parameter-types.ts +7 -7
  67. package/templates/default/packages/cucumber-runtime/src/paths.ts +66 -1
  68. package/templates/default/packages/cucumber-runtime/src/random-data.util.ts +35 -35
  69. package/templates/default/packages/cucumber-runtime/src/types.ts +13 -13
  70. package/templates/default/packages/cucumber-runtime/src/world.ts +44 -44
  71. package/templates/default/packages/cucumber-runtime/tsconfig.json +11 -11
  72. package/templates/default/packages/locator-picker-companion/dist/cli.d.ts +1 -0
  73. package/templates/default/packages/locator-picker-companion/dist/cli.js +302 -0
  74. package/templates/default/packages/locator-picker-companion/dist/index.d.ts +3 -0
  75. package/templates/default/packages/locator-picker-companion/dist/index.js +3 -0
  76. package/templates/default/packages/locator-picker-companion/dist/injected-picker-script.d.ts +1 -0
  77. package/templates/default/packages/locator-picker-companion/dist/injected-picker-script.js +615 -0
  78. package/templates/default/packages/locator-picker-companion/dist/launcher.d.ts +11 -0
  79. package/templates/default/packages/locator-picker-companion/dist/launcher.js +59 -0
  80. package/templates/default/packages/locator-picker-companion/dist/selector-generator.d.ts +3 -0
  81. package/templates/default/packages/locator-picker-companion/dist/selector-generator.js +257 -0
  82. package/templates/default/packages/locator-picker-companion/dist/session-file.d.ts +22 -0
  83. package/templates/default/packages/locator-picker-companion/dist/session-file.js +150 -0
  84. package/templates/default/packages/locator-picker-companion/dist/types.d.ts +31 -0
  85. package/templates/default/packages/locator-picker-companion/dist/types.js +1 -0
  86. package/templates/default/packages/locator-picker-companion/package.json +17 -0
  87. package/templates/default/packages/locator-picker-companion/src/cli.ts +428 -0
  88. package/templates/default/packages/locator-picker-companion/src/index.ts +3 -0
  89. package/templates/default/packages/locator-picker-companion/src/injected-picker-script.ts +771 -0
  90. package/templates/default/packages/locator-picker-companion/src/launcher.ts +77 -0
  91. package/templates/default/packages/locator-picker-companion/src/selector-generator.ts +339 -0
  92. package/templates/default/packages/locator-picker-companion/src/session-file.ts +226 -0
  93. package/templates/default/packages/locator-picker-companion/src/types.ts +42 -0
  94. package/templates/default/packages/locator-picker-companion/tsconfig.json +14 -0
  95. package/templates/default/postcss.config.mjs +8 -8
  96. package/templates/default/prisma/dev.db +0 -0
  97. package/templates/default/prisma/migrations/20251104113456_add_type_for_template_step_groups/migration.sql +16 -16
  98. package/templates/default/prisma/migrations/20251104170946_add_tags_to_test_suite_and_test_case/migration.sql +27 -27
  99. package/templates/default/prisma/migrations/20251112190024_add_cascade_delete_to_test_run_test_case/migration.sql +17 -17
  100. package/templates/default/prisma/migrations/20251113181100_add_test_run_log/migration.sql +12 -12
  101. package/templates/default/prisma/migrations/20251119191838_add_tag_type/migration.sql +28 -28
  102. package/templates/default/prisma/migrations/20251121164059_add_conflict_resolution/migration.sql +12 -12
  103. package/templates/default/prisma/migrations/20251223183400_add_report_model_to_db_schema/migration.sql +10 -10
  104. package/templates/default/prisma/migrations/20251223183637_add_report_test_case_entity_for_storing_test_results_for_individual_test_cases/migration.sql +10 -10
  105. package/templates/default/prisma/migrations/20251224083549_add_comprehensive_report_storage/migration.sql +108 -108
  106. package/templates/default/prisma/migrations/20251229194422_migrate_duration_to_string/migration.sql +55 -55
  107. package/templates/default/prisma/migrations/20251230124637_add_unique_constraint_to_test_run_name/migration.sql +27 -27
  108. package/templates/default/prisma/migrations/20260115094436_add_dashboard_metrics/migration.sql +59 -59
  109. package/templates/default/prisma/migrations/20260127172022_add_cascade_delete_to_step_parameters/migration.sql +34 -34
  110. package/templates/default/prisma/migrations/20260313093000_add_report_step_screenshot_path/migration.sql +1 -0
  111. package/templates/default/prisma/migrations/20260318120000_add_test_suite_context_to_test_run_test_case/migration.sql +20 -0
  112. package/templates/default/prisma/migrations/20260318173512_add_support_of_test_suite_level_runs/migration.sql +2 -0
  113. package/templates/default/prisma/schema.prisma +4 -0
  114. package/templates/default/scripts/install-playwright.ts +20 -0
  115. package/templates/default/scripts/protect-seeded-files.ts +38 -38
  116. package/templates/default/scripts/regenerate-features.ts +94 -94
  117. package/templates/default/scripts/setup-env.ts +19 -19
  118. package/templates/default/scripts/sync-all.ts +22 -66
  119. package/templates/default/scripts/sync-appraise-base-template.ts +57 -19
  120. package/templates/default/scripts/sync-environments.ts +40 -21
  121. package/templates/default/scripts/sync-locator-groups.ts +410 -410
  122. package/templates/default/scripts/sync-locators.ts +398 -398
  123. package/templates/default/scripts/sync-modules.ts +341 -341
  124. package/templates/default/scripts/sync-tags.ts +2 -3
  125. package/templates/default/scripts/sync-template-step-groups.ts +0 -3
  126. package/templates/default/scripts/sync-template-steps.ts +37 -11
  127. package/templates/default/scripts/sync-test-cases.ts +68 -16
  128. package/templates/default/scripts/sync-test-suites.ts +7 -5
  129. package/templates/default/src/actions/conflict/conflict.action.ts +33 -33
  130. package/templates/default/src/actions/dashboard/dashboard-actions.ts +240 -240
  131. package/templates/default/src/actions/environments/environment-actions.ts +188 -188
  132. package/templates/default/src/actions/locator/locator-actions.ts +490 -490
  133. package/templates/default/src/actions/locator-groups/locator-group-actions.ts +6 -3
  134. package/templates/default/src/actions/locator-picker/locator-picker-actions.ts +243 -0
  135. package/templates/default/src/actions/modules/module-actions.ts +135 -135
  136. package/templates/default/src/actions/reports/report-actions.ts +51 -49
  137. package/templates/default/src/actions/review/review-actions.ts +147 -147
  138. package/templates/default/src/actions/settings/sync-actions.ts +46 -0
  139. package/templates/default/src/actions/tags/tag-actions.ts +107 -107
  140. package/templates/default/src/actions/template-step/template-step-actions.ts +38 -4
  141. package/templates/default/src/actions/template-step-group/template-step-group-actions.ts +10 -3
  142. package/templates/default/src/actions/template-test-case/template-test-case-actions.ts +238 -238
  143. package/templates/default/src/actions/test-case/test-case-actions.ts +356 -356
  144. package/templates/default/src/actions/test-run/test-run-actions.ts +222 -129
  145. package/templates/default/src/actions/test-suite/test-suite-actions.ts +95 -17
  146. package/templates/default/src/actions/user/user-actions.ts +13 -13
  147. package/templates/default/src/app/(base)/environments/create/page.tsx +28 -28
  148. package/templates/default/src/app/(base)/environments/environment-form.tsx +219 -219
  149. package/templates/default/src/app/(base)/environments/environment-table-columns.tsx +96 -96
  150. package/templates/default/src/app/(base)/environments/environment-table.tsx +24 -24
  151. package/templates/default/src/app/(base)/environments/modify/[id]/page.tsx +46 -46
  152. package/templates/default/src/app/(base)/environments/page.tsx +59 -59
  153. package/templates/default/src/app/(base)/layout.tsx +10 -10
  154. package/templates/default/src/app/(base)/locator-groups/create/page.tsx +44 -44
  155. package/templates/default/src/app/(base)/locator-groups/locator-group-form.tsx +215 -215
  156. package/templates/default/src/app/(base)/locator-groups/locator-group-table-columns.tsx +77 -77
  157. package/templates/default/src/app/(base)/locator-groups/locator-group-table.tsx +28 -28
  158. package/templates/default/src/app/(base)/locator-groups/modify/[id]/page.tsx +46 -46
  159. package/templates/default/src/app/(base)/locator-groups/page.tsx +20 -8
  160. package/templates/default/src/app/(base)/locators/create/create-locator-workspace.tsx +542 -0
  161. package/templates/default/src/app/(base)/locators/create/page.tsx +32 -19
  162. package/templates/default/src/app/(base)/locators/locator-form.tsx +163 -163
  163. package/templates/default/src/app/(base)/locators/locator-table-columns.tsx +73 -73
  164. package/templates/default/src/app/(base)/locators/locator-table.tsx +28 -28
  165. package/templates/default/src/app/(base)/locators/modify/[id]/page.tsx +56 -27
  166. package/templates/default/src/app/(base)/locators/page.tsx +13 -13
  167. package/templates/default/src/app/(base)/locators/picker/page.tsx +7 -0
  168. package/templates/default/src/app/(base)/locators/sync-locators-button.tsx +66 -66
  169. package/templates/default/src/app/(base)/modules/create/page.tsx +34 -34
  170. package/templates/default/src/app/(base)/modules/modify/[id]/page.tsx +46 -46
  171. package/templates/default/src/app/(base)/modules/module-form.tsx +126 -126
  172. package/templates/default/src/app/(base)/modules/module-table-columns.tsx +85 -85
  173. package/templates/default/src/app/(base)/modules/module-table.tsx +24 -24
  174. package/templates/default/src/app/(base)/modules/page.tsx +59 -59
  175. package/templates/default/src/app/(base)/reports/[id]/page.tsx +40 -33
  176. package/templates/default/src/app/(base)/reports/duration-chart.tsx +33 -33
  177. package/templates/default/src/app/(base)/reports/feature-chart.tsx +78 -78
  178. package/templates/default/src/app/(base)/reports/overview-chart.tsx +49 -46
  179. package/templates/default/src/app/(base)/reports/page.tsx +98 -98
  180. package/templates/default/src/app/(base)/reports/report-metric-card.tsx +78 -16
  181. package/templates/default/src/app/(base)/reports/report-table-columns.tsx +189 -189
  182. package/templates/default/src/app/(base)/reports/report-table.tsx +72 -72
  183. package/templates/default/src/app/(base)/reports/report-view-table-columns.tsx +12 -1
  184. package/templates/default/src/app/(base)/reports/report-view-table.tsx +1 -0
  185. package/templates/default/src/app/(base)/reports/test-cases/page.tsx +40 -42
  186. package/templates/default/src/app/(base)/reports/test-cases/test-cases-metric-table-columns.tsx +115 -115
  187. package/templates/default/src/app/(base)/reports/test-cases/test-cases-metric-table.tsx +27 -27
  188. package/templates/default/src/app/(base)/reports/test-suites/page.tsx +42 -42
  189. package/templates/default/src/app/(base)/reports/test-suites/test-suites-metric-table-columns.tsx +79 -79
  190. package/templates/default/src/app/(base)/reports/test-suites/test-suites-metric-table.tsx +27 -27
  191. package/templates/default/src/app/(base)/reports/view-logs-button.tsx +58 -60
  192. package/templates/default/src/app/(base)/reviews/create/page.tsx +26 -26
  193. package/templates/default/src/app/(base)/reviews/created-reviews-table.tsx +15 -15
  194. package/templates/default/src/app/(base)/reviews/modify/[id]/page.tsx +26 -26
  195. package/templates/default/src/app/(base)/reviews/page.tsx +26 -26
  196. package/templates/default/src/app/(base)/reviews/review/[id]/page.tsx +26 -26
  197. package/templates/default/src/app/(base)/reviews/review-form.tsx +11 -11
  198. package/templates/default/src/app/(base)/reviews/review-table-by-creator-columns.tsx +9 -9
  199. package/templates/default/src/app/(base)/reviews/review-table-by-reviewer-columns.tsx +9 -9
  200. package/templates/default/src/app/(base)/reviews/reviewer-reviews-table.tsx +15 -15
  201. package/templates/default/src/app/(base)/settings/page.tsx +30 -0
  202. package/templates/default/src/app/(base)/settings/settings-sync-panel.tsx +261 -0
  203. package/templates/default/src/app/(base)/tags/create/page.tsx +39 -39
  204. package/templates/default/src/app/(base)/tags/modify/[id]/page.tsx +50 -50
  205. package/templates/default/src/app/(base)/tags/page.tsx +58 -58
  206. package/templates/default/src/app/(base)/tags/tag-form.tsx +147 -147
  207. package/templates/default/src/app/(base)/tags/tag-table-columns.tsx +63 -63
  208. package/templates/default/src/app/(base)/tags/tag-table.tsx +29 -29
  209. package/templates/default/src/app/(base)/template-step-groups/create/page.tsx +28 -28
  210. package/templates/default/src/app/(base)/template-step-groups/modify/[id]/page.tsx +45 -45
  211. package/templates/default/src/app/(base)/template-step-groups/page.tsx +60 -60
  212. package/templates/default/src/app/(base)/template-step-groups/template-step-group-form.tsx +167 -167
  213. package/templates/default/src/app/(base)/template-step-groups/template-step-group-table-columns.tsx +89 -89
  214. package/templates/default/src/app/(base)/template-step-groups/template-step-group-table.tsx +32 -32
  215. package/templates/default/src/app/(base)/template-steps/create/page.tsx +37 -37
  216. package/templates/default/src/app/(base)/template-steps/modify/[id]/page.tsx +49 -49
  217. package/templates/default/src/app/(base)/template-steps/page.tsx +58 -58
  218. package/templates/default/src/app/(base)/template-steps/paramChip.tsx +213 -213
  219. package/templates/default/src/app/(base)/template-steps/template-step-form.tsx +384 -384
  220. package/templates/default/src/app/(base)/template-steps/template-step-table-columns.tsx +158 -158
  221. package/templates/default/src/app/(base)/template-steps/template-step-table.tsx +24 -24
  222. package/templates/default/src/app/(base)/template-test-cases/create/page.tsx +56 -56
  223. package/templates/default/src/app/(base)/template-test-cases/modify/[id]/page.tsx +89 -89
  224. package/templates/default/src/app/(base)/template-test-cases/page.tsx +58 -58
  225. package/templates/default/src/app/(base)/template-test-cases/template-test-case-flow.tsx +84 -84
  226. package/templates/default/src/app/(base)/template-test-cases/template-test-case-form.tsx +262 -262
  227. package/templates/default/src/app/(base)/template-test-cases/template-test-case-table-columns.tsx +76 -76
  228. package/templates/default/src/app/(base)/template-test-cases/template-test-case-table.tsx +32 -32
  229. package/templates/default/src/app/(base)/test-cases/create/page.tsx +76 -76
  230. package/templates/default/src/app/(base)/test-cases/create-from-template/generate/[id]/page.tsx +96 -96
  231. package/templates/default/src/app/(base)/test-cases/create-from-template/page.tsx +38 -38
  232. package/templates/default/src/app/(base)/test-cases/create-from-template/template-selection-form.tsx +73 -73
  233. package/templates/default/src/app/(base)/test-cases/modify/[id]/page.tsx +106 -106
  234. package/templates/default/src/app/(base)/test-cases/page.tsx +2 -2
  235. package/templates/default/src/app/(base)/test-cases/test-case-flow.tsx +82 -82
  236. package/templates/default/src/app/(base)/test-cases/test-case-form.tsx +395 -395
  237. package/templates/default/src/app/(base)/test-cases/test-case-table-columns.tsx +6 -71
  238. package/templates/default/src/app/(base)/test-cases/test-case-table.tsx +2 -2
  239. package/templates/default/src/app/(base)/test-runs/[id]/page.tsx +4 -1
  240. package/templates/default/src/app/(base)/test-runs/create/page.tsx +11 -9
  241. package/templates/default/src/app/(base)/test-runs/page.tsx +60 -60
  242. package/templates/default/src/app/(base)/test-runs/test-run-form.tsx +37 -44
  243. package/templates/default/src/app/(base)/test-runs/test-run-table-columns.tsx +229 -229
  244. package/templates/default/src/app/(base)/test-runs/test-run-table.tsx +127 -127
  245. package/templates/default/src/app/(base)/test-suites/create/page.tsx +3 -2
  246. package/templates/default/src/app/(base)/test-suites/modify/[id]/page.tsx +2 -1
  247. package/templates/default/src/app/(base)/test-suites/page.tsx +57 -21
  248. package/templates/default/src/app/(base)/test-suites/test-suite-form.tsx +16 -17
  249. package/templates/default/src/app/(base)/test-suites/test-suite-table-columns.tsx +2 -1
  250. package/templates/default/src/app/(base)/test-suites/test-suite-table.tsx +29 -29
  251. package/templates/default/src/app/(dashboard-components)/app-drawer.tsx +187 -187
  252. package/templates/default/src/app/(dashboard-components)/data-card-grid.tsx +12 -12
  253. package/templates/default/src/app/(dashboard-components)/data-card.tsx +26 -26
  254. package/templates/default/src/app/(dashboard-components)/execution-health-panel.tsx +56 -56
  255. package/templates/default/src/app/(dashboard-components)/ongoing-test-runs-card.tsx +87 -87
  256. package/templates/default/src/app/(dashboard-components)/quick-actions-drawer.tsx +44 -44
  257. package/templates/default/src/app/api/reports/steps/[stepId]/screenshot/route.ts +52 -0
  258. package/templates/default/src/app/api/test-runs/[runId]/download/route.ts +97 -34
  259. package/templates/default/src/app/api/test-runs/[runId]/logs/route.ts +4 -1
  260. package/templates/default/src/app/api/test-runs/[runId]/trace/[testCaseId]/route.ts +146 -147
  261. package/templates/default/src/app/globals.css +147 -147
  262. package/templates/default/src/app/layout.tsx +4 -0
  263. package/templates/default/src/app/page.tsx +2 -0
  264. package/templates/default/src/assets/icons/empty-tube.tsx +23 -23
  265. package/templates/default/src/assets/icons/tube-plus.tsx +29 -29
  266. package/templates/default/src/components/base-node.tsx +21 -21
  267. package/templates/default/src/components/chart/pie-chart.tsx +73 -73
  268. package/templates/default/src/components/data-extraction/locator-inspector.tsx +460 -460
  269. package/templates/default/src/components/data-state/empty-state.tsx +40 -40
  270. package/templates/default/src/components/data-visualization/info-card.tsx +70 -70
  271. package/templates/default/src/components/data-visualization/info-grid.tsx +22 -22
  272. package/templates/default/src/components/diagram/button-edge.tsx +54 -54
  273. package/templates/default/src/components/diagram/dynamic-parameters.tsx +474 -474
  274. package/templates/default/src/components/diagram/edit-header-option.tsx +36 -36
  275. package/templates/default/src/components/diagram/flow-diagram.tsx +470 -470
  276. package/templates/default/src/components/diagram/node-form.tsx +262 -262
  277. package/templates/default/src/components/diagram/options-header-node.tsx +57 -57
  278. package/templates/default/src/components/diagram/template-step-combobox.tsx +155 -155
  279. package/templates/default/src/components/form/error-message.tsx +7 -7
  280. package/templates/default/src/components/kokonutui/smooth-tab.tsx +453 -453
  281. package/templates/default/src/components/loading-skeleton/data-table/data-table-skeleton.tsx +30 -30
  282. package/templates/default/src/components/loading-skeleton/form/button-skeleton.tsx +8 -8
  283. package/templates/default/src/components/loading-skeleton/form/icon-button-skeleton.tsx +8 -8
  284. package/templates/default/src/components/loading-skeleton/form/text-input-skeleton.tsx +8 -8
  285. package/templates/default/src/components/loading-skeleton/visualization/table-skeleton.tsx +14 -14
  286. package/templates/default/src/components/logo.tsx +15 -15
  287. package/templates/default/src/components/navigation/command-badge.tsx +34 -34
  288. package/templates/default/src/components/navigation/command-chain-input.tsx +51 -51
  289. package/templates/default/src/components/navigation/entity-search-command.tsx +116 -116
  290. package/templates/default/src/components/navigation/nav-card.tsx +31 -31
  291. package/templates/default/src/components/navigation/nav-command.tsx +13 -0
  292. package/templates/default/src/components/navigation/nav-link.tsx +60 -60
  293. package/templates/default/src/components/navigation/nav-menu-card-deck.tsx +112 -112
  294. package/templates/default/src/components/node-header.tsx +159 -159
  295. package/templates/default/src/components/reports/test-case-logs-modal.tsx +310 -253
  296. package/templates/default/src/components/table/table-actions.tsx +172 -172
  297. package/templates/default/src/components/test-case/test-case-columns.tsx +76 -0
  298. package/templates/default/src/components/test-case/test-case-picker.tsx +244 -0
  299. package/templates/default/src/components/test-run/download-logs-button.tsx +92 -99
  300. package/templates/default/src/components/test-run/log-viewer.tsx +1 -14
  301. package/templates/default/src/components/test-run/test-run-details.tsx +15 -2
  302. package/templates/default/src/components/test-run/test-run-header.tsx +149 -149
  303. package/templates/default/src/components/test-run/view-report-button.tsx +102 -102
  304. package/templates/default/src/components/test-suite/test-suite-picker.tsx +444 -0
  305. package/templates/default/src/components/theme/mode-toggle.tsx +54 -54
  306. package/templates/default/src/components/theme/theme-provider.tsx +8 -8
  307. package/templates/default/src/components/typography/page-header-subtitle.tsx +7 -7
  308. package/templates/default/src/components/typography/page-header.tsx +7 -7
  309. package/templates/default/src/components/ui/alert-dialog.tsx +106 -106
  310. package/templates/default/src/components/ui/alert.tsx +43 -43
  311. package/templates/default/src/components/ui/avatar.tsx +40 -40
  312. package/templates/default/src/components/ui/badge.tsx +29 -29
  313. package/templates/default/src/components/ui/button.tsx +47 -47
  314. package/templates/default/src/components/ui/calendar.tsx +158 -158
  315. package/templates/default/src/components/ui/card.tsx +43 -43
  316. package/templates/default/src/components/ui/checkbox.tsx +28 -28
  317. package/templates/default/src/components/ui/command.tsx +135 -135
  318. package/templates/default/src/components/ui/data-table-column-header.tsx +61 -61
  319. package/templates/default/src/components/ui/data-table-pagination.tsx +87 -87
  320. package/templates/default/src/components/ui/data-table-view-options.tsx +50 -50
  321. package/templates/default/src/components/ui/data-table.tsx +261 -261
  322. package/templates/default/src/components/ui/dialog.tsx +97 -97
  323. package/templates/default/src/components/ui/dropdown-menu.tsx +182 -182
  324. package/templates/default/src/components/ui/input.tsx +22 -22
  325. package/templates/default/src/components/ui/kbd.tsx +28 -28
  326. package/templates/default/src/components/ui/label.tsx +19 -19
  327. package/templates/default/src/components/ui/loading.tsx +12 -12
  328. package/templates/default/src/components/ui/multi-select-with-preview.tsx +116 -116
  329. package/templates/default/src/components/ui/multi-select.tsx +142 -142
  330. package/templates/default/src/components/ui/navigation-menu.tsx +120 -120
  331. package/templates/default/src/components/ui/popover.tsx +33 -33
  332. package/templates/default/src/components/ui/progress.tsx +25 -25
  333. package/templates/default/src/components/ui/radio-group.tsx +44 -44
  334. package/templates/default/src/components/ui/scroll-area.tsx +40 -40
  335. package/templates/default/src/components/ui/select.tsx +151 -151
  336. package/templates/default/src/components/ui/separator.tsx +22 -22
  337. package/templates/default/src/components/ui/skeleton.tsx +7 -7
  338. package/templates/default/src/components/ui/table.tsx +76 -76
  339. package/templates/default/src/components/ui/tabs.tsx +55 -55
  340. package/templates/default/src/components/ui/textarea.tsx +21 -21
  341. package/templates/default/src/components/ui/toast.tsx +113 -113
  342. package/templates/default/src/components/ui/toaster.tsx +26 -26
  343. package/templates/default/src/components/user-prompt/delete-prompt.tsx +87 -87
  344. package/templates/default/src/config/db-config.ts +10 -10
  345. package/templates/default/src/constants/form-opts/diagram/node-form.ts +30 -30
  346. package/templates/default/src/constants/form-opts/environment-form-opts.ts +24 -24
  347. package/templates/default/src/constants/form-opts/locator-form-opts.ts +20 -20
  348. package/templates/default/src/constants/form-opts/locator-group-form-opts.ts +28 -28
  349. package/templates/default/src/constants/form-opts/module-form-opts.ts +21 -21
  350. package/templates/default/src/constants/form-opts/review-form-opts.ts +23 -23
  351. package/templates/default/src/constants/form-opts/tag-form-opts.ts +42 -42
  352. package/templates/default/src/constants/form-opts/template-selection-form-opts.ts +16 -16
  353. package/templates/default/src/constants/form-opts/template-step-group-form-opts.ts +24 -24
  354. package/templates/default/src/constants/form-opts/template-test-case-form-opts.ts +39 -39
  355. package/templates/default/src/constants/form-opts/template-test-step-form-opts.ts +36 -36
  356. package/templates/default/src/constants/form-opts/test-case-form-opts.ts +43 -43
  357. package/templates/default/src/constants/form-opts/test-run-form-opts.ts +5 -3
  358. package/templates/default/src/constants/form-opts/test-suite-form-opts.ts +24 -24
  359. package/templates/default/src/hooks/use-toast.ts +187 -187
  360. package/templates/default/src/lib/automation/paths.ts +42 -12
  361. package/templates/default/src/lib/automation/projection-service.ts +2 -1
  362. package/templates/default/src/lib/bidirectional-sync.ts +432 -432
  363. package/templates/default/src/lib/database-sync.ts +2 -7
  364. package/templates/default/src/lib/environment-file-utils.ts +184 -184
  365. package/templates/default/src/lib/executor/local-executor-adapter.ts +24 -25
  366. package/templates/default/src/lib/executor/types.ts +2 -2
  367. package/templates/default/src/lib/feature-file-generator.ts +5 -66
  368. package/templates/default/src/lib/gherkin-parser.ts +259 -259
  369. package/templates/default/src/lib/locator-group-file-utils.ts +307 -307
  370. package/templates/default/src/lib/locator-picker/session-manager.ts +452 -0
  371. package/templates/default/src/lib/locator-picker/suggestions.ts +124 -0
  372. package/templates/default/src/lib/metrics/metric-calculator.ts +5 -0
  373. package/templates/default/src/lib/module-hierarchy-builder.ts +205 -205
  374. package/templates/default/src/lib/path-helpers/module-path.ts +71 -71
  375. package/templates/default/src/lib/process/task-spawner.ts +39 -2
  376. package/templates/default/src/lib/sync/projected-feature-utils.ts +113 -0
  377. package/templates/default/src/lib/sync/sync-executor.ts +130 -0
  378. package/templates/default/src/lib/sync/sync-pending-counts.test.ts +239 -0
  379. package/templates/default/src/lib/sync/sync-pending-counts.ts +1358 -0
  380. package/templates/default/src/lib/sync/sync-registry.ts +149 -0
  381. package/templates/default/src/lib/tag-utils.ts +28 -0
  382. package/templates/default/src/lib/template-sync-utils.d.ts +6 -6
  383. package/templates/default/src/lib/template-sync-utils.js +46 -46
  384. package/templates/default/src/lib/template-sync-utils.ts +63 -63
  385. package/templates/default/src/lib/test-case-utils.ts +6 -6
  386. package/templates/default/src/lib/test-run/log-formatter.ts +83 -83
  387. package/templates/default/src/lib/test-run/matching.ts +117 -0
  388. package/templates/default/src/lib/test-run/process-manager.ts +2 -0
  389. package/templates/default/src/lib/test-run/report-parser.ts +352 -316
  390. package/templates/default/src/lib/test-run/test-run-executor.ts +13 -15
  391. package/templates/default/src/lib/test-run/winston-logger.ts +64 -66
  392. package/templates/default/src/lib/test-suite-identifier-service.ts +76 -0
  393. package/templates/default/src/lib/test-suite-utils.ts +6 -0
  394. package/templates/default/src/lib/transformers/gherkin-converter.ts +42 -42
  395. package/templates/default/src/lib/transformers/key-to-icon-transformer.tsx +95 -95
  396. package/templates/default/src/lib/transformers/template-test-case-converter.ts +160 -160
  397. package/templates/default/src/lib/utils/node-param-validation.ts +81 -81
  398. package/templates/default/src/lib/utils/template-step-file-generator.ts +104 -104
  399. package/templates/default/src/lib/utils/template-step-file-manager-intelligent.ts +0 -8
  400. package/templates/default/src/lib/utils/template-step-file-manager.ts +166 -166
  401. package/templates/default/src/lib/utils.ts +31 -31
  402. package/templates/default/src/types/diagram/diagram.ts +34 -34
  403. package/templates/default/src/types/diagram/template-step.ts +11 -11
  404. package/templates/default/src/types/executor/browser.type.ts +1 -1
  405. package/templates/default/src/types/form/actionHandler.ts +6 -6
  406. package/templates/default/src/types/locator/locator.type.ts +11 -11
  407. package/templates/default/src/types/locator-picker.ts +65 -0
  408. package/templates/default/src/types/step/step.type.ts +1 -1
  409. package/templates/default/src/types/table/data-table.ts +6 -6
  410. package/templates/default/src/types/test-case-picker.ts +6 -0
  411. package/templates/default/src/types/test-suite-picker.ts +18 -0
  412. package/templates/default/tailwind.config.ts +62 -62
  413. package/templates/default/tsconfig.json +4 -1
  414. package/templates/default/appraisejs.config.json +0 -1
@@ -0,0 +1,77 @@
1
+ import { access, readdir, stat } from 'fs/promises'
2
+ import { execa } from 'execa'
3
+ import path from 'path'
4
+
5
+ async function pathExists(targetPath: string): Promise<boolean> {
6
+ try {
7
+ await access(targetPath)
8
+ return true
9
+ } catch {
10
+ return false
11
+ }
12
+ }
13
+
14
+ async function getLatestModifiedTime(targetPath: string): Promise<number> {
15
+ const stats = await stat(targetPath)
16
+ if (!stats.isDirectory()) {
17
+ return stats.mtimeMs
18
+ }
19
+
20
+ const entries = await readdir(targetPath, { withFileTypes: true })
21
+ const times = await Promise.all(
22
+ entries.map(entry => getLatestModifiedTime(path.join(targetPath, entry.name))),
23
+ )
24
+
25
+ return Math.max(stats.mtimeMs, ...times)
26
+ }
27
+
28
+ export function getLocatorPickerCompanionPaths(repoRoot = process.cwd()) {
29
+ const packageRoot = path.join(repoRoot, 'packages', 'locator-picker-companion')
30
+
31
+ return {
32
+ packageRoot,
33
+ sourceRoot: path.join(packageRoot, 'src'),
34
+ distCliPath: path.join(packageRoot, 'dist', 'cli.js'),
35
+ tsconfigPath: path.join(packageRoot, 'tsconfig.json'),
36
+ tscCliPath: path.join(repoRoot, 'node_modules', 'typescript', 'bin', 'tsc'),
37
+ }
38
+ }
39
+
40
+ async function ensureLocatorPickerCompanionBuilt(repoRoot = process.cwd()): Promise<string> {
41
+ const { distCliPath, sourceRoot, tsconfigPath, tscCliPath } = getLocatorPickerCompanionPaths(repoRoot)
42
+
43
+ if (await pathExists(distCliPath)) {
44
+ const [distMtime, srcMtime] = await Promise.all([
45
+ getLatestModifiedTime(distCliPath),
46
+ getLatestModifiedTime(sourceRoot),
47
+ ])
48
+
49
+ if (distMtime >= srcMtime) {
50
+ return distCliPath
51
+ }
52
+ }
53
+
54
+ await execa(process.execPath, [tscCliPath, '-p', tsconfigPath], {
55
+ cwd: repoRoot,
56
+ env: process.env,
57
+ stdio: 'pipe',
58
+ })
59
+
60
+ if (!(await pathExists(distCliPath))) {
61
+ throw new Error('Locator picker companion build completed without producing dist/cli.js.')
62
+ }
63
+
64
+ return distCliPath
65
+ }
66
+
67
+ export async function resolveLocatorPickerCompanionInvocation(
68
+ cliArgs: string[],
69
+ repoRoot = process.cwd(),
70
+ ): Promise<{ command: string; args: string[] }> {
71
+ const distCliPath = await ensureLocatorPickerCompanionBuilt(repoRoot)
72
+
73
+ return {
74
+ command: process.execPath,
75
+ args: [distCliPath, ...cliArgs],
76
+ }
77
+ }
@@ -0,0 +1,339 @@
1
+ import type { ElementHandle, Page } from 'playwright'
2
+ import type { CompanionPickedLocatorPayload, CompanionPickedLocatorStrategy } from './types.js'
3
+
4
+ interface ElementSnapshot {
5
+ tagName: string
6
+ text: string
7
+ accessibleName: string
8
+ labelText: string
9
+ placeholder: string
10
+ id: string
11
+ role: string
12
+ roleNameMatchCount: number
13
+ stableClasses: string[]
14
+ nameAttribute: string
15
+ typeAttribute: string
16
+ ariaLabel: string
17
+ testAttributeName: string
18
+ testAttributeValue: string
19
+ }
20
+
21
+ function normalizeText(value: string | null | undefined): string {
22
+ return (value ?? '').replace(/\s+/g, ' ').trim()
23
+ }
24
+
25
+ function normalizeRoute(value: string): string {
26
+ if (!value) {
27
+ return '/'
28
+ }
29
+
30
+ try {
31
+ return new URL(value).pathname || '/'
32
+ } catch {
33
+ return value.startsWith('/') ? value : `/${value}`
34
+ }
35
+ }
36
+
37
+ function escapeForCss(value: string): string {
38
+ return value.replace(/[^a-zA-Z0-9_-]/g, match => `\\${match}`)
39
+ }
40
+
41
+ function escapeForSelectorText(value: string): string {
42
+ return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')
43
+ }
44
+
45
+ function isLikelyStableIdentifier(value: string | null | undefined): value is string {
46
+ if (!value) {
47
+ return false
48
+ }
49
+
50
+ const normalized = value.trim()
51
+ if (!normalized || normalized.length > 120) {
52
+ return false
53
+ }
54
+
55
+ return !/\d{4,}/.test(normalized) && !/[A-Fa-f0-9]{8,}/.test(normalized)
56
+ }
57
+
58
+ function buildCssSelector(snapshot: ElementSnapshot): string {
59
+ const parts = [snapshot.tagName]
60
+
61
+ if (isLikelyStableIdentifier(snapshot.nameAttribute)) {
62
+ parts.push(`[name="${escapeForSelectorText(snapshot.nameAttribute)}"]`)
63
+ }
64
+
65
+ if (isLikelyStableIdentifier(snapshot.typeAttribute)) {
66
+ parts.push(`[type="${escapeForSelectorText(snapshot.typeAttribute)}"]`)
67
+ }
68
+
69
+ if (isLikelyStableIdentifier(snapshot.ariaLabel)) {
70
+ parts.push(`[aria-label="${escapeForSelectorText(snapshot.ariaLabel)}"]`)
71
+ }
72
+
73
+ if (parts.length === 1 && snapshot.stableClasses.length > 0) {
74
+ parts.push(...snapshot.stableClasses.slice(0, 2).map(className => `.${escapeForCss(className)}`))
75
+ }
76
+
77
+ return parts.length > 1 ? `css=${parts.join('')}` : ''
78
+ }
79
+
80
+ function buildXPathSelector(snapshot: ElementSnapshot): string {
81
+ if (snapshot.id) {
82
+ return `xpath=//*[@id="${snapshot.id.replace(/"/g, '\\"')}"]`
83
+ }
84
+
85
+ if (snapshot.text) {
86
+ return `xpath=//${snapshot.tagName}[normalize-space()="${snapshot.text.replace(/"/g, '\\"')}"]`
87
+ }
88
+
89
+ return `xpath=//${snapshot.tagName}`
90
+ }
91
+
92
+ function buildPrimarySelector(snapshot: ElementSnapshot): {
93
+ selector: string
94
+ strategy: CompanionPickedLocatorStrategy
95
+ } {
96
+ if (isLikelyStableIdentifier(snapshot.testAttributeValue)) {
97
+ return {
98
+ selector: `css=[${snapshot.testAttributeName}="${escapeForSelectorText(snapshot.testAttributeValue)}"]`,
99
+ strategy: 'test-id',
100
+ }
101
+ }
102
+
103
+ if (snapshot.role && snapshot.accessibleName && snapshot.roleNameMatchCount === 1) {
104
+ return {
105
+ selector: `role=${snapshot.role}[name="${escapeForSelectorText(snapshot.accessibleName)}"]`,
106
+ strategy: 'role',
107
+ }
108
+ }
109
+
110
+ if (snapshot.labelText) {
111
+ return {
112
+ selector: `label="${escapeForSelectorText(snapshot.labelText)}"`,
113
+ strategy: 'label',
114
+ }
115
+ }
116
+
117
+ if (snapshot.placeholder) {
118
+ return {
119
+ selector: `placeholder="${escapeForSelectorText(snapshot.placeholder)}"`,
120
+ strategy: 'placeholder',
121
+ }
122
+ }
123
+
124
+ if (isLikelyStableIdentifier(snapshot.id)) {
125
+ return {
126
+ selector: `css=#${escapeForCss(snapshot.id)}`,
127
+ strategy: 'id',
128
+ }
129
+ }
130
+
131
+ const cssSelector = buildCssSelector(snapshot)
132
+ if (cssSelector) {
133
+ return {
134
+ selector: cssSelector,
135
+ strategy: 'css',
136
+ }
137
+ }
138
+
139
+ return {
140
+ selector: buildXPathSelector(snapshot),
141
+ strategy: 'xpath',
142
+ }
143
+ }
144
+
145
+ async function getElementSnapshot(elementHandle: ElementHandle): Promise<ElementSnapshot> {
146
+ return elementHandle.evaluate(node => {
147
+ const element = node as Element
148
+ const normalizeText = (value: string | null | undefined): string => (value ?? '').replace(/\s+/g, ' ').trim()
149
+ const isLikelyStableIdentifier = (value: string | null | undefined): value is string => {
150
+ if (!value) {
151
+ return false
152
+ }
153
+
154
+ const normalized = value.trim()
155
+ if (!normalized || normalized.length > 120) {
156
+ return false
157
+ }
158
+
159
+ return !/\d{4,}/.test(normalized) && !/[A-Fa-f0-9]{8,}/.test(normalized)
160
+ }
161
+
162
+ const getLabelText = (candidate: Element): string => {
163
+ if (!['input', 'textarea', 'select'].includes(candidate.tagName.toLowerCase())) {
164
+ return ''
165
+ }
166
+
167
+ const input = candidate as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
168
+ if (input.labels && input.labels.length > 0) {
169
+ return normalizeText(
170
+ Array.from(input.labels)
171
+ .map(label => label.textContent || '')
172
+ .join(' '),
173
+ )
174
+ }
175
+
176
+ return ''
177
+ }
178
+
179
+ const getAccessibleName = (candidate: Element): string => {
180
+ const ariaLabel = normalizeText(candidate.getAttribute('aria-label'))
181
+ if (ariaLabel) {
182
+ return ariaLabel
183
+ }
184
+
185
+ const labelledBy = normalizeText(candidate.getAttribute('aria-labelledby'))
186
+ if (labelledBy) {
187
+ const text = labelledBy
188
+ .split(/\s+/)
189
+ .map(id => document.getElementById(id)?.textContent || '')
190
+ .join(' ')
191
+ const normalized = normalizeText(text)
192
+ if (normalized) {
193
+ return normalized
194
+ }
195
+ }
196
+
197
+ const labelText = getLabelText(candidate)
198
+ if (labelText) {
199
+ return labelText
200
+ }
201
+
202
+ const alt = normalizeText(candidate.getAttribute('alt'))
203
+ if (alt) {
204
+ return alt
205
+ }
206
+
207
+ const title = normalizeText(candidate.getAttribute('title'))
208
+ if (title) {
209
+ return title
210
+ }
211
+
212
+ const placeholder = normalizeText(candidate.getAttribute('placeholder'))
213
+ if (placeholder) {
214
+ return placeholder
215
+ }
216
+
217
+ return normalizeText(candidate.textContent).slice(0, 240)
218
+ }
219
+
220
+ const getRole = (candidate: Element): string => {
221
+ const explicitRole = normalizeText(candidate.getAttribute('role'))
222
+ if (explicitRole) {
223
+ return explicitRole
224
+ }
225
+
226
+ const tagName = candidate.tagName.toLowerCase()
227
+ if (tagName === 'button') {
228
+ return 'button'
229
+ }
230
+
231
+ if (tagName === 'a' && candidate.hasAttribute('href')) {
232
+ return 'link'
233
+ }
234
+
235
+ if (tagName === 'textarea') {
236
+ return 'textbox'
237
+ }
238
+
239
+ if (tagName === 'select') {
240
+ return 'combobox'
241
+ }
242
+
243
+ if (tagName === 'img') {
244
+ return 'img'
245
+ }
246
+
247
+ if (tagName === 'input') {
248
+ const type = (candidate.getAttribute('type') || 'text').toLowerCase()
249
+ if (['button', 'submit', 'reset'].includes(type)) {
250
+ return 'button'
251
+ }
252
+
253
+ if (type === 'checkbox') {
254
+ return 'checkbox'
255
+ }
256
+
257
+ if (type === 'radio') {
258
+ return 'radio'
259
+ }
260
+
261
+ return 'textbox'
262
+ }
263
+
264
+ return ''
265
+ }
266
+
267
+ const role = getRole(element)
268
+ const accessibleName = getAccessibleName(element)
269
+
270
+ let roleNameMatchCount = 0
271
+ if (role && accessibleName) {
272
+ for (const candidate of Array.from(document.querySelectorAll('*'))) {
273
+ if (getRole(candidate) !== role) {
274
+ continue
275
+ }
276
+
277
+ if (getAccessibleName(candidate) !== accessibleName) {
278
+ continue
279
+ }
280
+
281
+ roleNameMatchCount += 1
282
+ if (roleNameMatchCount > 1) {
283
+ break
284
+ }
285
+ }
286
+ }
287
+
288
+ let testAttributeName = ''
289
+ let testAttributeValue = ''
290
+ for (const attributeName of ['data-testid', 'data-test', 'data-qa']) {
291
+ const value = element.getAttribute(attributeName)
292
+ if (isLikelyStableIdentifier(value)) {
293
+ testAttributeName = attributeName
294
+ testAttributeValue = value
295
+ break
296
+ }
297
+ }
298
+
299
+ return {
300
+ tagName: element.tagName.toLowerCase(),
301
+ text: normalizeText(element.textContent).slice(0, 240),
302
+ accessibleName,
303
+ labelText: getLabelText(element),
304
+ placeholder: normalizeText(element.getAttribute('placeholder')),
305
+ id: normalizeText(element.getAttribute('id')),
306
+ role,
307
+ roleNameMatchCount,
308
+ stableClasses: Array.from(element.classList || [])
309
+ .filter((className): className is string => isLikelyStableIdentifier(className))
310
+ .slice(0, 2),
311
+ nameAttribute: normalizeText(element.getAttribute('name')),
312
+ typeAttribute: normalizeText(element.getAttribute('type')),
313
+ ariaLabel: normalizeText(element.getAttribute('aria-label')),
314
+ testAttributeName,
315
+ testAttributeValue,
316
+ }
317
+ })
318
+ }
319
+
320
+ export async function generatePickedLocatorPayload(
321
+ page: Page,
322
+ elementHandle: ElementHandle,
323
+ ): Promise<CompanionPickedLocatorPayload> {
324
+ const snapshot = await getElementSnapshot(elementHandle)
325
+ const selection = buildPrimarySelector(snapshot)
326
+ const currentUrl = page.url()
327
+ const pageTitle = normalizeText(await page.title().catch(() => ''))
328
+
329
+ return {
330
+ selector: selection.selector,
331
+ strategy: selection.strategy,
332
+ currentUrl,
333
+ pathname: normalizeRoute(currentUrl),
334
+ pageTitle,
335
+ tagName: snapshot.tagName,
336
+ text: snapshot.text || undefined,
337
+ accessibleName: snapshot.accessibleName || undefined,
338
+ }
339
+ }
@@ -0,0 +1,226 @@
1
+ import { randomUUID } from 'crypto'
2
+ import { access, appendFile, mkdir, readFile, rename, rm, unlink, writeFile } from 'fs/promises'
3
+ import path from 'path'
4
+ import type { CompanionSessionFile } from './types.js'
5
+
6
+ const sessionFileOperationQueue = new Map<string, Promise<unknown>>()
7
+
8
+ function withTimestamp(
9
+ session: Omit<CompanionSessionFile, 'updatedAt'> & { updatedAt?: string },
10
+ ): CompanionSessionFile {
11
+ return {
12
+ ...session,
13
+ updatedAt: session.updatedAt ?? new Date().toISOString(),
14
+ }
15
+ }
16
+
17
+ export function getLocatorPickerRootDir(repoRoot = process.cwd()): string {
18
+ return path.join(repoRoot, '.tmp', 'locator-picker')
19
+ }
20
+
21
+ export function getLocatorPickerSessionsDir(repoRoot = process.cwd()): string {
22
+ return path.join(getLocatorPickerRootDir(repoRoot), 'sessions')
23
+ }
24
+
25
+ export function getLocatorPickerProfilesDir(repoRoot = process.cwd()): string {
26
+ return path.join(getLocatorPickerRootDir(repoRoot), 'profiles')
27
+ }
28
+
29
+ export function getLocatorPickerLogsDir(repoRoot = process.cwd()): string {
30
+ return path.join(getLocatorPickerRootDir(repoRoot), 'logs')
31
+ }
32
+
33
+ export function getLocatorPickerProfileDir(sessionId: string, repoRoot = process.cwd()): string {
34
+ return path.join(getLocatorPickerProfilesDir(repoRoot), sessionId)
35
+ }
36
+
37
+ export function getLocatorPickerRuntimeDir(repoRoot = process.cwd()): string {
38
+ return path.join(getLocatorPickerRootDir(repoRoot), 'runtime')
39
+ }
40
+
41
+ export function getLocatorPickerRuntimeHomeDir(repoRoot = process.cwd()): string {
42
+ return path.join(getLocatorPickerRuntimeDir(repoRoot), 'home')
43
+ }
44
+
45
+ export function getLocatorPickerRuntimeEnv(
46
+ repoRoot = process.cwd(),
47
+ baseEnv: NodeJS.ProcessEnv = process.env,
48
+ ): NodeJS.ProcessEnv {
49
+ const runtimeDir = getLocatorPickerRuntimeDir(repoRoot)
50
+ const runtimeHomeDir = getLocatorPickerRuntimeHomeDir(repoRoot)
51
+ const tempDir = path.join(runtimeDir, 'tmp')
52
+ const configDir = path.join(runtimeDir, 'config')
53
+ const cacheDir = path.join(runtimeDir, 'cache')
54
+ const appDataDir = path.join(runtimeDir, 'appdata')
55
+ const localAppDataDir = path.join(runtimeDir, 'localappdata')
56
+
57
+ return {
58
+ ...baseEnv,
59
+ HOME: runtimeHomeDir,
60
+ USERPROFILE: runtimeHomeDir,
61
+ APPDATA: appDataDir,
62
+ LOCALAPPDATA: localAppDataDir,
63
+ XDG_CONFIG_HOME: configDir,
64
+ XDG_CACHE_HOME: cacheDir,
65
+ TMPDIR: tempDir,
66
+ TMP: tempDir,
67
+ TEMP: tempDir,
68
+ }
69
+ }
70
+
71
+ export function getLocatorPickerSessionFilePath(sessionId: string, repoRoot = process.cwd()): string {
72
+ return path.join(getLocatorPickerSessionsDir(repoRoot), `${sessionId}.json`)
73
+ }
74
+
75
+ export function getLocatorPickerCrashLogPath(sessionId: string, repoRoot = process.cwd()): string {
76
+ return path.join(getLocatorPickerLogsDir(repoRoot), `${sessionId}.log`)
77
+ }
78
+
79
+ export async function removeLocatorPickerProfileDir(
80
+ sessionId: string,
81
+ repoRoot = process.cwd(),
82
+ ): Promise<void> {
83
+ await rm(getLocatorPickerProfileDir(sessionId, repoRoot), {
84
+ force: true,
85
+ recursive: true,
86
+ }).catch(() => undefined)
87
+ }
88
+
89
+ export async function removeLocatorPickerSessionFile(
90
+ sessionId: string,
91
+ repoRoot = process.cwd(),
92
+ ): Promise<void> {
93
+ await unlink(getLocatorPickerSessionFilePath(sessionId, repoRoot)).catch(() => undefined)
94
+ }
95
+
96
+ export async function clearLocatorPickerCrashLogs(repoRoot = process.cwd()): Promise<void> {
97
+ await rm(getLocatorPickerLogsDir(repoRoot), {
98
+ force: true,
99
+ recursive: true,
100
+ }).catch(() => console.error('Failed to clear locator picker crash logs.'))
101
+
102
+ await mkdir(getLocatorPickerLogsDir(repoRoot), { recursive: true })
103
+ }
104
+
105
+ export async function createLocatorPickerCrashLog(logFilePath: string): Promise<void> {
106
+ await mkdir(path.dirname(logFilePath), { recursive: true })
107
+ await writeFile(logFilePath, '', 'utf8')
108
+ }
109
+
110
+ export async function appendLocatorPickerCrashLog(logFilePath: string, message: string): Promise<void> {
111
+ try {
112
+ await access(logFilePath)
113
+ } catch {
114
+ return
115
+ }
116
+
117
+ await appendFile(logFilePath, `[${new Date().toISOString()}] ${message}\n`, 'utf8')
118
+ }
119
+
120
+ export async function ensureLocatorPickerDirectories(repoRoot = process.cwd()): Promise<void> {
121
+ const runtimeDir = getLocatorPickerRuntimeDir(repoRoot)
122
+ const runtimeHomeDir = getLocatorPickerRuntimeHomeDir(repoRoot)
123
+ const tempDir = path.join(runtimeDir, 'tmp')
124
+ const configDir = path.join(runtimeDir, 'config')
125
+ const cacheDir = path.join(runtimeDir, 'cache')
126
+ const appDataDir = path.join(runtimeDir, 'appdata')
127
+ const localAppDataDir = path.join(runtimeDir, 'localappdata')
128
+
129
+ await mkdir(getLocatorPickerSessionsDir(repoRoot), { recursive: true })
130
+ await mkdir(getLocatorPickerProfilesDir(repoRoot), { recursive: true })
131
+ await mkdir(getLocatorPickerLogsDir(repoRoot), { recursive: true })
132
+ await mkdir(runtimeHomeDir, { recursive: true })
133
+ await mkdir(tempDir, { recursive: true })
134
+ await mkdir(configDir, { recursive: true })
135
+ await mkdir(cacheDir, { recursive: true })
136
+ await mkdir(appDataDir, { recursive: true })
137
+ await mkdir(localAppDataDir, { recursive: true })
138
+ }
139
+
140
+ export async function readLocatorPickerSessionFile(
141
+ sessionFilePath: string,
142
+ ): Promise<CompanionSessionFile | null> {
143
+ try {
144
+ const raw = await readFile(sessionFilePath, 'utf8')
145
+ return JSON.parse(raw) as CompanionSessionFile
146
+ } catch {
147
+ return null
148
+ }
149
+ }
150
+
151
+ async function enqueueSessionFileOperation<T>(
152
+ sessionFilePath: string,
153
+ operation: () => Promise<T>,
154
+ ): Promise<T> {
155
+ const previousOperation = sessionFileOperationQueue.get(sessionFilePath) ?? Promise.resolve()
156
+ const nextOperation = previousOperation.catch(() => undefined).then(operation)
157
+
158
+ sessionFileOperationQueue.set(sessionFilePath, nextOperation)
159
+
160
+ try {
161
+ return await nextOperation
162
+ } finally {
163
+ if (sessionFileOperationQueue.get(sessionFilePath) === nextOperation) {
164
+ sessionFileOperationQueue.delete(sessionFilePath)
165
+ }
166
+ }
167
+ }
168
+
169
+ async function writeLocatorPickerSessionFileImmediate(
170
+ sessionFilePath: string,
171
+ session: Omit<CompanionSessionFile, 'updatedAt'> & { updatedAt?: string },
172
+ ): Promise<CompanionSessionFile> {
173
+ await mkdir(path.dirname(sessionFilePath), { recursive: true })
174
+
175
+ const normalized = withTimestamp(session)
176
+ const serialized = `${JSON.stringify(normalized, null, 2)}\n`
177
+ const tempFilePath = `${sessionFilePath}.${process.pid}.${randomUUID()}.tmp`
178
+
179
+ try {
180
+ await writeFile(tempFilePath, serialized, 'utf8')
181
+ await rename(tempFilePath, sessionFilePath)
182
+ } catch (error) {
183
+ await unlink(tempFilePath).catch(() => undefined)
184
+
185
+ const code = error instanceof Error && 'code' in error ? String(error.code) : ''
186
+ if (process.platform === 'win32' || code === 'EPERM' || code === 'EACCES' || code === 'ENOENT') {
187
+ await writeFile(sessionFilePath, serialized, 'utf8')
188
+ } else {
189
+ throw error
190
+ }
191
+ }
192
+
193
+ return normalized
194
+ }
195
+
196
+ export async function writeLocatorPickerSessionFile(
197
+ sessionFilePath: string,
198
+ session: Omit<CompanionSessionFile, 'updatedAt'> & { updatedAt?: string },
199
+ ): Promise<CompanionSessionFile> {
200
+ return enqueueSessionFileOperation(sessionFilePath, () =>
201
+ writeLocatorPickerSessionFileImmediate(sessionFilePath, session),
202
+ )
203
+ }
204
+
205
+ export async function patchLocatorPickerSessionFile(
206
+ sessionFilePath: string,
207
+ patch:
208
+ | Partial<CompanionSessionFile>
209
+ | ((current: CompanionSessionFile) => Partial<CompanionSessionFile> | CompanionSessionFile),
210
+ ): Promise<CompanionSessionFile | null> {
211
+ return enqueueSessionFileOperation(sessionFilePath, async () => {
212
+ const current = await readLocatorPickerSessionFile(sessionFilePath)
213
+ if (!current) {
214
+ return null
215
+ }
216
+
217
+ const nextPatch = typeof patch === 'function' ? patch(current) : patch
218
+ const nextSession = {
219
+ ...current,
220
+ ...nextPatch,
221
+ updatedAt: new Date().toISOString(),
222
+ }
223
+
224
+ return writeLocatorPickerSessionFileImmediate(sessionFilePath, nextSession)
225
+ })
226
+ }
@@ -0,0 +1,42 @@
1
+ export type CompanionSessionStatus = 'starting' | 'ready' | 'picked' | 'saving' | 'closed' | 'error'
2
+
3
+ export type CompanionPickedLocatorStrategy =
4
+ | 'test-id'
5
+ | 'role'
6
+ | 'label'
7
+ | 'placeholder'
8
+ | 'id'
9
+ | 'css'
10
+ | 'xpath'
11
+
12
+ export interface CompanionLaunchSource {
13
+ environmentId?: string
14
+ environmentName?: string
15
+ url: string
16
+ }
17
+
18
+ export interface CompanionPickedLocatorPayload {
19
+ selector: string
20
+ currentUrl: string
21
+ pathname: string
22
+ pageTitle: string
23
+ tagName: string
24
+ text?: string
25
+ accessibleName?: string
26
+ strategy?: CompanionPickedLocatorStrategy
27
+ }
28
+
29
+ export interface CompanionSessionFile {
30
+ sessionId: string
31
+ status: CompanionSessionStatus
32
+ launchSource: CompanionLaunchSource
33
+ currentUrl: string
34
+ currentPathname: string
35
+ pageTitle: string
36
+ companionPid: number | null
37
+ crashLogPath?: string
38
+ pickedLocator?: CompanionPickedLocatorPayload
39
+ error?: string
40
+ startedAt: string
41
+ updatedAt: string
42
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "outDir": "dist",
6
+ "noEmit": false,
7
+ "declaration": true,
8
+ "emitDeclarationOnly": false,
9
+ "lib": ["dom", "esnext"],
10
+ "module": "NodeNext",
11
+ "moduleResolution": "NodeNext"
12
+ },
13
+ "include": ["src/**/*.ts"]
14
+ }