@sun-asterisk/sungen 2.4.6 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/dist/cli/commands/generate.d.ts.map +1 -1
  2. package/dist/cli/commands/generate.js +2 -0
  3. package/dist/cli/commands/generate.js.map +1 -1
  4. package/dist/cli/index.js +1 -1
  5. package/dist/generators/gherkin-parser/index.d.ts +1 -0
  6. package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
  7. package/dist/generators/gherkin-parser/index.js +3 -0
  8. package/dist/generators/gherkin-parser/index.js.map +1 -1
  9. package/dist/generators/test-generator/adapters/adapter-interface.d.ts +29 -1
  10. package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
  11. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +21 -1
  12. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
  13. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js +11 -2
  14. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
  15. package/dist/generators/test-generator/adapters/playwright/templates/after-all.hbs +8 -0
  16. package/dist/generators/test-generator/adapters/playwright/templates/after-each.hbs +8 -0
  17. package/dist/generators/test-generator/adapters/playwright/templates/before-all.hbs +8 -0
  18. package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  19. package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +24 -0
  20. package/dist/generators/test-generator/code-generator.d.ts +2 -0
  21. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  22. package/dist/generators/test-generator/code-generator.js +109 -12
  23. package/dist/generators/test-generator/code-generator.js.map +1 -1
  24. package/dist/generators/test-generator/step-mapper.d.ts +1 -0
  25. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  26. package/dist/generators/test-generator/step-mapper.js +1 -1
  27. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  28. package/dist/generators/test-generator/template-engine.d.ts +29 -1
  29. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  30. package/dist/generators/test-generator/template-engine.js +11 -2
  31. package/dist/generators/test-generator/template-engine.js.map +1 -1
  32. package/dist/generators/test-generator/utils/data-resolver.d.ts +11 -2
  33. package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
  34. package/dist/generators/test-generator/utils/data-resolver.js +36 -25
  35. package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
  36. package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts +7 -0
  37. package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts.map +1 -0
  38. package/dist/generators/test-generator/utils/runtime-data-transformer.js +42 -0
  39. package/dist/generators/test-generator/utils/runtime-data-transformer.js.map +1 -0
  40. package/dist/generators/types.d.ts +1 -0
  41. package/dist/generators/types.d.ts.map +1 -1
  42. package/dist/generators/types.js.map +1 -1
  43. package/dist/orchestrator/project-initializer.d.ts +9 -0
  44. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  45. package/dist/orchestrator/project-initializer.js +74 -10
  46. package/dist/orchestrator/project-initializer.js.map +1 -1
  47. package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +1 -1
  48. package/dist/orchestrator/templates/ai-instructions/claude-config.md +11 -2
  49. package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +66 -13
  50. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +54 -3
  51. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +1 -1
  52. package/dist/orchestrator/templates/ai-instructions/copilot-config.md +11 -2
  53. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +86 -13
  54. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +54 -3
  55. package/dist/orchestrator/templates/specs-base.d.ts +12 -1
  56. package/dist/orchestrator/templates/specs-base.d.ts.map +1 -1
  57. package/dist/orchestrator/templates/specs-base.js +47 -5
  58. package/dist/orchestrator/templates/specs-base.js.map +1 -1
  59. package/dist/orchestrator/templates/specs-base.ts +65 -7
  60. package/dist/orchestrator/templates/specs-test-data.d.ts +14 -0
  61. package/dist/orchestrator/templates/specs-test-data.d.ts.map +1 -0
  62. package/dist/orchestrator/templates/specs-test-data.js +100 -0
  63. package/dist/orchestrator/templates/specs-test-data.js.map +1 -0
  64. package/dist/orchestrator/templates/specs-test-data.ts +66 -0
  65. package/package.json +1 -1
  66. package/src/cli/commands/generate.ts +2 -0
  67. package/src/cli/index.ts +1 -1
  68. package/src/generators/gherkin-parser/index.ts +4 -0
  69. package/src/generators/test-generator/adapters/adapter-interface.ts +12 -1
  70. package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +14 -2
  71. package/src/generators/test-generator/adapters/playwright/templates/after-all.hbs +8 -0
  72. package/src/generators/test-generator/adapters/playwright/templates/after-each.hbs +8 -0
  73. package/src/generators/test-generator/adapters/playwright/templates/before-all.hbs +8 -0
  74. package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  75. package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +24 -0
  76. package/src/generators/test-generator/code-generator.ts +122 -13
  77. package/src/generators/test-generator/step-mapper.ts +2 -2
  78. package/src/generators/test-generator/template-engine.ts +28 -2
  79. package/src/generators/test-generator/utils/data-resolver.ts +45 -27
  80. package/src/generators/test-generator/utils/runtime-data-transformer.ts +51 -0
  81. package/src/generators/types.ts +1 -0
  82. package/src/orchestrator/project-initializer.ts +84 -10
  83. package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +1 -1
  84. package/src/orchestrator/templates/ai-instructions/claude-config.md +11 -2
  85. package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +66 -13
  86. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +54 -3
  87. package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +1 -1
  88. package/src/orchestrator/templates/ai-instructions/copilot-config.md +11 -2
  89. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +86 -13
  90. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +54 -3
  91. package/src/orchestrator/templates/specs-base.ts +65 -7
  92. package/src/orchestrator/templates/specs-test-data.ts +66 -0
