@sun-asterisk/sungen 2.1.1 → 2.2.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 +78 -51
- package/dist/cli/index.js +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/fill-action.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/fill-editor-action.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/checked-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/column-cell-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/contain-text-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/count-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-filter-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-variable-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/empty-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/enabled-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/focused-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/have-text-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-dialog-heading-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-filter-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-variable-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/is-hidden-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/label-value-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/list-item-count-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/loading-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/not-checked-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/page-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/selected-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/sorted-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-cell-by-filter.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-cell-by-index.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-column-exists.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-empty.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-count.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-exists.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-not-exists.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-dialog-heading-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-locator-variable-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-value-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-variable-assertion.hbs +0 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/navigation.hbs +1 -1
- package/dist/generators/test-generator/patterns/navigation-patterns.js +2 -2
- package/dist/generators/test-generator/patterns/navigation-patterns.js.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.d.ts +15 -8
- package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.js +26 -197
- package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
- package/dist/orchestrator/project-initializer.d.ts +4 -0
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +49 -4
- package/dist/orchestrator/project-initializer.js.map +1 -1
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +4 -3
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-make-tc.md +11 -46
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-make-test.md +11 -46
- package/dist/orchestrator/templates/ai-instructions/claude-config.md +9 -8
- package/dist/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md +11 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +2 -2
- package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +206 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +19 -21
- package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +256 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +14 -17
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-make-tc.md +16 -47
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-make-test.md +16 -47
- package/dist/orchestrator/templates/ai-instructions/copilot-config.md +8 -7
- package/dist/orchestrator/templates/ai-instructions/{copilot-skill-error-mapping.md → github-skill-sungen-error-mapping.md} +14 -3
- package/{src/orchestrator/templates/ai-instructions/copilot-skill-gherkin-syntax.md → dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md} +5 -5
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +206 -0
- package/dist/orchestrator/templates/ai-instructions/{copilot-skill-selector-keys.md → github-skill-sungen-selector-keys.md} +22 -24
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +256 -0
- package/dist/orchestrator/templates/playwright.config.d.ts.map +1 -1
- package/dist/orchestrator/templates/playwright.config.js +3 -1
- package/dist/orchestrator/templates/playwright.config.js.map +1 -1
- package/dist/orchestrator/templates/playwright.config.ts +3 -1
- package/dist/orchestrator/templates/readme.md +78 -101
- package/package.json +1 -1
- package/src/cli/index.ts +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/fill-action.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/fill-editor-action.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/checked-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/column-cell-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/contain-text-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/count-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-filter-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-variable-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/empty-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/enabled-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/focused-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/have-text-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-dialog-heading-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-filter-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-variable-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/is-hidden-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/label-value-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/list-item-count-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/loading-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/not-checked-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/page-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/selected-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/sorted-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-cell-by-filter.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-cell-by-index.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-column-exists.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-empty.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-count.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-exists.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-not-exists.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-dialog-heading-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-locator-variable-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-value-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-variable-assertion.hbs +0 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/navigation.hbs +1 -1
- package/src/generators/test-generator/patterns/navigation-patterns.ts +2 -2
- package/src/generators/test-generator/utils/selector-resolver.ts +27 -204
- package/src/orchestrator/project-initializer.ts +60 -5
- package/src/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +4 -3
- package/src/orchestrator/templates/ai-instructions/claude-cmd-make-tc.md +11 -46
- package/src/orchestrator/templates/ai-instructions/claude-cmd-make-test.md +11 -46
- package/src/orchestrator/templates/ai-instructions/claude-config.md +9 -8
- package/src/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md +11 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +2 -2
- package/src/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +206 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +19 -21
- package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +256 -0
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +14 -17
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-make-tc.md +16 -47
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-make-test.md +16 -47
- package/src/orchestrator/templates/ai-instructions/copilot-config.md +8 -7
- package/src/orchestrator/templates/ai-instructions/{copilot-skill-error-mapping.md → github-skill-sungen-error-mapping.md} +14 -3
- package/{dist/orchestrator/templates/ai-instructions/copilot-skill-gherkin-syntax.md → src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md} +5 -5
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +206 -0
- package/src/orchestrator/templates/ai-instructions/{copilot-skill-selector-keys.md → github-skill-sungen-selector-keys.md} +22 -24
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +256 -0
- package/src/orchestrator/templates/playwright.config.ts +3 -1
- package/src/orchestrator/templates/readme.md +78 -101
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
await page.waitForLoadState('networkidle');
|
|
2
1
|
{{#if name}}
|
|
3
2
|
{{#if dataValue}}
|
|
4
3
|
await expect(page.getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }).filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeVisible();
|
package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/navigation.hbs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
await page.goto('{{#if baseURL}}{{baseURL}}{{/if}}{{path}}', { waitUntil: '
|
|
1
|
+
await page.goto('{{#if baseURL}}{{baseURL}}{{/if}}{{path}}', { waitUntil: 'load' });
|
|
@@ -9,7 +9,7 @@ export const navigationPatterns: StepPattern[] = [
|
|
|
9
9
|
{
|
|
10
10
|
name: 'open-page-type',
|
|
11
11
|
matcher: (step: ParsedStep) =>
|
|
12
|
-
(step.text.includes('open') || step.text.includes('opens') || step.text.includes('is on')) &&
|
|
12
|
+
(step.text.includes('open') || step.text.includes('opens') || step.text.includes('is on') || step.text.includes('navigate to')) &&
|
|
13
13
|
step.elementType === 'page',
|
|
14
14
|
resolver: (step, context) => {
|
|
15
15
|
let path = context.featurePath || '/';
|
|
@@ -64,7 +64,7 @@ export const navigationPatterns: StepPattern[] = [
|
|
|
64
64
|
{
|
|
65
65
|
name: 'navigate-to-route',
|
|
66
66
|
matcher: (step: ParsedStep) =>
|
|
67
|
-
(step.text.includes('navigates to') || step.text.includes('is on')) &&
|
|
67
|
+
(step.text.includes('navigate to') || step.text.includes('navigates to') || step.text.includes('is on')) &&
|
|
68
68
|
!!(step.selectorRef || step.dataRef),
|
|
69
69
|
resolver: (step, context) => {
|
|
70
70
|
const route = step.selectorRef || step.dataRef;
|
|
@@ -3,191 +3,6 @@ import * as path from 'path';
|
|
|
3
3
|
import yaml from 'yaml';
|
|
4
4
|
import { readYaml, readYamlIfExists } from '../../../utils/yaml-io';
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
* Remove Vietnamese diacritics (accents) from text
|
|
8
|
-
* Example: "Thời gian" → "Thoi gian", "Địa điểm" → "Dia diem"
|
|
9
|
-
*/
|
|
10
|
-
function removeVietnameseTones(text: string): string {
|
|
11
|
-
const vietnameseTones: { [key: string]: string } = {
|
|
12
|
-
'à': 'a', 'á': 'a', 'ả': 'a', 'ã': 'a', 'ạ': 'a',
|
|
13
|
-
'ă': 'a', 'ằ': 'a', 'ắ': 'a', 'ẳ': 'a', 'ẵ': 'a', 'ặ': 'a',
|
|
14
|
-
'â': 'a', 'ầ': 'a', 'ấ': 'a', 'ẩ': 'a', 'ẫ': 'a', 'ậ': 'a',
|
|
15
|
-
'đ': 'd',
|
|
16
|
-
'è': 'e', 'é': 'e', 'ẻ': 'e', 'ẽ': 'e', 'ẹ': 'e',
|
|
17
|
-
'ê': 'e', 'ề': 'e', 'ế': 'e', 'ể': 'e', 'ễ': 'e', 'ệ': 'e',
|
|
18
|
-
'ì': 'i', 'í': 'i', 'ỉ': 'i', 'ĩ': 'i', 'ị': 'i',
|
|
19
|
-
'ò': 'o', 'ó': 'o', 'ỏ': 'o', 'õ': 'o', 'ọ': 'o',
|
|
20
|
-
'ô': 'o', 'ồ': 'o', 'ố': 'o', 'ổ': 'o', 'ỗ': 'o', 'ộ': 'o',
|
|
21
|
-
'ơ': 'o', 'ờ': 'o', 'ớ': 'o', 'ở': 'o', 'ỡ': 'o', 'ợ': 'o',
|
|
22
|
-
'ù': 'u', 'ú': 'u', 'ủ': 'u', 'ũ': 'u', 'ụ': 'u',
|
|
23
|
-
'ư': 'u', 'ừ': 'u', 'ứ': 'u', 'ử': 'u', 'ữ': 'u', 'ự': 'u',
|
|
24
|
-
'ỳ': 'y', 'ý': 'y', 'ỷ': 'y', 'ỹ': 'y', 'ỵ': 'y',
|
|
25
|
-
// Uppercase
|
|
26
|
-
'À': 'A', 'Á': 'A', 'Ả': 'A', 'Ã': 'A', 'Ạ': 'A',
|
|
27
|
-
'Ă': 'A', 'Ằ': 'A', 'Ắ': 'A', 'Ẳ': 'A', 'Ẵ': 'A', 'Ặ': 'A',
|
|
28
|
-
'Â': 'A', 'Ầ': 'A', 'Ấ': 'A', 'Ẩ': 'A', 'Ẫ': 'A', 'Ậ': 'A',
|
|
29
|
-
'Đ': 'D',
|
|
30
|
-
'È': 'E', 'É': 'E', 'Ẻ': 'E', 'Ẽ': 'E', 'Ẹ': 'E',
|
|
31
|
-
'Ê': 'E', 'Ề': 'E', 'Ế': 'E', 'Ể': 'E', 'Ễ': 'E', 'Ệ': 'E',
|
|
32
|
-
'Ì': 'I', 'Í': 'I', 'Ỉ': 'I', 'Ĩ': 'I', 'Ị': 'I',
|
|
33
|
-
'Ò': 'O', 'Ó': 'O', 'Ỏ': 'O', 'Õ': 'O', 'Ọ': 'O',
|
|
34
|
-
'Ô': 'O', 'Ồ': 'O', 'Ố': 'O', 'Ổ': 'O', 'Ỗ': 'O', 'Ộ': 'O',
|
|
35
|
-
'Ơ': 'O', 'Ờ': 'O', 'Ớ': 'O', 'Ở': 'O', 'Ỡ': 'O', 'Ợ': 'O',
|
|
36
|
-
'Ù': 'U', 'Ú': 'U', 'Ủ': 'U', 'Ũ': 'U', 'Ụ': 'U',
|
|
37
|
-
'Ư': 'U', 'Ừ': 'U', 'Ứ': 'U', 'Ử': 'U', 'Ữ': 'U', 'Ự': 'U',
|
|
38
|
-
'Ỳ': 'Y', 'Ý': 'Y', 'Ỷ': 'Y', 'Ỹ': 'Y', 'Ỵ': 'Y',
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
return text.split('').map(char => vietnameseTones[char] || char).join('');
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Convert Japanese Hiragana and Katakana to Romaji
|
|
46
|
-
* Example: "ひらがな" → "hiragana", "カタカナ" → "katakana"
|
|
47
|
-
*/
|
|
48
|
-
function convertJapaneseToRomaji(text: string): string {
|
|
49
|
-
// Hiragana to Romaji mapping
|
|
50
|
-
const hiraganaMap: { [key: string]: string } = {
|
|
51
|
-
// Basic vowels
|
|
52
|
-
'あ': 'a', 'い': 'i', 'う': 'u', 'え': 'e', 'お': 'o',
|
|
53
|
-
// K-row
|
|
54
|
-
'か': 'ka', 'き': 'ki', 'く': 'ku', 'け': 'ke', 'こ': 'ko',
|
|
55
|
-
'きゃ': 'kya', 'きゅ': 'kyu', 'きょ': 'kyo',
|
|
56
|
-
// G-row
|
|
57
|
-
'が': 'ga', 'ぎ': 'gi', 'ぐ': 'gu', 'げ': 'ge', 'ご': 'go',
|
|
58
|
-
'ぎゃ': 'gya', 'ぎゅ': 'gyu', 'ぎょ': 'gyo',
|
|
59
|
-
// S-row
|
|
60
|
-
'さ': 'sa', 'し': 'shi', 'す': 'su', 'せ': 'se', 'そ': 'so',
|
|
61
|
-
'しゃ': 'sha', 'しゅ': 'shu', 'しょ': 'sho',
|
|
62
|
-
// Z-row
|
|
63
|
-
'ざ': 'za', 'じ': 'ji', 'ず': 'zu', 'ぜ': 'ze', 'ぞ': 'zo',
|
|
64
|
-
'じゃ': 'ja', 'じゅ': 'ju', 'じょ': 'jo',
|
|
65
|
-
// T-row
|
|
66
|
-
'た': 'ta', 'ち': 'chi', 'つ': 'tsu', 'て': 'te', 'と': 'to',
|
|
67
|
-
'ちゃ': 'cha', 'ちゅ': 'chu', 'ちょ': 'cho',
|
|
68
|
-
// D-row
|
|
69
|
-
'だ': 'da', 'ぢ': 'ji', 'づ': 'zu', 'で': 'de', 'ど': 'do',
|
|
70
|
-
// N-row
|
|
71
|
-
'な': 'na', 'に': 'ni', 'ぬ': 'nu', 'ね': 'ne', 'の': 'no',
|
|
72
|
-
'にゃ': 'nya', 'にゅ': 'nyu', 'にょ': 'nyo',
|
|
73
|
-
// H-row
|
|
74
|
-
'は': 'ha', 'ひ': 'hi', 'ふ': 'fu', 'へ': 'he', 'ほ': 'ho',
|
|
75
|
-
'ひゃ': 'hya', 'ひゅ': 'hyu', 'ひょ': 'hyo',
|
|
76
|
-
// B-row
|
|
77
|
-
'ば': 'ba', 'び': 'bi', 'ぶ': 'bu', 'べ': 'be', 'ぼ': 'bo',
|
|
78
|
-
'びゃ': 'bya', 'びゅ': 'byu', 'びょ': 'byo',
|
|
79
|
-
// P-row
|
|
80
|
-
'ぱ': 'pa', 'ぴ': 'pi', 'ぷ': 'pu', 'ぺ': 'pe', 'ぽ': 'po',
|
|
81
|
-
'ぴゃ': 'pya', 'ぴゅ': 'pyu', 'ぴょ': 'pyo',
|
|
82
|
-
// M-row
|
|
83
|
-
'ま': 'ma', 'み': 'mi', 'む': 'mu', 'め': 'me', 'も': 'mo',
|
|
84
|
-
'みゃ': 'mya', 'みゅ': 'myu', 'みょ': 'myo',
|
|
85
|
-
// Y-row
|
|
86
|
-
'や': 'ya', 'ゆ': 'yu', 'よ': 'yo',
|
|
87
|
-
// R-row
|
|
88
|
-
'ら': 'ra', 'り': 'ri', 'る': 'ru', 'れ': 're', 'ろ': 'ro',
|
|
89
|
-
'りゃ': 'rya', 'りゅ': 'ryu', 'りょ': 'ryo',
|
|
90
|
-
// W-row
|
|
91
|
-
'わ': 'wa', 'ゐ': 'wi', 'ゑ': 'we', 'を': 'wo', 'ん': 'n',
|
|
92
|
-
// Small tsu (doubles next consonant)
|
|
93
|
-
'っ': '',
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
// Katakana to Romaji mapping
|
|
97
|
-
const katakanaMap: { [key: string]: string } = {
|
|
98
|
-
// Basic vowels
|
|
99
|
-
'ア': 'a', 'イ': 'i', 'ウ': 'u', 'エ': 'e', 'オ': 'o',
|
|
100
|
-
// K-row
|
|
101
|
-
'カ': 'ka', 'キ': 'ki', 'ク': 'ku', 'ケ': 'ke', 'コ': 'ko',
|
|
102
|
-
'キャ': 'kya', 'キュ': 'kyu', 'キョ': 'kyo',
|
|
103
|
-
// G-row
|
|
104
|
-
'ガ': 'ga', 'ギ': 'gi', 'グ': 'gu', 'ゲ': 'ge', 'ゴ': 'go',
|
|
105
|
-
'ギャ': 'gya', 'ギュ': 'gyu', 'ギョ': 'gyo',
|
|
106
|
-
// S-row
|
|
107
|
-
'サ': 'sa', 'シ': 'shi', 'ス': 'su', 'セ': 'se', 'ソ': 'so',
|
|
108
|
-
'シャ': 'sha', 'シュ': 'shu', 'ショ': 'sho',
|
|
109
|
-
// Z-row
|
|
110
|
-
'ザ': 'za', 'ジ': 'ji', 'ズ': 'zu', 'ゼ': 'ze', 'ゾ': 'zo',
|
|
111
|
-
'ジャ': 'ja', 'ジュ': 'ju', 'ジョ': 'jo',
|
|
112
|
-
// T-row
|
|
113
|
-
'タ': 'ta', 'チ': 'chi', 'ツ': 'tsu', 'テ': 'te', 'ト': 'to',
|
|
114
|
-
'チャ': 'cha', 'チュ': 'chu', 'チョ': 'cho',
|
|
115
|
-
// D-row
|
|
116
|
-
'ダ': 'da', 'ヂ': 'ji', 'ヅ': 'zu', 'デ': 'de', 'ド': 'do',
|
|
117
|
-
// N-row
|
|
118
|
-
'ナ': 'na', 'ニ': 'ni', 'ヌ': 'nu', 'ネ': 'ne', 'ノ': 'no',
|
|
119
|
-
'ニャ': 'nya', 'ニュ': 'nyu', 'ニョ': 'nyo',
|
|
120
|
-
// H-row
|
|
121
|
-
'ハ': 'ha', 'ヒ': 'hi', 'フ': 'fu', 'ヘ': 'he', 'ホ': 'ho',
|
|
122
|
-
'ヒャ': 'hya', 'ヒュ': 'hyu', 'ヒョ': 'hyo',
|
|
123
|
-
// B-row
|
|
124
|
-
'バ': 'ba', 'ビ': 'bi', 'ブ': 'bu', 'ベ': 'be', 'ボ': 'bo',
|
|
125
|
-
'ビャ': 'bya', 'ビュ': 'byu', 'ビョ': 'byo',
|
|
126
|
-
// P-row
|
|
127
|
-
'パ': 'pa', 'ピ': 'pi', 'プ': 'pu', 'ペ': 'pe', 'ポ': 'po',
|
|
128
|
-
'ピャ': 'pya', 'ピュ': 'pyu', 'ピョ': 'pyo',
|
|
129
|
-
// M-row
|
|
130
|
-
'マ': 'ma', 'ミ': 'mi', 'ム': 'mu', 'メ': 'me', 'モ': 'mo',
|
|
131
|
-
'ミャ': 'mya', 'ミュ': 'myu', 'ミョ': 'myo',
|
|
132
|
-
// Y-row
|
|
133
|
-
'ヤ': 'ya', 'ユ': 'yu', 'ヨ': 'yo',
|
|
134
|
-
// R-row
|
|
135
|
-
'ラ': 'ra', 'リ': 'ri', 'ル': 'ru', 'レ': 're', 'ロ': 'ro',
|
|
136
|
-
'リャ': 'rya', 'リュ': 'ryu', 'リョ': 'ryo',
|
|
137
|
-
// W-row
|
|
138
|
-
'ワ': 'wa', 'ヰ': 'wi', 'ヱ': 'we', 'ヲ': 'wo', 'ン': 'n',
|
|
139
|
-
// Small tsu (doubles next consonant)
|
|
140
|
-
'ッ': '',
|
|
141
|
-
// Extended katakana for foreign words
|
|
142
|
-
'ヴ': 'vu',
|
|
143
|
-
'ファ': 'fa', 'フィ': 'fi', 'フェ': 'fe', 'フォ': 'fo',
|
|
144
|
-
'ウィ': 'wi', 'ウェ': 'we', 'ウォ': 'wo',
|
|
145
|
-
'ティ': 'ti', 'ディ': 'di',
|
|
146
|
-
'トゥ': 'tu', 'ドゥ': 'du',
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
const allMaps = { ...hiraganaMap, ...katakanaMap };
|
|
150
|
-
|
|
151
|
-
let result = '';
|
|
152
|
-
let i = 0;
|
|
153
|
-
|
|
154
|
-
while (i < text.length) {
|
|
155
|
-
// Try to match 2-character combinations first (like きゃ, シャ)
|
|
156
|
-
if (i < text.length - 1) {
|
|
157
|
-
const twoChar = text.substring(i, i + 2);
|
|
158
|
-
if (allMaps[twoChar]) {
|
|
159
|
-
result += allMaps[twoChar];
|
|
160
|
-
i += 2;
|
|
161
|
-
continue;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Then try single character
|
|
166
|
-
const oneChar = text[i];
|
|
167
|
-
if (allMaps[oneChar]) {
|
|
168
|
-
// Handle small tsu (っ/ッ) - double next consonant
|
|
169
|
-
if (oneChar === 'っ' || oneChar === 'ッ') {
|
|
170
|
-
if (i < text.length - 1) {
|
|
171
|
-
const nextChar = text[i + 1];
|
|
172
|
-
const nextRomaji = allMaps[nextChar];
|
|
173
|
-
if (nextRomaji && nextRomaji.length > 0) {
|
|
174
|
-
result += nextRomaji[0]; // Add first letter of next romaji
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
} else {
|
|
178
|
-
result += allMaps[oneChar];
|
|
179
|
-
}
|
|
180
|
-
i++;
|
|
181
|
-
} else {
|
|
182
|
-
// Not Japanese character, keep as is
|
|
183
|
-
result += oneChar;
|
|
184
|
-
i++;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
return result;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
6
|
import { SelectorType } from '../../../utils/selector-types';
|
|
192
7
|
|
|
193
8
|
// Structured selector format v2
|
|
@@ -288,23 +103,31 @@ export class SelectorResolver {
|
|
|
288
103
|
}
|
|
289
104
|
|
|
290
105
|
/**
|
|
291
|
-
* Generate selector key from natural language label
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
* "
|
|
295
|
-
* "
|
|
296
|
-
* "
|
|
297
|
-
* "
|
|
298
|
-
* "
|
|
106
|
+
* Generate selector key from natural language label using Unicode NFC normalization.
|
|
107
|
+
* Preserves all Unicode characters (Vietnamese, Japanese, etc.) as-is.
|
|
108
|
+
*
|
|
109
|
+
* "Email Address" → "email address"
|
|
110
|
+
* "Submit Button" → "submit button"
|
|
111
|
+
* "User's Profile" → "user's profile"
|
|
112
|
+
* "Giới thiệu" → "giới thiệu"
|
|
113
|
+
* "Thời gian" → "thời gian"
|
|
114
|
+
* "ログイン" → "ログイン"
|
|
115
|
+
* "書類一覧" → "書類一覧"
|
|
299
116
|
*/
|
|
300
117
|
static generateKey(label: string): string {
|
|
301
|
-
return
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
118
|
+
return label.normalize('NFC').toLowerCase().trim().replace(/\s+/g, ' ');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Normalize all keys in a SelectorFile through generateKey()
|
|
123
|
+
* so that YAML keys are case-insensitive and Unicode-normalized on lookup.
|
|
124
|
+
*/
|
|
125
|
+
private static normalizeKeys(file: SelectorFile): SelectorFile {
|
|
126
|
+
const normalized: SelectorFile = {};
|
|
127
|
+
for (const [key, value] of Object.entries(file)) {
|
|
128
|
+
normalized[SelectorResolver.generateKey(key)] = value;
|
|
129
|
+
}
|
|
130
|
+
return normalized;
|
|
308
131
|
}
|
|
309
132
|
|
|
310
133
|
/**
|
|
@@ -742,7 +565,7 @@ export class SelectorResolver {
|
|
|
742
565
|
if (basePath) {
|
|
743
566
|
const baseSelectors = readYamlIfExists<SelectorFile>(basePath);
|
|
744
567
|
if (baseSelectors) {
|
|
745
|
-
Object.assign(selectors, baseSelectors);
|
|
568
|
+
Object.assign(selectors, SelectorResolver.normalizeKeys(baseSelectors));
|
|
746
569
|
}
|
|
747
570
|
}
|
|
748
571
|
|
|
@@ -751,7 +574,7 @@ export class SelectorResolver {
|
|
|
751
574
|
if (baseOverridePath) {
|
|
752
575
|
const overrideSelectors = readYamlIfExists<SelectorFile>(baseOverridePath);
|
|
753
576
|
if (overrideSelectors) {
|
|
754
|
-
Object.assign(selectors, overrideSelectors);
|
|
577
|
+
Object.assign(selectors, SelectorResolver.normalizeKeys(overrideSelectors));
|
|
755
578
|
}
|
|
756
579
|
}
|
|
757
580
|
}
|
|
@@ -769,7 +592,7 @@ export class SelectorResolver {
|
|
|
769
592
|
} else {
|
|
770
593
|
const featureSelectors = readYamlIfExists<SelectorFile>(featurePath);
|
|
771
594
|
if (featureSelectors) {
|
|
772
|
-
Object.assign(selectors, featureSelectors);
|
|
595
|
+
Object.assign(selectors, SelectorResolver.normalizeKeys(featureSelectors));
|
|
773
596
|
}
|
|
774
597
|
}
|
|
775
598
|
|
|
@@ -778,7 +601,7 @@ export class SelectorResolver {
|
|
|
778
601
|
if (featureOverridePath) {
|
|
779
602
|
const overrideSelectors = readYamlIfExists<SelectorFile>(featureOverridePath);
|
|
780
603
|
if (overrideSelectors) {
|
|
781
|
-
Object.assign(selectors, overrideSelectors);
|
|
604
|
+
Object.assign(selectors, SelectorResolver.normalizeKeys(overrideSelectors));
|
|
782
605
|
}
|
|
783
606
|
}
|
|
784
607
|
|
|
@@ -53,6 +53,9 @@ export class ProjectInitializer {
|
|
|
53
53
|
// Create AI rules files
|
|
54
54
|
this.createAIRules();
|
|
55
55
|
|
|
56
|
+
// Create VS Code settings for Copilot auto-attach
|
|
57
|
+
this.createVSCodeSettings();
|
|
58
|
+
|
|
56
59
|
// Display summary
|
|
57
60
|
this.displaySummary(normalizedProjectName);
|
|
58
61
|
}
|
|
@@ -159,6 +162,54 @@ export class ProjectInitializer {
|
|
|
159
162
|
this.createdItems.push('README.md');
|
|
160
163
|
}
|
|
161
164
|
|
|
165
|
+
/**
|
|
166
|
+
* Create VS Code settings for Copilot auto-attach
|
|
167
|
+
*/
|
|
168
|
+
private createVSCodeSettings(): void {
|
|
169
|
+
const settingsPath = path.join(this.cwd, '.vscode', 'settings.json');
|
|
170
|
+
|
|
171
|
+
if (fs.existsSync(settingsPath)) {
|
|
172
|
+
this.skippedItems.push('.vscode/settings.json');
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const settingsDir = path.dirname(settingsPath);
|
|
177
|
+
if (!fs.existsSync(settingsDir)) {
|
|
178
|
+
fs.mkdirSync(settingsDir, { recursive: true });
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const settings = {
|
|
182
|
+
'github.copilot.chat.agent.runTasks': true,
|
|
183
|
+
'chat.tools.terminal.autoApprove': {
|
|
184
|
+
sungen: true,
|
|
185
|
+
'npx playwright': true,
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
|
|
190
|
+
this.createdItems.push('.vscode/settings.json');
|
|
191
|
+
|
|
192
|
+
// Create MCP config for Playwright browser tools
|
|
193
|
+
const mcpPath = path.join(this.cwd, '.vscode', 'mcp.json');
|
|
194
|
+
|
|
195
|
+
if (fs.existsSync(mcpPath)) {
|
|
196
|
+
this.skippedItems.push('.vscode/mcp.json');
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const mcpConfig = {
|
|
201
|
+
servers: {
|
|
202
|
+
playwright: {
|
|
203
|
+
command: 'npx',
|
|
204
|
+
args: ['@playwright/mcp@latest'],
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
fs.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2) + '\n', 'utf-8');
|
|
210
|
+
this.createdItems.push('.vscode/mcp.json');
|
|
211
|
+
}
|
|
212
|
+
|
|
162
213
|
/**
|
|
163
214
|
* Display initialization summary
|
|
164
215
|
*/
|
|
@@ -216,11 +267,15 @@ export class ProjectInitializer {
|
|
|
216
267
|
['claude-skill-gherkin-syntax.md', '.claude/skills/sungen-gherkin-syntax/SKILL.md'],
|
|
217
268
|
['claude-skill-selector-keys.md', '.claude/skills/sungen-selector-keys/SKILL.md'],
|
|
218
269
|
['claude-skill-error-mapping.md', '.claude/skills/sungen-error-mapping/SKILL.md'],
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
['
|
|
270
|
+
['claude-skill-tc-generation.md', '.claude/skills/sungen-tc-generation/SKILL.md'],
|
|
271
|
+
['claude-skill-selector-fix.md', '.claude/skills/sungen-selector-fix/SKILL.md'],
|
|
272
|
+
|
|
273
|
+
// Skills — GitHub Copilot (separate copies with Copilot-friendly descriptions)
|
|
274
|
+
['github-skill-sungen-gherkin-syntax.md', '.github/skills/sungen-gherkin-syntax/SKILL.md'],
|
|
275
|
+
['github-skill-sungen-selector-keys.md', '.github/skills/sungen-selector-keys/SKILL.md'],
|
|
276
|
+
['github-skill-sungen-error-mapping.md', '.github/skills/sungen-error-mapping/SKILL.md'],
|
|
277
|
+
['github-skill-sungen-tc-generation.md', '.github/skills/sungen-tc-generation/SKILL.md'],
|
|
278
|
+
['github-skill-sungen-selector-fix.md', '.github/skills/sungen-selector-fix/SKILL.md'],
|
|
224
279
|
];
|
|
225
280
|
|
|
226
281
|
for (const [templateFile, outputRelPath] of fileMapping) {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: add-screen
|
|
3
3
|
description: 'Add a new Sungen screen — scaffolds directories and delegates to make-tc for test case creation'
|
|
4
4
|
argument-hint: [screen-name] [url-path]
|
|
5
|
-
allowed-tools: Read, Grep, Bash, Glob
|
|
5
|
+
allowed-tools: Read, Grep, Bash, Glob, AskUserQuestion
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
You are adding a new Sungen screen for test generation.
|
|
@@ -37,6 +37,7 @@ If yes, delegate to `/sungen:make-tc <screen>` to handle:
|
|
|
37
37
|
### 3. Confirm
|
|
38
38
|
|
|
39
39
|
Tell the user what was created and next steps:
|
|
40
|
-
- If the page requires authentication
|
|
40
|
+
- If the page requires authentication, the user will be asked to log in via the MCP browser during `/sungen:make-tc`
|
|
41
41
|
- Edit the generated files as needed
|
|
42
|
-
- Run
|
|
42
|
+
- Run `/sungen:make-tc <screen>` to create test cases
|
|
43
|
+
- Run `/sungen:make-test <screen>` to generate selectors, compile, and run tests
|
|
@@ -1,60 +1,25 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: make-tc
|
|
3
|
-
description: 'Create test cases for a Sungen screen —
|
|
3
|
+
description: 'Create or update test cases for a Sungen screen — generates feature + test-data files (20+ scenarios per viewpoint)'
|
|
4
4
|
argument-hint: [screen-name]
|
|
5
|
-
allowed-tools: Read, Grep, Bash, Glob
|
|
5
|
+
allowed-tools: Read, Grep, Bash, Glob, AskUserQuestion
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
## Role
|
|
9
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-
|
|
10
|
+
You are a **Senior QA Engineer** specialized in exhaustive test case design. You structure test cases by viewpoint categories and translate UI into comprehensive Gherkin test cases following the `sungen-gherkin-syntax` and `sungen-tc-generation` skills. Your focus is on **Gherkin scenarios and test data only** — selectors are handled later during `/sungen:make-test`.
|
|
11
11
|
|
|
12
12
|
## Parameters
|
|
13
13
|
|
|
14
|
-
Parse from `$ARGUMENTS
|
|
15
|
-
- **screen** — screen name (e.g., `login`, `dashboard`)
|
|
16
|
-
|
|
17
|
-
If missing, ask the user.
|
|
14
|
+
Parse **screen** from `$ARGUMENTS`. If missing, ask the user.
|
|
18
15
|
|
|
19
16
|
## Steps
|
|
20
17
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
|
18
|
+
1. Verify `qa/screens/<screen>/` exists. If not → `/sungen:add-screen` first.
|
|
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. Use `AskUserQuestion` to ask: **Live page** (explore via Playwright MCP) or **Static designs** (screenshots, Figma)? Explore accordingly (see CLAUDE.md for MCP rules).
|
|
21
|
+
4. Follow the `sungen-tc-generation` skill for section identification, viewpoint generation, and output format.
|
|
22
|
+
5. Generate or update `.feature` + `test-data.yaml` following `sungen-gherkin-syntax` and `sungen-tc-generation` skills.
|
|
23
|
+
6. Show summary → next: `/sungen:make-test <screen>`
|
|
59
24
|
|
|
60
|
-
|
|
25
|
+
**No selectors.yaml** — selectors are generated during `/sungen:make-test`.
|