@sun-asterisk/sungen 2.4.0 → 2.4.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.
Files changed (45) hide show
  1. package/dist/cli/index.js +1 -1
  2. package/dist/generators/gherkin-parser/index.d.ts +8 -0
  3. package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
  4. package/dist/generators/gherkin-parser/index.js +12 -0
  5. package/dist/generators/gherkin-parser/index.js.map +1 -1
  6. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-match-data.hbs +15 -0
  7. package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
  8. package/dist/generators/test-generator/patterns/index.js +2 -1
  9. package/dist/generators/test-generator/patterns/index.js.map +1 -1
  10. package/dist/generators/test-generator/patterns/table-patterns.d.ts +4 -1
  11. package/dist/generators/test-generator/patterns/table-patterns.d.ts.map +1 -1
  12. package/dist/generators/test-generator/patterns/table-patterns.js +49 -1
  13. package/dist/generators/test-generator/patterns/table-patterns.js.map +1 -1
  14. package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +19 -16
  15. package/dist/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md +5 -5
  16. package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +8 -1
  17. package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +46 -61
  18. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +20 -0
  19. package/dist/orchestrator/templates/ai-instructions/claude-skill-viewpoint.md +3 -1
  20. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +19 -16
  21. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-error-mapping.md +5 -5
  22. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +8 -1
  23. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +46 -61
  24. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +20 -0
  25. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint.md +3 -1
  26. package/dist/orchestrator/templates/readme.md +82 -19
  27. package/package.json +1 -1
  28. package/src/cli/index.ts +1 -1
  29. package/src/generators/gherkin-parser/index.ts +23 -0
  30. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-match-data.hbs +15 -0
  31. package/src/generators/test-generator/patterns/index.ts +2 -1
  32. package/src/generators/test-generator/patterns/table-patterns.ts +58 -1
  33. package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +19 -16
  34. package/src/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md +5 -5
  35. package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +8 -1
  36. package/src/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +46 -61
  37. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +20 -0
  38. package/src/orchestrator/templates/ai-instructions/claude-skill-viewpoint.md +3 -1
  39. package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +19 -16
  40. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-error-mapping.md +5 -5
  41. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +8 -1
  42. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +46 -61
  43. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +20 -0
  44. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint.md +3 -1
  45. package/src/orchestrator/templates/readme.md +82 -19
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: sungen-run-test
3
- description: 'Generate selectors, compile, and run Playwright tests — auto-fixes selectors on failure. Uses sungen-selector-fix, sungen-selector-keys, and sungen-error-mapping skills.'
3
+ description: 'Compile and run Playwright tests — auto-fixes selectors on failure. Uses sungen-selector-fix, sungen-selector-keys, and sungen-error-mapping skills.'
4
4
  argument-hint: '[screen-name]'
5
5
  agent: 'agent'
6
6
  tools: [vscode, execute, read, agent, edit, search, web, browser, todo, 'playwright/*']
