@sun-asterisk/sungen 2.4.5 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) 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/generate.d.ts.map +1 -1
  6. package/dist/cli/commands/generate.js +2 -0
  7. package/dist/cli/commands/generate.js.map +1 -1
  8. package/dist/cli/commands/update.d.ts.map +1 -1
  9. package/dist/cli/commands/update.js +64 -1
  10. package/dist/cli/commands/update.js.map +1 -1
  11. package/dist/cli/index.js +4 -2
  12. package/dist/cli/index.js.map +1 -1
  13. package/dist/exporters/csv-exporter.d.ts +32 -0
  14. package/dist/exporters/csv-exporter.d.ts.map +1 -0
  15. package/dist/exporters/csv-exporter.js +311 -0
  16. package/dist/exporters/csv-exporter.js.map +1 -0
  17. package/dist/exporters/feature-parser.d.ts +48 -0
  18. package/dist/exporters/feature-parser.d.ts.map +1 -0
  19. package/dist/exporters/feature-parser.js +178 -0
  20. package/dist/exporters/feature-parser.js.map +1 -0
  21. package/dist/exporters/package-info.d.ts +9 -0
  22. package/dist/exporters/package-info.d.ts.map +1 -0
  23. package/dist/exporters/package-info.js +73 -0
  24. package/dist/exporters/package-info.js.map +1 -0
  25. package/dist/exporters/playwright-report-parser.d.ts +21 -0
  26. package/dist/exporters/playwright-report-parser.d.ts.map +1 -0
  27. package/dist/exporters/playwright-report-parser.js +184 -0
  28. package/dist/exporters/playwright-report-parser.js.map +1 -0
  29. package/dist/exporters/scenario-merger.d.ts +21 -0
  30. package/dist/exporters/scenario-merger.d.ts.map +1 -0
  31. package/dist/exporters/scenario-merger.js +51 -0
  32. package/dist/exporters/scenario-merger.js.map +1 -0
  33. package/dist/exporters/spec-parser.d.ts +20 -0
  34. package/dist/exporters/spec-parser.d.ts.map +1 -0
  35. package/dist/exporters/spec-parser.js +259 -0
  36. package/dist/exporters/spec-parser.js.map +1 -0
  37. package/dist/exporters/step-formatter.d.ts +32 -0
  38. package/dist/exporters/step-formatter.d.ts.map +1 -0
  39. package/dist/exporters/step-formatter.js +76 -0
  40. package/dist/exporters/step-formatter.js.map +1 -0
  41. package/dist/exporters/test-data-resolver.d.ts +20 -0
  42. package/dist/exporters/test-data-resolver.d.ts.map +1 -0
  43. package/dist/exporters/test-data-resolver.js +96 -0
  44. package/dist/exporters/test-data-resolver.js.map +1 -0
  45. package/dist/exporters/types.d.ts +104 -0
  46. package/dist/exporters/types.d.ts.map +1 -0
  47. package/dist/exporters/types.js +6 -0
  48. package/dist/exporters/types.js.map +1 -0
  49. package/dist/exporters/xlsx-exporter.d.ts +19 -0
  50. package/dist/exporters/xlsx-exporter.d.ts.map +1 -0
  51. package/dist/exporters/xlsx-exporter.js +309 -0
  52. package/dist/exporters/xlsx-exporter.js.map +1 -0
  53. package/dist/generators/gherkin-parser/index.d.ts +1 -0
  54. package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
  55. package/dist/generators/gherkin-parser/index.js +3 -0
  56. package/dist/generators/gherkin-parser/index.js.map +1 -1
  57. package/dist/generators/test-generator/adapters/adapter-interface.d.ts +29 -1
  58. package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
  59. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +21 -1
  60. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
  61. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js +11 -2
  62. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
  63. package/dist/generators/test-generator/adapters/playwright/templates/after-all.hbs +8 -0
  64. package/dist/generators/test-generator/adapters/playwright/templates/after-each.hbs +8 -0
  65. package/dist/generators/test-generator/adapters/playwright/templates/before-all.hbs +8 -0
  66. package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  67. package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +24 -0
  68. package/dist/generators/test-generator/code-generator.d.ts +2 -0
  69. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  70. package/dist/generators/test-generator/code-generator.js +109 -12
  71. package/dist/generators/test-generator/code-generator.js.map +1 -1
  72. package/dist/generators/test-generator/step-mapper.d.ts +1 -0
  73. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  74. package/dist/generators/test-generator/step-mapper.js +1 -1
  75. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  76. package/dist/generators/test-generator/template-engine.d.ts +29 -1
  77. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  78. package/dist/generators/test-generator/template-engine.js +11 -2
  79. package/dist/generators/test-generator/template-engine.js.map +1 -1
  80. package/dist/generators/test-generator/utils/data-resolver.d.ts +11 -2
  81. package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
  82. package/dist/generators/test-generator/utils/data-resolver.js +36 -25
  83. package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
  84. package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts +7 -0
  85. package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts.map +1 -0
  86. package/dist/generators/test-generator/utils/runtime-data-transformer.js +42 -0
  87. package/dist/generators/test-generator/utils/runtime-data-transformer.js.map +1 -0
  88. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
  89. package/dist/generators/test-generator/utils/selector-resolver.js +26 -0
  90. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
  91. package/dist/generators/types.d.ts +1 -0
  92. package/dist/generators/types.d.ts.map +1 -1
  93. package/dist/generators/types.js.map +1 -1
  94. package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
  95. package/dist/orchestrator/ai-rules-updater.js +12 -0
  96. package/dist/orchestrator/ai-rules-updater.js.map +1 -1
  97. package/dist/orchestrator/project-initializer.d.ts +21 -1
  98. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  99. package/dist/orchestrator/project-initializer.js +158 -74
  100. package/dist/orchestrator/project-initializer.js.map +1 -1
  101. package/dist/orchestrator/screen-manager.d.ts.map +1 -1
  102. package/dist/orchestrator/screen-manager.js +2 -0
  103. package/dist/orchestrator/screen-manager.js.map +1 -1
  104. package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +15 -17
  105. package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +7 -5
  106. package/dist/orchestrator/templates/ai-instructions/claude-cmd-delivery.md +71 -0
  107. package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +28 -1
  108. package/dist/orchestrator/templates/ai-instructions/claude-config.md +23 -4
  109. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-figma.md +142 -0
  110. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +100 -0
  111. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-local.md +73 -0
  112. package/dist/orchestrator/templates/ai-instructions/claude-skill-delivery.md +103 -0
  113. package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +68 -13
  114. package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +22 -0
  115. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +54 -3
  116. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +13 -15
  117. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +6 -4
  118. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +71 -0
  119. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +38 -14
  120. package/dist/orchestrator/templates/ai-instructions/copilot-config.md +23 -4
  121. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-figma.md +142 -0
  122. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +100 -0
  123. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-local.md +73 -0
  124. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +103 -0
  125. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +88 -13
  126. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +22 -0
  127. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +54 -3
  128. package/dist/orchestrator/templates/playwright.config.d.ts.map +1 -1
  129. package/dist/orchestrator/templates/playwright.config.js +6 -1
  130. package/dist/orchestrator/templates/playwright.config.js.map +1 -1
  131. package/dist/orchestrator/templates/playwright.config.ts +6 -1
  132. package/dist/orchestrator/templates/specs-base.d.ts +12 -1
  133. package/dist/orchestrator/templates/specs-base.d.ts.map +1 -1
  134. package/dist/orchestrator/templates/specs-base.js +47 -5
  135. package/dist/orchestrator/templates/specs-base.js.map +1 -1
  136. package/dist/orchestrator/templates/specs-base.ts +65 -7
  137. package/dist/orchestrator/templates/specs-test-data.d.ts +14 -0
  138. package/dist/orchestrator/templates/specs-test-data.d.ts.map +1 -0
  139. package/dist/orchestrator/templates/specs-test-data.js +100 -0
  140. package/dist/orchestrator/templates/specs-test-data.js.map +1 -0
  141. package/dist/orchestrator/templates/specs-test-data.ts +66 -0
  142. package/package.json +2 -1
  143. package/src/cli/commands/delivery.ts +348 -0
  144. package/src/cli/commands/generate.ts +2 -0
  145. package/src/cli/commands/update.ts +84 -2
  146. package/src/cli/index.ts +4 -2
  147. package/src/exporters/csv-exporter.ts +304 -0
  148. package/src/exporters/feature-parser.ts +168 -0
  149. package/src/exporters/package-info.ts +35 -0
  150. package/src/exporters/playwright-report-parser.ts +168 -0
  151. package/src/exporters/scenario-merger.ts +63 -0
  152. package/src/exporters/spec-parser.ts +247 -0
  153. package/src/exporters/step-formatter.ts +80 -0
  154. package/src/exporters/test-data-resolver.ts +59 -0
  155. package/src/exporters/types.ts +112 -0
  156. package/src/exporters/xlsx-exporter.ts +301 -0
  157. package/src/generators/gherkin-parser/index.ts +4 -0
  158. package/src/generators/test-generator/adapters/adapter-interface.ts +12 -1
  159. package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +14 -2
  160. package/src/generators/test-generator/adapters/playwright/templates/after-all.hbs +8 -0
  161. package/src/generators/test-generator/adapters/playwright/templates/after-each.hbs +8 -0
  162. package/src/generators/test-generator/adapters/playwright/templates/before-all.hbs +8 -0
  163. package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  164. package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +24 -0
  165. package/src/generators/test-generator/code-generator.ts +122 -13
  166. package/src/generators/test-generator/step-mapper.ts +2 -2
  167. package/src/generators/test-generator/template-engine.ts +28 -2
  168. package/src/generators/test-generator/utils/data-resolver.ts +45 -27
  169. package/src/generators/test-generator/utils/runtime-data-transformer.ts +51 -0
  170. package/src/generators/test-generator/utils/selector-resolver.ts +26 -0
  171. package/src/generators/types.ts +1 -0
  172. package/src/orchestrator/ai-rules-updater.ts +12 -0
  173. package/src/orchestrator/project-initializer.ts +187 -80
  174. package/src/orchestrator/screen-manager.ts +2 -0
  175. package/src/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +15 -17
  176. package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +7 -5
  177. package/src/orchestrator/templates/ai-instructions/claude-cmd-delivery.md +71 -0
  178. package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +28 -1
  179. package/src/orchestrator/templates/ai-instructions/claude-config.md +23 -4
  180. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-figma.md +142 -0
  181. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +100 -0
  182. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-local.md +73 -0
  183. package/src/orchestrator/templates/ai-instructions/claude-skill-delivery.md +103 -0
  184. package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +68 -13
  185. package/src/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +22 -0
  186. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +54 -3
  187. package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +13 -15
  188. package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +6 -4
  189. package/src/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +71 -0
  190. package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +38 -14
  191. package/src/orchestrator/templates/ai-instructions/copilot-config.md +23 -4
  192. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-figma.md +142 -0
  193. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +100 -0
  194. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-local.md +73 -0
  195. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +103 -0
  196. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +88 -13
  197. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +22 -0
  198. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +54 -3
  199. package/src/orchestrator/templates/playwright.config.ts +6 -1
  200. package/src/orchestrator/templates/specs-base.ts +65 -7
  201. package/src/orchestrator/templates/specs-test-data.ts +66 -0
