@sun-asterisk/sungen 2.4.3 → 2.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/add.d.ts.map +1 -1
- package/dist/cli/commands/add.js +8 -2
- package/dist/cli/commands/add.js.map +1 -1
- package/dist/cli/commands/delivery.d.ts +7 -0
- package/dist/cli/commands/delivery.d.ts.map +1 -0
- package/dist/cli/commands/delivery.js +348 -0
- package/dist/cli/commands/delivery.js.map +1 -0
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +64 -1
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/index.js +4 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/exporters/csv-exporter.d.ts +32 -0
- package/dist/exporters/csv-exporter.d.ts.map +1 -0
- package/dist/exporters/csv-exporter.js +311 -0
- package/dist/exporters/csv-exporter.js.map +1 -0
- package/dist/exporters/feature-parser.d.ts +48 -0
- package/dist/exporters/feature-parser.d.ts.map +1 -0
- package/dist/exporters/feature-parser.js +178 -0
- package/dist/exporters/feature-parser.js.map +1 -0
- package/dist/exporters/package-info.d.ts +9 -0
- package/dist/exporters/package-info.d.ts.map +1 -0
- package/dist/exporters/package-info.js +73 -0
- package/dist/exporters/package-info.js.map +1 -0
- package/dist/exporters/playwright-report-parser.d.ts +21 -0
- package/dist/exporters/playwright-report-parser.d.ts.map +1 -0
- package/dist/exporters/playwright-report-parser.js +184 -0
- package/dist/exporters/playwright-report-parser.js.map +1 -0
- package/dist/exporters/scenario-merger.d.ts +21 -0
- package/dist/exporters/scenario-merger.d.ts.map +1 -0
- package/dist/exporters/scenario-merger.js +51 -0
- package/dist/exporters/scenario-merger.js.map +1 -0
- package/dist/exporters/spec-parser.d.ts +20 -0
- package/dist/exporters/spec-parser.d.ts.map +1 -0
- package/dist/exporters/spec-parser.js +259 -0
- package/dist/exporters/spec-parser.js.map +1 -0
- package/dist/exporters/step-formatter.d.ts +32 -0
- package/dist/exporters/step-formatter.d.ts.map +1 -0
- package/dist/exporters/step-formatter.js +76 -0
- package/dist/exporters/step-formatter.js.map +1 -0
- package/dist/exporters/test-data-resolver.d.ts +20 -0
- package/dist/exporters/test-data-resolver.d.ts.map +1 -0
- package/dist/exporters/test-data-resolver.js +96 -0
- package/dist/exporters/test-data-resolver.js.map +1 -0
- package/dist/exporters/types.d.ts +104 -0
- package/dist/exporters/types.d.ts.map +1 -0
- package/dist/exporters/types.js +6 -0
- package/dist/exporters/types.js.map +1 -0
- package/dist/exporters/xlsx-exporter.d.ts +19 -0
- package/dist/exporters/xlsx-exporter.d.ts.map +1 -0
- package/dist/exporters/xlsx-exporter.js +309 -0
- package/dist/exporters/xlsx-exporter.js.map +1 -0
- package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.js +26 -0
- package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
- package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
- package/dist/orchestrator/ai-rules-updater.js +12 -0
- package/dist/orchestrator/ai-rules-updater.js.map +1 -1
- package/dist/orchestrator/project-initializer.d.ts +12 -1
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +84 -64
- package/dist/orchestrator/project-initializer.js.map +1 -1
- package/dist/orchestrator/screen-manager.d.ts +1 -0
- package/dist/orchestrator/screen-manager.d.ts.map +1 -1
- package/dist/orchestrator/screen-manager.js +4 -2
- package/dist/orchestrator/screen-manager.js.map +1 -1
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +18 -9
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +7 -5
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-delivery.md +71 -0
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +38 -10
- package/dist/orchestrator/templates/ai-instructions/claude-config.md +12 -2
- package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-figma.md +142 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +100 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-local.md +73 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-delivery.md +103 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +2 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +87 -4
- package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +22 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +18 -9
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +6 -4
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +71 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +45 -20
- package/dist/orchestrator/templates/ai-instructions/copilot-config.md +12 -2
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-figma.md +142 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +100 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-local.md +73 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +103 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +2 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +87 -4
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +22 -0
- package/dist/orchestrator/templates/playwright.config.d.ts.map +1 -1
- package/dist/orchestrator/templates/playwright.config.js +6 -1
- package/dist/orchestrator/templates/playwright.config.js.map +1 -1
- package/dist/orchestrator/templates/playwright.config.ts +6 -1
- package/package.json +2 -1
- package/src/cli/commands/add.ts +9 -2
- package/src/cli/commands/delivery.ts +348 -0
- package/src/cli/commands/update.ts +84 -2
- package/src/cli/index.ts +4 -2
- package/src/exporters/csv-exporter.ts +304 -0
- package/src/exporters/feature-parser.ts +168 -0
- package/src/exporters/package-info.ts +35 -0
- package/src/exporters/playwright-report-parser.ts +168 -0
- package/src/exporters/scenario-merger.ts +63 -0
- package/src/exporters/spec-parser.ts +247 -0
- package/src/exporters/step-formatter.ts +80 -0
- package/src/exporters/test-data-resolver.ts +59 -0
- package/src/exporters/types.ts +112 -0
- package/src/exporters/xlsx-exporter.ts +301 -0
- package/src/generators/test-generator/utils/selector-resolver.ts +26 -0
- package/src/orchestrator/ai-rules-updater.ts +12 -0
- package/src/orchestrator/project-initializer.ts +103 -70
- package/src/orchestrator/screen-manager.ts +5 -2
- package/src/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +18 -9
- package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +7 -5
- package/src/orchestrator/templates/ai-instructions/claude-cmd-delivery.md +71 -0
- package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +38 -10
- package/src/orchestrator/templates/ai-instructions/claude-config.md +12 -2
- package/src/orchestrator/templates/ai-instructions/claude-skill-capture-figma.md +142 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +100 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-capture-local.md +73 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-delivery.md +103 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +2 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +87 -4
- package/src/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +22 -0
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +18 -9
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +6 -4
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +71 -0
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +45 -20
- package/src/orchestrator/templates/ai-instructions/copilot-config.md +12 -2
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-figma.md +142 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +100 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-local.md +73 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +103 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +2 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +87 -4
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +22 -0
- package/src/orchestrator/templates/playwright.config.ts +6 -1
|
@@ -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);
|
|
@@ -218,6 +218,14 @@ export class SelectorResolver {
|
|
|
218
218
|
'tab': () => ({ strategy: 'role', role: 'tab', name: label, value: 'tab' }),
|
|
219
219
|
'dialog': () => ({ strategy: 'role', role: 'dialog', name: label, value: 'dialog' }),
|
|
220
220
|
'alertdialog': () => ({ strategy: 'role', role: 'alertdialog', name: label, value: 'alertdialog' }),
|
|
221
|
+
'combobox': () => ({ strategy: 'role', role: 'combobox', name: label, value: 'combobox' }),
|
|
222
|
+
'menuitem': () => ({ strategy: 'role', role: 'menuitem', name: label, value: 'menuitem' }),
|
|
223
|
+
'progressbar': () => ({ strategy: 'role', role: 'progressbar', name: label, value: 'progressbar' }),
|
|
224
|
+
'region': () => ({ strategy: 'role', role: 'region', name: label, value: 'region' }),
|
|
225
|
+
'article': () => ({ strategy: 'role', role: 'article', name: label, value: 'article' }),
|
|
226
|
+
'cell': () => ({ strategy: 'role', role: 'cell', name: label, value: 'cell' }),
|
|
227
|
+
'status': () => ({ strategy: 'role', role: 'status', name: label, value: 'status' }),
|
|
228
|
+
'navigation': () => ({ strategy: 'role', role: 'navigation', name: label, value: 'navigation' }),
|
|
221
229
|
};
|
|
222
230
|
|
|
223
231
|
const factory = strategyMap[normalized];
|
|
@@ -263,6 +271,24 @@ export class SelectorResolver {
|
|
|
263
271
|
// Modal/drawer alias → dialog role
|
|
264
272
|
'modal': 'dialog',
|
|
265
273
|
'drawer': 'dialog',
|
|
274
|
+
// Dropdown/select alias -> combobox role
|
|
275
|
+
'dropdown': 'combobox',
|
|
276
|
+
'select': 'combobox',
|
|
277
|
+
// Data aliases
|
|
278
|
+
'item': 'listitem',
|
|
279
|
+
'card': 'article',
|
|
280
|
+
'cell': 'cell',
|
|
281
|
+
'section': 'region',
|
|
282
|
+
// Feedback aliases
|
|
283
|
+
'badge': 'text',
|
|
284
|
+
'tooltip': 'text',
|
|
285
|
+
'tag': 'text',
|
|
286
|
+
// Trigger aliases
|
|
287
|
+
'menuitem': 'menuitem',
|
|
288
|
+
// System aliases
|
|
289
|
+
'progressbar': 'progressbar',
|
|
290
|
+
'spinner': 'status',
|
|
291
|
+
'breadcrumb': 'navigation',
|
|
266
292
|
};
|
|
267
293
|
return aliasMap[elementType] || elementType;
|
|
268
294
|
}
|
|
@@ -18,11 +18,15 @@ export const AI_RULES_FILE_MAPPING: [string, string][] = [
|
|
|
18
18
|
['claude-cmd-add-screen.md', '.claude/commands/sungen/add-screen.md'],
|
|
19
19
|
['claude-cmd-create-test.md', '.claude/commands/sungen/create-test.md'],
|
|
20
20
|
['claude-cmd-run-test.md', '.claude/commands/sungen/run-test.md'],
|
|
21
|
+
['claude-cmd-review.md', '.claude/commands/sungen/review.md'],
|
|
22
|
+
['claude-cmd-delivery.md', '.claude/commands/sungen/delivery.md'],
|
|
21
23
|
|
|
22
24
|
// Commands — GitHub Copilot
|
|
23
25
|
['copilot-cmd-add-screen.md', '.github/prompts/sungen-add-screen.prompt.md'],
|
|
24
26
|
['copilot-cmd-create-test.md', '.github/prompts/sungen-create-test.prompt.md'],
|
|
25
27
|
['copilot-cmd-run-test.md', '.github/prompts/sungen-run-test.prompt.md'],
|
|
28
|
+
['copilot-cmd-review.md', '.github/prompts/sungen-review.prompt.md'],
|
|
29
|
+
['copilot-cmd-delivery.md', '.github/prompts/sungen-delivery.prompt.md'],
|
|
26
30
|
|
|
27
31
|
// Skills — Claude Code
|
|
28
32
|
['claude-skill-gherkin-syntax.md', '.claude/skills/sungen-gherkin-syntax/SKILL.md'],
|
|
@@ -33,6 +37,10 @@ export const AI_RULES_FILE_MAPPING: [string, string][] = [
|
|
|
33
37
|
['claude-skill-selector-fix.md', '.claude/skills/sungen-selector-fix/SKILL.md'],
|
|
34
38
|
['claude-skill-tc-review.md', '.claude/skills/sungen-tc-review/SKILL.md'],
|
|
35
39
|
['claude-skill-viewpoint.md', '.claude/skills/sungen-viewpoint/SKILL.md'],
|
|
40
|
+
['claude-skill-delivery.md', '.claude/skills/sungen-delivery/SKILL.md'],
|
|
41
|
+
['claude-skill-capture-figma.md', '.claude/skills/sungen-capture-figma/SKILL.md'],
|
|
42
|
+
['claude-skill-capture-local.md', '.claude/skills/sungen-capture-local/SKILL.md'],
|
|
43
|
+
['claude-skill-capture-live.md', '.claude/skills/sungen-capture-live/SKILL.md'],
|
|
36
44
|
|
|
37
45
|
// Skills — GitHub Copilot
|
|
38
46
|
['github-skill-sungen-gherkin-syntax.md', '.github/skills/sungen-gherkin-syntax/SKILL.md'],
|
|
@@ -43,6 +51,10 @@ export const AI_RULES_FILE_MAPPING: [string, string][] = [
|
|
|
43
51
|
['github-skill-sungen-selector-fix.md', '.github/skills/sungen-selector-fix/SKILL.md'],
|
|
44
52
|
['github-skill-sungen-tc-review.md', '.github/skills/sungen-tc-review/SKILL.md'],
|
|
45
53
|
['github-skill-sungen-viewpoint.md', '.github/skills/sungen-viewpoint/SKILL.md'],
|
|
54
|
+
['github-skill-sungen-delivery.md', '.github/skills/sungen-delivery/SKILL.md'],
|
|
55
|
+
['github-skill-sungen-capture-figma.md', '.github/skills/sungen-capture-figma/SKILL.md'],
|
|
56
|
+
['github-skill-sungen-capture-local.md', '.github/skills/sungen-capture-local/SKILL.md'],
|
|
57
|
+
['github-skill-sungen-capture-live.md', '.github/skills/sungen-capture-live/SKILL.md'],
|
|
46
58
|
];
|
|
47
59
|
|
|
48
60
|
export class AIRulesUpdater {
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import * as fs from 'fs';
|
|
7
7
|
import * as path from 'path';
|
|
8
8
|
import { execSync } from 'child_process';
|
|
9
|
+
import { AI_RULES_FILE_MAPPING } from './ai-rules-updater';
|
|
9
10
|
|
|
10
11
|
export class ProjectInitializer {
|
|
11
12
|
private baseCwd: string;
|
|
@@ -62,6 +63,9 @@ export class ProjectInitializer {
|
|
|
62
63
|
// Create VS Code settings for Copilot auto-attach
|
|
63
64
|
this.createVSCodeSettings();
|
|
64
65
|
|
|
66
|
+
// Create root .mcp.json for Claude Code
|
|
67
|
+
this.createClaudeMCPConfig();
|
|
68
|
+
|
|
65
69
|
// Display summary
|
|
66
70
|
this.displaySummary(normalizedProjectName);
|
|
67
71
|
}
|
|
@@ -190,46 +194,112 @@ export class ProjectInitializer {
|
|
|
190
194
|
private createVSCodeSettings(): void {
|
|
191
195
|
const settingsPath = path.join(this.cwd, '.vscode', 'settings.json');
|
|
192
196
|
|
|
193
|
-
if (fs.existsSync(settingsPath)) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
+
if (!fs.existsSync(settingsPath)) {
|
|
198
|
+
const settingsDir = path.dirname(settingsPath);
|
|
199
|
+
if (!fs.existsSync(settingsDir)) {
|
|
200
|
+
fs.mkdirSync(settingsDir, { recursive: true });
|
|
201
|
+
}
|
|
197
202
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
203
|
+
const settings = {
|
|
204
|
+
'github.copilot.chat.agent.runTasks': true,
|
|
205
|
+
'chat.tools.terminal.autoApprove': {
|
|
206
|
+
sungen: true,
|
|
207
|
+
'npx playwright': true,
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
|
|
212
|
+
this.createdItems.push('.vscode/settings.json');
|
|
213
|
+
} else {
|
|
214
|
+
this.skippedItems.push('.vscode/settings.json');
|
|
201
215
|
}
|
|
202
216
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
217
|
+
// .vscode/mcp.json (Copilot format) — safe-merge Playwright + Figma entries
|
|
218
|
+
this.mergeMCPConfig(
|
|
219
|
+
path.join(this.cwd, '.vscode', 'mcp.json'),
|
|
220
|
+
'servers',
|
|
221
|
+
{
|
|
222
|
+
playwright: {
|
|
223
|
+
command: 'npx',
|
|
224
|
+
args: ['@playwright/mcp@latest'],
|
|
225
|
+
},
|
|
226
|
+
figma: {
|
|
227
|
+
type: 'http',
|
|
228
|
+
url: 'https://mcp.figma.com/mcp',
|
|
229
|
+
},
|
|
208
230
|
},
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
this.createdItems.push('.vscode/settings.json');
|
|
213
|
-
|
|
214
|
-
// Create MCP config for Playwright browser tools
|
|
215
|
-
const mcpPath = path.join(this.cwd, '.vscode', 'mcp.json');
|
|
216
|
-
|
|
217
|
-
if (fs.existsSync(mcpPath)) {
|
|
218
|
-
this.skippedItems.push('.vscode/mcp.json');
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
231
|
+
'.vscode/mcp.json',
|
|
232
|
+
);
|
|
233
|
+
}
|
|
221
234
|
|
|
222
|
-
|
|
223
|
-
|
|
235
|
+
/**
|
|
236
|
+
* Create root .mcp.json for Claude Code — safe-merge Playwright + Figma entries
|
|
237
|
+
*/
|
|
238
|
+
private createClaudeMCPConfig(): void {
|
|
239
|
+
this.mergeMCPConfig(
|
|
240
|
+
path.join(this.cwd, '.mcp.json'),
|
|
241
|
+
'mcpServers',
|
|
242
|
+
{
|
|
224
243
|
playwright: {
|
|
244
|
+
type: 'stdio',
|
|
225
245
|
command: 'npx',
|
|
226
246
|
args: ['@playwright/mcp@latest'],
|
|
227
247
|
},
|
|
248
|
+
figma: {
|
|
249
|
+
type: 'http',
|
|
250
|
+
url: 'https://mcp.figma.com/mcp',
|
|
251
|
+
},
|
|
228
252
|
},
|
|
229
|
-
|
|
253
|
+
'.mcp.json',
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Add MCP server entries without overwriting user config.
|
|
259
|
+
* Preserves existing entries with the same key — user edits always win.
|
|
260
|
+
*/
|
|
261
|
+
private mergeMCPConfig(
|
|
262
|
+
filePath: string,
|
|
263
|
+
rootKey: string,
|
|
264
|
+
newServers: Record<string, unknown>,
|
|
265
|
+
displayName: string,
|
|
266
|
+
): void {
|
|
267
|
+
const dir = path.dirname(filePath);
|
|
268
|
+
if (!fs.existsSync(dir)) {
|
|
269
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
let config: Record<string, Record<string, unknown>> = {};
|
|
273
|
+
const existed = fs.existsSync(filePath);
|
|
274
|
+
|
|
275
|
+
if (existed) {
|
|
276
|
+
try {
|
|
277
|
+
config = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
278
|
+
} catch {
|
|
279
|
+
this.skippedItems.push(`${displayName} (invalid JSON, skipped)`);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
230
283
|
|
|
231
|
-
|
|
232
|
-
|
|
284
|
+
if (!config[rootKey] || typeof config[rootKey] !== 'object') {
|
|
285
|
+
config[rootKey] = {};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
let added = 0;
|
|
289
|
+
for (const [name, entry] of Object.entries(newServers)) {
|
|
290
|
+
if (!config[rootKey][name]) {
|
|
291
|
+
config[rootKey][name] = entry;
|
|
292
|
+
added++;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (added === 0) {
|
|
297
|
+
this.skippedItems.push(displayName);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
302
|
+
this.createdItems.push(existed ? `${displayName} (updated)` : displayName);
|
|
233
303
|
}
|
|
234
304
|
|
|
235
305
|
/**
|
|
@@ -264,51 +334,14 @@ export class ProjectInitializer {
|
|
|
264
334
|
}
|
|
265
335
|
|
|
266
336
|
/**
|
|
267
|
-
* Create AI rules files for GitHub Copilot and Claude Code
|
|
337
|
+
* Create AI rules files for GitHub Copilot and Claude Code.
|
|
338
|
+
* Shares `AI_RULES_FILE_MAPPING` with `sungen update` so a newly added
|
|
339
|
+
* skill / command template only needs to be registered once.
|
|
268
340
|
*/
|
|
269
341
|
private createAIRules(): void {
|
|
270
342
|
const aiTemplateDir = path.join(__dirname, 'templates', 'ai-instructions');
|
|
271
343
|
|
|
272
|
-
|
|
273
|
-
const fileMapping: [string, string][] = [
|
|
274
|
-
// Config
|
|
275
|
-
['claude-config.md', 'CLAUDE.md'],
|
|
276
|
-
['copilot-config.md', '.github/copilot-instructions.md'],
|
|
277
|
-
|
|
278
|
-
// Commands — Claude Code
|
|
279
|
-
['claude-cmd-add-screen.md', '.claude/commands/sungen/add-screen.md'],
|
|
280
|
-
['claude-cmd-create-test.md', '.claude/commands/sungen/create-test.md'],
|
|
281
|
-
['claude-cmd-run-test.md', '.claude/commands/sungen/run-test.md'],
|
|
282
|
-
['claude-cmd-review.md', '.claude/commands/sungen/review.md'],
|
|
283
|
-
|
|
284
|
-
// Commands — GitHub Copilot
|
|
285
|
-
['copilot-cmd-add-screen.md', '.github/prompts/sungen-add-screen.prompt.md'],
|
|
286
|
-
['copilot-cmd-create-test.md', '.github/prompts/sungen-create-test.prompt.md'],
|
|
287
|
-
['copilot-cmd-run-test.md', '.github/prompts/sungen-run-test.prompt.md'],
|
|
288
|
-
['copilot-cmd-review.md', '.github/prompts/sungen-review.prompt.md'],
|
|
289
|
-
|
|
290
|
-
// Skills — Claude Code
|
|
291
|
-
['claude-skill-gherkin-syntax.md', '.claude/skills/sungen-gherkin-syntax/SKILL.md'],
|
|
292
|
-
['claude-skill-selector-keys.md', '.claude/skills/sungen-selector-keys/SKILL.md'],
|
|
293
|
-
['claude-skill-error-mapping.md', '.claude/skills/sungen-error-mapping/SKILL.md'],
|
|
294
|
-
['claude-skill-tc-generation.md', '.claude/skills/sungen-tc-generation/SKILL.md'],
|
|
295
|
-
['claude-skill-test-design-techniques.md', '.claude/skills/sungen-test-design-techniques/SKILL.md'],
|
|
296
|
-
['claude-skill-selector-fix.md', '.claude/skills/sungen-selector-fix/SKILL.md'],
|
|
297
|
-
['claude-skill-tc-review.md', '.claude/skills/sungen-tc-review/SKILL.md'],
|
|
298
|
-
['claude-skill-viewpoint.md', '.claude/skills/sungen-viewpoint/SKILL.md'],
|
|
299
|
-
|
|
300
|
-
// Skills — GitHub Copilot (separate copies with Copilot-friendly descriptions)
|
|
301
|
-
['github-skill-sungen-gherkin-syntax.md', '.github/skills/sungen-gherkin-syntax/SKILL.md'],
|
|
302
|
-
['github-skill-sungen-selector-keys.md', '.github/skills/sungen-selector-keys/SKILL.md'],
|
|
303
|
-
['github-skill-sungen-error-mapping.md', '.github/skills/sungen-error-mapping/SKILL.md'],
|
|
304
|
-
['github-skill-sungen-tc-generation.md', '.github/skills/sungen-tc-generation/SKILL.md'],
|
|
305
|
-
['github-skill-sungen-test-design-techniques.md', '.github/skills/sungen-test-design-techniques/SKILL.md'],
|
|
306
|
-
['github-skill-sungen-selector-fix.md', '.github/skills/sungen-selector-fix/SKILL.md'],
|
|
307
|
-
['github-skill-sungen-tc-review.md', '.github/skills/sungen-tc-review/SKILL.md'],
|
|
308
|
-
['github-skill-sungen-viewpoint.md', '.github/skills/sungen-viewpoint/SKILL.md'],
|
|
309
|
-
];
|
|
310
|
-
|
|
311
|
-
for (const [templateFile, outputRelPath] of fileMapping) {
|
|
344
|
+
for (const [templateFile, outputRelPath] of AI_RULES_FILE_MAPPING) {
|
|
312
345
|
const outputPath = path.join(this.cwd, outputRelPath);
|
|
313
346
|
|
|
314
347
|
if (fs.existsSync(outputPath)) {
|
|
@@ -10,6 +10,7 @@ import { chromium } from '@playwright/test';
|
|
|
10
10
|
export interface ScreenOptions {
|
|
11
11
|
name: string;
|
|
12
12
|
path?: string;
|
|
13
|
+
capture?: boolean;
|
|
13
14
|
feature?: string;
|
|
14
15
|
description?: string;
|
|
15
16
|
}
|
|
@@ -185,9 +186,9 @@ export class ScreenManager {
|
|
|
185
186
|
].join('\n'), 'utf-8');
|
|
186
187
|
}
|
|
187
188
|
|
|
188
|
-
// Auto-screenshot: capture page
|
|
189
|
+
// Auto-screenshot: capture page only when --capture is set (requires --path)
|
|
189
190
|
// Use filename (not screenName) so each feature gets its own screenshots
|
|
190
|
-
if (options.path) {
|
|
191
|
+
if (options.capture && options.path) {
|
|
191
192
|
await this.captureScreenshot(options.path, requirementsUiDir, filename);
|
|
192
193
|
}
|
|
193
194
|
|
|
@@ -295,6 +296,8 @@ export class ScreenManager {
|
|
|
295
296
|
|
|
296
297
|
## Overview
|
|
297
298
|
- **URL Path:** ${pagePath}
|
|
299
|
+
- **Live URL:** <!-- optional, full URL for live page capture (falls back to baseURL + URL Path) -->
|
|
300
|
+
- **Figma URL:** <!-- optional, Figma frame URL for design-driven capture (requires Figma MCP) -->
|
|
298
301
|
- **Auth Required:** no
|
|
299
302
|
- **Platform:** web
|
|
300
303
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: add-screen
|
|
3
|
-
description: 'Add a new Sungen screen — scaffolds directories and
|
|
3
|
+
description: 'Add a new Sungen screen — scaffolds directories, helps fill spec.md, and can capture visuals from Figma (pre-launch) or live page via the capture skills'
|
|
4
4
|
argument-hint: [screen-name] [url-path]
|
|
5
|
-
allowed-tools: Read, Grep, Bash, Glob, AskUserQuestion
|
|
5
|
+
allowed-tools: Read, Grep, Bash, Glob, Edit, Write, AskUserQuestion, mcp__playwright__browser_navigate, mcp__playwright__browser_take_screenshot, mcp__playwright__browser_snapshot, mcp__figma__get_design_context, mcp__figma__get_variable_defs, mcp__figma__get_screenshot
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
You are adding a new Sungen screen for test generation.
|
|
@@ -25,20 +25,29 @@ Run:
|
|
|
25
25
|
sungen add --screen <screen> --path <path>
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
### 2. Fill
|
|
28
|
+
### 2. Fill spec.md
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
Use `AskUserQuestion`: *"Fill `spec.md` now?"* — offer **Yes, fill now (Recommended)** / **Skip, fill later**.
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
- If they have UI designs (screenshots, Figma exports, mockups) → suggest copying them to `requirements/ui/`.
|
|
34
|
-
- If no → proceed to step 3.
|
|
32
|
+
If yes → open `qa/screens/<screen>/requirements/spec.md` and help the user fill sections, fields, validation rules, business rules, and states. Especially prompt for the optional **Figma URL** and **Live URL** fields in Overview — those unlock auto-capture without re-asking next run.
|
|
35
33
|
|
|
36
|
-
### 3.
|
|
34
|
+
### 3. Capture visual source
|
|
35
|
+
|
|
36
|
+
Use `AskUserQuestion`: *"Capture a visual reference for this screen?"* — always offer all three so pre-launch projects work:
|
|
37
|
+
|
|
38
|
+
- **Figma design** (Recommended for pre-launch) — invoke `sungen-capture-figma` skill
|
|
39
|
+
- **Live page scan** (dev/staging is up) — invoke `sungen-capture-live` skill
|
|
40
|
+
- **Skip** — user will drop images manually into `requirements/ui/` later, or rely on `/sungen:create-test` to prompt again
|
|
41
|
+
|
|
42
|
+
Each capture skill writes outputs into `qa/screens/<screen>/requirements/ui/` and reports back a summary. Do not inline capture logic here — always delegate to the skill so behavior stays consistent with `/sungen:create-test`.
|
|
43
|
+
|
|
44
|
+
If the user has additional UI designs (mockups, hand-drawn sketches), suggest copying them to `requirements/ui/` — `sungen-capture-local` will pick them up during `/sungen:create-test`.
|
|
45
|
+
|
|
46
|
+
### 4. Next steps
|
|
37
47
|
|
|
38
48
|
Tell the user what was created, then use `AskUserQuestion` to offer next steps:
|
|
39
49
|
|
|
40
50
|
- **`/sungen:create-test <screen>`** — Create test cases from requirements/designs (Recommended)
|
|
41
|
-
- **Fill `requirements/spec.md`** — Write screen specs first for better test quality
|
|
42
51
|
- **Done for now** — I'll come back later
|
|
43
52
|
|
|
44
53
|
If user picks `/sungen:create-test`, **you MUST use the Skill tool** to invoke it. Do NOT generate test cases yourself — the skill auto-loads `sungen-gherkin-syntax` and `sungen-tc-generation`.
|