@@ -10,23 +10,26 @@ tools: [vscode, execute, read, agent, edit, search, web, browser, todo, 'playwri
10
10
 
11
11
  ## Role
12
12
 
13
- You are a **Senior Developer** specialized in Playwright test debugging. You generate selectors from live pages, diagnose test failures, and fix selectors/test-data using the `sungen-selector-fix`, `sungen-selector-keys`, and `sungen-error-mapping` skills.
13
+ You are a **Senior Developer**. Use `sungen-selector-fix`, `sungen-selector-keys`, and `sungen-error-mapping` skills.
14
14
 
15
15
  ## Parameters
16
16
 
17
17
  - **screen** — ${input:screen:screen name (e.g., login, dashboard)}
18
18
 
19
- ## Steps
20
-
21
- 1. Verify `qa/screens/${input:screen}/` has `.feature` + `test-data.yaml`. If not → run `/sungen-create-test` first.
22
- 2. **Generate selectors** explore live page via #tool:playwright, build `selectors.yaml` (per `sungen-selector-fix` skill).
23
- 3. **Proactive validation** — verify EVERY selector against the live page using `browser_snapshot` + `browser_evaluate` BEFORE running any test. Fix mismatches immediately. See `sungen-selector-fix` skill "Proactive Selector Validation" section. Target: 80%+ issues fixed before first run.
24
- 4. **Compile** with #tool:terminal: `sungen generate --screen ${input:screen}`
25
- 5. **Batched test run** run tests in batches of 20 via `--grep`:
26
- `npx playwright test specs/generated/${input:screen}/*.spec.ts --grep "VP-UI-001|...|VP-UI-020" --reporter=line`
27
- - If failures in batch → group by root cause, fix, recompile, re-run only failing tests
28
- - If batch passes → move to next 20 tests
29
- - Max 5 fix attempts per batch
30
- 6. **Final confirmation** run ALL tests once to catch regressions.
31
- 7. After 5 fix attempts still failing → ask user about direct `.spec.ts` fix.
32
- 8. Show: pass/fail, attempt count, files changed.
19
+ ## Phase 1: Compile & Run (no MCP)
20
+
21
+ 1. Verify `qa/screens/${input:screen}/` has `.feature` + `test-data.yaml`
22
+ 2. Ensure `selectors.yaml` has page selector. If missing, ask user for URL path
23
+ 3. `sungen generate --screen ${input:screen}`
24
+ 4. `npx playwright test specs/generated/${input:screen}/*.spec.ts --reporter=line`
25
+ 5. If all pass done
26
+
27
+ ## Phase 2: Targeted Fix (only if failures)
28
+
29
+ 6. Parse failures group by root cause
30
+ 7. Navigate to page ONCE via #tool:playwright ONE `browser_snapshot`
31
+ 8. Fix broken selectors per `sungen-selector-fix` skill
32
+ 9. Recompile re-run only failing tests
33
+ 10. Repeat up to 3 attempts
34
+ 11. Final full run for regression check
35
+ 12. Still failing → ask user
@@ -81,12 +81,12 @@ If `toHaveText` fails on an input → the Gherkin step has wrong target type. Fi
81
81
 
82
82
  | Symptom | Fix |
83
83
  |---|---|
84
- | Redirect to login page | Auth expired. Tell user: `sungen makeauth <role> --url <baseURL>` |
85
- | `storageState` file not found | Run `sungen makeauth <role> --url <baseURL>` |
86
- | Most tests timeout on first step | Auth expired — re-run makeauth |
87
- | Page shows home instead of target | SPA + expired auth. Re-run makeauth + add `wait for` step |
84
+ | Redirect to login page | Auth expired. Ask user to log in manually via MCP browser |
85
+ | `storageState` file not found | Ask user to log in manually via MCP browser, then save storage state |
86
+ | Most tests timeout on first step | Auth expired — ask user to re-authenticate via MCP browser |
87
+ | Page shows home instead of target | SPA + expired auth. Re-authenticate + add `wait for` step |
88
88
 
89
- **Never run `sungen makeauth` yourself.** Tell the user.
89
+ **Never use `sungen makeauth`.** Always let the user log in manually via the MCP browser.
90
90
 
91
91
  ---
92
92
 
@@ -50,7 +50,7 @@ User check [T] radio # select radio
50
50
  User uncheck [T] checkbox # uncheck
51
51
  User uncheck [T] toggle # toggle off
52
52
  User select [T] dropdown with {{v}} # select option
53
- User upload [T] uploader with {{f}} # file upload
53
+ User fill [T] uploader with {{f}} # file upload
54
54
  ```
55
55
 
56
56
  ### Interaction
@@ -161,10 +161,17 @@ User see [Table] table with {{count}} # row count
161
161
  User see [Table] table is empty # empty table
162
162
  User see [Col] column with {{v}} # cell value (row scoped)
163
163
  User click [Act] button in [Table] table with {{v}} # action in row
164
+ User see [Table] table match data: # exact table match (inline DataTable)
165
+ | Header1 | Header2 |
166
+ | {{value1}} | {{value2}} |
164
167
  ```
165
168
 
169
+ **Priority**: Use row scope patterns for single-row verification + actions. Use `table match data` only when verifying multiple rows at once.
170
+
166
171
  Row scope: `see [Ref] row in [Table] table with {{v}}` defines `tableRow`. Subsequent `see [Col] column with {{v}}` checks the cell in that row. With `columns` config → exact `nth(index)`, without → `filter({ hasText })`.
167
172
 
173
+ Table match data: `see [Table] table match data:` followed by a Cucumber DataTable. First row = headers, remaining rows = expected data. Uses **filter-based matching** — verifies rows containing expected values exist, resilient to data changes, extra rows, and row reordering. Use `{{variable}}` for cell values.
174
+
168
175
  ### States
169
176
 
170
177
  `hidden` `visible` `disabled` `enabled` `checked` `unchecked` `focused` `empty` `loading` `selected` `sorted ascending` `sorted descending`
@@ -1,41 +1,51 @@
1
1
  ---
2
2
  name: sungen-selector-fix
3
- description: 'Selector generation and fixing strategy — explore live page, generate selectors.yaml, validate, auto-fix. Auto-loaded by run-test command.'
3
+ description: 'Selector fixing strategy — diagnose failures, explore live page targeted, fix broken selectors. Auto-loaded by run-test command.'
4
4
  user-invocable: false
5
5
  ---
6
6
 
7
- ## When to Generate
7
+ ## Strategy: Fix Only What Breaks
8
8
 
9
- Selectors are generated during `/sungen:run-test`, NOT during `/sungen:create-test`.
9
+ Most selectors auto-infer from Gherkin element types (see `sungen-selector-keys` skill). Only add YAML entries for selectors that fail during test execution.
10
+
11
+ **Minimal `selectors.yaml`**: page selectors (required), overrides for elements where auto-infer doesn't work. Never generate a full page catalog upfront.
10
12
 
11
13
  ---
12
14
 
13
- ## Step 1: Authenticate & Snapshot
15
+ ## Step 1: Diagnose Failures
14
16
 
15
- 1. Read `baseURL` from `playwright.config.ts`
16
- 2. `browser_navigate` to `baseURL`
17
- 3. If redirected to login → ask user to log in manually via MCP browser
18
- 4. Navigate to target page → `browser_snapshot`
17
+ Parse Playwright error output to categorize failures:
19
18
 
20
- **Never use `sungen makeauth`.** Never use `browser_evaluate` to inject cookies.
19
+ | Error pattern | Root cause | Fix target |
20
+ |---|---|---|
21
+ | `No element found` / `strict mode violation` | Selector mismatch | `selectors.yaml` |
22
+ | `toBeVisible` timeout | Wrong name or missing element | `selectors.yaml` |
23
+ | `toHaveText` / `toHaveValue` mismatch | Wrong expected data | `test-data.yaml` |
24
+ | `page.goto` error | Wrong URL | page selector in `selectors.yaml` |
25
+ | `frame` error | Element inside iframe | add `frame` field |
26
+
27
+ **Group by root cause** — if 5 tests fail because `[Submit]` button has a different name, that's 1 fix, not 5.
28
+
29
+ **Check `test-results/` first** — Playwright captures failure screenshots automatically. Use these to diagnose before any MCP exploration.
21
30
 
22
31
  ---
23
32
 
24
- ## Step 2: Build Element Catalog
33
+ ## Step 2: Targeted MCP Exploration
25
34
 
26
- **Never guess names from Gherkin labels. Copy exact values from snapshot.**
35
+ Only when `test-results/` screenshots are insufficient:
27
36
 
28
- Catalog all accessible elements from snapshot: buttons, links, headings, inputs, regions, images.
37
+ 1. Read `baseURL` from `playwright.config.ts`
38
+ 2. `browser_navigate` to target page
39
+ 3. If redirected to login → ask user to log in manually via MCP browser
40
+ 4. Take **ONE** `browser_snapshot` — fix all broken selectors from this single snapshot
29
41
 
30
- Then check for `data-testid` attributes:
31
- ```js
32
- Array.from(document.querySelectorAll('[data-testid]'))
33
- .map(e => ({ testid: e.dataset.testid, tag: e.tagName, text: e.textContent.trim().slice(0, 60) }))
34
- ```
42
+ **Never use `sungen makeauth`.** Never use `browser_evaluate` to inject cookies.
35
43
 
36
44
  ---
37
45
 
38
- ## Step 3: Map Gherkin Labels → Selectors
46
+ ## Step 3: Fix Broken Selectors
47
+
48
+ For each failed selector, find the correct locator from the snapshot:
39
49
 
40
50
  Selector priority (use first applicable):
41
51
 
@@ -50,24 +60,24 @@ Selector priority (use first applicable):
50
60
 
51
61
  **Exact name rule**: copy name character-for-character from snapshot. Never infer from Gherkin label.
52
62
 
53
- **No accessible name**: use `locator` with CSS/ARIA attribute:
54
- ```yaml
55
- search field:
56
- type: 'locator'
57
- value: '[role="searchbox"]'
63
+ Check for `data-testid` attributes if role-based matching fails:
64
+ ```js
65
+ Array.from(document.querySelectorAll('[data-testid]'))
66
+ .map(e => ({ testid: e.dataset.testid, tag: e.tagName, text: e.textContent.trim().slice(0, 60) }))
58
67
  ```
59
68
 
60
- **Dynamic content**: never use `type: text` with specific value. Use `testid` or structural `role` + `nth`.
61
-
62
- **Multiple matches**: add `nth: 0` for first occurrence.
63
-
64
- Auto-infer rules and lookup priority see `sungen-selector-keys` skill.
69
+ Common fixes:
70
+ - Name mismatch → copy exact name from snapshot
71
+ - Multiple matches add `nth` or `exact: true`
72
+ - No accessible name → use `testid` or `locator` (CSS)
73
+ - Element in iframeadd `frame` field
74
+ - Dynamic content → use `testid` or structural `role` + `nth`
65
75
 
66
76
  ---
67
77
 
68
78
  ## Step 4: Table Selectors
69
79
 
70
- For table patterns using the v2.3 syntax, generate table selectors with `columns` config:
80
+ For table patterns, add table selectors with `columns` config:
71
81
 
72
82
  ```yaml
73
83
  users:
@@ -88,15 +98,6 @@ users:
88
98
 
89
99
  **How to build `columns`**: count column headers in snapshot (left to right, 0-indexed). Map each `[Col] column` reference from feature file to its index.
90
100
 
91
- **Why**: enables exact cell assertion via `cell.nth(index).toHaveText(value)` instead of approximate `cell.filter({ hasText })`.
92
-
93
- **Row scope Gherkin** — these patterns use the table selector:
94
- ```gherkin
95
- Then User see [Username] row in [Users] table with {{user_name}}
96
- Then User see [Status] column with {{expected_status}}
97
- When User click [Edit] button in [Users] table with {{user_name}}
98
- ```
99
-
100
101
  ---
101
102
 
102
103
  ## Step 5: Detail Screens with Dynamic IDs
@@ -113,28 +114,12 @@ user detail:
113
114
 
114
115
  ---
115
116
 
116
- ## Step 6: Validate Before Running
117
-
118
- **Fix 80%+ of selector issues before first test run.**
119
-
120
- After generating `selectors.yaml`, verify each entry against the live snapshot:
121
- - `role` + `name` → search snapshot for `role "name"`
122
- - `testid` → `browser_evaluate`: `document.querySelector('[data-testid="xxx"]')`
123
- - `placeholder` → search snapshot for textbox with placeholder
124
- - `page` → verify URL path exists
125
-
126
- Common fixes: name mismatch → copy from snapshot, element in iframe → add `frame`, multiple matches → add `nth` or `exact: true`.
127
-
128
- ---
129
-
130
- ## Step 7: Run & Fix Loop
131
-
132
- Run tests in batches of 20. Group failures by root cause (same selector, same error type). Fix once, verify batch, move to next.
133
-
134
- ```
135
- compile → batch run 20 → fix root cause → recompile → re-run failures → next batch
136
- ```
117
+ ## Step 6: Fix Loop
137
118
 
138
- After all batches pass → run ALL tests once for regression check.
119
+ After fixing selectors:
120
+ 1. Recompile: `sungen generate --screen <screen>`
121
+ 2. Re-run only failing tests: `npx playwright test --grep "VP-XXX-001|VP-XXX-002" --reporter=line`
122
+ 3. If new failures → repeat (max 3 attempts)
123
+ 4. After all fixes → run ALL tests once for regression check
139
124
 
140
- If still failing after 5 attempts per batch → ask user about direct `.spec.ts` fix.
125
+ If still failing after 3 attempts → ask user for guidance.
@@ -100,8 +100,28 @@ Feature: <Screen> Screen
100
100
  Then User see [Name] column in [Users] table
101
101
  And User see [Email] column in [Users] table
102
102
  And User see [Status] column in [Users] table
103
+
104
+ # --- Data & Validate ---
105
+
106
+ Scenario: VP-VAL-010 Table displays correct data
107
+ Then User see [Users] table match data:
108
+ | Name | Email | Status |
109
+ | {{name_1}} | {{email_1}} | {{status_1}} |
110
+ | {{name_2}} | {{email_2}} | {{status_2}} |
111
+
112
+ Scenario: VP-VAL-011 Edit button targets correct row
113
+ Given User see [Target] row in [Users] table with {{name_1}}
114
+ When User click [Edit] button in [Users] table with {{name_1}}
115
+ Then User see [Name] field with {{name_1}}
103
116
  ```
104
117
 
118
+ ### When to use DataTable vs Row Scope
119
+
120
+ | Pattern | Use when |
121
+ |---|---|
122
+ | `table match data:` + DataTable | Verifying **multiple rows** exist with expected values |
123
+ | `row in [Table] table with {{v}}` + `column with {{v}}` | Checking **single row** details or **acting** on a row (click, edit) |
124
+
105
125
  **Naming**: `VP-<CATEGORY>-<NNN>` prefix.
106
126
 
107
127
  **Test data** — `qa/screens/<screen>/test-data/<screen>.yaml`, grouped by section.
@@ -52,6 +52,8 @@ user-invocable: false
52
52
  - Consistency: total record count on UI matches server data
53
53
  - Row Limit: displayed rows never exceed page size/limit
54
54
  - Cell Integrity: cell data matches database, correct format (date, currency, status)
55
+ - **Use `table match data:` with inline DataTable** for multi-row content verification (filter-based, resilient to data changes)
56
+ - Use row scope (`row in [Table] table with {{v}}` + `column with {{v}}`) for single-row detail checks or when you need actions on a row
55
57
 
56
58
  **Logic**
57
59
  - Sorting: column sort refreshes data with correct order, updates header icon
@@ -82,7 +84,7 @@ user-invocable: false
82
84
 
83
85
  **Security**
84
86
  - Injection: XSS/SQL encoded as plain text, never executed
85
- - Wildcards: %, _, * treated as normal text (escaped)
87
+ - Wildcards: `%, _, *` treated as normal text (escaped)
86
88
 
87
89
  ---
88
90
 
@@ -1,6 +1,6 @@
1
1
  # Sungen Test Automation
2
2
 
3
- This project uses [Sungen v2](https://github.com/sun-asterisk/sungen) — a deterministic E2E test compiler.
3
+ This project uses [Sungen v2.4.1](https://github.com/sun-asterisk/sungen) — a deterministic E2E test compiler.
4
4
 
5
5
  ## How it works
6
6
 
@@ -37,16 +37,16 @@ sungen generate → compiles Gherkin + selectors + data → Playwright .spec.ts
37
37
  ## Workflow
38
38
 
39
39
  ```
40
- Step 1 Step 2 Step 3
41
- ┌──────────┐ ┌──────────┐ ┌──────────┐
42
- │ /add- │────────▶│ /create-test │────────▶│ /make-
43
- │ screen │ │ │ │ test │
44
- └──────────┘ └──────────┘ └──────────┘
45
- Scaffold Pick sections Generate
46
- directories → design TCs selectors
47
- → generate → compile
48
- .feature + → run tests
49
- test-data → auto-fix
40
+ Step 1 Step 2 Step 3 Step 4
41
+ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
42
+ │ /add- │────────▶│ /create- │────────▶│ /run- │────────▶│ /review
43
+ │ screen │ │ test │ │ test │ │ │
44
+ └──────────┘ └──────────┘ └──────────┘ └──────────┘
45
+ Scaffold Pick sections Generate Review
46
+ directories → design TCs selectors syntax &
47
+ → generate → compile coverage
48
+ .feature + → run tests → score &
49
+ test-data → auto-fix auto-fix
50
50
  ```
51
51
 
52
52
  Use AI commands (Claude Code or GitHub Copilot) to drive the workflow:
@@ -84,6 +84,17 @@ AI acts as a **Senior Developer**:
84
84
  3. Compiles Gherkin → Playwright `.spec.ts`
85
85
  4. Runs tests, auto-fixes selectors on failure (up to 5 attempts)
86
86
 
87
+ ### Step 4: Review test cases
88
+
89
+ | Claude Code | GitHub Copilot (VS Code) |
90
+ |---|---|
91
+ | `/sungen:review login` | `/sungen-review login` |
92
+
93
+ AI acts as a **Senior QA Reviewer**:
94
+ 1. Validates syntax against all Gherkin rules
95
+ 2. Scores coverage across viewpoint categories
96
+ 3. Can auto-fix issues when `--fix` flag is used
97
+
87
98
  ### Auth setup
88
99
 
89
100
  If any page requires authentication, the AI will ask you to **log in manually via the MCP browser** during Step 2 or Step 3. No separate auth command needed.
@@ -123,38 +134,85 @@ Add to `.vscode/settings.json` to auto-load Gherkin syntax when editing `.featur
123
134
  ### Syntax
124
135
 
125
136
  ```
126
- User <action> [<Target>] <type> with {{<Value>}}
137
+ [Keyword] User <action> [Target] <type> <in [Parent] parentType> <with {{value}}> <is state>
127
138
  ```
128
139
 
129
140
  - `[Target]` → selector reference → lookup in `selectors/*.yaml`
130
- - `{{Value}}` → test data reference → lookup in `test-data/*.yaml`
131
- - `<type>` → element type: button, link, field, heading, text, etc.
141
+ - `{{value}}` → test data reference → lookup in `test-data/*.yaml`
142
+ - `<type>` → element type: button, link, field, heading, text, table, etc.
143
+ - `in [Parent] parentType` → optional parent scope for disambiguation
132
144
 
133
145
  ### Key Patterns
134
146
 
135
147
  | Pattern | Example |
136
148
  |---|---|
137
149
  | Navigate | `User is on [login] page` |
150
+ | Navigate with data | `User is on [user detail] page with {{user_id}}` |
138
151
  | Click | `User click [Submit] button` |
139
152
  | Fill | `User fill [Email] field with {{email}}` |
140
- | Assert visible | `User see [Welcome] heading is visible` |
153
+ | Select | `User select [Country] dropdown with {{country}}` |
154
+ | Check | `User check [Remember me] checkbox` |
155
+ | Upload | `User fill [Avatar] uploader with {{file}}` |
156
+ | Clear | `User clear [Search] field` |
157
+ | Hover | `User hover [Info] icon` |
158
+ | Keyboard | `User press Enter on [Search] field` |
159
+ | Scroll | `User scroll to [Footer] section` |
160
+ | Assert visible | `User see [Welcome] heading` |
161
+ | Assert hidden | `User see [Error] message is hidden` |
141
162
  | Assert text | `User see [Title] heading with {{title}}` |
163
+ | Assert value | `User see [Email] field with {{email}}` |
142
164
  | Assert state | `User see [Submit] button is disabled` |
143
- | Wait for | `User wait for [Modal] dialog is visible` |
165
+ | Contains text | `User see [Message] text contains {{partial}}` |
166
+ | Wait for | `User wait for [Modal] dialog` |
144
167
  | Table row | `User see [Username] row in [Users] table with {{name}}` |
168
+ | Table cell | `User see [Status] column with {{status}}` (row scoped) |
145
169
  | Table column | `User see [Email] column in [Users] table` |
170
+ | Table count | `User see [Users] table with {{count}}` |
171
+ | Table empty | `User see [Users] table is empty` |
172
+ | Table action | `User click [Edit] button in [Users] table with {{name}}` |
173
+ | Table match | `User see [Users] table match data:` + inline DataTable |
174
+ | Alert | `User click [OK] alert` |
175
+ | Frame | `User switch to [Payment] frame` |
176
+
177
+ ### Table Match Data
178
+
179
+ Verify multiple rows exist using filter-based matching (resilient to data changes and row reordering):
180
+
181
+ ```gherkin
182
+ Then User see [Users] table match data:
183
+ | ID | Name | Status |
184
+ | {{id_1}} | {{name_1}} | {{status_1}} |
185
+ | {{id_2}} | {{name_2}} | {{status_2}} |
186
+ ```
187
+
188
+ First row = headers, remaining rows = expected data. For single-row verification + actions, use row scope patterns instead.
146
189
 
147
- States: `hidden` `visible` `disabled` `enabled` `checked` `unchecked` `focused` `empty` `loading` `selected`
190
+ ### States
191
+
192
+ `hidden` `visible` `disabled` `enabled` `checked` `unchecked` `focused` `empty` `loading` `selected` `sorted ascending` `sorted descending`
193
+
194
+ ### Element Types
195
+
196
+ | Group | Types |
197
+ |---|---|
198
+ | **Context** | `page` `dialog` `modal` `drawer` `tab` `alert` `overlay` `step` |
199
+ | **Input** | `field` `textarea` `search` `dropdown` `option` `checkbox` `radio` `toggle` `uploader` `slider` `date-picker` |
200
+ | **Trigger** | `button` `link` `icon` `menuitem` `tag` |
201
+ | **Data** | `table` `row` `column` `cell` `list` `item` `card` `section` |
202
+ | **Feedback** | `message` `header` `label` `text` `tooltip` `badge` `breadcrumb` `image` |
203
+ | **System** | `key` `frame` `spinner` `progressbar` |
148
204
 
149
205
  ### Tags
150
206
 
151
207
  | Tag | Purpose |
152
208
  |---|---|
209
+ | `@auto` | Standard scenario, ready for automation |
210
+ | `@manual` | Skip scenario in generation |
211
+ | `@smoke` / `@regression` | Test suite grouping |
153
212
  | `@auth:role` | Use Playwright storage state for auth |
154
213
  | `@no-auth` | Disable inherited auth for this scenario |
155
214
  | `@steps:name` | Define reusable step group |
156
215
  | `@extend:name` | Inherit steps from another scenario |
157
- | `@manual` | Skip scenario in generation |
158
216
 
159
217
  ### YAML Selector Keys
160
218
 
@@ -167,10 +225,15 @@ Keys are **lowercase with spaces**, Unicode preserved:
167
225
 
168
226
  ```yaml
169
227
  search:
170
- type: 'placeholder' # testid, role, placeholder, label, text, locator, page
228
+ type: 'placeholder' # testid, role, placeholder, label, text, locator, page, upload, frame
171
229
  value: 'Search users' # placeholder text, role name, CSS selector, etc.
172
230
  name: 'Search' # accessible name (for role type)
173
231
  nth: 0 # element index (for multiple matches)
232
+ exact: true # exact name matching
233
+ columns: # table column config (for table type)
234
+ status:
235
+ index: 2
236
+ header: Status
174
237
  ```
175
238
 
176
239
  Priority: `data-testid` > `role+name` > `placeholder` > `label` > `text` > `CSS locator`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sun-asterisk/sungen",
3
- "version": "2.4.0",
3
+ "version": "2.4.1",
4
4
  "description": "Deterministic E2E Test Compiler - Gherkin + Selectors → Playwright tests",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/cli/index.ts CHANGED
@@ -17,7 +17,7 @@ async function main() {
17
17
  program
18
18
  .name('sungen')
19
19
  .description('Deterministic E2E Test Compiler — Gherkin + Selectors → Playwright')
20
- .version('2.4.0');
20
+ .version('2.4.1');
21
21
 
22
22
  // Global options
23
23
  program
@@ -5,6 +5,15 @@ import fs from 'fs';
5
5
  import path from 'path';
6
6
  import { SelectorResolver } from '../test-generator/utils/selector-resolver';
7
7
 
8
+ export interface DataTableRow {
9
+ cells: string[]; // Cell values in order
10
+ }
11
+
12
+ export interface ParsedDataTable {
13
+ headers: string[]; // First row = column headers
14
+ rows: DataTableRow[]; // Remaining rows = data rows
15
+ }
16
+
8
17
  export interface ParsedStep {
9
18
  keyword: string; // Given, When, Then, And, But
10
19
  text: string; // Original step text
@@ -16,6 +25,7 @@ export interface ParsedStep {
16
25
  featurePath?: string; // NEW: Feature path for page type (e.g., "/", "/dashboard")
17
26
  parentRef?: string; // Parent scope reference: "in [Parent Name] parentType" → parentRef = "Parent Name"
18
27
  parentType?: string; // Parent scope type: table, list, section, dialog, form
28
+ dataTable?: ParsedDataTable; // Inline data table (Cucumber DataTable syntax)
19
29
  }
20
30
 
21
31
  export interface ParsedScenario {
@@ -172,6 +182,18 @@ export class GherkinParser {
172
182
  nth = SelectorResolver.extractNthFromStep(afterElement);
173
183
  }
174
184
 
185
+ // Extract inline data table (Cucumber DataTable syntax)
186
+ let dataTable: ParsedDataTable | undefined;
187
+ if (step.dataTable && step.dataTable.rows && step.dataTable.rows.length >= 2) {
188
+ const rows = step.dataTable.rows;
189
+ dataTable = {
190
+ headers: rows[0].cells.map((cell: any) => cell.value),
191
+ rows: rows.slice(1).map((row: any) => ({
192
+ cells: row.cells.map((cell: any) => cell.value),
193
+ })),
194
+ };
195
+ }
196
+
175
197
  return {
176
198
  keyword: step.keyword.trim(),
177
199
  text: step.text, // Preserve original text (with parent scoping) for pattern matching
@@ -182,6 +204,7 @@ export class GherkinParser {
182
204
  nth,
183
205
  parentRef,
184
206
  parentType,
207
+ dataTable,
185
208
  };
186
209
  }
187
210
 
@@ -0,0 +1,15 @@
1
+ {{~#if isGiven~}}
2
+ {
3
+ const rows = {{> locator}}.locator('tbody').getByRole('row');
4
+ {{#each assertions}}
5
+ {{this}}
6
+ {{/each~}}
7
+ }
8
+ {{~else~}}
9
+ {
10
+ const rows = {{> locator}}.locator('tbody').getByRole('row');
11
+ {{#each assertions}}
12
+ {{this}}
13
+ {{/each~}}
14
+ }
15
+ {{~/if}}
@@ -74,7 +74,8 @@ export class PatternRegistry {
74
74
  const resolved = pattern.resolver(step, context);
75
75
 
76
76
  // Auto-inject parent scoping if step has parentRef
77
- if (step.parentRef && step.parentType) {
77
+ // Skip for table-* patterns — they resolve the table name internally from step text
78
+ if (step.parentRef && step.parentType && !pattern.name.startsWith('table-')) {
78
79
  resolved.data.parentLocator = PatternRegistry.resolveParentLocator(
79
80
  step.parentRef, step.parentType, context
80
81
  );
@@ -2,7 +2,7 @@
2
2
  * Table Patterns
3
3
  * Handles: table row assertions, table cell lookups, actions in table rows
4
4
  *
5
- * Syntax (v2.3 final):
5
+ * Syntax (v2.4 final):
6
6
  * - User see [Col] column in [Table] table # column exists
7
7
  * - User see [Ref] row in [Table] table with {{value}} # row exists (enters row scope)
8
8
  * - User see [Ref] row in [Table] table with {{value}} is hidden # row hidden
@@ -10,6 +10,9 @@
10
10
  * - User see [Table] table is empty # empty table
11
11
  * - User see [Col] column with {{value}} # cell value (row scoped — handled in step-mapper)
12
12
  * - User click [Act] button in [Table] table with {{filter}} # action in row
13
+ * - User see [Table] table match data: # exact table match with inline DataTable
14
+ * | Header1 | Header2 |
15
+ * | {{value1}} | {{value2}} |
13
16
  */
14
17
 
15
18
  import { StepPattern, PatternContext } from './types';
@@ -219,4 +222,58 @@ export const tablePatterns: StepPattern[] = [
219
222
  },
220
223
  priority: 17,
221
224
  },
225
+
226
+ // "User see [Table] table match data:" — filter-based table match with inline DataTable
227
+ // First row = headers, remaining rows = expected data
228
+ // Uses filter({ hasText }) per row — resilient to data changes, row ordering, extra rows
229
+ {
230
+ name: 'table-match-data',
231
+ matcher: (step) => {
232
+ return step.elementType === 'table' &&
233
+ /\btable\s+match\s+data\b/i.test(step.text) &&
234
+ !!step.dataTable;
235
+ },
236
+ resolver: (step, context) => {
237
+ const resolved = context.selectorResolver.resolveSelector(
238
+ step.selectorRef!, context.featureName, 'table', step.nth
239
+ );
240
+
241
+ const dataTable = step.dataTable!;
242
+
243
+ // Resolve {{variable}} references in cell values
244
+ const resolvedRows = dataTable.rows.map(row => ({
245
+ cells: row.cells.map(cell => {
246
+ const varMatch = cell.match(/^\{\{([a-z0-9\-\.\_]+)\}\}$/i);
247
+ if (varMatch) {
248
+ return context.dataResolver.resolveData(varMatch[1], context.featureName);
249
+ }
250
+ return cell;
251
+ }),
252
+ }));
253
+
254
+ // Pre-compute filter-based assertion lines
255
+ // For each expected row: chain .filter({ hasText }) for all cell values, then assert visible
256
+ const assertions: string[] = [];
257
+ for (const row of resolvedRows) {
258
+ const filters = row.cells
259
+ .map(cell => `.filter({ hasText: '${cell.replace(/'/g, "\\'")}' })`)
260
+ .join('');
261
+ assertions.push(
262
+ `await expect(rows${filters}).toBeVisible();`
263
+ );
264
+ }
265
+
266
+ const isGiven = isGivenContext(context);
267
+ return {
268
+ templateName: 'table-match-data',
269
+ data: {
270
+ ...resolved,
271
+ assertions,
272
+ isGiven,
273
+ },
274
+ comment: `${isGiven ? 'Wait' : 'Assert'} ${step.selectorRef} table contains ${resolvedRows.length} expected row(s)`,
275
+ };
276
+ },
277
+ priority: 20, // Higher than other table patterns to match first
278
+ },
222
279
  ];