@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.
Files changed (140) hide show
  1. package/README.md +78 -51
  2. package/dist/cli/index.js +1 -1
  3. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/fill-action.hbs +1 -1
  4. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/fill-editor-action.hbs +1 -1
  5. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/checked-assertion.hbs +0 -1
  6. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/column-cell-assertion.hbs +0 -1
  7. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/contain-text-assertion.hbs +0 -1
  8. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/count-assertion.hbs +0 -1
  9. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-assertion.hbs +0 -1
  10. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-filter-assertion.hbs +0 -1
  11. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +0 -1
  12. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-variable-assertion.hbs +0 -1
  13. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/empty-assertion.hbs +0 -1
  14. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/enabled-assertion.hbs +0 -1
  15. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/focused-assertion.hbs +0 -1
  16. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/have-text-assertion.hbs +0 -1
  17. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-dialog-heading-assertion.hbs +0 -1
  18. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-filter-assertion.hbs +0 -1
  19. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +0 -1
  20. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-variable-assertion.hbs +0 -1
  21. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/is-hidden-assertion.hbs +0 -1
  22. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/label-value-assertion.hbs +0 -1
  23. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/list-item-count-assertion.hbs +0 -1
  24. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/loading-assertion.hbs +0 -1
  25. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/not-checked-assertion.hbs +0 -1
  26. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/page-assertion.hbs +0 -1
  27. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/selected-assertion.hbs +0 -1
  28. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/sorted-assertion.hbs +0 -1
  29. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-cell-by-filter.hbs +0 -1
  30. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-cell-by-index.hbs +0 -1
  31. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-column-exists.hbs +0 -1
  32. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-empty.hbs +0 -1
  33. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-count.hbs +0 -1
  34. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-exists.hbs +0 -1
  35. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-not-exists.hbs +0 -1
  36. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-assertion.hbs +0 -1
  37. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-dialog-heading-assertion.hbs +0 -1
  38. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-locator-variable-assertion.hbs +0 -1
  39. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +0 -1
  40. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-value-assertion.hbs +0 -1
  41. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-variable-assertion.hbs +0 -1
  42. package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/navigation.hbs +1 -1
  43. package/dist/generators/test-generator/patterns/navigation-patterns.js +2 -2
  44. package/dist/generators/test-generator/patterns/navigation-patterns.js.map +1 -1
  45. package/dist/generators/test-generator/utils/selector-resolver.d.ts +15 -8
  46. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
  47. package/dist/generators/test-generator/utils/selector-resolver.js +26 -197
  48. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
  49. package/dist/orchestrator/project-initializer.d.ts +4 -0
  50. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  51. package/dist/orchestrator/project-initializer.js +49 -4
  52. package/dist/orchestrator/project-initializer.js.map +1 -1
  53. package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +4 -3
  54. package/dist/orchestrator/templates/ai-instructions/claude-cmd-make-tc.md +11 -46
  55. package/dist/orchestrator/templates/ai-instructions/claude-cmd-make-test.md +11 -46
  56. package/dist/orchestrator/templates/ai-instructions/claude-config.md +9 -8
  57. package/dist/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md +11 -0
  58. package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +2 -2
  59. package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +206 -0
  60. package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +19 -21
  61. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +256 -0
  62. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +14 -17
  63. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-make-tc.md +16 -47
  64. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-make-test.md +16 -47
  65. package/dist/orchestrator/templates/ai-instructions/copilot-config.md +8 -7
  66. package/dist/orchestrator/templates/ai-instructions/{copilot-skill-error-mapping.md → github-skill-sungen-error-mapping.md} +14 -3
  67. package/{src/orchestrator/templates/ai-instructions/copilot-skill-gherkin-syntax.md → dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md} +5 -5
  68. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +206 -0
  69. package/dist/orchestrator/templates/ai-instructions/{copilot-skill-selector-keys.md → github-skill-sungen-selector-keys.md} +22 -24
  70. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +256 -0
  71. package/dist/orchestrator/templates/playwright.config.d.ts.map +1 -1
  72. package/dist/orchestrator/templates/playwright.config.js +3 -1
  73. package/dist/orchestrator/templates/playwright.config.js.map +1 -1
  74. package/dist/orchestrator/templates/playwright.config.ts +3 -1
  75. package/dist/orchestrator/templates/readme.md +78 -101
  76. package/package.json +1 -1
  77. package/src/cli/index.ts +1 -1
  78. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/fill-action.hbs +1 -1
  79. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/fill-editor-action.hbs +1 -1
  80. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/checked-assertion.hbs +0 -1
  81. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/column-cell-assertion.hbs +0 -1
  82. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/contain-text-assertion.hbs +0 -1
  83. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/count-assertion.hbs +0 -1
  84. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-assertion.hbs +0 -1
  85. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-filter-assertion.hbs +0 -1
  86. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +0 -1
  87. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-variable-assertion.hbs +0 -1
  88. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/empty-assertion.hbs +0 -1
  89. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/enabled-assertion.hbs +0 -1
  90. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/focused-assertion.hbs +0 -1
  91. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/have-text-assertion.hbs +0 -1
  92. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-dialog-heading-assertion.hbs +0 -1
  93. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-filter-assertion.hbs +0 -1
  94. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +0 -1
  95. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-variable-assertion.hbs +0 -1
  96. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/is-hidden-assertion.hbs +0 -1
  97. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/label-value-assertion.hbs +0 -1
  98. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/list-item-count-assertion.hbs +0 -1
  99. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/loading-assertion.hbs +0 -1
  100. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/not-checked-assertion.hbs +0 -1
  101. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/page-assertion.hbs +0 -1
  102. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/selected-assertion.hbs +0 -1
  103. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/sorted-assertion.hbs +0 -1
  104. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-cell-by-filter.hbs +0 -1
  105. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-cell-by-index.hbs +0 -1
  106. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-column-exists.hbs +0 -1
  107. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-empty.hbs +0 -1
  108. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-count.hbs +0 -1
  109. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-exists.hbs +0 -1
  110. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-not-exists.hbs +0 -1
  111. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-assertion.hbs +0 -1
  112. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-dialog-heading-assertion.hbs +0 -1
  113. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-locator-variable-assertion.hbs +0 -1
  114. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +0 -1
  115. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-value-assertion.hbs +0 -1
  116. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-variable-assertion.hbs +0 -1
  117. package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/navigation.hbs +1 -1
  118. package/src/generators/test-generator/patterns/navigation-patterns.ts +2 -2
  119. package/src/generators/test-generator/utils/selector-resolver.ts +27 -204
  120. package/src/orchestrator/project-initializer.ts +60 -5
  121. package/src/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +4 -3
  122. package/src/orchestrator/templates/ai-instructions/claude-cmd-make-tc.md +11 -46
  123. package/src/orchestrator/templates/ai-instructions/claude-cmd-make-test.md +11 -46
  124. package/src/orchestrator/templates/ai-instructions/claude-config.md +9 -8
  125. package/src/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md +11 -0
  126. package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +2 -2
  127. package/src/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +206 -0
  128. package/src/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +19 -21
  129. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +256 -0
  130. package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +14 -17
  131. package/src/orchestrator/templates/ai-instructions/copilot-cmd-make-tc.md +16 -47
  132. package/src/orchestrator/templates/ai-instructions/copilot-cmd-make-test.md +16 -47
  133. package/src/orchestrator/templates/ai-instructions/copilot-config.md +8 -7
  134. package/src/orchestrator/templates/ai-instructions/{copilot-skill-error-mapping.md → github-skill-sungen-error-mapping.md} +14 -3
  135. package/{dist/orchestrator/templates/ai-instructions/copilot-skill-gherkin-syntax.md → src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md} +5 -5
  136. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +206 -0
  137. package/src/orchestrator/templates/ai-instructions/{copilot-skill-selector-keys.md → github-skill-sungen-selector-keys.md} +22 -24
  138. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +256 -0
  139. package/src/orchestrator/templates/playwright.config.ts +3 -1
  140. package/src/orchestrator/templates/readme.md +78 -101
