@sun-asterisk/sungen 2.6.0 → 2.6.2

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 (104) hide show
  1. package/dist/cli/commands/dashboard.d.ts +10 -0
  2. package/dist/cli/commands/dashboard.d.ts.map +1 -0
  3. package/dist/cli/commands/dashboard.js +171 -0
  4. package/dist/cli/commands/dashboard.js.map +1 -0
  5. package/dist/cli/index.js +4 -2
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/dashboard/history-store.d.ts +27 -0
  8. package/dist/dashboard/history-store.d.ts.map +1 -0
  9. package/dist/dashboard/history-store.js +112 -0
  10. package/dist/dashboard/history-store.js.map +1 -0
  11. package/dist/dashboard/html-renderer.d.ts +30 -0
  12. package/dist/dashboard/html-renderer.d.ts.map +1 -0
  13. package/dist/dashboard/html-renderer.js +111 -0
  14. package/dist/dashboard/html-renderer.js.map +1 -0
  15. package/dist/dashboard/snapshot-builder.d.ts +30 -0
  16. package/dist/dashboard/snapshot-builder.d.ts.map +1 -0
  17. package/dist/dashboard/snapshot-builder.js +263 -0
  18. package/dist/dashboard/snapshot-builder.js.map +1 -0
  19. package/dist/dashboard/templates/index.html +287 -0
  20. package/dist/dashboard/types.d.ts +122 -0
  21. package/dist/dashboard/types.d.ts.map +1 -0
  22. package/dist/dashboard/types.js +11 -0
  23. package/dist/dashboard/types.js.map +1 -0
  24. package/dist/exporters/json-exporter.d.ts +25 -0
  25. package/dist/exporters/json-exporter.d.ts.map +1 -0
  26. package/dist/exporters/json-exporter.js +135 -0
  27. package/dist/exporters/json-exporter.js.map +1 -0
  28. package/dist/exporters/playwright-report-parser.d.ts +2 -1
  29. package/dist/exporters/playwright-report-parser.d.ts.map +1 -1
  30. package/dist/exporters/playwright-report-parser.js +12 -5
  31. package/dist/exporters/playwright-report-parser.js.map +1 -1
  32. package/dist/exporters/spec-parser.d.ts.map +1 -1
  33. package/dist/exporters/spec-parser.js +8 -3
  34. package/dist/exporters/spec-parser.js.map +1 -1
  35. package/dist/generators/test-generator/adapters/adapter-interface.d.ts +15 -0
  36. package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
  37. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +2 -0
  38. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
  39. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
  40. package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +2 -1
  41. package/dist/generators/test-generator/adapters/playwright/templates/scenario.hbs +20 -1
  42. package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +84 -4
  43. package/dist/generators/test-generator/code-generator.d.ts +1 -0
  44. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  45. package/dist/generators/test-generator/code-generator.js +76 -6
  46. package/dist/generators/test-generator/code-generator.js.map +1 -1
  47. package/dist/generators/test-generator/patterns/interaction-patterns.d.ts.map +1 -1
  48. package/dist/generators/test-generator/patterns/interaction-patterns.js +22 -3
  49. package/dist/generators/test-generator/patterns/interaction-patterns.js.map +1 -1
  50. package/dist/generators/test-generator/patterns/navigation-patterns.d.ts.map +1 -1
  51. package/dist/generators/test-generator/patterns/navigation-patterns.js +8 -3
  52. package/dist/generators/test-generator/patterns/navigation-patterns.js.map +1 -1
  53. package/dist/generators/test-generator/template-engine.d.ts +13 -0
  54. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  55. package/dist/generators/test-generator/template-engine.js +1 -1
  56. package/dist/generators/test-generator/template-engine.js.map +1 -1
  57. package/dist/orchestrator/screen-manager.d.ts.map +1 -1
  58. package/dist/orchestrator/screen-manager.js +3 -1
  59. package/dist/orchestrator/screen-manager.js.map +1 -1
  60. package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +70 -10
  61. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +23 -0
  62. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +70 -10
  63. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +23 -0
  64. package/dist/orchestrator/templates/playwright.config.d.ts.map +1 -1
  65. package/dist/orchestrator/templates/playwright.config.js +9 -1
  66. package/dist/orchestrator/templates/playwright.config.js.map +1 -1
  67. package/dist/orchestrator/templates/playwright.config.ts +11 -1
  68. package/dist/orchestrator/templates/specs-base.d.ts +3 -4
  69. package/dist/orchestrator/templates/specs-base.d.ts.map +1 -1
  70. package/dist/orchestrator/templates/specs-base.js +53 -39
  71. package/dist/orchestrator/templates/specs-base.js.map +1 -1
  72. package/dist/orchestrator/templates/specs-base.ts +55 -45
  73. package/dist/orchestrator/templates/specs-test-data.d.ts.map +1 -1
  74. package/dist/orchestrator/templates/specs-test-data.js +43 -0
  75. package/dist/orchestrator/templates/specs-test-data.js.map +1 -1
  76. package/dist/orchestrator/templates/specs-test-data.ts +47 -0
  77. package/package.json +4 -3
  78. package/src/cli/commands/dashboard.ts +158 -0
  79. package/src/cli/index.ts +4 -2
  80. package/src/dashboard/history-store.ts +86 -0
  81. package/src/dashboard/html-renderer.ts +90 -0
  82. package/src/dashboard/snapshot-builder.ts +273 -0
  83. package/src/dashboard/templates/index.html +287 -0
  84. package/src/dashboard/types.ts +148 -0
  85. package/src/exporters/json-exporter.ts +162 -0
  86. package/src/exporters/playwright-report-parser.ts +12 -5
  87. package/src/exporters/spec-parser.ts +8 -3
  88. package/src/generators/test-generator/adapters/adapter-interface.ts +6 -1
  89. package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +1 -1
  90. package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +2 -1
  91. package/src/generators/test-generator/adapters/playwright/templates/scenario.hbs +20 -1
  92. package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +84 -4
  93. package/src/generators/test-generator/code-generator.ts +88 -7
  94. package/src/generators/test-generator/patterns/interaction-patterns.ts +25 -3
  95. package/src/generators/test-generator/patterns/navigation-patterns.ts +8 -3
  96. package/src/generators/test-generator/template-engine.ts +5 -2
  97. package/src/orchestrator/screen-manager.ts +3 -1
  98. package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +70 -10
  99. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +23 -0
  100. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +70 -10
  101. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +23 -0
  102. package/src/orchestrator/templates/playwright.config.ts +11 -1
  103. package/src/orchestrator/templates/specs-base.ts +55 -45
  104. package/src/orchestrator/templates/specs-test-data.ts +47 -0
