@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.
Files changed (187) hide show
  1. package/dist/cli/commands/add-flow.d.ts +3 -0
  2. package/dist/cli/commands/add-flow.d.ts.map +1 -0
  3. package/dist/cli/commands/add-flow.js +27 -0
  4. package/dist/cli/commands/add-flow.js.map +1 -0
  5. package/dist/cli/commands/delivery.d.ts.map +1 -1
  6. package/dist/cli/commands/delivery.js +95 -60
  7. package/dist/cli/commands/delivery.js.map +1 -1
  8. package/dist/cli/commands/generate.d.ts.map +1 -1
  9. package/dist/cli/commands/generate.js +38 -6
  10. package/dist/cli/commands/generate.js.map +1 -1
  11. package/dist/cli/index.js +3 -1
  12. package/dist/cli/index.js.map +1 -1
  13. package/dist/generators/test-generator/adapters/adapter-interface.d.ts +16 -0
  14. package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
  15. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +3 -0
  16. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
  17. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
  18. package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -2
  19. package/dist/generators/test-generator/adapters/playwright/templates/scenario.hbs +20 -1
  20. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/alert-accept-action.hbs +1 -1
  21. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/alert-dismiss-action.hbs +1 -1
  22. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/alert-fill-action.hbs +1 -1
  23. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/alert-text-assertion.hbs +2 -2
  24. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/column-cell-assertion.hbs +1 -0
  25. package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/navigation.hbs +2 -1
  26. package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/route-assertion.hbs +1 -2
  27. package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-timeout.hbs +1 -1
  28. package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +84 -4
  29. package/dist/generators/test-generator/code-generator.d.ts +2 -0
  30. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  31. package/dist/generators/test-generator/code-generator.js +105 -17
  32. package/dist/generators/test-generator/code-generator.js.map +1 -1
  33. package/dist/generators/test-generator/patterns/interaction-patterns.d.ts.map +1 -1
  34. package/dist/generators/test-generator/patterns/interaction-patterns.js +22 -3
  35. package/dist/generators/test-generator/patterns/interaction-patterns.js.map +1 -1
  36. package/dist/generators/test-generator/patterns/navigation-patterns.d.ts.map +1 -1
  37. package/dist/generators/test-generator/patterns/navigation-patterns.js +8 -3
  38. package/dist/generators/test-generator/patterns/navigation-patterns.js.map +1 -1
  39. package/dist/generators/test-generator/step-mapper.d.ts +4 -0
  40. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  41. package/dist/generators/test-generator/step-mapper.js +7 -0
  42. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  43. package/dist/generators/test-generator/template-engine.d.ts +14 -0
  44. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  45. package/dist/generators/test-generator/template-engine.js +1 -1
  46. package/dist/generators/test-generator/template-engine.js.map +1 -1
  47. package/dist/generators/test-generator/utils/data-resolver.d.ts +3 -20
  48. package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
  49. package/dist/generators/test-generator/utils/data-resolver.js +23 -66
  50. package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
  51. package/dist/generators/test-generator/utils/selector-resolver.d.ts +2 -6
  52. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
  53. package/dist/generators/test-generator/utils/selector-resolver.js +18 -80
  54. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
  55. package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
  56. package/dist/orchestrator/ai-rules-updater.js +4 -0
  57. package/dist/orchestrator/ai-rules-updater.js.map +1 -1
  58. package/dist/orchestrator/flow-manager.d.ts +22 -0
  59. package/dist/orchestrator/flow-manager.d.ts.map +1 -0
  60. package/dist/orchestrator/flow-manager.js +251 -0
  61. package/dist/orchestrator/flow-manager.js.map +1 -0
  62. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  63. package/dist/orchestrator/project-initializer.js +1 -0
  64. package/dist/orchestrator/project-initializer.js.map +1 -1
  65. package/dist/orchestrator/screen-manager.d.ts.map +1 -1
  66. package/dist/orchestrator/screen-manager.js +3 -1
  67. package/dist/orchestrator/screen-manager.js.map +1 -1
  68. package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-flow.md +88 -0
  69. package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +11 -8
  70. package/dist/orchestrator/templates/ai-instructions/claude-cmd-review.md +8 -6
  71. package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +15 -11
  72. package/dist/orchestrator/templates/ai-instructions/claude-config.md +41 -10
  73. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +12 -0
  74. package/dist/orchestrator/templates/ai-instructions/claude-skill-delivery.md +19 -18
  75. package/dist/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md +12 -0
  76. package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +122 -10
  77. package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +31 -3
  78. package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +45 -0
  79. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +92 -0
  80. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-review.md +30 -0
  81. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-flow.md +86 -0
  82. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +13 -10
  83. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +16 -15
  84. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-review.md +9 -7
  85. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +21 -17
  86. package/dist/orchestrator/templates/ai-instructions/copilot-config.md +40 -9
  87. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +12 -0
  88. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +19 -18
  89. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-error-mapping.md +12 -0
  90. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +122 -10
  91. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +31 -3
  92. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +45 -0
  93. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +93 -0
  94. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-review.md +30 -0
  95. package/dist/orchestrator/templates/playwright.config.d.ts.map +1 -1
  96. package/dist/orchestrator/templates/playwright.config.js +3 -1
  97. package/dist/orchestrator/templates/playwright.config.js.map +1 -1
  98. package/dist/orchestrator/templates/playwright.config.ts +4 -1
  99. package/dist/orchestrator/templates/specs-base.d.ts +3 -4
  100. package/dist/orchestrator/templates/specs-base.d.ts.map +1 -1
  101. package/dist/orchestrator/templates/specs-base.js +60 -91
  102. package/dist/orchestrator/templates/specs-base.js.map +1 -1
  103. package/dist/orchestrator/templates/specs-base.ts +61 -101
  104. package/dist/orchestrator/templates/specs-test-data.d.ts +3 -1
  105. package/dist/orchestrator/templates/specs-test-data.d.ts.map +1 -1
  106. package/dist/orchestrator/templates/specs-test-data.js +53 -2
  107. package/dist/orchestrator/templates/specs-test-data.js.map +1 -1
  108. package/dist/orchestrator/templates/specs-test-data.ts +56 -2
  109. package/package.json +1 -1
  110. package/src/cli/commands/add-flow.ts +25 -0
  111. package/src/cli/commands/delivery.ts +109 -58
  112. package/src/cli/commands/generate.ts +43 -6
  113. package/src/cli/index.ts +3 -1
  114. package/src/generators/test-generator/adapters/adapter-interface.ts +6 -1
  115. package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +1 -1
  116. package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -2
  117. package/src/generators/test-generator/adapters/playwright/templates/scenario.hbs +20 -1
  118. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/alert-accept-action.hbs +1 -1
  119. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/alert-dismiss-action.hbs +1 -1
  120. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/alert-fill-action.hbs +1 -1
  121. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/alert-text-assertion.hbs +2 -2
  122. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/column-cell-assertion.hbs +1 -0
  123. package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/navigation.hbs +2 -1
  124. package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/route-assertion.hbs +1 -2
  125. package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-timeout.hbs +1 -1
  126. package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +84 -4
  127. package/src/generators/test-generator/code-generator.ts +119 -20
  128. package/src/generators/test-generator/patterns/interaction-patterns.ts +25 -3
  129. package/src/generators/test-generator/patterns/navigation-patterns.ts +8 -3
  130. package/src/generators/test-generator/step-mapper.ts +8 -0
  131. package/src/generators/test-generator/template-engine.ts +5 -2
  132. package/src/generators/test-generator/utils/data-resolver.ts +25 -77
  133. package/src/generators/test-generator/utils/selector-resolver.ts +23 -109
  134. package/src/orchestrator/ai-rules-updater.ts +5 -0
  135. package/src/orchestrator/flow-manager.ts +243 -0
  136. package/src/orchestrator/project-initializer.ts +1 -0
  137. package/src/orchestrator/screen-manager.ts +3 -1
  138. package/src/orchestrator/templates/ai-instructions/claude-cmd-add-flow.md +88 -0
  139. package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +11 -8
  140. package/src/orchestrator/templates/ai-instructions/claude-cmd-review.md +8 -6
  141. package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +15 -11
  142. package/src/orchestrator/templates/ai-instructions/claude-config.md +41 -10
  143. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +12 -0
  144. package/src/orchestrator/templates/ai-instructions/claude-skill-delivery.md +19 -18
  145. package/src/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md +12 -0
  146. package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +122 -10
  147. package/src/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +31 -3
  148. package/src/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +45 -0
  149. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +92 -0
  150. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-review.md +30 -0
  151. package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-flow.md +86 -0
  152. package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +13 -10
  153. package/src/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +16 -15
  154. package/src/orchestrator/templates/ai-instructions/copilot-cmd-review.md +9 -7
  155. package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +21 -17
  156. package/src/orchestrator/templates/ai-instructions/copilot-config.md +40 -9
  157. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +12 -0
  158. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +19 -18
  159. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-error-mapping.md +12 -0
  160. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +122 -10
  161. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +31 -3
  162. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +45 -0
  163. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +93 -0
  164. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-review.md +30 -0
  165. package/src/orchestrator/templates/playwright.config.ts +4 -1
  166. package/src/orchestrator/templates/specs-base.ts +61 -101
  167. package/src/orchestrator/templates/specs-test-data.ts +56 -2
  168. package/dist/utils/feature-finder.d.ts +0 -9
  169. package/dist/utils/feature-finder.d.ts.map +0 -1
  170. package/dist/utils/feature-finder.js +0 -67
  171. package/dist/utils/feature-finder.js.map +0 -1
  172. package/dist/utils/screen-paths.d.ts +0 -10
  173. package/dist/utils/screen-paths.d.ts.map +0 -1
  174. package/dist/utils/screen-paths.js +0 -73
  175. package/dist/utils/screen-paths.js.map +0 -1
  176. package/dist/utils/selector-loader.d.ts +0 -6
  177. package/dist/utils/selector-loader.d.ts.map +0 -1
  178. package/dist/utils/selector-loader.js +0 -20
  179. package/dist/utils/selector-loader.js.map +0 -1
  180. package/dist/utils/test-data-loader.d.ts +0 -6
  181. package/dist/utils/test-data-loader.d.ts.map +0 -1
  182. package/dist/utils/test-data-loader.js +0 -20
  183. package/dist/utils/test-data-loader.js.map +0 -1
  184. package/src/utils/feature-finder.ts +0 -33
  185. package/src/utils/screen-paths.ts +0 -37
  186. package/src/utils/selector-loader.ts +0 -23
  187. 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,wBAyEG"}
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: 10000,
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: 10_000,
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;AAGxD,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;AAOF,QAAA,MAAM,IAAI;iBACK,aAAa;yBACL,OAAO;kBACd,IAAI;qBACD,IAAI;wGA6GrB,CAAC;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,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
- // Share one context per storageState — avoids creating multiple sessions
7
- // that trigger server rate limiting or session invalidation
8
- const contextCache = new Map();
9
- const GOTO_PATCHED = Symbol('goto-patched');
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
- if (storageState) {
15
- const cacheKey = typeof storageState === 'string' ? storageState : JSON.stringify(storageState);
16
- let cached = contextCache.get(cacheKey);
17
- if (!cached) {
18
- const context = await browser.newContext({ storageState });
19
- const page = await context.newPage();
20
- cached = { context, page };
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":"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"}
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
- // Share one context per storageState avoids creating multiple sessions
12
- // that trigger server rate limiting or session invalidation
13
- const contextCache = new Map<string, { context: BrowserContext; page: Page }>();
14
- const GOTO_PATCHED = Symbol('goto-patched');
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
- if (storageState) {
27
- const cacheKey = typeof storageState === 'string' ? storageState : JSON.stringify(storageState);
28
-
29
- let cached = contextCache.get(cacheKey);
30
- if (!cached) {
31
- const context = await browser.newContext({ storageState });
32
- const page = await context.newPage();
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":"AAIA,qBAAa,cAAc;IACzB,OAAO,CAAC,IAAI,CAAsB;IAElC,OAAO;IAIP;;;;;;OAMG;IACH,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,cAAc;IAcpE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;CAczB"}
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"}