@@ -1,2 +1 @@
1
- await page.waitForLoadState('networkidle');
2
1
  await expect({{> locator}}.getByRole('listitem')).toHaveCount({{expectedCount}});
@@ -1,2 +1 @@
1
- await page.waitForLoadState('networkidle');
2
1
  await expect({{> locator}}).toHaveAttribute('aria-busy', 'true');
@@ -1,2 +1 @@
1
- await page.waitForLoadState('networkidle');
2
1
  await expect({{> locator}}).toBeChecked({ checked: false });
@@ -1,2 +1 @@
1
- await page.waitForLoadState('networkidle');
2
1
  await expect(page).toHaveURL(/{{pathRegex}}/);
@@ -1,2 +1 @@
1
- await page.waitForLoadState('networkidle');
2
1
  await expect({{> locator}}).toHaveAttribute('aria-selected', 'true');
@@ -1,2 +1 @@
1
- await page.waitForLoadState('networkidle');
2
1
  await expect({{> locator}}).toHaveAttribute('aria-sort', '{{sortDirection}}');
@@ -1,3 +1,2 @@
1
- await page.waitForLoadState('networkidle');
2
1
  { const tableRow = {{> locator}}.getByRole('row').filter({ hasText: '{{escapeQuotes filterValue}}' });
3
2
  await expect(tableRow.getByRole('cell').filter({ hasText: '{{escapeQuotes cellValue}}' })).toBeVisible(); }
