@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.
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +2 -0
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/generators/gherkin-parser/index.d.ts +1 -0
- package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
- package/dist/generators/gherkin-parser/index.js +3 -0
- package/dist/generators/gherkin-parser/index.js.map +1 -1
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts +29 -1
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +21 -1
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js +11 -2
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/after-all.hbs +8 -0
- package/dist/generators/test-generator/adapters/playwright/templates/after-each.hbs +8 -0
- package/dist/generators/test-generator/adapters/playwright/templates/before-all.hbs +8 -0
- package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
- package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +24 -0
- package/dist/generators/test-generator/code-generator.d.ts +2 -0
- package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.js +109 -12
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/generators/test-generator/step-mapper.d.ts +1 -0
- package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.js +1 -1
- package/dist/generators/test-generator/step-mapper.js.map +1 -1
- package/dist/generators/test-generator/template-engine.d.ts +29 -1
- package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
- package/dist/generators/test-generator/template-engine.js +11 -2
- package/dist/generators/test-generator/template-engine.js.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.d.ts +11 -2
- package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.js +36 -25
- package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
- package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts +7 -0
- package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts.map +1 -0
- package/dist/generators/test-generator/utils/runtime-data-transformer.js +42 -0
- package/dist/generators/test-generator/utils/runtime-data-transformer.js.map +1 -0
- package/dist/generators/types.d.ts +1 -0
- package/dist/generators/types.d.ts.map +1 -1
- package/dist/generators/types.js.map +1 -1
- package/dist/orchestrator/project-initializer.d.ts +9 -0
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +74 -10
- package/dist/orchestrator/project-initializer.js.map +1 -1
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +1 -1
- package/dist/orchestrator/templates/ai-instructions/claude-config.md +11 -2
- package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +66 -13
- package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +54 -3
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +1 -1
- package/dist/orchestrator/templates/ai-instructions/copilot-config.md +11 -2
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +86 -13
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +54 -3
- package/dist/orchestrator/templates/specs-base.d.ts +12 -1
- package/dist/orchestrator/templates/specs-base.d.ts.map +1 -1
- package/dist/orchestrator/templates/specs-base.js +47 -5
- package/dist/orchestrator/templates/specs-base.js.map +1 -1
- package/dist/orchestrator/templates/specs-base.ts +65 -7
- package/dist/orchestrator/templates/specs-test-data.d.ts +14 -0
- package/dist/orchestrator/templates/specs-test-data.d.ts.map +1 -0
- package/dist/orchestrator/templates/specs-test-data.js +100 -0
- package/dist/orchestrator/templates/specs-test-data.js.map +1 -0
- package/dist/orchestrator/templates/specs-test-data.ts +66 -0
- package/package.json +1 -1
- package/src/cli/commands/generate.ts +2 -0
- package/src/cli/index.ts +1 -1
- package/src/generators/gherkin-parser/index.ts +4 -0
- package/src/generators/test-generator/adapters/adapter-interface.ts +12 -1
- package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +14 -2
- package/src/generators/test-generator/adapters/playwright/templates/after-all.hbs +8 -0
- package/src/generators/test-generator/adapters/playwright/templates/after-each.hbs +8 -0
- package/src/generators/test-generator/adapters/playwright/templates/before-all.hbs +8 -0
- package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
- package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +24 -0
- package/src/generators/test-generator/code-generator.ts +122 -13
- package/src/generators/test-generator/step-mapper.ts +2 -2
- package/src/generators/test-generator/template-engine.ts +28 -2
- package/src/generators/test-generator/utils/data-resolver.ts +45 -27
- package/src/generators/test-generator/utils/runtime-data-transformer.ts +51 -0
- package/src/generators/types.ts +1 -0
- package/src/orchestrator/project-initializer.ts +84 -10
- package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +1 -1
- package/src/orchestrator/templates/ai-instructions/claude-config.md +11 -2
- package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +66 -13
- package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +54 -3
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +1 -1
- package/src/orchestrator/templates/ai-instructions/copilot-config.md +11 -2
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +86 -13
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +54 -3
- package/src/orchestrator/templates/specs-base.ts +65 -7
- 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
|
-
|
|
|
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
|
-
|
|
191
|
+
Both `Background` and `@steps`/`@extend` are valid — they serve different purposes.
|
|
184
192
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
**
|
|
198
|
+
**Use `Background` for simple navigation:**
|
|
191
199
|
```gherkin
|
|
192
200
|
Background:
|
|
193
|
-
Given User is on [
|
|
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:
|
|
198
|
-
Then User see [
|
|
203
|
+
Scenario: View stats
|
|
204
|
+
Then User see [Revenue Chart] section
|
|
199
205
|
```
|
|
200
206
|
|
|
201
|
-
**
|
|
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
|
-
|
|
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
|
-
|
|
|
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
|
-
|
|
191
|
+
Both `Background` and `@steps`/`@extend` are valid — they serve different purposes.
|
|
184
192
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
**
|
|
198
|
+
**Use `Background` for simple navigation:**
|
|
191
199
|
```gherkin
|
|
192
200
|
Background:
|
|
193
|
-
Given User is on [
|
|
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:
|
|
198
|
-
Then User see [
|
|
203
|
+
Scenario: View stats
|
|
204
|
+
Then User see [Revenue Chart] section
|
|
199
205
|
```
|
|
200
206
|
|
|
201
|
-
**
|
|
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
|
-
|
|
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
|
+
}
|