@sun-asterisk/sungen 2.4.5 → 2.4.6

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 (129) hide show
  1. package/dist/cli/commands/delivery.d.ts +7 -0
  2. package/dist/cli/commands/delivery.d.ts.map +1 -0
  3. package/dist/cli/commands/delivery.js +348 -0
  4. package/dist/cli/commands/delivery.js.map +1 -0
  5. package/dist/cli/commands/update.d.ts.map +1 -1
  6. package/dist/cli/commands/update.js +64 -1
  7. package/dist/cli/commands/update.js.map +1 -1
  8. package/dist/cli/index.js +4 -2
  9. package/dist/cli/index.js.map +1 -1
  10. package/dist/exporters/csv-exporter.d.ts +32 -0
  11. package/dist/exporters/csv-exporter.d.ts.map +1 -0
  12. package/dist/exporters/csv-exporter.js +311 -0
  13. package/dist/exporters/csv-exporter.js.map +1 -0
  14. package/dist/exporters/feature-parser.d.ts +48 -0
  15. package/dist/exporters/feature-parser.d.ts.map +1 -0
  16. package/dist/exporters/feature-parser.js +178 -0
  17. package/dist/exporters/feature-parser.js.map +1 -0
  18. package/dist/exporters/package-info.d.ts +9 -0
  19. package/dist/exporters/package-info.d.ts.map +1 -0
  20. package/dist/exporters/package-info.js +73 -0
  21. package/dist/exporters/package-info.js.map +1 -0
  22. package/dist/exporters/playwright-report-parser.d.ts +21 -0
  23. package/dist/exporters/playwright-report-parser.d.ts.map +1 -0
  24. package/dist/exporters/playwright-report-parser.js +184 -0
  25. package/dist/exporters/playwright-report-parser.js.map +1 -0
  26. package/dist/exporters/scenario-merger.d.ts +21 -0
  27. package/dist/exporters/scenario-merger.d.ts.map +1 -0
  28. package/dist/exporters/scenario-merger.js +51 -0
  29. package/dist/exporters/scenario-merger.js.map +1 -0
  30. package/dist/exporters/spec-parser.d.ts +20 -0
  31. package/dist/exporters/spec-parser.d.ts.map +1 -0
  32. package/dist/exporters/spec-parser.js +259 -0
  33. package/dist/exporters/spec-parser.js.map +1 -0
  34. package/dist/exporters/step-formatter.d.ts +32 -0
  35. package/dist/exporters/step-formatter.d.ts.map +1 -0
  36. package/dist/exporters/step-formatter.js +76 -0
  37. package/dist/exporters/step-formatter.js.map +1 -0
  38. package/dist/exporters/test-data-resolver.d.ts +20 -0
  39. package/dist/exporters/test-data-resolver.d.ts.map +1 -0
  40. package/dist/exporters/test-data-resolver.js +96 -0
  41. package/dist/exporters/test-data-resolver.js.map +1 -0
  42. package/dist/exporters/types.d.ts +104 -0
  43. package/dist/exporters/types.d.ts.map +1 -0
  44. package/dist/exporters/types.js +6 -0
  45. package/dist/exporters/types.js.map +1 -0
  46. package/dist/exporters/xlsx-exporter.d.ts +19 -0
  47. package/dist/exporters/xlsx-exporter.d.ts.map +1 -0
  48. package/dist/exporters/xlsx-exporter.js +309 -0
  49. package/dist/exporters/xlsx-exporter.js.map +1 -0
  50. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
  51. package/dist/generators/test-generator/utils/selector-resolver.js +26 -0
  52. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
  53. package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
  54. package/dist/orchestrator/ai-rules-updater.js +12 -0
  55. package/dist/orchestrator/ai-rules-updater.js.map +1 -1
  56. package/dist/orchestrator/project-initializer.d.ts +12 -1
  57. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  58. package/dist/orchestrator/project-initializer.js +84 -64
  59. package/dist/orchestrator/project-initializer.js.map +1 -1
  60. package/dist/orchestrator/screen-manager.d.ts.map +1 -1
  61. package/dist/orchestrator/screen-manager.js +2 -0
  62. package/dist/orchestrator/screen-manager.js.map +1 -1
  63. package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +15 -17
  64. package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +7 -5
  65. package/dist/orchestrator/templates/ai-instructions/claude-cmd-delivery.md +71 -0
  66. package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +27 -0
  67. package/dist/orchestrator/templates/ai-instructions/claude-config.md +12 -2
  68. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-figma.md +142 -0
  69. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +100 -0
  70. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-local.md +73 -0
  71. package/dist/orchestrator/templates/ai-instructions/claude-skill-delivery.md +103 -0
  72. package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +2 -0
  73. package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +22 -0
  74. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +13 -15
  75. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +6 -4
  76. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +71 -0
  77. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +38 -14
  78. package/dist/orchestrator/templates/ai-instructions/copilot-config.md +12 -2
  79. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-figma.md +142 -0
  80. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +100 -0
  81. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-local.md +73 -0
  82. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +103 -0
  83. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +2 -0
  84. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +22 -0
  85. package/dist/orchestrator/templates/playwright.config.d.ts.map +1 -1
  86. package/dist/orchestrator/templates/playwright.config.js +6 -1
  87. package/dist/orchestrator/templates/playwright.config.js.map +1 -1
  88. package/dist/orchestrator/templates/playwright.config.ts +6 -1
  89. package/package.json +2 -1
  90. package/src/cli/commands/delivery.ts +348 -0
  91. package/src/cli/commands/update.ts +84 -2
  92. package/src/cli/index.ts +4 -2
  93. package/src/exporters/csv-exporter.ts +304 -0
  94. package/src/exporters/feature-parser.ts +168 -0
  95. package/src/exporters/package-info.ts +35 -0
  96. package/src/exporters/playwright-report-parser.ts +168 -0
  97. package/src/exporters/scenario-merger.ts +63 -0
  98. package/src/exporters/spec-parser.ts +247 -0
  99. package/src/exporters/step-formatter.ts +80 -0
  100. package/src/exporters/test-data-resolver.ts +59 -0
  101. package/src/exporters/types.ts +112 -0
  102. package/src/exporters/xlsx-exporter.ts +301 -0
  103. package/src/generators/test-generator/utils/selector-resolver.ts +26 -0
  104. package/src/orchestrator/ai-rules-updater.ts +12 -0
  105. package/src/orchestrator/project-initializer.ts +103 -70
  106. package/src/orchestrator/screen-manager.ts +2 -0
  107. package/src/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +15 -17
  108. package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +7 -5
  109. package/src/orchestrator/templates/ai-instructions/claude-cmd-delivery.md +71 -0
  110. package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +27 -0
  111. package/src/orchestrator/templates/ai-instructions/claude-config.md +12 -2
  112. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-figma.md +142 -0
  113. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +100 -0
  114. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-local.md +73 -0
  115. package/src/orchestrator/templates/ai-instructions/claude-skill-delivery.md +103 -0
  116. package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +2 -0
  117. package/src/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +22 -0
  118. package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +13 -15
  119. package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +6 -4
  120. package/src/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +71 -0
  121. package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +38 -14
  122. package/src/orchestrator/templates/ai-instructions/copilot-config.md +12 -2
  123. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-figma.md +142 -0
  124. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +100 -0
  125. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-local.md +73 -0
  126. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +103 -0
  127. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +2 -0
  128. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +22 -0
  129. package/src/orchestrator/templates/playwright.config.ts +6 -1
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Cross-validate .feature scenarios against compiled .spec.ts tests.
3
+ * Produces a merged view for CSV row assembly.
4
+ *
5
+ * Priority of truth:
6
+ * - .spec.ts (resolved Playwright calls) is the source of truth for Steps/Expected
7
+ * - .feature (tags, VP ID, referenced vars) is source for metadata
8
+ * - Scenarios in .feature but NOT in .spec.ts → marked "Not compiled"
9
+ */
10
+
11
+ import { FeatureMetadata, ScenarioMetadata, SpecFileData, SpecTest } from './types';
12
+ import { isStepsBaseScenario, isSampleScaffoldScenario } from './feature-parser';
13
+
14
+ export interface MergedScenario {
15
+ /** Scenario metadata from .feature */
16
+ feature: ScenarioMetadata;
17
+
18
+ /** Matched compiled test from .spec.ts, or null if not compiled. */
19
+ spec: SpecTest | null;
20
+ }
21
+
22
+ /**
23
+ * Merge features and spec data, excluding base @steps and scaffold scenarios.
24
+ */
25
+ export function mergeFeatureAndSpec(
26
+ feature: FeatureMetadata,
27
+ spec: SpecFileData
28
+ ): MergedScenario[] {
29
+ const result: MergedScenario[] = [];
30
+
31
+ for (const scenario of feature.scenarios) {
32
+ if (isStepsBaseScenario(scenario)) continue; // skip @steps base scenarios
33
+ if (isSampleScaffoldScenario(scenario)) continue; // skip default sample
34
+
35
+ const specTest = findMatchingSpecTest(scenario, spec.tests);
36
+ result.push({
37
+ feature: scenario,
38
+ spec: specTest,
39
+ });
40
+ }
41
+
42
+ return result;
43
+ }
44
+
45
+ /**
46
+ * Find the spec test that corresponds to a given feature scenario.
47
+ * Match by scenario title (exact match on the scenario name).
48
+ */
49
+ function findMatchingSpecTest(scenario: ScenarioMetadata, tests: SpecTest[]): SpecTest | null {
50
+ // Exact title match
51
+ const exact = tests.find((t) => t.scenarioName === scenario.name);
52
+ if (exact) return exact;
53
+
54
+ // Fallback: match by VP-ID prefix (ignore trailing text differences)
55
+ const vpMatch = scenario.name.match(/^(VP-[A-Z]+-\d+[a-zA-Z]?)/);
56
+ if (vpMatch) {
57
+ const vpId = vpMatch[1];
58
+ const byVp = tests.find((t) => t.vpId === vpId);
59
+ if (byVp) return byVp;
60
+ }
61
+
62
+ return null;
63
+ }
@@ -0,0 +1,247 @@
1
+ /**
2
+ * Parse compiled .spec.ts files to extract resolved test structure.
3
+ * Uses regex parsing (not TS AST) since files are auto-generated with stable format.
4
+ */
5
+
6
+ import * as fs from 'fs';
7
+ import { SpecTest, SpecFileData } from './types';
8
+
9
+ /**
10
+ * Extract the inner body of a test('title', async (...) => { BODY }) block.
11
+ * Returns null if no match.
12
+ */
13
+ function extractTestBlock(content: string, startIdx: number): {
14
+ title: string;
15
+ body: string;
16
+ endIdx: number;
17
+ } | null {
18
+ // Match test('title', async ({ page }) => {
19
+ const testRegex = /test\s*\(\s*['"]([^'"]+)['"]\s*,\s*async\s*\([^)]*\)\s*=>\s*\{/g;
20
+ testRegex.lastIndex = startIdx;
21
+ const match = testRegex.exec(content);
22
+ if (!match) return null;
23
+
24
+ const title = match[1];
25
+ const bodyStart = match.index + match[0].length;
26
+
27
+ // Find matching closing brace (accounting for nested braces)
28
+ let depth = 1;
29
+ let i = bodyStart;
30
+ while (i < content.length && depth > 0) {
31
+ const ch = content[i];
32
+ if (ch === '{') depth++;
33
+ else if (ch === '}') depth--;
34
+ if (depth === 0) break;
35
+ i++;
36
+ }
37
+ if (depth !== 0) return null;
38
+
39
+ const body = content.substring(bodyStart, i);
40
+ // The closing `)` of the arrow function call comes after the `}`, e.g., `});` — skip it
41
+ const endIdx = content.indexOf(';', i) + 1;
42
+ return { title, body, endIdx };
43
+ }
44
+
45
+ /**
46
+ * Find describe('name', ...) blocks and return their names + body ranges.
47
+ */
48
+ function extractDescribeBlocks(content: string): Array<{ name: string; bodyStart: number; bodyEnd: number }> {
49
+ const result: Array<{ name: string; bodyStart: number; bodyEnd: number }> = [];
50
+ const regex = /test\.describe\s*\(\s*['"]([^'"]+)['"]\s*,\s*\(\s*\)\s*=>\s*\{/g;
51
+ let match: RegExpExecArray | null;
52
+ while ((match = regex.exec(content)) !== null) {
53
+ const name = match[1];
54
+ const bodyStart = match.index + match[0].length;
55
+ let depth = 1;
56
+ let i = bodyStart;
57
+ while (i < content.length && depth > 0) {
58
+ const ch = content[i];
59
+ if (ch === '{') depth++;
60
+ else if (ch === '}') depth--;
61
+ if (depth === 0) break;
62
+ i++;
63
+ }
64
+ result.push({ name, bodyStart, bodyEnd: i });
65
+ }
66
+ return result;
67
+ }
68
+
69
+ /**
70
+ * A "block" in a test body: one leading // comment followed by 1+ await statements.
71
+ */
72
+ interface CodeBlock {
73
+ comment: string;
74
+ code: string[]; // each string is one logical line (may contain the await keyword)
75
+ }
76
+
77
+ /**
78
+ * Tokenize test body into code blocks.
79
+ * Each block = one or more `// <comment>` lines, followed by `await ...` lines until the next comment.
80
+ */
81
+ function tokenizeBody(body: string): CodeBlock[] {
82
+ const lines = body.split('\n').map((l) => l.trim()).filter((l) => l.length > 0);
83
+ const blocks: CodeBlock[] = [];
84
+ let currentComment = '';
85
+ let currentCode: string[] = [];
86
+
87
+ const flush = () => {
88
+ if (currentComment || currentCode.length > 0) {
89
+ blocks.push({ comment: currentComment, code: [...currentCode] });
90
+ }
91
+ currentComment = '';
92
+ currentCode = [];
93
+ };
94
+
95
+ for (const line of lines) {
96
+ if (line.startsWith('//')) {
97
+ // A new comment boundary — flush the previous block first if it has code
98
+ if (currentCode.length > 0) flush();
99
+ currentComment = line.replace(/^\/\/\s*/, '').trim();
100
+ } else if (line.startsWith('await ') || line.startsWith('await\t')) {
101
+ currentCode.push(line);
102
+ } else if (line.endsWith(';') || line.endsWith('{') || line.endsWith('}')) {
103
+ // Continuation of an await (multi-line code) OR block-structure like `{ ... }`
104
+ if (currentCode.length > 0) {
105
+ currentCode[currentCode.length - 1] += ' ' + line;
106
+ }
107
+ }
108
+ }
109
+ flush();
110
+ return blocks;
111
+ }
112
+
113
+ /**
114
+ * Classify a CodeBlock into precondition / step / expectation.
115
+ *
116
+ * Rules:
117
+ * - Starts with page.goto or is from @steps setup → precondition
118
+ * - Contains `expect(` → expectation
119
+ * - Otherwise → step (action)
120
+ *
121
+ * Additional heuristic: if the comment matches "Open <screen> page" or similar
122
+ * setup verbs, treat as precondition even if not first.
123
+ */
124
+ function classifyBlock(block: CodeBlock): 'precondition' | 'step' | 'expectation' {
125
+ const codeJoined = block.code.join(' ');
126
+ if (codeJoined.includes('expect(')) return 'expectation';
127
+ if (codeJoined.includes('page.goto(')) return 'precondition';
128
+ return 'step';
129
+ }
130
+
131
+ /**
132
+ * Try to extract VP ID (e.g., VP-UI-001) from the start of a scenario title.
133
+ */
134
+ function extractVpId(title: string): string | undefined {
135
+ const match = title.match(/^(VP-[A-Z]+-\d+[a-zA-Z]?)\b/);
136
+ return match ? match[1] : undefined;
137
+ }
138
+
139
+ /**
140
+ * Parse a compiled .spec.ts file.
141
+ *
142
+ * Structure:
143
+ * test.describe('<Feature Name>', () => {
144
+ * test.describe('<auth role>', () => { // optional nesting
145
+ * test.use({ storageState: '...' });
146
+ * test('<title>', async ({ page }) => { ... });
147
+ * ...
148
+ * });
149
+ * test('<title>', async ({ page }) => { ... }); // no-auth cases at top level
150
+ * });
151
+ */
152
+ export function parseSpecFile(specFilePath: string): SpecFileData {
153
+ if (!fs.existsSync(specFilePath)) {
154
+ return { tests: [] };
155
+ }
156
+
157
+ const content = fs.readFileSync(specFilePath, 'utf-8');
158
+ const tests: SpecTest[] = [];
159
+
160
+ const describes = extractDescribeBlocks(content);
161
+ if (describes.length === 0) return { tests: [] };
162
+
163
+ // First describe is the feature-level one
164
+ const featureDescribe = describes[0];
165
+ const featureName = featureDescribe.name;
166
+
167
+ // Nested auth describes live inside the feature describe range
168
+ const nestedAuthDescribes = describes.filter(
169
+ (d) => d !== featureDescribe && d.bodyStart > featureDescribe.bodyStart && d.bodyEnd < featureDescribe.bodyEnd
170
+ );
171
+
172
+ // Collect tests inside each nested auth describe
173
+ const processedRanges: Array<[number, number]> = [];
174
+ for (const authDesc of nestedAuthDescribes) {
175
+ const innerBody = content.substring(authDesc.bodyStart, authDesc.bodyEnd);
176
+ const innerTests = collectTestsFromBody(innerBody, featureName, authDesc.name);
177
+ tests.push(...innerTests);
178
+ processedRanges.push([authDesc.bodyStart, authDesc.bodyEnd]);
179
+ }
180
+
181
+ // Collect tests at the feature level that are NOT inside any nested auth describe
182
+ // i.e., top-level tests (no auth / @no-auth scenarios)
183
+ let cursor = featureDescribe.bodyStart;
184
+ const featureBodyEnd = featureDescribe.bodyEnd;
185
+
186
+ while (cursor < featureBodyEnd) {
187
+ // Skip any nested describe range
188
+ const nextSkip = processedRanges.find(([s, e]) => s > cursor && s < featureBodyEnd && e > cursor);
189
+ const searchEnd = nextSkip ? nextSkip[0] : featureBodyEnd;
190
+
191
+ const slice = content.substring(cursor, searchEnd);
192
+ const sliceTests = collectTestsFromBody(slice, featureName, undefined);
193
+ tests.push(...sliceTests);
194
+
195
+ cursor = nextSkip ? nextSkip[1] : featureBodyEnd;
196
+ }
197
+
198
+ return { tests };
199
+ }
200
+
201
+ function collectTestsFromBody(body: string, describeName: string, authRole?: string): SpecTest[] {
202
+ const tests: SpecTest[] = [];
203
+ let cursor = 0;
204
+ while (cursor < body.length) {
205
+ const block = extractTestBlock(body, cursor);
206
+ if (!block) break;
207
+
208
+ const codeBlocks = tokenizeBody(block.body);
209
+ const precondition: string[] = [];
210
+ const steps: string[] = [];
211
+ const expectations: string[] = [];
212
+
213
+ for (const cb of codeBlocks) {
214
+ const category = classifyBlock(cb);
215
+ const line = cb.comment || cb.code.join(' ');
216
+ if (category === 'precondition') precondition.push(line);
217
+ else if (category === 'step') steps.push(line);
218
+ else expectations.push(line);
219
+ }
220
+
221
+ tests.push({
222
+ describeName,
223
+ authRole,
224
+ testTitle: buildFullTestTitle(describeName, authRole, block.title),
225
+ scenarioName: block.title,
226
+ vpId: extractVpId(block.title),
227
+ precondition,
228
+ steps,
229
+ expectations,
230
+ });
231
+
232
+ cursor = block.endIdx;
233
+ }
234
+ return tests;
235
+ }
236
+
237
+ /**
238
+ * Build full test title matching Playwright report format:
239
+ * "<Feature Name> > <auth role> > <scenario title>"
240
+ * Playwright separator is " > " in CLI output, but internally it uses title arrays.
241
+ */
242
+ function buildFullTestTitle(featureName: string, authRole: string | undefined, scenarioTitle: string): string {
243
+ if (authRole) {
244
+ return `${featureName} > ${authRole} > ${scenarioTitle}`;
245
+ }
246
+ return `${featureName} > ${scenarioTitle}`;
247
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Convert raw Gherkin step text OR generated .spec.ts comment lines into
3
+ * concise, numbered natural language for the CSV Steps/Expected columns.
4
+ *
5
+ * Deterministic — no AI calls.
6
+ */
7
+
8
+ /**
9
+ * Format a list of raw step lines for the Steps / Expected Results columns.
10
+ *
11
+ * - 0 lines → empty string
12
+ * - 1 line → the line verbatim (no "1. " prefix — avoids Google Sheets clutter)
13
+ * - ≥ 2 → each line on its own row, "1. ", "2. ", … prefixes, newline-joined
14
+ * so spreadsheet cells auto-wrap one step per visual line.
15
+ */
16
+ export function formatNumberedSteps(lines: string[]): string {
17
+ const cleaned = lines.map(cleanStepLine).filter((l) => l.length > 0);
18
+ if (cleaned.length === 0) return '';
19
+ if (cleaned.length === 1) return cleaned[0];
20
+ return cleaned.map((line, idx) => `${idx + 1}. ${line}`).join('\n');
21
+ }
22
+
23
+ /**
24
+ * Clean a raw step line (either Gherkin text or .spec.ts comment).
25
+ *
26
+ * Handles patterns like:
27
+ * "User click [login] button" → "Click 'login' button"
28
+ * "User fill [Email] field with {{email}}" → "Fill 'Email' field with {{email}}"
29
+ * "Click Signature 2025 Creator" → "Click 'Signature 2025 Creator'"
30
+ * "Assert title error has text err_xxx" → "Title error has text err_xxx"
31
+ * "Open awards page" → "Open awards page"
32
+ */
33
+ export function cleanStepLine(raw: string): string {
34
+ let s = raw.trim();
35
+
36
+ // Strip leading "User " actor
37
+ s = s.replace(/^User\s+/i, '');
38
+
39
+ // Strip Gherkin-style element decoration "[Ref] type" → "'Ref' type"
40
+ // e.g., "click [Login] button" → "click 'Login' button"
41
+ s = s.replace(/\[([^\]]+)\]/g, "'$1'");
42
+
43
+ // Collapse multiple spaces
44
+ s = s.replace(/\s+/g, ' ');
45
+
46
+ // For .spec.ts comments that start with "Assert ..." keep as-is (they read naturally)
47
+ // For action comments like "Click X", "Fill X with Y" keep as-is.
48
+
49
+ // Capitalize first letter
50
+ if (s.length > 0) s = s.charAt(0).toUpperCase() + s.slice(1);
51
+
52
+ return s;
53
+ }
54
+
55
+ /**
56
+ * Natural language for Given → Pre-condition.
57
+ * Combines auth info + Given steps into a single sentence.
58
+ */
59
+ export function formatPrecondition(
60
+ authRole: string | null,
61
+ givenLines: string[]
62
+ ): string {
63
+ const parts: string[] = [];
64
+
65
+ // Auth prefix
66
+ if (authRole === 'no-auth') {
67
+ parts.push('Not authenticated.');
68
+ } else if (authRole) {
69
+ parts.push(`Logged in as ${authRole}.`);
70
+ }
71
+
72
+ // Given lines (clean + period-separated)
73
+ for (const line of givenLines) {
74
+ const cleaned = cleanStepLine(line);
75
+ if (!cleaned) continue;
76
+ parts.push(cleaned.endsWith('.') ? cleaned : cleaned + '.');
77
+ }
78
+
79
+ return parts.join(' ').trim();
80
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Resolve test-data.yaml variables for the CSV Test Data column.
3
+ */
4
+
5
+ import * as fs from 'fs';
6
+ import * as path from 'path';
7
+ import { parse as parseYaml } from 'yaml';
8
+
9
+ /**
10
+ * Load test-data.yaml into a flat key→value map.
11
+ * Returns empty map if file missing.
12
+ */
13
+ export function loadTestData(testDataFilePath: string): Record<string, string> {
14
+ if (!fs.existsSync(testDataFilePath)) return {};
15
+ const content = fs.readFileSync(testDataFilePath, 'utf-8');
16
+ const parsed = parseYaml(content);
17
+ if (!parsed || typeof parsed !== 'object') return {};
18
+
19
+ // Flatten shallow object to string values
20
+ const result: Record<string, string> = {};
21
+ for (const [key, value] of Object.entries(parsed as Record<string, unknown>)) {
22
+ if (value === null || value === undefined) continue;
23
+ if (typeof value === 'object') continue; // skip nested structures
24
+ result[key] = String(value);
25
+ }
26
+ return result;
27
+ }
28
+
29
+ /**
30
+ * Format a list of variable references into a "key: value; key2: value2" string
31
+ * suitable for the CSV Test Data column.
32
+ *
33
+ * Long values (>80 chars) are truncated with "…".
34
+ */
35
+ export function formatTestData(
36
+ referencedVars: string[],
37
+ testData: Record<string, string>
38
+ ): string {
39
+ if (referencedVars.length === 0) return '';
40
+ const pairs: string[] = [];
41
+ for (const key of referencedVars) {
42
+ const value = testData[key];
43
+ if (value === undefined) continue;
44
+ pairs.push(`${key}: ${truncate(value, 80)}`);
45
+ }
46
+ return pairs.join('; ');
47
+ }
48
+
49
+ function truncate(s: string, max: number): string {
50
+ if (s.length <= max) return s;
51
+ return s.substring(0, max - 1) + '…';
52
+ }
53
+
54
+ /**
55
+ * Resolve the test-data file path for a given screen.
56
+ */
57
+ export function resolveTestDataPath(cwd: string, screen: string): string {
58
+ return path.join(cwd, 'qa', 'screens', screen, 'test-data', `${screen}.yaml`);
59
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Shared types for the delivery exporter.
3
+ */
4
+
5
+ /**
6
+ * Test case row in the final CSV output.
7
+ */
8
+ export interface TestCaseRow {
9
+ tcId: string; // e.g. KUDOS-UI-001
10
+ category1: string; // Scenario name (human part)
11
+ category2: string; // "Accessing" | "GUI" | "Function"
12
+ category3: string; // Feature name (e.g. "Create Kudo Modal")
13
+ category4: string; // Screen name (e.g. "kudos")
14
+ precondition: string; // Natural language Given + auth
15
+ testData: string; // semicolon-separated key: value
16
+ steps: string; // numbered steps
17
+ expectedResults: string; // numbered expected results
18
+ priority: string; // Critical | High | Normal | Low
19
+ testcaseType: string; // Auto | Manual | Not compiled
20
+ testResult: string; // Passed | Failed | Pending | N/A
21
+ executedDate: string; // dd/mm/yyyy
22
+ testExecutor: string; // git user.name
23
+ testEnvironment: string; // baseURL + project
24
+ note: string; // error + trace for failed
25
+ }
26
+
27
+ /**
28
+ * Summary stats per screen.
29
+ */
30
+ export interface ScreenSummary {
31
+ screen: string;
32
+ total: number;
33
+ passed: number;
34
+ failed: number;
35
+ pending: number;
36
+ na: number;
37
+ notCompiled: number;
38
+ outputFile: string;
39
+ }
40
+
41
+ /**
42
+ * Pre-flight check result for a single screen.
43
+ */
44
+ export interface PreflightCheck {
45
+ screen: string;
46
+ featureOk: boolean;
47
+ testDataOk: boolean;
48
+ selectorsOk: boolean;
49
+ specOk: boolean;
50
+ resultsOk: boolean;
51
+ missing: string[]; // Human-readable list of missing items
52
+ suggestions: string[]; // Suggested commands to run
53
+ }
54
+
55
+ /**
56
+ * Metadata extracted from a .feature file.
57
+ */
58
+ export interface FeatureMetadata {
59
+ featureName: string; // e.g., "Create Kudo Modal"
60
+ featurePath?: string; // e.g., "/kudos"
61
+ featureTags: string[]; // Feature-level tags
62
+ scenarios: ScenarioMetadata[];
63
+ }
64
+
65
+ export interface ScenarioMetadata {
66
+ name: string; // Full scenario name (with VP prefix)
67
+ tags: string[]; // Scenario-level tags (merged with feature tags)
68
+ stepsName?: string; // @steps:<name>
69
+ extendsName?: string; // @extend:<name>
70
+ referencedVars: string[]; // Variables {{var}} used in this scenario
71
+ rawGivenSteps: string[]; // Raw Given/And-after-Given step text
72
+ rawWhenSteps: string[]; // Raw When/And-after-When step text
73
+ rawThenSteps: string[]; // Raw Then/And-after-Then step text
74
+ }
75
+
76
+ /**
77
+ * Structure extracted from a compiled .spec.ts test block.
78
+ */
79
+ export interface SpecTest {
80
+ describeName: string; // test.describe title
81
+ authRole?: string; // nested describe for auth (e.g., "user")
82
+ testTitle: string; // Full Playwright test title (matches results.json)
83
+ scenarioName: string; // Scenario name without VP prefix stripped
84
+ vpId?: string; // Extracted VP-XX-NNN if present
85
+ precondition: string[]; // Lines before first // [VP-...] marker
86
+ steps: string[]; // Lines between marker and first expect()
87
+ expectations: string[]; // Lines with expect() calls
88
+ }
89
+
90
+ export interface SpecFileData {
91
+ tests: SpecTest[];
92
+ }
93
+
94
+ /**
95
+ * Result from results.json per test.
96
+ */
97
+ export interface PlaywrightResult {
98
+ testTitle: string;
99
+ status: 'passed' | 'failed' | 'skipped' | 'timedOut' | 'interrupted' | 'unknown';
100
+ startTime?: string;
101
+ error?: string;
102
+ tracePath?: string;
103
+ }
104
+
105
+ /**
106
+ * Environment metadata.
107
+ */
108
+ export interface EnvironmentInfo {
109
+ baseURL: string;
110
+ projectName: string;
111
+ executor: string;
112
+ }