@sun-asterisk/sungen 2.5.2 → 2.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/add-flow.d.ts +3 -0
- package/dist/cli/commands/add-flow.d.ts.map +1 -0
- package/dist/cli/commands/add-flow.js +27 -0
- package/dist/cli/commands/add-flow.js.map +1 -0
- package/dist/cli/commands/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +95 -60
- package/dist/cli/commands/delivery.js.map +1 -1
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +38 -6
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/index.js +3 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts +16 -0
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +3 -0
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -2
- package/dist/generators/test-generator/adapters/playwright/templates/scenario.hbs +20 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/alert-accept-action.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/alert-dismiss-action.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/alert-fill-action.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/alert-text-assertion.hbs +2 -2
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/column-cell-assertion.hbs +1 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/navigation.hbs +2 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/route-assertion.hbs +1 -2
- package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-timeout.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +84 -4
- 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 +105 -17
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/generators/test-generator/patterns/interaction-patterns.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/interaction-patterns.js +22 -3
- package/dist/generators/test-generator/patterns/interaction-patterns.js.map +1 -1
- package/dist/generators/test-generator/patterns/navigation-patterns.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/navigation-patterns.js +8 -3
- package/dist/generators/test-generator/patterns/navigation-patterns.js.map +1 -1
- package/dist/generators/test-generator/step-mapper.d.ts +4 -0
- package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.js +7 -0
- package/dist/generators/test-generator/step-mapper.js.map +1 -1
- package/dist/generators/test-generator/template-engine.d.ts +14 -0
- package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
- package/dist/generators/test-generator/template-engine.js +1 -1
- package/dist/generators/test-generator/template-engine.js.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.d.ts +3 -20
- package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.js +23 -66
- package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.d.ts +2 -6
- package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.js +18 -80
- package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
- package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
- package/dist/orchestrator/ai-rules-updater.js +4 -0
- package/dist/orchestrator/ai-rules-updater.js.map +1 -1
- package/dist/orchestrator/flow-manager.d.ts +22 -0
- package/dist/orchestrator/flow-manager.d.ts.map +1 -0
- package/dist/orchestrator/flow-manager.js +251 -0
- package/dist/orchestrator/flow-manager.js.map +1 -0
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +1 -0
- package/dist/orchestrator/project-initializer.js.map +1 -1
- package/dist/orchestrator/screen-manager.d.ts.map +1 -1
- package/dist/orchestrator/screen-manager.js +3 -1
- package/dist/orchestrator/screen-manager.js.map +1 -1
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-flow.md +88 -0
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +11 -8
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-review.md +8 -6
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +15 -11
- package/dist/orchestrator/templates/ai-instructions/claude-config.md +41 -10
- package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +12 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-delivery.md +19 -18
- package/dist/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md +12 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +122 -10
- package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +31 -3
- package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +45 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +92 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-review.md +30 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-flow.md +86 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +13 -10
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +16 -15
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-review.md +9 -7
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +21 -17
- package/dist/orchestrator/templates/ai-instructions/copilot-config.md +40 -9
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +12 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +19 -18
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-error-mapping.md +12 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +122 -10
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +31 -3
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +45 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +93 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-review.md +30 -0
- package/dist/orchestrator/templates/playwright.config.d.ts.map +1 -1
- package/dist/orchestrator/templates/playwright.config.js +3 -1
- package/dist/orchestrator/templates/playwright.config.js.map +1 -1
- package/dist/orchestrator/templates/playwright.config.ts +4 -1
- package/dist/orchestrator/templates/specs-base.d.ts +3 -4
- package/dist/orchestrator/templates/specs-base.d.ts.map +1 -1
- package/dist/orchestrator/templates/specs-base.js +60 -91
- package/dist/orchestrator/templates/specs-base.js.map +1 -1
- package/dist/orchestrator/templates/specs-base.ts +61 -101
- package/dist/orchestrator/templates/specs-test-data.d.ts +3 -1
- package/dist/orchestrator/templates/specs-test-data.d.ts.map +1 -1
- package/dist/orchestrator/templates/specs-test-data.js +53 -2
- package/dist/orchestrator/templates/specs-test-data.js.map +1 -1
- package/dist/orchestrator/templates/specs-test-data.ts +56 -2
- package/package.json +1 -1
- package/src/cli/commands/add-flow.ts +25 -0
- package/src/cli/commands/delivery.ts +109 -58
- package/src/cli/commands/generate.ts +43 -6
- package/src/cli/index.ts +3 -1
- package/src/generators/test-generator/adapters/adapter-interface.ts +6 -1
- package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -2
- package/src/generators/test-generator/adapters/playwright/templates/scenario.hbs +20 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/alert-accept-action.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/alert-dismiss-action.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/alert-fill-action.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/alert-text-assertion.hbs +2 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/column-cell-assertion.hbs +1 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/navigation.hbs +2 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/route-assertion.hbs +1 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-timeout.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +84 -4
- package/src/generators/test-generator/code-generator.ts +119 -20
- package/src/generators/test-generator/patterns/interaction-patterns.ts +25 -3
- package/src/generators/test-generator/patterns/navigation-patterns.ts +8 -3
- package/src/generators/test-generator/step-mapper.ts +8 -0
- package/src/generators/test-generator/template-engine.ts +5 -2
- package/src/generators/test-generator/utils/data-resolver.ts +25 -77
- package/src/generators/test-generator/utils/selector-resolver.ts +23 -109
- package/src/orchestrator/ai-rules-updater.ts +5 -0
- package/src/orchestrator/flow-manager.ts +243 -0
- package/src/orchestrator/project-initializer.ts +1 -0
- package/src/orchestrator/screen-manager.ts +3 -1
- package/src/orchestrator/templates/ai-instructions/claude-cmd-add-flow.md +88 -0
- package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +11 -8
- package/src/orchestrator/templates/ai-instructions/claude-cmd-review.md +8 -6
- package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +15 -11
- package/src/orchestrator/templates/ai-instructions/claude-config.md +41 -10
- package/src/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +12 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-delivery.md +19 -18
- package/src/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md +12 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +122 -10
- package/src/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +31 -3
- package/src/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +45 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +92 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-tc-review.md +30 -0
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-flow.md +86 -0
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +13 -10
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +16 -15
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-review.md +9 -7
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +21 -17
- package/src/orchestrator/templates/ai-instructions/copilot-config.md +40 -9
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +12 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +19 -18
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-error-mapping.md +12 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +122 -10
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +31 -3
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +45 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +93 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-review.md +30 -0
- package/src/orchestrator/templates/playwright.config.ts +4 -1
- package/src/orchestrator/templates/specs-base.ts +61 -101
- package/src/orchestrator/templates/specs-test-data.ts +56 -2
- package/dist/utils/feature-finder.d.ts +0 -9
- package/dist/utils/feature-finder.d.ts.map +0 -1
- package/dist/utils/feature-finder.js +0 -67
- package/dist/utils/feature-finder.js.map +0 -1
- package/dist/utils/screen-paths.d.ts +0 -10
- package/dist/utils/screen-paths.d.ts.map +0 -1
- package/dist/utils/screen-paths.js +0 -73
- package/dist/utils/screen-paths.js.map +0 -1
- package/dist/utils/selector-loader.d.ts +0 -6
- package/dist/utils/selector-loader.d.ts.map +0 -1
- package/dist/utils/selector-loader.js +0 -20
- package/dist/utils/selector-loader.js.map +0 -1
- package/dist/utils/test-data-loader.d.ts +0 -6
- package/dist/utils/test-data-loader.d.ts.map +0 -1
- package/dist/utils/test-data-loader.js +0 -20
- package/dist/utils/test-data-loader.js.map +0 -1
- package/src/utils/feature-finder.ts +0 -33
- package/src/utils/screen-paths.ts +0 -37
- package/src/utils/selector-loader.ts +0 -23
- package/src/utils/test-data-loader.ts +0 -23
|
@@ -174,6 +174,29 @@ Scenario: Reset filters to default
|
|
|
174
174
|
|
|
175
175
|
For one-time setup/teardown. Most screens don't need these.
|
|
176
176
|
|
|
177
|
+
### `@parallel` — when tests need independent browser state
|
|
178
|
+
|
|
179
|
+
Add `@parallel` at feature level when:
|
|
180
|
+
|
|
181
|
+
1. **Multiple auth groups** (required) — e.g., `@auth:user` + `@no-auth` scenarios. Serial mode uses one shared context and cannot mix auth roles. Compiler will error without this tag.
|
|
182
|
+
2. **Validation-heavy features** (recommended) — each scenario fills forms with different invalid data and needs a clean form. Serial shared page keeps previous test's input.
|
|
183
|
+
|
|
184
|
+
Serial (default) is best for: CRUD flows, sequential user journeys, UI checks on the same page.
|
|
185
|
+
|
|
186
|
+
```gherkin
|
|
187
|
+
@parallel @auth:user
|
|
188
|
+
@cleanup:forms
|
|
189
|
+
Feature: kudos Screen
|
|
190
|
+
|
|
191
|
+
@critical
|
|
192
|
+
Scenario: Send kudos
|
|
193
|
+
...
|
|
194
|
+
|
|
195
|
+
@critical @no-auth
|
|
196
|
+
Scenario: Unauthenticated user is redirected
|
|
197
|
+
...
|
|
198
|
+
```
|
|
199
|
+
|
|
177
200
|
## Output Format
|
|
178
201
|
|
|
179
202
|
**Feature file** — `qa/screens/<screen>/features/<screen>.feature`
|
|
@@ -251,3 +274,73 @@ valid_password: StagingPass456
|
|
|
251
274
|
```
|
|
252
275
|
|
|
253
276
|
**Do NOT generate**: `selectors.yaml` (created during run-test), Playwright code (sungen compiles).
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
## Flow Test Generation
|
|
281
|
+
|
|
282
|
+
When generating tests for a **flow** (`qa/flows/<name>/`), adapt the strategy:
|
|
283
|
+
|
|
284
|
+
### Structure
|
|
285
|
+
|
|
286
|
+
- **Background** — starting page only: `Given User is on [Login] page`
|
|
287
|
+
- **Scenarios** — each phase of the E2E journey as a separate scenario
|
|
288
|
+
- **Selector refs** — use `[Screen:Element]` namespace format (see `sungen-gherkin-syntax`)
|
|
289
|
+
- **Test data** — namespace by phase: `login.email`, `submission.nominee`
|
|
290
|
+
- **Feature tag** — `@flow` required at feature level
|
|
291
|
+
|
|
292
|
+
### Flow vs Screen Differences
|
|
293
|
+
|
|
294
|
+
| Aspect | Screen | Flow |
|
|
295
|
+
|---|---|---|
|
|
296
|
+
| Section focus | UI patterns per section | Journey phases across screens |
|
|
297
|
+
| Viewpoints | VP-UI, VP-VAL, VP-LOGIC, VP-SEC per section | VP-LOGIC (flow transitions), VP-SEC (auth persistence), VP-VAL (cross-screen data) |
|
|
298
|
+
| Tier 1 focus | Happy path + required validation per section | Happy path through entire flow + auth + key error recovery |
|
|
299
|
+
| Background | Navigate to screen | Navigate to starting page |
|
|
300
|
+
|
|
301
|
+
### Flow-specific scenarios to generate
|
|
302
|
+
|
|
303
|
+
| Category | Examples |
|
|
304
|
+
|---|---|
|
|
305
|
+
| **Happy path** | Complete flow end-to-end with valid data |
|
|
306
|
+
| **Auth persistence** | Auth state maintained across screen transitions |
|
|
307
|
+
| **Error recovery** | Invalid input mid-flow → fix → continue |
|
|
308
|
+
| **Incomplete flow** | User abandons at each phase → state cleanup |
|
|
309
|
+
| **Cross-screen data** | Data entered on screen A visible on screen B |
|
|
310
|
+
|
|
311
|
+
### Output Format (Flow)
|
|
312
|
+
|
|
313
|
+
```gherkin
|
|
314
|
+
@flow @auth:user
|
|
315
|
+
Feature: Award Submission Flow
|
|
316
|
+
|
|
317
|
+
Background:
|
|
318
|
+
Given User is on [Login] page
|
|
319
|
+
|
|
320
|
+
@critical
|
|
321
|
+
Scenario: User login successfully
|
|
322
|
+
When User fill [Login:Email] field with {{login.email}}
|
|
323
|
+
And User fill [Login:Password] field with {{login.password}}
|
|
324
|
+
And User click [Login:Submit] button
|
|
325
|
+
Then User see [Dashboard] page
|
|
326
|
+
|
|
327
|
+
@critical
|
|
328
|
+
Scenario: User navigates to awards and submits
|
|
329
|
+
Given User is on [Awards] page
|
|
330
|
+
When User fill [Awards:Nominee] field with {{submission.nominee}}
|
|
331
|
+
And User fill [Awards:Reason] field with {{submission.reason}}
|
|
332
|
+
And User click [Awards:Submit] button
|
|
333
|
+
Then User see [Awards:Success Message] text with {{submission.success_message}}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
```yaml
|
|
337
|
+
# test-data — namespaced by phase
|
|
338
|
+
login:
|
|
339
|
+
email: "user@example.com"
|
|
340
|
+
password: "Password123"
|
|
341
|
+
|
|
342
|
+
submission:
|
|
343
|
+
nominee: "John Doe"
|
|
344
|
+
reason: "Outstanding contribution to the team"
|
|
345
|
+
success_message: "Award submitted successfully"
|
|
346
|
+
```
|
|
@@ -89,6 +89,36 @@ Do NOT mark `@manual` when data is visible in snapshot or documented in spec —
|
|
|
89
89
|
|
|
90
90
|
---
|
|
91
91
|
|
|
92
|
+
## Flow Review Additions
|
|
93
|
+
|
|
94
|
+
When reviewing a `@flow` feature (`qa/flows/<name>/`), apply standard scoring PLUS these flow-specific checks:
|
|
95
|
+
|
|
96
|
+
### Syntax — additional checks
|
|
97
|
+
- `[Screen:Element]` format used consistently (not mixing bare `[Element]` refs)
|
|
98
|
+
- YAML keys quoted with colon: `"login:submit":` not `login:submit:`
|
|
99
|
+
- `@flow` tag present at feature level
|
|
100
|
+
|
|
101
|
+
### Coverage — additional dimensions
|
|
102
|
+
| Dimension | Pts (from existing 40) | What to check |
|
|
103
|
+
|---|---|---|
|
|
104
|
+
| Screen transitions | (part of State transitions) | Each screen-to-screen navigation tested |
|
|
105
|
+
| Auth persistence | (part of Happy paths) | Auth state maintained across transitions |
|
|
106
|
+
| Error recovery mid-flow | (part of Negative cases) | Invalid input at each phase → fix → continue |
|
|
107
|
+
| Cross-screen data | (part of Edge cases) | Data entered on screen A asserted on screen B |
|
|
108
|
+
|
|
109
|
+
### Viewpoint — flow-specific classification
|
|
110
|
+
- **VP-LOGIC** — screen transitions, navigation flow, auth persistence
|
|
111
|
+
- **VP-VAL** — cross-screen data consistency, form data carried across pages
|
|
112
|
+
- **VP-SEC** — auth state across redirects, permission changes mid-flow
|
|
113
|
+
- VP-UI is typically minimal in flows (focus on functionality over layout)
|
|
114
|
+
|
|
115
|
+
### Checklist — flow-specific items
|
|
116
|
+
11. **Missing screen transitions** — flow visits 4 screens but only 2 transitions tested? Add missing
|
|
117
|
+
12. **Orphan scenarios** — scenario doesn't connect to previous/next phase? Flag broken flow
|
|
118
|
+
13. **Selector namespace consistency** — mixing `[Submit]` and `[Login:Submit]` in same flow? Standardize
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
92
122
|
## Unverified Selectors metric
|
|
93
123
|
|
|
94
124
|
**When to check**: if `qa/screens/<screen>/selectors/<screen>.yaml` exists, count lines matching the pattern `@needs-live-verify`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"playwright.config.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/templates/playwright.config.ts"],"names":[],"mappings":"AAEA;;;GAGG;AAKH;;GAEG;;AACH,
|
|
1
|
+
{"version":3,"file":"playwright.config.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/templates/playwright.config.ts"],"names":[],"mappings":"AAEA;;;GAGG;AAKH;;GAEG;;AACH,wBA4EG"}
|
|
@@ -22,7 +22,7 @@ exports.default = (0, test_1.defineConfig)({
|
|
|
22
22
|
/* Opt out of parallel tests on CI. */
|
|
23
23
|
workers: process.env.CI ? 1 : 2,
|
|
24
24
|
/* Global timeout per test */
|
|
25
|
-
timeout:
|
|
25
|
+
timeout: 15000,
|
|
26
26
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
|
27
27
|
/* JSON reporter is required by `sungen delivery` to populate test result columns in the exported CSV. */
|
|
28
28
|
/* Output file path is controlled by PLAYWRIGHT_JSON_OUTPUT_NAME env var for per-screen isolation. */
|
|
@@ -34,6 +34,8 @@ exports.default = (0, test_1.defineConfig)({
|
|
|
34
34
|
use: {
|
|
35
35
|
/* Base URL to use in actions like `await page.goto('')`. */
|
|
36
36
|
baseURL: 'https://example.com',
|
|
37
|
+
/* Per-action timeout (click, fill, etc.) */
|
|
38
|
+
actionTimeout: 10000,
|
|
37
39
|
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
|
38
40
|
trace: 'on-first-retry',
|
|
39
41
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"playwright.config.js","sourceRoot":"","sources":["../../../src/orchestrator/templates/playwright.config.ts"],"names":[],"mappings":";;AAAA,2CAAyD;AAEzD;;;GAGG;AACH,+BAA+B;AAC/B,2BAA2B;AAC3B,4DAA4D;AAE5D;;GAEG;AACH,kBAAe,IAAA,mBAAY,EAAC;IAC1B,OAAO,EAAE,mBAAmB;IAC5B,oCAAoC;IACpC,aAAa,EAAE,IAAI;IACnB,iFAAiF;IACjF,UAAU,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE;IAC5B,sBAAsB;IACtB,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/B,sCAAsC;IACtC,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/B,6BAA6B;IAC7B,OAAO,EAAE,KAAM;IACf,qEAAqE;IACrE,yGAAyG;IACzG,qGAAqG;IACrG,QAAQ,EAAE;QACR,CAAC,MAAM,CAAC;QACR,CAAC,MAAM,EAAE,EAAE,UAAU,EAAE,OAAO,CAAC,GAAG,CAAC,2BAA2B,IAAI,2BAA2B,EAAE,CAAC;KACjG;IACD,wGAAwG;IACxG,GAAG,EAAE;QACH,4DAA4D;QAC5D,OAAO,EAAE,qBAAqB;QAE9B,+FAA+F;QAC/F,KAAK,EAAE,gBAAgB;KACxB;IAED,2CAA2C;IAC3C,QAAQ,EAAE;QACR;YACE,IAAI,EAAE,UAAU;YAChB,GAAG,EAAE,EAAE,GAAG,cAAO,CAAC,gBAAgB,CAAC,EAAE;SACtC;QAED;;;;;;;;;;;;;;;;;;;;;4CAqBoC;QACpC,IAAI;QACJ,4BAA4B;QAC5B,4DAA4D;QAC5D,KAAK;QACL,IAAI;QACJ,2BAA2B;QAC3B,8DAA8D;QAC9D,KAAK;KACN;IAED,yDAAyD;IACzD,eAAe;IACf,8BAA8B;IAC9B,kCAAkC;IAClC,0CAA0C;IAC1C,KAAK;CACN,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"playwright.config.js","sourceRoot":"","sources":["../../../src/orchestrator/templates/playwright.config.ts"],"names":[],"mappings":";;AAAA,2CAAyD;AAEzD;;;GAGG;AACH,+BAA+B;AAC/B,2BAA2B;AAC3B,4DAA4D;AAE5D;;GAEG;AACH,kBAAe,IAAA,mBAAY,EAAC;IAC1B,OAAO,EAAE,mBAAmB;IAC5B,oCAAoC;IACpC,aAAa,EAAE,IAAI;IACnB,iFAAiF;IACjF,UAAU,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE;IAC5B,sBAAsB;IACtB,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/B,sCAAsC;IACtC,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/B,6BAA6B;IAC7B,OAAO,EAAE,KAAM;IACf,qEAAqE;IACrE,yGAAyG;IACzG,qGAAqG;IACrG,QAAQ,EAAE;QACR,CAAC,MAAM,CAAC;QACR,CAAC,MAAM,EAAE,EAAE,UAAU,EAAE,OAAO,CAAC,GAAG,CAAC,2BAA2B,IAAI,2BAA2B,EAAE,CAAC;KACjG;IACD,wGAAwG;IACxG,GAAG,EAAE;QACH,4DAA4D;QAC5D,OAAO,EAAE,qBAAqB;QAE9B,4CAA4C;QAC5C,aAAa,EAAE,KAAM;QAErB,+FAA+F;QAC/F,KAAK,EAAE,gBAAgB;KACxB;IAED,2CAA2C;IAC3C,QAAQ,EAAE;QACR;YACE,IAAI,EAAE,UAAU;YAChB,GAAG,EAAE,EAAE,GAAG,cAAO,CAAC,gBAAgB,CAAC,EAAE;SACtC;QAED;;;;;;;;;;;;;;;;;;;;;4CAqBoC;QACpC,IAAI;QACJ,4BAA4B;QAC5B,4DAA4D;QAC5D,KAAK;QACL,IAAI;QACJ,2BAA2B;QAC3B,8DAA8D;QAC9D,KAAK;KACN;IAED,yDAAyD;IACzD,eAAe;IACf,8BAA8B;IAC9B,kCAAkC;IAClC,0CAA0C;IAC1C,KAAK;CACN,CAAC,CAAC"}
|
|
@@ -22,7 +22,7 @@ export default defineConfig({
|
|
|
22
22
|
/* Opt out of parallel tests on CI. */
|
|
23
23
|
workers: process.env.CI ? 1 : 2,
|
|
24
24
|
/* Global timeout per test */
|
|
25
|
-
timeout:
|
|
25
|
+
timeout: 15_000,
|
|
26
26
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
|
27
27
|
/* JSON reporter is required by `sungen delivery` to populate test result columns in the exported CSV. */
|
|
28
28
|
/* Output file path is controlled by PLAYWRIGHT_JSON_OUTPUT_NAME env var for per-screen isolation. */
|
|
@@ -35,6 +35,9 @@ export default defineConfig({
|
|
|
35
35
|
/* Base URL to use in actions like `await page.goto('')`. */
|
|
36
36
|
baseURL: 'https://example.com',
|
|
37
37
|
|
|
38
|
+
/* Per-action timeout (click, fill, etc.) */
|
|
39
|
+
actionTimeout: 10_000,
|
|
40
|
+
|
|
38
41
|
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
|
39
42
|
trace: 'on-first-retry',
|
|
40
43
|
},
|
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import { expect } from '@playwright/test';
|
|
1
|
+
import { expect, type Page } from '@playwright/test';
|
|
2
2
|
type CleanupConfig = {
|
|
3
3
|
overlay?: boolean;
|
|
4
4
|
forms?: boolean;
|
|
5
5
|
scroll?: boolean;
|
|
6
6
|
storage?: boolean;
|
|
7
7
|
};
|
|
8
|
+
declare function cleanupPage(page: Page, config: CleanupConfig): Promise<void>;
|
|
8
9
|
declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & {
|
|
9
|
-
autoCleanup: CleanupConfig;
|
|
10
10
|
screenshotOnFailure: boolean;
|
|
11
|
-
_autoCleanup: void;
|
|
12
11
|
_autoScreenshot: void;
|
|
13
12
|
}, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
|
|
14
|
-
export { test, expect };
|
|
13
|
+
export { test, expect, cleanupPage };
|
|
15
14
|
//# sourceMappingURL=specs-base.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"specs-base.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-base.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,MAAM,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"specs-base.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-base.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,MAAM,EAAE,KAAK,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAEnE,KAAK,aAAa,GAAG;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,iBAAe,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAmD3E;AAED,QAAA,MAAM,IAAI;yBACa,OAAO;qBACX,IAAI;wGAwBrB,CAAC;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC"}
|
|
@@ -1,103 +1,72 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.expect = exports.test = void 0;
|
|
4
|
+
exports.cleanupPage = cleanupPage;
|
|
4
5
|
const test_1 = require("@playwright/test");
|
|
5
6
|
Object.defineProperty(exports, "expect", { enumerable: true, get: function () { return test_1.expect; } });
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
async function cleanupPage(page, config) {
|
|
8
|
+
if (config.overlay) {
|
|
9
|
+
await page.keyboard.press('Escape').catch(() => { });
|
|
10
|
+
await page.locator('body').click({ position: { x: 1, y: 1 }, force: true }).catch(() => { });
|
|
11
|
+
const hasOverlay = await page.evaluate(`(() => {
|
|
12
|
+
const el = document.elementFromPoint(window.innerWidth / 2, window.innerHeight / 2);
|
|
13
|
+
if (!el) return false;
|
|
14
|
+
let current = el;
|
|
15
|
+
while (current && current !== document.body) {
|
|
16
|
+
if (getComputedStyle(current).position === 'fixed') return true;
|
|
17
|
+
current = current.parentElement;
|
|
18
|
+
}
|
|
19
|
+
return false;
|
|
20
|
+
})()`).catch(() => false);
|
|
21
|
+
if (hasOverlay) {
|
|
22
|
+
await page.keyboard.press('Escape').catch(() => { });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (config.forms) {
|
|
26
|
+
await page.evaluate(`(() => {
|
|
27
|
+
const inputSetter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set;
|
|
28
|
+
const textareaSetter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')?.set;
|
|
29
|
+
document.querySelectorAll('input:not([type=hidden]):not([type=submit]):not([type=checkbox]):not([type=radio])').forEach(el => {
|
|
30
|
+
inputSetter?.call(el, '');
|
|
31
|
+
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
32
|
+
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
33
|
+
});
|
|
34
|
+
document.querySelectorAll('textarea').forEach(el => {
|
|
35
|
+
textareaSetter?.call(el, '');
|
|
36
|
+
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
37
|
+
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
38
|
+
});
|
|
39
|
+
document.querySelectorAll('select').forEach(el => {
|
|
40
|
+
el.selectedIndex = 0;
|
|
41
|
+
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
42
|
+
});
|
|
43
|
+
document.querySelectorAll('input[type=checkbox]').forEach(el => {
|
|
44
|
+
if (el.checked !== el.defaultChecked) {
|
|
45
|
+
el.checked = el.defaultChecked;
|
|
46
|
+
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
document.querySelectorAll('form').forEach(f => f.reset());
|
|
50
|
+
})()`).catch(() => { });
|
|
51
|
+
}
|
|
52
|
+
if (config.scroll) {
|
|
53
|
+
await page.evaluate('window.scrollTo(0, 0)').catch(() => { });
|
|
54
|
+
}
|
|
55
|
+
if (config.storage) {
|
|
56
|
+
await page.evaluate('sessionStorage.clear()').catch(() => { });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
10
59
|
const test = test_1.test.extend({
|
|
11
|
-
autoCleanup: [{}, { option: true }],
|
|
12
60
|
screenshotOnFailure: [false, { option: true }],
|
|
13
61
|
page: async ({ browser, storageState }, use) => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
contextCache.set(cacheKey, cached);
|
|
22
|
-
}
|
|
23
|
-
const page = cached.page;
|
|
24
|
-
// Patch goto once: skip navigation if already on the target path
|
|
25
|
-
if (!page[GOTO_PATCHED]) {
|
|
26
|
-
const originalGoto = page.goto.bind(page);
|
|
27
|
-
page.goto = async function (url, options) {
|
|
28
|
-
try {
|
|
29
|
-
const currentPath = new URL(page.url()).pathname;
|
|
30
|
-
if (currentPath === url || currentPath === url + '/') {
|
|
31
|
-
const hasOverlay = await page.evaluate(`(() => {
|
|
32
|
-
const el = document.elementFromPoint(window.innerWidth / 2, window.innerHeight / 2);
|
|
33
|
-
if (!el) return false;
|
|
34
|
-
let current = el;
|
|
35
|
-
while (current && current !== document.body) {
|
|
36
|
-
if (getComputedStyle(current).position === 'fixed') return true;
|
|
37
|
-
current = current.parentElement;
|
|
38
|
-
}
|
|
39
|
-
return false;
|
|
40
|
-
})()`).catch(() => true);
|
|
41
|
-
if (hasOverlay) {
|
|
42
|
-
return originalGoto(url, options);
|
|
43
|
-
}
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
catch {
|
|
48
|
-
// page.url() might be about:blank on first run
|
|
49
|
-
}
|
|
50
|
-
return originalGoto(url, options);
|
|
51
|
-
};
|
|
52
|
-
page[GOTO_PATCHED] = true;
|
|
53
|
-
}
|
|
54
|
-
await use(page);
|
|
55
|
-
}
|
|
56
|
-
else {
|
|
57
|
-
// No storageState: fresh context (e.g., unauthenticated tests)
|
|
58
|
-
const context = await browser.newContext();
|
|
59
|
-
const page = await context.newPage();
|
|
60
|
-
await use(page);
|
|
61
|
-
await page.close();
|
|
62
|
-
await context.close();
|
|
63
|
-
}
|
|
62
|
+
const context = storageState
|
|
63
|
+
? await browser.newContext({ storageState })
|
|
64
|
+
: await browser.newContext();
|
|
65
|
+
const page = await context.newPage();
|
|
66
|
+
await use(page);
|
|
67
|
+
await page.close();
|
|
68
|
+
await context.close();
|
|
64
69
|
},
|
|
65
|
-
// Auto-cleanup fixture: runs teardown after each test based on @cleanup:* tags
|
|
66
|
-
_autoCleanup: [async ({ page, autoCleanup }, use) => {
|
|
67
|
-
await use();
|
|
68
|
-
if (autoCleanup.overlay) {
|
|
69
|
-
await page.keyboard.press('Escape').catch(() => { });
|
|
70
|
-
await page.locator('body').click({ position: { x: 1, y: 1 }, force: true }).catch(() => { });
|
|
71
|
-
// Dismiss persistent fixed overlays (modals, dialogs)
|
|
72
|
-
const hasOverlay = await page.evaluate(`(() => {
|
|
73
|
-
const el = document.elementFromPoint(window.innerWidth / 2, window.innerHeight / 2);
|
|
74
|
-
if (!el) return false;
|
|
75
|
-
let current = el;
|
|
76
|
-
while (current && current !== document.body) {
|
|
77
|
-
if (getComputedStyle(current).position === 'fixed') return true;
|
|
78
|
-
current = current.parentElement;
|
|
79
|
-
}
|
|
80
|
-
return false;
|
|
81
|
-
})()`).catch(() => false);
|
|
82
|
-
if (hasOverlay) {
|
|
83
|
-
await page.keyboard.press('Escape').catch(() => { });
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
if (autoCleanup.forms) {
|
|
87
|
-
await page.evaluate(`(() => {
|
|
88
|
-
document.querySelectorAll('input:not([type=hidden]):not([type=submit])').forEach(el => { el.value = ''; });
|
|
89
|
-
document.querySelectorAll('textarea').forEach(el => { el.value = ''; });
|
|
90
|
-
document.querySelectorAll('select').forEach(el => { el.selectedIndex = 0; });
|
|
91
|
-
})()`).catch(() => { });
|
|
92
|
-
}
|
|
93
|
-
if (autoCleanup.scroll) {
|
|
94
|
-
await page.evaluate('window.scrollTo(0, 0)').catch(() => { });
|
|
95
|
-
}
|
|
96
|
-
if (autoCleanup.storage) {
|
|
97
|
-
await page.evaluate('sessionStorage.clear()').catch(() => { });
|
|
98
|
-
}
|
|
99
|
-
}, { auto: true }],
|
|
100
|
-
// Auto-screenshot fixture: captures screenshot on test failure
|
|
101
70
|
_autoScreenshot: [async ({ page, screenshotOnFailure }, use, testInfo) => {
|
|
102
71
|
await use();
|
|
103
72
|
if (screenshotOnFailure && testInfo.status !== testInfo.expectedStatus) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"specs-base.js","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-base.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"specs-base.js","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-base.ts"],"names":[],"mappings":";;;AA0FuB,kCAAW;AA1FlC,2CAAmE;AA0FpD,uFA1FQ,aAAM,OA0FR;AAjFrB,KAAK,UAAU,WAAW,CAAC,IAAU,EAAE,MAAqB;IAC1D,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACpD,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAC5F,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC;;;;;;;;;SASlC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC;QAC1B,IAAI,UAAU,EAAE,CAAC;YACf,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACtD,CAAC;IACH,CAAC;IACD,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,MAAM,IAAI,CAAC,QAAQ,CAAC;;;;;;;;;;;;;;;;;;;;;;;;SAwBf,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACzB,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClB,MAAM,IAAI,CAAC,QAAQ,CAAC,uBAAuB,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAC/D,CAAC;IACD,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,MAAM,IAAI,CAAC,QAAQ,CAAC,wBAAwB,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAChE,CAAC;AACH,CAAC;AAED,MAAM,IAAI,GAAG,WAAI,CAAC,MAAM,CAGrB;IACD,mBAAmB,EAAE,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IAE9C,IAAI,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,GAAG,EAAE,EAAE;QAC7C,MAAM,OAAO,GAAG,YAAY;YAC1B,CAAC,CAAC,MAAM,OAAO,CAAC,UAAU,CAAC,EAAE,YAAY,EAAE,CAAC;YAC5C,CAAC,CAAC,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC;QAC/B,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;QACrC,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC;QAChB,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;QACnB,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;IAED,eAAe,EAAE,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,mBAAmB,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,EAAE;YACvE,MAAM,GAAG,EAAE,CAAC;YAEZ,IAAI,mBAAmB,IAAI,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,cAAc,EAAE,CAAC;gBACvE,MAAM,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE;oBAClC,IAAI,EAAE,MAAM,IAAI,CAAC,UAAU,EAAE;oBAC7B,WAAW,EAAE,WAAW;iBACzB,CAAC,CAAC;YACL,CAAC;QACH,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;CACnB,CAAC,CAAC;AAEM,oBAAI"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { test as base, expect } from '@playwright/test';
|
|
2
|
-
import type { BrowserContext, Page } from '@playwright/test';
|
|
1
|
+
import { test as base, expect, type Page } from '@playwright/test';
|
|
3
2
|
|
|
4
3
|
type CleanupConfig = {
|
|
5
4
|
overlay?: boolean;
|
|
@@ -8,114 +7,75 @@ type CleanupConfig = {
|
|
|
8
7
|
storage?: boolean;
|
|
9
8
|
};
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
async function cleanupPage(page: Page, config: CleanupConfig): Promise<void> {
|
|
11
|
+
if (config.overlay) {
|
|
12
|
+
await page.keyboard.press('Escape').catch(() => {});
|
|
13
|
+
await page.locator('body').click({ position: { x: 1, y: 1 }, force: true }).catch(() => {});
|
|
14
|
+
const hasOverlay = await page.evaluate(`(() => {
|
|
15
|
+
const el = document.elementFromPoint(window.innerWidth / 2, window.innerHeight / 2);
|
|
16
|
+
if (!el) return false;
|
|
17
|
+
let current = el;
|
|
18
|
+
while (current && current !== document.body) {
|
|
19
|
+
if (getComputedStyle(current).position === 'fixed') return true;
|
|
20
|
+
current = current.parentElement;
|
|
21
|
+
}
|
|
22
|
+
return false;
|
|
23
|
+
})()`).catch(() => false);
|
|
24
|
+
if (hasOverlay) {
|
|
25
|
+
await page.keyboard.press('Escape').catch(() => {});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (config.forms) {
|
|
29
|
+
await page.evaluate(`(() => {
|
|
30
|
+
const inputSetter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set;
|
|
31
|
+
const textareaSetter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')?.set;
|
|
32
|
+
document.querySelectorAll('input:not([type=hidden]):not([type=submit]):not([type=checkbox]):not([type=radio])').forEach(el => {
|
|
33
|
+
inputSetter?.call(el, '');
|
|
34
|
+
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
35
|
+
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
36
|
+
});
|
|
37
|
+
document.querySelectorAll('textarea').forEach(el => {
|
|
38
|
+
textareaSetter?.call(el, '');
|
|
39
|
+
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
40
|
+
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
41
|
+
});
|
|
42
|
+
document.querySelectorAll('select').forEach(el => {
|
|
43
|
+
el.selectedIndex = 0;
|
|
44
|
+
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
45
|
+
});
|
|
46
|
+
document.querySelectorAll('input[type=checkbox]').forEach(el => {
|
|
47
|
+
if (el.checked !== el.defaultChecked) {
|
|
48
|
+
el.checked = el.defaultChecked;
|
|
49
|
+
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
document.querySelectorAll('form').forEach(f => f.reset());
|
|
53
|
+
})()`).catch(() => {});
|
|
54
|
+
}
|
|
55
|
+
if (config.scroll) {
|
|
56
|
+
await page.evaluate('window.scrollTo(0, 0)').catch(() => {});
|
|
57
|
+
}
|
|
58
|
+
if (config.storage) {
|
|
59
|
+
await page.evaluate('sessionStorage.clear()').catch(() => {});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
15
62
|
|
|
16
63
|
const test = base.extend<{
|
|
17
|
-
autoCleanup: CleanupConfig;
|
|
18
64
|
screenshotOnFailure: boolean;
|
|
19
|
-
_autoCleanup: void;
|
|
20
65
|
_autoScreenshot: void;
|
|
21
66
|
}>({
|
|
22
|
-
autoCleanup: [{}, { option: true }],
|
|
23
67
|
screenshotOnFailure: [false, { option: true }],
|
|
24
68
|
|
|
25
69
|
page: async ({ browser, storageState }, use) => {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
cached = { context, page };
|
|
34
|
-
contextCache.set(cacheKey, cached);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const page = cached.page;
|
|
38
|
-
|
|
39
|
-
// Patch goto once: skip navigation if already on the target path
|
|
40
|
-
if (!(page as any)[GOTO_PATCHED]) {
|
|
41
|
-
const originalGoto = page.goto.bind(page);
|
|
42
|
-
page.goto = async function (url: string, options?: any) {
|
|
43
|
-
try {
|
|
44
|
-
const currentPath = new URL(page.url()).pathname;
|
|
45
|
-
if (currentPath === url || currentPath === url + '/') {
|
|
46
|
-
const hasOverlay = await page.evaluate(`(() => {
|
|
47
|
-
const el = document.elementFromPoint(window.innerWidth / 2, window.innerHeight / 2);
|
|
48
|
-
if (!el) return false;
|
|
49
|
-
let current = el;
|
|
50
|
-
while (current && current !== document.body) {
|
|
51
|
-
if (getComputedStyle(current).position === 'fixed') return true;
|
|
52
|
-
current = current.parentElement;
|
|
53
|
-
}
|
|
54
|
-
return false;
|
|
55
|
-
})()`).catch(() => true);
|
|
56
|
-
|
|
57
|
-
if (hasOverlay) {
|
|
58
|
-
return originalGoto(url, options);
|
|
59
|
-
}
|
|
60
|
-
return null as any;
|
|
61
|
-
}
|
|
62
|
-
} catch {
|
|
63
|
-
// page.url() might be about:blank on first run
|
|
64
|
-
}
|
|
65
|
-
return originalGoto(url, options);
|
|
66
|
-
};
|
|
67
|
-
(page as any)[GOTO_PATCHED] = true;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
await use(page);
|
|
71
|
-
} else {
|
|
72
|
-
// No storageState: fresh context (e.g., unauthenticated tests)
|
|
73
|
-
const context = await browser.newContext();
|
|
74
|
-
const page = await context.newPage();
|
|
75
|
-
await use(page);
|
|
76
|
-
await page.close();
|
|
77
|
-
await context.close();
|
|
78
|
-
}
|
|
70
|
+
const context = storageState
|
|
71
|
+
? await browser.newContext({ storageState })
|
|
72
|
+
: await browser.newContext();
|
|
73
|
+
const page = await context.newPage();
|
|
74
|
+
await use(page);
|
|
75
|
+
await page.close();
|
|
76
|
+
await context.close();
|
|
79
77
|
},
|
|
80
78
|
|
|
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
79
|
_autoScreenshot: [async ({ page, screenshotOnFailure }, use, testInfo) => {
|
|
120
80
|
await use();
|
|
121
81
|
|
|
@@ -128,4 +88,4 @@ const test = base.extend<{
|
|
|
128
88
|
}, { auto: true }],
|
|
129
89
|
});
|
|
130
90
|
|
|
131
|
-
export { test, expect };
|
|
91
|
+
export { test, expect, cleanupPage };
|
|
@@ -2,11 +2,13 @@ export declare class TestDataLoader {
|
|
|
2
2
|
private data;
|
|
3
3
|
private constructor();
|
|
4
4
|
/**
|
|
5
|
-
* Load test data for a screen/feature combination.
|
|
5
|
+
* Load test data for a screen/feature or flow/feature combination.
|
|
6
6
|
*
|
|
7
7
|
* Priority (later wins):
|
|
8
8
|
* 1. {feature}.yaml — base data
|
|
9
9
|
* 2. {feature}.{SUNGEN_ENV}.yaml — environment-specific (if SUNGEN_ENV set)
|
|
10
|
+
*
|
|
11
|
+
* Paths: screenName starting with "flows/" loads from qa/flows/, otherwise qa/screens/
|
|
10
12
|
*/
|
|
11
13
|
static load(screenName: string, featureName: string): TestDataLoader;
|
|
12
14
|
get(key: string): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"specs-test-data.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-test-data.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"specs-test-data.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-test-data.ts"],"names":[],"mappings":"AAKA,qBAAa,cAAc;IACzB,OAAO,CAAC,IAAI,CAAsB;IAElC,OAAO;IAIP;;;;;;;;OAQG;IACH,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,cAAc;IAqBpE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;CAczB"}
|