@sun-asterisk/sungen 2.2.3 → 2.3.1
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 +6 -6
- package/dist/cli/commands/update.d.ts +3 -0
- package/dist/cli/commands/update.d.ts.map +1 -0
- package/dist/cli/commands/update.js +21 -0
- package/dist/cli/commands/update.js.map +1 -0
- package/dist/cli/index.js +3 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/generators/gherkin-parser/index.d.ts +2 -0
- package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
- package/dist/generators/gherkin-parser/index.js +16 -2
- package/dist/generators/gherkin-parser/index.js.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/attribute-assertion.hbs +3 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +12 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +12 -1
- package/dist/generators/test-generator/patterns/assertion-patterns.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/assertion-patterns.js +12 -0
- package/dist/generators/test-generator/patterns/assertion-patterns.js.map +1 -1
- package/dist/generators/test-generator/patterns/index.d.ts +9 -0
- package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/index.js +32 -0
- package/dist/generators/test-generator/patterns/index.js.map +1 -1
- package/dist/generators/test-generator/patterns/interaction-patterns.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/interaction-patterns.js +0 -13
- package/dist/generators/test-generator/patterns/interaction-patterns.js.map +1 -1
- package/dist/generators/test-generator/patterns/table-patterns.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/table-patterns.js +8 -5
- package/dist/generators/test-generator/patterns/table-patterns.js.map +1 -1
- package/dist/orchestrator/ai-rules-updater.d.ts +13 -0
- package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -0
- package/dist/orchestrator/ai-rules-updater.js +159 -0
- package/dist/orchestrator/ai-rules-updater.js.map +1 -0
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +2 -27
- package/dist/orchestrator/project-initializer.js.map +1 -1
- package/dist/orchestrator/screen-manager.d.ts +1 -0
- package/dist/orchestrator/screen-manager.d.ts.map +1 -1
- package/dist/orchestrator/screen-manager.js +70 -3
- package/dist/orchestrator/screen-manager.js.map +1 -1
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +18 -9
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-make-tc.md +12 -4
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-make-test.md +9 -11
- package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-review.md +228 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +30 -11
- package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +91 -25
- package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +92 -71
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +13 -5
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-make-tc.md +13 -4
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-make-test.md +9 -11
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-review.md +228 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +30 -11
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +72 -31
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +92 -72
- package/dist/orchestrator/templates/readme.md +13 -8
- package/package.json +1 -1
- package/src/cli/commands/update.ts +18 -0
- package/src/cli/index.ts +3 -1
- package/src/generators/gherkin-parser/index.ts +19 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/attribute-assertion.hbs +3 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +12 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +12 -1
- package/src/generators/test-generator/patterns/assertion-patterns.ts +13 -0
- package/src/generators/test-generator/patterns/index.ts +41 -0
- package/src/generators/test-generator/patterns/interaction-patterns.ts +0 -14
- package/src/generators/test-generator/patterns/table-patterns.ts +8 -5
- package/src/orchestrator/ai-rules-updater.ts +141 -0
- package/src/orchestrator/project-initializer.ts +2 -32
- package/src/orchestrator/screen-manager.ts +72 -3
- package/src/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +18 -9
- package/src/orchestrator/templates/ai-instructions/claude-cmd-make-tc.md +12 -4
- package/src/orchestrator/templates/ai-instructions/claude-cmd-make-test.md +9 -11
- package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-review.md +228 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +30 -11
- package/src/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +91 -25
- package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +92 -71
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +13 -5
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-make-tc.md +13 -4
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-make-test.md +9 -11
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-review.md +228 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +30 -11
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +72 -31
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +92 -72
- package/src/orchestrator/templates/readme.md +13 -8
- package/docs/gherkin standards/gherkin-core-standard.md +0 -431
- package/docs/gherkin standards/gherkin-core-standard.vi.md +0 -399
- package/docs/gherkin-dictionary.md +0 -1126
- package/docs/makeauth.md +0 -225
|
@@ -124,6 +124,24 @@ To determine the correct `nth` offset, count how many matching elements appear b
|
|
|
124
124
|
|
|
125
125
|
---
|
|
126
126
|
|
|
127
|
+
### Step 4b: Handle Detail Screens with Dynamic IDs
|
|
128
|
+
|
|
129
|
+
For screens like `/admin/users/:id` or `/products/:slug`:
|
|
130
|
+
1. Navigate to the **list page** first via MCP browser to find a real record ID
|
|
131
|
+
2. Use that ID in the page selector value
|
|
132
|
+
3. Use `User is on [X] page` — sungen resolves the path from the selector
|
|
133
|
+
|
|
134
|
+
```yaml
|
|
135
|
+
# selectors.yaml — full path with real ID
|
|
136
|
+
user detail:
|
|
137
|
+
type: 'page'
|
|
138
|
+
value: '/admin/users/de42d800-0f5a-490e-9dcf-344fedbd34a5'
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Note: the selector uses a hardcoded ID from the live page. If the record is deleted, update the ID in `selectors.yaml`.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
127
145
|
### Step 5: Handle SPA / Client-side Routing
|
|
128
146
|
|
|
129
147
|
Many modern apps (Next.js, Nuxt, SvelteKit, etc.) return the same HTML shell for all URLs and route client-side. `domcontentloaded` fires on the shell before the target page renders.
|
|
@@ -188,54 +206,77 @@ Many elements don't need a YAML entry — sungen auto-infers from the Gherkin la
|
|
|
188
206
|
|
|
189
207
|
---
|
|
190
208
|
|
|
191
|
-
###
|
|
209
|
+
### Proactive Selector Validation (before running tests)
|
|
192
210
|
|
|
193
|
-
|
|
211
|
+
**Most failures are selector mismatches.** Validate selectors against the live page BEFORE running any test — this eliminates slow compile→run→read→fix cycles.
|
|
194
212
|
|
|
195
|
-
|
|
196
|
-
1. INITIAL RUN — run ALL tests, collect full failure list
|
|
197
|
-
npx playwright test <spec> --reporter=line
|
|
213
|
+
After generating `selectors.yaml` (Step 3), verify each entry:
|
|
198
214
|
|
|
199
|
-
|
|
200
|
-
a. Read test output → group failures by root cause:
|
|
201
|
-
- Same selector broken → 1 fix covers many tests
|
|
202
|
-
- Same error type (strict mode, timeout, text mismatch)
|
|
203
|
-
b. Fix selectors.yaml or test-data.yaml for current batch
|
|
204
|
-
c. Recompile: sungen generate --screen <screen>
|
|
205
|
-
d. Re-run ONLY previously-failing tests (max 20):
|
|
206
|
-
npx playwright test <spec> --grep "VP-UI-001|VP-UI-002|VP-VAL-001" --reporter=line
|
|
207
|
-
e. If batch passes → pick next batch of remaining failures
|
|
208
|
-
f. If batch still fails → fix and retry (counts toward max 5)
|
|
209
|
-
|
|
210
|
-
3. FINAL CONFIRMATION — run ALL tests once:
|
|
211
|
-
npx playwright test <spec> --reporter=line
|
|
212
|
-
This catches regressions from selector changes.
|
|
215
|
+
#### How to validate
|
|
213
216
|
|
|
214
|
-
|
|
217
|
+
Use `browser_evaluate` to check if each selector actually finds an element on the page:
|
|
218
|
+
|
|
219
|
+
```js
|
|
220
|
+
// Validate a role selector
|
|
221
|
+
document.querySelectorAll('[role="button"]').length;
|
|
222
|
+
// or check accessible name
|
|
223
|
+
Array.from(document.querySelectorAll('button'))
|
|
224
|
+
.filter(el => el.textContent.includes('Submit') || el.getAttribute('aria-label')?.includes('Submit'))
|
|
225
|
+
.length;
|
|
215
226
|
```
|
|
216
227
|
|
|
217
|
-
|
|
228
|
+
Or use `browser_snapshot` and cross-check:
|
|
229
|
+
1. Read all `[Reference]` entries from `selectors.yaml`
|
|
230
|
+
2. Take a `browser_snapshot`
|
|
231
|
+
3. For each entry, verify the element exists in the snapshot
|
|
232
|
+
4. Fix mismatches immediately — no test run needed
|
|
233
|
+
|
|
234
|
+
#### What to check
|
|
235
|
+
|
|
236
|
+
| Selector type | Validation method |
|
|
237
|
+
|---|---|
|
|
238
|
+
| `role` + `name` | Search snapshot for `role "name"` text |
|
|
239
|
+
| `testid` | `browser_evaluate`: `document.querySelector('[data-testid="xxx"]')` |
|
|
240
|
+
| `placeholder` | Search snapshot for textbox with placeholder |
|
|
241
|
+
| `locator` (CSS) | `browser_evaluate`: `document.querySelector('xxx')` |
|
|
242
|
+
|
|
243
|
+
**Target: fix 80%+ of selector issues before the first test run.**
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
### Batched Test Execution
|
|
248
|
+
|
|
249
|
+
After proactive validation, run tests in **batches of 20** for faster feedback:
|
|
218
250
|
|
|
219
|
-
Extract scenario names from the failure output and join with `|`:
|
|
220
|
-
```bash
|
|
221
|
-
# Example: re-run only 3 failing tests
|
|
222
|
-
npx playwright test <spec> --grep "VP-VAL-001|VP-VAL-002|VP-VAL-003" --reporter=line
|
|
223
251
|
```
|
|
252
|
+
1. COMPILE — sungen generate --screen <screen>
|
|
224
253
|
|
|
225
|
-
|
|
226
|
-
|
|
254
|
+
2. BATCH RUN — run 20 tests at a time:
|
|
255
|
+
npx playwright test <spec> --grep "VP-UI-001|VP-UI-002|...|VP-UI-020" --reporter=line
|
|
256
|
+
|
|
257
|
+
3. IF FAILURES in batch:
|
|
258
|
+
a. Group failures by root cause (same selector, same error type)
|
|
259
|
+
b. Fix selectors.yaml or test-data.yaml
|
|
260
|
+
c. Recompile, re-run only failing tests from this batch
|
|
261
|
+
d. If fixed → move to next batch
|
|
262
|
+
|
|
263
|
+
4. NEXT BATCH — repeat with next 20 tests
|
|
264
|
+
|
|
265
|
+
5. FINAL CONFIRMATION — run ALL tests once:
|
|
266
|
+
npx playwright test <spec> --reporter=line
|
|
267
|
+
|
|
268
|
+
6. If still failing after 5 fix attempts per batch → ask user about direct .spec.ts fix
|
|
269
|
+
```
|
|
227
270
|
|
|
228
271
|
#### Grouping failures by root cause
|
|
229
272
|
|
|
230
273
|
Common patterns where 1 fix resolves many failures:
|
|
231
|
-
- **Same selector** — e.g., all `[Email Error]` tests fail → fix
|
|
274
|
+
- **Same selector** — e.g., all `[Email Error]` tests fail → fix once
|
|
232
275
|
- **Same error type** — e.g., all `strict mode violation` → add `exact: true` or `nth`
|
|
233
|
-
- **Same assertion** — e.g., all `toHaveText` on inputs fail → change Gherkin pattern
|
|
276
|
+
- **Same assertion** — e.g., all `toHaveText` on inputs fail → change Gherkin pattern
|
|
234
277
|
|
|
235
278
|
Fix the root cause first, verify with the batch, then move on.
|
|
236
279
|
|
|
237
|
-
**Always read the error context snapshot first** — it shows the exact page state when the test failed, which is more reliable than re-navigating with MCP.
|
|
238
|
-
|
|
239
280
|
---
|
|
240
281
|
|
|
241
282
|
### Key Rules (from sungen-selector-keys)
|
|
@@ -33,6 +33,46 @@ For options 1 and 2:
|
|
|
33
33
|
For option 3:
|
|
34
34
|
- Overwrite both `.feature` and `test-data.yaml` completely
|
|
35
35
|
|
|
36
|
+
### Requirements-Driven Generation
|
|
37
|
+
|
|
38
|
+
When `qa/screens/<screen>/requirements/` exists, use it as the **primary source** for test case generation. This produces higher quality tests because requirements contain exact validation messages, field constraints, business rules, and states.
|
|
39
|
+
|
|
40
|
+
#### Reading Requirements
|
|
41
|
+
|
|
42
|
+
1. **`spec.md`** (primary) — structured screen specification:
|
|
43
|
+
- **Sections** → map directly to test case sections
|
|
44
|
+
- **Fields table** → generate boundary value tests (min/max constraints), required field tests, format tests
|
|
45
|
+
- **Validation Rules table** → generate exact assertion tests using the error messages as `{{test_data}}` values
|
|
46
|
+
- **Actions table** → generate interaction tests (click, submit, navigate)
|
|
47
|
+
- **States table** → generate state transition tests (loading, error, success, disabled)
|
|
48
|
+
- **Business Rules** → generate logic tests (limits, permissions, conditional behavior)
|
|
49
|
+
- **Accessibility** → generate tab-order and aria tests
|
|
50
|
+
|
|
51
|
+
2. **`ui/`** (supplementary) — screenshots, mockups, design images:
|
|
52
|
+
- Read images to understand layout, element positions, visual states
|
|
53
|
+
- Cross-reference with spec.md — identify elements visible in UI but missing from spec
|
|
54
|
+
- Use for UI/UX viewpoint tests (element visibility, placement, responsive)
|
|
55
|
+
|
|
56
|
+
3. **`notes.md`** (supplementary) — free-form edge cases, decisions, known issues:
|
|
57
|
+
- Extract edge cases → add to test coverage
|
|
58
|
+
- Flag known issues → add TODO scenarios
|
|
59
|
+
|
|
60
|
+
#### How Requirements Improve Each Viewpoint
|
|
61
|
+
|
|
62
|
+
| Viewpoint | Without Requirements | With Requirements |
|
|
63
|
+
|-----------|---------------------|-------------------|
|
|
64
|
+
| **UI/UX** | "see [Field] is visible" (generic) | "see [Field] field" + verify label, placeholder, default from spec |
|
|
65
|
+
| **Validation** | "submit empty → see error" (vague) | "submit empty email → see {{email_required_error}}" with exact message from spec |
|
|
66
|
+
| **Logic** | Based on observed page behavior | Business rules drive specific tests (lockout after N attempts, session expiry) |
|
|
67
|
+
| **Security** | Generic injection tests | Role-based tests from auth requirements, permission-specific scenarios |
|
|
68
|
+
|
|
69
|
+
#### Merging Requirements + Live Page
|
|
70
|
+
|
|
71
|
+
If the user also explores the live page:
|
|
72
|
+
- **Verify** spec.md against actual page — flag mismatches (e.g., field in spec but not on page)
|
|
73
|
+
- **Supplement** — discover elements on page not in spec (e.g., footer links, tooltips)
|
|
74
|
+
- **Exact text** — capture actual placeholder text, button labels, error messages from live page and update test-data accordingly
|
|
75
|
+
|
|
36
76
|
### Page Exploration & Auth
|
|
37
77
|
|
|
38
78
|
Use Playwright MCP to explore the live page. If the page requires authentication:
|
|
@@ -55,26 +95,7 @@ Use Playwright MCP to explore the live page. If the page requires authentication
|
|
|
55
95
|
|
|
56
96
|
#### Detail screens with dynamic IDs
|
|
57
97
|
|
|
58
|
-
For screens like `/admin/users/:id` or `/products/:slug
|
|
59
|
-
1. Navigate to the **list page** first via MCP browser to find a real record ID
|
|
60
|
-
2. Use that ID in the page selector value
|
|
61
|
-
3. Use `User is on [X] page` — sungen resolves the path from the selector
|
|
62
|
-
|
|
63
|
-
```yaml
|
|
64
|
-
# selectors.yaml — full path with real ID (generated during make-test)
|
|
65
|
-
user detail:
|
|
66
|
-
type: 'page'
|
|
67
|
-
value: '/admin/users/de42d800-0f5a-490e-9dcf-344fedbd34a5'
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
```gherkin
|
|
71
|
-
Scenario: VP-UI-001 User detail displays name
|
|
72
|
-
Given User is on [User Detail] page
|
|
73
|
-
And User wait for [User Name] heading is visible
|
|
74
|
-
Then User see [User Name] heading with {{user_name}}
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
Note: the selector uses a hardcoded ID from the live page. If the record is deleted, update the ID in `selectors.yaml`.
|
|
98
|
+
For screens like `/admin/users/:id` or `/products/:slug`, write Gherkin normally (`User is on [User Detail] page`). The real ID will be resolved during `/sungen-make-test` when selectors are generated from the live page. See `sungen-selector-fix` skill for details.
|
|
78
99
|
|
|
79
100
|
### Section-Focused Approach
|
|
80
101
|
|
|
@@ -113,72 +134,58 @@ For each selected section, generate **20+ scenarios per applicable viewpoint**.
|
|
|
113
134
|
|
|
114
135
|
Present a test plan summary and wait for user confirmation before generating files.
|
|
115
136
|
|
|
137
|
+
### Assertion & Review Quality
|
|
138
|
+
|
|
139
|
+
For assertion quality rules, action-result coherence rules, and the 9-point quality review checklist, see the `sungen-gherkin-review` skill. That skill is auto-loaded during the self-review step.
|
|
140
|
+
|
|
116
141
|
### Viewpoint Categories
|
|
117
142
|
|
|
118
143
|
| VP Category | Description |
|
|
119
144
|
|---|---|
|
|
120
|
-
| **UI/UX** | Default appearance,
|
|
121
|
-
| **Validation** | Input validation, error messages, edge cases. **
|
|
145
|
+
| **UI/UX** | Default appearance, default values/text, default states. Group related elements per scenario. |
|
|
146
|
+
| **Validation** | Input validation, error messages, edge cases. **Assert exact error messages via `User see [T] with {{error_var}}`** — store texts in test-data.yaml. |
|
|
122
147
|
| **Logic** | Business logic, interactions, state changes |
|
|
123
148
|
| **Security** | Auth guards, injection, permission checks |
|
|
124
149
|
|
|
125
150
|
### Coverage Checklist per Section Pattern
|
|
126
151
|
|
|
127
|
-
Apply the relevant checklist based on the section pattern:
|
|
128
|
-
|
|
129
152
|
#### Form sections
|
|
130
|
-
- **UI/UX**: All fields
|
|
131
|
-
- **Validation**: Empty submit
|
|
132
|
-
- **
|
|
133
|
-
- **
|
|
134
|
-
- **Security**: CSRF protection, unauthorized submit, role-based field visibility, input sanitization
|
|
153
|
+
- **UI/UX**: All fields + labels + default values in 1-2 grouped scenarios. Default states (button enabled/disabled, checkbox unchecked). Placeholder text via `with {{placeholder}}`.
|
|
154
|
+
- **Validation**: Empty submit → verify all error messages. Each required field empty individually → verify specific error. Invalid formats → verify specific error. Boundary values (min/max length). Special chars, unicode, XSS, SQL injection.
|
|
155
|
+
- **Logic**: Successful submit → verify success state/redirect. Edit existing → verify pre-filled values. Cancel → verify form resets. Field dependencies (show/hide).
|
|
156
|
+
- **Security**: Unauthorized submit, role-based field visibility, input sanitization
|
|
135
157
|
|
|
136
158
|
#### Data Table sections
|
|
137
|
-
- **UI/UX**: All
|
|
138
|
-
- **
|
|
139
|
-
- **
|
|
140
|
-
- **Security**: Cannot access other users' data, action permissions per role, data not exposed in DOM
|
|
159
|
+
- **UI/UX**: All column headers in one scenario (`User see [Col] column in [Table] table`). Row data displays with correct values. Empty state message. Action buttons per row.
|
|
160
|
+
- **Logic**: Sort ascending/descending. Filter by column. Search. Row actions (edit/delete/view). Bulk select + action.
|
|
161
|
+
- **Security**: Action permissions per role
|
|
141
162
|
|
|
142
163
|
#### Search & Filter sections
|
|
143
|
-
- **UI/UX**: Search field
|
|
144
|
-
- **Validation**: Empty search, special chars,
|
|
145
|
-
- **Logic**: Apply single filter, combine
|
|
146
|
-
- **Security**: Injection via search
|
|
164
|
+
- **UI/UX**: Search field + filter buttons + clear button states in one scenario
|
|
165
|
+
- **Validation**: Empty search, special chars, injection, long text, unicode
|
|
166
|
+
- **Logic**: Apply/clear single filter, combine filters, no results state with message
|
|
167
|
+
- **Security**: Injection via search/filter params
|
|
147
168
|
|
|
148
169
|
#### Pagination sections
|
|
149
|
-
- **UI/UX**: Page indicator
|
|
150
|
-
- **Logic**:
|
|
151
|
-
- **Security**: Filters persist across pages, search persists, cannot access page beyond max
|
|
170
|
+
- **UI/UX**: Page indicator + button states (previous disabled on page 1, next enabled)
|
|
171
|
+
- **Logic**: Navigate pages, boundary behavior, indicator updates
|
|
152
172
|
|
|
153
173
|
#### Modal / Dialog sections
|
|
154
|
-
- **UI/UX**: Modal
|
|
155
|
-
- **Validation**:
|
|
156
|
-
- **Logic**: Open
|
|
157
|
-
- **Security**: Cannot open unauthorized modals, CSRF on modal submit
|
|
158
|
-
|
|
159
|
-
#### Card Grid / List sections
|
|
160
|
-
- **UI/UX**: Cards display all expected fields (title, image, description, metadata), empty state, loading state
|
|
161
|
-
- **Logic**: Click card navigates to detail, load more / infinite scroll, card action buttons (like, share, delete), responsive layout
|
|
162
|
-
- **Security**: User-generated content sanitized, cannot access other users' cards
|
|
163
|
-
|
|
164
|
-
#### Carousel / Slider sections
|
|
165
|
-
- **UI/UX**: Arrows visible, indicators visible, current slide highlighted
|
|
166
|
-
- **Logic**: Next slide, previous slide, wrap at end, auto-play, indicator click navigates, swipe gesture
|
|
167
|
-
- **Security**: N/A
|
|
174
|
+
- **UI/UX**: Modal content + all fields + close button in one scenario
|
|
175
|
+
- **Validation**: Field validation inside modal with exact errors
|
|
176
|
+
- **Logic**: Open/close methods (X, Escape, overlay), submit success/error behavior
|
|
168
177
|
|
|
169
178
|
#### Tabs / Accordion sections
|
|
170
|
-
- **UI/UX**: All
|
|
171
|
-
- **Logic**: Switch tabs
|
|
172
|
-
- **Security**: Tab content respects permissions
|
|
179
|
+
- **UI/UX**: All tab labels + active tab highlighted + default tab content
|
|
180
|
+
- **Logic**: Switch tabs → verify content changes, accordion expand/collapse
|
|
173
181
|
|
|
174
182
|
### General Coverage Dimensions (aim for 20+ total per viewpoint)
|
|
175
183
|
|
|
176
|
-
**Happy paths (3-5):** Standard flow
|
|
177
|
-
**Edge cases (5-8):** Empty, max length, special chars, unicode
|
|
178
|
-
**
|
|
179
|
-
**
|
|
180
|
-
**
|
|
181
|
-
**Combinatorial (2-4):** Multiple invalid fields, valid+invalid combos, different roles
|
|
184
|
+
**Happy paths (3-5):** Standard flow with full assertion of result state
|
|
185
|
+
**Edge cases (5-8):** Empty, max length, special chars, unicode, whitespace, boundary values
|
|
186
|
+
**Negative cases (3-5):** Invalid format, missing required, wrong type, exact error messages
|
|
187
|
+
**State transitions (2-4):** Before/after action, verify both old and new state
|
|
188
|
+
**Combinatorial (2-4):** Multiple invalid fields, valid+invalid combos
|
|
182
189
|
|
|
183
190
|
### SPA Wait-For Steps
|
|
184
191
|
|
|
@@ -209,24 +216,37 @@ Feature: <Screen> Screen
|
|
|
209
216
|
# Section: Create User Form
|
|
210
217
|
# ============================================================
|
|
211
218
|
|
|
212
|
-
# --- UI/UX
|
|
219
|
+
# --- UI/UX ---
|
|
213
220
|
|
|
214
|
-
Scenario: VP-UI-001
|
|
215
|
-
|
|
221
|
+
Scenario: VP-UI-001 Form displays all fields with correct defaults
|
|
222
|
+
Given User is on [Create User] page
|
|
223
|
+
Then User see [Create User] heading with {{form_title}}
|
|
224
|
+
And User see [Name] field
|
|
225
|
+
And User see [Email] field
|
|
226
|
+
And User see [Role] dropdown with {{default_role}}
|
|
227
|
+
And User see [Submit] button is disabled
|
|
228
|
+
And User see [Cancel] button is enabled
|
|
216
229
|
|
|
217
|
-
# --- Validation
|
|
230
|
+
# --- Validation ---
|
|
218
231
|
|
|
219
|
-
Scenario: VP-VAL-001 Submit with empty
|
|
220
|
-
|
|
232
|
+
Scenario: VP-VAL-001 Submit with all empty fields shows errors
|
|
233
|
+
Given User is on [Create User] page
|
|
234
|
+
When User click [Submit] button
|
|
235
|
+
Then User see [Name error] message with {{name_required_error}}
|
|
236
|
+
And User see [Email error] message with {{email_required_error}}
|
|
221
237
|
|
|
222
238
|
# ============================================================
|
|
223
239
|
# Section: User Table
|
|
224
240
|
# ============================================================
|
|
225
241
|
|
|
226
|
-
# --- UI/UX
|
|
242
|
+
# --- UI/UX ---
|
|
227
243
|
|
|
228
|
-
Scenario: VP-UI-
|
|
229
|
-
|
|
244
|
+
Scenario: VP-UI-010 Table displays all columns
|
|
245
|
+
Given User is on [Users] page
|
|
246
|
+
Then User see [Name] column in [Users] table
|
|
247
|
+
And User see [Email] column in [Users] table
|
|
248
|
+
And User see [Status] column in [Users] table
|
|
249
|
+
And User see [Actions] column in [Users] table
|
|
230
250
|
```
|
|
231
251
|
|
|
232
252
|
**Naming convention:** `VP-<CATEGORY>-<NNN>` prefix in Scenario name.
|
|
@@ -15,7 +15,11 @@ sungen generate → compiles Gherkin + selectors + data → Playwright .spec.ts
|
|
|
15
15
|
├── qa/screens/<name>/
|
|
16
16
|
│ ├── features/ # .feature files (Gherkin)
|
|
17
17
|
│ ├── selectors/ # Element locator YAML mappings
|
|
18
|
-
│
|
|
18
|
+
│ ├── test-data/ # Test data YAML values
|
|
19
|
+
│ └── requirements/ # Screen specs, UI designs, notes
|
|
20
|
+
│ ├── spec.md # Structured screen specification
|
|
21
|
+
│ ├── ui/ # Screenshots, mockups, design images
|
|
22
|
+
│ └── notes.md # Edge cases, decisions (optional)
|
|
19
23
|
├── specs/
|
|
20
24
|
│ └── generated/ # Auto-generated Playwright tests
|
|
21
25
|
├── .claude/
|
|
@@ -53,7 +57,7 @@ Use AI commands (Claude Code or GitHub Copilot) to drive the workflow:
|
|
|
53
57
|
|---|---|
|
|
54
58
|
| `/sungen:add-screen` | `/sungen-add-screen` |
|
|
55
59
|
|
|
56
|
-
Scaffolds `qa/screens/<name>/` with empty feature, selectors,
|
|
60
|
+
Scaffolds `qa/screens/<name>/` with empty feature, selectors, test-data, and requirements files. Fill `requirements/spec.md` with screen specs before creating test cases for higher quality output.
|
|
57
61
|
|
|
58
62
|
### Step 2: Create test cases
|
|
59
63
|
|
|
@@ -62,10 +66,11 @@ Scaffolds `qa/screens/<name>/` with empty feature, selectors, and test-data file
|
|
|
62
66
|
| `/sungen:make-tc login` | `/sungen-make-tc login` |
|
|
63
67
|
|
|
64
68
|
AI acts as a **Senior QA Engineer**:
|
|
65
|
-
1.
|
|
66
|
-
2.
|
|
67
|
-
3.
|
|
68
|
-
4.
|
|
69
|
+
1. Reads `requirements/spec.md` for screen specs (fields, validation, business rules, states)
|
|
70
|
+
2. Optionally explores the live page via Playwright MCP to verify and supplement
|
|
71
|
+
3. Identifies screen sections → asks user which to focus on
|
|
72
|
+
4. Generates **20+ scenarios per viewpoint** (UI/UX, Validation, Logic, Security) for each section
|
|
73
|
+
5. Confirms test plan before generating `.feature` + `test-data.yaml`
|
|
69
74
|
|
|
70
75
|
### Step 3: Compile & run tests
|
|
71
76
|
|
|
@@ -136,8 +141,8 @@ User <action> [<Target>] <type> with {{<Value>}}
|
|
|
136
141
|
| Assert text | `User see [Title] heading with {{title}}` |
|
|
137
142
|
| Assert state | `User see [Submit] button is disabled` |
|
|
138
143
|
| Wait for | `User wait for [Modal] dialog is visible` |
|
|
139
|
-
| Table row | `User see [Users] table
|
|
140
|
-
| Table column | `User see [
|
|
144
|
+
| Table row | `User see [Users] table row with {{name}}` |
|
|
145
|
+
| Table column | `User see [Email] column in [Users] table` |
|
|
141
146
|
|
|
142
147
|
States: `hidden` `visible` `disabled` `enabled` `checked` `unchecked` `focused` `empty` `loading` `selected`
|
|
143
148
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
|
|
3
|
+
export function registerUpdateCommand(program: Command): void {
|
|
4
|
+
program
|
|
5
|
+
.command('update')
|
|
6
|
+
.description('Update AI rules, commands, and skills to the latest version')
|
|
7
|
+
.option('--dry-run', 'Show what would be updated without making changes')
|
|
8
|
+
.action(async (options: { dryRun?: boolean }) => {
|
|
9
|
+
try {
|
|
10
|
+
const { AIRulesUpdater } = require('../../orchestrator/ai-rules-updater');
|
|
11
|
+
const updater = new AIRulesUpdater(process.cwd());
|
|
12
|
+
await updater.update(options.dryRun ?? false);
|
|
13
|
+
} catch (error) {
|
|
14
|
+
console.error('❌ Update failed:', error);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
package/src/cli/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { registerInitCommand } from './commands/init';
|
|
|
9
9
|
import { registerAddCommand } from './commands/add';
|
|
10
10
|
import { registerGenerateCommand } from './commands/generate';
|
|
11
11
|
import { registerMakeauthCommand } from './commands/makeauth';
|
|
12
|
+
import { registerUpdateCommand } from './commands/update';
|
|
12
13
|
|
|
13
14
|
async function main() {
|
|
14
15
|
const program = new Command();
|
|
@@ -16,7 +17,7 @@ async function main() {
|
|
|
16
17
|
program
|
|
17
18
|
.name('sungen')
|
|
18
19
|
.description('Deterministic E2E Test Compiler — Gherkin + Selectors → Playwright')
|
|
19
|
-
.version('2.
|
|
20
|
+
.version('2.3.1');
|
|
20
21
|
|
|
21
22
|
// Global options
|
|
22
23
|
program
|
|
@@ -27,6 +28,7 @@ async function main() {
|
|
|
27
28
|
registerAddCommand(program);
|
|
28
29
|
registerGenerateCommand(program);
|
|
29
30
|
registerMakeauthCommand(program);
|
|
31
|
+
registerUpdateCommand(program);
|
|
30
32
|
|
|
31
33
|
await program.parseAsync(process.argv);
|
|
32
34
|
}
|
|
@@ -14,6 +14,8 @@ export interface ParsedStep {
|
|
|
14
14
|
elementType?: string; // Element type after [Selector] e.g., "button", "text", "link", "field"
|
|
15
15
|
nth?: number; // Positional index from "[Element] field 3" → 3 (0 = not specified)
|
|
16
16
|
featurePath?: string; // NEW: Feature path for page type (e.g., "/", "/dashboard")
|
|
17
|
+
parentRef?: string; // Parent scope reference: "in [Parent Name] parentType" → parentRef = "Parent Name"
|
|
18
|
+
parentType?: string; // Parent scope type: table, list, section, dialog, form
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
export interface ParsedScenario {
|
|
@@ -122,7 +124,20 @@ export class GherkinParser {
|
|
|
122
124
|
* Parse a single step and extract selector/data references
|
|
123
125
|
*/
|
|
124
126
|
private parseStep(step: Messages.Step): ParsedStep {
|
|
125
|
-
|
|
127
|
+
let text = step.text;
|
|
128
|
+
|
|
129
|
+
// === Extract parent scoping: "in [Parent Name] parentType" ===
|
|
130
|
+
// Must be extracted first so the parent [brackets] don't interfere with target selector extraction.
|
|
131
|
+
// Valid parent types: table, list, section, dialog, form
|
|
132
|
+
const parentMatch = text.match(/\s+in\s+\[([^\]]+)\]\s+(table|list|section|dialog|form)\b/i);
|
|
133
|
+
let parentRef: string | undefined;
|
|
134
|
+
let parentType: string | undefined;
|
|
135
|
+
if (parentMatch) {
|
|
136
|
+
parentRef = parentMatch[1];
|
|
137
|
+
parentType = parentMatch[2].toLowerCase();
|
|
138
|
+
// Remove parent scoping from text so it doesn't interfere with other parsing
|
|
139
|
+
text = text.slice(0, parentMatch.index) + text.slice(parentMatch.index! + parentMatch[0].length);
|
|
140
|
+
}
|
|
126
141
|
|
|
127
142
|
// Extract selector reference: [Email Address] or [login:email_field] (legacy)
|
|
128
143
|
// Support natural language and legacy formats
|
|
@@ -159,12 +174,14 @@ export class GherkinParser {
|
|
|
159
174
|
|
|
160
175
|
return {
|
|
161
176
|
keyword: step.keyword.trim(),
|
|
162
|
-
text,
|
|
177
|
+
text: step.text, // Preserve original text (with parent scoping) for pattern matching
|
|
163
178
|
selectorRef,
|
|
164
179
|
dataRef,
|
|
165
180
|
value,
|
|
166
181
|
elementType,
|
|
167
182
|
nth,
|
|
183
|
+
parentRef,
|
|
184
|
+
parentType,
|
|
168
185
|
};
|
|
169
186
|
}
|
|
170
187
|
|
package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
{{#if parentLocator}}{{parentLocator}}.{{#switch strategy~}}
|
|
2
|
+
{{~#case 'testid'}}getByTestId('{{value}}'){{/case~}}
|
|
3
|
+
{{~#case 'role'}}{{#if name}}getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }){{else}}getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}){{/if}}{{/case~}}
|
|
4
|
+
{{~#case 'placeholder'}}getByPlaceholder('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
|
|
5
|
+
{{~#case 'label'}}getByLabel('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
|
|
6
|
+
{{~#case 'text'}}getByText('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
|
|
7
|
+
{{~#case 'locator'}}locator('{{value}}'){{/case~}}
|
|
8
|
+
{{~#case 'id'}}locator('{{value}}'){{/case~}}
|
|
9
|
+
{{~#default}}locator('{{value}}'){{/default~}}
|
|
10
|
+
{{~/switch}}{{else~}}
|
|
1
11
|
{{#if scope}}{{pageRoot}}.getByLabel('{{escapeQuotes scope}}').{{#switch strategy~}}
|
|
2
12
|
{{~#case 'testid'}}getByTestId('{{value}}'){{/case~}}
|
|
3
13
|
{{~#case 'role'}}{{#if name}}getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }){{else}}getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}){{/if}}{{/case~}}
|
|
@@ -16,4 +26,5 @@
|
|
|
16
26
|
{{~#case 'locator'}}{{> locator-strategies/locator}}{{/case~}}
|
|
17
27
|
{{~#case 'id'}}{{> locator-strategies/id}}{{/case~}}
|
|
18
28
|
{{~#default}}{{> locator-strategies/default}}{{/default~}}
|
|
19
|
-
{{~/switch}}{{/if}}
|
|
29
|
+
{{~/switch}}{{/if~}}
|
|
30
|
+
{{/if}}
|
package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
{{#if parentLocator}}{{parentLocator}}.{{#switch strategy~}}
|
|
2
|
+
{{~#case 'testid'}}getByTestId('{{value}}'){{/case~}}
|
|
3
|
+
{{~#case 'role'}}{{#if name}}getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }){{else}}getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}){{/if}}{{/case~}}
|
|
4
|
+
{{~#case 'placeholder'}}getByPlaceholder('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
|
|
5
|
+
{{~#case 'label'}}getByLabel('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
|
|
6
|
+
{{~#case 'text'}}getByText('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
|
|
7
|
+
{{~#case 'locator'}}locator('{{value}}'){{/case~}}
|
|
8
|
+
{{~#case 'id'}}locator('{{value}}'){{/case~}}
|
|
9
|
+
{{~#default}}locator('{{value}}'){{/default~}}
|
|
10
|
+
{{~/switch}}{{> locator-nth}}{{else~}}
|
|
1
11
|
{{#if scope}}{{pageRoot}}.getByLabel('{{escapeQuotes scope}}').{{#switch strategy~}}
|
|
2
12
|
{{~#case 'testid'}}getByTestId('{{value}}'){{/case~}}
|
|
3
13
|
{{~#case 'role'}}{{#if name}}getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }){{else}}getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}){{/if}}{{/case~}}
|
|
@@ -16,4 +26,5 @@
|
|
|
16
26
|
{{~#case 'locator'}}{{> locator-strategies/locator}}{{/case~}}
|
|
17
27
|
{{~#case 'id'}}{{> locator-strategies/id}}{{/case~}}
|
|
18
28
|
{{~#default}}{{> locator-strategies/default}}{{/default~}}
|
|
19
|
-
{{~/switch}}{{> locator-nth}}{{/if}}
|
|
29
|
+
{{~/switch}}{{> locator-nth}}{{/if~}}
|
|
30
|
+
{{/if}}
|
|
@@ -279,6 +279,19 @@ export const assertionPatterns: StepPattern[] = [
|
|
|
279
279
|
// Selector not in YAML or context issue - will use variable-only
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
+
// --- Attribute assertion: toHaveAttribute ---
|
|
283
|
+
// When selector YAML has `attribute` field (e.g., attribute: 'src', 'href')
|
|
284
|
+
if (resolved.attribute) {
|
|
285
|
+
const code = context.templateEngine.renderStep('attribute-assertion', {
|
|
286
|
+
...resolved,
|
|
287
|
+
dataValue,
|
|
288
|
+
});
|
|
289
|
+
return {
|
|
290
|
+
code,
|
|
291
|
+
comment: `Assert ${step.selectorRef} ${resolved.attribute} matches ${step.dataRef}`,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
282
295
|
// --- Input types: toHaveValue ---
|
|
283
296
|
if (step.elementType && INPUT_TYPES.has(step.elementType)) {
|
|
284
297
|
const code = context.templateEngine.renderStep('have-value-assertion', {
|
|
@@ -72,6 +72,14 @@ export class PatternRegistry {
|
|
|
72
72
|
// Prefer resolver (framework-agnostic) over generator (legacy)
|
|
73
73
|
if (pattern.resolver) {
|
|
74
74
|
const resolved = pattern.resolver(step, context);
|
|
75
|
+
|
|
76
|
+
// Auto-inject parent scoping if step has parentRef
|
|
77
|
+
if (step.parentRef && step.parentType) {
|
|
78
|
+
resolved.data.parentLocator = PatternRegistry.resolveParentLocator(
|
|
79
|
+
step.parentRef, step.parentType, context
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
75
83
|
const code = context.templateEngine.renderStep(resolved.templateName, resolved.data);
|
|
76
84
|
return {
|
|
77
85
|
code,
|
|
@@ -86,6 +94,39 @@ export class PatternRegistry {
|
|
|
86
94
|
return null;
|
|
87
95
|
}
|
|
88
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Resolve parent scoping to a Playwright locator string.
|
|
99
|
+
* Tries YAML lookup first, falls back to auto-infer from parentType.
|
|
100
|
+
*
|
|
101
|
+
* Parent type → Playwright role:
|
|
102
|
+
* table → 'table', list → 'list', section → 'region',
|
|
103
|
+
* dialog → 'dialog', form → 'form'
|
|
104
|
+
*/
|
|
105
|
+
private static resolveParentLocator(
|
|
106
|
+
parentRef: string, parentType: string, context: PatternContext
|
|
107
|
+
): string {
|
|
108
|
+
// Try resolving from selectors YAML
|
|
109
|
+
try {
|
|
110
|
+
const resolved = context.selectorResolver.resolveSelector(
|
|
111
|
+
parentRef, context.featureName, parentType, 0
|
|
112
|
+
);
|
|
113
|
+
return context.renderLocator(resolved);
|
|
114
|
+
} catch {
|
|
115
|
+
// Fallback: auto-infer from parentType + parentRef as accessible name
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const roleMap: Record<string, string> = {
|
|
119
|
+
table: 'table',
|
|
120
|
+
list: 'list',
|
|
121
|
+
section: 'region',
|
|
122
|
+
dialog: 'dialog',
|
|
123
|
+
form: 'form',
|
|
124
|
+
};
|
|
125
|
+
const role = roleMap[parentType] || parentType;
|
|
126
|
+
const escapedName = parentRef.replace(/'/g, "\\'");
|
|
127
|
+
return `page.getByRole('${role}', { name: '${escapedName}' })`;
|
|
128
|
+
}
|
|
129
|
+
|
|
89
130
|
/**
|
|
90
131
|
* Check if step matches a pattern matcher
|
|
91
132
|
*/
|