@@ -40,7 +40,9 @@ After each command completes, use `AskUserQuestion` to present the next actions
40
40
  qa/screens/<screen-name>/
41
41
  ├── features/<screen>.feature # Gherkin scenarios
42
42
  ├── selectors/<screen>.yaml # Element locators (generated during run-test)
43
- ├── test-data/<screen>.yaml # Test data variables
43
+ ├── test-data/<screen>.yaml # Test data variables (loaded at runtime)
44
+ ├── test-data/<screen>.staging.yaml # Environment override (optional)
45
+ ├── test-data/<screen>.production.yaml # Environment override (optional)
44
46
  └── requirements/
45
47
  ├── spec.md # Screen specification (primary source)
46
48
  └── ui/ # Screenshots, mockups
@@ -49,12 +51,19 @@ qa/deliverables/<screen>-testcases.csv # Exported test cases (from /sungen:deli
49
51
  qa/deliverables/<screen>-testcases.xlsx # Styled workbook for client hand-off
50
52
  ```
51
53
 
54
+ ## Test Data
55
+
56
+ `{{variable}}` references in `.feature` map to keys in `test-data/<screen>.yaml`. Data is loaded **at runtime** — the same generated `.spec.ts` works across environments without recompiling.
57
+
58
+ **Environment overrides**: `SUNGEN_ENV=staging npx playwright test` merges `<screen>.staging.yaml` on top of `<screen>.yaml`. Create `<screen>.<env>.yaml` for environment-specific values (different credentials, URLs, test users).
59
+
52
60
  ## CLI Commands
53
61
 
54
62
  ```bash
55
63
  sungen add --screen <name> --path <url-path> # Scaffold screen directories
56
64
  sungen add --screen <name> --path <path> --feature <name> # Scaffold with sub-feature
57
- sungen generate --screen <name> # Compile .feature → .spec.ts
65
+ sungen generate --screen <name> # Compile .feature → .spec.ts (runtime data)
66
+ sungen generate --screen <name> --inline-data # Compile with hardcoded data (legacy)
58
67
  sungen generate --all # Compile all screens
59
68
  sungen delivery # Export all screens → CSV + XLSX
60
69
  sungen delivery <screen> # Export a single screen
@@ -151,6 +151,14 @@ Options: `nth` `exact` `scope` `match` `variant` `frame` `contenteditable` `colu
151
151
  | `@no-auth` | Disable inherited auth |
152
152
  | `@steps:name` | Define reusable step block (base scenario) |
153
153
  | `@extend:name` | Prepend Given→When from @steps block (skip Then) |
154
+ | `@cleanup:overlay` | Auto-cleanup: dismiss dialogs/overlays after each test (base.ts fixture) |
155
+ | `@cleanup:forms` | Auto-cleanup: clear form fields after each test (base.ts fixture) |
156
+ | `@cleanup:scroll` | Auto-cleanup: scroll to top after each test (base.ts fixture) |
157
+ | `@cleanup:storage` | Auto-cleanup: clear sessionStorage after each test (base.ts fixture) |
158
+ | `@screenshot:on-failure` | Auto-capture screenshot when test fails (base.ts fixture) |
159
+ | `@beforeAll` | Hook: runs once before all tests → `test.beforeAll()` |
160
+ | `@afterEach` | Hook: runs after each test → `test.afterEach()` (custom cleanup) |
161
+ | `@afterAll` | Hook: runs once after all tests → `test.afterAll()` |
154
162
 
155
163
  ### @extend behavior
156
164
 
@@ -175,30 +183,28 @@ Options: `nth` `exact` `scope` `match` `variant` `frame` `contenteditable` `colu
175
183
  | Missing `is` for state | `with {{text}} hidden` | `with {{text}} is hidden` |
176
184
  | State as value | `with {{disabled}}` | `is disabled` |
177
185
  | Missing target type | `fill [email] with {{v}}` | `fill [email] field with {{v}}` |
178
- | Using Background | `Background: Given User is on...` | Use `@steps` + `@extend` instead |
186
+ | Background with scope | `Background: ... And User is on [X] dialog` | Use `@steps` + `@extend` for scope-dependent flows |
179
187
  | `is on` after When | `When ... And User is on [X] dialog` | `And User see [X] dialog` or separate Given |
180
188
 
181
189
  ## Background vs @steps/@extend
182
190
 
183
- **Do NOT use `Background:` block.** Use `@steps` + `@extend` instead.
191
+ Both `Background` and `@steps`/`@extend` are valid — they serve different purposes.
184
192
 
