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.
- 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 +11 -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 +123 -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
|
@@ -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 (
|
|
25
|
+
} catch (error) {
|
|
38
26
|
return {
|
|
39
27
|
status: 500,
|
|
40
|
-
error: `Server error occurred: ${
|
|
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
|
|
84
|
-
} catch (
|
|
85
|
-
console.error('Error generating feature file:',
|
|
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 (
|
|
95
|
-
if (
|
|
69
|
+
} catch (error) {
|
|
70
|
+
if (error instanceof ZodError) {
|
|
96
71
|
return {
|
|
97
72
|
status: 400,
|
|
98
|
-
error:
|
|
73
|
+
error: error.message,
|
|
99
74
|
}
|
|
100
75
|
}
|
|
101
|
-
if (
|
|
76
|
+
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
|
102
77
|
return {
|
|
103
78
|
status: 500,
|
|
104
|
-
error:
|
|
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
|
|
125
|
-
} catch (
|
|
126
|
-
console.error(`Error deleting feature file for test suite ${testSuiteId}:`,
|
|
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 (
|
|
107
|
+
} catch (error) {
|
|
140
108
|
return {
|
|
141
109
|
status: 500,
|
|
142
|
-
error: `Server error occurred: ${
|
|
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 (
|
|
163
|
-
console.error(
|
|
164
|
-
throw
|
|
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
|
|
205
|
-
} catch (
|
|
206
|
-
console.error('Error deleting old feature file:',
|
|
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(
|
|
170
|
+
set: value.testCases?.map(testCaseId => ({ id: testCaseId })),
|
|
219
171
|
},
|
|
220
172
|
tags: {
|
|
221
|
-
set: value.tagIds?.map(
|
|
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
|
|
237
|
-
} catch (
|
|
238
|
-
console.error('Error generating updated feature file:',
|
|
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 (
|
|
194
|
+
} catch (error) {
|
|
248
195
|
return {
|
|
249
196
|
status: 500,
|
|
250
|
-
error: `Server error occurred: ${
|
|
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>
|
|
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
|
|
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>
|
package/templates/default/src/app/(base)/reports/test-cases/test-cases-metric-table-columns.tsx
CHANGED
|
@@ -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
|
|
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>
|
|
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>
|
|
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>
|
|
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 '@/
|
|
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 '@/
|
|
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 =
|
|
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>
|
|
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
|
-
<
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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-
|
|
424
|
-
<CardHeader className=
|
|
425
|
-
<CardTitle className=
|
|
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-
|
|
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>
|