create-appraisejs 0.1.9 → 0.1.10-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 (159) hide show
  1. package/README.md +24 -17
  2. package/dist/cli.d.ts +2 -1
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.e2e.test.js +11 -8
  5. package/dist/cli.e2e.test.js.map +1 -1
  6. package/dist/cli.js +32 -48
  7. package/dist/cli.js.map +1 -1
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/config.js +5 -1
  10. package/dist/config.js.map +1 -1
  11. package/dist/config.test.js +9 -5
  12. package/dist/config.test.js.map +1 -1
  13. package/dist/copy-template.d.ts +1 -1
  14. package/dist/copy-template.d.ts.map +1 -1
  15. package/dist/copy-template.js +7 -3
  16. package/dist/copy-template.js.map +1 -1
  17. package/dist/copy-template.test.js +14 -9
  18. package/dist/copy-template.test.js.map +1 -1
  19. package/dist/create-project.d.ts +23 -0
  20. package/dist/create-project.d.ts.map +1 -0
  21. package/dist/create-project.js +58 -0
  22. package/dist/create-project.js.map +1 -0
  23. package/dist/create-project.test.d.ts +2 -0
  24. package/dist/create-project.test.d.ts.map +1 -0
  25. package/dist/create-project.test.js +80 -0
  26. package/dist/create-project.test.js.map +1 -0
  27. package/dist/install.d.ts +8 -4
  28. package/dist/install.d.ts.map +1 -1
  29. package/dist/install.js +22 -72
  30. package/dist/install.js.map +1 -1
  31. package/dist/install.test.js +26 -10
  32. package/dist/install.test.js.map +1 -1
  33. package/dist/package-manager.d.ts +11 -0
  34. package/dist/package-manager.d.ts.map +1 -0
  35. package/dist/package-manager.js +47 -0
  36. package/dist/package-manager.js.map +1 -0
  37. package/dist/package-manager.test.d.ts +2 -0
  38. package/dist/package-manager.test.d.ts.map +1 -0
  39. package/dist/package-manager.test.js +51 -0
  40. package/dist/package-manager.test.js.map +1 -0
  41. package/dist/prepare-template-utils.d.ts +10 -0
  42. package/dist/prepare-template-utils.d.ts.map +1 -0
  43. package/dist/prepare-template-utils.js +53 -0
  44. package/dist/prepare-template-utils.js.map +1 -0
  45. package/dist/prepare-template-utils.test.d.ts +2 -0
  46. package/dist/prepare-template-utils.test.d.ts.map +1 -0
  47. package/dist/prepare-template-utils.test.js +67 -0
  48. package/dist/prepare-template-utils.test.js.map +1 -0
  49. package/dist/prompts.d.ts +2 -0
  50. package/dist/prompts.d.ts.map +1 -1
  51. package/dist/prompts.js +11 -3
  52. package/dist/prompts.js.map +1 -1
  53. package/dist/prompts.test.js +17 -7
  54. package/dist/prompts.test.js.map +1 -1
  55. package/dist/template-sync-utils.test.d.ts +2 -0
  56. package/dist/template-sync-utils.test.d.ts.map +1 -0
  57. package/dist/template-sync-utils.test.js +41 -0
  58. package/dist/template-sync-utils.test.js.map +1 -0
  59. package/package.json +3 -2
  60. package/templates/default/.appraise-template-meta.json +5 -0
  61. package/templates/default/.env.example +1 -1
  62. package/templates/default/.vscode/settings.json +10 -3
  63. package/templates/default/README.md +27 -25
  64. package/templates/default/automation/features/base/login.feature +15 -0
  65. package/templates/default/automation/locators/base/home.json +3 -0
  66. package/templates/default/automation/locators/base/login.json +6 -0
  67. package/templates/default/automation/locators/base/test.json +1 -0
  68. package/templates/default/automation/mapping/locator-map.json +14 -0
  69. package/templates/default/{src/tests → automation}/steps/actions/click.step.ts +1 -4
  70. package/templates/default/{src/tests → automation}/steps/actions/hover.step.ts +1 -4
  71. package/templates/default/{src/tests → automation}/steps/actions/input.step.ts +1 -4
  72. package/templates/default/{src/tests → automation}/steps/actions/navigation.step.ts +1 -3
  73. package/templates/default/{src/tests → automation}/steps/actions/random_data.step.ts +1 -3
  74. package/templates/default/{src/tests → automation}/steps/actions/store.step.ts +1 -4
  75. package/templates/default/automation/steps/actions/wait.step.ts +91 -0
  76. package/templates/default/{src/tests → automation}/steps/validations/active_state_assertion.step.ts +1 -4
  77. package/templates/default/{src/tests → automation}/steps/validations/navigation_assertion.step.ts +1 -2
  78. package/templates/default/{src/tests → automation}/steps/validations/text_assertion.step.ts +1 -4
  79. package/templates/default/{src/tests → automation}/steps/validations/visibility_assertion.step.ts +1 -4
  80. package/templates/default/cucumber.mjs +6 -6
  81. package/templates/default/eslint.config.mjs +5 -4
  82. package/templates/default/package-lock.json +322 -485
  83. package/templates/default/package.json +12 -6
  84. package/templates/default/packages/cucumber-runtime/package.json +13 -0
  85. package/templates/default/packages/cucumber-runtime/src/cache.util.ts +93 -0
  86. package/templates/default/packages/cucumber-runtime/src/cli.ts +68 -0
  87. package/templates/default/packages/cucumber-runtime/src/environment.util.ts +21 -0
  88. package/templates/default/packages/cucumber-runtime/src/executor.ts +32 -0
  89. package/templates/default/{src/tests/hooks → packages/cucumber-runtime/src}/hooks.ts +17 -32
  90. package/templates/default/packages/cucumber-runtime/src/index.ts +17 -0
  91. package/templates/default/{src/tests/utils → packages/cucumber-runtime/src}/locator.util.ts +50 -64
  92. package/templates/default/packages/cucumber-runtime/src/parameter-types.ts +7 -0
  93. package/templates/default/packages/cucumber-runtime/src/paths.ts +33 -0
  94. package/templates/default/packages/cucumber-runtime/src/random-data.util.ts +35 -0
  95. package/templates/default/packages/cucumber-runtime/src/types.ts +13 -0
  96. package/templates/default/{src/tests/config/executor → packages/cucumber-runtime/src}/world.ts +4 -1
  97. package/templates/default/packages/cucumber-runtime/tsconfig.json +11 -0
  98. package/templates/default/scripts/setup-env.ts +4 -4
  99. package/templates/default/scripts/sync-appraise-base-template.ts +124 -105
  100. package/templates/default/scripts/sync-environments.ts +8 -5
  101. package/templates/default/scripts/sync-locator-groups.ts +7 -10
  102. package/templates/default/scripts/sync-locators.ts +5 -9
  103. package/templates/default/scripts/sync-modules.ts +9 -17
  104. package/templates/default/scripts/sync-tags.ts +2 -2
  105. package/templates/default/scripts/sync-template-step-groups.ts +16 -6
  106. package/templates/default/scripts/sync-template-steps.ts +16 -5
  107. package/templates/default/scripts/sync-test-cases.ts +6 -3
  108. package/templates/default/scripts/sync-test-suites.ts +7 -4
  109. package/templates/default/src/actions/environments/environment-actions.ts +6 -23
  110. package/templates/default/src/actions/locator/locator-actions.ts +36 -93
  111. package/templates/default/src/actions/locator-groups/locator-group-actions.ts +24 -78
  112. package/templates/default/src/actions/modules/module-actions.ts +4 -2
  113. package/templates/default/src/actions/tags/tag-actions.ts +4 -1
  114. package/templates/default/src/actions/template-step/template-step-actions.ts +10 -101
  115. package/templates/default/src/actions/template-step-group/template-step-group-actions.ts +31 -130
  116. package/templates/default/src/actions/test-case/test-case-actions.ts +31 -94
  117. package/templates/default/src/actions/test-run/test-run-actions.ts +11 -13
  118. package/templates/default/src/actions/test-suite/test-suite-actions.ts +29 -82
  119. package/templates/default/src/app/(base)/locator-groups/page.tsx +1 -3
  120. package/templates/default/src/app/(base)/reports/page.tsx +1 -1
  121. package/templates/default/src/app/(base)/reports/test-cases/page.tsx +2 -2
  122. package/templates/default/src/app/(base)/reports/test-cases/test-cases-metric-table-columns.tsx +1 -1
  123. package/templates/default/src/app/(base)/tags/page.tsx +2 -2
  124. package/templates/default/src/app/(base)/template-steps/page.tsx +1 -2
  125. package/templates/default/src/app/(base)/test-runs/page.tsx +2 -2
  126. package/templates/default/src/app/api/test-runs/[runId]/logs/route.ts +2 -1
  127. package/templates/default/src/app/api/test-runs/[runId]/trace/[testCaseId]/route.ts +2 -1
  128. package/templates/default/src/app/page.tsx +4 -5
  129. package/templates/default/src/components/diagram/dynamic-parameters.tsx +76 -40
  130. package/templates/default/src/components/diagram/options-header-node.tsx +1 -1
  131. package/templates/default/src/components/ui/data-table.tsx +33 -39
  132. package/templates/default/src/lib/automation/paths.ts +181 -0
  133. package/templates/default/src/lib/automation/projection-service.ts +230 -0
  134. package/templates/default/src/lib/environment-file-utils.ts +14 -51
  135. package/templates/default/src/lib/executor/local-executor-adapter.ts +101 -0
  136. package/templates/default/src/lib/executor/types.ts +24 -0
  137. package/templates/default/src/lib/feature-file-generator.ts +22 -112
  138. package/templates/default/src/lib/locator-group-file-utils.ts +57 -120
  139. package/templates/default/src/lib/process/task-spawner.ts +236 -0
  140. package/templates/default/src/lib/template-sync-utils.d.ts +7 -0
  141. package/templates/default/src/lib/template-sync-utils.d.ts.map +1 -0
  142. package/templates/default/src/lib/template-sync-utils.js +47 -0
  143. package/templates/default/src/lib/template-sync-utils.js.map +1 -0
  144. package/templates/default/src/lib/template-sync-utils.ts +63 -0
  145. package/templates/default/src/lib/test-run/process-manager.ts +9 -87
  146. package/templates/default/src/lib/test-run/test-run-executor.ts +7 -136
  147. package/templates/default/src/lib/test-run/winston-logger.ts +6 -35
  148. package/templates/default/src/lib/utils/template-step-file-generator.ts +22 -85
  149. package/templates/default/src/lib/utils/template-step-file-manager-intelligent.ts +7 -22
  150. package/templates/default/public/favicon.ico +0 -0
  151. package/templates/default/src/tests/executor.ts +0 -80
  152. package/templates/default/src/tests/mapping/locator-map.json +0 -1
  153. package/templates/default/src/tests/steps/actions/wait.step.ts +0 -107
  154. package/templates/default/src/tests/support/parameter-types.ts +0 -12
  155. package/templates/default/src/tests/utils/cache.util.ts +0 -260
  156. package/templates/default/src/tests/utils/cli.util.ts +0 -177
  157. package/templates/default/src/tests/utils/environment.util.ts +0 -65
  158. package/templates/default/src/tests/utils/random-data.util.ts +0 -45
  159. package/templates/default/src/tests/utils/spawner.util.ts +0 -617