185
- **Why:**
186
- - `Background` runs before EVERY scenario but cannot set dialog/frame scope correctly
187
- - `Background` with `When` + `And User is on [X] dialog` creates keyword mismatch (`is on` = Given, not When)
188
- - `@steps` + `@extend` gives explicit control: base steps run Given→When, extending scenario starts with `Given User is on [X] dialog` (correct scope setup)
193
+ | Pattern | Use when | Generates |
194
+ |---|---|---|
195
+ | `Background` | Simple shared setup (navigate to page) | `test.beforeEach()` |
196
+ | `@steps`/`@extend` | Complex reusable flows with scope (dialog, frame) | Inline merged steps in `test()` |
189
197
 
190
- **Wrong:**
198
+ **Use `Background` for simple navigation:**
191
199
  ```gherkin
192
200
  Background:
193
- Given User is on [Kudos] page
194
- When User click [Open] button
195
- And User is on [Modal] dialog ← keyword mismatch
201
+ Given User is on [Dashboard] page
196
202
 
197
- Scenario: VP-UI-001 Title visible
198
- Then User see [Title] heading
203
+ Scenario: View stats
204
+ Then User see [Revenue Chart] section
199
205
  ```
200
206
 
201
- **Correct:**
207
+ **Use `@steps`/`@extend` when scope matters (dialog, frame):**
202
208
  ```gherkin
203
209
  @steps:open_modal
204
210
  Scenario: Open modal
@@ -211,3 +217,50 @@ Scenario: VP-UI-001 Title visible
211
217
  Given User is on [Modal] dialog
212
218
  Then User see [Title] heading
213
219
  ```
220
+
221
+ **Avoid `Background` with scope-dependent steps** — `When` + `And User is on [X] dialog` creates keyword mismatch (`is on` = Given, not When). Use `@steps`/`@extend` instead.
222
+
223
+ ## Hooks & Cleanup
224
+
225
+ Two layers for test lifecycle management. Prefer `@cleanup:*` tags (Layer 1) — they work with base.ts automatically. Use hook scenarios (Layer 2) only for custom logic.
226
+
227
+ ### Layer 1: `@cleanup:*` tags (automatic via base.ts)
228
+
229
+ Feature-level tags that activate cleanup fixtures in base.ts. No Gherkin steps needed.
230
+
231
+ ```gherkin
232
+ @auth:admin
233
+ @cleanup:overlay
234
+ @cleanup:forms
235
+ Feature: User Management
236
+ Path: /users
237
+
238
+ Background:
239
+ Given User is on [User Management] page
240
+
241
+ Scenario: Create user shows form
242
+ When User click [Add User] button
243
+ Then User see [Create User] dialog
244
+
245
+ Scenario: Search user by name
246
+ When User fill [Search] field with {{search_name}}
247
+ Then User see [User Row] row
248
+ ```
249
+
250
+ | Tag | What base.ts does after each test |
251
+ |---|---|
252
+ | `@cleanup:overlay` | Press Escape, click body, dismiss fixed overlays |
253
+ | `@cleanup:forms` | Clear all input/textarea fields, reset selects |
254
+ | `@cleanup:scroll` | Scroll to top of page |
255
+ | `@cleanup:storage` | Clear sessionStorage |
256
+
257
+ ### Layer 2: `@afterEach` scenario (custom cleanup)
258
+
259
+ Only when `@cleanup:*` tags aren't enough — feature-specific logic.
260
+
261
+ ### Layer 3: `@beforeAll` / `@afterAll` (optional)
262
+
263
+ For one-time setup/teardown.
264
+
265
+ **Rendering order in `.spec.ts`:**
266
+ `test.describe` → `test.use(storageState)` → `test.use(autoCleanup)` → `test.beforeAll` → `test.beforeEach` → `test.afterEach` → `test.afterAll` → `test()` blocks
@@ -112,20 +112,59 @@ Given User is on [Screen] page
112
112
  And User wait for [Page Title] heading is visible
