create-appraisejs 0.1.9 → 0.1.10-alpha.0

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 +11 -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 +123 -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
@@ -2,46 +2,19 @@
2
2
 
3
3
  import prisma from '@/config/db-config'
4
4
  import { templateStepGroupSchema } from '@/constants/form-opts/template-step-group-form-opts'
5
+ import { automationProjectionService } from '@/lib/automation/projection-service'
5
6
  import { ActionResponse } from '@/types/form/actionHandler'
6
7
  import { Prisma } from '@prisma/client'
7
8
  import { revalidatePath } from 'next/cache'
8
9
  import { z, ZodError } from 'zod'
9
10
 
10
- // TemplateStepGroupType will be available after Prisma migration
11
11
  type TemplateStepGroupType = 'ACTION' | 'VALIDATION'
12
12
 
13
- // Type helper to safely extract type from Prisma records
14
- type TemplateStepGroupWithType = {
15
- id: string
16
- name: string
17
- description: string | null
18
- type?: TemplateStepGroupType
19
- createdAt: Date
20
- updatedAt: Date
21
- }
22
-
23
13
  function getGroupType(group: unknown): TemplateStepGroupType {
24
- const groupWithType = group as TemplateStepGroupWithType
25
- const type = groupWithType.type
26
- if (type === 'VALIDATION' || type === 'ACTION') {
27
- return type
28
- }
29
- return 'ACTION' // default
14
+ const type = (group as { type?: TemplateStepGroupType }).type
15
+ return type === 'VALIDATION' ? 'VALIDATION' : 'ACTION'
30
16
  }
31
17
 
32
- import {
33
- createTemplateStepGroupFile,
34
- removeTemplateStepGroupFile,
35
- renameTemplateStepGroupFile,
36
- ensureGroupJSDoc,
37
- } from '@/lib/utils/template-step-file-manager-intelligent'
38
- import { promises as fs } from 'fs'
39
- import { getFilePath, formatFileContent } from '@/lib/utils/template-step-file-generator'
40
-
41
- /**
42
- * Get all template step groups
43
- * @returns ActionResponse
44
- */
45
18
  export async function getAllTemplateStepGroupsAction(): Promise<ActionResponse> {
46
19
  try {
47
20
  const templateStepGroups = await prisma.templateStepGroup.findMany()
@@ -49,20 +22,14 @@ export async function getAllTemplateStepGroupsAction(): Promise<ActionResponse>
49
22
  status: 200,
50
23
  data: templateStepGroups,
51
24
  }
52
- } catch (e) {
25
+ } catch (error) {
53
26
  return {
54
27
  status: 500,
55
- error: `Server error occurred: ${e}`,
28
+ error: `Server error occurred: ${error}`,
56
29
  }
57
30
  }
58
31
  }
59
32
 
