create-appraisejs 0.3.1-alpha.1 → 0.4.0-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/package.json +1 -1
- package/templates/blank/.env.example +2 -2
- package/templates/blank/.prettierrc +13 -13
- package/templates/blank/cucumber.mjs +12 -1
- package/templates/blank/e2e/helpers/test-data.ts +10 -0
- package/templates/blank/next-env.d.ts +6 -6
- package/templates/blank/package-lock.json +2 -2
- package/templates/blank/package.json +1 -1
- package/templates/blank/packages/cucumber-runtime/package.json +2 -1
- package/templates/blank/packages/cucumber-runtime/src/paths.ts +22 -5
- package/templates/blank/packages/locator-picker-companion/package.json +2 -1
- package/templates/blank/prisma/dev.db +0 -0
- package/templates/blank/prisma/migrations/20251104113456_add_type_for_template_step_groups/migration.sql +16 -16
- package/templates/blank/prisma/migrations/20251104170946_add_tags_to_test_suite_and_test_case/migration.sql +27 -27
- package/templates/blank/prisma/migrations/20251112190024_add_cascade_delete_to_test_run_test_case/migration.sql +17 -17
- package/templates/blank/prisma/migrations/20251113181100_add_test_run_log/migration.sql +12 -12
- package/templates/blank/prisma/migrations/20251119191838_add_tag_type/migration.sql +28 -28
- package/templates/blank/prisma/migrations/20251121164059_add_conflict_resolution/migration.sql +12 -12
- package/templates/blank/prisma/migrations/20251223183400_add_report_model_to_db_schema/migration.sql +10 -10
- package/templates/blank/prisma/migrations/20251223183637_add_report_test_case_entity_for_storing_test_results_for_individual_test_cases/migration.sql +10 -10
- package/templates/blank/prisma/migrations/20251224083549_add_comprehensive_report_storage/migration.sql +108 -108
- package/templates/blank/prisma/migrations/20251229194422_migrate_duration_to_string/migration.sql +55 -55
- package/templates/blank/prisma/migrations/20251230124637_add_unique_constraint_to_test_run_name/migration.sql +27 -27
- package/templates/blank/prisma/migrations/20260115094436_add_dashboard_metrics/migration.sql +59 -59
- package/templates/blank/prisma/migrations/20260127172022_add_cascade_delete_to_step_parameters/migration.sql +34 -34
- package/templates/blank/prisma/migrations/20260313093000_add_report_step_screenshot_path/migration.sql +1 -1
- package/templates/blank/scripts/setup-env.ts +0 -0
- package/templates/blank/scripts/sync-test-cases.ts +60 -10
- package/templates/blank/src/components/diagram/flow-diagram-node-search.tsx +9 -2
- package/templates/blank/src/components/diagram/flow-diagram-toolbar.tsx +37 -3
- package/templates/blank/src/components/diagram/flow-diagram.test.tsx +225 -0
- package/templates/blank/src/components/diagram/use-flow-diagram-search.ts +2 -0
- package/templates/blank/src/components/diagram/use-flow-diagram.ts +93 -0
- package/templates/blank/src/lib/appraise-test-case-metadata.test.ts +78 -0
- package/templates/blank/src/lib/appraise-test-case-metadata.ts +220 -0
- package/templates/blank/src/lib/automation/automation-path-roots.test.ts +14 -0
- package/templates/blank/src/lib/automation/automation-path-roots.ts +10 -2
- package/templates/blank/src/lib/database-sync.ts +166 -15
- package/templates/blank/src/lib/executor/local-executor-adapter.ts +6 -2
- package/templates/blank/src/lib/feature-file-generator.ts +54 -10
- package/templates/blank/src/lib/gherkin-parser.test.ts +52 -0
- package/templates/blank/src/lib/gherkin-parser.ts +39 -1
- package/templates/blank/src/lib/sync/projected-feature-utils.ts +5 -1
- package/templates/blank/src/lib/sync/sync-pending-counts.test.ts +115 -0
- package/templates/blank/src/lib/sync/sync-pending-counts.ts +108 -13
- package/templates/blank/src/services/test-run/test-run-service.test.ts +10 -0
- package/templates/blank/src/services/test-run/test-run-service.ts +41 -1
- package/templates/starter/.env.example +2 -2
- package/templates/starter/.prettierrc +13 -13
- package/templates/starter/cucumber.mjs +12 -1
- package/templates/starter/e2e/helpers/test-data.ts +10 -0
- package/templates/starter/next-env.d.ts +6 -6
- package/templates/starter/package-lock.json +2 -2
- package/templates/starter/package.json +1 -1
- package/templates/starter/packages/cucumber-runtime/package.json +2 -1
- package/templates/starter/packages/cucumber-runtime/src/paths.ts +22 -5
- package/templates/starter/packages/locator-picker-companion/package.json +2 -1
- package/templates/starter/prisma/dev.db +0 -0
- package/templates/starter/prisma/migrations/20251104113456_add_type_for_template_step_groups/migration.sql +16 -16
- package/templates/starter/prisma/migrations/20251104170946_add_tags_to_test_suite_and_test_case/migration.sql +27 -27
- package/templates/starter/prisma/migrations/20251112190024_add_cascade_delete_to_test_run_test_case/migration.sql +17 -17
- package/templates/starter/prisma/migrations/20251113181100_add_test_run_log/migration.sql +12 -12
- package/templates/starter/prisma/migrations/20251119191838_add_tag_type/migration.sql +28 -28
- package/templates/starter/prisma/migrations/20251121164059_add_conflict_resolution/migration.sql +12 -12
- package/templates/starter/prisma/migrations/20251223183400_add_report_model_to_db_schema/migration.sql +10 -10
- package/templates/starter/prisma/migrations/20251223183637_add_report_test_case_entity_for_storing_test_results_for_individual_test_cases/migration.sql +10 -10
- package/templates/starter/prisma/migrations/20251224083549_add_comprehensive_report_storage/migration.sql +108 -108
- package/templates/starter/prisma/migrations/20251229194422_migrate_duration_to_string/migration.sql +55 -55
- package/templates/starter/prisma/migrations/20251230124637_add_unique_constraint_to_test_run_name/migration.sql +27 -27
- package/templates/starter/prisma/migrations/20260115094436_add_dashboard_metrics/migration.sql +59 -59
- package/templates/starter/prisma/migrations/20260127172022_add_cascade_delete_to_step_parameters/migration.sql +34 -34
- package/templates/starter/prisma/migrations/20260313093000_add_report_step_screenshot_path/migration.sql +1 -1
- package/templates/starter/scripts/setup-env.ts +0 -0
- package/templates/starter/scripts/sync-test-cases.ts +60 -10
- package/templates/starter/src/components/diagram/flow-diagram-node-search.tsx +9 -2
- package/templates/starter/src/components/diagram/flow-diagram-toolbar.tsx +37 -3
- package/templates/starter/src/components/diagram/flow-diagram.test.tsx +225 -0
- package/templates/starter/src/components/diagram/use-flow-diagram-search.ts +2 -0
- package/templates/starter/src/components/diagram/use-flow-diagram.ts +93 -0
- package/templates/starter/src/lib/appraise-test-case-metadata.test.ts +78 -0
- package/templates/starter/src/lib/appraise-test-case-metadata.ts +220 -0
- package/templates/starter/src/lib/automation/automation-path-roots.test.ts +14 -0
- package/templates/starter/src/lib/automation/automation-path-roots.ts +10 -2
- package/templates/starter/src/lib/database-sync.ts +166 -15
- package/templates/starter/src/lib/executor/local-executor-adapter.ts +6 -2
- package/templates/starter/src/lib/feature-file-generator.ts +54 -10
- package/templates/starter/src/lib/gherkin-parser.test.ts +52 -0
- package/templates/starter/src/lib/gherkin-parser.ts +39 -1
- package/templates/starter/src/lib/sync/projected-feature-utils.ts +5 -1
- package/templates/starter/src/lib/sync/sync-pending-counts.test.ts +115 -0
- package/templates/starter/src/lib/sync/sync-pending-counts.ts +108 -13
- package/templates/starter/src/services/test-run/test-run-service.test.ts +10 -0
- package/templates/starter/src/services/test-run/test-run-service.ts +41 -1
- package/dist/cli.e2e.test.d.ts +0 -2
- package/dist/cli.e2e.test.d.ts.map +0 -1
- package/dist/cli.e2e.test.js +0 -75
- package/dist/cli.e2e.test.js.map +0 -1
- package/dist/config.test.d.ts +0 -2
- package/dist/config.test.d.ts.map +0 -1
- package/dist/config.test.js +0 -65
- package/dist/config.test.js.map +0 -1
- package/dist/copy-template.test.d.ts +0 -2
- package/dist/copy-template.test.d.ts.map +0 -1
- package/dist/copy-template.test.js +0 -71
- package/dist/copy-template.test.js.map +0 -1
- package/dist/download-repo.test.d.ts +0 -2
- package/dist/download-repo.test.d.ts.map +0 -1
- package/dist/download-repo.test.js +0 -16
- package/dist/download-repo.test.js.map +0 -1
- package/dist/install.test.d.ts +0 -2
- package/dist/install.test.d.ts.map +0 -1
- package/dist/install.test.js +0 -120
- package/dist/install.test.js.map +0 -1
- package/dist/prompts.test.d.ts +0 -2
- package/dist/prompts.test.d.ts.map +0 -1
- package/dist/prompts.test.js +0 -58
- package/dist/prompts.test.js.map +0 -1
- package/templates/default/next-env.d.ts +0 -6
- package/templates/default/packages/locator-picker-companion/dist/cli.d.ts +0 -1
- package/templates/default/packages/locator-picker-companion/dist/cli.js +0 -336
- package/templates/default/packages/locator-picker-companion/dist/index.d.ts +0 -3
- package/templates/default/packages/locator-picker-companion/dist/index.js +0 -3
- package/templates/default/packages/locator-picker-companion/dist/injected-picker-script.d.ts +0 -1
- package/templates/default/packages/locator-picker-companion/dist/injected-picker-script.js +0 -660
- package/templates/default/packages/locator-picker-companion/dist/launcher.d.ts +0 -14
- package/templates/default/packages/locator-picker-companion/dist/launcher.js +0 -58
- package/templates/default/packages/locator-picker-companion/dist/selector-generator.d.ts +0 -6
- package/templates/default/packages/locator-picker-companion/dist/selector-generator.js +0 -261
- package/templates/default/packages/locator-picker-companion/dist/session-file.d.ts +0 -30
- package/templates/default/packages/locator-picker-companion/dist/session-file.js +0 -162
- package/templates/default/packages/locator-picker-companion/dist/types.d.ts +0 -31
- package/templates/default/packages/locator-picker-companion/dist/types.js +0 -1
- package/templates/default/prisma/dev.db +0 -0
|
@@ -4,6 +4,7 @@ import prisma from '@/config/db-config'
|
|
|
4
4
|
import { buildModulePath } from '@/lib/path-helpers/module-path'
|
|
5
5
|
import { getAutomationFeaturesDir } from '@/lib/automation/automation-path-roots'
|
|
6
6
|
import { ensureAutomationWorkspaceReady } from '@/lib/automation/automation-workspace'
|
|
7
|
+
import { buildAppraiseMetadata, getAppraiseMetadataPath } from '@/lib/appraise-test-case-metadata'
|
|
7
8
|
import { generateProjectedGherkinSteps, getTestSuiteFilesystemKey } from '@/lib/sync/projected-feature-utils'
|
|
8
9
|
|
|
9
10
|
async function isDirectoryEmpty(dirPath: string): Promise<boolean> {
|
|
@@ -32,6 +33,18 @@ async function removeEmptyDirectoriesUp(dirPath: string, basePath: string): Prom
|
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
async function unlinkIfExists(filePath: string): Promise<boolean> {
|
|
37
|
+
try {
|
|
38
|
+
await fs.unlink(filePath)
|
|
39
|
+
return true
|
|
40
|
+
} catch (error: unknown) {
|
|
41
|
+
if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
|
|
42
|
+
return false
|
|
43
|
+
}
|
|
44
|
+
throw error
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
35
48
|
export async function generateFeatureFile(
|
|
36
49
|
testSuiteId: string,
|
|
37
50
|
testSuiteName: string,
|
|
@@ -53,6 +66,14 @@ export async function generateFeatureFile(
|
|
|
53
66
|
order: 'asc',
|
|
54
67
|
},
|
|
55
68
|
},
|
|
69
|
+
flowBlocks: {
|
|
70
|
+
include: {
|
|
71
|
+
nodes: true,
|
|
72
|
+
},
|
|
73
|
+
orderBy: {
|
|
74
|
+
order: 'asc',
|
|
75
|
+
},
|
|
76
|
+
},
|
|
56
77
|
tags: true,
|
|
57
78
|
},
|
|
58
79
|
},
|
|
@@ -73,6 +94,15 @@ export async function generateFeatureFile(
|
|
|
73
94
|
testSuite.testCases,
|
|
74
95
|
testSuite.tags,
|
|
75
96
|
)
|
|
97
|
+
const metadataContent = JSON.stringify(
|
|
98
|
+
buildAppraiseMetadata({
|
|
99
|
+
testSuiteName,
|
|
100
|
+
modulePath,
|
|
101
|
+
testCases: testSuite.testCases,
|
|
102
|
+
}),
|
|
103
|
+
null,
|
|
104
|
+
2,
|
|
105
|
+
)
|
|
76
106
|
|
|
77
107
|
const featuresBaseDir = getAutomationFeaturesDir()
|
|
78
108
|
const moduleDir = join(featuresBaseDir, modulePath.substring(1))
|
|
@@ -82,6 +112,7 @@ export async function generateFeatureFile(
|
|
|
82
112
|
const featureFilePath = join(moduleDir, `${safeFileName}.feature`)
|
|
83
113
|
|
|
84
114
|
await fs.writeFile(featureFilePath, featureContent, 'utf8')
|
|
115
|
+
await fs.writeFile(getAppraiseMetadataPath(featureFilePath), `${metadataContent}\n`, 'utf8')
|
|
85
116
|
return featureFilePath
|
|
86
117
|
} catch (error) {
|
|
87
118
|
console.error('Error generating feature file:', error)
|
|
@@ -178,17 +209,12 @@ export async function deleteFeatureFile(testSuiteId: string): Promise<boolean> {
|
|
|
178
209
|
const featuresBaseDir = getAutomationFeaturesDir()
|
|
179
210
|
const moduleDir = join(featuresBaseDir, modulePath.substring(1))
|
|
180
211
|
const featureFilePath = join(moduleDir, `${safeFileName}.feature`)
|
|
212
|
+
const metadataFilePath = getAppraiseMetadataPath(featureFilePath)
|
|
181
213
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
} catch (error: unknown) {
|
|
187
|
-
if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
|
|
188
|
-
return false
|
|
189
|
-
}
|
|
190
|
-
throw error
|
|
191
|
-
}
|
|
214
|
+
const deletedFeature = await unlinkIfExists(featureFilePath)
|
|
215
|
+
const deletedMetadata = await unlinkIfExists(metadataFilePath)
|
|
216
|
+
await removeEmptyDirectoriesUp(moduleDir, featuresBaseDir)
|
|
217
|
+
return deletedFeature || deletedMetadata
|
|
192
218
|
} catch (error) {
|
|
193
219
|
console.error('Error deleting feature file:', error)
|
|
194
220
|
throw error
|
|
@@ -220,6 +246,14 @@ export async function regenerateAllFeatureFiles(): Promise<string[]> {
|
|
|
220
246
|
order: 'asc',
|
|
221
247
|
},
|
|
222
248
|
},
|
|
249
|
+
flowBlocks: {
|
|
250
|
+
include: {
|
|
251
|
+
nodes: true,
|
|
252
|
+
},
|
|
253
|
+
orderBy: {
|
|
254
|
+
order: 'asc',
|
|
255
|
+
},
|
|
256
|
+
},
|
|
223
257
|
tags: true,
|
|
224
258
|
},
|
|
225
259
|
},
|
|
@@ -239,6 +273,15 @@ export async function regenerateAllFeatureFiles(): Promise<string[]> {
|
|
|
239
273
|
testSuite.testCases,
|
|
240
274
|
testSuite.tags,
|
|
241
275
|
)
|
|
276
|
+
const metadataContent = JSON.stringify(
|
|
277
|
+
buildAppraiseMetadata({
|
|
278
|
+
testSuiteName: testSuite.name,
|
|
279
|
+
modulePath,
|
|
280
|
+
testCases: testSuite.testCases,
|
|
281
|
+
}),
|
|
282
|
+
null,
|
|
283
|
+
2,
|
|
284
|
+
)
|
|
242
285
|
|
|
243
286
|
const moduleDir = join(featuresBaseDir, modulePath.substring(1))
|
|
244
287
|
await fs.mkdir(moduleDir, { recursive: true })
|
|
@@ -247,6 +290,7 @@ export async function regenerateAllFeatureFiles(): Promise<string[]> {
|
|
|
247
290
|
const featureFilePath = join(moduleDir, `${safeFileName}.feature`)
|
|
248
291
|
|
|
249
292
|
await fs.writeFile(featureFilePath, featureContent, 'utf8')
|
|
293
|
+
await fs.writeFile(getAppraiseMetadataPath(featureFilePath), `${metadataContent}\n`, 'utf8')
|
|
250
294
|
generatedFiles.push(featureFilePath)
|
|
251
295
|
} catch (error) {
|
|
252
296
|
console.error(`Error generating feature file for test suite ${testSuite.name}:`, error)
|
|
@@ -3,6 +3,7 @@ import { join } from 'path'
|
|
|
3
3
|
import { tmpdir } from 'os'
|
|
4
4
|
import { expect, test } from 'vitest'
|
|
5
5
|
|
|
6
|
+
import { getAppraiseMetadataPath } from '@/lib/appraise-test-case-metadata'
|
|
6
7
|
import { parseFeatureFile } from '@/lib/gherkin-parser'
|
|
7
8
|
|
|
8
9
|
async function withTempFeatureFile(content: string): Promise<string> {
|
|
@@ -42,3 +43,54 @@ Scenario: buys item
|
|
|
42
43
|
expect(parsed).not.toBeNull()
|
|
43
44
|
expect(parsed?.featureDescription).toBe('Checkout flow')
|
|
44
45
|
})
|
|
46
|
+
|
|
47
|
+
test('attaches adjacent Appraise metadata to matching scenarios', async () => {
|
|
48
|
+
const filePath = await withTempFeatureFile(`
|
|
49
|
+
Feature: Checkout flow
|
|
50
|
+
|
|
51
|
+
@tc_checkout_buy @smoke
|
|
52
|
+
Scenario: [Legacy description] Legacy title
|
|
53
|
+
Given user adds item to cart
|
|
54
|
+
`)
|
|
55
|
+
await fs.writeFile(
|
|
56
|
+
getAppraiseMetadataPath(filePath),
|
|
57
|
+
JSON.stringify({
|
|
58
|
+
version: 1,
|
|
59
|
+
testSuite: { name: 'checkout-flow', modulePath: '/checkout' },
|
|
60
|
+
testCases: [
|
|
61
|
+
{
|
|
62
|
+
identifierTag: '@tc_checkout_buy',
|
|
63
|
+
title: 'Buys item',
|
|
64
|
+
description: 'Happy path',
|
|
65
|
+
nodes: [{ nodeId: 'node-cart', order: 1, label: 'Add item' }],
|
|
66
|
+
flowBlocks: [{ id: 'block-cart', name: 'Cart', order: 0, nodeIds: ['node-cart'] }],
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
}),
|
|
70
|
+
'utf8',
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
const parsed = await parseFeatureFile(filePath)
|
|
74
|
+
|
|
75
|
+
expect(parsed?.scenarios[0]?.appraiseMetadata).toMatchObject({
|
|
76
|
+
title: 'Buys item',
|
|
77
|
+
description: 'Happy path',
|
|
78
|
+
nodes: [{ nodeId: 'node-cart', order: 1, label: 'Add item' }],
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test('falls back to feature-only parsing when sidecar is malformed', async () => {
|
|
83
|
+
const filePath = await withTempFeatureFile(`
|
|
84
|
+
Feature: Checkout flow
|
|
85
|
+
|
|
86
|
+
@tc_checkout_buy
|
|
87
|
+
Scenario: buys item
|
|
88
|
+
Given user adds item to cart
|
|
89
|
+
`)
|
|
90
|
+
await fs.writeFile(getAppraiseMetadataPath(filePath), '{', 'utf8')
|
|
91
|
+
|
|
92
|
+
const parsed = await parseFeatureFile(filePath)
|
|
93
|
+
|
|
94
|
+
expect(parsed?.scenarios[0]?.appraiseMetadata).toBeUndefined()
|
|
95
|
+
expect(parsed?.metadataWarnings).toHaveLength(1)
|
|
96
|
+
})
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { promises as fs } from 'fs'
|
|
2
2
|
import { join } from 'path'
|
|
3
|
+
import {
|
|
4
|
+
getAppraiseMetadataPath,
|
|
5
|
+
getMetadataByIdentifier,
|
|
6
|
+
readAppraiseMetadataFile,
|
|
7
|
+
type AppraiseTestCaseMetadataEntry,
|
|
8
|
+
} from '@/lib/appraise-test-case-metadata'
|
|
3
9
|
import { getFeatureModulePath } from '@/lib/path-helpers/feature-path'
|
|
4
10
|
|
|
5
11
|
/**
|
|
@@ -11,6 +17,7 @@ export interface ParsedFeature {
|
|
|
11
17
|
featureDescription?: string
|
|
12
18
|
tags: string[]
|
|
13
19
|
scenarios: ParsedScenario[]
|
|
20
|
+
metadataWarnings: string[]
|
|
14
21
|
}
|
|
15
22
|
|
|
16
23
|
/**
|
|
@@ -21,6 +28,7 @@ export interface ParsedScenario {
|
|
|
21
28
|
description?: string
|
|
22
29
|
tags: string[]
|
|
23
30
|
steps: ParsedStep[]
|
|
31
|
+
appraiseMetadata?: AppraiseTestCaseMetadataEntry
|
|
24
32
|
}
|
|
25
33
|
|
|
26
34
|
/**
|
|
@@ -30,6 +38,7 @@ export interface ParsedStep {
|
|
|
30
38
|
keyword: string
|
|
31
39
|
text: string
|
|
32
40
|
order: number
|
|
41
|
+
appraiseNode?: AppraiseTestCaseMetadataEntry['nodes'][number]
|
|
33
42
|
}
|
|
34
43
|
|
|
35
44
|
const STEP_KEYWORDS = ['Given', 'When', 'Then', 'And', 'But'] as const
|
|
@@ -103,6 +112,23 @@ function parseFeatureLine(line: string) {
|
|
|
103
112
|
return line.replace('Feature:', '').trim()
|
|
104
113
|
}
|
|
105
114
|
|
|
115
|
+
function normalizeTagExpression(tagExpression: string): string {
|
|
116
|
+
return tagExpression.startsWith('@') ? tagExpression : `@${tagExpression}`
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function getScenarioIdentifierTag(scenario: ParsedScenario): string | null {
|
|
120
|
+
for (const tagLine of scenario.tags) {
|
|
121
|
+
const tags = tagLine.split(/\s+/).filter(tag => tag.trim().startsWith('@'))
|
|
122
|
+
const identifierTag = tags.find(tag => normalizeTagExpression(tag).replace(/^@/, '').startsWith('tc_'))
|
|
123
|
+
|
|
124
|
+
if (identifierTag) {
|
|
125
|
+
return normalizeTagExpression(identifierTag)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return null
|
|
130
|
+
}
|
|
131
|
+
|
|
106
132
|
function startScenario(lines: string[], index: number): ParsedScenario {
|
|
107
133
|
const { name, description } = parseScenarioHeader(lines[index])
|
|
108
134
|
|
|
@@ -170,10 +196,21 @@ function parseGherkinLines(lines: string[]) {
|
|
|
170
196
|
*/
|
|
171
197
|
export async function parseFeatureFile(filePath: string): Promise<ParsedFeature | null> {
|
|
172
198
|
try {
|
|
173
|
-
const content = await
|
|
199
|
+
const [content, metadataResult] = await Promise.all([
|
|
200
|
+
fs.readFile(filePath, 'utf-8'),
|
|
201
|
+
readAppraiseMetadataFile(getAppraiseMetadataPath(filePath)),
|
|
202
|
+
])
|
|
174
203
|
const lines = normalizeGherkinLines(content)
|
|
175
204
|
const featureTags = getFeatureTags(lines)
|
|
176
205
|
const { featureName, featureDescription, scenarios } = parseGherkinLines(lines)
|
|
206
|
+
const metadataByIdentifier = getMetadataByIdentifier(metadataResult.metadata)
|
|
207
|
+
|
|
208
|
+
for (const scenario of scenarios) {
|
|
209
|
+
const identifierTag = getScenarioIdentifierTag(scenario)
|
|
210
|
+
if (identifierTag) {
|
|
211
|
+
scenario.appraiseMetadata = metadataByIdentifier.get(identifierTag)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
177
214
|
|
|
178
215
|
if (!featureName) {
|
|
179
216
|
console.warn(`No feature found in file: ${filePath}`)
|
|
@@ -186,6 +223,7 @@ export async function parseFeatureFile(filePath: string): Promise<ParsedFeature
|
|
|
186
223
|
featureDescription: featureDescription || undefined,
|
|
187
224
|
tags: featureTags,
|
|
188
225
|
scenarios,
|
|
226
|
+
metadataWarnings: metadataResult.warnings,
|
|
189
227
|
}
|
|
190
228
|
} catch (error) {
|
|
191
229
|
console.error(`Error parsing feature file ${filePath}:`, error)
|
|
@@ -7,6 +7,8 @@ type StoredProjectedStep = {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
type StoredProjectedDbStep = StoredProjectedStep & {
|
|
10
|
+
flowNodeId: string | null
|
|
11
|
+
label: string
|
|
10
12
|
TemplateStep: { signature: string } | null
|
|
11
13
|
parameters: Array<{ name: string; value: string; order: number; type: StepParameterType }>
|
|
12
14
|
}
|
|
@@ -16,6 +18,7 @@ export type ProjectedDbTestCaseStep = {
|
|
|
16
18
|
keyword: string
|
|
17
19
|
text: string
|
|
18
20
|
gherkinStep: string
|
|
21
|
+
flowNodeId: string | null
|
|
19
22
|
label: string
|
|
20
23
|
icon: TemplateStepIcon
|
|
21
24
|
templateStepSignature: string | null
|
|
@@ -64,7 +67,8 @@ export function normalizeProjectedDbTestCaseSteps(steps: StoredProjectedDbStep[]
|
|
|
64
67
|
keyword,
|
|
65
68
|
text,
|
|
66
69
|
gherkinStep,
|
|
67
|
-
|
|
70
|
+
flowNodeId: step.flowNodeId,
|
|
71
|
+
label: step.label,
|
|
68
72
|
icon: determineProjectedStepIcon(keyword),
|
|
69
73
|
templateStepSignature: step.TemplateStep?.signature ?? null,
|
|
70
74
|
parameters: step.parameters,
|
|
@@ -342,6 +342,53 @@ describe('sync pending counts', () => {
|
|
|
342
342
|
expect(count).toBe(0)
|
|
343
343
|
})
|
|
344
344
|
|
|
345
|
+
it('counts sidecar-backed node label and flow block mismatches', () => {
|
|
346
|
+
const baseFilesystemCase = {
|
|
347
|
+
identifierTag: '@tc_checkout',
|
|
348
|
+
title: 'Checkout',
|
|
349
|
+
description: 'Buys an item',
|
|
350
|
+
testSuiteName: 'checkout-suite',
|
|
351
|
+
modulePath: '/commerce',
|
|
352
|
+
filterTags: [],
|
|
353
|
+
steps: [{ order: 1, keyword: 'Given' as const, text: 'open checkout' }],
|
|
354
|
+
hasAppraiseMetadata: true,
|
|
355
|
+
nodes: [{ nodeId: 'node-open', order: 1, label: 'Open checkout' }],
|
|
356
|
+
flowBlocks: [{ id: 'block-flow', name: 'Checkout flow', order: 0, nodeIds: ['node-open'] }],
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const dbCase = {
|
|
360
|
+
title: 'Checkout',
|
|
361
|
+
description: 'Buys an item',
|
|
362
|
+
tags: [{ tagExpression: '@tc_checkout', type: TagType.IDENTIFIER }],
|
|
363
|
+
TestSuite: [{ name: 'Checkout Suite', moduleId: 'module-commerce' }],
|
|
364
|
+
steps: [
|
|
365
|
+
{
|
|
366
|
+
order: 1,
|
|
367
|
+
gherkinStep: 'When open checkout',
|
|
368
|
+
flowNodeId: 'node-open',
|
|
369
|
+
label: 'Old label',
|
|
370
|
+
icon: TemplateStepIcon.MOUSE,
|
|
371
|
+
TemplateStep: { signature: 'open checkout' },
|
|
372
|
+
parameters: [],
|
|
373
|
+
},
|
|
374
|
+
],
|
|
375
|
+
flowBlocks: [
|
|
376
|
+
{
|
|
377
|
+
id: 'block-flow',
|
|
378
|
+
name: 'Checkout flow',
|
|
379
|
+
order: 0,
|
|
380
|
+
nodes: [{ flowNodeId: 'node-other' }],
|
|
381
|
+
},
|
|
382
|
+
],
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const count = countTestCaseMismatches([baseFilesystemCase], [dbCase], new Map([['module-commerce', '/commerce']]), [
|
|
386
|
+
{ signature: 'open checkout', parameters: [] },
|
|
387
|
+
])
|
|
388
|
+
|
|
389
|
+
expect(count).toBe(1)
|
|
390
|
+
})
|
|
391
|
+
|
|
345
392
|
it('ignores duplicate stale DB test cases when one matching identifier row exists', () => {
|
|
346
393
|
const count = countTestCaseMismatches(
|
|
347
394
|
[
|
|
@@ -402,6 +449,74 @@ describe('sync pending counts', () => {
|
|
|
402
449
|
expect(count).toBe(0)
|
|
403
450
|
})
|
|
404
451
|
|
|
452
|
+
it('treats And steps after Then as in sync when DB stores the feature-file keyword', () => {
|
|
453
|
+
const count = countTestCaseMismatches(
|
|
454
|
+
[
|
|
455
|
+
{
|
|
456
|
+
identifierTag: '@tc_route',
|
|
457
|
+
title: 'Route Check',
|
|
458
|
+
description: 'Validates route after login',
|
|
459
|
+
testSuiteName: 'authentication',
|
|
460
|
+
modulePath: '/E2E Auth',
|
|
461
|
+
filterTags: [],
|
|
462
|
+
steps: [
|
|
463
|
+
{ order: 1, keyword: 'Given', text: 'open the login page' },
|
|
464
|
+
{ order: 2, keyword: 'Then', text: 'the url route should be equal to "/home"' },
|
|
465
|
+
{ order: 3, keyword: 'And', text: 'the page title should be "Home"' },
|
|
466
|
+
],
|
|
467
|
+
},
|
|
468
|
+
],
|
|
469
|
+
[
|
|
470
|
+
{
|
|
471
|
+
title: 'Route Check',
|
|
472
|
+
description: 'Validates route after login',
|
|
473
|
+
tags: [{ tagExpression: '@tc_route', type: TagType.IDENTIFIER }],
|
|
474
|
+
TestSuite: [{ name: 'Authentication', moduleId: 'module-auth' }],
|
|
475
|
+
steps: [
|
|
476
|
+
{
|
|
477
|
+
order: 1,
|
|
478
|
+
gherkinStep: 'Given open the login page',
|
|
479
|
+
label: 'open the login page',
|
|
480
|
+
icon: TemplateStepIcon.NAVIGATION,
|
|
481
|
+
TemplateStep: { signature: 'open the login page' },
|
|
482
|
+
parameters: [],
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
order: 2,
|
|
486
|
+
gherkinStep: 'Then the url route should be equal to "/home"',
|
|
487
|
+
label: 'the url route should be equal to "/home"',
|
|
488
|
+
icon: TemplateStepIcon.VALIDATION,
|
|
489
|
+
TemplateStep: { signature: 'the url route should be equal to {string}' },
|
|
490
|
+
parameters: [{ name: 'route', value: '/home', order: 0, type: StepParameterType.STRING }],
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
order: 3,
|
|
494
|
+
gherkinStep: 'And the page title should be "Home"',
|
|
495
|
+
label: 'the page title should be "Home"',
|
|
496
|
+
icon: TemplateStepIcon.MOUSE,
|
|
497
|
+
TemplateStep: { signature: 'the page title should be {string}' },
|
|
498
|
+
parameters: [{ name: 'title', value: 'Home', order: 0, type: StepParameterType.STRING }],
|
|
499
|
+
},
|
|
500
|
+
],
|
|
501
|
+
},
|
|
502
|
+
],
|
|
503
|
+
new Map([['module-auth', '/E2E Auth']]),
|
|
504
|
+
[
|
|
505
|
+
{ signature: 'open the login page', parameters: [] },
|
|
506
|
+
{
|
|
507
|
+
signature: 'the url route should be equal to {string}',
|
|
508
|
+
parameters: [{ name: 'route', order: 0, type: StepParameterType.STRING }],
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
signature: 'the page title should be {string}',
|
|
512
|
+
parameters: [{ name: 'title', order: 0, type: StepParameterType.STRING }],
|
|
513
|
+
},
|
|
514
|
+
],
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
expect(count).toBe(0)
|
|
518
|
+
})
|
|
519
|
+
|
|
405
520
|
it('collapses duplicate filesystem test-case identifiers to the script final state', () => {
|
|
406
521
|
const count = countTestCaseMismatches(
|
|
407
522
|
[
|
|
@@ -27,9 +27,11 @@ import { getTagTypeFromName } from '@/lib/tag-identifiers'
|
|
|
27
27
|
import { extractModulePathFromAutomationFile, getAutomationLocatorMapPath } from '@/lib/template-sync-utils'
|
|
28
28
|
import {
|
|
29
29
|
determineProjectedStepIcon,
|
|
30
|
+
generateProjectedGherkinSteps,
|
|
30
31
|
getTestSuiteSyncIdentity,
|
|
31
32
|
normalizeProjectedDbTestCaseSteps,
|
|
32
33
|
} from '@/lib/sync/projected-feature-utils'
|
|
34
|
+
import type { AppraiseTestCaseMetadataFlowBlock, AppraiseTestCaseMetadataNode } from '@/lib/appraise-test-case-metadata'
|
|
33
35
|
import {
|
|
34
36
|
parseGroupJSDocLenient as parseGroupJSDoc,
|
|
35
37
|
parseStepJSDocLenient as parseStepJSDoc,
|
|
@@ -116,6 +118,9 @@ type TestCaseFromFs = {
|
|
|
116
118
|
modulePath: string
|
|
117
119
|
filterTags: string[]
|
|
118
120
|
steps: ParsedStep[]
|
|
121
|
+
hasAppraiseMetadata?: boolean
|
|
122
|
+
nodes: AppraiseTestCaseMetadataNode[]
|
|
123
|
+
flowBlocks: AppraiseTestCaseMetadataFlowBlock[]
|
|
119
124
|
}
|
|
120
125
|
|
|
121
126
|
type CollapsedTestCaseFromFs = TestCaseFromFs & {
|
|
@@ -686,15 +691,18 @@ async function buildFilesystemSnapshot(baseDir: string): Promise<FilesystemSnaps
|
|
|
686
691
|
continue
|
|
687
692
|
}
|
|
688
693
|
|
|
689
|
-
const
|
|
694
|
+
const parsedTitle = parseScenarioTitle(scenario.name, scenario.description)
|
|
690
695
|
testCases.push({
|
|
691
696
|
identifierTag: normalizeTagExpression(identifierTag),
|
|
692
|
-
title,
|
|
693
|
-
description,
|
|
697
|
+
title: scenario.appraiseMetadata?.title ?? parsedTitle.title,
|
|
698
|
+
description: scenario.appraiseMetadata?.description ?? parsedTitle.description,
|
|
694
699
|
testSuiteName,
|
|
695
700
|
modulePath,
|
|
696
701
|
filterTags: flattenedTags.filter(tag => normalizeTagExpression(tag) !== normalizeTagExpression(identifierTag)),
|
|
697
702
|
steps: scenario.steps,
|
|
703
|
+
hasAppraiseMetadata: scenario.appraiseMetadata != null,
|
|
704
|
+
nodes: scenario.appraiseMetadata?.nodes ?? [],
|
|
705
|
+
flowBlocks: scenario.appraiseMetadata?.flowBlocks ?? [],
|
|
698
706
|
})
|
|
699
707
|
}
|
|
700
708
|
}
|
|
@@ -987,11 +995,41 @@ export function countTestSuiteMismatches(
|
|
|
987
995
|
return count
|
|
988
996
|
}
|
|
989
997
|
|
|
998
|
+
type ProjectedTestCaseStep = {
|
|
999
|
+
order: number
|
|
1000
|
+
gherkinStep: string
|
|
1001
|
+
label: string
|
|
1002
|
+
icon: TemplateStepIcon
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
function normalizeProjectedFsTestCaseSteps(stepsFromFs: ParsedStep[]): ProjectedTestCaseStep[] {
|
|
1006
|
+
const storedSteps = stepsFromFs.map(step => ({
|
|
1007
|
+
order: step.order,
|
|
1008
|
+
gherkinStep: `${step.keyword} ${step.text}`,
|
|
1009
|
+
}))
|
|
1010
|
+
const projectedGherkinSteps = generateProjectedGherkinSteps(storedSteps)
|
|
1011
|
+
|
|
1012
|
+
return stepsFromFs.map((step, index) => {
|
|
1013
|
+
const gherkinStep = projectedGherkinSteps[index] ?? ''
|
|
1014
|
+
const [keyword = '', ...textParts] = gherkinStep.split(' ')
|
|
1015
|
+
const label = textParts.join(' ')
|
|
1016
|
+
|
|
1017
|
+
return {
|
|
1018
|
+
order: step.order,
|
|
1019
|
+
gherkinStep,
|
|
1020
|
+
label,
|
|
1021
|
+
icon: determineProjectedStepIcon(keyword),
|
|
1022
|
+
}
|
|
1023
|
+
})
|
|
1024
|
+
}
|
|
1025
|
+
|
|
990
1026
|
function hasProjectedTestCaseStepMismatch(
|
|
991
1027
|
stepsFromFs: ParsedStep[],
|
|
1028
|
+
nodesFromFs: AppraiseTestCaseMetadataNode[] = [],
|
|
992
1029
|
dbSteps: Array<{
|
|
993
1030
|
order: number
|
|
994
1031
|
gherkinStep: string
|
|
1032
|
+
flowNodeId: string | null
|
|
995
1033
|
label: string
|
|
996
1034
|
icon: TemplateStepIcon
|
|
997
1035
|
TemplateStep: { signature: string } | null
|
|
@@ -1003,23 +1041,26 @@ function hasProjectedTestCaseStepMismatch(
|
|
|
1003
1041
|
}>,
|
|
1004
1042
|
): boolean {
|
|
1005
1043
|
const projectedDbSteps = normalizeProjectedDbTestCaseSteps(dbSteps)
|
|
1044
|
+
const projectedFsSteps = normalizeProjectedFsTestCaseSteps(stepsFromFs)
|
|
1006
1045
|
const dbStepsByOrder = new Map(projectedDbSteps.map(step => [step.order, step]))
|
|
1046
|
+
const fsStepsByOrder = new Map(stepsFromFs.map(step => [step.order, step]))
|
|
1047
|
+
const nodesByOrder = new Map(nodesFromFs.map(node => [node.order, node]))
|
|
1007
1048
|
|
|
1008
|
-
for (const
|
|
1009
|
-
const existing = dbStepsByOrder.get(
|
|
1010
|
-
const
|
|
1049
|
+
for (const projectedFsStep of projectedFsSteps) {
|
|
1050
|
+
const existing = dbStepsByOrder.get(projectedFsStep.order)
|
|
1051
|
+
const sourceStep = fsStepsByOrder.get(projectedFsStep.order)
|
|
1052
|
+
const metadataNode = nodesByOrder.get(projectedFsStep.order)
|
|
1053
|
+
const matchedTemplateStep = sourceStep ? matchGherkinStepToTemplateStep(sourceStep, dbTemplateSteps) : null
|
|
1011
1054
|
|
|
1012
1055
|
if (!existing || !matchedTemplateStep) {
|
|
1013
1056
|
return true
|
|
1014
1057
|
}
|
|
1015
1058
|
|
|
1016
|
-
const expectedIcon = determineProjectedStepIcon(step.keyword)
|
|
1017
|
-
const expectedGherkinStep = `${step.keyword} ${step.text}`
|
|
1018
|
-
|
|
1019
1059
|
if (
|
|
1020
|
-
existing.gherkinStep !==
|
|
1021
|
-
existing.
|
|
1022
|
-
existing.
|
|
1060
|
+
existing.gherkinStep !== projectedFsStep.gherkinStep ||
|
|
1061
|
+
existing.flowNodeId !== (metadataNode?.nodeId ?? existing.flowNodeId) ||
|
|
1062
|
+
existing.label !== (metadataNode?.label ?? projectedFsStep.label) ||
|
|
1063
|
+
existing.icon !== projectedFsStep.icon ||
|
|
1023
1064
|
existing.templateStepSignature !== matchedTemplateStep.signature ||
|
|
1024
1065
|
!sameResolvedParameters(existing.parameters, matchedTemplateStep.parameters)
|
|
1025
1066
|
) {
|
|
@@ -1031,6 +1072,39 @@ function hasProjectedTestCaseStepMismatch(
|
|
|
1031
1072
|
return projectedDbSteps.some(step => !fsOrders.has(step.order))
|
|
1032
1073
|
}
|
|
1033
1074
|
|
|
1075
|
+
function hasFlowBlockMismatch(
|
|
1076
|
+
flowBlocksFromFs: AppraiseTestCaseMetadataFlowBlock[] = [],
|
|
1077
|
+
hasAppraiseMetadata: boolean | undefined,
|
|
1078
|
+
dbFlowBlocks: Array<{
|
|
1079
|
+
id: string
|
|
1080
|
+
name: string
|
|
1081
|
+
order: number
|
|
1082
|
+
nodes: Array<{ flowNodeId: string }>
|
|
1083
|
+
}>,
|
|
1084
|
+
): boolean {
|
|
1085
|
+
if (!hasAppraiseMetadata) {
|
|
1086
|
+
return false
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
if (flowBlocksFromFs.length !== dbFlowBlocks.length) {
|
|
1090
|
+
return true
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
const dbById = new Map(dbFlowBlocks.map(block => [block.id, block]))
|
|
1094
|
+
|
|
1095
|
+
return flowBlocksFromFs.some(block => {
|
|
1096
|
+
const existing = dbById.get(block.id)
|
|
1097
|
+
if (!existing || existing.name !== block.name || existing.order !== block.order) {
|
|
1098
|
+
return true
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
return !sameStringSet(
|
|
1102
|
+
existing.nodes.map(node => node.flowNodeId),
|
|
1103
|
+
block.nodeIds,
|
|
1104
|
+
)
|
|
1105
|
+
})
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1034
1108
|
export function countTestCaseMismatches(
|
|
1035
1109
|
filesystemTestCases: TestCaseFromFs[],
|
|
1036
1110
|
dbTestCases: Array<{
|
|
@@ -1041,11 +1115,18 @@ export function countTestCaseMismatches(
|
|
|
1041
1115
|
steps: Array<{
|
|
1042
1116
|
order: number
|
|
1043
1117
|
gherkinStep: string
|
|
1118
|
+
flowNodeId: string | null
|
|
1044
1119
|
label: string
|
|
1045
1120
|
icon: TemplateStepIcon
|
|
1046
1121
|
TemplateStep: { signature: string } | null
|
|
1047
1122
|
parameters: Array<{ name: string; value: string; order: number; type: StepParameterType }>
|
|
1048
1123
|
}>
|
|
1124
|
+
flowBlocks: Array<{
|
|
1125
|
+
id: string
|
|
1126
|
+
name: string
|
|
1127
|
+
order: number
|
|
1128
|
+
nodes: Array<{ flowNodeId: string }>
|
|
1129
|
+
}>
|
|
1049
1130
|
}>,
|
|
1050
1131
|
modulePathMap: Map<string, string>,
|
|
1051
1132
|
dbTemplateSteps: Array<{
|
|
@@ -1110,7 +1191,8 @@ export function countTestCaseMismatches(
|
|
|
1110
1191
|
existing.description === testCase.description &&
|
|
1111
1192
|
isLinkedToExpectedSuites &&
|
|
1112
1193
|
sameStringSet(existingFilterTags, normalizedFilterTags) &&
|
|
1113
|
-
!hasProjectedTestCaseStepMismatch(testCase.steps, existing.steps, dbTemplateSteps)
|
|
1194
|
+
!hasProjectedTestCaseStepMismatch(testCase.steps, testCase.nodes, existing.steps, dbTemplateSteps) &&
|
|
1195
|
+
!hasFlowBlockMismatch(testCase.flowBlocks, testCase.hasAppraiseMetadata, existing.flowBlocks ?? [])
|
|
1114
1196
|
)
|
|
1115
1197
|
})
|
|
1116
1198
|
|
|
@@ -1216,6 +1298,7 @@ export async function getSyncPendingCounts(): Promise<SyncPendingCounts> {
|
|
|
1216
1298
|
select: {
|
|
1217
1299
|
order: true,
|
|
1218
1300
|
gherkinStep: true,
|
|
1301
|
+
flowNodeId: true,
|
|
1219
1302
|
label: true,
|
|
1220
1303
|
icon: true,
|
|
1221
1304
|
TemplateStep: {
|
|
@@ -1227,6 +1310,18 @@ export async function getSyncPendingCounts(): Promise<SyncPendingCounts> {
|
|
|
1227
1310
|
},
|
|
1228
1311
|
},
|
|
1229
1312
|
},
|
|
1313
|
+
flowBlocks: {
|
|
1314
|
+
select: {
|
|
1315
|
+
id: true,
|
|
1316
|
+
name: true,
|
|
1317
|
+
order: true,
|
|
1318
|
+
nodes: {
|
|
1319
|
+
select: {
|
|
1320
|
+
flowNodeId: true,
|
|
1321
|
+
},
|
|
1322
|
+
},
|
|
1323
|
+
},
|
|
1324
|
+
},
|
|
1230
1325
|
},
|
|
1231
1326
|
}),
|
|
1232
1327
|
])
|
|
@@ -39,6 +39,7 @@ const {
|
|
|
39
39
|
mockProcessManagerOn,
|
|
40
40
|
mockProcessManagerRemoveListener,
|
|
41
41
|
mockFsAccess,
|
|
42
|
+
mockGenerateFeature,
|
|
42
43
|
} = vi.hoisted(() => ({
|
|
43
44
|
mockEnvironmentFindUnique: vi.fn(),
|
|
44
45
|
mockTagFindMany: vi.fn(),
|
|
@@ -68,6 +69,7 @@ const {
|
|
|
68
69
|
mockProcessManagerOn: vi.fn(),
|
|
69
70
|
mockProcessManagerRemoveListener: vi.fn(),
|
|
70
71
|
mockFsAccess: vi.fn(),
|
|
72
|
+
mockGenerateFeature: vi.fn(),
|
|
71
73
|
}))
|
|
72
74
|
|
|
73
75
|
vi.mock('@/config/db-config', () => ({
|
|
@@ -118,6 +120,12 @@ vi.mock('@/lib/test-suite-identifier-service', () => ({
|
|
|
118
120
|
ensureTestSuiteIdentifierTags: mockEnsureTestSuiteIdentifierTags,
|
|
119
121
|
}))
|
|
120
122
|
|
|
123
|
+
vi.mock('@/lib/automation/projection-service', () => ({
|
|
124
|
+
automationProjectionService: {
|
|
125
|
+
generateFeature: mockGenerateFeature,
|
|
126
|
+
},
|
|
127
|
+
}))
|
|
128
|
+
|
|
121
129
|
vi.mock('@/services/report/report-service', () => ({
|
|
122
130
|
storeReportFromFileService: mockStoreReportFromFileService,
|
|
123
131
|
}))
|
|
@@ -266,6 +274,7 @@ describe('createTestRunFromValidatedValue', () => {
|
|
|
266
274
|
error: vi.fn(),
|
|
267
275
|
})
|
|
268
276
|
mockGetLogFilePath.mockReturnValue('logs/run-1.log')
|
|
277
|
+
mockGenerateFeature.mockResolvedValue('automation/features/login.feature')
|
|
269
278
|
mockTestRunUpdate.mockResolvedValue({})
|
|
270
279
|
mockExecuteTestRun.mockResolvedValue({
|
|
271
280
|
process: {
|
|
@@ -297,6 +306,7 @@ describe('createTestRunFromValidatedValue', () => {
|
|
|
297
306
|
const result = await createTestRunFromValidatedValue(baseValue)
|
|
298
307
|
|
|
299
308
|
expect(mockEnsureTestSuiteIdentifierTags).toHaveBeenCalledWith(['suite-1'])
|
|
309
|
+
expect(mockGenerateFeature).toHaveBeenCalledWith('suite-1')
|
|
300
310
|
expect(mockTestRunCreate).toHaveBeenCalledWith({
|
|
301
311
|
data: {
|
|
302
312
|
name: 'Nightly Run',
|