113
113
  ```
114
114
 
115
+ ## Cleanup & Hooks
116
+
117
+ ### Auto-assign `@cleanup:*` tags based on screen sections
118
+
119
+ After identifying screen sections, add appropriate `@cleanup:*` feature-level tags. These activate base.ts fixtures that auto-clean state between tests.
120
+
121
+ | Screen has | Add tag | Why |
122
+ |---|---|---|
123
+ | Modal / Dialog / Drawer | `@cleanup:overlay` | Dismiss leftover overlays between tests |
124
+ | Form & Inputs / Search / Filter | `@cleanup:forms` | Clear form fields, reset selects |
125
+ | Long scrollable content | `@cleanup:scroll` | Scroll to top for consistent assertions |
126
+ | Auth tokens / session data in tests | `@cleanup:storage` | Clear sessionStorage |
127
+ | CI/CD or debug-heavy screens | `@screenshot:on-failure` | Auto-capture screenshot on test failure |
128
+
129
+ **Always add `@cleanup:overlay`** if ANY section opens a dialog (Create/Add, Update/Edit, Delete confirmation). Most CRUD screens need it.
130
+
131
+ **Always add `@cleanup:forms`** if the screen has inline search, filter dropdowns, or editable forms that persist between tests.
132
+
133
+ ### When to add `@afterEach` hook scenario
134
+
135
+ Only when `@cleanup:*` tags aren't enough — feature-specific cleanup logic:
136
+ - Reset a dropdown filter to default value (not just clear)
137
+ - Navigate away from a sub-tab back to the main tab
138
+ - Close a specific sidebar panel
139
+
140
+ ```gherkin
141
+ @afterEach
142
+ Scenario: Reset filters to default
143
+ When User select [Status Filter] dropdown with {{default_status}}
144
+ ```
145
+
146
+ ### `@beforeAll` / `@afterAll` — optional, low priority
147
+
148
+ For one-time setup/teardown. Most screens don't need these.
149
+
115
150
  ## Output Format
116
151
 
117
152
  **Feature file** — `qa/screens/<screen>/features/<screen>.feature`
118
153
 
119
- **Never use `Background:`.** Use `@steps` + `@extend` for shared setup (see `sungen-gherkin-syntax` skill).
154
+ `Background` is valid for simple shared setup (navigate to page). Use `@steps`/`@extend` for complex flows with scope (dialog, frame).
120
155
 
121
156
  ```gherkin
122
157
  @auth:role
158
+ @cleanup:overlay
159
+ @cleanup:forms
123
160
  Feature: <Screen> Screen
124
161
 
162
+ Background:
163
+ Given User is on [Screen] page
164
+
125
165
  # Shared setup — NO priority tag on @steps
126
166
  @steps:open_form
127
167
  Scenario: Open form
128
- Given User is on [Screen] page
129
168
  When User click [Create] button
130
169
  Then User see [Form] dialog
131
170
 
@@ -165,6 +204,18 @@ Feature: <Screen> Screen
165
204
 
166
205
  **Naming**: `VP-<CATEGORY>-<NNN>` prefix. Scenario name must use the **same element type** as the steps — e.g., if the step uses `dialog`, write "dialog opens" not "modal opens".
167
206
 
168
- **Test data** — `qa/screens/<screen>/test-data/<screen>.yaml`, grouped by section.
207
+ **Test data** — `qa/screens/<screen>/test-data/<screen>.yaml`, grouped by section. Data is loaded **at runtime** — keys become runtime lookups, not hardcoded strings. The same compiled test works across environments.
208
+
209
+ **Environment-specific data**: For values that differ per environment (credentials, URLs, test users), create `<screen>.<env>.yaml` alongside the base file. Users run `SUNGEN_ENV=staging npx playwright test` to merge overrides. Structure env YAML with the same keys, only including values that change:
210
+
211
+ ```yaml
212
+ # login.yaml (base)
213
+ valid_email: admin@dev.example.com
214
+ valid_password: DevPass123
215
+
216
+ # login.staging.yaml (override for staging)
217
+ valid_email: admin@staging.example.com
218
+ valid_password: StagingPass456
219
+ ```
169
220
 
170
221
  **Do NOT generate**: `selectors.yaml` (created during run-test), Playwright code (sungen compiles).
@@ -18,7 +18,7 @@ Parse **screen** from `$ARGUMENTS`. If missing, ask the user.
18
18
  1. Verify `qa/screens/<screen>/` has `.feature` + `test-data.yaml`.
19
19
  2. **Phase 0 — Selector Pre-gen**: if `selectors.yaml` is missing/empty or doesn't cover the feature file's `[Reference]`s, run Phase 0 from `sungen-selector-fix` — confirm with user, `browser_navigate` → one `browser_snapshot` → merge YAML entries.
20
20
  3. **Phase 0.5 — Auth Persistence**: if the feature has `@auth:<role>` tags and `specs/.auth/<role>.json` is missing/expired, run Phase 0.5 from `sungen-selector-fix` — user logs in manually in MCP browser → `browser_storage_state` → `specs/.auth/<role>.json`. Offer `sungen makeauth <role>` as CLI fallback only if `browser_storage_state` isn't available in this MCP version.
21
- 4. Compile: `sungen generate --screen <screen>`.
21
+ 4. Compile: `sungen generate --screen <screen>` (default: runtime data loading from YAML). Use `--inline-data` only if user requests compile-time hardcoded values.
22
22
 
23
23
  ## Run & Fix (phased — per `sungen-selector-fix` skill)
24
24
 
@@ -40,7 +40,9 @@ After each command completes, present the next actions as selectable options. Ne
40
40
  qa/screens/<screen-name>/
41
41
  ├── features/<screen>.feature # Gherkin scenarios
42
42
  ├── selectors/<screen>.yaml # Element locators (generated during run-test)
43
- ├── test-data/<screen>.yaml # Test data variables
43
+ ├── test-data/<screen>.yaml # Test data variables (loaded at runtime)
44
+ ├── test-data/<screen>.staging.yaml # Environment override (optional)
45
+ ├── test-data/<screen>.production.yaml # Environment override (optional)
44
46
  └── requirements/
