@sun-asterisk/sungen 2.2.3 → 2.3.0
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/README.md +4 -4
- package/dist/cli/commands/update.d.ts +3 -0
- package/dist/cli/commands/update.d.ts.map +1 -0
- package/dist/cli/commands/update.js +21 -0
- package/dist/cli/commands/update.js.map +1 -0
- package/dist/cli/index.js +3 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/generators/gherkin-parser/index.d.ts +2 -0
- package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
- package/dist/generators/gherkin-parser/index.js +16 -2
- package/dist/generators/gherkin-parser/index.js.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/attribute-assertion.hbs +3 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +12 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +12 -1
- package/dist/generators/test-generator/patterns/assertion-patterns.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/assertion-patterns.js +12 -0
- package/dist/generators/test-generator/patterns/assertion-patterns.js.map +1 -1
- package/dist/generators/test-generator/patterns/index.d.ts +9 -0
- package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/index.js +32 -0
- package/dist/generators/test-generator/patterns/index.js.map +1 -1
- package/dist/generators/test-generator/patterns/table-patterns.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/table-patterns.js +8 -5
- package/dist/generators/test-generator/patterns/table-patterns.js.map +1 -1
- package/dist/orchestrator/ai-rules-updater.d.ts +13 -0
- package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -0
- package/dist/orchestrator/ai-rules-updater.js +157 -0
- package/dist/orchestrator/ai-rules-updater.js.map +1 -0
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +2 -27
- 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 +70 -3
- 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-make-tc.md +11 -4
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-make-test.md +9 -11
- package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +27 -8
- package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +91 -25
- package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +124 -71
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +13 -5
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-make-tc.md +12 -4
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-make-test.md +9 -11
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +27 -8
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +72 -31
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +124 -72
- package/dist/orchestrator/templates/readme.md +13 -8
- package/package.json +1 -1
- package/src/cli/commands/update.ts +18 -0
- package/src/cli/index.ts +3 -1
- package/src/generators/gherkin-parser/index.ts +19 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/attribute-assertion.hbs +3 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +12 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +12 -1
- package/src/generators/test-generator/patterns/assertion-patterns.ts +13 -0
- package/src/generators/test-generator/patterns/index.ts +41 -0
- package/src/generators/test-generator/patterns/table-patterns.ts +8 -5
- package/src/orchestrator/ai-rules-updater.ts +139 -0
- package/src/orchestrator/project-initializer.ts +2 -32
- package/src/orchestrator/screen-manager.ts +72 -3
- package/src/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +18 -9
- package/src/orchestrator/templates/ai-instructions/claude-cmd-make-tc.md +11 -4
- package/src/orchestrator/templates/ai-instructions/claude-cmd-make-test.md +9 -11
- package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +27 -8
- package/src/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +91 -25
- package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +124 -71
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +13 -5
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-make-tc.md +12 -4
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-make-test.md +9 -11
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +27 -8
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +72 -31
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +124 -72
- package/src/orchestrator/templates/readme.md +13 -8
- package/docs/gherkin standards/gherkin-core-standard.md +0 -431
- package/docs/gherkin standards/gherkin-core-standard.vi.md +0 -399
- package/docs/gherkin-dictionary.md +0 -1126
- package/docs/makeauth.md +0 -225
|
@@ -72,6 +72,14 @@ export class PatternRegistry {
|
|
|
72
72
|
// Prefer resolver (framework-agnostic) over generator (legacy)
|
|
73
73
|
if (pattern.resolver) {
|
|
74
74
|
const resolved = pattern.resolver(step, context);
|
|
75
|
+
|
|
76
|
+
// Auto-inject parent scoping if step has parentRef
|
|
77
|
+
if (step.parentRef && step.parentType) {
|
|
78
|
+
resolved.data.parentLocator = PatternRegistry.resolveParentLocator(
|
|
79
|
+
step.parentRef, step.parentType, context
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
75
83
|
const code = context.templateEngine.renderStep(resolved.templateName, resolved.data);
|
|
76
84
|
return {
|
|
77
85
|
code,
|
|
@@ -86,6 +94,39 @@ export class PatternRegistry {
|
|
|
86
94
|
return null;
|
|
87
95
|
}
|
|
88
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Resolve parent scoping to a Playwright locator string.
|
|
99
|
+
* Tries YAML lookup first, falls back to auto-infer from parentType.
|
|
100
|
+
*
|
|
101
|
+
* Parent type → Playwright role:
|
|
102
|
+
* table → 'table', list → 'list', section → 'region',
|
|
103
|
+
* dialog → 'dialog', form → 'form'
|
|
104
|
+
*/
|
|
105
|
+
private static resolveParentLocator(
|
|
106
|
+
parentRef: string, parentType: string, context: PatternContext
|
|
107
|
+
): string {
|
|
108
|
+
// Try resolving from selectors YAML
|
|
109
|
+
try {
|
|
110
|
+
const resolved = context.selectorResolver.resolveSelector(
|
|
111
|
+
parentRef, context.featureName, parentType, 0
|
|
112
|
+
);
|
|
113
|
+
return context.renderLocator(resolved);
|
|
114
|
+
} catch {
|
|
115
|
+
// Fallback: auto-infer from parentType + parentRef as accessible name
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const roleMap: Record<string, string> = {
|
|
119
|
+
table: 'table',
|
|
120
|
+
list: 'list',
|
|
121
|
+
section: 'region',
|
|
122
|
+
dialog: 'dialog',
|
|
123
|
+
form: 'form',
|
|
124
|
+
};
|
|
125
|
+
const role = roleMap[parentType] || parentType;
|
|
126
|
+
const escapedName = parentRef.replace(/'/g, "\\'");
|
|
127
|
+
return `page.getByRole('${role}', { name: '${escapedName}' })`;
|
|
128
|
+
}
|
|
129
|
+
|
|
89
130
|
/**
|
|
90
131
|
* Check if step matches a pattern matcher
|
|
91
132
|
*/
|
|
@@ -6,11 +6,12 @@
|
|
|
6
6
|
import { StepPattern } from './types';
|
|
7
7
|
|
|
8
8
|
export const tablePatterns: StepPattern[] = [
|
|
9
|
-
// "User see [Users] table
|
|
9
|
+
// "User see [Users] table with {{count}} rows" (preferred)
|
|
10
|
+
// Also accepts: "User see [Users] table has {{count}} rows" (backward compat)
|
|
10
11
|
{
|
|
11
12
|
name: 'table-row-count',
|
|
12
13
|
matcher: (step) => {
|
|
13
|
-
return /\btable\s+has\b/i.test(step.text) &&
|
|
14
|
+
return /\btable\s+(?:has|with)\b/i.test(step.text) &&
|
|
14
15
|
/\}\}\s*rows?\b/i.test(step.text) &&
|
|
15
16
|
!!step.dataRef;
|
|
16
17
|
},
|
|
@@ -29,7 +30,8 @@ export const tablePatterns: StepPattern[] = [
|
|
|
29
30
|
priority: 16,
|
|
30
31
|
},
|
|
31
32
|
|
|
32
|
-
// "User see [Users] table has [Email] column"
|
|
33
|
+
// "User see [Users] table has [Email] column" (backward compat)
|
|
34
|
+
// Preferred: "User see [Email] column in [Users] table" (parent scoping)
|
|
33
35
|
{
|
|
34
36
|
name: 'table-column-exists',
|
|
35
37
|
matcher: (step) => {
|
|
@@ -152,11 +154,12 @@ export const tablePatterns: StepPattern[] = [
|
|
|
152
154
|
priority: 17,
|
|
153
155
|
},
|
|
154
156
|
|
|
155
|
-
// "User see [Users] table
|
|
157
|
+
// "User see [Users] table row with {{name}}" (preferred)
|
|
158
|
+
// Also accepts: "User see [Users] table has row with {{name}}" (backward compat)
|
|
156
159
|
{
|
|
157
160
|
name: 'table-row-exists',
|
|
158
161
|
matcher: (step) => {
|
|
159
|
-
return /\btable\s+has\s+row\s+with\b/i.test(step.text) && !!step.dataRef;
|
|
162
|
+
return /\btable\s+(?:has\s+)?row\s+with\b/i.test(step.text) && !!step.dataRef;
|
|
160
163
|
},
|
|
161
164
|
resolver: (step, context) => {
|
|
162
165
|
const resolved = context.selectorResolver.resolveSelector(
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Rules Updater
|
|
3
|
+
* Updates AI rules, commands, and skills from bundled templates.
|
|
4
|
+
* Used by `sungen update` command.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
|
|
10
|
+
// File mapping: [templateFile, outputPath]
|
|
11
|
+
// Shared with project-initializer.ts
|
|
12
|
+
export const AI_RULES_FILE_MAPPING: [string, string][] = [
|
|
13
|
+
// Config
|
|
14
|
+
['claude-config.md', 'CLAUDE.md'],
|
|
15
|
+
['copilot-config.md', '.github/copilot-instructions.md'],
|
|
16
|
+
|
|
17
|
+
// Commands — Claude Code
|
|
18
|
+
['claude-cmd-add-screen.md', '.claude/commands/sungen/add-screen.md'],
|
|
19
|
+
['claude-cmd-make-tc.md', '.claude/commands/sungen/make-tc.md'],
|
|
20
|
+
['claude-cmd-make-test.md', '.claude/commands/sungen/make-test.md'],
|
|
21
|
+
|
|
22
|
+
// Commands — GitHub Copilot
|
|
23
|
+
['copilot-cmd-add-screen.md', '.github/prompts/sungen-add-screen.prompt.md'],
|
|
24
|
+
['copilot-cmd-make-tc.md', '.github/prompts/sungen-make-tc.prompt.md'],
|
|
25
|
+
['copilot-cmd-make-test.md', '.github/prompts/sungen-make-test.prompt.md'],
|
|
26
|
+
|
|
27
|
+
// Skills — Claude Code
|
|
28
|
+
['claude-skill-gherkin-syntax.md', '.claude/skills/sungen-gherkin-syntax/SKILL.md'],
|
|
29
|
+
['claude-skill-selector-keys.md', '.claude/skills/sungen-selector-keys/SKILL.md'],
|
|
30
|
+
['claude-skill-error-mapping.md', '.claude/skills/sungen-error-mapping/SKILL.md'],
|
|
31
|
+
['claude-skill-tc-generation.md', '.claude/skills/sungen-tc-generation/SKILL.md'],
|
|
32
|
+
['claude-skill-selector-fix.md', '.claude/skills/sungen-selector-fix/SKILL.md'],
|
|
33
|
+
|
|
34
|
+
// Skills — GitHub Copilot
|
|
35
|
+
['github-skill-sungen-gherkin-syntax.md', '.github/skills/sungen-gherkin-syntax/SKILL.md'],
|
|
36
|
+
['github-skill-sungen-selector-keys.md', '.github/skills/sungen-selector-keys/SKILL.md'],
|
|
37
|
+
['github-skill-sungen-error-mapping.md', '.github/skills/sungen-error-mapping/SKILL.md'],
|
|
38
|
+
['github-skill-sungen-tc-generation.md', '.github/skills/sungen-tc-generation/SKILL.md'],
|
|
39
|
+
['github-skill-sungen-selector-fix.md', '.github/skills/sungen-selector-fix/SKILL.md'],
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
export class AIRulesUpdater {
|
|
43
|
+
private cwd: string;
|
|
44
|
+
private aiTemplateDir: string;
|
|
45
|
+
|
|
46
|
+
constructor(cwd: string) {
|
|
47
|
+
this.cwd = cwd;
|
|
48
|
+
this.aiTemplateDir = path.join(__dirname, 'templates', 'ai-instructions');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async update(dryRun: boolean): Promise<void> {
|
|
52
|
+
console.log('🔄 Updating AI rules, commands, and skills...\n');
|
|
53
|
+
|
|
54
|
+
const updated: string[] = [];
|
|
55
|
+
const created: string[] = [];
|
|
56
|
+
const unchanged: string[] = [];
|
|
57
|
+
const missing: string[] = [];
|
|
58
|
+
|
|
59
|
+
for (const [templateFile, outputRelPath] of AI_RULES_FILE_MAPPING) {
|
|
60
|
+
const templatePath = path.join(this.aiTemplateDir, templateFile);
|
|
61
|
+
const outputPath = path.join(this.cwd, outputRelPath);
|
|
62
|
+
|
|
63
|
+
if (!fs.existsSync(templatePath)) {
|
|
64
|
+
missing.push(templateFile);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const newContent = fs.readFileSync(templatePath, 'utf-8');
|
|
69
|
+
|
|
70
|
+
if (fs.existsSync(outputPath)) {
|
|
71
|
+
const currentContent = fs.readFileSync(outputPath, 'utf-8');
|
|
72
|
+
if (currentContent === newContent) {
|
|
73
|
+
unchanged.push(outputRelPath);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!dryRun) {
|
|
78
|
+
fs.writeFileSync(outputPath, newContent, 'utf-8');
|
|
79
|
+
}
|
|
80
|
+
updated.push(outputRelPath);
|
|
81
|
+
} else {
|
|
82
|
+
if (!dryRun) {
|
|
83
|
+
const outputDir = path.dirname(outputPath);
|
|
84
|
+
if (!fs.existsSync(outputDir)) {
|
|
85
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
86
|
+
}
|
|
87
|
+
fs.writeFileSync(outputPath, newContent, 'utf-8');
|
|
88
|
+
}
|
|
89
|
+
created.push(outputRelPath);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Print results
|
|
94
|
+
if (dryRun) {
|
|
95
|
+
console.log('📋 Dry run — no files changed\n');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (updated.length > 0) {
|
|
99
|
+
console.log(`✏️ Updated (${updated.length}):`);
|
|
100
|
+
for (const f of updated) {
|
|
101
|
+
console.log(` ${f}`);
|
|
102
|
+
}
|
|
103
|
+
console.log();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (created.length > 0) {
|
|
107
|
+
console.log(`✨ Created (${created.length}):`);
|
|
108
|
+
for (const f of created) {
|
|
109
|
+
console.log(` ${f}`);
|
|
110
|
+
}
|
|
111
|
+
console.log();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (unchanged.length > 0) {
|
|
115
|
+
console.log(`✅ Already up to date (${unchanged.length}):`);
|
|
116
|
+
for (const f of unchanged) {
|
|
117
|
+
console.log(` ${f}`);
|
|
118
|
+
}
|
|
119
|
+
console.log();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (missing.length > 0) {
|
|
123
|
+
console.log(`⚠️ Template not found (${missing.length}):`);
|
|
124
|
+
for (const f of missing) {
|
|
125
|
+
console.log(` ${f}`);
|
|
126
|
+
}
|
|
127
|
+
console.log();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const totalChanges = updated.length + created.length;
|
|
131
|
+
if (totalChanges === 0) {
|
|
132
|
+
console.log('All AI rules are up to date. No changes needed.');
|
|
133
|
+
} else if (dryRun) {
|
|
134
|
+
console.log(`${totalChanges} file(s) would be changed. Run without --dry-run to apply.`);
|
|
135
|
+
} else {
|
|
136
|
+
console.log(`${totalChanges} file(s) updated successfully.`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -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;
|
|
@@ -250,38 +251,7 @@ export class ProjectInitializer {
|
|
|
250
251
|
private createAIRules(): void {
|
|
251
252
|
const aiTemplateDir = path.join(__dirname, 'templates', 'ai-instructions');
|
|
252
253
|
|
|
253
|
-
|
|
254
|
-
const fileMapping: [string, string][] = [
|
|
255
|
-
// Config
|
|
256
|
-
['claude-config.md', 'CLAUDE.md'],
|
|
257
|
-
['copilot-config.md', '.github/copilot-instructions.md'],
|
|
258
|
-
|
|
259
|
-
// Commands — Claude Code
|
|
260
|
-
['claude-cmd-add-screen.md', '.claude/commands/sungen/add-screen.md'],
|
|
261
|
-
['claude-cmd-make-tc.md', '.claude/commands/sungen/make-tc.md'],
|
|
262
|
-
['claude-cmd-make-test.md', '.claude/commands/sungen/make-test.md'],
|
|
263
|
-
|
|
264
|
-
// Commands — GitHub Copilot
|
|
265
|
-
['copilot-cmd-add-screen.md', '.github/prompts/sungen-add-screen.prompt.md'],
|
|
266
|
-
['copilot-cmd-make-tc.md', '.github/prompts/sungen-make-tc.prompt.md'],
|
|
267
|
-
['copilot-cmd-make-test.md', '.github/prompts/sungen-make-test.prompt.md'],
|
|
268
|
-
|
|
269
|
-
// Skills — Claude Code
|
|
270
|
-
['claude-skill-gherkin-syntax.md', '.claude/skills/sungen-gherkin-syntax/SKILL.md'],
|
|
271
|
-
['claude-skill-selector-keys.md', '.claude/skills/sungen-selector-keys/SKILL.md'],
|
|
272
|
-
['claude-skill-error-mapping.md', '.claude/skills/sungen-error-mapping/SKILL.md'],
|
|
273
|
-
['claude-skill-tc-generation.md', '.claude/skills/sungen-tc-generation/SKILL.md'],
|
|
274
|
-
['claude-skill-selector-fix.md', '.claude/skills/sungen-selector-fix/SKILL.md'],
|
|
275
|
-
|
|
276
|
-
// Skills — GitHub Copilot (separate copies with Copilot-friendly descriptions)
|
|
277
|
-
['github-skill-sungen-gherkin-syntax.md', '.github/skills/sungen-gherkin-syntax/SKILL.md'],
|
|
278
|
-
['github-skill-sungen-selector-keys.md', '.github/skills/sungen-selector-keys/SKILL.md'],
|
|
279
|
-
['github-skill-sungen-error-mapping.md', '.github/skills/sungen-error-mapping/SKILL.md'],
|
|
280
|
-
['github-skill-sungen-tc-generation.md', '.github/skills/sungen-tc-generation/SKILL.md'],
|
|
281
|
-
['github-skill-sungen-selector-fix.md', '.github/skills/sungen-selector-fix/SKILL.md'],
|
|
282
|
-
];
|
|
283
|
-
|
|
284
|
-
for (const [templateFile, outputRelPath] of fileMapping) {
|
|
254
|
+
for (const [templateFile, outputRelPath] of AI_RULES_FILE_MAPPING) {
|
|
285
255
|
const outputPath = path.join(this.cwd, outputRelPath);
|
|
286
256
|
|
|
287
257
|
if (fs.existsSync(outputPath)) {
|
|
@@ -47,6 +47,8 @@ export class ScreenManager {
|
|
|
47
47
|
const featuresDir = path.join(screenDir, 'features');
|
|
48
48
|
const selectorsDir = path.join(screenDir, 'selectors');
|
|
49
49
|
const testDataDir = path.join(screenDir, 'test-data');
|
|
50
|
+
const requirementsDir = path.join(screenDir, 'requirements');
|
|
51
|
+
const requirementsUiDir = path.join(requirementsDir, 'ui');
|
|
50
52
|
|
|
51
53
|
// File paths
|
|
52
54
|
const featurePath = path.join(featuresDir, `${filename}.feature`);
|
|
@@ -66,6 +68,7 @@ export class ScreenManager {
|
|
|
66
68
|
fs.mkdirSync(featuresDir, { recursive: true });
|
|
67
69
|
fs.mkdirSync(selectorsDir, { recursive: true });
|
|
68
70
|
fs.mkdirSync(testDataDir, { recursive: true });
|
|
71
|
+
fs.mkdirSync(requirementsUiDir, { recursive: true });
|
|
69
72
|
} catch (error) {
|
|
70
73
|
console.error(`Error: Failed to create directories`);
|
|
71
74
|
console.error(` ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -111,15 +114,27 @@ export class ScreenManager {
|
|
|
111
114
|
].join('\n'), 'utf-8');
|
|
112
115
|
}
|
|
113
116
|
|
|
117
|
+
// Generate requirements spec.md (only on first screen creation)
|
|
118
|
+
const specPath = path.join(requirementsDir, 'spec.md');
|
|
119
|
+
if (!fs.existsSync(specPath)) {
|
|
120
|
+
fs.writeFileSync(specPath, this.generateSpecTemplate(options, screenName), 'utf-8');
|
|
121
|
+
}
|
|
122
|
+
|
|
114
123
|
// Display success
|
|
115
124
|
console.log(`Created files:`);
|
|
116
125
|
console.log(` ${path.relative(this.cwd, featurePath)}`);
|
|
117
126
|
console.log(` ${path.relative(this.cwd, selectorPath)}`);
|
|
118
|
-
console.log(` ${path.relative(this.cwd, testDataPath)}
|
|
127
|
+
console.log(` ${path.relative(this.cwd, testDataPath)}`);
|
|
128
|
+
if (isFirstFile) {
|
|
129
|
+
console.log(` ${path.relative(this.cwd, specPath)}`);
|
|
130
|
+
console.log(` ${path.relative(this.cwd, requirementsUiDir)}/`);
|
|
131
|
+
}
|
|
132
|
+
console.log('');
|
|
119
133
|
|
|
120
134
|
console.log('Next steps:');
|
|
121
|
-
console.log(` 1.
|
|
122
|
-
console.log(`
|
|
135
|
+
console.log(` 1. Fill requirements/spec.md with screen spec (fields, validation, business rules)`);
|
|
136
|
+
console.log(` Optionally add UI designs to requirements/ui/ (screenshots, mockups)`);
|
|
137
|
+
console.log(` 2. Generate test cases: /sungen:make-tc ${screenName} (or /sungen-make-tc)`);
|
|
123
138
|
console.log(` 3. Compile: sungen generate --screen ${screenName}`);
|
|
124
139
|
console.log(` 4. Run: npx playwright test\n`);
|
|
125
140
|
}
|
|
@@ -150,6 +165,60 @@ export class ScreenManager {
|
|
|
150
165
|
return this.normalizeScreenName(lastSegment);
|
|
151
166
|
}
|
|
152
167
|
|
|
168
|
+
private generateSpecTemplate(options: ScreenOptions, screenName: string): string {
|
|
169
|
+
const pagePath = options.path || `/${screenName}`;
|
|
170
|
+
return `# ${options.name} Screen Specification
|
|
171
|
+
|
|
172
|
+
## Overview
|
|
173
|
+
- **URL Path:** ${pagePath}
|
|
174
|
+
- **Auth Required:** no
|
|
175
|
+
- **Platform:** web
|
|
176
|
+
|
|
177
|
+
## Sections
|
|
178
|
+
|
|
179
|
+
### Section: [Section Name]
|
|
180
|
+
- **Type:** form | table | list | card | tabs | modal | search | navigation
|
|
181
|
+
- **Description:** [Brief description of this section]
|
|
182
|
+
|
|
183
|
+
#### Fields
|
|
184
|
+
<!-- Remove this table if section has no input fields -->
|
|
185
|
+
| Field | Type | Required | Constraints | Default |
|
|
186
|
+
|-------|------|----------|-------------|---------|
|
|
187
|
+
| [Field Name] | input (text) | yes | max 255 | — |
|
|
188
|
+
|
|
189
|
+
#### Actions
|
|
190
|
+
| Action | Element | Behavior |
|
|
191
|
+
|--------|---------|----------|
|
|
192
|
+
| [Action Name] | button | [What happens on click] |
|
|
193
|
+
|
|
194
|
+
#### Validation Rules
|
|
195
|
+
<!-- Exact error messages help AI generate accurate assertions -->
|
|
196
|
+
| Condition | Error Message |
|
|
197
|
+
|-----------|---------------|
|
|
198
|
+
| Empty required field | "[Exact error message from UI]" |
|
|
199
|
+
|
|
200
|
+
#### States
|
|
201
|
+
| State | Condition | Visual |
|
|
202
|
+
|-------|-----------|--------|
|
|
203
|
+
| Default | Page load | [Default appearance] |
|
|
204
|
+
| Loading | After submit | [Loading indicator] |
|
|
205
|
+
| Error | Validation fail | [Error appearance] |
|
|
206
|
+
| Success | Action complete | [Success behavior] |
|
|
207
|
+
|
|
208
|
+
## Business Rules
|
|
209
|
+
<!-- Rules that affect test logic: limits, permissions, conditions -->
|
|
210
|
+
- [Rule 1]
|
|
211
|
+
|
|
212
|
+
## Accessibility
|
|
213
|
+
<!-- Tab order, aria-labels, screen reader behavior -->
|
|
214
|
+
- Tab order: [field1] → [field2] → [submit]
|
|
215
|
+
|
|
216
|
+
## Notes
|
|
217
|
+
<!-- Edge cases, known issues, environment-specific behavior -->
|
|
218
|
+
- [Note 1]
|
|
219
|
+
`;
|
|
220
|
+
}
|
|
221
|
+
|
|
153
222
|
private generateFeatureTemplate(options: ScreenOptions, filename: string): string {
|
|
154
223
|
const screenName = this.normalizeScreenName(options.name);
|
|
155
224
|
const featurePath = options.path || `/${screenName}`;
|
|
@@ -25,19 +25,28 @@ Run:
|
|
|
25
25
|
sungen add --screen <screen> --path <path>
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
### 2.
|
|
28
|
+
### 2. Fill requirements (recommended)
|
|
29
|
+
|
|
30
|
+
Ask the user: "Would you like to fill in `requirements/spec.md` now? This helps generate higher quality test cases."
|
|
31
|
+
|
|
32
|
+
- If yes → open `qa/screens/<screen>/requirements/spec.md` and help the user fill sections, fields, validation rules, business rules, and states.
|
|
33
|
+
- If they have UI designs (screenshots, Figma exports, mockups) → suggest copying them to `requirements/ui/`.
|
|
34
|
+
- If no → proceed to step 3.
|
|
35
|
+
|
|
36
|
+
### 3. Create test cases
|
|
29
37
|
|
|
30
38
|
Ask the user: "Would you like to create test cases now?"
|
|
31
39
|
|
|
32
|
-
If yes
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
40
|
+
If yes → **you MUST use the Skill tool** to invoke `/sungen:make-tc <screen>`. This is critical because `make-tc` auto-loads the `sungen-gherkin-syntax` and `sungen-tc-generation` skills which contain the full Gherkin syntax rules, pattern shapes, viewpoint checklists, and output format. **Do NOT attempt to generate test cases yourself** — always invoke the Skill tool so these skills are properly loaded.
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
Skill: make-tc
|
|
44
|
+
Args: <screen>
|
|
45
|
+
```
|
|
36
46
|
|
|
37
|
-
###
|
|
47
|
+
### 4. Confirm
|
|
38
48
|
|
|
39
|
-
|
|
40
|
-
-
|
|
41
|
-
- Edit the generated files as needed
|
|
49
|
+
If the user declined test case creation, tell them next steps:
|
|
50
|
+
- Fill `requirements/spec.md` with screen specs (if not done)
|
|
42
51
|
- Run `/sungen:make-tc <screen>` to create test cases
|
|
43
52
|
- Run `/sungen:make-test <screen>` to generate selectors, compile, and run tests
|
|
@@ -17,9 +17,16 @@ Parse **screen** from `$ARGUMENTS`. If missing, ask the user.
|
|
|
17
17
|
|
|
18
18
|
1. Verify `qa/screens/<screen>/` exists. If not → `/sungen:add-screen` first.
|
|
19
19
|
2. Check if `.feature` file already has scenarios. If yes → use `AskUserQuestion` to ask the update mode (see `sungen-tc-generation` skill for details). If no → fresh creation.
|
|
20
|
-
3.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
3. **Read requirements** — check `qa/screens/<screen>/requirements/`:
|
|
21
|
+
- If `spec.md` exists → read it as PRIMARY source (sections, fields, validation rules, business rules, states).
|
|
22
|
+
- If `ui/` has images → read them for visual context (layout, element positions, states).
|
|
23
|
+
- If `notes.md` exists → read for edge cases and additional context.
|
|
24
|
+
- Summarize what you found in requirements and present to the user.
|
|
25
|
+
4. **Explore page** (supplements requirements, or is primary source if no requirements):
|
|
26
|
+
- Use `AskUserQuestion` to ask: **Live page** (explore via Playwright MCP) or **Static designs** (screenshots, Figma)? Or **Skip** (if requirements are sufficient)?
|
|
27
|
+
- If exploring, verify and supplement requirements — flag any discrepancies found.
|
|
28
|
+
5. Follow the `sungen-tc-generation` skill for section identification, viewpoint generation, and output format. When requirements exist, use the "Requirements-Driven Generation" strategy.
|
|
29
|
+
6. Generate or update `.feature` + `test-data.yaml` following `sungen-gherkin-syntax` and `sungen-tc-generation` skills.
|
|
30
|
+
7. Show summary → next: `/sungen:make-test <screen>`
|
|
24
31
|
|
|
25
32
|
**No selectors.yaml** — selectors are generated during `/sungen:make-test`.
|
|
@@ -16,16 +16,14 @@ Parse **screen** from `$ARGUMENTS`. If missing, ask the user.
|
|
|
16
16
|
## Steps
|
|
17
17
|
|
|
18
18
|
1. Verify `qa/screens/<screen>/` has `.feature` + `test-data.yaml`. If not → `/sungen:make-tc` first.
|
|
19
|
-
2. Generate
|
|
20
|
-
3.
|
|
21
|
-
4. **
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
|
|
28
|
-
- Repeat until no failures remain or max attempts reached
|
|
29
|
-
6. **Final confirmation** — run ALL tests once to ensure no regressions.
|
|
19
|
+
2. **Generate selectors** — explore live page via MCP, build `selectors.yaml` using `sungen-selector-fix` and `sungen-selector-keys` skills.
|
|
20
|
+
3. **Proactive validation** — verify EVERY selector against the live page using `browser_snapshot` + `browser_evaluate` BEFORE running any test. Fix mismatches immediately. See `sungen-selector-fix` skill "Proactive Selector Validation" section. Target: 80%+ issues fixed before first run.
|
|
21
|
+
4. **Compile**: `sungen generate --screen <screen>`
|
|
22
|
+
5. **Batched test run** — run tests in batches of 20 via `--grep`:
|
|
23
|
+
`npx playwright test specs/generated/<screen>/*.spec.ts --grep "VP-UI-001|...|VP-UI-020" --reporter=line`
|
|
24
|
+
- If failures in batch → group by root cause, fix, recompile, re-run only failing tests
|
|
25
|
+
- If batch passes → move to next 20 tests
|
|
26
|
+
- Max 5 fix attempts per batch
|
|
27
|
+
6. **Final confirmation** — run ALL tests once to catch regressions.
|
|
30
28
|
7. After 5 fix attempts still failing → ask user about direct `.spec.ts` fix.
|
|
31
29
|
8. Show: pass/fail, attempt count, files changed.
|
|
@@ -7,12 +7,13 @@ user-invocable: false
|
|
|
7
7
|
## Standard Syntax
|
|
8
8
|
|
|
9
9
|
```
|
|
10
|
-
[Keyword] User <Action> [Target Name] <Target Type> <with {{Value}}> <is State>
|
|
10
|
+
[Keyword] User <Action> [Target Name] <Target Type> <in [Parent Name] <Parent Type>> <with {{Value}}> <is State>
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
- **Actor**: Always `User`, always active voice.
|
|
14
14
|
- **Value**: `with {{snake_case}}` — never hardcode static data.
|
|
15
15
|
- **State**: `is <keyword>` — never use `{{}}` for states.
|
|
16
|
+
- **Parent scope**: `in [Parent] parentType` — optional, only when page has 2+ similar blocks needing disambiguation.
|
|
16
17
|
|
|
17
18
|
## Keyword → Action Rules
|
|
18
19
|
|
|
@@ -112,11 +113,11 @@ User switch to [T] frame # enter iframe
|
|
|
112
113
|
User switch to [main] frame # exit iframe
|
|
113
114
|
```
|
|
114
115
|
|
|
115
|
-
### Assertions (
|
|
116
|
+
### Assertions (8 verify patterns)
|
|
116
117
|
|
|
117
118
|
```
|
|
118
119
|
# 1. Visibility
|
|
119
|
-
User see [T] message # visible (default)
|
|
120
|
+
User see [T] message # visible (default — NEVER add "is visible")
|
|
120
121
|
User see [T] modal is hidden # hidden
|
|
121
122
|
|
|
122
123
|
# 2. Text Content (exact full match — toHaveText)
|
|
@@ -140,26 +141,44 @@ User see [T] checkbox is checked # checked state
|
|
|
140
141
|
User see [T] toggle is unchecked # unchecked state
|
|
141
142
|
User see [T] dialog with {{v}} is hidden # text + state combined
|
|
142
143
|
|
|
143
|
-
# 6.
|
|
144
|
+
# 6. Attribute (toHaveAttribute — when selector YAML has `attribute` field)
|
|
145
|
+
User see [T] image with {{v}} # image src
|
|
146
|
+
User see [T] link with {{v}} # link href
|
|
147
|
+
|
|
148
|
+
# 7. Count
|
|
144
149
|
User see [T] row with {{count}} # element count
|
|
145
150
|
|
|
146
|
-
#
|
|
151
|
+
# 8. Page Context
|
|
147
152
|
User see [T] page # URL assertion
|
|
148
153
|
```
|
|
149
154
|
|
|
150
155
|
### Table
|
|
151
156
|
|
|
152
157
|
```
|
|
153
|
-
User see [
|
|
158
|
+
User see [Col] column in [Table] table # column exists (parent scoping)
|
|
159
|
+
User see [Table] table row with {{f}} # row exists
|
|
154
160
|
User see [Table] table has no row with {{f}} # row not exists
|
|
155
|
-
User see [Table] table
|
|
156
|
-
User see [Table] table has [Col] column # column exists
|
|
161
|
+
User see [Table] table with {{count}} rows # row count
|
|
157
162
|
User see [Table] table is empty # empty table
|
|
158
163
|
User see [Table] table row with {{f}} has [Col] with {{v}} # cell by filter
|
|
159
164
|
User see [Table] table row 1 [Col] cell with {{v}} # cell by index
|
|
160
165
|
User click [Act] in [Table] table row with {{f}} # action in row
|
|
161
166
|
```
|
|
162
167
|
|
|
168
|
+
### Parent Scoping (disambiguation)
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
User click [Submit] button in [User Info] form # button inside specific form
|
|
172
|
+
User fill [Email] field in [Registration] form with {{v}} # field inside specific form
|
|
173
|
+
User see [Total] text in [Summary] section with {{v}} # text inside specific section
|
|
174
|
+
User click [Delete] button in [Active Users] table # button inside specific table
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
- **Optional** — only use when page has 2+ similar UI blocks
|
|
178
|
+
- **Valid parent types**: `table`, `list`, `section`, `dialog`, `form`
|
|
179
|
+
- **Max 2 levels**: `[Target] in [Parent]`. **NEVER** nest 3 levels: `[A] in [B] in [C]`
|
|
180
|
+
- Parent resolves from selectors YAML first, falls back to auto-infer `getByRole(parentType, { name })`
|
|
181
|
+
|
|
163
182
|
### States
|
|
164
183
|
|
|
165
184
|
`hidden` `visible` `disabled` `enabled` `checked` `unchecked` `focused` `empty` `loading` `selected` `sorted ascending` `sorted descending`
|