@@ -1,3 +1,2 @@
1
- await page.waitForLoadState('networkidle');
2
1
  { const tableRow = {{> locator}}.getByRole('row').nth({{rowIndex}});
3
2
  await expect(tableRow.getByRole('cell').filter({ hasText: '{{escapeQuotes cellValue}}' })).toBeVisible(); }
@@ -1,2 +1 @@
1
- await page.waitForLoadState('networkidle');
2
1
  await expect({{> locator}}.getByRole('columnheader', { name: '{{escapeQuotes columnName}}' })).toBeVisible();
@@ -1,2 +1 @@
1
- await page.waitForLoadState('networkidle');
2
1
  await expect({{> locator}}.locator('tbody').getByRole('row')).toHaveCount(0);
@@ -1,2 +1 @@
1
- await page.waitForLoadState('networkidle');
2
1
  await expect({{> locator}}.locator('tbody').getByRole('row')).toHaveCount({{expectedCount}});
@@ -1,2 +1 @@
1
- await page.waitForLoadState('networkidle');
2
1
  await expect({{> locator}}.getByRole('row').filter({ hasText: '{{escapeQuotes filterValue}}' })).toBeVisible();
@@ -1,2 +1 @@
1
- await page.waitForLoadState('networkidle');
2
1
  await expect({{> locator}}.getByRole('row').filter({ hasText: '{{escapeQuotes filterValue}}' })).toHaveCount(0);
@@ -1,2 +1 @@
1
- await page.waitForLoadState('networkidle');
2
1
  await expect({{> locator}}).toBeVisible();
@@ -1,2 +1 @@
1
- await page.waitForLoadState('networkidle');
2
1
  await expect(page.getByRole('dialog').getByRole('heading', { name: '{{escapeQuotes dataValue}}' })).toBeVisible();
@@ -1,2 +1 @@
1
- await page.waitForLoadState('networkidle');
2
1
  await expect({{> locator-base}}.filter({ hasText: /^{{escapeRegex dataValue}}$/ }){{> locator-nth}}).toBeVisible();
@@ -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();
@@ -1,2 +1 @@
1
- await page.waitForLoadState('networkidle');
2
1
  await expect(page.getByText('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeVisible();
@@ -1,2 +1 @@
1
- await page.waitForLoadState('networkidle');
2
1
  await expect(page.getByText('{{escapeQuotes selectorValue}}').filter({ hasText: /.*{{escapeRegex dataValue}}$/ }){{#if (gte nth 0)}}.nth({{nth}}){{/if}}).toBeVisible();
@@ -1 +1 @@
1
- await page.goto('{{#if baseURL}}{{baseURL}}{{/if}}{{path}}', { waitUntil: 'networkidle' });
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
- * "Email Address" "email.address"
293
- * "Submit Button" → "submit.button"
294
- * "User's Profile" → "users.profile"
295
- * "Thời gian" → "thoi.gian"
296
- * "Địa điểm" → "dia.diem"
297
- * "ログイン" → "roguin"
298
- * "パスワード" → "pasuwaado"
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 convertJapaneseToRomaji(removeVietnameseTones(label)) // Convert Japanese and Vietnamese first
302
- .toLowerCase()
303
- .replace(/['\u2019]s/g, 's') // Apostrophe s to just s ("User's" → "users")
304
- .replace(/['\u2019]/g, '') // Remove remaining apostrophes
305
- .replace(/[^a-z0-9\s]/g, '') // Remove special chars except spaces
306
- .trim()
307
- .replace(/\s+/g, '.'); // Spaces to dots
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
- // 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'],
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 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.**
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 `sungen generate --screen <screen>` to compile to Playwright `.spec.ts`
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 — gathers viewpoints, explores live page or static designs, generates feature/selectors/test-data files'
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-selector-keys` skills.
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
- ### 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
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
- Show what was generated. Next: `sungen generate --screen <screen>`
25
+ **No selectors.yaml** selectors are generated during `/sungen:make-test`.