@sun-asterisk/sungen 2.4.0 → 2.4.2
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/dist/cli/index.js +1 -1
- package/dist/generators/gherkin-parser/index.d.ts +8 -0
- package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
- package/dist/generators/gherkin-parser/index.js +12 -0
- package/dist/generators/gherkin-parser/index.js.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-match-data.hbs +15 -0
- package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/index.js +2 -1
- package/dist/generators/test-generator/patterns/index.js.map +1 -1
- package/dist/generators/test-generator/patterns/table-patterns.d.ts +4 -1
- package/dist/generators/test-generator/patterns/table-patterns.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/table-patterns.js +49 -1
- package/dist/generators/test-generator/patterns/table-patterns.js.map +1 -1
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +19 -16
- package/dist/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md +5 -5
- package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +44 -1
- package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +46 -61
- package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +34 -1
- package/dist/orchestrator/templates/ai-instructions/claude-skill-viewpoint.md +3 -1
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +19 -16
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-error-mapping.md +5 -5
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +45 -2
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +46 -61
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +34 -1
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint.md +3 -1
- package/dist/orchestrator/templates/readme.md +82 -19
- package/package.json +1 -1
- package/src/cli/index.ts +1 -1
- package/src/generators/gherkin-parser/index.ts +23 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-match-data.hbs +15 -0
- package/src/generators/test-generator/patterns/index.ts +2 -1
- package/src/generators/test-generator/patterns/table-patterns.ts +58 -1
- package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +19 -16
- package/src/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md +5 -5
- package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +44 -1
- package/src/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +46 -61
- package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +34 -1
- package/src/orchestrator/templates/ai-instructions/claude-skill-viewpoint.md +3 -1
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +19 -16
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-error-mapping.md +5 -5
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +45 -2
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +46 -61
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +34 -1
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint.md +3 -1
- package/src/orchestrator/templates/readme.md +82 -19
|
@@ -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
|
-
|
|
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.
|
|
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
|
];
|
|
@@ -1,29 +1,32 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: run-test
|
|
3
|
-
description: '
|
|
3
|
+
description: 'Compile and run Playwright tests — auto-fixes selectors on failure'
|
|
4
4
|
argument-hint: [screen-name]
|
|
5
5
|
allowed-tools: Read, Grep, Bash, Glob, Edit, AskUserQuestion
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
## Role
|
|
9
9
|
|
|
10
|
-
You are a **Senior Developer
|
|
10
|
+
You are a **Senior Developer**. Use `sungen-selector-fix`, `sungen-selector-keys`, and `sungen-error-mapping` skills.
|
|
11
11
|
|
|
12
12
|
## Parameters
|
|
13
13
|
|
|
14
14
|
Parse **screen** from `$ARGUMENTS`. If missing, ask the user.
|
|
15
15
|
|
|
16
|
-
##
|
|
17
|
-
|
|
18
|
-
1. Verify `qa/screens/<screen>/` has `.feature` + `test-data.yaml
|
|
19
|
-
2.
|
|
20
|
-
3.
|
|
21
|
-
4.
|
|
22
|
-
5.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
16
|
+
## Phase 1: Compile & Run (no MCP)
|
|
17
|
+
|
|
18
|
+
1. Verify `qa/screens/<screen>/` has `.feature` + `test-data.yaml`
|
|
19
|
+
2. Ensure `selectors.yaml` has page selector. If missing, ask user for URL path
|
|
20
|
+
3. `sungen generate --screen <screen>`
|
|
21
|
+
4. `npx playwright test specs/generated/<screen>/*.spec.ts --reporter=line`
|
|
22
|
+
5. If all pass → done
|
|
23
|
+
|
|
24
|
+
## Phase 2: Targeted Fix (only if failures)
|
|
25
|
+
|
|
26
|
+
6. Parse failures → group by root cause
|
|
27
|
+
7. Navigate to page ONCE via MCP → ONE `browser_snapshot`
|
|
28
|
+
8. Fix broken selectors per `sungen-selector-fix` skill
|
|
29
|
+
9. Recompile → re-run only failing tests
|
|
30
|
+
10. Repeat up to 3 attempts
|
|
31
|
+
11. Final full run for regression check
|
|
32
|
+
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.
|
|
85
|
-
| `storageState` file not found |
|
|
86
|
-
| Most tests timeout on first step | Auth expired — re-
|
|
87
|
-
| Page shows home instead of target | SPA + expired auth. Re-
|
|
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
|
|
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
|
|
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`
|
|
@@ -240,3 +247,39 @@ Options: `nth` `exact` `scope` `match` `variant` `frame` `contenteditable` `colu
|
|
|
240
247
|
| Missing `is` for state | `with {{text}} hidden` | `with {{text}} is hidden` |
|
|
241
248
|
| State as value | `with {{disabled}}` | `is disabled` |
|
|
242
249
|
| Missing target type | `fill [email] with {{v}}` | `fill [email] field with {{v}}` |
|
|
250
|
+
| Using Background | `Background: Given User is on...` | Use `@steps` + `@extend` instead |
|
|
251
|
+
| `is on` after When | `When ... And User is on [X] dialog` | `And User see [X] dialog` or separate Given |
|
|
252
|
+
|
|
253
|
+
## Background vs @steps/@extend
|
|
254
|
+
|
|
255
|
+
**Do NOT use `Background:` block.** Use `@steps` + `@extend` instead.
|
|
256
|
+
|
|
257
|
+
**Why:**
|
|
258
|
+
- `Background` runs before EVERY scenario but cannot set dialog/frame scope correctly
|
|
259
|
+
- `Background` with `When` + `And User is on [X] dialog` creates keyword mismatch (`is on` = Given, not When)
|
|
260
|
+
- `@steps` + `@extend` gives explicit control: base steps run Given→When, extending scenario starts with `Given User is on [X] dialog` (correct scope setup)
|
|
261
|
+
|
|
262
|
+
**Wrong:**
|
|
263
|
+
```gherkin
|
|
264
|
+
Background:
|
|
265
|
+
Given User is on [Kudos] page
|
|
266
|
+
When User click [Open] button
|
|
267
|
+
And User is on [Modal] dialog ← keyword mismatch
|
|
268
|
+
|
|
269
|
+
Scenario: VP-UI-001 Title visible
|
|
270
|
+
Then User see [Title] heading
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
**Correct:**
|
|
274
|
+
```gherkin
|
|
275
|
+
@steps:open_modal
|
|
276
|
+
Scenario: Open modal
|
|
277
|
+
Given User is on [Kudos] page
|
|
278
|
+
When User click [Open] button
|
|
279
|
+
Then User see [Modal] dialog
|
|
280
|
+
|
|
281
|
+
@extend:open_modal
|
|
282
|
+
Scenario: VP-UI-001 Title visible
|
|
283
|
+
Given User is on [Modal] dialog
|
|
284
|
+
Then User see [Title] heading
|
|
285
|
+
```
|
|
@@ -1,41 +1,51 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sungen-selector-fix
|
|
3
|
-
description: 'Selector
|
|
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
|
-
##
|
|
7
|
+
## Strategy: Fix Only What Breaks
|
|
8
8
|
|
|
9
|
-
|
|
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:
|
|
15
|
+
## Step 1: Diagnose Failures
|
|
14
16
|
|
|
15
|
-
|
|
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
|
-
|
|
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:
|
|
33
|
+
## Step 2: Targeted MCP Exploration
|
|
25
34
|
|
|
26
|
-
|
|
35
|
+
Only when `test-results/` screenshots are insufficient:
|
|
27
36
|
|
|
28
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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 iframe → add `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
|
|
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:
|
|
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
|
|
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
|
|
125
|
+
If still failing after 3 attempts → ask user for guidance.
|
|
@@ -68,25 +68,38 @@ And User wait for [Page Title] heading is visible
|
|
|
68
68
|
|
|
69
69
|
**Feature file** — `qa/screens/<screen>/features/<screen>.feature`
|
|
70
70
|
|
|
71
|
+
**Never use `Background:`.** Use `@steps` + `@extend` for shared setup (see `sungen-gherkin-syntax` skill).
|
|
72
|
+
|
|
71
73
|
```gherkin
|
|
72
74
|
@auth:role
|
|
73
75
|
Feature: <Screen> Screen
|
|
74
76
|
|
|
77
|
+
# Shared setup — use @steps, not Background
|
|
78
|
+
@steps:open_form
|
|
79
|
+
Scenario: Open form
|
|
80
|
+
Given User is on [Screen] page
|
|
81
|
+
And User wait for [Screen Title] heading is visible
|
|
82
|
+
When User click [Create] button
|
|
83
|
+
Then User see [Form] dialog
|
|
84
|
+
|
|
75
85
|
# ============================================================
|
|
76
86
|
# Section: Create User Form
|
|
77
87
|
# ============================================================
|
|
78
88
|
|
|
79
89
|
# --- UI/UX ---
|
|
80
90
|
|
|
91
|
+
@extend:open_form
|
|
81
92
|
Scenario: VP-UI-001 Form displays all fields with correct defaults
|
|
82
|
-
Given User is on [
|
|
93
|
+
Given User is on [Form] dialog
|
|
83
94
|
Then User see [Name] field
|
|
84
95
|
And User see [Email] field
|
|
85
96
|
And User see [Submit] button is disabled
|
|
86
97
|
|
|
87
98
|
# --- Validation ---
|
|
88
99
|
|
|
100
|
+
@extend:open_form
|
|
89
101
|
Scenario: VP-VAL-001 Submit with all empty fields shows errors
|
|
102
|
+
Given User is on [Form] dialog
|
|
90
103
|
When User click [Submit] button
|
|
91
104
|
Then User see [Name error] message with {{name_required_error}}
|
|
92
105
|
|
|
@@ -100,8 +113,28 @@ Feature: <Screen> Screen
|
|
|
100
113
|
Then User see [Name] column in [Users] table
|
|
101
114
|
And User see [Email] column in [Users] table
|
|
102
115
|
And User see [Status] column in [Users] table
|
|
116
|
+
|
|
117
|
+
# --- Data & Validate ---
|
|
118
|
+
|
|
119
|
+
Scenario: VP-VAL-010 Table displays correct data
|
|
120
|
+
Then User see [Users] table match data:
|
|
121
|
+
| Name | Email | Status |
|
|
122
|
+
| {{name_1}} | {{email_1}} | {{status_1}} |
|
|
123
|
+
| {{name_2}} | {{email_2}} | {{status_2}} |
|
|
124
|
+
|
|
125
|
+
Scenario: VP-VAL-011 Edit button targets correct row
|
|
126
|
+
Given User see [Target] row in [Users] table with {{name_1}}
|
|
127
|
+
When User click [Edit] button in [Users] table with {{name_1}}
|
|
128
|
+
Then User see [Name] field with {{name_1}}
|
|
103
129
|
```
|
|
104
130
|
|
|
131
|
+
### When to use DataTable vs Row Scope
|
|
132
|
+
|
|
133
|
+
| Pattern | Use when |
|
|
134
|
+
|---|---|
|
|
135
|
+
| `table match data:` + DataTable | Verifying **multiple rows** exist with expected values |
|
|
136
|
+
| `row in [Table] table with {{v}}` + `column with {{v}}` | Checking **single row** details or **acting** on a row (click, edit) |
|
|
137
|
+
|
|
105
138
|
**Naming**: `VP-<CATEGORY>-<NNN>` prefix.
|
|
106
139
|
|
|
107
140
|
**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:
|
|
87
|
+
- Wildcards: `%, _, *` treated as normal text (escaped)
|
|
86
88
|
|
|
87
89
|
---
|
|
88
90
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sungen-run-test
|
|
3
|
-
description: '
|
|
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
|
|
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
|
-
##
|
|
20
|
-
|
|
21
|
-
1. Verify `qa/screens/${input:screen}/` has `.feature` + `test-data.yaml
|
|
22
|
-
2.
|
|
23
|
-
3.
|
|
24
|
-
4.
|
|
25
|
-
5.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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.
|
|
85
|
-
| `storageState` file not found |
|
|
86
|
-
| Most tests timeout on first step | Auth expired — re-
|
|
87
|
-
| Page shows home instead of target | SPA + expired auth. Re-
|
|
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
|
|
89
|
+
**Never use `sungen makeauth`.** Always let the user log in manually via the MCP browser.
|
|
90
90
|
|
|
91
91
|
---
|
|
92
92
|
|