@@ -49,13 +49,21 @@ interface SyncResult {
49
49
  function parseGroupJSDoc(content: string): StepGroupJSDoc | null {
50
50
  const lines = content.split('\n')
51
51
 
52
- // Look for JSDoc comment at the very top of the file
53
52
  if (lines.length === 0) {
54
53
  return null
55
54
  }
56
55
 
57
- const firstLine = lines[0].trim()
58
- if (!firstLine.startsWith('/**')) {
56
+ let startLine = 0
57
+ while (startLine < lines.length) {
58
+ const line = lines[startLine].trim()
59
+ if (line === '' || line.startsWith('import ')) {
60
+ startLine++
61
+ continue
62
+ }
63
+ break
64
+ }
65
+
66
+ if (startLine >= lines.length || !lines[startLine].trim().startsWith('/**')) {
59
67
  return null
60
68
  }
61
69
 
@@ -68,8 +76,8 @@ function parseGroupJSDoc(content: string): StepGroupJSDoc | null {
68
76
 
69
77
  // Look through the JSDoc block until we find the closing */
70
78
  // Use a reasonable limit (50 lines) to avoid parsing entire files if JSDoc is malformed
71
- const maxLines = Math.min(lines.length, 50)
72
- for (let i = 0; i < maxLines; i++) {
79
+ const maxLines = Math.min(lines.length, startLine + 50)
80
+ for (let i = startLine; i < maxLines; i++) {
73
81
  const line = lines[i].trim()
74
82
 
75
83
  // Check if this line contains the closing */ (could be on same line as content)
@@ -148,7 +156,7 @@ async function scanStepFiles(baseDir: string): Promise<string[]> {
148
156
 
149
157
  try {
150
158
  // Get all .step.ts files in actions and validations directories
151
- const patterns = ['src/tests/steps/actions/**/*.step.ts', 'src/tests/steps/validations/**/*.step.ts']
159
+ const patterns = ['automation/steps/actions/**/*.step.ts', 'automation/steps/validations/**/*.step.ts']
152
160
 
153
161
  for (const pattern of patterns) {
154
162
  const files = await glob(pattern, {
@@ -397,3 +405,5 @@ async function main() {
397
405
  }
398
406
 
399
407
  main()
408
+
409
+
@@ -77,8 +77,17 @@ function parseGroupJSDoc(content: string): StepGroupJSDoc | null {
77
77
  return null
78
78
  }
79
79
 
80
- const firstLine = lines[0].trim()
81
- if (!firstLine.startsWith('/**')) {
80
+ let startLine = 0
81
+ while (startLine < lines.length) {
82
+ const line = lines[startLine].trim()
83
+ if (line === '' || line.startsWith('import ')) {
84
+ startLine++
85
+ continue
86
+ }
87
+ break
88
+ }
89
+
90
+ if (startLine >= lines.length || !lines[startLine].trim().startsWith('/**')) {
82
91
  return null
83
92
  }
84
93
 
@@ -88,8 +97,8 @@ function parseGroupJSDoc(content: string): StepGroupJSDoc | null {
88
97
  let description: string | null = null
89
98
  let type: string | null = null
90
99
 
91
- const maxLines = Math.min(lines.length, 50)
92
- for (let i = 0; i < maxLines; i++) {
100
+ const maxLines = Math.min(lines.length, startLine + 50)
101
+ for (let i = startLine; i < maxLines; i++) {
93
102
  const line = lines[i].trim()
94
103
 
95
104
  if (line.includes('*/')) {
@@ -455,7 +464,7 @@ function parseStepFile(content: string, filePath: string): StepData | null {
455
464
  * Scans step definition files
456
465
  */
457
466
  async function scanStepFiles(baseDir: string): Promise<string[]> {
458
- const patterns = ['src/tests/steps/actions/**/*.step.ts', 'src/tests/steps/validations/**/*.step.ts']
467
+ const patterns = ['automation/steps/actions/**/*.step.ts', 'automation/steps/validations/**/*.step.ts']
459
468
  const stepFiles: string[] = []
460
469
 
461
470
  for (const pattern of patterns) {
@@ -804,3 +813,5 @@ async function main() {
804
813
  }
805
814
 
806
815
  main()
816
+
817
+
@@ -9,7 +9,6 @@
9
9
  * Usage: npx tsx scripts/sync-test-cases.ts
10
10
  */
11
11
 
12
- import { join } from 'path'
13
12
  import prisma from '../src/config/db-config'
14
13
  import {
15
14
  scanFeatureFiles,
@@ -18,6 +17,7 @@ import {
18
17
  } from '../src/lib/gherkin-parser'
19
18
  import { buildModuleHierarchy, findModuleByPath } from '../src/lib/module-hierarchy-builder'
20
19
  import { TemplateStepType, TemplateStepIcon, StepParameterType, TagType } from '@prisma/client'
20
+ import { ensureAutomationWorkspaceReady, getAutomationFeaturesDir } from '../src/lib/automation/paths'
21
21
 
22
22
  interface TestCaseFromFS {
23
23
  identifierTag: string // @tc_... tag
@@ -852,8 +852,8 @@ async function main() {
852
852
  console.log('This will scan feature files and sync test cases to database.')
853
853
  console.log('Filesystem is the source of truth - test cases in DB but not in FS will be deleted.\n')
854
854
 
855
- const baseDir = process.cwd()
856
- const featuresDir = join(baseDir, 'src', 'tests', 'features')
855
+ await ensureAutomationWorkspaceReady()
856
+ const featuresDir = getAutomationFeaturesDir()
857
857
 
858
858
  // Scan test cases from filesystem
859
859
  const testCasesFromFS = await scanTestCasesFromFilesystem(featuresDir)
@@ -903,3 +903,6 @@ async function main() {
903
903
  }
904
904
 
905
905
  main()
906
+
907
+
908
+
@@ -9,10 +9,10 @@
9
9
  * Usage: npx tsx scripts/sync-test-suites.ts
10
10
  */
11
11
 
12
- import { join } from 'path'
13
12
  import prisma from '../src/config/db-config'
14
13
  import { scanFeatureFiles, extractModulePathFromFilePath, ParsedFeature } from '../src/lib/gherkin-parser'
15
14
  import { buildModuleHierarchy, findModuleByPath, getAllModulesWithPaths } from '../src/lib/module-hierarchy-builder'
15
+ import { ensureAutomationWorkspaceReady, getAutomationFeaturesDir } from '../src/lib/automation/paths'
16
16
 
17
17
  interface TestSuiteFromFS {
18
18
  name: string // From filename (without .feature extension)
@@ -358,9 +358,9 @@ async function main() {
358
358
  console.log('This will scan feature files and sync test suites to database.')
359
359
  console.log('Filesystem is the source of truth - test suites in DB but not in FS will be deleted.')
360
360
  console.log('Note: Test cases are not synced by this script (they will be handled separately).\n')
361
-
362
- const baseDir = process.cwd()
363
- const featuresDir = join(baseDir, 'src', 'tests', 'features')
361
+
362
+ await ensureAutomationWorkspaceReady()
363
+ const featuresDir = getAutomationFeaturesDir()
364
364
 
365
365
  // Scan test suites from filesystem
366
366
  const testSuitesFromFS = await scanTestSuitesFromFilesystem(featuresDir)
@@ -409,3 +409,6 @@ async function main() {
409
409
  }
410
410
 
411
411
  main()
412
+
413
+
414
+
@@ -2,18 +2,15 @@
2
2
 
3
3
  import prisma from '@/config/db-config'
4
4
  import { environmentSchema } from '@/constants/form-opts/environment-form-opts'
5
+ import { automationProjectionService } from '@/lib/automation/projection-service'
5
6
  import { ActionResponse } from '@/types/form/actionHandler'
6
7
  import { revalidatePath } from 'next/cache'
7
8
  import { z } from 'zod'
8
- import { createOrUpdateEnvironmentsFile, updateEnvironmentEntry } from '@/lib/environment-file-utils'
9
9
 
10
- /**
11
- * Check if an environment name already exists
12
- */
13
10
  async function checkUniqueName(name: string, excludeId?: string): Promise<boolean> {
14
11
  const existing = await prisma.environment.findFirst({
15
12
  where: {
16
- name: name,
13
+ name,
17
14
  ...(excludeId && { id: { not: excludeId } }),
18
15
  },
19
16
  })
@@ -28,8 +25,7 @@ export async function getAllEnvironmentsAction(): Promise<ActionResponse> {
28
25
  },
29
26
  })
30
27
 
31
- // Update the environments.json file
32
- await createOrUpdateEnvironmentsFile()
28
+ await automationProjectionService.syncEnvironments()
33
29
 
34
30
  return {
35
31
  status: 200,
@@ -51,8 +47,7 @@ export async function deleteEnvironmentAction(ids: string[]): Promise<ActionResp
51
47
  },
52
48
  })
53
49
 
54
- // Update the environments.json file
55
- await createOrUpdateEnvironmentsFile()
50
+ await automationProjectionService.syncEnvironments()
56
51
 
57
52
  revalidatePath('/environments')
58
53
  return {
@@ -74,7 +69,6 @@ export async function createEnvironmentAction(
74
69
  try {
75
70
  environmentSchema.parse(value)
76
71
 
77
- // Check if name already exists
78
72
  const nameExists = await checkUniqueName(value.name)
79
73
  if (nameExists) {
80
74
  return {
@@ -83,7 +77,6 @@ export async function createEnvironmentAction(
83
77
  }
84
78
  }
85
79
 
86
- // Convert empty strings to null for optional fields
87
80
  const environmentData = {
88
81
  ...value,
89
82
  apiBaseUrl: value.apiBaseUrl === '' ? null : value.apiBaseUrl,
@@ -95,8 +88,7 @@ export async function createEnvironmentAction(
95
88
  data: environmentData,
96
89
  })
97
90
 
98
- // Update the environments.json file
99
- await createOrUpdateEnvironmentsFile()
91
+ await automationProjectionService.syncEnvironments()
100
92
 
101
93
  revalidatePath('/environments')
102
94
  return {
@@ -137,13 +129,11 @@ export async function updateEnvironmentAction(
137
129
  try {
138
130
  environmentSchema.parse(value)
139
131
 
140
- // Get the current environment to check if name changed
141
132
  const currentEnvironment = await prisma.environment.findUnique({
142
133
  where: { id },
143
134
  select: { name: true },
144
135
  })
145
136
 
146
- // Check if name is being changed and if the new name already exists
147
137
  if (currentEnvironment?.name !== value.name) {
148
138
  const nameExists = await checkUniqueName(value.name, id)
149
139
  if (nameExists) {
@@ -154,7 +144,6 @@ export async function updateEnvironmentAction(
154
144
  }
155
145
  }
156
146
 
157
- // Convert empty strings to null for optional fields
158
147
  const environmentData = {
159
148
  ...value,
160
149
  apiBaseUrl: value.apiBaseUrl === '' ? null : value.apiBaseUrl,
@@ -167,10 +156,7 @@ export async function updateEnvironmentAction(
167
156
  data: environmentData,
168
157
  })
169
158
 
170
- // Update the environments.json file
171
- // If name changed, we need to remove the old entry and add the new one
172
- const oldName = currentEnvironment?.name !== value.name ? currentEnvironment?.name : undefined
173
- await updateEnvironmentEntry(id!, oldName)
159
+ await automationProjectionService.syncEnvironments()
174
160
 
175
161
  revalidatePath('/environments')
176
162
  return {
@@ -186,9 +172,6 @@ export async function updateEnvironmentAction(
186
172
  }
187
173
  }
188
174
 
189
- /**
190
- * Check if an environment name is unique
191
- */
192
175
  export async function checkEnvironmentNameUniqueAction(name: string, excludeId?: string): Promise<ActionResponse> {
193
176
  try {
194
177
  const nameExists = await checkUniqueName(name, excludeId)
@@ -2,21 +2,24 @@
2
2
 
3
3
  import prisma from '@/config/db-config'
4
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'
5
9
  import { ActionResponse } from '@/types/form/actionHandler'
6
10
  import { revalidatePath } from 'next/cache'
7
11
  import { z } from 'zod'
8
- import { createOrUpdateLocatorGroupFile, getLocatorGroupFilePath } from '@/lib/locator-group-file-utils'
9
12
  import { promises as fs } from 'fs'
10
13
  import path from 'path'
11
14
  import { glob } from 'glob'
12
- import { buildModuleHierarchy } from '@/lib/module-hierarchy-builder'
13
15
 
14
- // Helper function to update locator group JSON file when locators change
15
16
  async function updateLocatorGroupFile(locatorGroupId: string | null): Promise<void> {
16
- if (!locatorGroupId) return
17
+ if (!locatorGroupId) {
18
+ return
19
+ }
17
20
 
18
21
  try {
19
- await createOrUpdateLocatorGroupFile(locatorGroupId)
22
+ await automationProjectionService.syncLocatorGroup(locatorGroupId)
20
23
  } catch (error) {
21
24
  console.error('Error updating locator group file:', error)
22
25
  }
@@ -52,20 +55,18 @@ export async function getAllLocatorsAction(): Promise<ActionResponse> {
52
55
 
53
56
  export async function deleteLocatorAction(ids: string[]): Promise<ActionResponse> {
54
57
  try {
55
- // Get locator group IDs before deletion to update files
56
58
  const locatorsToDelete = await prisma.locator.findMany({
57
59
  where: { id: { in: ids } },
58
60
  select: { locatorGroupId: true },
59
61
  })
60
62
 
61
- const locatorGroupIds = [...new Set(locatorsToDelete.map(l => l.locatorGroupId).filter(Boolean))]
63
+ const locatorGroupIds = [...new Set(locatorsToDelete.map(locator => locator.locatorGroupId).filter(Boolean))]
62
64
 
63
65
  const locator = await prisma.locator.deleteMany({
64
66
  where: { id: { in: ids } },
65
67
  })
66
68
 
67
- // Update JSON files for affected locator groups in parallel
68
- await Promise.all(locatorGroupIds.filter(Boolean).map(groupId => updateLocatorGroupFile(groupId)))
69
+ await Promise.all(locatorGroupIds.map(groupId => updateLocatorGroupFile(groupId)))
69
70
 
70
71
  revalidatePath('/locators')
71
72
  return {
@@ -102,7 +103,6 @@ export async function createLocatorAction(
102
103
  },
103
104
  })
104
105
 
105
- // Update the locator group JSON file
106
106
  if (value.locatorGroupId) {
107
107
  await updateLocatorGroupFile(value.locatorGroupId)
108
108
  }
@@ -113,10 +113,10 @@ export async function createLocatorAction(
113
113
  data: newLocator,
114
114
  message: 'Locator created successfully',
115
115
  }
116
- } catch (e) {
116
+ } catch (error) {
117
117
  return {
118
118
  status: 500,
119
- error: `Server error occurred: ${e}`,
119
+ error: `Server error occurred: ${error}`,
120
120
  }
121
121
  }
122
122
  }
@@ -127,7 +127,6 @@ export async function updateLocatorAction(
127
127
  id?: string,
128
128
  ): Promise<ActionResponse> {
129
129
  try {
130
- // Get the current locator to check if locatorGroupId changed
131
130
  const currentLocator = await prisma.locator.findUnique({
132
131
  where: { id },
133
132
  select: { locatorGroupId: true },
@@ -149,11 +148,9 @@ export async function updateLocatorAction(
149
148
  },
150
149
  })
151
150
 
152
- // Update JSON files for affected locator groups
153
151
  const groupsToUpdate = new Set<string>()
154
152
 
155
153
  if (currentLocator?.locatorGroupId !== value.locatorGroupId) {
156
- // Group changed - update both old and new groups
157
154
  if (currentLocator?.locatorGroupId) {
158
155
  groupsToUpdate.add(currentLocator.locatorGroupId)
159
156
  }
@@ -161,11 +158,9 @@ export async function updateLocatorAction(
161
158
  groupsToUpdate.add(value.locatorGroupId)
162
159
  }
163
160
  } else if (value.locatorGroupId) {
164
- // Same group - just update the current group
165
161
  groupsToUpdate.add(value.locatorGroupId)
166
162
  }
167
163
 
168
- // Update all affected groups in parallel
169
164
  await Promise.all(Array.from(groupsToUpdate).map(groupId => updateLocatorGroupFile(groupId)))
170
165
 
171
166
  revalidatePath('/locators')
@@ -174,10 +169,10 @@ export async function updateLocatorAction(
174
169
  data: updatedLocator,
175
170
  message: 'Locator updated successfully',
176
171
  }
177
- } catch (e) {
172
+ } catch (error) {
178
173
  return {
179
174
  status: 500,
180
- error: `Server error occurred: ${e}`,
175
+ error: `Server error occurred: ${error}`,
181
176
  }
182
177
  }
183
178
  }
@@ -198,10 +193,10 @@ export async function getLocatorByIdAction(id: string): Promise<ActionResponse>
198
193
  status: 200,
199
194
  data: locator,
200
195
  }
201
- } catch (e) {
196
+ } catch (error) {
202
197
  return {
203
198
  status: 500,
204
- error: `Server error occurred: ${e}`,
199
+ error: `Server error occurred: ${error}`,
205
200
  }
206
201
  }
207
202
  }
@@ -230,29 +225,18 @@ export async function getUngroupedLocatorsAction(): Promise<ActionResponse> {
230
225
  }
231
226
  }
232
227
 
233
- /**
234
- * Extracts module path from locator file path
235
- */
236
228
  function extractModulePathFromLocatorFile(filePath: string): string {
237
- const testsDir = path.join(process.cwd(), 'src', 'tests')
238
- const relativePath = path.relative(testsDir, filePath)
239
- const pathParts = relativePath.split(/[/\\]/).filter(p => p && p !== 'locators')
240
- const moduleParts = pathParts.slice(0, -1) // Remove filename
241
- return moduleParts.length > 0 ? '/' + moduleParts.join('/') : '/'
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('/')}` : '/'
242
234
  }
243
235
 
244
- /**
245
- * Extracts locator group name from file path
246
- * The group name is just the filename without extension
247
- */
248
236
  function extractLocatorGroupName(filePath: string): string {
249
- const fileName = path.basename(filePath, '.json')
250
- return fileName
237
+ return path.basename(filePath, '.json')
251
238
  }
252
239
 
253
- /**
254
- * Detects and creates conflict resolution entries for locators
255
- */
256
240
  async function detectAndCreateConflicts(
257
241
  locatorId: string,
258
242
  locatorName: string,
@@ -261,7 +245,6 @@ async function detectAndCreateConflicts(
261
245
  ): Promise<number> {
262
246
  let conflictCount = 0
263
247
 
264
- // Find existing locators in the same group
265
248
  const existingLocators = await prisma.locator.findMany({
266
249
  where: {
267
250
  locatorGroupId,
@@ -270,9 +253,7 @@ async function detectAndCreateConflicts(
270
253
  })
271
254
 
272
255
  for (const existingLocator of existingLocators) {
273
- // Check for duplicate name conflict
274
256
  if (existingLocator.name === locatorName) {
275
- // Check if conflict already exists for this locator
276
257
  const existingConflict = await prisma.conflictResolution.findFirst({
277
258
  where: {
278
259
  entityType: 'LOCATOR',
@@ -284,7 +265,6 @@ async function detectAndCreateConflicts(
284
265
  })
285
266
 
286
267
  if (!existingConflict) {
287
- // Create conflict for both locators
288
268
  await prisma.conflictResolution.create({
289
269
  data: {
290
270
  entityType: 'LOCATOR',
@@ -305,10 +285,7 @@ async function detectAndCreateConflicts(
305
285
  })
306
286
  conflictCount++
307
287
  }
308
- }
309
- // Check for duplicate value conflict (different name)
310
- else if (existingLocator.value === locatorValue && existingLocator.name !== locatorName) {
311
- // Check if conflict already exists for this locator
288
+ } else if (existingLocator.value === locatorValue && existingLocator.name !== locatorName) {
312
289
  const existingConflict = await prisma.conflictResolution.findFirst({
313
290
  where: {
314
291
  entityType: 'LOCATOR',
@@ -320,7 +297,6 @@ async function detectAndCreateConflicts(
320
297
  })
321
298
 
322
299
  if (!existingConflict) {
323
- // Create conflict for both locators
324
300
  await prisma.conflictResolution.create({
325
301
  data: {
326
302
  entityType: 'LOCATOR',
@@ -347,18 +323,12 @@ async function detectAndCreateConflicts(
347
323
  return conflictCount
348
324
  }
349
325
 
350
- /**
351
- * Syncs locators from JSON files to database
352
- */
353
326
  export async function syncLocatorsFromFilesAction(): Promise<ActionResponse> {
354
327
  try {
355
- // Use relative pattern with forward slashes for cross-platform compatibility
356
- // glob library requires forward slashes in patterns on all platforms
357
- const pattern = 'src/tests/locators/**/*.json'
328
+ const pattern = 'automation/locators/**/*.json'
358
329
  const relativeFiles = await glob(pattern, {
359
330
  cwd: process.cwd(),
360
331
  })
361
- // Resolve to absolute paths for file operations
362
332
  const files = relativeFiles.map(file => path.resolve(process.cwd(), file))
363
333
 
364
334
  let synced = 0
@@ -368,20 +338,17 @@ export async function syncLocatorsFromFilesAction(): Promise<ActionResponse> {
368
338
 
369
339
  for (const filePath of files) {
370
340
  try {
371
- // Read locator file
372
341
  const content = await fs.readFile(filePath, 'utf-8')
373
342
  const locators = JSON.parse(content) as Record<string, string>
374
343
 
375
- // Extract module path and group name
376
344
  const modulePath = extractModulePathFromLocatorFile(filePath)
377
345
  const moduleId = await buildModuleHierarchy(modulePath)
378
346
  const groupName = extractLocatorGroupName(filePath)
379
347
 
380
- // Find or create locator group
381
348
  let locatorGroup = await prisma.locatorGroup.findFirst({
382
349
  where: {
383
350
  name: groupName,
384
- moduleId: moduleId,
351
+ moduleId,
385
352
  },
386
353
  })
387
354
 
@@ -390,17 +357,14 @@ export async function syncLocatorsFromFilesAction(): Promise<ActionResponse> {
390
357
  data: {
391
358
  name: groupName,
392
359
  route: `/${groupName}`,
393
- moduleId: moduleId,
360
+ moduleId,
394
361
  },
395
362
  })
396
363
  }
397
364
 
398
- // Track this locator group as affected
399
365
  affectedLocatorGroupIds.add(locatorGroup.id)
400
366
 
401
- // Sync locators FROM FILE TO DATABASE
402
367
  for (const [locatorName, locatorValue] of Object.entries(locators)) {
403
- // Check if locator already exists
404
368
  const existingLocator = await prisma.locator.findFirst({
405
369
  where: {
406
370
  name: locatorName,
@@ -411,7 +375,6 @@ export async function syncLocatorsFromFilesAction(): Promise<ActionResponse> {
411
375
  let locatorId: string
412
376
 
413
377
  if (existingLocator) {
414
- // Update existing locator value if different (file takes precedence)
415
378
  if (existingLocator.value !== locatorValue) {
416
379
  await prisma.locator.update({
417
380
  where: { id: existingLocator.id },
@@ -420,7 +383,6 @@ export async function syncLocatorsFromFilesAction(): Promise<ActionResponse> {
420
383
  }
421
384
  locatorId = existingLocator.id
422
385
  } else {
423
- // Create new locator from file
424
386
  const newLocator = await prisma.locator.create({
425
387
  data: {
426
388
  name: locatorName,
@@ -432,29 +394,22 @@ export async function syncLocatorsFromFilesAction(): Promise<ActionResponse> {
432
394
  synced++
433
395
  }
434
396
 
435
- // Detect and create conflicts
436
- const conflictCount = await detectAndCreateConflicts(locatorId, locatorName, locatorValue, locatorGroup.id)
437
- totalConflicts += conflictCount
397
+ totalConflicts += await detectAndCreateConflicts(locatorId, locatorName, locatorValue, locatorGroup.id)
438
398
  }
439
399
 
440
- // Sync locators FROM DATABASE TO FILE (bidirectional sync)
441
- // Get all locators from database for this group
442
400
  const dbLocators = await prisma.locator.findMany({
443
401
  where: { locatorGroupId: locatorGroup.id },
444
402
  select: { name: true, value: true },
445
403
  })
446
404
 
447
- // Merge: file values take precedence, but add DB locators that aren't in file
448
405
  const mergedLocators: Record<string, string> = { ...locators }
449
406
  for (const dbLocator of dbLocators) {
450
- // Only add if not already in file (file values take precedence)
451
407
  if (!(dbLocator.name in mergedLocators)) {
452
408
  mergedLocators[dbLocator.name] = dbLocator.value
453
409
  synced++
454
410
  }
455
411
  }
456
412
 
457
- // Write merged content back to file
458
413
  await fs.writeFile(filePath, JSON.stringify(mergedLocators, null, 2) + '\n', 'utf-8')
459
414
  } catch (error) {
460
415
  const errorMessage = `Error syncing locator file ${filePath}: ${error}`
@@ -463,57 +418,45 @@ export async function syncLocatorsFromFilesAction(): Promise<ActionResponse> {
463
418
  }
464
419
  }
465
420
 
466
- // Handle locator groups that exist in DB but don't have files yet
467
- // Create files for these groups with their DB locators
468
421
  try {
469
422
  const allLocatorGroups = await prisma.locatorGroup.findMany({
470
423
  include: {
471
424
  locators: {
472
425
  select: { name: true, value: true },
473
426
  },
474
- module: true,
475
427
  },
476
428
  })
477
429
 
478
430
  for (const locatorGroup of allLocatorGroups) {
479
- // Skip if we already processed this group (it has a file)
480
431
  if (affectedLocatorGroupIds.has(locatorGroup.id)) {
481
432
  continue
482
433
  }
483
434
 
484
435
  try {
485
- // Get the file path for this locator group
486
- const relativeFilePath = await getLocatorGroupFilePath(locatorGroup.id)
487
- if (!relativeFilePath) {
488
- // If we can't determine the path, skip this group
436
+ const filePath = await getLocatorGroupFilePath(locatorGroup.id)
437
+ if (!filePath) {
489
438
  continue
490
439
  }
491
440
 
492
- const fullPath = path.join(process.cwd(), relativeFilePath)
493
-
494
- // Check if file exists
495
441
  try {
496
- await fs.access(fullPath)
497
- // File exists, merge DB locators into it
498
- const fileContent = await fs.readFile(fullPath, 'utf-8')
442
+ await fs.access(filePath)
443
+ const fileContent = await fs.readFile(filePath, 'utf-8')
499
444
  const fileLocators = JSON.parse(fileContent) as Record<string, string>
500
-
501
- // Merge: file values take precedence, but add DB locators that aren't in file
502
445
  const mergedLocators: Record<string, string> = { ...fileLocators }
446
+
503
447
  for (const dbLocator of locatorGroup.locators) {
504
448
  if (!(dbLocator.name in mergedLocators)) {
505
449
  mergedLocators[dbLocator.name] = dbLocator.value
506
450
  }
507
451
  }
508
452
 
509
- await fs.writeFile(fullPath, JSON.stringify(mergedLocators, null, 2) + '\n', 'utf-8')
453
+ await fs.writeFile(filePath, JSON.stringify(mergedLocators, null, 2) + '\n', 'utf-8')
510
454
  } catch {
511
- // File doesn't exist, create it with DB locators
512
- await fs.mkdir(path.dirname(fullPath), { recursive: true })
455
+ await fs.mkdir(path.dirname(filePath), { recursive: true })
513
456
  const dbLocators: Record<string, string> = Object.fromEntries(
514
- locatorGroup.locators.map(loc => [loc.name, loc.value]),
457
+ locatorGroup.locators.map(locator => [locator.name, locator.value]),
515
458
  )
516
- await fs.writeFile(fullPath, JSON.stringify(dbLocators, null, 2) + '\n', 'utf-8')
459
+ await fs.writeFile(filePath, JSON.stringify(dbLocators, null, 2) + '\n', 'utf-8')
517
460
  }
518
461
  } catch (error) {
519
462
  const errorMessage = `Error syncing locator group ${locatorGroup.id} to file: ${error}`