45
47
  ├── spec.md # Screen specification (primary source)
46
48
  └── ui/ # Screenshots, mockups
@@ -49,12 +51,19 @@ qa/deliverables/<screen>-testcases.csv # Exported test cases (from /sungen-deli
49
51
  qa/deliverables/<screen>-testcases.xlsx # Styled workbook for client hand-off
50
52
  ```
51
53
 
54
+ ## Test Data
55
+
56
+ `{{variable}}` references in `.feature` map to keys in `test-data/<screen>.yaml`. Data is loaded **at runtime** — the same generated `.spec.ts` works across environments without recompiling.
57
+
58
+ **Environment overrides**: `SUNGEN_ENV=staging npx playwright test` merges `<screen>.staging.yaml` on top of `<screen>.yaml`. Create `<screen>.<env>.yaml` for environment-specific values (different credentials, URLs, test users).
59
+
52
60
  ## CLI Commands
53
61
 
54
62
  ```bash
55
63
  sungen add --screen <name> --path <url-path> # Scaffold screen directories
56
64
  sungen add --screen <name> --path <path> --feature <name> # Scaffold with sub-feature
57
- sungen generate --screen <name> # Compile .feature → .spec.ts
65
+ sungen generate --screen <name> # Compile .feature → .spec.ts (runtime data)
66
+ sungen generate --screen <name> --inline-data # Compile with hardcoded data (legacy)
58
67
  sungen generate --all # Compile all screens
59
68
  sungen delivery # Export all screens → CSV + XLSX
60
69
  sungen delivery <screen> # Export a single screen
@@ -151,6 +151,14 @@ Options: `nth` `exact` `scope` `match` `variant` `frame` `contenteditable` `colu
151
151
  | `@no-auth` | Disable inherited auth |
152
152
  | `@steps:name` | Define reusable step block (base scenario) |
153
153
  | `@extend:name` | Prepend Given→When from @steps block (skip Then) |
154
+ | `@cleanup:overlay` | Auto-cleanup: dismiss dialogs/overlays after each test (base.ts fixture) |
155
+ | `@cleanup:forms` | Auto-cleanup: clear form fields after each test (base.ts fixture) |
156
+ | `@cleanup:scroll` | Auto-cleanup: scroll to top after each test (base.ts fixture) |
157
+ | `@cleanup:storage` | Auto-cleanup: clear sessionStorage after each test (base.ts fixture) |
158
+ | `@screenshot:on-failure` | Auto-capture screenshot when test fails (base.ts fixture) |
159
+ | `@beforeAll` | Hook: runs once before all tests → `test.beforeAll()` |
160
+ | `@afterEach` | Hook: runs after each test → `test.afterEach()` (custom cleanup) |
161
+ | `@afterAll` | Hook: runs once after all tests → `test.afterAll()` |
154
162
 
155
163
  ### @extend behavior
156
164
 
@@ -175,30 +183,28 @@ Options: `nth` `exact` `scope` `match` `variant` `frame` `contenteditable` `colu
175
183
  | Missing `is` for state | `with {{text}} hidden` | `with {{text}} is hidden` |
176
184
  | State as value | `with {{disabled}}` | `is disabled` |
177
185
  | Missing target type | `fill [email] with {{v}}` | `fill [email] field with {{v}}` |
178
- | Using Background | `Background: Given User is on...` | Use `@steps` + `@extend` instead |
186
+ | Background with scope | `Background: ... And User is on [X] dialog` | Use `@steps` + `@extend` for scope-dependent flows |
179
187
  | `is on` after When | `When ... And User is on [X] dialog` | `And User see [X] dialog` or separate Given |
180
188
 
181
189
  ## Background vs @steps/@extend
182
190
 
183
- **Do NOT use `Background:` block.** Use `@steps` + `@extend` instead.
191
+ Both `Background` and `@steps`/`@extend` are valid — they serve different purposes.
184
192
 
185
- **Why:**
186
- - `Background` runs before EVERY scenario but cannot set dialog/frame scope correctly
187
- - `Background` with `When` + `And User is on [X] dialog` creates keyword mismatch (`is on` = Given, not When)
188
- - `@steps` + `@extend` gives explicit control: base steps run Given→When, extending scenario starts with `Given User is on [X] dialog` (correct scope setup)
193
+ | Pattern | Use when | Generates |
194
+ |---|---|---|
195
+ | `Background` | Simple shared setup (navigate to page) | `test.beforeEach()` |
196
+ | `@steps`/`@extend` | Complex reusable flows with scope (dialog, frame) | Inline merged steps in `test()` |
189
197
 
190
- **Wrong:**
198
+ **Use `Background` for simple navigation:**
191
199
  ```gherkin
192
200
  Background:
193
- Given User is on [Kudos] page
194
- When User click [Open] button
195
- And User is on [Modal] dialog ← keyword mismatch
201
+ Given User is on [Dashboard] page
196
202
 
197
- Scenario: VP-UI-001 Title visible
198
- Then User see [Title] heading
203
+ Scenario: View stats
204
+ Then User see [Revenue Chart] section
199
205
  ```
