@sun-asterisk/sungen 2.0.2 → 2.1.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 +2 -2
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +3 -2
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/unknown-element-action.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +2 -2
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-variable-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +2 -2
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-variable-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +3 -3
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-value-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-variable-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-nth.hbs +1 -1
- package/dist/generators/test-generator/patterns/assertion-patterns.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/assertion-patterns.js +12 -13
- package/dist/generators/test-generator/patterns/assertion-patterns.js.map +1 -1
- package/dist/generators/test-generator/patterns/interaction-patterns.js +4 -4
- package/dist/generators/test-generator/patterns/interaction-patterns.js.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.js +14 -14
- package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
- package/dist/orchestrator/project-initializer.d.ts +3 -6
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +62 -58
- 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 +0 -1
- package/dist/orchestrator/screen-manager.js.map +1 -1
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +42 -0
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-make-tc.md +60 -0
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-make-test.md +59 -0
- package/dist/orchestrator/templates/ai-instructions/claude-config.md +90 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md +27 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +123 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +94 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +41 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-make-tc.md +59 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-make-test.md +58 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-config.md +90 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-skill-error-mapping.md +27 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-skill-gherkin-syntax.md +123 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-skill-selector-keys.md +94 -0
- package/dist/orchestrator/templates/readme.md +43 -39
- package/docs/gherkin standards/gherkin-core-standard.md +141 -90
- package/docs/gherkin standards/gherkin-core-standard.vi.md +264 -54
- package/package.json +2 -2
- package/src/cli/commands/init.ts +3 -2
- package/src/cli/index.ts +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/unknown-element-action.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +2 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-variable-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +2 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-variable-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +3 -3
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-value-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-variable-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-nth.hbs +1 -1
- package/src/generators/test-generator/patterns/assertion-patterns.ts +12 -13
- package/src/generators/test-generator/patterns/interaction-patterns.ts +4 -4
- package/src/generators/test-generator/utils/selector-resolver.ts +15 -15
- package/src/orchestrator/project-initializer.ts +74 -58
- package/src/orchestrator/screen-manager.ts +0 -1
- package/src/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +42 -0
- package/src/orchestrator/templates/ai-instructions/claude-cmd-make-tc.md +60 -0
- package/src/orchestrator/templates/ai-instructions/claude-cmd-make-test.md +59 -0
- package/src/orchestrator/templates/ai-instructions/claude-config.md +90 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md +27 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +123 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +94 -0
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +41 -0
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-make-tc.md +59 -0
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-make-test.md +58 -0
- package/src/orchestrator/templates/ai-instructions/copilot-config.md +90 -0
- package/src/orchestrator/templates/ai-instructions/copilot-skill-error-mapping.md +27 -0
- package/src/orchestrator/templates/ai-instructions/copilot-skill-gherkin-syntax.md +123 -0
- package/src/orchestrator/templates/ai-instructions/copilot-skill-selector-keys.md +94 -0
- package/src/orchestrator/templates/readme.md +43 -39
- package/dist/orchestrator/templates/ai-rules.md +0 -189
- package/src/orchestrator/templates/ai-rules.md +0 -189
|
@@ -109,7 +109,7 @@ export const assertionPatterns: StepPattern[] = [
|
|
|
109
109
|
|
|
110
110
|
if (resolved.strategy === 'locator') {
|
|
111
111
|
const code = context.templateEngine.renderStep('hidden-with-filter-assertion', {
|
|
112
|
-
...resolved, dataValue, nth: resolved.nth
|
|
112
|
+
...resolved, dataValue, nth: resolved.nth,
|
|
113
113
|
});
|
|
114
114
|
return { code, comment: `Assert ${step.selectorRef} hidden with ${step.dataRef}` };
|
|
115
115
|
}
|
|
@@ -127,7 +127,7 @@ export const assertionPatterns: StepPattern[] = [
|
|
|
127
127
|
role: resolved.role,
|
|
128
128
|
name: resolved.name && resolved.name.trim() ? resolved.name : undefined,
|
|
129
129
|
dataValue,
|
|
130
|
-
nth: resolved.nth
|
|
130
|
+
nth: resolved.nth,
|
|
131
131
|
});
|
|
132
132
|
return { code, comment: `Assert ${step.selectorRef} hidden with ${step.dataRef}` };
|
|
133
133
|
}
|
|
@@ -135,7 +135,7 @@ export const assertionPatterns: StepPattern[] = [
|
|
|
135
135
|
const selectorValue = resolved.value || '';
|
|
136
136
|
if (selectorValue && selectorValue.trim()) {
|
|
137
137
|
const code = context.templateEngine.renderStep('hidden-with-filter-assertion', {
|
|
138
|
-
...resolved, dataValue, nth: resolved.nth
|
|
138
|
+
...resolved, dataValue, nth: resolved.nth,
|
|
139
139
|
});
|
|
140
140
|
return { code, comment: `Assert ${step.selectorRef} hidden with ${step.dataRef}` };
|
|
141
141
|
}
|
|
@@ -144,7 +144,7 @@ export const assertionPatterns: StepPattern[] = [
|
|
|
144
144
|
selectorRef: step.selectorRef,
|
|
145
145
|
selectorValue: resolved.value || '',
|
|
146
146
|
dataValue,
|
|
147
|
-
nth: resolved.nth
|
|
147
|
+
nth: resolved.nth,
|
|
148
148
|
});
|
|
149
149
|
return { code, comment: `Assert ${step.selectorRef} hidden with ${step.dataRef}` };
|
|
150
150
|
},
|
|
@@ -181,7 +181,7 @@ export const assertionPatterns: StepPattern[] = [
|
|
|
181
181
|
|
|
182
182
|
if (resolved.strategy === 'locator') {
|
|
183
183
|
const code = context.templateEngine.renderStep('disabled-with-filter-assertion', {
|
|
184
|
-
...resolved, dataValue, nth: resolved.nth
|
|
184
|
+
...resolved, dataValue, nth: resolved.nth,
|
|
185
185
|
});
|
|
186
186
|
return { code, comment: `Assert ${step.selectorRef} disabled with ${step.dataRef}` };
|
|
187
187
|
}
|
|
@@ -191,7 +191,7 @@ export const assertionPatterns: StepPattern[] = [
|
|
|
191
191
|
role: resolved.role,
|
|
192
192
|
name: resolved.name && resolved.name.trim() ? resolved.name : undefined,
|
|
193
193
|
dataValue,
|
|
194
|
-
nth: resolved.nth
|
|
194
|
+
nth: resolved.nth,
|
|
195
195
|
});
|
|
196
196
|
return { code, comment: `Assert ${step.selectorRef} disabled with ${step.dataRef}` };
|
|
197
197
|
}
|
|
@@ -199,7 +199,7 @@ export const assertionPatterns: StepPattern[] = [
|
|
|
199
199
|
const selectorValue = resolved.value || '';
|
|
200
200
|
if (selectorValue && selectorValue.trim()) {
|
|
201
201
|
const code = context.templateEngine.renderStep('disabled-with-filter-assertion', {
|
|
202
|
-
...resolved, dataValue, nth: resolved.nth
|
|
202
|
+
...resolved, dataValue, nth: resolved.nth,
|
|
203
203
|
});
|
|
204
204
|
return { code, comment: `Assert ${step.selectorRef} disabled with ${step.dataRef}` };
|
|
205
205
|
}
|
|
@@ -208,7 +208,7 @@ export const assertionPatterns: StepPattern[] = [
|
|
|
208
208
|
selectorRef: step.selectorRef,
|
|
209
209
|
selectorValue: resolved.value || '',
|
|
210
210
|
dataValue,
|
|
211
|
-
nth: resolved.nth
|
|
211
|
+
nth: resolved.nth,
|
|
212
212
|
});
|
|
213
213
|
return { code, comment: `Assert ${step.selectorRef} disabled with ${step.dataRef}` };
|
|
214
214
|
},
|
|
@@ -284,7 +284,7 @@ export const assertionPatterns: StepPattern[] = [
|
|
|
284
284
|
const code = context.templateEngine.renderStep('visible-with-locator-variable-assertion', {
|
|
285
285
|
...resolved,
|
|
286
286
|
dataValue,
|
|
287
|
-
nth: resolved.nth
|
|
287
|
+
nth: resolved.nth,
|
|
288
288
|
});
|
|
289
289
|
return {
|
|
290
290
|
code,
|
|
@@ -313,7 +313,7 @@ export const assertionPatterns: StepPattern[] = [
|
|
|
313
313
|
name: isImgRole ? (hasName ? resolved.name : dataValue) : (hasName ? resolved.name : undefined),
|
|
314
314
|
dataValue: isImgRole ? undefined : dataValue,
|
|
315
315
|
exact: resolved.exact || false,
|
|
316
|
-
nth: resolved.nth
|
|
316
|
+
nth: resolved.nth,
|
|
317
317
|
});
|
|
318
318
|
return {
|
|
319
319
|
code,
|
|
@@ -331,7 +331,7 @@ export const assertionPatterns: StepPattern[] = [
|
|
|
331
331
|
value: selectorValue,
|
|
332
332
|
dataValue,
|
|
333
333
|
dataRef: step.dataRef,
|
|
334
|
-
nth: resolved.nth
|
|
334
|
+
nth: resolved.nth,
|
|
335
335
|
exact: resolved.exact || false,
|
|
336
336
|
});
|
|
337
337
|
return {
|
|
@@ -344,7 +344,7 @@ export const assertionPatterns: StepPattern[] = [
|
|
|
344
344
|
selectorRef: step.selectorRef,
|
|
345
345
|
selectorValue: resolved.value || '',
|
|
346
346
|
dataValue,
|
|
347
|
-
nth: resolved.nth
|
|
347
|
+
nth: resolved.nth,
|
|
348
348
|
});
|
|
349
349
|
return {
|
|
350
350
|
code,
|
|
@@ -523,7 +523,6 @@ export const assertionPatterns: StepPattern[] = [
|
|
|
523
523
|
data: {
|
|
524
524
|
strategy: 'text',
|
|
525
525
|
value: dataValue,
|
|
526
|
-
nth: 0,
|
|
527
526
|
},
|
|
528
527
|
comment: `Assert ${step.dataRef} is visible`,
|
|
529
528
|
};
|
|
@@ -39,16 +39,16 @@ export const interactionPatterns: StepPattern[] = [
|
|
|
39
39
|
},
|
|
40
40
|
resolver: (step, context) => {
|
|
41
41
|
let selectorValue = '';
|
|
42
|
-
let nth
|
|
42
|
+
let nth: number | undefined;
|
|
43
43
|
try {
|
|
44
44
|
const resolved = context.selectorResolver.resolveSelector(
|
|
45
45
|
step.selectorRef!, context.featureName, step.elementType, step.nth
|
|
46
46
|
);
|
|
47
47
|
selectorValue = resolved.value || '';
|
|
48
|
-
nth = resolved.nth
|
|
48
|
+
nth = resolved.nth;
|
|
49
49
|
} catch (error) {
|
|
50
50
|
selectorValue = '';
|
|
51
|
-
nth =
|
|
51
|
+
nth = undefined;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
let dataValue: string;
|
|
@@ -174,7 +174,7 @@ export const interactionPatterns: StepPattern[] = [
|
|
|
174
174
|
|
|
175
175
|
return {
|
|
176
176
|
templateName: 'click-action',
|
|
177
|
-
data: { strategy: 'text', value
|
|
177
|
+
data: { strategy: 'text', value },
|
|
178
178
|
comment: `Click ${step.dataRef}`,
|
|
179
179
|
};
|
|
180
180
|
},
|
|
@@ -196,7 +196,7 @@ interface SelectorEntry {
|
|
|
196
196
|
type?: SelectorType; // Locator strategy
|
|
197
197
|
value?: string; // Natural language value for the locator (e.g., 'button', 'link' for role)
|
|
198
198
|
name?: string; // Accessible name for role-based selectors (e.g., 'Login', 'Submit')
|
|
199
|
-
nth?: number; // Element index (
|
|
199
|
+
nth?: number; // Element index (undefined = no .nth(), 0+ = .nth(N))
|
|
200
200
|
exact?: boolean; // Whether to use exact matching (default: false)
|
|
201
201
|
inputMethod?: string; // 'pressSequentially' for contenteditable/rich-text elements
|
|
202
202
|
|
|
@@ -375,19 +375,19 @@ export class SelectorResolver {
|
|
|
375
375
|
const normalized = SelectorResolver.normalizeElementType(elementType);
|
|
376
376
|
|
|
377
377
|
const strategyMap: Record<string, () => ResolvedSelector> = {
|
|
378
|
-
'img': () => ({ strategy: 'role', role: 'img', name: label, value: 'img'
|
|
379
|
-
'button': () => ({ strategy: 'role', role: 'button', name: label, value: 'button'
|
|
380
|
-
'link': () => ({ strategy: 'role', role: 'link', name: label, value: 'link'
|
|
381
|
-
'text': () => ({ strategy: 'text', value: label
|
|
382
|
-
'field': () => ({ strategy: 'placeholder', value: label
|
|
383
|
-
'checkbox': () => ({ strategy: 'role', role: 'checkbox', name: label, value: 'checkbox'
|
|
384
|
-
'radio': () => ({ strategy: 'role', role: 'radio', name: label, value: 'radio'
|
|
385
|
-
'row': () => ({ strategy: 'role', role: 'row', name: label, value: 'row'
|
|
386
|
-
'table': () => ({ strategy: 'role', role: 'table', name: label, value: 'table'
|
|
387
|
-
'columnheader': () => ({ strategy: 'role', role: 'columnheader', name: label, value: 'columnheader'
|
|
388
|
-
'heading': () => ({ strategy: 'role', role: 'heading', name: label, value: 'heading'
|
|
389
|
-
'list': () => ({ strategy: 'role', role: 'list', name: label, value: 'list'
|
|
390
|
-
'listitem': () => ({ strategy: 'role', role: 'listitem', value: 'listitem'
|
|
378
|
+
'img': () => ({ strategy: 'role', role: 'img', name: label, value: 'img' }),
|
|
379
|
+
'button': () => ({ strategy: 'role', role: 'button', name: label, value: 'button' }),
|
|
380
|
+
'link': () => ({ strategy: 'role', role: 'link', name: label, value: 'link' }),
|
|
381
|
+
'text': () => ({ strategy: 'text', value: label }),
|
|
382
|
+
'field': () => ({ strategy: 'placeholder', value: label }),
|
|
383
|
+
'checkbox': () => ({ strategy: 'role', role: 'checkbox', name: label, value: 'checkbox' }),
|
|
384
|
+
'radio': () => ({ strategy: 'role', role: 'radio', name: label, value: 'radio' }),
|
|
385
|
+
'row': () => ({ strategy: 'role', role: 'row', name: label, value: 'row' }),
|
|
386
|
+
'table': () => ({ strategy: 'role', role: 'table', name: label, value: 'table' }),
|
|
387
|
+
'columnheader': () => ({ strategy: 'role', role: 'columnheader', name: label, value: 'columnheader' }),
|
|
388
|
+
'heading': () => ({ strategy: 'role', role: 'heading', name: label, value: 'heading' }),
|
|
389
|
+
'list': () => ({ strategy: 'role', role: 'list', name: label, value: 'list' }),
|
|
390
|
+
'listitem': () => ({ strategy: 'role', role: 'listitem', value: 'listitem' }),
|
|
391
391
|
};
|
|
392
392
|
|
|
393
393
|
const factory = strategyMap[normalized];
|
|
@@ -549,7 +549,7 @@ export class SelectorResolver {
|
|
|
549
549
|
const value = entry.value !== undefined && entry.value !== null ? entry.value : originalLabel;
|
|
550
550
|
// Check if name exists in entry (even if empty string), use it; otherwise use originalLabel
|
|
551
551
|
const name = entry.name !== undefined && entry.name !== null ? entry.name : originalLabel;
|
|
552
|
-
const nth = entry.nth
|
|
552
|
+
const nth = entry.nth !== undefined && entry.nth !== null ? entry.nth : undefined;
|
|
553
553
|
const exact = entry.exact === true || entry.match === 'exact';
|
|
554
554
|
const inputMethod = entry.inputMethod;
|
|
555
555
|
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import * as fs from 'fs';
|
|
7
7
|
import * as path from 'path';
|
|
8
|
+
import { execSync } from 'child_process';
|
|
8
9
|
|
|
9
10
|
export class ProjectInitializer {
|
|
10
11
|
private baseCwd: string;
|
|
@@ -12,7 +13,7 @@ export class ProjectInitializer {
|
|
|
12
13
|
private createdItems: string[] = [];
|
|
13
14
|
private skippedItems: string[] = [];
|
|
14
15
|
|
|
15
|
-
constructor() {
|
|
16
|
+
constructor(private baseUrl: string) {
|
|
16
17
|
this.baseCwd = process.cwd();
|
|
17
18
|
this.cwd = this.baseCwd;
|
|
18
19
|
}
|
|
@@ -37,6 +38,9 @@ export class ProjectInitializer {
|
|
|
37
38
|
// Create directories
|
|
38
39
|
this.createDirectories();
|
|
39
40
|
|
|
41
|
+
// Ensure package.json and install Playwright
|
|
42
|
+
await this.setupDependencies();
|
|
43
|
+
|
|
40
44
|
// Create playwright config if doesn't exist
|
|
41
45
|
this.createPlaywrightConfig();
|
|
42
46
|
|
|
@@ -99,7 +103,8 @@ export class ProjectInitializer {
|
|
|
99
103
|
return;
|
|
100
104
|
}
|
|
101
105
|
|
|
102
|
-
const configContent = this.getPlaywrightConfigTemplate()
|
|
106
|
+
const configContent = this.getPlaywrightConfigTemplate()
|
|
107
|
+
.replace('https://example.com', this.baseUrl);
|
|
103
108
|
fs.writeFileSync(configPath, configContent, 'utf-8');
|
|
104
109
|
this.createdItems.push('playwright.config.ts');
|
|
105
110
|
}
|
|
@@ -174,9 +179,6 @@ export class ProjectInitializer {
|
|
|
174
179
|
});
|
|
175
180
|
}
|
|
176
181
|
|
|
177
|
-
// Check for Playwright installation
|
|
178
|
-
this.checkDependencies();
|
|
179
|
-
|
|
180
182
|
console.log('\nNext steps:');
|
|
181
183
|
let stepIndex = 1;
|
|
182
184
|
if (projectName) {
|
|
@@ -192,48 +194,54 @@ export class ProjectInitializer {
|
|
|
192
194
|
* Create AI rules files for GitHub Copilot and Claude Code
|
|
193
195
|
*/
|
|
194
196
|
private createAIRules(): void {
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
//
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
197
|
+
const aiTemplateDir = path.join(__dirname, 'templates', 'ai-instructions');
|
|
198
|
+
|
|
199
|
+
// File mapping: [templateFile, outputPath]
|
|
200
|
+
const fileMapping: [string, string][] = [
|
|
201
|
+
// Config
|
|
202
|
+
['claude-config.md', 'CLAUDE.md'],
|
|
203
|
+
['copilot-config.md', '.github/copilot-instructions.md'],
|
|
204
|
+
|
|
205
|
+
// Commands — Claude Code
|
|
206
|
+
['claude-cmd-add-screen.md', '.claude/commands/sungen/add-screen.md'],
|
|
207
|
+
['claude-cmd-make-tc.md', '.claude/commands/sungen/make-tc.md'],
|
|
208
|
+
['claude-cmd-make-test.md', '.claude/commands/sungen/make-test.md'],
|
|
209
|
+
|
|
210
|
+
// Commands — GitHub Copilot
|
|
211
|
+
['copilot-cmd-add-screen.md', '.github/prompts/sungen-add-screen.prompt.md'],
|
|
212
|
+
['copilot-cmd-make-tc.md', '.github/prompts/sungen-make-tc.prompt.md'],
|
|
213
|
+
['copilot-cmd-make-test.md', '.github/prompts/sungen-make-test.prompt.md'],
|
|
214
|
+
|
|
215
|
+
// Skills — Claude Code
|
|
216
|
+
['claude-skill-gherkin-syntax.md', '.claude/skills/sungen-gherkin-syntax/SKILL.md'],
|
|
217
|
+
['claude-skill-selector-keys.md', '.claude/skills/sungen-selector-keys/SKILL.md'],
|
|
218
|
+
['claude-skill-error-mapping.md', '.claude/skills/sungen-error-mapping/SKILL.md'],
|
|
219
|
+
|
|
220
|
+
// Skills — GitHub Copilot
|
|
221
|
+
['copilot-skill-gherkin-syntax.md', '.github/prompts/sungen-gherkin-syntax.prompt.md'],
|
|
222
|
+
['copilot-skill-selector-keys.md', '.github/prompts/sungen-selector-keys.prompt.md'],
|
|
223
|
+
['copilot-skill-error-mapping.md', '.github/prompts/sungen-error-mapping.prompt.md'],
|
|
224
|
+
];
|
|
209
225
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if (!fs.existsSync(claudePath)) {
|
|
213
|
-
fs.writeFileSync(claudePath, rulesContent, 'utf-8');
|
|
214
|
-
this.createdItems.push('CLAUDE.md');
|
|
215
|
-
} else {
|
|
216
|
-
this.skippedItems.push('CLAUDE.md');
|
|
217
|
-
}
|
|
226
|
+
for (const [templateFile, outputRelPath] of fileMapping) {
|
|
227
|
+
const outputPath = path.join(this.cwd, outputRelPath);
|
|
218
228
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if (!fs.existsSync(dictPath)) {
|
|
223
|
-
// Copy from sungen package if available, otherwise generate stub
|
|
224
|
-
const packageDictPath = path.join(__dirname, '..', '..', 'docs', 'gherkin-dictionary.md');
|
|
225
|
-
if (!fs.existsSync(docsDir)) {
|
|
226
|
-
fs.mkdirSync(docsDir, { recursive: true });
|
|
229
|
+
if (fs.existsSync(outputPath)) {
|
|
230
|
+
this.skippedItems.push(outputRelPath);
|
|
231
|
+
continue;
|
|
227
232
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
fs.
|
|
233
|
+
|
|
234
|
+
const outputDir = path.dirname(outputPath);
|
|
235
|
+
if (!fs.existsSync(outputDir)) {
|
|
236
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
232
237
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
238
|
+
|
|
239
|
+
const templatePath = path.join(aiTemplateDir, templateFile);
|
|
240
|
+
const content = fs.readFileSync(templatePath, 'utf-8');
|
|
241
|
+
fs.writeFileSync(outputPath, content, 'utf-8');
|
|
242
|
+
this.createdItems.push(outputRelPath);
|
|
236
243
|
}
|
|
244
|
+
|
|
237
245
|
}
|
|
238
246
|
|
|
239
247
|
/**
|
|
@@ -244,30 +252,38 @@ export class ProjectInitializer {
|
|
|
244
252
|
return fs.readFileSync(templatePath, 'utf-8');
|
|
245
253
|
}
|
|
246
254
|
|
|
247
|
-
/**
|
|
248
|
-
* Get AI rules content (shared between Copilot and Claude)
|
|
249
|
-
*/
|
|
250
|
-
private getAIRulesContent(): string {
|
|
251
|
-
return this.readTemplate('ai-rules.md');
|
|
252
|
-
}
|
|
253
|
-
|
|
254
255
|
/**
|
|
255
256
|
* Check for required dependencies
|
|
256
257
|
*/
|
|
257
|
-
private
|
|
258
|
+
private async setupDependencies(): Promise<void> {
|
|
259
|
+
const packageJsonPath = path.join(this.cwd, 'package.json');
|
|
260
|
+
const execOpts = { cwd: this.cwd, stdio: 'inherit' as const };
|
|
261
|
+
|
|
262
|
+
// Initialize package.json if it doesn't exist
|
|
263
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
264
|
+
console.log('📦 No package.json found. Initializing...\n');
|
|
265
|
+
execSync('npm init -y', execOpts);
|
|
266
|
+
this.createdItems.push('package.json');
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Check if @playwright/test is already installed
|
|
258
270
|
try {
|
|
259
|
-
const packageJsonPath = path.join(this.cwd, 'package.json');
|
|
260
271
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
261
272
|
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
console.log(' npm install -D @playwright/test');
|
|
266
|
-
console.log(' npx playwright install');
|
|
273
|
+
if (deps['@playwright/test']) {
|
|
274
|
+
console.log('✓ @playwright/test already installed\n');
|
|
275
|
+
return;
|
|
267
276
|
}
|
|
268
|
-
} catch
|
|
269
|
-
//
|
|
277
|
+
} catch {
|
|
278
|
+
// package.json just created, proceed with install
|
|
270
279
|
}
|
|
280
|
+
|
|
281
|
+
// Install Playwright
|
|
282
|
+
console.log('📦 Installing @playwright/test...\n');
|
|
283
|
+
execSync('npm install -D @playwright/test', execOpts);
|
|
284
|
+
|
|
285
|
+
console.log('\n🎭 Installing Playwright browsers...\n');
|
|
286
|
+
execSync('npx playwright install', execOpts);
|
|
271
287
|
}
|
|
272
288
|
|
|
273
289
|
/**
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: add-screen
|
|
3
|
+
description: 'Add a new Sungen screen — scaffolds directories and delegates to make-tc for test case creation'
|
|
4
|
+
argument-hint: [screen-name] [url-path]
|
|
5
|
+
allowed-tools: Read, Grep, Bash, Glob
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You are adding a new Sungen screen for test generation.
|
|
9
|
+
|
|
10
|
+
## Parameters
|
|
11
|
+
|
|
12
|
+
Parse from `$ARGUMENTS`:
|
|
13
|
+
- **screen** — screen name (e.g., `login`, `dashboard`, `settings`)
|
|
14
|
+
- **path** — URL path (e.g., `/login`, `/dashboard`, `/settings`)
|
|
15
|
+
|
|
16
|
+
If **screen** is missing, ask: "What is the screen name? (e.g., `login`, `dashboard`)"
|
|
17
|
+
If **path** is missing, ask: "What is the URL path? (e.g., `/login`, `/dashboard`)"
|
|
18
|
+
|
|
19
|
+
## Steps
|
|
20
|
+
|
|
21
|
+
### 1. Scaffold the screen
|
|
22
|
+
|
|
23
|
+
Run:
|
|
24
|
+
```bash
|
|
25
|
+
sungen add --screen <screen> --path <path>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### 2. Create test cases
|
|
29
|
+
|
|
30
|
+
Ask the user: "Would you like to create test cases now?"
|
|
31
|
+
|
|
32
|
+
If yes, delegate to `/sungen:make-tc <screen>` to handle:
|
|
33
|
+
- Exploring the live page or analyzing static designs
|
|
34
|
+
- Gathering test viewpoints (UI/UX, Validation, Logic, Security)
|
|
35
|
+
- Generating the 3 files (feature, selectors, test-data)
|
|
36
|
+
|
|
37
|
+
### 3. Confirm
|
|
38
|
+
|
|
39
|
+
Tell the user what was created and next steps:
|
|
40
|
+
- If the page requires authentication before exploring via browser, read `baseURL` from `playwright.config.ts` and tell the user to manually run: `sungen makeauth <role> --url <baseURL>` (e.g., `sungen makeauth admin --url http://localhost:3000`). **Do NOT execute this command yourself.**
|
|
41
|
+
- Edit the generated files as needed
|
|
42
|
+
- Run `sungen generate --screen <screen>` to compile to Playwright `.spec.ts`
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: make-tc
|
|
3
|
+
description: 'Create test cases for a Sungen screen — gathers viewpoints, explores live page or static designs, generates feature/selectors/test-data files'
|
|
4
|
+
argument-hint: [screen-name]
|
|
5
|
+
allowed-tools: Read, Grep, Bash, Glob
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Role
|
|
9
|
+
|
|
10
|
+
You are a **Senior QA Engineer** specialized in test case design. You structure test cases by viewpoint categories and translate UI into Gherkin test cases following the `sungen-gherkin-syntax` and `sungen-selector-keys` skills.
|
|
11
|
+
|
|
12
|
+
## Parameters
|
|
13
|
+
|
|
14
|
+
Parse from `$ARGUMENTS`:
|
|
15
|
+
- **screen** — screen name (e.g., `login`, `dashboard`)
|
|
16
|
+
|
|
17
|
+
If missing, ask the user.
|
|
18
|
+
|
|
19
|
+
## Steps
|
|
20
|
+
|
|
21
|
+
### 1. Verify screen exists
|
|
22
|
+
|
|
23
|
+
Check `qa/screens/<screen>/` exists. If not, tell user to run `/sungen:add-screen` first.
|
|
24
|
+
|
|
25
|
+
### 2. Determine source mode
|
|
26
|
+
|
|
27
|
+
Ask: "Do you have a **live page** or **static designs** (screenshots, Figma, images)?"
|
|
28
|
+
|
|
29
|
+
**Live page →** explore via browser (see CLAUDE.md for Playwright MCP rules). If auth needed, check `specs/.auth/<role>.json` exists — if not, read `baseURL` from `playwright.config.ts` and tell the user to manually run: `sungen makeauth <role> --url <baseURL>` (e.g., `sungen makeauth admin --url http://localhost:3000`). **Do NOT execute `sungen makeauth` yourself.** Wait for the user to confirm auth is ready before proceeding.
|
|
30
|
+
|
|
31
|
+
**Static designs →** ask user to provide screenshot paths, Figma exports, or UI descriptions. Note: selectors will be best-guess until live page is available.
|
|
32
|
+
|
|
33
|
+
Present discovered elements to user, then proceed.
|
|
34
|
+
|
|
35
|
+
### 3. Gather test viewpoints
|
|
36
|
+
|
|
37
|
+
| VP Category | Description |
|
|
38
|
+
|---|---|
|
|
39
|
+
| **UI/UX** | Default screen appearance, static elements, default states |
|
|
40
|
+
| **Validation** | Field/form validation, error messages |
|
|
41
|
+
| **Logic** | Business logic, happy paths, redirects |
|
|
42
|
+
| **Security** | Auth guards, permission checks |
|
|
43
|
+
|
|
44
|
+
For each viewpoint, gather: **UI Target**, **Test Viewpoint**, **Expected Result**.
|
|
45
|
+
|
|
46
|
+
User can provide all at once as a table:
|
|
47
|
+
```
|
|
48
|
+
| VP Category | UI Target | Test Viewpoint | Expected Result |
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 4. Generate files
|
|
52
|
+
|
|
53
|
+
Generate the 3 files following `sungen-gherkin-syntax` and `sungen-selector-keys` skill rules:
|
|
54
|
+
- `qa/screens/<screen>/features/<screen>.feature` — one Scenario per viewpoint
|
|
55
|
+
- `qa/screens/<screen>/selectors/<screen>.yaml`
|
|
56
|
+
- `qa/screens/<screen>/test-data/<screen>.yaml`
|
|
57
|
+
|
|
58
|
+
### 5. Confirm
|
|
59
|
+
|
|
60
|
+
Show what was generated. Next: `sungen generate --screen <screen>`
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: make-test
|
|
3
|
+
description: 'Compile and run Playwright tests for a Sungen screen — auto-fixes selectors on failure, falls back to AI .spec.ts fix after 5 attempts'
|
|
4
|
+
argument-hint: [screen-name]
|
|
5
|
+
allowed-tools: Read, Grep, Bash, Glob, Edit
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Role
|
|
9
|
+
|
|
10
|
+
You are a **Senior Developer** specialized in Playwright test debugging. You diagnose test failures, fix selectors/test-data, and patch `.spec.ts` when needed. Use the `sungen-error-mapping` and `sungen-selector-keys` skills for error diagnosis and key fixes.
|
|
11
|
+
|
|
12
|
+
## Parameters
|
|
13
|
+
|
|
14
|
+
Parse from `$ARGUMENTS`:
|
|
15
|
+
- **screen** — screen name (e.g., `login`, `dashboard`)
|
|
16
|
+
|
|
17
|
+
If missing, ask the user.
|
|
18
|
+
|
|
19
|
+
## Steps
|
|
20
|
+
|
|
21
|
+
### 1. Verify screen exists
|
|
22
|
+
|
|
23
|
+
Check `qa/screens/<screen>/` has all 3 source files. If not, tell user to run `/sungen:add-screen` and `/sungen:make-tc` first.
|
|
24
|
+
|
|
25
|
+
### 2. Compile
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
sungen generate --screen <screen>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
If fails, fix source files per `sungen-error-mapping` skill and retry.
|
|
32
|
+
|
|
33
|
+
### 3. Run tests
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npx playwright test specs/screens/<screen>/<screen>.spec.ts
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
If pass → Step 5. If fail → Step 4.
|
|
40
|
+
|
|
41
|
+
### 4. Fix and retry (max 5 attempts)
|
|
42
|
+
|
|
43
|
+
Per attempt:
|
|
44
|
+
1. Read Playwright error output
|
|
45
|
+
2. Map error to fix using `sungen-error-mapping` skill
|
|
46
|
+
3. Fix `selectors.yaml` or `test-data.yaml`
|
|
47
|
+
4. Recompile: `sungen generate --screen <screen>`
|
|
48
|
+
5. Retest
|
|
49
|
+
|
|
50
|
+
### 5. Fallback — AI fix .spec.ts
|
|
51
|
+
|
|
52
|
+
After 5 failed attempts, ask user:
|
|
53
|
+
> Tests still failing. Would you like me to directly fix the `.spec.ts` file?
|
|
54
|
+
|
|
55
|
+
If yes: read `.spec.ts`, fix locators/assertions, mark with `// AI-fixed: <reason>`
|
|
56
|
+
|
|
57
|
+
### 6. Confirm
|
|
58
|
+
|
|
59
|
+
Show: pass/fail, attempt count, files changed.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Sungen AI Rules
|
|
2
|
+
|
|
3
|
+
You generate 3 files for sungen — a Gherkin compiler that produces Playwright tests.
|
|
4
|
+
**You do NOT write Playwright code.** You only write `.feature`, `selectors.yaml`, and `test-data.yaml`.
|
|
5
|
+
|
|
6
|
+
For Gherkin syntax, selector types, and YAML rules, the `sungen-gherkin-syntax` skill is auto-loaded when needed.
|
|
7
|
+
For error diagnosis, the `sungen-error-mapping` skill is auto-loaded when needed.
|
|
8
|
+
|
|
9
|
+
## Complete Example
|
|
10
|
+
|
|
11
|
+
Given a login page, here are the 3 files you generate:
|
|
12
|
+
|
|
13
|
+
**qa/screens/login/features/login.feature**
|
|
14
|
+
```gherkin
|
|
15
|
+
@auth:admin
|
|
16
|
+
Feature: Login Screen
|
|
17
|
+
|
|
18
|
+
Scenario: Successful login
|
|
19
|
+
Given User is on [login] page
|
|
20
|
+
When User fill [Email] field with {{email}}
|
|
21
|
+
And User fill [Password] field with {{password}}
|
|
22
|
+
And User click [Submit] button
|
|
23
|
+
Then User see [Welcome] heading with {{welcome_text}}
|
|
24
|
+
And User see [Dashboard] link is visible
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**qa/screens/login/selectors/login.yaml**
|
|
28
|
+
```yaml
|
|
29
|
+
login:
|
|
30
|
+
type: 'page'
|
|
31
|
+
value: '/login'
|
|
32
|
+
|
|
33
|
+
email:
|
|
34
|
+
type: 'placeholder'
|
|
35
|
+
value: 'Enter your email'
|
|
36
|
+
|
|
37
|
+
password:
|
|
38
|
+
type: 'placeholder'
|
|
39
|
+
value: 'Enter your password'
|
|
40
|
+
|
|
41
|
+
submit:
|
|
42
|
+
type: 'role'
|
|
43
|
+
value: 'button'
|
|
44
|
+
name: 'Submit'
|
|
45
|
+
|
|
46
|
+
welcome:
|
|
47
|
+
type: 'role'
|
|
48
|
+
value: 'heading'
|
|
49
|
+
name: 'Welcome'
|
|
50
|
+
|
|
51
|
+
dashboard:
|
|
52
|
+
type: 'role'
|
|
53
|
+
value: 'link'
|
|
54
|
+
name: 'Dashboard'
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**qa/screens/login/test-data/login.yaml**
|
|
58
|
+
```yaml
|
|
59
|
+
email: 'admin@example.com'
|
|
60
|
+
password: 'password123'
|
|
61
|
+
welcome_text: 'Welcome back'
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Using Playwright MCP to explore pages
|
|
65
|
+
|
|
66
|
+
When exploring a page to generate test files:
|
|
67
|
+
1. Read `playwright.config.ts` for `baseURL`
|
|
68
|
+
2. Use `browser_navigate` to open `baseURL + /path`
|
|
69
|
+
3. Use `browser_snapshot` to see all elements
|
|
70
|
+
4. Generate the 3 files from the snapshot
|
|
71
|
+
|
|
72
|
+
**NEVER use `browser_run_code`.** It fails with `require is not defined`.
|
|
73
|
+
Only use: `browser_navigate`, `browser_snapshot`, `browser_click`, `browser_fill_form`, `browser_evaluate`.
|
|
74
|
+
|
|
75
|
+
To browse authenticated pages via MCP:
|
|
76
|
+
1. Check if `specs/.auth/<role>.json` exists
|
|
77
|
+
2. If not, read `baseURL` from `playwright.config.ts` and tell the user to manually run: `sungen makeauth <role> --url <baseURL>`. **Do NOT execute `sungen makeauth` yourself.** Wait for user confirmation.
|
|
78
|
+
3. Read `specs/.auth/<role>.json`
|
|
79
|
+
4. `browser_navigate` to the page
|
|
80
|
+
5. `browser_evaluate` to inject localStorage and cookies from the JSON
|
|
81
|
+
6. `browser_navigate` again to reload with auth
|
|
82
|
+
|
|
83
|
+
## Commands
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
sungen add --screen <name> --path <url-path> # Create screen
|
|
87
|
+
sungen generate --screen <name> # Compile to .spec.ts
|
|
88
|
+
sungen generate --all # Compile all
|
|
89
|
+
sungen makeauth <role> # Capture auth state
|
|
90
|
+
```
|