@@ -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
+ }
@@ -0,0 +1,301 @@
1
+ /**
2
+ * Render the same rows/summary the CSV exporter produces into a styled .xlsx
3
+ * workbook. Keeps the BM-2-901-13 layout but adds:
4
+ * - bold header row with filled background + white font
5
+ * - borders on the data region
6
+ * - text wrapping in Steps / Expected / Pre-condition / Test Data
7
+ * - frozen header so the first data rows stay aligned with the title row
8
+ * - sensible column widths
9
+ * - category-separator rows rendered as a filled bar spanning the table
10
+ */
11
+
12
+ import * as fs from 'fs';
13
+ import * as path from 'path';
14
+ import ExcelJS from 'exceljs';
15
+ import { ScreenSummary, TestCaseRow } from './types';
16
+ import { getPackageVersion } from './package-info';
17
+
18
+ const COL_COUNT = 16;
19
+ const HEADER_FILL = 'FF1F4E78'; // deep blue band
20
+ const HEADER_FONT = 'FFFFFFFF';
21
+ const META_FILL = 'FFDCE6F1'; // soft blue for meta rows
22
+ const CATEGORY_FILL = 'FFFFF2CC'; // soft yellow for group dividers
23
+ const BORDER_COLOR = 'FFBFBFBF';
24
+
25
+ type AnyCell = ExcelJS.Cell;
26
+ type AnyRow = ExcelJS.Row;
27
+
28
+ function applyBorder(cell: AnyCell): void {
29
+ cell.border = {
30
+ top: { style: 'thin', color: { argb: BORDER_COLOR } },
31
+ left: { style: 'thin', color: { argb: BORDER_COLOR } },
32
+ bottom: { style: 'thin', color: { argb: BORDER_COLOR } },
33
+ right: { style: 'thin', color: { argb: BORDER_COLOR } },
34
+ };
35
+ }
36
+
37
+ export function renderXlsx(
38
+ summary: ScreenSummary,
39
+ rows: TestCaseRow[],
40
+ specLink: string
41
+ ): ExcelJS.Workbook {
42
+ const wb = new ExcelJS.Workbook();
43
+ wb.creator = 'sungen delivery';
44
+ wb.created = new Date();
45
+ const ws = wb.addWorksheet('Testcases', {
46
+ views: [{ state: 'frozen', ySplit: 0, xSplit: 0 }],
47
+ });
48
+
49
+ const issueDate = (() => {
50
+ const d = new Date();
51
+ return `${String(d.getDate()).padStart(2, '0')}/${String(d.getMonth() + 1).padStart(2, '0')}/${d.getFullYear()}`;
52
+ })();
53
+
54
+ const total = summary.total || 1;
55
+ const pct = (n: number) => `${Math.round((n / total) * 100)}%`;
56
+
57
+ // -- Column widths (matches CSV column order) --
58
+ ws.columns = [
59
+ { width: 18 }, // TC ID
60
+ { width: 42 }, // Category 1
61
+ { width: 14 }, // Category 2
62
+ { width: 26 }, // Category 3
63
+ { width: 14 }, // Category 4
64
+ { width: 36 }, // Pre-condition
65
+ { width: 30 }, // Test Data
66
+ { width: 46 }, // Steps
67
+ { width: 52 }, // Expected results
68
+ { width: 10 }, // Priority
69
+ { width: 14 }, // Testcase type
70
+ { width: 14 }, // Test Result
71
+ { width: 14 }, // Executed Date
72
+ { width: 16 }, // Test Executor
73
+ { width: 38 }, // Test Environment
74
+ { width: 40 }, // Note
75
+ ];
76
+
77
+ // -- Top metadata band (rows 1-4) --
78
+ const titleLabel = `${summary.screen.toUpperCase()} TESTCASE`;
79
+ const titleRow = ws.addRow(['', '', '', titleLabel, '', '', 'No: BM-2-901-13']);
80
+ titleRow.getCell(4).font = { bold: true, size: 16 };
81
+ titleRow.getCell(4).alignment = { horizontal: 'center', vertical: 'middle' };
82
+ ws.mergeCells(titleRow.number, 4, titleRow.number, 6);
83
+ titleRow.getCell(7).font = { bold: true };
84
+ titleRow.height = 22;
85
+
86
+ const versionRow = ws.addRow(['', '', '', '', '', '', `Version: ${getPackageVersion()}`]);
87
+ versionRow.getCell(7).font = { bold: true };
88
+ const dateRow = ws.addRow(['', '', '', '', '', '', `Issue Date: ${issueDate}`]);
89
+ dateRow.getCell(7).font = { bold: true };
90
+ const companyRow = ws.addRow([
91
+ 'SUN ASTERISK VIETNAM CO., LTD',
92
+ '',
93
+ '',
94
+ '',
95
+ '',
96
+ '',
97
+ 'ISO/IEC 27001:2022 & ISO 9001:2015',
98
+ ]);
99
+ companyRow.getCell(1).font = { bold: true };
100
+ companyRow.getCell(7).font = { bold: true };
101
+
102
+ ws.addRow([]); // spacer
103
+
104
+ // -- Summary band --
105
+ const summaryHeader = ws.addRow([
106
+ '',
107
+ '',
108
+ 'Total TCs',
109
+ 'Passed',
110
+ 'Failed',
111
+ 'Pending',
112
+ 'N/A',
113
+ 'Remaining',
114
+ ]);
115
+ for (let i = 3; i <= 8; i++) {
116
+ const c = summaryHeader.getCell(i);
117
+ c.font = { bold: true, color: { argb: HEADER_FONT } };
118
+ c.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: HEADER_FILL } };
119
+ c.alignment = { horizontal: 'center', vertical: 'middle' };
120
+ applyBorder(c);
121
+ }
122
+
123
+ const summaryCounts = ws.addRow([
124
+ '',
125
+ '',
126
+ summary.total,
127
+ summary.passed,
128
+ summary.failed,
129
+ summary.pending,
130
+ summary.na,
131
+ summary.pending + summary.na,
132
+ ]);
133
+ for (let i = 3; i <= 8; i++) {
134
+ const c = summaryCounts.getCell(i);
135
+ c.font = { bold: true };
136
+ c.alignment = { horizontal: 'center' };
137
+ c.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: META_FILL } };
138
+ applyBorder(c);
139
+ }
140
+
141
+ const summaryPct = ws.addRow([
142
+ '',
143
+ '',
144
+ '',
145
+ pct(summary.passed),
146
+ pct(summary.failed),
147
+ pct(summary.pending),
148
+ pct(summary.na),
149
+ pct(summary.pending + summary.na),
150
+ ]);
151
+ for (let i = 4; i <= 8; i++) {
152
+ const c = summaryPct.getCell(i);
153
+ c.alignment = { horizontal: 'center' };
154
+ c.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: META_FILL } };
155
+ applyBorder(c);
156
+ }
157
+
158
+ ws.addRow([]); // spacer
159
+
160
+ const specRow = ws.addRow(['Spec/Design link:', specLink]);
161
+ specRow.getCell(1).font = { bold: true };
162
+ specRow.getCell(2).font = { color: { argb: 'FF1F4E78' }, underline: true };
163
+
164
+ ws.addRow([]); // spacer
165
+
166
+ const legendRow = ws.addRow(['*: Mandatory']);
167
+ legendRow.getCell(1).font = { italic: true, color: { argb: 'FF808080' } };
168
+
169
+ // -- Column header row (bold, filled, wrapped) --
170
+ const headerRow = ws.addRow([
171
+ 'TC ID*',
172
+ '{Category 1}',
173
+ '{Category 2}',
174
+ '{Category 3}',
175
+ '{Category 4}',
176
+ 'Pre-condition',
177
+ 'Test Data',
178
+ 'Steps*',
179
+ 'Expected results*',
180
+ 'Priority',
181
+ 'Testcase type',
182
+ 'Test Result*',
183
+ 'Executed Date*',
184
+ 'Test Executor*',
185
+ 'Test Environment',
186
+ 'Note\n(Test evidence, DefectID, Actual result)',
187
+ ]);
188
+ headerRow.height = 38;
189
+ headerRow.eachCell((cell) => {
190
+ cell.font = { bold: true, color: { argb: HEADER_FONT } };
191
+ cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: HEADER_FILL } };
192
+ cell.alignment = { horizontal: 'center', vertical: 'middle', wrapText: true };
193
+ applyBorder(cell);
194
+ });
195
+
196
+ // Freeze everything above and below the header row — reveal header when scrolling.
197
+ ws.views = [{ state: 'frozen', ySplit: headerRow.number }];
198
+
199
+ // -- Data rows grouped by category --
200
+ const order = ['Accessing', 'GUI', 'Function'];
201
+ const grouped = new Map<string, TestCaseRow[]>();
202
+ for (const r of rows) {
203
+ const g = grouped.get(r.category2) || [];
204
+ g.push(r);
205
+ grouped.set(r.category2, g);
206
+ }
207
+ const emittedGroups = new Set<string>();
208
+
209
+ function emitGroup(groupName: string, groupRows: TestCaseRow[]): void {
210
+ const divider = ws.addRow(['', groupName]);
211
+ divider.getCell(2).font = { bold: true };
212
+ divider.eachCell({ includeEmpty: false }, (cell) => {
213
+ cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: CATEGORY_FILL } };
214
+ });
215
+ // Fill the full width so the band spans the whole table
216
+ for (let i = 1; i <= COL_COUNT; i++) {
217
+ const c = divider.getCell(i);
218
+ c.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: CATEGORY_FILL } };
219
+ applyBorder(c);
220
+ }
221
+
222
+ for (const r of groupRows) {
223
+ const row = ws.addRow([
224
+ r.tcId,
225
+ r.category1,
226
+ '',
227
+ '',
228
+ '',
229
+ r.precondition,
230
+ r.testData,
231
+ r.steps,
232
+ r.expectedResults,
233
+ r.priority,
234
+ r.testcaseType,
235
+ r.testResult,
236
+ r.executedDate,
237
+ r.testExecutor,
238
+ r.testEnvironment,
239
+ r.note,
240
+ ]);
241
+ row.alignment = { vertical: 'top', wrapText: true };
242
+ row.eachCell({ includeEmpty: true }, (cell, colNumber) => {
243
+ applyBorder(cell);
244
+ if (colNumber === 1) {
245
+ cell.font = { bold: true };
246
+ }
247
+ if (colNumber === 12) {
248
+ // Test Result — colour-code
249
+ const val = String(cell.value || '');
250
+ if (val === 'Passed') {
251
+ cell.font = { color: { argb: 'FF107C41' }, bold: true };
252
+ } else if (val === 'Failed') {
253
+ cell.font = { color: { argb: 'FFC00000' }, bold: true };
254
+ } else if (val === 'Pending') {
255
+ cell.font = { color: { argb: 'FF996D00' }, bold: true };
256
+ } else if (val === 'N/A') {
257
+ cell.font = { color: { argb: 'FF808080' } };
258
+ }
259
+ }
260
+ });
261
+ }
262
+ }
263
+
264
+ for (const groupName of order) {
265
+ const groupRows = grouped.get(groupName);
266
+ if (!groupRows || groupRows.length === 0) continue;
267
+ emittedGroups.add(groupName);
268
+ emitGroup(groupName, groupRows);
269
+ }
270
+ for (const [groupName, groupRows] of grouped.entries()) {
271
+ if (emittedGroups.has(groupName)) continue;
272
+ emitGroup(groupName, groupRows);
273
+ }
274
+
275
+ // Auto-filter on the header row so QA can filter by result / priority / category
276
+ ws.autoFilter = {
277
+ from: { row: headerRow.number, column: 1 },
278
+ to: { row: ws.rowCount, column: COL_COUNT },
279
+ };
280
+
281
+ return wb;
282
+ }
283
+
284
+ /**
285
+ * Write the workbook to `qa/deliverables/<screen>-testcases.xlsx`.
286
+ * Directory is created on demand. Returns the absolute output path.
287
+ */
288
+ export async function writeXlsx(
289
+ cwd: string,
290
+ screen: string,
291
+ wb: ExcelJS.Workbook
292
+ ): Promise<string> {
293
+ const outDir = path.join(cwd, 'qa', 'deliverables');
294
+ if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
295
+ const outPath = path.join(outDir, `${screen}-testcases.xlsx`);
296
+ await wb.xlsx.writeFile(outPath);
297
+ return outPath;
298
+ }
299
+
300
+ void applyBorder;
301
+ void ({} as AnyRow);
@@ -34,6 +34,7 @@ export interface ParsedScenario {
34
34
  steps: ParsedStep[];
35
35
  stepsName?: string; // set when scenario has @steps:<name> — marks it as a reusable block
36
36
  extendsName?: string; // set when scenario has @extend:<name> — merges base steps inline
37
+ hookType?: 'beforeAll' | 'afterEach' | 'afterAll'; // set when scenario has @beforeAll/@afterEach/@afterAll
37
38
  }