200
206
 
201
- **Correct:**
207
+ **Use `@steps`/`@extend` when scope matters (dialog, frame):**
202
208
  ```gherkin
203
209
  @steps:open_modal
204
210
  Scenario: Open modal
@@ -211,3 +217,70 @@ Scenario: VP-UI-001 Title visible
211
217
  Given User is on [Modal] dialog
212
218
  Then User see [Title] heading
213
219
  ```
220
+
221
+ **Avoid `Background` with scope-dependent steps** — `When` + `And User is on [X] dialog` creates keyword mismatch (`is on` = Given, not When). Use `@steps`/`@extend` instead.
222
+
223
+ ## Hooks & Cleanup
224
+
225
+ Two layers for test lifecycle management. Prefer `@cleanup:*` tags (Layer 1) — they work with base.ts automatically. Use hook scenarios (Layer 2) only for custom logic.
226
+
227
+ ### Layer 1: `@cleanup:*` tags (automatic via base.ts)
228
+
229
+ Feature-level tags that activate cleanup fixtures in base.ts. No Gherkin steps needed.
230
+
231
+ ```gherkin
232
+ @auth:admin
233
+ @cleanup:overlay
234
+ @cleanup:forms
235
+ Feature: User Management
236
+ Path: /users
237
+
238
+ Background:
239
+ Given User is on [User Management] page
240
+
241
+ Scenario: Create user shows form
242
+ When User click [Add User] button
243
+ Then User see [Create User] dialog
244
+ # After test: overlay auto-dismissed, forms auto-cleared by base.ts
245
+
246
+ Scenario: Search user by name
247
+ When User fill [Search] field with {{search_name}}
248
+ Then User see [User Row] row
249
+ # After test: search field auto-cleared by base.ts
250
+ ```
251
+
252
+ | Tag | What base.ts does after each test |
253
+ |---|---|
254
+ | `@cleanup:overlay` | Press Escape, click body, dismiss fixed overlays |
255
+ | `@cleanup:forms` | Clear all input/textarea fields, reset selects |
256
+ | `@cleanup:scroll` | Scroll to top of page |
257
+ | `@cleanup:storage` | Clear sessionStorage |
258
+
259
+ ### Layer 2: `@afterEach` scenario (custom cleanup)
260
+
261
+ Only when `@cleanup:*` tags aren't enough — feature-specific logic.
262
+
263
+ ```gherkin
264
+ @auth:admin
265
+ @cleanup:overlay
266
+ Feature: Dashboard
267
+ Path: /dashboard
268
+
269
+ Background:
270
+ Given User is on [Dashboard] page
271
+
272
+ @afterEach
273
+ Scenario: Reset dashboard filters
274
+ When User select [Date Filter] dropdown with {{default_period}}
275
+
276
+ Scenario: Filter by last week
277
+ When User select [Date Filter] dropdown with {{last_week}}
278
+ Then User see [Revenue Chart] section
279
+ ```
280
+
281
+ ### Layer 3: `@beforeAll` / `@afterAll` (optional)
282
+
283
+ For one-time setup/teardown. Low priority — most e2e tests don't need these.
284
+
285
+ **Rendering order in `.spec.ts`:**
286
+ `test.describe` → `test.use(storageState)` → `test.use(autoCleanup)` → `test.beforeAll` → `test.beforeEach` → `test.afterEach` → `test.afterAll` → `test()` blocks
@@ -112,20 +112,59 @@ Given User is on [Screen] page
112
112
  And User wait for [Page Title] heading is visible
113
113
  ```
114
114
 
115
+ ## Cleanup & Hooks
116
+
117
+ ### Auto-assign `@cleanup:*` tags based on screen sections
118
+
119
+ After identifying screen sections, add appropriate `@cleanup:*` feature-level tags. These activate base.ts fixtures that auto-clean state between tests.
120
+
121
+ | Screen has | Add tag | Why |
122
+ |---|---|---|
123
+ | Modal / Dialog / Drawer | `@cleanup:overlay` | Dismiss leftover overlays between tests |
124
+ | Form & Inputs / Search / Filter | `@cleanup:forms` | Clear form fields, reset selects |
125
+ | Long scrollable content | `@cleanup:scroll` | Scroll to top for consistent assertions |
126
+ | Auth tokens / session data in tests | `@cleanup:storage` | Clear sessionStorage |
127
+ | CI/CD or debug-heavy screens | `@screenshot:on-failure` | Auto-capture screenshot on test failure |
128
+
129
+ **Always add `@cleanup:overlay`** if ANY section opens a dialog (Create/Add, Update/Edit, Delete confirmation). Most CRUD screens need it.
130
+
131
+ **Always add `@cleanup:forms`** if the screen has inline search, filter dropdowns, or editable forms that persist between tests.
132
+
133
+ ### When to add `@afterEach` hook scenario
134
+
135
+ Only when `@cleanup:*` tags aren't enough — feature-specific cleanup logic:
136
+ - Reset a dropdown filter to default value (not just clear)
137
+ - Navigate away from a sub-tab back to the main tab
138
+ - Close a specific sidebar panel
139
+
140
+ ```gherkin
141
+ @afterEach
142
+ Scenario: Reset filters to default
143
+ When User select [Status Filter] dropdown with {{default_status}}
144
+ ```
145
+
146
+ ### `@beforeAll` / `@afterAll` — optional, low priority
147
+
148
+ For one-time setup/teardown. Most screens don't need these.
149
+
115
150
  ## Output Format
116
151
 
117
152
  **Feature file** — `qa/screens/<screen>/features/<screen>.feature`
118
153
 
119
- **Never use `Background:`.** Use `@steps` + `@extend` for shared setup (see `sungen-gherkin-syntax` skill).
154
+ `Background` is valid for simple shared setup (navigate to page). Use `@steps`/`@extend` for complex flows with scope (dialog, frame).
120
155
 
121
156
  ```gherkin
