create-appraisejs 0.1.8 → 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
@@ -3,24 +3,12 @@
3
3
 
4
4
  import prisma from '@/config/db-config'
5
5
  import { testSuiteSchema } from '@/constants/form-opts/test-suite-form-opts'
6
+ import { automationProjectionService } from '@/lib/automation/projection-service'
6
7
  import { ActionResponse } from '@/types/form/actionHandler'
7
8
  import { Prisma } from '@prisma/client'
8
9
  import { revalidatePath } from 'next/cache'
9
10
  import { z, ZodError } from 'zod'
10
- import { generateFeatureFile, deleteFeatureFile } from '@/lib/feature-file-generator'
11
-
12
- const generateSafeFileName = (testSuiteName: string): string => {
13
- return testSuiteName
14
- .toLowerCase()
15
- .replace(/[^a-z0-9]+/g, '-')
16
- .replace(/^-+|-+$/g, '')
17
- .replace(/-+/g, '-')
18
- }
19
11
 
20
- /**
21
- * Get all test suites
22
- * @returns ActionResponse
23
- */
24
12
  export async function getAllTestSuitesAction(): Promise<ActionResponse> {
25
13
  try {
26
14
  const testSuites = await prisma.testSuite.findMany({
@@ -34,20 +22,14 @@ export async function getAllTestSuitesAction(): Promise<ActionResponse> {
34
22
  status: 200,
35
23
  data: testSuites,
36
24
  }
37
- } catch (e) {
25
+ } catch (error) {
38
26
  return {
39
27
  status: 500,
40
- error: `Server error occurred: ${e}`,
28
+ error: `Server error occurred: ${error}`,
41
29
  }
42
30
  }
43
31
  }
44
32
 
45
- /**
46
- * Create a new test suite
47
- * @param _prev - Previous state
48
- * @param value - Test suite data
49
- * @returns ActionResponse
50
- */
51
33
  export async function createTestSuiteAction(
52
34
  _prev: unknown,
53
35
  value: z.infer<typeof testSuiteSchema>,
@@ -55,7 +37,6 @@ export async function createTestSuiteAction(
55
37
  try {
56
38
  testSuiteSchema.parse(value)
57
39
 
58
- // Create the test suite
59
40
  const newTestSuite = await prisma.testSuite.create({
60
41
  data: {
61
42
  name: value.name,
@@ -72,18 +53,12 @@ export async function createTestSuiteAction(
72
53
  connect: value.tagIds?.map(id => ({ id })) || [],
73
54
  },
74
55
  },
75
- include: {
76
- module: true,
77
- },
78
56
  })
79
57
 
80
- const sanitizedTestSuiteName = generateSafeFileName(newTestSuite.name)
81
- // Generate feature file for the new test suite
82
58
  try {
83
- await generateFeatureFile(newTestSuite.id, sanitizedTestSuiteName, newTestSuite.description || undefined)
84
- } catch (featureFileError) {
85
- console.error('Error generating feature file:', featureFileError)
86
- // Don't fail the test suite creation if feature file generation fails
59
+ await automationProjectionService.generateFeature(newTestSuite.id)
60
+ } catch (error) {
61
+ console.error('Error generating feature file:', error)
87
62
  }
88
63
 
89
64
  revalidatePath('/test-suites')
@@ -91,17 +66,17 @@ export async function createTestSuiteAction(
91
66
  status: 200,
92
67
  message: 'Test suite created successfully',
93
68
  }
94
- } catch (e) {
95
- if (e instanceof ZodError) {
69
+ } catch (error) {
70
+ if (error instanceof ZodError) {
96
71
  return {
97
72
  status: 400,
98
- error: e.message,
73
+ error: error.message,
99
74
  }
100
75
  }
101
- if (e instanceof Prisma.PrismaClientKnownRequestError) {
76
+ if (error instanceof Prisma.PrismaClientKnownRequestError) {
102
77
  return {
103
78
  status: 500,
104
- error: e.message,
79
+ error: error.message,
105
80
  }
106
81
  }
107
82
  return {
@@ -111,20 +86,13 @@ export async function createTestSuiteAction(
111
86
  }
112
87
  }
113
88
 
114
- /**
115
- * Delete a test suite
116
- * @param id - Test suite id
117
- * @returns ActionResponse
118
- */
119
89
  export async function deleteTestSuiteAction(id: string[]): Promise<ActionResponse> {
120
90
  try {
121
- // Delete corresponding feature files before deleting test suites
122
91
  for (const testSuiteId of id) {
123
92
  try {
124
- await deleteFeatureFile(testSuiteId)
125
- } catch (featureFileError) {
126
- console.error(`Error deleting feature file for test suite ${testSuiteId}:`, featureFileError)
127
- // Don't fail the test suite deletion if feature file deletion fails
93
+ await automationProjectionService.deleteFeature(testSuiteId)
94
+ } catch (error) {
95
+ console.error(`Error deleting feature file for test suite ${testSuiteId}:`, error)
128
96
  }
129
97
  }
130
98
 
@@ -136,19 +104,14 @@ export async function deleteTestSuiteAction(id: string[]): Promise<ActionRespons
136
104
  status: 200,
137
105
  message: 'Test suite(s) deleted successfully',
138
106
  }
139
- } catch (e) {
107
+ } catch (error) {
140
108
  return {
141
109
  status: 500,
142
- error: `Server error occurred: ${e}`,
110
+ error: `Server error occurred: ${error}`,
143
111
  }
144
112
  }
145
113
  }
146
114
 
147
- /**
148
- * Get a test suite by id
149
- * @param id - Test suite id
150
- * @returns ActionResponse
151
- */
152
115
  export async function getTestSuiteByIdAction(id: string): Promise<ActionResponse> {
153
116
  try {
154
117
  const testSuite = await prisma.testSuite.findUnique({
@@ -159,19 +122,12 @@ export async function getTestSuiteByIdAction(id: string): Promise<ActionResponse
159
122
  status: 200,
160
123
  data: testSuite,
161
124
  }
162
- } catch (e) {
163
- console.error(e)
164
- throw e
125
+ } catch (error) {
126
+ console.error(error)
127
+ throw error
165
128
  }
166
129
  }
167
130
 
168
- /**
169
- * Update a test suite
170
- * @param _prev - Previous state
171
- * @param value - Test suite data
172
- * @param id - Test suite id
173
- * @returns ActionResponse
174
- */
175
131
  export async function updateTestSuiteAction(
176
132
  _prev: unknown,
177
133
  value: z.infer<typeof testSuiteSchema>,
@@ -180,7 +136,6 @@ export async function updateTestSuiteAction(
180
136
  try {
181
137
  testSuiteSchema.parse(value)
182
138
 
183
- // Get the current test suite to check if name or module changed
184
139
  const currentTestSuite = await prisma.testSuite.findUnique({
185
140
  where: { id },
186
141
  include: {
@@ -195,30 +150,27 @@ export async function updateTestSuiteAction(
195
150
  }
196
151
  }
197
152
 
198
- // Check if name or module changed - if so, delete old feature file
199
153
  const nameChanged = currentTestSuite.name !== value.name
200
154
  const moduleChanged = currentTestSuite.moduleId !== value.moduleId
201
155
 
202
156
  if (nameChanged || moduleChanged) {
203
157
  try {
204
- await deleteFeatureFile(currentTestSuite.id)
205
- } catch (featureFileError) {
206
- console.error('Error deleting old feature file:', featureFileError)
207
- // Don't fail the update if old file deletion fails
158
+ await automationProjectionService.deleteFeature(currentTestSuite.id)
159
+ } catch (error) {
160
+ console.error('Error deleting old feature file:', error)
208
161
  }
209
162
  }
210
163
 
211
- // Update the test suite
212
164
  const updatedTestSuite = await prisma.testSuite.update({
213
165
  where: { id },
214
166
  data: {
215
167
  name: value.name,
216
168
  description: value.description,
217
169
  testCases: {
218
- set: value.testCases?.map(id => ({ id })),
170
+ set: value.testCases?.map(testCaseId => ({ id: testCaseId })),
219
171
  },
220
172
  tags: {
221
- set: value.tagIds?.map(id => ({ id })) || [],
173
+ set: value.tagIds?.map(tagId => ({ id: tagId })) || [],
222
174
  },
223
175
  module: {
224
176
  connect: {
@@ -226,17 +178,12 @@ export async function updateTestSuiteAction(
226
178
  },
227
179
  },
228
180
  },
229
- include: {
230
- module: true,
231
- },
232
181
  })
233
182
 
234
- // Generate new feature file with updated information
235
183
  try {
236
- await generateFeatureFile(updatedTestSuite.id, updatedTestSuite.name, updatedTestSuite.description || undefined)
237
- } catch (featureFileError) {
238
- console.error('Error generating updated feature file:', featureFileError)
239
- // Don't fail the test suite update if feature file generation fails
184
+ await automationProjectionService.generateFeature(updatedTestSuite.id)
185
+ } catch (error) {
186
+ console.error('Error generating updated feature file:', error)
240
187
  }
241
188
 
242
189
  revalidatePath('/test-suites')
@@ -244,10 +191,10 @@ export async function updateTestSuiteAction(
244
191
  status: 200,
245
192
  message: 'Test suite updated successfully',
246
193
  }
247
- } catch (e) {
194
+ } catch (error) {
248
195
  return {
249
196
  status: 500,
250
- error: `Server error occurred: ${e}`,
197
+ error: `Server error occurred: ${error}`,
251
198
  }
252
199
  }
253
200
  }
@@ -47,9 +47,7 @@ const LocatorGroups = async () => {
47
47
  Locator Groups
48
48
  </span>
49
49
  </PageHeader>
50
- <HeaderSubtitle>
51
- Locator groups are used to group locators together. They are used to identify the elements on the page.
52
- </HeaderSubtitle>
50
+ <HeaderSubtitle>Organize locators for better maintainability and reusability</HeaderSubtitle>
53
51
  </div>
54
52
  <Suspense fallback={<DataTableSkeleton />}>
55
53
  <LocatorGroupTable />
@@ -88,7 +88,7 @@ const Reports = async () => {
88
88
  Reports
89
89
  </span>
90
90
  </PageHeader>
91
- <HeaderSubtitle>Reports are the reports of the test runs.</HeaderSubtitle>
91
+ <HeaderSubtitle>Review test execution details, identify patterns, and optimize performance</HeaderSubtitle>
92
92
  </div>
93
93
  <ReportTable />
94
94
  </>
@@ -11,7 +11,7 @@ export const metadata: Metadata = {
11
11
  description: 'Manage test cases report for identifying elements on pages',
12
12
  }
13
13
 
14
- const TestCasesMetricsReport = async ({searchParams}: {searchParams: Promise<{filter?: string}>}) => {
14
+ const TestCasesMetricsReport = async ({ searchParams }: { searchParams: Promise<{ filter?: string }> }) => {
15
15
  const resolvedSearchParams = await searchParams
16
16
  const filter = resolvedSearchParams?.filter as 'repeatedlyFailing' | 'flaky' | undefined
17
17
 
@@ -27,7 +27,7 @@ const TestCasesMetricsReport = async ({searchParams}: {searchParams: Promise<{fi
27
27
  </span>
28
28
  </PageHeader>
29
29
  <HeaderSubtitle>
30
- Test cases are the individual tests that are used to test a specific feature or functionality
30
+ Test cases are the individual tests that validate the functionality a specific feature or functionality
31
31
  </HeaderSubtitle>
32
32
  </div>
33
33
  </div>
@@ -57,7 +57,7 @@ export const testCasesMetricTableCols: ColumnDef<TestCaseMetrics & { testCase: T
57
57
  header: ({ column }) => <DataTableColumnHeader column={column} title="Failure Rate" />,
58
58
  cell: ({ row }) => {
59
59
  const testCaseMetrics = row.original
60
- return <div>{testCaseMetrics.failureRate * 100}%</div>
60
+ return <div>{((testCaseMetrics.failureRate as number) * 100).toFixed(2)}%</div>
61
61
  },
62
62
  },
63
63
  {
@@ -2,7 +2,7 @@ import DataTableSkeleton from '@/components/loading-skeleton/data-table/data-tab
2
2
  import PageHeader from '@/components/typography/page-header'
3
3
  import HeaderSubtitle from '@/components/typography/page-header-subtitle'
4
4
  import { Tag } from 'lucide-react'
5
- import React, { Suspense } from 'react'
5
+ import { Suspense } from 'react'
6
6
  import TagTable from './tag-table'
7
7
  import { getAllTagsAction } from '@/actions/tags/tag-actions'
8
8
  import EmptyState from '@/components/data-state/empty-state'
@@ -46,7 +46,7 @@ const Tags = async () => {
46
46
  Tags
47
47
  </span>
48
48
  </PageHeader>
49
- <HeaderSubtitle>Tags are used to categorize test cases and test runs</HeaderSubtitle>
49
+ <HeaderSubtitle>Categorize test cases and test runs for better organization and filtering</HeaderSubtitle>
50
50
  </div>
51
51
  <Suspense fallback={<DataTableSkeleton />}>
52
52
  <TagTable />
@@ -1,6 +1,5 @@
1
1
  import HeaderSubtitle from '@/components/typography/page-header-subtitle'
2
2
  import PageHeader from '@/components/typography/page-header'
3
- import React from 'react'
4
3
  import TemplateStepTable from './template-step-table'
5
4
  import { LayoutTemplate } from 'lucide-react'
6
5
  import { Suspense } from 'react'
@@ -47,7 +46,7 @@ const TemplateSteps = async () => {
47
46
  Template Steps
48
47
  </span>
49
48
  </PageHeader>
50
- <HeaderSubtitle>Template steps are the steps that are used to define a reusable test step</HeaderSubtitle>
49
+ <HeaderSubtitle>Define reusable test steps for consistent and efficient test authoring</HeaderSubtitle>
51
50
  </div>
52
51
  <Suspense fallback={<DataTableSkeleton />}>
53
52
  <TemplateStepTable />
@@ -14,7 +14,7 @@ export const metadata: Metadata = {
14
14
  description: 'Manage test runs and their execution results',
15
15
  }
16
16
 
17
- const TestRuns = async ({searchParams}: {searchParams: Promise<{filter?: string}>}) => {
17
+ const TestRuns = async ({ searchParams }: { searchParams: Promise<{ filter?: string }> }) => {
18
18
  const resolvedSearchParams = await searchParams
19
19
  const filter = resolvedSearchParams?.filter
20
20
  const { data: testRuns, error: testRunsError } = await getAllTestRunsAction(filter)
@@ -48,7 +48,7 @@ const TestRuns = async ({searchParams}: {searchParams: Promise<{filter?: string}
48
48
  Test Runs
49
49
  </span>
50
50
  </PageHeader>
51
- <HeaderSubtitle>Test runs are the runs of the test cases.</HeaderSubtitle>
51
+ <HeaderSubtitle>Orchestrate tests and track the execution progress with detailed results</HeaderSubtitle>
52
52
  </div>
53
53
  <Suspense fallback={<DataTableSkeleton />}>
54
54
  <TestRunTable initialData={testRunsData} filter={filter as 'recentFailed' | 'all'} />
@@ -1,6 +1,6 @@
1
1
  import { NextRequest } from 'next/server'
2
2
  import { processManager } from '@/lib/test-run/process-manager'
3
- import { taskSpawner } from '@/tests/utils/spawner.util'
3
+ import { taskSpawner } from '@/lib/process/task-spawner'
4
4
  import prisma from '@/config/db-config'
5
5
  import { TestRunStatus } from '@prisma/client'
6
6
 
@@ -418,3 +418,4 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
418
418
  },
419
419
  })
420
420
  }
421
+
@@ -1,6 +1,6 @@
1
1
  import { NextRequest, NextResponse } from 'next/server'
2
2
  import { promises as fs } from 'fs'
3
- import { taskSpawner } from '@/tests/utils/spawner.util'
3
+ import { taskSpawner } from '@/lib/process/task-spawner'
4
4
  import prisma from '@/config/db-config'
5
5
  import path from 'path'
6
6
 
@@ -144,3 +144,4 @@ export async function POST(
144
144
  }
145
145
  }
146
146
 
147
+
@@ -20,7 +20,8 @@ const Dashboard = async () => {
20
20
  const metrics = metricsResponse.status === 200 ? (metricsResponse.data as DashboardMetrics | null) : null
21
21
 
22
22
  const entityMetricsResponse = await getEntityMetricsAction()
23
- const entityMetrics = entityMetricsResponse.status === 200 ? (entityMetricsResponse.data as unknown as EntityMetrics) : null
23
+ const entityMetrics =
24
+ entityMetricsResponse.status === 200 ? (entityMetricsResponse.data as unknown as EntityMetrics) : null
24
25
  if (!entityMetrics) {
25
26
  return <div>Error loading entity metrics</div>
26
27
  }
@@ -30,15 +31,13 @@ const Dashboard = async () => {
30
31
  // Fetch test suite execution data
31
32
  const testSuiteExecutionResponse = await getTestSuiteExecutionDataAction()
32
33
  const testSuiteExecutionData =
33
- testSuiteExecutionResponse.status === 200
34
- ? (testSuiteExecutionResponse.data as TestSuiteExecutionData)
35
- : []
34
+ testSuiteExecutionResponse.status === 200 ? (testSuiteExecutionResponse.data as TestSuiteExecutionData) : []
36
35
 
37
36
  return (
38
37
  <div>
39
38
  <div className="mb-8">
40
39
  <PageHeader>Dashboard</PageHeader>
41
- <HeaderSubtitle>Welcome to the dashboard. Here you can see your test suites and run them.</HeaderSubtitle>
40
+ <HeaderSubtitle>Check metrics, entity states, execution health, and more</HeaderSubtitle>
42
41
  </div>
43
42
  <div className="flex gap-7" id="dashboard-content">
44
43
  <div className="flex flex-col gap-7">
@@ -108,19 +108,50 @@ const DynamicFormFields = forwardRef<DynamicFormFieldsRef, DynamicFormFieldsProp
108
108
  return values
109
109
  }, [templateStepParams, initialParameterValues])
110
110
 
111
+ // Derive initial locator groups from initialParameterValues (locator name -> group id via locators lookup)
112
+ const initialSelectedLocatorGroups = useMemo(() => {
113
+ const groups: Record<string, string> = {}
114
+ const initialValueMap: Record<string, string> = {}
115
+ initialParameterValues?.forEach(v => {
116
+ initialValueMap[v.name] = v.value
117
+ })
118
+ templateStepParams.forEach(param => {
119
+ if (param.type === 'LOCATOR') {
120
+ const locatorName = initialValueMap[param.name]
121
+ if (locatorName) {
122
+ const locator = locators.find(l => l.name === locatorName)
123
+ if (locator?.locatorGroupId) {
124
+ groups[param.name] = locator.locatorGroupId
125
+ }
126
+ }
127
+ }
128
+ })
129
+ return groups
130
+ }, [templateStepParams, initialParameterValues, locators])
131
+
111
132
  // Initialize state with initial values
112
133
  const [values, setValues] = useState<{
113
134
  [key: string]: string | number | boolean | Date
114
135
  }>(initialValues)
115
136
  const [errors, setErrors] = useState<Record<string, string>>({})
116
137
 
117
- // State for locator group selection
118
- const [selectedLocatorGroups, setSelectedLocatorGroups] = useState<Record<string, string>>({})
138
+ // State for locator group selection (initialized from initial data so edit restores group + locator)
139
+ const [selectedLocatorGroups, setSelectedLocatorGroups] = useState<Record<string, string>>(
140
+ initialSelectedLocatorGroups,
141
+ )
119
142
 
120
143
  useEffect(() => {
121
144
  queueMicrotask(() => setErrors({}))
122
145
  }, [templateStepParams])
123
146
 
147
+ // Sync state when initial data changes (e.g. opening edit for a different node)
148
+ useEffect(() => {
149
+ queueMicrotask(() => {
150
+ setValues(initialValues)
151
+ setSelectedLocatorGroups(initialSelectedLocatorGroups)
152
+ })
153
+ }, [initialValues, initialSelectedLocatorGroups])
154
+
124
155
  useImperativeHandle(ref, () => ({
125
156
  validate: () => {
126
157
  // Skip all validation if defaultValueInput is true (all fields are optional)
@@ -251,8 +282,8 @@ const DynamicFormFields = forwardRef<DynamicFormFieldsRef, DynamicFormFieldsProp
251
282
  switch (type) {
252
283
  case 'NUMBER':
253
284
  return (
254
- <div className="grid w-full items-center gap-1.5">
255
- <Label htmlFor={`input-${name}`}>
285
+ <div className="grid w-full items-center gap-1.5 rounded-md bg-gray-500/10 p-4">
286
+ <Label htmlFor={`input-${name}`} className="text-primary">
256
287
  {defaultValueInput ? `Default ${name}` : name}{' '}
257
288
  {!defaultValueInput && <span className="text-red-500">*</span>}
258
289
  </Label>
@@ -269,8 +300,8 @@ const DynamicFormFields = forwardRef<DynamicFormFieldsRef, DynamicFormFieldsProp
269
300
 
270
301
  case 'STRING':
271
302
  return (
272
- <div className="grid w-full items-center gap-1.5">
273
- <Label htmlFor={`input-${name}`}>
303
+ <div className="grid w-full items-center gap-1.5 rounded-md bg-gray-500/10 p-4">
304
+ <Label htmlFor={`input-${name}`} className="text-primary">
274
305
  {defaultValueInput ? `Default ${name}` : name}{' '}
275
306
  {!defaultValueInput && <span className="text-red-500">*</span>}
276
307
  </Label>
@@ -287,8 +318,8 @@ const DynamicFormFields = forwardRef<DynamicFormFieldsRef, DynamicFormFieldsProp
287
318
 
288
319
  case 'DATE':
289
320
  return (
290
- <div className="grid w-full items-center gap-1.5">
291
- <Label>
321
+ <div className="grid w-full items-center gap-1.5 rounded-md bg-gray-500/10 p-4">
322
+ <Label className="text-primary">
292
323
  {defaultValueInput ? `Default ${name}` : name}{' '}
293
324
  {!defaultValueInput && <span className="text-red-500">*</span>}
294
325
  </Label>
@@ -324,8 +355,8 @@ const DynamicFormFields = forwardRef<DynamicFormFieldsRef, DynamicFormFieldsProp
324
355
 
325
356
  case 'BOOLEAN':
326
357
  return (
327
- <div className="grid w-full items-center gap-1.5">
328
- <Label htmlFor={`select-${name}`}>
358
+ <div className="grid w-full items-center gap-1.5 rounded-md bg-gray-500/10 p-4">
359
+ <Label htmlFor={`select-${name}`} className="text-primary">
329
360
  {defaultValueInput ? `Default ${name}` : name}{' '}
330
361
  {!defaultValueInput && <span className="text-red-500">*</span>}
331
362
  </Label>
@@ -350,8 +381,8 @@ const DynamicFormFields = forwardRef<DynamicFormFieldsRef, DynamicFormFieldsProp
350
381
  const availableLocators = selectedGroupId ? getLocatorsForGroup(selectedGroupId) : []
351
382
 
352
383
  return (
353
- <div className="grid w-full items-center gap-1.5">
354
- <Label htmlFor={`select-${name}`}>
384
+ <div className="grid w-full items-center gap-1.5 rounded-md bg-gray-500/10 p-4">
385
+ <Label htmlFor={`select-${name}`} className="text-primary">
355
386
  {defaultValueInput ? `Default ${name}` : name}{' '}
356
387
  {!defaultValueInput && <span className="text-red-500">*</span>}
357
388
  </Label>
@@ -380,31 +411,36 @@ const DynamicFormFields = forwardRef<DynamicFormFieldsRef, DynamicFormFieldsProp
380
411
  </div>
381
412
 
382
413
  {/* Locator Selection */}
383
- <Select
384
- value={typeof values[name] === 'string' ? values[name] : ''}
385
- onValueChange={value => handleInputChange(name, value)}
386
- required={!defaultValueInput}
387
- disabled={!selectedGroupId}
388
- >
389
- <SelectTrigger id={`select-${name}`} className="w-full">
390
- <SelectValue
391
- placeholder={
392
- !selectedGroupId
393
- ? 'Select a locator group first'
394
- : defaultValueInput
395
- ? 'Select a locator (optional)'
396
- : 'Select a locator *'
397
- }
398
- />
399
- </SelectTrigger>
400
- <SelectContent isEmpty={availableLocators.length === 0}>
401
- {availableLocators.map(locator => (
402
- <SelectItem key={locator.id} value={locator.name}>
403
- {locator.name}
404
- </SelectItem>
405
- ))}
406
- </SelectContent>
407
- </Select>
414
+ <div>
415
+ <Label htmlFor={`select-${name}`} className="text-sm text-muted-foreground">
416
+ Locator
417
+ </Label>
418
+ <Select
419
+ value={typeof values[name] === 'string' ? values[name] : ''}
420
+ onValueChange={value => handleInputChange(name, value)}
421
+ required={!defaultValueInput}
422
+ disabled={!selectedGroupId}
423
+ >
424
+ <SelectTrigger id={`select-${name}`} className="w-full">
425
+ <SelectValue
426
+ placeholder={
427
+ !selectedGroupId
428
+ ? 'Select a locator group first'
429
+ : defaultValueInput
430
+ ? 'Select a locator (optional)'
431
+ : 'Select a locator *'
432
+ }
433
+ />
434
+ </SelectTrigger>
435
+ <SelectContent isEmpty={availableLocators.length === 0}>
436
+ {availableLocators.map(locator => (
437
+ <SelectItem key={locator.id} value={locator.name}>
438
+ {locator.name}
439
+ </SelectItem>
440
+ ))}
441
+ </SelectContent>
442
+ </Select>
443
+ </div>
408
444
  <ErrorMessage message={errorMessage || ''} visible={!!errorMessage} />
409
445
  </div>
410
446
  )
@@ -420,9 +456,9 @@ const DynamicFormFields = forwardRef<DynamicFormFieldsRef, DynamicFormFieldsProp
420
456
  }
421
457
 
422
458
  return (
423
- <Card className="border-none shadow-none" key={resetKey}>
424
- <CardHeader className='py-3'>
425
- <CardTitle className='text-xs font-bold text-primary'>Parameters</CardTitle>
459
+ <Card className="border-gray-700 bg-transparent shadow-none" key={resetKey}>
460
+ <CardHeader className="py-3">
461
+ <CardTitle className="text-xs font-bold text-primary">Parameters</CardTitle>
426
462
  </CardHeader>
427
463
  <CardContent>
428
464
  <div className="space-y-6">
@@ -33,7 +33,7 @@ const OptionsHeaderNode = memo(({ selected, data, onEdit }: OptionsHeaderNodePro
33
33
  const { label, gherkinStep, isFirstNode, icon, isMissingParams } = data as unknown as OptionsHeaderNodeData
34
34
 
35
35
  return (
36
- <BaseNode selected={selected} className={cn('w-52 border-none px-3 py-2', isMissingParams && 'bg-red-700')}>
36
+ <BaseNode selected={selected} className={cn('max-w-80 border-none px-3 py-2', isMissingParams && 'bg-red-700')}>
37
37
  {!isFirstNode && <Handle type="target" position={Position.Left} />}
38
38
  <NodeHeader className="-mx-3 -mt-2 border-b">
39
39
  <NodeHeaderIcon>{KeyToIconTransformer(icon as TemplateStepIcon)}</NodeHeaderIcon>