38
39
 
39
40
  export interface ParsedFeature {
@@ -120,12 +121,15 @@ export class GherkinParser {
120
121
  const tags = scenario.tags.map((tag) => tag.name);
121
122
  const stepsName = tags.find(t => t.startsWith('@steps:'))?.replace('@steps:', '') || undefined;
122
123
  const extendsName = tags.find(t => t.startsWith('@extend:'))?.replace('@extend:', '') || undefined;
124
+ const hookTag = tags.find(t => /^@(beforeAll|afterEach|afterAll)$/.test(t));
125
+ const hookType = hookTag?.replace('@', '') as ParsedScenario['hookType'];
123
126
  return {
124
127
  name: scenario.name,
125
128
  tags,
126
129
  steps: scenario.steps.map((step) => this.parseStep(step)),
127
130
  stepsName,
128
131
  extendsName,
132
+ hookType,
129
133
  };
130
134
  });
131
135
  }
@@ -13,6 +13,14 @@ export interface TestFileData {
13
13
  featureName: string;
14
14
  featureDescription?: string;
15
15
  background?: string;
16
+ beforeAll?: string;
17
+ afterEach?: string;
18
+ afterAll?: string;
19
+ cleanupConfig?: string; // Pre-formatted autoCleanup config from @cleanup:* tags
20
+ screenshotOnFailure?: boolean; // @screenshot:on-failure tag
21
+ runtimeData?: boolean; // --runtime-data flag: testData.get() instead of hardcoded values
22
+ screenName?: string; // Screen name for TestDataLoader.load()
23
+ featureFileName?: string; // Feature file name for TestDataLoader.load()
16
24
  scenarios: string[];
17
25
  authGroups?: AuthGroup[]; // Grouped by auth role for nested describes
18
26
  singleAuthRole?: string; // Auth role when all scenarios share the same role
@@ -49,8 +57,11 @@ export interface TestGeneratorAdapter {
49
57
  // Template rendering methods
50
58
  renderTestFile(data: TestFileData): string;
51
59
  renderScenario(data: ScenarioData): string;
52
- renderImports(): string;
60
+ renderImports(options?: { runtimeData?: boolean }): string;
53
61
  renderBeforeEach(data: { steps: Array<{ comment?: string; code: string }> }): string;
62
+ renderBeforeAll(data: { steps: Array<{ comment?: string; code: string }> }): string;
63
+ renderAfterEach(data: { steps: Array<{ comment?: string; code: string }> }): string;
64
+ renderAfterAll(data: { steps: Array<{ comment?: string; code: string }> }): string;
54
65
 
55
66
  // Step rendering
56
67
  renderStep(templateName: string, data: any): string;
@@ -26,14 +26,26 @@ export class PlaywrightAdapter implements TestGeneratorAdapter {
26
26
  return this.templateEngine.renderScenario(data);
27
27
  }
28
28
 
29
- renderImports(): string {
30
- return this.templateEngine.renderImports();
29
+ renderImports(options?: { runtimeData?: boolean }): string {
30
+ return this.templateEngine.renderImports(options);
31
31
  }
32
32
 
33
33
  renderBeforeEach(data: { steps: Array<{ comment?: string; code: string }> }): string {
34
34
  return this.templateEngine.renderBeforeEach(data);
35
35
  }
36
36
 
37
+ renderBeforeAll(data: { steps: Array<{ comment?: string; code: string }> }): string {
38
+ return this.templateEngine.renderBeforeAll(data);
39
+ }
40
+
41
+ renderAfterEach(data: { steps: Array<{ comment?: string; code: string }> }): string {
42
+ return this.templateEngine.renderAfterEach(data);
43
+ }
44
+
45
+ renderAfterAll(data: { steps: Array<{ comment?: string; code: string }> }): string {
46
+ return this.templateEngine.renderAfterAll(data);
47
+ }
48
+
37
49
  renderStep(templateName: string, data: any): string {
38
50
  return this.templateEngine.renderStep(templateName, data);
39
51
  }
@@ -0,0 +1,8 @@
1
+ test.afterAll(async ({ request }) => {
2
+ {{#each steps}}
3
+ {{#if comment}}
4
+ // {{comment}}
5
+ {{/if}}
6
+ {{code}}
7
+ {{/each}}
8
+ });
@@ -0,0 +1,8 @@
1
+ test.afterEach(async ({ page }) => {
2
+ {{#each steps}}
3
+ {{#if comment}}
4
+ // {{comment}}
5
+ {{/if}}
6
+ {{code}}
7
+ {{/each}}
8
+ });
@@ -0,0 +1,8 @@
1
+ test.beforeAll(async ({ request }) => {
2
+ {{#each steps}}
3
+ {{#if comment}}
4
+ // {{comment}}
5
+ {{/if}}
6
+ {{code}}
7
+ {{/each}}
8
+ });
@@ -1,4 +1,7 @@
1
1
  import { test, expect } from '../base';
2
+ {{#if runtimeData}}
3
+ import { TestDataLoader } from '../test-data';
4
+ {{/if}}
2
5
 
3
6
  // This file is auto-generated from Gherkin feature files
4
7
  // DO NOT EDIT MANUALLY - changes will be overwritten
@@ -1,4 +1,8 @@
1
1
  {{imports}}
2
+ {{#if runtimeData}}
3
+
4
+ const testData = TestDataLoader.load('{{screenName}}', '{{featureFileName}}');
5
+ {{/if}}
2
6
 
3
7
  {{#if featureDescription}}
4
8
  /**
@@ -11,10 +15,30 @@ test.describe('{{featureName}}', () => {
11
15
  {{#if singleAuthRole}}
12
16
  test.use({ storageState: 'specs/.auth/{{singleAuthRole}}.json' });
13
17
 
18
+ {{/if}}
19
+ {{#if cleanupConfig}}
20
+ test.use({ autoCleanup: { {{cleanupConfig}} } });
21
+
22
+ {{/if}}
23
+ {{#if screenshotOnFailure}}
24
+ test.use({ screenshotOnFailure: true });
25
+
26
+ {{/if}}
27
+ {{#if beforeAll}}
28
+ {{beforeAll}}
29
+
14
30
  {{/if}}
15
31
  {{#if background}}
16
32
  {{background}}
17
33
 
34
+ {{/if}}
35
+ {{#if afterEach}}
36
+ {{afterEach}}
37
+
38
+ {{/if}}
39
+ {{#if afterAll}}
40
+ {{afterAll}}
41
+
18
42
  {{/if}}
19
43
  {{#if authGroups}}
20
44
  {{#each authGroups}}