@sun-asterisk/sungen 2.4.5 → 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/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.map +1 -1
- package/dist/orchestrator/screen-manager.js +2 -0
- package/dist/orchestrator/screen-manager.js.map +1 -1
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +15 -17
- 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 +27 -0
- 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-keys.md +22 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +13 -15
- 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 +38 -14
- 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-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/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 +2 -0
- package/src/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +15 -17
- 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 +27 -0
- 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-keys.md +22 -0
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +13 -15
- 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 +38 -14
- 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-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)) {
|
|
@@ -296,6 +296,8 @@ export class ScreenManager {
|
|
|
296
296
|
|
|
297
297
|
## Overview
|
|
298
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) -->
|
|
299
301
|
- **Auth Required:** no
|
|
300
302
|
- **Platform:** web
|
|
301
303
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: add-screen
|
|
3
|
-
description: 'Add a new Sungen screen — scaffolds directories, helps fill spec.md, and can
|
|
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, Edit, Write, AskUserQuestion, mcp__playwright__browser_navigate, mcp__playwright__browser_take_screenshot, mcp__playwright__browser_snapshot
|
|
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,27 +25,25 @@ Run:
|
|
|
25
25
|
sungen add --screen <screen> --path <path>
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
### 2.
|
|
28
|
+
### 2. Fill spec.md
|
|
29
29
|
|
|
30
|
-
Use `AskUserQuestion
|
|
30
|
+
Use `AskUserQuestion`: *"Fill `spec.md` now?"* — offer **Yes, fill now (Recommended)** / **Skip, fill later**.
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
- **Fill `spec.md` only** — app not live yet, or no need for visuals
|
|
34
|
-
- **Capture live-page screenshot only** — spec will come later
|
|
35
|
-
- **Skip requirements prep** — proceed to `/sungen:create-test` immediately
|
|
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.
|
|
36
33
|
|
|
37
|
-
|
|
34
|
+
### 3. Capture visual source
|
|
38
35
|
|
|
39
|
-
|
|
40
|
-
1. Read `baseURL` from `playwright.config.ts` (fall back to `APP_BASE_URL` env, then ask the user).
|
|
41
|
-
2. `browser_navigate` to `<baseURL><path>`.
|
|
42
|
-
3. If redirected to login → ask the user to log in manually in the MCP browser, wait for confirmation, then re-navigate. (No auth persistence needed here — that's handled by Phase 0.5 in `sungen-selector-fix` when tests run.)
|
|
43
|
-
4. `browser_take_screenshot` with `filename: "qa/screens/<screen>/requirements/ui/<screen>.png"`.
|
|
44
|
-
5. If the screen has multiple important states (empty, loaded, error, modal open), offer additional captures named `<screen>-<state>.png`.
|
|
36
|
+
Use `AskUserQuestion`: *"Capture a visual reference for this screen?"* — always offer all three so pre-launch projects work:
|
|
45
37
|
|
|
46
|
-
|
|
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
|
|
47
41
|
|
|
48
|
-
|
|
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
|
|
49
47
|
|
|
50
48
|
Tell the user what was created, then use `AskUserQuestion` to offer next steps:
|
|
51
49
|
|