@sun-asterisk/sungen 2.6.8 → 2.6.10
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 +142 -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.map +1 -1
- package/dist/exporters/xlsx-exporter.js +169 -99
- package/dist/exporters/xlsx-exporter.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 +142 -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 +176 -101
- package/src/orchestrator/templates/playwright.config.ts +2 -0
- package/src/orchestrator/templates/specs-base.ts +1 -5
|
@@ -8,7 +8,11 @@ import { parse as parseYaml } from 'yaml';
|
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Load test-data.yaml into a flat key→value map.
|
|
11
|
-
*
|
|
11
|
+
* Nested objects are flattened with dot notation so flow files like
|
|
12
|
+
* kudos:
|
|
13
|
+
* recipient: "An"
|
|
14
|
+
* become { "kudos.recipient": "An" }, matching {{kudos.recipient}} refs.
|
|
15
|
+
* Arrays are JSON-stringified. Returns empty map if file missing.
|
|
12
16
|
*/
|
|
13
17
|
export function loadTestData(testDataFilePath: string): Record<string, string> {
|
|
14
18
|
if (!fs.existsSync(testDataFilePath)) return {};
|
|
@@ -16,38 +20,58 @@ export function loadTestData(testDataFilePath: string): Record<string, string> {
|
|
|
16
20
|
const parsed = parseYaml(content);
|
|
17
21
|
if (!parsed || typeof parsed !== 'object') return {};
|
|
18
22
|
|
|
19
|
-
// Flatten shallow object to string values
|
|
20
23
|
const result: Record<string, string> = {};
|
|
21
|
-
|
|
24
|
+
flatten(parsed as Record<string, unknown>, '', result);
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function flatten(obj: Record<string, unknown>, prefix: string, out: Record<string, string>): void {
|
|
29
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
22
30
|
if (value === null || value === undefined) continue;
|
|
23
|
-
|
|
24
|
-
|
|
31
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
32
|
+
if (Array.isArray(value)) {
|
|
33
|
+
out[path] = JSON.stringify(value);
|
|
34
|
+
} else if (typeof value === 'object') {
|
|
35
|
+
flatten(value as Record<string, unknown>, path, out);
|
|
36
|
+
} else {
|
|
37
|
+
out[path] = String(value);
|
|
38
|
+
}
|
|
25
39
|
}
|
|
26
|
-
return result;
|
|
27
40
|
}
|
|
28
41
|
|
|
29
42
|
/**
|
|
30
|
-
* Format a list of variable references into a "key: value
|
|
31
|
-
* suitable for the CSV Test Data column.
|
|
43
|
+
* Format a list of variable references into a "key: value" string.
|
|
32
44
|
*
|
|
33
|
-
*
|
|
45
|
+
* - `separator` controls how pairs are joined. Default `'; '` keeps CSV/XLSX
|
|
46
|
+
* cells on one line. Pass `'\n'` for surfaces that render multi-line text
|
|
47
|
+
* (e.g. the dashboard modal uses whitespace-pre-wrap).
|
|
48
|
+
* - Test data is never truncated by default — values represent real test
|
|
49
|
+
* inputs and clients need to see them in full. Pass a finite `maxLen` only
|
|
50
|
+
* if the downstream surface requires a cap (e.g. terminal preview).
|
|
34
51
|
*/
|
|
35
52
|
export function formatTestData(
|
|
36
53
|
referencedVars: string[],
|
|
37
|
-
testData: Record<string, string
|
|
54
|
+
testData: Record<string, string>,
|
|
55
|
+
maxLen: number = Infinity,
|
|
56
|
+
separator: string = '; ',
|
|
38
57
|
): string {
|
|
39
58
|
if (referencedVars.length === 0) return '';
|
|
59
|
+
// When joining with newlines (dashboard multi-line view), prefix each line
|
|
60
|
+
// with a bullet point so the list scans easily. CSV-style "; " join skips
|
|
61
|
+
// the bullet — single-cell text shouldn't have list markers.
|
|
62
|
+
const useBullet = separator.includes('\n');
|
|
40
63
|
const pairs: string[] = [];
|
|
41
64
|
for (const key of referencedVars) {
|
|
42
65
|
const value = testData[key];
|
|
43
66
|
if (value === undefined) continue;
|
|
44
|
-
|
|
67
|
+
const line = `${key}: ${truncate(value, maxLen)}`;
|
|
68
|
+
pairs.push(useBullet ? `• ${line}` : line);
|
|
45
69
|
}
|
|
46
|
-
return pairs.join(
|
|
70
|
+
return pairs.join(separator);
|
|
47
71
|
}
|
|
48
72
|
|
|
49
73
|
function truncate(s: string, max: number): string {
|
|
50
|
-
if (s.length <= max) return s;
|
|
74
|
+
if (!isFinite(max) || s.length <= max) return s;
|
|
51
75
|
return s.substring(0, max - 1) + '…';
|
|
52
76
|
}
|
|
53
77
|
|
package/src/exporters/types.ts
CHANGED
|
@@ -15,7 +15,7 @@ export interface TestCaseRow {
|
|
|
15
15
|
testData: string; // semicolon-separated key: value
|
|
16
16
|
steps: string; // numbered steps
|
|
17
17
|
expectedResults: string; // numbered expected results
|
|
18
|
-
priority: string; //
|
|
18
|
+
priority: string; // High | Normal | Low
|
|
19
19
|
testcaseType: string; // Auto | Manual | Not compiled
|
|
20
20
|
testResult: string; // Passed | Failed | Pending | N/A
|
|
21
21
|
executedDate: string; // dd/mm/yyyy
|
|
@@ -59,9 +59,20 @@ export interface FeatureMetadata {
|
|
|
59
59
|
featureName: string; // e.g., "Create Kudo Modal"
|
|
60
60
|
featurePath?: string; // e.g., "/kudos"
|
|
61
61
|
featureTags: string[]; // Feature-level tags
|
|
62
|
+
/** Background block classified by bucket. Shared across all runnable
|
|
63
|
+
* scenarios — the merger is responsible for prepending these. */
|
|
64
|
+
backgroundGivenSteps: string[];
|
|
65
|
+
backgroundWhenSteps: string[];
|
|
66
|
+
backgroundThenSteps: string[];
|
|
67
|
+
backgroundOrderedSteps: OrderedStep[];
|
|
62
68
|
scenarios: ScenarioMetadata[];
|
|
63
69
|
}
|
|
64
70
|
|
|
71
|
+
export interface OrderedStep {
|
|
72
|
+
text: string;
|
|
73
|
+
bucket: 'given' | 'when' | 'then';
|
|
74
|
+
}
|
|
75
|
+
|
|
65
76
|
export interface ScenarioMetadata {
|
|
66
77
|
name: string; // Full scenario name (with VP prefix)
|
|
67
78
|
tags: string[]; // Scenario-level tags (merged with feature tags)
|
|
@@ -71,6 +82,12 @@ export interface ScenarioMetadata {
|
|
|
71
82
|
rawGivenSteps: string[]; // Raw Given/And-after-Given step text
|
|
72
83
|
rawWhenSteps: string[]; // Raw When/And-after-When step text
|
|
73
84
|
rawThenSteps: string[]; // Raw Then/And-after-Then step text
|
|
85
|
+
/**
|
|
86
|
+
* Steps in original chronological order with each step labelled by its
|
|
87
|
+
* effective bucket (And/But inherits from the prior explicit keyword).
|
|
88
|
+
* Use this when ordering matters — e.g. mid-scenario `Given` after `When`.
|
|
89
|
+
*/
|
|
90
|
+
orderedSteps: OrderedStep[];
|
|
74
91
|
}
|
|
75
92
|
|
|
76
93
|
/**
|
|
@@ -14,12 +14,13 @@ import * as path from 'path';
|
|
|
14
14
|
import ExcelJS from 'exceljs';
|
|
15
15
|
import { ScreenSummary, TestCaseRow } from './types';
|
|
16
16
|
import { getPackageVersion } from './package-info';
|
|
17
|
+
import { SUN_LOGO_PNG_BASE64 } from './sun-logo';
|
|
17
18
|
|
|
18
19
|
const COL_COUNT = 16;
|
|
19
|
-
const HEADER_FILL = '
|
|
20
|
-
const HEADER_FONT = '
|
|
20
|
+
const HEADER_FILL = 'FFD9D2E9'; // lavender — matches the summary-header band on row 6
|
|
21
|
+
const HEADER_FONT = 'FF000000'; // black text reads better on the light lavender
|
|
21
22
|
const META_FILL = 'FFDCE6F1'; // soft blue for meta rows
|
|
22
|
-
const CATEGORY_FILL = '
|
|
23
|
+
const CATEGORY_FILL = 'FFD9D2E9'; // same lavender for group dividers (Accessing / GUI / Function)
|
|
23
24
|
const BORDER_COLOR = 'FFBFBFBF';
|
|
24
25
|
|
|
25
26
|
type AnyCell = ExcelJS.Cell;
|
|
@@ -51,111 +52,185 @@ export function renderXlsx(
|
|
|
51
52
|
return `${String(d.getDate()).padStart(2, '0')}/${String(d.getMonth() + 1).padStart(2, '0')}/${d.getFullYear()}`;
|
|
52
53
|
})();
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
const pct = (n: number) => `${Math.round((n / total) * 100)}%`;
|
|
56
|
-
|
|
57
|
-
// -- Column widths (matches CSV column order) --
|
|
55
|
+
// -- Column widths matching template_report.xlsx (Sample sheet) --
|
|
58
56
|
ws.columns = [
|
|
59
|
-
{ width:
|
|
60
|
-
{ width:
|
|
61
|
-
{ width:
|
|
62
|
-
{ width:
|
|
63
|
-
{ width:
|
|
64
|
-
{ width:
|
|
65
|
-
{ width:
|
|
66
|
-
{ width:
|
|
67
|
-
{ width:
|
|
68
|
-
{ width:
|
|
69
|
-
{ width:
|
|
70
|
-
{ width:
|
|
71
|
-
{ width:
|
|
72
|
-
{ width: 16 },
|
|
73
|
-
{ width:
|
|
74
|
-
{ width:
|
|
57
|
+
{ width: 6.38 }, // A — TC ID
|
|
58
|
+
{ width: 12.5 }, // B — Screen/Function
|
|
59
|
+
{ width: 15.38 }, // C — Big item
|
|
60
|
+
{ width: 16.25 }, // D — Medium item
|
|
61
|
+
{ width: 17 }, // E — Test Object
|
|
62
|
+
{ width: 25.5 }, // F — Pre-condition
|
|
63
|
+
{ width: 24.88 }, // G — Test Data
|
|
64
|
+
{ width: 24.88 }, // H — Steps
|
|
65
|
+
{ width: 41 }, // I — Expected results
|
|
66
|
+
{ width: 12 }, // J — Priority
|
|
67
|
+
{ width: 13.5 }, // K — Testcase type
|
|
68
|
+
{ width: 15.88 }, // L — Test Result
|
|
69
|
+
{ width: 13.5 }, // M — Executed Date
|
|
70
|
+
{ width: 16.5 }, // N — Test Executor
|
|
71
|
+
{ width: 23 }, // O — Test Environment
|
|
72
|
+
{ width: 29 }, // P — Note
|
|
75
73
|
];
|
|
76
74
|
|
|
77
|
-
// -- Top metadata band (rows 1-4) --
|
|
75
|
+
// -- Top metadata band (rows 1-4) — 1:1 with template_report.xlsx --
|
|
76
|
+
const TIMES = 'Times New Roman';
|
|
77
|
+
const ARIAL_FONT = 'Arial';
|
|
78
|
+
const BLACK = { argb: 'FF000000' };
|
|
79
|
+
const WHITE = { argb: 'FFFFFFFF' };
|
|
80
|
+
const LAVENDER = { argb: 'FFD9D2E9' };
|
|
81
|
+
const thinBlack = { style: 'thin' as const, color: BLACK };
|
|
82
|
+
const allBordersBlack = { top: thinBlack, left: thinBlack, bottom: thinBlack, right: thinBlack };
|
|
83
|
+
|
|
84
|
+
// Ensure rows 1..8 exist before merging.
|
|
85
|
+
for (let r = 1; r <= 8; r++) ws.getRow(r);
|
|
86
|
+
|
|
87
|
+
ws.mergeCells('A1:C3');
|
|
88
|
+
ws.mergeCells('D1:F3');
|
|
89
|
+
ws.mergeCells('G1:H1');
|
|
90
|
+
ws.mergeCells('G2:H2');
|
|
91
|
+
ws.mergeCells('G3:H3');
|
|
92
|
+
ws.mergeCells('A4:F4');
|
|
93
|
+
ws.mergeCells('G4:H4');
|
|
94
|
+
|
|
95
|
+
// A1 (logo band, merged A1:C3) — give it bordering + embed the Sun* logo.
|
|
96
|
+
const a1 = ws.getCell('A1');
|
|
97
|
+
a1.alignment = { horizontal: 'center', vertical: 'middle' };
|
|
98
|
+
a1.border = allBordersBlack;
|
|
99
|
+
|
|
100
|
+
// Embed Sun* logo using a oneCellAnchor + explicit pixel `ext`, exactly
|
|
101
|
+
// like the template. tl is offset into the merged band so the logo lands
|
|
102
|
+
// centred in A1:C3 instead of pinned to the top-left corner.
|
|
103
|
+
try {
|
|
104
|
+
const imageId = wb.addImage({
|
|
105
|
+
buffer: Buffer.from(SUN_LOGO_PNG_BASE64, 'base64') as unknown as ExcelJS.Buffer,
|
|
106
|
+
extension: 'png',
|
|
107
|
+
});
|
|
108
|
+
// A1:C3 ≈ 240 × 60 px. For a 90×50 logo centred horizontally / vertically:
|
|
109
|
+
// left margin = (240 − 90)/2 ≈ 75 px → 9525 EMU/px → 714375 EMU
|
|
110
|
+
// top margin = (60 − 50)/2 ≈ 5 px → 47625 EMU
|
|
111
|
+
// ExcelJS's fractional `col`/`row` clamps to a small EMU range, so we
|
|
112
|
+
// bypass it and write `native*` fields directly (the underlying XML uses
|
|
113
|
+
// the same field names).
|
|
114
|
+
ws.addImage(imageId, {
|
|
115
|
+
tl: { nativeCol: 0, nativeColOff: 714375, nativeRow: 0, nativeRowOff: 47625 } as unknown as ExcelJS.Anchor,
|
|
116
|
+
ext: { width: 90, height: 50 },
|
|
117
|
+
editAs: 'oneCell',
|
|
118
|
+
} as unknown as ExcelJS.ImageRange);
|
|
119
|
+
} catch { /* logo is decorative — never block export */ }
|
|
120
|
+
|
|
121
|
+
// D1 (TESTCASE title)
|
|
78
122
|
const titleLabel = `${summary.screen.toUpperCase()} TESTCASE`;
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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 };
|
|
123
|
+
const d1 = ws.getCell('D1');
|
|
124
|
+
d1.value = titleLabel;
|
|
125
|
+
d1.font = { bold: true, size: 18, name: TIMES };
|
|
126
|
+
d1.alignment = { horizontal: 'center', vertical: 'middle', wrapText: true };
|
|
127
|
+
d1.border = allBordersBlack;
|
|
101
128
|
|
|
102
|
-
|
|
129
|
+
// G1 — No: BM-2-901-13
|
|
130
|
+
const g1 = ws.getCell('G1');
|
|
131
|
+
g1.value = 'No: BM-2-901-13';
|
|
132
|
+
g1.font = { size: 12, name: TIMES };
|
|
133
|
+
g1.alignment = { vertical: 'middle' };
|
|
134
|
+
g1.border = { top: thinBlack, left: thinBlack, right: thinBlack };
|
|
103
135
|
|
|
104
|
-
//
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
136
|
+
// G2 — Version
|
|
137
|
+
const g2 = ws.getCell('G2');
|
|
138
|
+
g2.value = `Version: ${getPackageVersion()}`;
|
|
139
|
+
g2.font = { size: 12, name: TIMES };
|
|
140
|
+
g2.alignment = { vertical: 'middle' };
|
|
141
|
+
g2.border = { left: thinBlack, right: thinBlack };
|
|
142
|
+
|
|
143
|
+
// G3 — Issue Date
|
|
144
|
+
const g3 = ws.getCell('G3');
|
|
145
|
+
g3.value = `Issue Date: ${issueDate}`;
|
|
146
|
+
g3.font = { size: 12, name: TIMES };
|
|
147
|
+
g3.alignment = { vertical: 'middle' };
|
|
148
|
+
g3.border = { left: thinBlack, right: thinBlack, bottom: thinBlack };
|
|
149
|
+
|
|
150
|
+
// A4 — SUN ASTERISK VIETNAM CO., LTD
|
|
151
|
+
const a4 = ws.getCell('A4');
|
|
152
|
+
a4.value = 'SUN ASTERISK VIETNAM CO., LTD';
|
|
153
|
+
a4.font = { bold: true, size: 10, name: TIMES };
|
|
154
|
+
a4.alignment = { vertical: 'middle' };
|
|
155
|
+
a4.border = allBordersBlack;
|
|
156
|
+
|
|
157
|
+
// G4 — ISO/IEC ...
|
|
158
|
+
const g4 = ws.getCell('G4');
|
|
159
|
+
g4.value = 'ISO/IEC 27001:2022 & ISO 9001:2015';
|
|
160
|
+
g4.font = { bold: true, size: 10, name: TIMES };
|
|
161
|
+
g4.alignment = { horizontal: 'right', vertical: 'middle', wrapText: true };
|
|
162
|
+
g4.border = allBordersBlack;
|
|
163
|
+
|
|
164
|
+
// -- Row 5: spacer (height 12) --
|
|
165
|
+
ws.getRow(5).height = 12;
|
|
166
|
+
|
|
167
|
+
// -- Row 6: Summary headers (cols C..H), lavender fill --
|
|
168
|
+
const sumLabels: Record<string, string> = {
|
|
169
|
+
C: 'Total TCs', D: 'Passed', E: 'Failed', F: 'Pending', G: 'N/A', H: 'Remaining',
|
|
170
|
+
};
|
|
171
|
+
for (const [col, label] of Object.entries(sumLabels)) {
|
|
172
|
+
const c = ws.getCell(`${col}6`);
|
|
173
|
+
c.value = label;
|
|
174
|
+
c.font = { bold: true, size: 10, color: BLACK, name: ARIAL_FONT };
|
|
175
|
+
c.fill = { type: 'pattern', pattern: 'solid', fgColor: LAVENDER };
|
|
176
|
+
c.alignment = { horizontal: 'center', vertical: 'top' };
|
|
177
|
+
c.border = allBordersBlack;
|
|
121
178
|
}
|
|
179
|
+
ws.getRow(6).height = 12;
|
|
122
180
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
summary.
|
|
130
|
-
summary.
|
|
131
|
-
summary.
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
c
|
|
138
|
-
|
|
181
|
+
// -- Row 7: Counts (cols C..H), live formulas referencing the data area --
|
|
182
|
+
// Data table starts at row 15 (col header at row 14, divider at row 14? actually
|
|
183
|
+
// header at row 13 + divider at 14 + first data at 15). The formulas read col L
|
|
184
|
+
// (Test Result) for status counts, col A (TC ID) for total via COUNTA.
|
|
185
|
+
const COUNT_RANGE_END = 10000;
|
|
186
|
+
const r7Values: Record<string, ExcelJS.CellValue> = {
|
|
187
|
+
C: { formula: `COUNTA(A15:A${COUNT_RANGE_END})`, result: summary.total } as ExcelJS.CellFormulaValue,
|
|
188
|
+
D: { formula: `COUNTIF(L15:L${COUNT_RANGE_END},"Passed")`, result: summary.passed } as ExcelJS.CellFormulaValue,
|
|
189
|
+
E: { formula: `COUNTIF(L15:L${COUNT_RANGE_END},"Failed")`, result: summary.failed } as ExcelJS.CellFormulaValue,
|
|
190
|
+
F: { formula: `COUNTIF(L15:L${COUNT_RANGE_END},"Pending")`, result: summary.pending } as ExcelJS.CellFormulaValue,
|
|
191
|
+
G: { formula: `COUNTIF(L15:L${COUNT_RANGE_END},"N/A")`, result: summary.na } as ExcelJS.CellFormulaValue,
|
|
192
|
+
H: { formula: 'C7-D7-G7', result: summary.failed + summary.pending } as ExcelJS.CellFormulaValue,
|
|
193
|
+
};
|
|
194
|
+
for (const [col, val] of Object.entries(r7Values)) {
|
|
195
|
+
const c = ws.getCell(`${col}7`);
|
|
196
|
+
c.value = val;
|
|
197
|
+
c.font = { size: 10, color: BLACK, name: ARIAL_FONT };
|
|
198
|
+
c.fill = { type: 'pattern', pattern: 'solid', fgColor: WHITE };
|
|
199
|
+
c.alignment = { horizontal: 'center', vertical: 'top' };
|
|
200
|
+
c.border = allBordersBlack;
|
|
201
|
+
if (col === 'H') c.numFmt = '#,##0';
|
|
139
202
|
}
|
|
203
|
+
ws.getRow(7).height = 12;
|
|
140
204
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
205
|
+
// C8 has no value in the template but still needs a border so the band
|
|
206
|
+
// visually closes underneath "Total TCs".
|
|
207
|
+
const c8 = ws.getCell('C8');
|
|
208
|
+
c8.font = { size: 10, color: BLACK, name: ARIAL_FONT };
|
|
209
|
+
c8.fill = { type: 'pattern', pattern: 'solid', fgColor: WHITE };
|
|
210
|
+
c8.alignment = { horizontal: 'center', vertical: 'top' };
|
|
211
|
+
c8.border = allBordersBlack;
|
|
212
|
+
|
|
213
|
+
// -- Row 8: Percentages (cols D..H), formula = count / (Total − N/A) --
|
|
214
|
+
const r8Values: Record<string, ExcelJS.CellValue> = {
|
|
215
|
+
D: { formula: 'IFERROR(D7/(C7-G7),0)', result: summary.passed / Math.max(1, summary.total - summary.na) } as ExcelJS.CellFormulaValue,
|
|
216
|
+
E: { formula: 'IFERROR(E7/(C7-G7),0)', result: summary.failed / Math.max(1, summary.total - summary.na) } as ExcelJS.CellFormulaValue,
|
|
217
|
+
F: { formula: 'IFERROR(F7/(C7-G7),0)', result: summary.pending / Math.max(1, summary.total - summary.na) } as ExcelJS.CellFormulaValue,
|
|
218
|
+
G: { formula: 'IFERROR(G7/(C7-G7),0)', result: summary.na / Math.max(1, summary.total - summary.na) } as ExcelJS.CellFormulaValue,
|
|
219
|
+
H: { formula: 'IFERROR(H7/(C7-G7),0)', result: (summary.failed + summary.pending) / Math.max(1, summary.total - summary.na) } as ExcelJS.CellFormulaValue,
|
|
220
|
+
};
|
|
221
|
+
for (const [col, val] of Object.entries(r8Values)) {
|
|
222
|
+
const c = ws.getCell(`${col}8`);
|
|
223
|
+
c.value = val;
|
|
224
|
+
c.font = { size: 10, color: BLACK, name: ARIAL_FONT };
|
|
225
|
+
c.fill = { type: 'pattern', pattern: 'solid', fgColor: WHITE };
|
|
226
|
+
c.alignment = { horizontal: 'center', vertical: 'top' };
|
|
227
|
+
c.border = allBordersBlack;
|
|
228
|
+
c.numFmt = '0%';
|
|
156
229
|
}
|
|
230
|
+
ws.getRow(8).height = 12;
|
|
157
231
|
|
|
158
|
-
|
|
232
|
+
// After rows 1-8 are populated, append a blank row 9 spacer.
|
|
233
|
+
while (ws.rowCount < 9) ws.addRow([]);
|
|
159
234
|
|
|
160
235
|
const specRow = ws.addRow(['Spec/Design link:', specLink]);
|
|
161
236
|
specRow.getCell(1).font = { bold: true };
|
|
@@ -169,10 +244,10 @@ export function renderXlsx(
|
|
|
169
244
|
// -- Column header row (bold, filled, wrapped) --
|
|
170
245
|
const headerRow = ws.addRow([
|
|
171
246
|
'TC ID*',
|
|
172
|
-
'
|
|
173
|
-
'
|
|
174
|
-
'
|
|
175
|
-
'
|
|
247
|
+
'Screen/Function',
|
|
248
|
+
'Big item',
|
|
249
|
+
'Medium item',
|
|
250
|
+
'Test Object',
|
|
176
251
|
'Pre-condition',
|
|
177
252
|
'Test Data',
|
|
178
253
|
'Steps*',
|
|
@@ -222,10 +297,10 @@ export function renderXlsx(
|
|
|
222
297
|
for (const r of groupRows) {
|
|
223
298
|
const row = ws.addRow([
|
|
224
299
|
r.tcId,
|
|
225
|
-
r.category1,
|
|
226
300
|
'',
|
|
227
301
|
'',
|
|
228
302
|
'',
|
|
303
|
+
r.category1,
|
|
229
304
|
r.precondition,
|
|
230
305
|
r.testData,
|
|
231
306
|
r.steps,
|
|
@@ -13,6 +13,8 @@ import { defineConfig, devices } from '@playwright/test';
|
|
|
13
13
|
*/
|
|
14
14
|
export default defineConfig({
|
|
15
15
|
testDir: './specs/generated',
|
|
16
|
+
/* Output directory for test artifacts (screenshots, videos, traces). */
|
|
17
|
+
outputDir: './test-results',
|
|
16
18
|
/* Run tests in files in parallel */
|
|
17
19
|
fullyParallel: true,
|
|
18
20
|
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
|
@@ -66,14 +66,10 @@ const test = base.extend<{
|
|
|
66
66
|
}>({
|
|
67
67
|
screenshotOnFailure: [false, { option: true }],
|
|
68
68
|
|
|
69
|
-
page: async ({
|
|
70
|
-
const context = storageState
|
|
71
|
-
? await browser.newContext({ storageState })
|
|
72
|
-
: await browser.newContext();
|
|
69
|
+
page: async ({ context }, use) => {
|
|
73
70
|
const page = await context.newPage();
|
|
74
71
|
await use(page);
|
|
75
72
|
await page.close();
|
|
76
|
-
await context.close();
|
|
77
73
|
},
|
|
78
74
|
|
|
79
75
|
_autoScreenshot: [async ({ page, screenshotOnFailure }, use, testInfo) => {
|