122
157
  @auth:role
158
+ @cleanup:overlay
159
+ @cleanup:forms
123
160
  Feature: <Screen> Screen
124
161
 
162
+ Background:
163
+ Given User is on [Screen] page
164
+
125
165
  # Shared setup — NO priority tag on @steps
126
166
  @steps:open_form
127
167
  Scenario: Open form
128
- Given User is on [Screen] page
129
168
  When User click [Create] button
130
169
  Then User see [Form] dialog
131
170
 
@@ -171,6 +210,18 @@ Feature: <Screen> Screen
171
210
 
172
211
  **Naming**: `VP-<CATEGORY>-<NNN>` prefix. Scenario name must use the **same element type** as the steps — e.g., if the step uses `dialog`, write "dialog opens" not "modal opens".
173
212
 
174
- **Test data** — `qa/screens/<screen>/test-data/<screen>.yaml`, grouped by section.
213
+ **Test data** — `qa/screens/<screen>/test-data/<screen>.yaml`, grouped by section. Data is loaded **at runtime** — keys become runtime lookups, not hardcoded strings. The same compiled test works across environments.
214
+
215
+ **Environment-specific data**: For values that differ per environment (credentials, URLs, test users), create `<screen>.<env>.yaml` alongside the base file. Users run `SUNGEN_ENV=staging npx playwright test` to merge overrides. Structure env YAML with the same keys, only including values that change:
216
+
217
+ ```yaml
218
+ # login.yaml (base)
219
+ valid_email: admin@dev.example.com
220
+ valid_password: DevPass123
221
+
222
+ # login.staging.yaml (override for staging)
223
+ valid_email: admin@staging.example.com
224
+ valid_password: StagingPass456
225
+ ```
175
226
 
176
227
  **Do NOT generate**: `selectors.yaml` (created during run-test), Playwright code (sungen compiles).
@@ -1,12 +1,27 @@
1
1
  import { test as base, expect } from '@playwright/test';
2
2
  import type { BrowserContext, Page } from '@playwright/test';
3
3
 
4
+ type CleanupConfig = {
5
+ overlay?: boolean;
6
+ forms?: boolean;
7
+ scroll?: boolean;
8
+ storage?: boolean;
9
+ };
10
+
4
11
  // Share one context per storageState — avoids creating multiple sessions
5
12
  // that trigger server rate limiting or session invalidation
6
13
  const contextCache = new Map<string, { context: BrowserContext; page: Page }>();
7
14
  const GOTO_PATCHED = Symbol('goto-patched');
8
15
 