60
- /**
61
- * Create a new template step group
62
- * @param _prev - Previous state
63
- * @param value - Template step group data
64
- * @returns ActionResponse
65
- */
66
33
  export async function createTemplateStepGroupAction(
67
34
  _prev: unknown,
68
35
  value: z.infer<typeof templateStepGroupSchema>,
@@ -71,36 +38,32 @@ export async function createTemplateStepGroupAction(
71
38
  templateStepGroupSchema.parse(value)
72
39
 
73
40
  const type: TemplateStepGroupType = (value.type as string) === 'VALIDATION' ? 'VALIDATION' : 'ACTION'
74
-
75
- // First, try to create the file - if this fails, we won't create the database record
76
- await createTemplateStepGroupFile(value.name, type, value.description)
77
-
78
- // If file creation succeeds, create the database record
79
- // Note: Using type assertion because Prisma client hasn't been regenerated yet
80
- await prisma.templateStepGroup.create({
41
+ const createdGroup = await prisma.templateStepGroup.create({
81
42
  data: {
82
43
  name: value.name,
83
44
  description: value.description,
84
- type: type,
45
+ type,
85
46
  } as Parameters<typeof prisma.templateStepGroup.create>[0]['data'],
86
47
  })
87
48
 
49
+ await automationProjectionService.syncTemplateStepGroup(createdGroup.id)
50
+
88
51
  revalidatePath('/template-step-groups')
89
52
  return {
90
53
  status: 200,
91
54
  message: 'Template step group created successfully',
92
55
  }
93
- } catch (e) {
94
- if (e instanceof ZodError) {
56
+ } catch (error) {
57
+ if (error instanceof ZodError) {
95
58
  return {
96
59
  status: 400,
97
- error: e.message,
60
+ error: error.message,
98
61
  }
99
62
  }
100
- if (e instanceof Prisma.PrismaClientKnownRequestError) {
63
+ if (error instanceof Prisma.PrismaClientKnownRequestError) {
101
64
  return {
102
65
  status: 500,
103
- error: e.message,
66
+ error: error.message,
104
67
  }
105
68
  }
106
69
  return {
@@ -110,54 +73,27 @@ export async function createTemplateStepGroupAction(
110
73
  }
111
74
  }
112
75
 
113
- /**
114
- * Delete a template step group
115
- * @param id - Template step group id(s)
116
- * @returns ActionResponse
117
- */
118
- export async function deleteTemplateStepGroupAction(id: string[]): Promise<ActionResponse> {
76
+ export async function deleteTemplateStepGroupAction(ids: string[]): Promise<ActionResponse> {
119
77
  try {
120
- // Get the group names and types before deletion for file cleanup
121
- const groupsToDelete = await prisma.templateStepGroup.findMany({
122
- where: { id: { in: id } },
123
- })
78
+ await Promise.all(ids.map(id => automationProjectionService.deleteTemplateStepGroup(id)))
124
79
 
125
- // With proper cascade deletes in the schema, we can now rely on the database
126
- // to handle the deletion of related entities automatically
127
80
  await prisma.templateStepGroup.deleteMany({
128
- where: { id: { in: id } },
81
+ where: { id: { in: ids } },
129
82
  })
130
83
 
131
- // Clean up the files after successful database deletion
132
- for (const group of groupsToDelete) {
133
- try {
134
- const groupType = getGroupType(group)
135
- await removeTemplateStepGroupFile(group.name, groupType)
136
- } catch (fileError) {
137
- console.error(`Failed to delete file for group "${group.name}":`, fileError)
138
- // Don't fail the entire operation if file deletion fails
139
- }
140
- }
141
-
142
84
  revalidatePath('/template-step-groups')
143
85
  return {
144
86
  status: 200,
145
87
  message: 'Template step group(s) deleted successfully',
146
88
  }
147
- } catch (e) {
148
- console.error('Error deleting template step group:', e)
89
+ } catch (error) {
149
90
  return {
150
91
  status: 500,
151
- error: `Server error occurred: ${e}`,
92
+ error: `Server error occurred: ${error}`,
152
93
  }
153
94
  }
154
95
  }
155
96
 
156
- /**
157
- * Get a template step group by id
158
- * @param id - Template step group id
159
- * @returns ActionResponse
160
- */
161
97
  export async function getTemplateStepGroupByIdAction(id: string): Promise<ActionResponse> {
162
98
  try {
163
99
  const templateStepGroup = await prisma.templateStepGroup.findUnique({
@@ -167,19 +103,12 @@ export async function getTemplateStepGroupByIdAction(id: string): Promise<Action
167
103
  status: 200,
168
104
  data: templateStepGroup,
169
105
  }
170
- } catch (e) {
171
- console.error(e)
172
- throw e
106
+ } catch (error) {
107
+ console.error(error)
108
+ throw error
173
109
  }
174
110
  }
175
111
 
176
- /**
177
- * Update a template step group
178
- * @param _prev - Previous state
179
- * @param value - Template step group data
180
- * @param id - Template step group id
181
- * @returns ActionResponse
182
- */
183
112
  export async function updateTemplateStepGroupAction(
184
113
  _prev: unknown,
185
114
  value: z.infer<typeof templateStepGroupSchema>,
@@ -195,7 +124,6 @@ export async function updateTemplateStepGroupAction(
195
124
  }
196
125
  }
197
126
 
198
- // Get the current group to check if name or type changed
199
127
  const currentGroup = await prisma.templateStepGroup.findUnique({
200
128
  where: { id },
201
129
  })
@@ -209,40 +137,11 @@ export async function updateTemplateStepGroupAction(
209
137
 
210
138
  const newType: TemplateStepGroupType = (value.type as string) === 'VALIDATION' ? 'VALIDATION' : 'ACTION'
211
139
  const currentType = getGroupType(currentGroup)
212
- const nameChanged = currentGroup.name !== value.name
213
- const typeChanged = currentType !== newType
214
- const descriptionChanged = currentGroup.description !== value.description
215
-
216
- // If name or type changed, we need to handle file renaming/moving
217
- if (nameChanged || typeChanged) {
218
- // Rename/move the file to preserve all existing content and update JSDoc
219
- try {
220
- await renameTemplateStepGroupFile(currentGroup.name, value.name, currentType, newType, value.description)
221
- } catch (fileError) {
222
- console.error(
223
- `Failed to rename/move file from "${currentGroup.name}" (${currentType}) to "${value.name}" (${newType}):`,
224
- fileError,
225
- )
226
- // Continue with the update even if file rename fails
227
- }
228
- } else if (descriptionChanged) {
229
- // Only description changed, update JSDoc in place
230
- try {
231
- const filePath = getFilePath(value.name, newType)
232
-
233
- let fileContent = await fs.readFile(filePath, 'utf8')
234
- fileContent = ensureGroupJSDoc(fileContent, value.name, value.description || null, newType)
235
140
 
236
- const formattedContent = await formatFileContent(fileContent)
237
- await fs.writeFile(filePath, formattedContent, 'utf8')
238
- } catch (fileError) {
239
- console.error(`Failed to update JSDoc for group "${value.name}":`, fileError)
240
- // Continue with the update even if file update fails
241
- }
141
+ if (currentGroup.name !== value.name || currentType !== newType) {
142
+ await automationProjectionService.renameTemplateStepGroup(id, value.name, newType, value.description)
242
143
  }
243
144
 
244
- // Update the database record
245
- // Note: Using type assertion because Prisma client hasn't been regenerated yet
246
145
  await prisma.templateStepGroup.update({
247
146
  where: { id },
248
147
  data: {
@@ -252,22 +151,24 @@ export async function updateTemplateStepGroupAction(
252
151
  } as Parameters<typeof prisma.templateStepGroup.update>[0]['data'],
253
152
  })
254
153
 
154
+ await automationProjectionService.syncTemplateStepGroup(id)
155
+
255
156
  revalidatePath('/template-step-groups')
256
157
  return {
257
158
  status: 200,
258
159
  message: 'Template step group updated successfully',
259
160
  }
260
- } catch (e) {
261
- if (e instanceof ZodError) {
161
+ } catch (error) {
162
+ if (error instanceof ZodError) {
262
163
  return {
263
164
  status: 400,
264
- error: e.message,
165
+ error: error.message,
265
166
  }
266
167
  }
267
- if (e instanceof Prisma.PrismaClientKnownRequestError) {
168
+ if (error instanceof Prisma.PrismaClientKnownRequestError) {
268
169
  return {
269
170
  status: 500,
270
- error: e.message,
171
+ error: error.message,
271
172
  }
272
173
  }
273
174
  return {
@@ -4,16 +4,11 @@ import prisma from '@/config/db-config'
4
4
  import { ActionResponse } from '@/types/form/actionHandler'
5
5
  import { revalidatePath } from 'next/cache'
6
6
  import { testCaseSchema } from '@/constants/form-opts/test-case-form-opts'
7
+ import { automationProjectionService } from '@/lib/automation/projection-service'
7
8
  import { z } from 'zod'
8
- import { generateFeatureFile } from '@/lib/feature-file-generator'
9
-
10
9
  import { StepParameterType, TagType } from '@prisma/client'
11
10
  import { generateUniqueTestCaseIdentifier } from '@/lib/test-case-utils'
12
11
 
13
- /**
14
- * Get all test cases
15
- * @returns ActionResponse
16
- */
17
12
  export async function getAllTestCasesAction(): Promise<ActionResponse> {
18
13
  try {
19
14
  const testCases = await prisma.testCase.findMany({
@@ -31,35 +26,27 @@ export async function getAllTestCasesAction(): Promise<ActionResponse> {
31
26
  status: 200,
32
27
  data: testCases,
33
28
  }
34
- } catch (e) {
29
+ } catch (error) {
35
30
  return {
36
31
  status: 500,
37
- error: `Server error occurred: ${e}`,
32
+ error: `Server error occurred: ${error}`,
38
33
  }
39
34
  }
40
35
  }
41
36
 
42
- /**
43
- * Delete a test case
44
- * @param id - Test case id
45
- * @returns ActionResponse
46
- */
47
- export async function deleteTestCaseAction(id: string[]): Promise<ActionResponse> {
37
+ export async function deleteTestCaseAction(ids: string[]): Promise<ActionResponse> {
48
38
  try {
49
- // Get the test suites that will be affected before deletion
50
39
  const affectedTestSuites = await prisma.testSuite.findMany({
51
40
  where: {
52
41
  testCases: {
53
42
  some: {
54
43
  id: {
55
- in: id,
44
+ in: ids,
56
45
  },
57
46
  },
58
47
  },
59
48
  },
60
- include: {
61
- module: true,
62
- },
49
+ select: { id: true },
63
50
  })
64
51
 
65
52
  const testCaseIdentifierTags = await prisma.tag.findMany({
@@ -68,7 +55,7 @@ export async function deleteTestCaseAction(id: string[]): Promise<ActionResponse
68
55
  testCases: {
69
56
  some: {
70
57
  id: {
71
- in: id,
58
+ in: ids,
72
59
  },
73
60
  },
74
61
  },
@@ -79,96 +66,74 @@ export async function deleteTestCaseAction(id: string[]): Promise<ActionResponse
79
66
  })
80
67
 
81
68
  await prisma.$transaction(async tx => {
82
- // Delete all test run test cases associated with the test cases
83
- // Note: This has RESTRICT constraint in the database, so must be deleted first
84
69
  await tx.testRunTestCase.deleteMany({
85
70
  where: {
86
71
  testCaseId: {
87
- in: id,
72
+ in: ids,
88
73
  },
89
74
  },
90
75
  })
91
76
 
92
- // Delete all reviews associated with the test cases
93
77
  await tx.review.deleteMany({
94
78
  where: {
95
79
  testCaseId: {
96
- in: id,
80
+ in: ids,
97
81
  },
98
82
  },
99
83
  })
100
84
 
101
- // Delete all linked Jira tickets associated with the test cases
102
85
  await tx.linkedJiraTicket.deleteMany({
103
86
  where: {
104
87
  testCaseId: {
105
- in: id,
88
+ in: ids,
106
89
  },
107
90
  },
108
91
  })
109
92
 
110
- // Delete all step parameters associated with the test case steps
111
93
  await tx.testCaseStepParameter.deleteMany({
112
94
  where: {
113
95
  testCaseStep: {
114
96
  testCaseId: {
115
- in: id,
97
+ in: ids,
116
98
  },
117
99
  },
118
100
  },
119
101
  })
120
102
 
121
- // Delete all test case steps
122
103
  await tx.testCaseStep.deleteMany({
123
104
  where: {
124
105
  testCaseId: {
125
- in: id,
106
+ in: ids,
126
107
  },
127
108
  },
128
109
  })
129
110
 
130
- // Delete all test case identifier tags associated with the test cases
131
111
  await tx.tag.deleteMany({
132
112
  where: {
133
113
  id: { in: testCaseIdentifierTags.map(tag => tag.id) },
134
114
  },
135
115
  })
136
116
 
137
- // Delete the test cases
138
117
  await tx.testCase.deleteMany({
139
- where: { id: { in: id } },
118
+ where: { id: { in: ids } },
140
119
  })
141
120
  })
142
121
 
143
- // Regenerate feature files for affected test suites
144
- for (const testSuite of affectedTestSuites) {
145
- try {
146
- await generateFeatureFile(testSuite.id, testSuite.name, testSuite.description || undefined)
147
- } catch (featureFileError) {
148
- console.error(`Error regenerating feature file for test suite ${testSuite.name}:`, featureFileError)
149
- // Don't fail the deletion if feature file regeneration fails
150
- }
151
- }
122
+ await Promise.all(affectedTestSuites.map(testSuite => automationProjectionService.generateFeature(testSuite.id)))
152
123
 
153
124
  revalidatePath('/test-cases')
154
125
  return {
155
126
  status: 200,
156
127
  message: 'Test case(s) deleted successfully',
157
128
  }
158
- } catch (e) {
159
- console.error('Error deleting test case(s):', e)
129
+ } catch (error) {
160
130
  return {
161
131
  status: 500,
162
- error: `Server error occurred: ${e}`,
132
+ error: `Server error occurred: ${error}`,
163
133
  }
164
134
  }
165
135
  }
166
136
 
167
- /**
168
- * Create a test case
169
- * @param testCase - Test case
170
- * @returns ActionResponse
171
- */
172
137
  export async function createTestCaseAction(value: z.infer<typeof testCaseSchema>): Promise<ActionResponse> {
173
138
  try {
174
139
  testCaseSchema.parse(value)
@@ -180,6 +145,7 @@ export async function createTestCaseAction(value: z.infer<typeof testCaseSchema>
180
145
  tagExpression: `@${uniqueTestCaseIdentifier}`,
181
146
  },
182
147
  })
148
+
183
149
  const baseData = {
184
150
  title: value.title,
185
151
  description: value.description ?? '',
@@ -205,7 +171,6 @@ export async function createTestCaseAction(value: z.infer<typeof testCaseSchema>
205
171
  },
206
172
  }
207
173
 
208
- // Only include tags if there are tagIds provided
209
174
  const data =
210
175
  value.tagIds && value.tagIds.length > 0
211
176
  ? {
@@ -225,22 +190,14 @@ export async function createTestCaseAction(value: z.infer<typeof testCaseSchema>
225
190
  data,
226
191
  include: {
227
192
  TestSuite: {
228
- include: {
229
- module: true,
193
+ select: {
194
+ id: true,
230
195
  },
231
196
  },
232
197
  },
233
198
  })
234
199
 
235
- // Regenerate feature files for all related test suites
236
- for (const testSuite of newTestCase.TestSuite) {
237
- try {
238
- await generateFeatureFile(testSuite.id, testSuite.name, testSuite.description || undefined)
239
- } catch (featureFileError) {
240
- console.error(`Error regenerating feature file for test suite ${testSuite.name}:`, featureFileError)
241
- // Don't fail the creation if feature file regeneration fails
242
- }
243
- }
200
+ await Promise.all(newTestCase.TestSuite.map(testSuite => automationProjectionService.generateFeature(testSuite.id)))
244
201
 
245
202
  revalidatePath('/test-cases')
246
203
  return {
@@ -248,19 +205,14 @@ export async function createTestCaseAction(value: z.infer<typeof testCaseSchema>
248
205
  message: 'Test case created successfully',
249
206
  data: newTestCase,
250
207
  }
251
- } catch (e) {
208
+ } catch (error) {
252
209
  return {
253
210
  status: 500,
254
- error: `Server error occurred: ${e}`,
211
+ error: `Server error occurred: ${error}`,
255
212
  }
256
213
  }
257
214
  }
258
215
 
259
- /**
260
- * Get a test case by id
261
- * @param id - Test case id
262
- * @returns ActionResponse
263
- */
264
216
  export async function getTestCaseByIdAction(id: string): Promise<ActionResponse> {
265
217
  try {
266
218
  const testCase = await prisma.testCase.findUnique({
@@ -294,10 +246,10 @@ export async function getTestCaseByIdAction(id: string): Promise<ActionResponse>
294
246
  tagIds: testCase?.tags.map(tag => tag.id) || [],
295
247
  },
296
248
  }
297
- } catch (e) {
249
+ } catch (error) {
298
250
  return {
299
251
  status: 500,
300
- error: `Server error occurred: ${e}`,
252
+ error: `Server error occurred: ${error}`,
301
253
  }
302
254
  }
303
255
  }
@@ -310,40 +262,35 @@ export async function updateTestCaseAction(
310
262
  throw new Error("updateTestCaseAction: 'id' parameter is required for updating a test case.")
311
263
  }
312
264
  try {
313
- // Get the test suites that will be affected before updating
314
265
  const affectedTestSuites = await prisma.testSuite.findMany({
315
266
  where: {
316
267
  testCases: {
317
268
  some: {
318
- id: id,
269
+ id,
319
270
  },
320
271
  },
321
272
  },
322
- include: {
323
- module: true,
273
+ select: {
274
+ id: true,
324
275
  },
325
276
  })
326
277
 
327
- // 1. Find all step IDs for the test case
328
278
  const steps = await prisma.testCaseStep.findMany({
329
279
  where: { testCaseId: id },
330
280
  select: { id: true },
331
281
  })
332
282
  const stepIds = steps.map(step => step.id)
333
283
 
334
- // 2. Delete all parameters for those steps
335
284
  if (stepIds.length > 0) {
336
285
  await prisma.testCaseStepParameter.deleteMany({
337
286
  where: { testCaseStepId: { in: stepIds } },
338
287
  })
339
288
  }
340
289
 
341
- // 3. Delete all steps for the test case
342
290
  await prisma.testCaseStep.deleteMany({
343
291
  where: { testCaseId: id },
344
292
  })
345
293
 
346
- // 4. Get existing IDENTIFIER tags to preserve them
347
294
  const existingTestCase = await prisma.testCase.findUnique({
348
295
  where: { id },
349
296
  include: {
@@ -358,19 +305,17 @@ export async function updateTestCaseAction(
358
305
  },
359
306
  })
360
307
 
361
- // 5. Combine IDENTIFIER tags with FILTER tags from the form
362
308
  const identifierTagIds = existingTestCase?.tags.map(tag => tag.id) || []
363
309
  const filterTagIds = value.tagIds || []
364
310
  const allTagIds = [...identifierTagIds, ...filterTagIds]
365
311
 
366
- // 6. Then, update the test case with new steps
367
312
  const testCase = await prisma.testCase.update({
368
313
  where: { id },
369
314
  data: {
370
315
  title: value.title,
371
316
  description: value.description ?? '',
372
317
  tags: {
373
- set: allTagIds.map(id => ({ id })),
318
+ set: allTagIds.map(tagId => ({ id: tagId })),
374
319
  },
375
320
  steps: {
376
321
  create: value.steps.map(step => ({
@@ -395,25 +340,17 @@ export async function updateTestCaseAction(
395
340
  },
396
341
  })
397
342
 
398
- // Regenerate feature files for all affected test suites
399
- for (const testSuite of affectedTestSuites) {
400
- try {
401
- await generateFeatureFile(testSuite.id, testSuite.name, testSuite.description || undefined)
402
- } catch (featureFileError) {
403
- console.error(`Error regenerating feature file for test suite ${testSuite.name}:`, featureFileError)
404
- // Don't fail the update if feature file regeneration fails
405
- }
406
- }
343
+ await Promise.all(affectedTestSuites.map(testSuite => automationProjectionService.generateFeature(testSuite.id)))
407
344
 
408
345
  return {
409
346
  status: 200,
410
347
  message: 'Test case updated successfully',
411
348
  data: testCase,
412
349
  }
413
- } catch (e) {
350
+ } catch (error) {
414
351
  return {
415
352
  status: 500,
416
- error: `Server error occurred: ${e}`,
353
+ error: `Server error occurred: ${error}`,
417
354
  }
418
355
  }
419
356
  }
@@ -12,8 +12,7 @@ import {
12
12
  TagType,
13
13
  Tag,
14
14
  } from '@prisma/client'
15
- import { executeTestRun } from '@/lib/test-run/test-run-executor'
16
- import { waitForTask, taskSpawner, killTask } from '@/tests/utils/spawner.util'
15
+ import { localExecutorAdapter } from '@/lib/executor/local-executor-adapter'
17
16
  import { revalidatePath } from 'next/cache'
18
17
  import { formatLogsForStorage, parseLogsFromStorage, type LogEntry } from '@/lib/test-run/log-formatter'
19
18
  import { processManager } from '@/lib/test-run/process-manager'
@@ -471,7 +470,7 @@ export async function createTestRunAction(
471
470
 
472
471
  // Execute test run asynchronously (don't await, let it run in background)
473
472
  try {
474
- const { process: spawnedProcess, reportPath } = await executeTestRun({
473
+ const { process: spawnedProcess, reportPath } = await localExecutorAdapter.executeTestRun({
475
474
  testRunId: testRun.runId,
476
475
  environment,
477
476
  tags,
@@ -533,7 +532,7 @@ export async function createTestRunAction(
533
532
  executePromise
534
533
  .then(async spawnedProcess => {
535
534
  // Wait for process to complete
536
- const exitCode = await waitForTask(spawnedProcess.name)
535
+ const exitCode = await localExecutorAdapter.waitForProcess(spawnedProcess.name)
537
536
 
538
537
  // Collect all logs from the process output
539
538
  const logEntries: LogEntry[] = []
@@ -906,7 +905,7 @@ export async function checkTraceViewerStatusAction(testRunId: string, testCaseId
906
905
 
907
906
  // Check if trace viewer process is running
908
907
  const processName = `trace-viewer-${testCaseId}`
909
- const process = taskSpawner.getProcess(processName)
908
+ const process = localExecutorAdapter.getProcess(processName)
910
909
  const isRunning = process?.isRunning ?? false
911
910
 
912
911
  return {
@@ -989,12 +988,7 @@ export async function spawnTraceViewerAction(testRunId: string, testCaseId: stri
989
988
 
990
989
  // Spawn playwright show-trace command
991
990
  // The process is self-closing when the user closes the trace viewer
992
- const spawnedProcess = await taskSpawner.spawn('npx', ['playwright', 'show-trace', absoluteTracePath], {
993
- streamLogs: true,
994
- prefixLogs: true,
995
- logPrefix: `trace-viewer-${testCaseId}`,
996
- captureOutput: false, // No need to capture output for trace viewer
997
- })
991
+ const spawnedProcess = await localExecutorAdapter.spawnTraceViewer(testCaseId, absoluteTracePath)
998
992
 
999
993
  console.log(
1000
994
  `[TestRunAction] Spawned trace viewer process for testCaseId: ${testCaseId}, tracePath: ${absoluteTracePath}`,
@@ -1077,10 +1071,10 @@ export async function cancelTestRunAction(testRunId: string): Promise<ActionResp
1077
1071
  }
1078
1072
  }
1079
1073
 
1080
- const killed = killTask(process.name, 'SIGTERM')
1074
+ const killed = localExecutorAdapter.killProcess(process.name, 'SIGTERM')
1081
1075
  console.log(`[TestRunAction] Killed: ${killed}`)
1082
1076
  if (!killed) {
1083
- const forceKilled = killTask(process.name, 'SIGKILL')
1077
+ const forceKilled = localExecutorAdapter.killProcess(process.name, 'SIGKILL')
1084
1078
  if (!forceKilled) {
1085
1079
  console.warn(`[TestRunAction] Failed to force kill process for testRunId: ${testRunId}`)
1086
1080
  }
@@ -1183,3 +1177,7 @@ export async function checkTestRunNameUniqueAction(name: string, excludeId?: str
1183
1177
  }
1184
1178
  }
1185
1179
  }
1180
+
1181
+
1182
+
1183
+