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.
- package/README.md +24 -17
- package/dist/cli.d.ts +2 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.e2e.test.js +11 -8
- package/dist/cli.e2e.test.js.map +1 -1
- package/dist/cli.js +32 -48
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +5 -1
- package/dist/config.js.map +1 -1
- package/dist/config.test.js +9 -5
- package/dist/config.test.js.map +1 -1
- package/dist/copy-template.d.ts +1 -1
- package/dist/copy-template.d.ts.map +1 -1
- package/dist/copy-template.js +7 -3
- package/dist/copy-template.js.map +1 -1
- package/dist/copy-template.test.js +14 -9
- package/dist/copy-template.test.js.map +1 -1
- package/dist/create-project.d.ts +23 -0
- package/dist/create-project.d.ts.map +1 -0
- package/dist/create-project.js +58 -0
- package/dist/create-project.js.map +1 -0
- package/dist/create-project.test.d.ts +2 -0
- package/dist/create-project.test.d.ts.map +1 -0
- package/dist/create-project.test.js +80 -0
- package/dist/create-project.test.js.map +1 -0
- package/dist/install.d.ts +8 -4
- package/dist/install.d.ts.map +1 -1
- package/dist/install.js +22 -72
- package/dist/install.js.map +1 -1
- package/dist/install.test.js +26 -10
- package/dist/install.test.js.map +1 -1
- package/dist/package-manager.d.ts +11 -0
- package/dist/package-manager.d.ts.map +1 -0
- package/dist/package-manager.js +47 -0
- package/dist/package-manager.js.map +1 -0
- package/dist/package-manager.test.d.ts +2 -0
- package/dist/package-manager.test.d.ts.map +1 -0
- package/dist/package-manager.test.js +51 -0
- package/dist/package-manager.test.js.map +1 -0
- package/dist/prepare-template-utils.d.ts +10 -0
- package/dist/prepare-template-utils.d.ts.map +1 -0
- package/dist/prepare-template-utils.js +53 -0
- package/dist/prepare-template-utils.js.map +1 -0
- package/dist/prepare-template-utils.test.d.ts +2 -0
- package/dist/prepare-template-utils.test.d.ts.map +1 -0
- package/dist/prepare-template-utils.test.js +67 -0
- package/dist/prepare-template-utils.test.js.map +1 -0
- package/dist/prompts.d.ts +2 -0
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +11 -3
- package/dist/prompts.js.map +1 -1
- package/dist/prompts.test.js +17 -7
- package/dist/prompts.test.js.map +1 -1
- package/dist/template-sync-utils.test.d.ts +2 -0
- package/dist/template-sync-utils.test.d.ts.map +1 -0
- package/dist/template-sync-utils.test.js +41 -0
- package/dist/template-sync-utils.test.js.map +1 -0
- package/package.json +3 -2
- package/templates/default/.appraise-template-meta.json +5 -0
- package/templates/default/.env.example +1 -1
- package/templates/default/.vscode/settings.json +10 -3
- package/templates/default/README.md +27 -25
- package/templates/default/automation/features/base/login.feature +15 -0
- package/templates/default/automation/locators/base/home.json +3 -0
- package/templates/default/automation/locators/base/login.json +6 -0
- package/templates/default/automation/locators/base/test.json +1 -0
- package/templates/default/automation/mapping/locator-map.json +14 -0
- package/templates/default/{src/tests → automation}/steps/actions/click.step.ts +1 -4
- package/templates/default/{src/tests → automation}/steps/actions/hover.step.ts +1 -4
- package/templates/default/{src/tests → automation}/steps/actions/input.step.ts +1 -4
- package/templates/default/{src/tests → automation}/steps/actions/navigation.step.ts +1 -3
- package/templates/default/{src/tests → automation}/steps/actions/random_data.step.ts +1 -3
- package/templates/default/{src/tests → automation}/steps/actions/store.step.ts +1 -4
- package/templates/default/automation/steps/actions/wait.step.ts +91 -0
- package/templates/default/{src/tests → automation}/steps/validations/active_state_assertion.step.ts +1 -4
- package/templates/default/{src/tests → automation}/steps/validations/navigation_assertion.step.ts +1 -2
- package/templates/default/{src/tests → automation}/steps/validations/text_assertion.step.ts +1 -4
- package/templates/default/{src/tests → automation}/steps/validations/visibility_assertion.step.ts +1 -4
- package/templates/default/cucumber.mjs +6 -6
- package/templates/default/eslint.config.mjs +5 -4
- package/templates/default/package-lock.json +322 -485
- package/templates/default/package.json +12 -6
- package/templates/default/packages/cucumber-runtime/package.json +13 -0
- package/templates/default/packages/cucumber-runtime/src/cache.util.ts +93 -0
- package/templates/default/packages/cucumber-runtime/src/cli.ts +68 -0
- package/templates/default/packages/cucumber-runtime/src/environment.util.ts +21 -0
- package/templates/default/packages/cucumber-runtime/src/executor.ts +32 -0
- package/templates/default/{src/tests/hooks → packages/cucumber-runtime/src}/hooks.ts +17 -32
- package/templates/default/packages/cucumber-runtime/src/index.ts +17 -0
- package/templates/default/{src/tests/utils → packages/cucumber-runtime/src}/locator.util.ts +50 -64
- package/templates/default/packages/cucumber-runtime/src/parameter-types.ts +7 -0
- package/templates/default/packages/cucumber-runtime/src/paths.ts +33 -0
- package/templates/default/packages/cucumber-runtime/src/random-data.util.ts +35 -0
- package/templates/default/packages/cucumber-runtime/src/types.ts +13 -0
- package/templates/default/{src/tests/config/executor → packages/cucumber-runtime/src}/world.ts +4 -1
- package/templates/default/packages/cucumber-runtime/tsconfig.json +11 -0
- package/templates/default/scripts/setup-env.ts +4 -4
- package/templates/default/scripts/sync-appraise-base-template.ts +124 -105
- package/templates/default/scripts/sync-environments.ts +8 -5
- package/templates/default/scripts/sync-locator-groups.ts +7 -10
- package/templates/default/scripts/sync-locators.ts +5 -9
- package/templates/default/scripts/sync-modules.ts +9 -17
- package/templates/default/scripts/sync-tags.ts +2 -2
- package/templates/default/scripts/sync-template-step-groups.ts +16 -6
- package/templates/default/scripts/sync-template-steps.ts +16 -5
- package/templates/default/scripts/sync-test-cases.ts +6 -3
- package/templates/default/scripts/sync-test-suites.ts +7 -4
- package/templates/default/src/actions/environments/environment-actions.ts +6 -23
- package/templates/default/src/actions/locator/locator-actions.ts +36 -93
- package/templates/default/src/actions/locator-groups/locator-group-actions.ts +24 -78
- package/templates/default/src/actions/modules/module-actions.ts +4 -2
- package/templates/default/src/actions/tags/tag-actions.ts +4 -1
- package/templates/default/src/actions/template-step/template-step-actions.ts +10 -101
- package/templates/default/src/actions/template-step-group/template-step-group-actions.ts +31 -130
- package/templates/default/src/actions/test-case/test-case-actions.ts +31 -94
- package/templates/default/src/actions/test-run/test-run-actions.ts +11 -13
- package/templates/default/src/actions/test-suite/test-suite-actions.ts +29 -82
- package/templates/default/src/app/(base)/locator-groups/page.tsx +1 -3
- package/templates/default/src/app/(base)/reports/page.tsx +1 -1
- package/templates/default/src/app/(base)/reports/test-cases/page.tsx +2 -2
- package/templates/default/src/app/(base)/reports/test-cases/test-cases-metric-table-columns.tsx +1 -1
- package/templates/default/src/app/(base)/tags/page.tsx +2 -2
- package/templates/default/src/app/(base)/template-steps/page.tsx +1 -2
- package/templates/default/src/app/(base)/test-runs/page.tsx +2 -2
- package/templates/default/src/app/api/test-runs/[runId]/logs/route.ts +2 -1
- package/templates/default/src/app/api/test-runs/[runId]/trace/[testCaseId]/route.ts +2 -1
- package/templates/default/src/app/page.tsx +4 -5
- package/templates/default/src/components/diagram/dynamic-parameters.tsx +76 -40
- package/templates/default/src/components/diagram/options-header-node.tsx +1 -1
- package/templates/default/src/components/ui/data-table.tsx +33 -39
- package/templates/default/src/lib/automation/paths.ts +181 -0
- package/templates/default/src/lib/automation/projection-service.ts +230 -0
- package/templates/default/src/lib/environment-file-utils.ts +14 -51
- package/templates/default/src/lib/executor/local-executor-adapter.ts +101 -0
- package/templates/default/src/lib/executor/types.ts +24 -0
- package/templates/default/src/lib/feature-file-generator.ts +22 -112
- package/templates/default/src/lib/locator-group-file-utils.ts +57 -120
- package/templates/default/src/lib/process/task-spawner.ts +236 -0
- package/templates/default/src/lib/template-sync-utils.d.ts +7 -0
- package/templates/default/src/lib/template-sync-utils.d.ts.map +1 -0
- package/templates/default/src/lib/template-sync-utils.js +47 -0
- package/templates/default/src/lib/template-sync-utils.js.map +1 -0
- package/templates/default/src/lib/template-sync-utils.ts +63 -0
- package/templates/default/src/lib/test-run/process-manager.ts +9 -87
- package/templates/default/src/lib/test-run/test-run-executor.ts +7 -136
- package/templates/default/src/lib/test-run/winston-logger.ts +6 -35
- package/templates/default/src/lib/utils/template-step-file-generator.ts +22 -85
- package/templates/default/src/lib/utils/template-step-file-manager-intelligent.ts +7 -22
- package/templates/default/public/favicon.ico +0 -0
- package/templates/default/src/tests/executor.ts +0 -80
- package/templates/default/src/tests/mapping/locator-map.json +0 -1
- package/templates/default/src/tests/steps/actions/wait.step.ts +0 -107
- package/templates/default/src/tests/support/parameter-types.ts +0 -12
- package/templates/default/src/tests/utils/cache.util.ts +0 -260
- package/templates/default/src/tests/utils/cli.util.ts +0 -177
- package/templates/default/src/tests/utils/environment.util.ts +0 -65
- package/templates/default/src/tests/utils/random-data.util.ts +0 -45
- 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
|
-
|
|
58
|
-
|
|
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 =
|
|
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 = ['
|
|
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
|
-
|
|
81
|
-
|
|
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 =
|
|
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 = ['
|
|
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
|
-
|
|
856
|
-
const featuresDir =
|
|
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
|
-
|
|
363
|
-
const featuresDir =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
17
|
+
if (!locatorGroupId) {
|
|
18
|
+
return
|
|
19
|
+
}
|
|
17
20
|
|
|
18
21
|
try {
|
|
19
|
-
await
|
|
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(
|
|
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
|
-
|
|
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 (
|
|
116
|
+
} catch (error) {
|
|
117
117
|
return {
|
|
118
118
|
status: 500,
|
|
119
|
-
error: `Server error occurred: ${
|
|
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 (
|
|
172
|
+
} catch (error) {
|
|
178
173
|
return {
|
|
179
174
|
status: 500,
|
|
180
|
-
error: `Server error occurred: ${
|
|
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 (
|
|
196
|
+
} catch (error) {
|
|
202
197
|
return {
|
|
203
198
|
status: 500,
|
|
204
|
-
error: `Server error occurred: ${
|
|
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
|
|
238
|
-
const relativePath = path.relative(
|
|
239
|
-
const pathParts = relativePath.split(/[/\\]/).filter(
|
|
240
|
-
const moduleParts = pathParts.slice(0, -1)
|
|
241
|
-
return moduleParts.length > 0 ?
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
486
|
-
|
|
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(
|
|
497
|
-
|
|
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(
|
|
453
|
+
await fs.writeFile(filePath, JSON.stringify(mergedLocators, null, 2) + '\n', 'utf-8')
|
|
510
454
|
} catch {
|
|
511
|
-
|
|
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(
|
|
457
|
+
locatorGroup.locators.map(locator => [locator.name, locator.value]),
|
|
515
458
|
)
|
|
516
|
-
await fs.writeFile(
|
|
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}`
|