9
- const test = base.extend({
16
+ const test = base.extend<{
17
+ autoCleanup: CleanupConfig;
18
+ screenshotOnFailure: boolean;
19
+ _autoCleanup: void;
20
+ _autoScreenshot: void;
21
+ }>({
22
+ autoCleanup: [{}, { option: true }],
23
+ screenshotOnFailure: [false, { option: true }],
24
+
10
25
  page: async ({ browser, storageState }, use) => {
11
26
  if (storageState) {
12
27
  const cacheKey = typeof storageState === 'string' ? storageState : JSON.stringify(storageState);
@@ -28,12 +43,6 @@ const test = base.extend({
28
43
  try {
29
44
  const currentPath = new URL(page.url()).pathname;
30
45
  if (currentPath === url || currentPath === url + '/') {
31
- // Dismiss any open overlays (dropdowns, dialogs) from previous test
32
- await page.keyboard.press('Escape').catch(() => {});
33
- await page.locator('body').click({ position: { x: 1, y: 1 }, force: true }).catch(() => {});
34
-
35
- // Safety check: if a fixed-position overlay (modal/dialog) is still present, full reload
36
- // eslint-disable-next-line no-eval -- runs in browser context via Playwright
37
46
  const hasOverlay = await page.evaluate(`(() => {
38
47
  const el = document.elementFromPoint(window.innerWidth / 2, window.innerHeight / 2);
39
48
  if (!el) return false;
@@ -68,6 +77,55 @@ const test = base.extend({
68
77
  await context.close();
69
78
  }
70
79
  },
80
+
81
+ // Auto-cleanup fixture: runs teardown after each test based on @cleanup:* tags
82
+ _autoCleanup: [async ({ page, autoCleanup }, use) => {
83
+ await use();
84
+
85
+ if (autoCleanup.overlay) {
86
+ await page.keyboard.press('Escape').catch(() => {});
87
+ await page.locator('body').click({ position: { x: 1, y: 1 }, force: true }).catch(() => {});
88
+ // Dismiss persistent fixed overlays (modals, dialogs)
89
+ const hasOverlay = await page.evaluate(`(() => {
90
+ const el = document.elementFromPoint(window.innerWidth / 2, window.innerHeight / 2);
91
+ if (!el) return false;
92
+ let current = el;
93
+ while (current && current !== document.body) {
94
+ if (getComputedStyle(current).position === 'fixed') return true;
95
+ current = current.parentElement;
96
+ }
97
+ return false;
98
+ })()`).catch(() => false);
99
+ if (hasOverlay) {
100
+ await page.keyboard.press('Escape').catch(() => {});
101
+ }
102
+ }
103
+ if (autoCleanup.forms) {
104
+ await page.evaluate(`(() => {
105
+ document.querySelectorAll('input:not([type=hidden]):not([type=submit])').forEach(el => { el.value = ''; });
106
+ document.querySelectorAll('textarea').forEach(el => { el.value = ''; });
107
+ document.querySelectorAll('select').forEach(el => { el.selectedIndex = 0; });
108
+ })()`).catch(() => {});
109
+ }
110
+ if (autoCleanup.scroll) {
111
+ await page.evaluate('window.scrollTo(0, 0)').catch(() => {});
112
+ }
113
+ if (autoCleanup.storage) {
114
+ await page.evaluate('sessionStorage.clear()').catch(() => {});
115
+ }
116
+ }, { auto: true }],
117
+
118
+ // Auto-screenshot fixture: captures screenshot on test failure
119
+ _autoScreenshot: [async ({ page, screenshotOnFailure }, use, testInfo) => {
120
+ await use();
121
+
122
+ if (screenshotOnFailure && testInfo.status !== testInfo.expectedStatus) {
123
+ await testInfo.attach('screenshot', {
124
+ body: await page.screenshot(),
125
+ contentType: 'image/png',
126
+ });
127
+ }
128
+ }, { auto: true }],
71
129
  });
72
130
 
73
131
  export { test, expect };
@@ -0,0 +1,66 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import yaml from 'yaml';
4
+
5
+ export class TestDataLoader {
6
+ private data: Record<string, any>;
7
+
8
+ private constructor(data: Record<string, any>) {
9
+ this.data = data;
10
+ }
11
+
12
+ /**
13
+ * Load test data for a screen/feature combination.
14
+ *
15
+ * Priority (later wins):
16
+ * 1. {feature}.yaml — base data
17
+ * 2. {feature}.{SUNGEN_ENV}.yaml — environment-specific (if SUNGEN_ENV set)
18
+ */
19
+ static load(screenName: string, featureName: string): TestDataLoader {
20
+ const baseDir = path.join(process.cwd(), 'qa', 'screens', screenName, 'test-data');
21
+ const env = process.env.SUNGEN_ENV;
22
+
23
+ let data = loadYamlSync(path.join(baseDir, `${featureName}.yaml`)) || {};
24
+
25
+ if (env) {
26
+ const envData = loadYamlSync(path.join(baseDir, `${featureName}.${env}.yaml`));
27
+ if (envData) data = deepMerge(data, envData);
28
+ }
29
+
30
+ return new TestDataLoader(data);
31
+ }
32
+
33
+ get(key: string): string {
34
+ const parts = key.split('.');
35
+ let current: any = this.data;
36
+ for (const part of parts) {
37
+ if (current == null || typeof current !== 'object') {
38
+ throw new Error(`Test data key not found: ${key} (failed at '${part}')`);
39
+ }
40
+ current = current[part];
41
+ }
42
+ if (current === undefined || current === null) {
43
+ throw new Error(`Test data key not found: ${key}`);
44
+ }
45
+ return String(current);
46
+ }
47
+ }
48
+
49
+ function loadYamlSync(filePath: string): Record<string, any> | null {
50
+ if (!fs.existsSync(filePath)) return null;
51
+ const content = fs.readFileSync(filePath, 'utf-8');
52
+ return yaml.parse(content) || null;
53
+ }
54
+
55
+ function deepMerge(base: Record<string, any>, override: Record<string, any>): Record<string, any> {
56
+ const result = { ...base };
57
+ for (const [key, value] of Object.entries(override)) {
58
+ if (value && typeof value === 'object' && !Array.isArray(value) &&
59
+ result[key] && typeof result[key] === 'object' && !Array.isArray(result[key])) {
60
+ result[key] = deepMerge(result[key], value);
61
+ } else {
62
+ result[key] = value;
63
+ }
64
+ }
65
+ return result;
66
+ }