@@ -0,0 +1,273 @@
1
+ /**
2
+ * Orchestrate dashboard snapshot construction across all screens/flows.
3
+ *
4
+ * Inputs are read from the same on-disk sources used by `sungen delivery`:
5
+ * qa/screens/<name>/... feature, test-data, selectors, requirements
6
+ * qa/flows/<name>/...
7
+ * specs/generated/<...>/... compiled .spec.ts + per-target test-result.json
8
+ * test-results/results.json global fallback
9
+ *
10
+ * Output is a DashboardSnapshot ready to be embedded in HTML or written as
11
+ * a history entry under qa/dashboard/history/<runId>.json.
12
+ */
13
+
14
+ import * as fs from 'fs';
15
+ import * as path from 'path';
16
+ import { execSync } from 'child_process';
17
+ import { parseFeatureMetadata } from '../exporters/feature-parser';
18
+ import { parseSpecFile } from '../exporters/spec-parser';
19
+ import { loadTestData } from '../exporters/test-data-resolver';
20
+ import { loadPlaywrightReport } from '../exporters/playwright-report-parser';
21
+ import { mergeFeatureAndSpec } from '../exporters/scenario-merger';
22
+ import { buildScreenSnapshot } from '../exporters/json-exporter';
23
+ import { getPackageVersion } from '../exporters/package-info';
24
+ import { EnvironmentInfo } from '../exporters/types';
25
+ import {
26
+ AggregateSummary,
27
+ DashboardSnapshot,
28
+ ScenarioSnapshot,
29
+ ScreenSnapshot,
30
+ SNAPSHOT_VERSION,
31
+ } from './types';
32
+
33
+ export interface DashboardTarget {
34
+ name: string;
35
+ isFlow: boolean;
36
+ }
37
+
38
+ export interface BuildSnapshotOptions {
39
+ cwd: string;
40
+ targets: DashboardTarget[];
41
+ env: EnvironmentInfo;
42
+ /** Skip targets where the .feature/.spec.ts pair fails to parse. */
43
+ continueOnError?: boolean;
44
+ }
45
+
46
+ export function buildDashboardSnapshot(options: BuildSnapshotOptions): DashboardSnapshot {
47
+ const { cwd, targets, env } = options;
48
+ const screens: ScreenSnapshot[] = [];
49
+
50
+ for (const target of targets) {
51
+ try {
52
+ const screen = buildOneScreen(cwd, target, env);
53
+ if (screen) screens.push(screen);
54
+ } catch (err) {
55
+ if (!options.continueOnError) throw err;
56
+ // eslint-disable-next-line no-console
57
+ console.warn(`[dashboard] skipping ${target.name}: ${(err as Error).message}`);
58
+ }
59
+ }
60
+
61
+ const now = new Date();
62
+ return {
63
+ version: SNAPSHOT_VERSION,
64
+ runId: makeRunId(now),
65
+ generatedAt: toLocalIso(now),
66
+ environment: collectEnvironment(cwd, env),
67
+ summary: aggregateSummary(screens),
68
+ screens: screens.sort((a, b) => a.name.localeCompare(b.name)),
69
+ };
70
+ }
71
+
72
+ // ----------------------------------------------------------------------------
73
+ // Per-target build
74
+ // ----------------------------------------------------------------------------
75
+
76
+ function buildOneScreen(
77
+ cwd: string,
78
+ target: DashboardTarget,
79
+ env: EnvironmentInfo
80
+ ): ScreenSnapshot | null {
81
+ const base = qaDir(cwd, target);
82
+ const genBase = generatedDir(cwd, target);
83
+ const featureFile = path.join(base, 'features', `${target.name}.feature`);
84
+ const testDataFile = path.join(base, 'test-data', `${target.name}.yaml`);
85
+ const specFile = path.join(genBase, `${target.name}.spec.ts`);
86
+ const specMdFile = path.join(base, 'requirements', 'spec.md');
87
+ const resultsFile = resolveResultsPath(cwd, target);
88
+
89
+ if (!fs.existsSync(featureFile)) return null;
90
+
91
+ const feature = parseFeatureMetadata(featureFile);
92
+ const spec = fs.existsSync(specFile)
93
+ ? parseSpecFile(specFile)
94
+ : { tests: [] };
95
+ const testData = fs.existsSync(testDataFile) ? loadTestData(testDataFile) : {};
96
+ const results = resultsFile ? loadPlaywrightReport(resultsFile) : null;
97
+
98
+ const merged = mergeFeatureAndSpec(feature, spec);
99
+ const label = target.isFlow ? `flow/${target.name}` : target.name;
100
+ const specLink = fs.existsSync(specMdFile) ? path.relative(cwd, specMdFile) : undefined;
101
+
102
+ return buildScreenSnapshot({
103
+ screen: label,
104
+ isFlow: target.isFlow,
105
+ featureName: feature.featureName,
106
+ featurePath: feature.featurePath,
107
+ specLink,
108
+ merged,
109
+ testData,
110
+ results,
111
+ env,
112
+ });
113
+ }
114
+
115
+ // ----------------------------------------------------------------------------
116
+ // Aggregate summary across all screens
117
+ // ----------------------------------------------------------------------------
118
+
119
+ function aggregateSummary(screens: ScreenSnapshot[]): AggregateSummary {
120
+ const summary: AggregateSummary = {
121
+ total: 0,
122
+ passed: 0,
123
+ failed: 0,
124
+ pending: 0,
125
+ na: 0,
126
+ notCompiled: 0,
127
+ passRate: 0,
128
+ byPriority: { Critical: 0, High: 0, Normal: 0, Low: 0 },
129
+ byCategory: { Accessing: 0, GUI: 0, Function: 0 },
130
+ byType: { Auto: 0, Manual: 0, 'Not compiled': 0 },
131
+ };
132
+
133
+ for (const screen of screens) {
134
+ summary.total += screen.summary.total;
135
+ summary.passed += screen.summary.passed;
136
+ summary.failed += screen.summary.failed;
137
+ summary.pending += screen.summary.pending;
138
+ summary.na += screen.summary.na;
139
+ summary.notCompiled += screen.summary.notCompiled;
140
+
141
+ for (const s of screen.scenarios) {
142
+ bumpKey(summary.byPriority, s.priority);
143
+ bumpKey(summary.byCategory, s.category2);
144
+ bumpKey(summary.byType, s.type);
145
+ }
146
+ }
147
+
148
+ const executed = summary.passed + summary.failed;
149
+ summary.passRate = executed > 0 ? summary.passed / executed : 0;
150
+ return summary;
151
+ }
152
+
153
+ function bumpKey(record: Record<string, number>, key: string): void {
154
+ record[key] = (record[key] ?? 0) + 1;
155
+ }
156
+
157
+ // ----------------------------------------------------------------------------
158
+ // Path helpers (mirror delivery.ts to stay consistent)
159
+ // ----------------------------------------------------------------------------
160
+
161
+ function qaDir(cwd: string, target: DashboardTarget): string {
162
+ return path.join(cwd, 'qa', target.isFlow ? 'flows' : 'screens', target.name);
163
+ }
164
+
165
+ function generatedDir(cwd: string, target: DashboardTarget): string {
166
+ return target.isFlow
167
+ ? path.join(cwd, 'specs', 'generated', 'flows', target.name)
168
+ : path.join(cwd, 'specs', 'generated', target.name);
169
+ }
170
+
171
+ function resolveResultsPath(cwd: string, target: DashboardTarget): string | null {
172
+ const genDir = generatedDir(cwd, target);
173
+ const perTarget = path.join(genDir, `${target.name}-test-result.json`);
174
+ if (fs.existsSync(perTarget)) return perTarget;
175
+ const global = path.join(cwd, 'test-results', 'results.json');
176
+ if (fs.existsSync(global)) return global;
177
+ return null;
178
+ }
179
+
180
+ // ----------------------------------------------------------------------------
181
+ // Discovery (also used by CLI)
182
+ // ----------------------------------------------------------------------------
183
+
184
+ export function listDashboardTargets(cwd: string): DashboardTarget[] {
185
+ const targets: DashboardTarget[] = [];
186
+
187
+ const screensDir = path.join(cwd, 'qa', 'screens');
188
+ if (fs.existsSync(screensDir)) {
189
+ for (const d of fs.readdirSync(screensDir, { withFileTypes: true })) {
190
+ if (d.isDirectory()) targets.push({ name: d.name, isFlow: false });
191
+ }
192
+ }
193
+
194
+ const flowsDir = path.join(cwd, 'qa', 'flows');
195
+ if (fs.existsSync(flowsDir)) {
196
+ for (const d of fs.readdirSync(flowsDir, { withFileTypes: true })) {
197
+ if (d.isDirectory()) targets.push({ name: d.name, isFlow: true });
198
+ }
199
+ }
200
+
201
+ return targets.sort((a, b) => a.name.localeCompare(b.name));
202
+ }
203
+
204
+ export function resolveTargetType(cwd: string, name: string): DashboardTarget {
205
+ if (fs.existsSync(path.join(cwd, 'qa', 'flows', name))) {
206
+ return { name, isFlow: true };
207
+ }
208
+ return { name, isFlow: false };
209
+ }
210
+
211
+ // ----------------------------------------------------------------------------
212
+ // Run ID + environment metadata (Vietnam Indochina Time, UTC+7, no DST)
213
+ // ----------------------------------------------------------------------------
214
+
215
+ const VN_OFFSET_MS = 7 * 60 * 60 * 1000;
216
+
217
+ /**
218
+ * Filename-safe ISO timestamp in Vietnam time (UTC+7).
219
+ * Example: 2026-04-29T08-13-53+0700
220
+ *
221
+ * Colons in HH:MM:SS are replaced with `-` (filename-safe on Windows too).
222
+ * The `+0700` suffix makes the offset explicit so consumers don't need
223
+ * to assume a default timezone.
224
+ */
225
+ function makeRunId(now: Date): string {
226
+ const vn = new Date(now.getTime() + VN_OFFSET_MS);
227
+ const yyyy = vn.getUTCFullYear();
228
+ const MM = String(vn.getUTCMonth() + 1).padStart(2, '0');
229
+ const dd = String(vn.getUTCDate()).padStart(2, '0');
230
+ const HH = String(vn.getUTCHours()).padStart(2, '0');
231
+ const mm = String(vn.getUTCMinutes()).padStart(2, '0');
232
+ const ss = String(vn.getUTCSeconds()).padStart(2, '0');
233
+ return `${yyyy}-${MM}-${dd}T${HH}-${mm}-${ss}+0700`;
234
+ }
235
+
236
+ /**
237
+ * Full ISO 8601 timestamp in Vietnam time, with milliseconds and `+07:00` offset.
238
+ * Example: 2026-04-29T08:13:53.495+07:00
239
+ */
240
+ function toLocalIso(now: Date): string {
241
+ const vn = new Date(now.getTime() + VN_OFFSET_MS);
242
+ const yyyy = vn.getUTCFullYear();
243
+ const MM = String(vn.getUTCMonth() + 1).padStart(2, '0');
244
+ const dd = String(vn.getUTCDate()).padStart(2, '0');
245
+ const HH = String(vn.getUTCHours()).padStart(2, '0');
246
+ const mm = String(vn.getUTCMinutes()).padStart(2, '0');
247
+ const ss = String(vn.getUTCSeconds()).padStart(2, '0');
248
+ const ms = String(vn.getUTCMilliseconds()).padStart(3, '0');
249
+ return `${yyyy}-${MM}-${dd}T${HH}:${mm}:${ss}.${ms}+07:00`;
250
+ }
251
+
252
+ function collectEnvironment(cwd: string, env: EnvironmentInfo) {
253
+ let gitBranch: string | undefined;
254
+ let gitSha: string | undefined;
255
+ try {
256
+ gitBranch = execSync('git rev-parse --abbrev-ref HEAD', { cwd, encoding: 'utf-8' }).trim();
257
+ } catch { /* ignore */ }
258
+ try {
259
+ gitSha = execSync('git rev-parse --short HEAD', { cwd, encoding: 'utf-8' }).trim();
260
+ } catch { /* ignore */ }
261
+
262
+ return {
263
+ baseURL: env.baseURL,
264
+ projectName: env.projectName,
265
+ executor: env.executor,
266
+ sungenVersion: getPackageVersion(),
267
+ gitBranch,
268
+ gitSha,
269
+ };
270
+ }
271
+
272
+ // Helper for ScenarioSnapshot tree filters in UI (re-exported for callers)
273
+ export type { ScenarioSnapshot };