@sun-asterisk/sungen 2.6.8 → 2.6.11
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/dist/cli/commands/dashboard.d.ts +2 -1
- package/dist/cli/commands/dashboard.d.ts.map +1 -1
- package/dist/cli/commands/dashboard.js +9 -9
- package/dist/cli/commands/dashboard.js.map +1 -1
- package/dist/cli/commands/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +33 -0
- package/dist/cli/commands/delivery.js.map +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/history-store.d.ts +13 -9
- package/dist/dashboard/history-store.d.ts.map +1 -1
- package/dist/dashboard/history-store.js +19 -28
- package/dist/dashboard/history-store.js.map +1 -1
- package/dist/dashboard/html-renderer.d.ts +1 -1
- package/dist/dashboard/html-renderer.d.ts.map +1 -1
- package/dist/dashboard/html-renderer.js +2 -2
- package/dist/dashboard/html-renderer.js.map +1 -1
- package/dist/dashboard/snapshot-builder.d.ts.map +1 -1
- package/dist/dashboard/snapshot-builder.js +38 -2
- package/dist/dashboard/snapshot-builder.js.map +1 -1
- package/dist/dashboard/templates/index.html +153 -221
- package/dist/exporters/csv-exporter.d.ts +4 -0
- package/dist/exporters/csv-exporter.d.ts.map +1 -1
- package/dist/exporters/csv-exporter.js +35 -26
- package/dist/exporters/csv-exporter.js.map +1 -1
- package/dist/exporters/feature-parser.d.ts.map +1 -1
- package/dist/exporters/feature-parser.js +16 -4
- package/dist/exporters/feature-parser.js.map +1 -1
- package/dist/exporters/json-exporter.d.ts.map +1 -1
- package/dist/exporters/json-exporter.js +28 -20
- package/dist/exporters/json-exporter.js.map +1 -1
- package/dist/exporters/playwright-report-parser.d.ts.map +1 -1
- package/dist/exporters/playwright-report-parser.js +22 -5
- package/dist/exporters/playwright-report-parser.js.map +1 -1
- package/dist/exporters/scenario-merger.d.ts +23 -1
- package/dist/exporters/scenario-merger.d.ts.map +1 -1
- package/dist/exporters/scenario-merger.js +39 -0
- package/dist/exporters/scenario-merger.js.map +1 -1
- package/dist/exporters/step-formatter.d.ts +31 -3
- package/dist/exporters/step-formatter.d.ts.map +1 -1
- package/dist/exporters/step-formatter.js +52 -19
- package/dist/exporters/step-formatter.js.map +1 -1
- package/dist/exporters/sun-logo.d.ts +10 -0
- package/dist/exporters/sun-logo.d.ts.map +1 -0
- package/dist/exporters/sun-logo.js +13 -0
- package/dist/exporters/sun-logo.js.map +1 -0
- package/dist/exporters/test-data-resolver.d.ts +13 -5
- package/dist/exporters/test-data-resolver.d.ts.map +1 -1
- package/dist/exporters/test-data-resolver.js +36 -14
- package/dist/exporters/test-data-resolver.js.map +1 -1
- package/dist/exporters/types.d.ts +16 -0
- package/dist/exporters/types.d.ts.map +1 -1
- package/dist/exporters/xlsx-exporter.d.ts +6 -0
- package/dist/exporters/xlsx-exporter.d.ts.map +1 -1
- package/dist/exporters/xlsx-exporter.js +204 -100
- package/dist/exporters/xlsx-exporter.js.map +1 -1
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +4 -3
- package/dist/orchestrator/project-initializer.js.map +1 -1
- package/dist/orchestrator/templates/playwright.config.d.ts.map +1 -1
- package/dist/orchestrator/templates/playwright.config.js +2 -0
- package/dist/orchestrator/templates/playwright.config.js.map +1 -1
- package/dist/orchestrator/templates/playwright.config.ts +2 -0
- package/dist/orchestrator/templates/specs-base.d.ts.map +1 -1
- package/dist/orchestrator/templates/specs-base.js +1 -5
- package/dist/orchestrator/templates/specs-base.js.map +1 -1
- package/dist/orchestrator/templates/specs-base.ts +1 -5
- package/package.json +1 -1
- package/src/cli/commands/dashboard.ts +9 -9
- package/src/cli/commands/delivery.ts +30 -0
- package/src/cli/index.ts +1 -1
- package/src/dashboard/history-store.ts +22 -28
- package/src/dashboard/html-renderer.ts +6 -2
- package/src/dashboard/snapshot-builder.ts +36 -2
- package/src/dashboard/templates/index.html +153 -221
- package/src/dashboard/types.ts +1 -1
- package/src/exporters/csv-exporter.ts +44 -27
- package/src/exporters/feature-parser.ts +27 -8
- package/src/exporters/json-exporter.ts +31 -21
- package/src/exporters/playwright-report-parser.ts +23 -5
- package/src/exporters/scenario-merger.ts +65 -1
- package/src/exporters/step-formatter.ts +48 -23
- package/src/exporters/sun-logo.ts +10 -0
- package/src/exporters/test-data-resolver.ts +37 -13
- package/src/exporters/types.ts +18 -1
- package/src/exporters/xlsx-exporter.ts +216 -102
- package/src/orchestrator/project-initializer.ts +4 -3
- package/src/orchestrator/templates/playwright.config.ts +2 -0
- package/src/orchestrator/templates/specs-base.ts +1 -5
package/package.json
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Builds qa/dashboard/index.html — a single-file, share-ready test report —
|
|
5
5
|
* from existing Gherkin features, compiled .spec.ts files, and Playwright
|
|
6
|
-
* results. Snapshots are persisted under qa/dashboard/history/ (
|
|
6
|
+
* results. Snapshots are persisted under qa/dashboard/history/ (never deleted;
|
|
7
|
+
* --max-history only controls how many are included in dashboard stats).
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
10
|
import { Command } from 'commander';
|
|
@@ -68,7 +69,7 @@ export function registerDashboardCommand(program: Command): void {
|
|
|
68
69
|
.description('Build a single-file HTML dashboard summarising all test cases & results')
|
|
69
70
|
.argument('[names...]', 'Specific screen or flow names. Omit to include all.')
|
|
70
71
|
.option('--no-history', 'Do not persist this run under qa/dashboard/history/')
|
|
71
|
-
.option('--max-history <n>', `
|
|
72
|
+
.option('--max-history <n>', `Limit snapshots included in dashboard stats (default: ${DEFAULT_MAX_HISTORY}). Older files are kept on disk.`)
|
|
72
73
|
.option('--open', 'Open the rendered HTML in the default browser when done')
|
|
73
74
|
.action(async (names: string[], options: DashboardOptions) => {
|
|
74
75
|
try {
|
|
@@ -103,18 +104,17 @@ export function registerDashboardCommand(program: Command): void {
|
|
|
103
104
|
`${COLOR.gray}(${snapshot.screens.length} screens, ${snapshot.summary.total} TCs)${COLOR.reset}`
|
|
104
105
|
);
|
|
105
106
|
|
|
106
|
-
// 3. Persist history (unless disabled)
|
|
107
|
+
// 3. Persist history (unless disabled). Files are never deleted —
|
|
108
|
+
// `max` only controls how many feed into the dashboard payload.
|
|
109
|
+
const max = options.maxHistory ? Math.max(1, parseInt(options.maxHistory, 10) || DEFAULT_MAX_HISTORY) : DEFAULT_MAX_HISTORY;
|
|
107
110
|
if (!options.noHistory) {
|
|
108
|
-
const max = options.maxHistory ? Math.max(1, parseInt(options.maxHistory, 10) || DEFAULT_MAX_HISTORY) : DEFAULT_MAX_HISTORY;
|
|
109
111
|
const result = writeSnapshotToHistory(cwd, snapshot, max);
|
|
110
112
|
log(` ${COLOR.green}✓${COLOR.reset} history ${path.relative(cwd, result.written)}`);
|
|
111
|
-
|
|
112
|
-
log(` ${COLOR.gray} pruned ${result.pruned.length} older snapshot(s)${COLOR.reset}`);
|
|
113
|
-
}
|
|
113
|
+
log(` ${COLOR.gray} ${result.total} on disk · ${result.included.length} within limit (${max})${result.archived.length > 0 ? ` · ${result.archived.length} archived (kept)` : ''}${COLOR.reset}`);
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
// 4. Render HTML
|
|
117
|
-
const payload = buildPayload(cwd, snapshot);
|
|
116
|
+
// 4. Render HTML — limit how many history files feed the payload.
|
|
117
|
+
const payload = buildPayload(cwd, snapshot, max);
|
|
118
118
|
const rendered = renderDashboardHtml(cwd, payload);
|
|
119
119
|
log(` ${COLOR.green}✓${COLOR.reset} html ${path.relative(cwd, rendered.outputPath)} ${COLOR.gray}(${(rendered.bytes / 1024 / 1024).toFixed(2)} MB)${COLOR.reset}`);
|
|
120
120
|
|
|
@@ -7,6 +7,7 @@ import { Command } from 'commander';
|
|
|
7
7
|
import * as path from 'path';
|
|
8
8
|
import * as fs from 'fs';
|
|
9
9
|
import { execSync } from 'child_process';
|
|
10
|
+
import { parse as parseYaml } from 'yaml';
|
|
10
11
|
import { parseFeatureMetadata } from '../../exporters/feature-parser';
|
|
11
12
|
import { parseSpecFile } from '../../exporters/spec-parser';
|
|
12
13
|
import { loadTestData } from '../../exporters/test-data-resolver';
|
|
@@ -102,6 +103,26 @@ function resolveTestDataPathForTarget(cwd: string, target: DeliveryTarget): stri
|
|
|
102
103
|
return path.join(qaDir(cwd, target), 'test-data', `${target.name}.yaml`);
|
|
103
104
|
}
|
|
104
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Flows record their entry-point URL inside selectors/<name>.yaml under the
|
|
108
|
+
* page-selector block (entry with `type: 'page'`). Return its `value`, or
|
|
109
|
+
* undefined when the file is missing or has no page selector.
|
|
110
|
+
*/
|
|
111
|
+
function readFlowPagePath(selectorsFile: string): string | undefined {
|
|
112
|
+
if (!fs.existsSync(selectorsFile)) return undefined;
|
|
113
|
+
try {
|
|
114
|
+
const parsed = parseYaml(fs.readFileSync(selectorsFile, 'utf-8'));
|
|
115
|
+
if (!parsed || typeof parsed !== 'object') return undefined;
|
|
116
|
+
for (const v of Object.values(parsed as Record<string, unknown>)) {
|
|
117
|
+
if (v && typeof v === 'object' && (v as { type?: string }).type === 'page') {
|
|
118
|
+
const value = (v as { value?: unknown }).value;
|
|
119
|
+
if (typeof value === 'string' && value.length > 0) return value;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
} catch { /* malformed YAML — ignore */ }
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
|
|
105
126
|
function runPreflight(cwd: string, target: DeliveryTarget): PreflightCheck {
|
|
106
127
|
const base = qaDir(cwd, target);
|
|
107
128
|
const genBase = generatedDir(cwd, target);
|
|
@@ -240,9 +261,18 @@ async function exportTarget(
|
|
|
240
261
|
const results = resultsFile ? loadPlaywrightReport(resultsFile) : null;
|
|
241
262
|
|
|
242
263
|
const merged = mergeFeatureAndSpec(feature, spec);
|
|
264
|
+
// Screens get their URL path from the .feature file directly. Flows record
|
|
265
|
+
// it in selectors/<name>.yaml under the page-selector block — fall back to
|
|
266
|
+
// that so the Environment column shows a full URL for flows too.
|
|
267
|
+
let featurePath = feature.featurePath;
|
|
268
|
+
if (!featurePath && target.isFlow) {
|
|
269
|
+
const selectorsFile = path.join(base, 'selectors', `${target.name}.yaml`);
|
|
270
|
+
featurePath = readFlowPagePath(selectorsFile);
|
|
271
|
+
}
|
|
243
272
|
const rows = buildTestCaseRows({
|
|
244
273
|
screen: label,
|
|
245
274
|
featureName: feature.featureName,
|
|
275
|
+
featurePath,
|
|
246
276
|
merged,
|
|
247
277
|
testData,
|
|
248
278
|
results,
|
package/src/cli/index.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Persist dashboard snapshots under qa/dashboard/history
|
|
3
|
-
* max
|
|
4
|
-
*
|
|
2
|
+
* Persist dashboard snapshots under qa/dashboard/history/. Files are never
|
|
3
|
+
* deleted — `max` only controls how many are *included* in dashboard stats /
|
|
4
|
+
* payload. Older files beyond `max` are retained on disk as `archived` so
|
|
5
|
+
* trends / compare data is recoverable.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
import * as fs from 'fs';
|
|
@@ -12,8 +13,9 @@ export const DEFAULT_MAX_HISTORY = 20;
|
|
|
12
13
|
|
|
13
14
|
export interface HistoryWriteResult {
|
|
14
15
|
written: string; // absolute path written
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
total: number; // total files on disk (after write)
|
|
17
|
+
included: string[]; // newest → oldest, capped at max
|
|
18
|
+
archived: string[]; // files beyond max, still on disk
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
export function historyDir(cwd: string): string {
|
|
@@ -21,8 +23,8 @@ export function historyDir(cwd: string): string {
|
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
/**
|
|
24
|
-
* Write `snapshot` as <runId>.json
|
|
25
|
-
*
|
|
26
|
+
* Write `snapshot` as <runId>.json. Never deletes existing files; `max` only
|
|
27
|
+
* partitions the on-disk list into `included` (newest N) vs `archived`.
|
|
26
28
|
*/
|
|
27
29
|
export function writeSnapshotToHistory(
|
|
28
30
|
cwd: string,
|
|
@@ -36,10 +38,10 @@ export function writeSnapshotToHistory(
|
|
|
36
38
|
const target = path.join(dir, filename);
|
|
37
39
|
fs.writeFileSync(target, JSON.stringify(snapshot, null, 2), 'utf-8');
|
|
38
40
|
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
return { written: target,
|
|
41
|
+
const all = listHistoryFiles(dir); // newest first
|
|
42
|
+
const included = all.slice(0, max);
|
|
43
|
+
const archived = all.slice(max);
|
|
44
|
+
return { written: target, total: all.length, included, archived };
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
/**
|
|
@@ -54,11 +56,17 @@ export function listHistoryFiles(dir: string): string[] {
|
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
/**
|
|
57
|
-
* Read
|
|
59
|
+
* Read history JSONs, oldest → newest. When `limit` is provided, only the
|
|
60
|
+
* newest `limit` files are loaded (older ones stay on disk but are skipped).
|
|
61
|
+
* Skips files that fail to parse.
|
|
58
62
|
*/
|
|
59
|
-
export function readHistory(cwd: string): DashboardSnapshot[] {
|
|
63
|
+
export function readHistory(cwd: string, limit?: number): DashboardSnapshot[] {
|
|
60
64
|
const dir = historyDir(cwd);
|
|
61
|
-
|
|
65
|
+
let files = listHistoryFiles(dir); // newest first
|
|
66
|
+
if (typeof limit === 'number' && limit >= 0) {
|
|
67
|
+
files = files.slice(0, limit);
|
|
68
|
+
}
|
|
69
|
+
files = files.reverse(); // oldest → newest
|
|
62
70
|
const out: DashboardSnapshot[] = [];
|
|
63
71
|
for (const f of files) {
|
|
64
72
|
try {
|
|
@@ -70,17 +78,3 @@ export function readHistory(cwd: string): DashboardSnapshot[] {
|
|
|
70
78
|
}
|
|
71
79
|
return out;
|
|
72
80
|
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Delete oldest files in `dir` until at most `max` remain.
|
|
76
|
-
* Returns the absolute paths removed.
|
|
77
|
-
*/
|
|
78
|
-
function pruneHistory(dir: string, max: number): string[] {
|
|
79
|
-
const files = listHistoryFiles(dir); // newest first
|
|
80
|
-
if (files.length <= max) return [];
|
|
81
|
-
const toRemove = files.slice(max);
|
|
82
|
-
for (const f of toRemove) {
|
|
83
|
-
try { fs.unlinkSync(f); } catch { /* ignore */ }
|
|
84
|
-
}
|
|
85
|
-
return toRemove;
|
|
86
|
-
}
|
|
@@ -42,8 +42,12 @@ export function resolveTemplatePath(): string {
|
|
|
42
42
|
);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
export function buildPayload(
|
|
46
|
-
|
|
45
|
+
export function buildPayload(
|
|
46
|
+
cwd: string,
|
|
47
|
+
current: DashboardSnapshot,
|
|
48
|
+
historyLimit?: number,
|
|
49
|
+
): DashboardPayload {
|
|
50
|
+
const all = readHistory(cwd, historyLimit);
|
|
47
51
|
// Exclude `current` from history if it's been written there already.
|
|
48
52
|
const history = all.filter((s) => s.runId !== current.runId);
|
|
49
53
|
return { current, history };
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import * as fs from 'fs';
|
|
15
15
|
import * as path from 'path';
|
|
16
16
|
import { execSync } from 'child_process';
|
|
17
|
+
import { parse as parseYaml } from 'yaml';
|
|
17
18
|
import { parseFeatureMetadata } from '../exporters/feature-parser';
|
|
18
19
|
import { parseSpecFile } from '../exporters/spec-parser';
|
|
19
20
|
import { loadTestData } from '../exporters/test-data-resolver';
|
|
@@ -99,11 +100,20 @@ function buildOneScreen(
|
|
|
99
100
|
const label = target.isFlow ? `flow/${target.name}` : target.name;
|
|
100
101
|
const specLink = fs.existsSync(specMdFile) ? path.relative(cwd, specMdFile) : undefined;
|
|
101
102
|
|
|
103
|
+
// Screens declare their URL inline in the .feature ("Path: /awards"). Flows
|
|
104
|
+
// don't have one there, so fall back to the first `type: 'page'` entry in
|
|
105
|
+
// the flow's selectors YAML — that's where the entry point is recorded.
|
|
106
|
+
let featurePath = feature.featurePath;
|
|
107
|
+
if (!featurePath && target.isFlow) {
|
|
108
|
+
const selectorsFile = path.join(base, 'selectors', `${target.name}.yaml`);
|
|
109
|
+
featurePath = readPagePathFromSelectors(selectorsFile);
|
|
110
|
+
}
|
|
111
|
+
|
|
102
112
|
return buildScreenSnapshot({
|
|
103
113
|
screen: label,
|
|
104
114
|
isFlow: target.isFlow,
|
|
105
115
|
featureName: feature.featureName,
|
|
106
|
-
featurePath
|
|
116
|
+
featurePath,
|
|
107
117
|
specLink,
|
|
108
118
|
merged,
|
|
109
119
|
testData,
|
|
@@ -125,7 +135,7 @@ function aggregateSummary(screens: ScreenSnapshot[]): AggregateSummary {
|
|
|
125
135
|
na: 0,
|
|
126
136
|
notCompiled: 0,
|
|
127
137
|
passRate: 0,
|
|
128
|
-
byPriority: {
|
|
138
|
+
byPriority: { High: 0, Normal: 0, Low: 0 },
|
|
129
139
|
byCategory: { Accessing: 0, GUI: 0, Function: 0 },
|
|
130
140
|
byType: { Auto: 0, Manual: 0, 'Not compiled': 0 },
|
|
131
141
|
};
|
|
@@ -269,5 +279,29 @@ function collectEnvironment(cwd: string, env: EnvironmentInfo) {
|
|
|
269
279
|
};
|
|
270
280
|
}
|
|
271
281
|
|
|
282
|
+
/**
|
|
283
|
+
* Read the entry-point path for a flow from its selectors YAML. We pick the
|
|
284
|
+
* first selector whose `type` is `page` — that's the convention sungen uses
|
|
285
|
+
* for the page-selector block in flow YAML files. Returns undefined when the
|
|
286
|
+
* file is missing or has no page entry.
|
|
287
|
+
*/
|
|
288
|
+
function readPagePathFromSelectors(selectorsFile: string): string | undefined {
|
|
289
|
+
if (!fs.existsSync(selectorsFile)) return undefined;
|
|
290
|
+
try {
|
|
291
|
+
const raw = fs.readFileSync(selectorsFile, 'utf-8');
|
|
292
|
+
const parsed = parseYaml(raw);
|
|
293
|
+
if (!parsed || typeof parsed !== 'object') return undefined;
|
|
294
|
+
for (const value of Object.values(parsed as Record<string, unknown>)) {
|
|
295
|
+
if (value && typeof value === 'object' && (value as { type?: string }).type === 'page') {
|
|
296
|
+
const v = (value as { value?: unknown }).value;
|
|
297
|
+
if (typeof v === 'string' && v.length > 0) return v;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
} catch {
|
|
301
|
+
// Malformed YAML — fall through to undefined.
|
|
302
|
+
}
|
|
303
|
+
return undefined;
|
|
304
|
+
}
|
|
305
|
+
|
|
272
306
|
// Helper for ScenarioSnapshot tree filters in UI (re-exported for callers)
|
|
273
307
|
export type { ScenarioSnapshot };
|