@sun-asterisk/sungen 3.2.18 → 3.2.19

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 (249) hide show
  1. package/dist/capabilities/discover.d.ts.map +1 -1
  2. package/dist/capabilities/discover.js +4 -1
  3. package/dist/capabilities/discover.js.map +1 -1
  4. package/dist/cli/commands/add.d.ts.map +1 -1
  5. package/dist/cli/commands/add.js +4 -2
  6. package/dist/cli/commands/add.js.map +1 -1
  7. package/dist/cli/commands/dashboard.d.ts.map +1 -1
  8. package/dist/cli/commands/dashboard.js +10 -1
  9. package/dist/cli/commands/dashboard.js.map +1 -1
  10. package/dist/cli/commands/delivery.d.ts.map +1 -1
  11. package/dist/cli/commands/delivery.js +19 -1
  12. package/dist/cli/commands/delivery.js.map +1 -1
  13. package/dist/cli/commands/generate.d.ts.map +1 -1
  14. package/dist/cli/commands/generate.js +4 -1
  15. package/dist/cli/commands/generate.js.map +1 -1
  16. package/dist/dashboard/html-renderer.d.ts.map +1 -1
  17. package/dist/dashboard/html-renderer.js +7 -2
  18. package/dist/dashboard/html-renderer.js.map +1 -1
  19. package/dist/dashboard/snapshot-builder.d.ts.map +1 -1
  20. package/dist/dashboard/snapshot-builder.js +3 -0
  21. package/dist/dashboard/snapshot-builder.js.map +1 -1
  22. package/dist/dashboard/types.d.ts +4 -0
  23. package/dist/dashboard/types.d.ts.map +1 -1
  24. package/dist/exporters/api-environment.d.ts.map +1 -1
  25. package/dist/exporters/api-environment.js +4 -24
  26. package/dist/exporters/api-environment.js.map +1 -1
  27. package/dist/exporters/api-testcase-formatter.d.ts +6 -0
  28. package/dist/exporters/api-testcase-formatter.d.ts.map +1 -1
  29. package/dist/exporters/api-testcase-formatter.js +11 -4
  30. package/dist/exporters/api-testcase-formatter.js.map +1 -1
  31. package/dist/exporters/csv-exporter.d.ts.map +1 -1
  32. package/dist/exporters/csv-exporter.js +40 -9
  33. package/dist/exporters/csv-exporter.js.map +1 -1
  34. package/dist/exporters/env-qa.d.ts +13 -0
  35. package/dist/exporters/env-qa.d.ts.map +1 -0
  36. package/dist/exporters/env-qa.js +82 -0
  37. package/dist/exporters/env-qa.js.map +1 -0
  38. package/dist/exporters/feature-parser.d.ts.map +1 -1
  39. package/dist/exporters/feature-parser.js +16 -33
  40. package/dist/exporters/feature-parser.js.map +1 -1
  41. package/dist/exporters/json-exporter.d.ts.map +1 -1
  42. package/dist/exporters/json-exporter.js +57 -20
  43. package/dist/exporters/json-exporter.js.map +1 -1
  44. package/dist/exporters/matrix/build.d.ts.map +1 -1
  45. package/dist/exporters/matrix/build.js +6 -5
  46. package/dist/exporters/matrix/build.js.map +1 -1
  47. package/dist/exporters/matrix/gates.d.ts.map +1 -1
  48. package/dist/exporters/matrix/gates.js +3 -2
  49. package/dist/exporters/matrix/gates.js.map +1 -1
  50. package/dist/exporters/matrix/step-buckets.d.ts +13 -0
  51. package/dist/exporters/matrix/step-buckets.d.ts.map +1 -0
  52. package/dist/exporters/matrix/step-buckets.js +7 -0
  53. package/dist/exporters/matrix/step-buckets.js.map +1 -0
  54. package/dist/exporters/qa-env-secret-denylist.d.ts +43 -0
  55. package/dist/exporters/qa-env-secret-denylist.d.ts.map +1 -0
  56. package/dist/exporters/qa-env-secret-denylist.js +143 -0
  57. package/dist/exporters/qa-env-secret-denylist.js.map +1 -0
  58. package/dist/exporters/scenario-merger.d.ts +20 -13
  59. package/dist/exporters/scenario-merger.d.ts.map +1 -1
  60. package/dist/exporters/scenario-merger.js +55 -30
  61. package/dist/exporters/scenario-merger.js.map +1 -1
  62. package/dist/exporters/step-formatter.d.ts +42 -33
  63. package/dist/exporters/step-formatter.d.ts.map +1 -1
  64. package/dist/exporters/step-formatter.js +68 -68
  65. package/dist/exporters/step-formatter.js.map +1 -1
  66. package/dist/exporters/types.d.ts +9 -8
  67. package/dist/exporters/types.d.ts.map +1 -1
  68. package/dist/generators/test-generator/adapters/adapter-interface.d.ts +1 -0
  69. package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
  70. package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts +1 -0
  71. package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts.map +1 -1
  72. package/dist/generators/test-generator/adapters/appium/appium-adapter.js.map +1 -1
  73. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/keyboard-global-action.hbs +3 -1
  74. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +1 -0
  75. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
  76. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
  77. package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  78. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/keyboard-global-action.hbs +3 -1
  79. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/page-assertion.hbs +1 -1
  80. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/storage-key-assertion.hbs +4 -0
  81. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/tab-order-assertion.hbs +30 -0
  82. package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/route-assertion.hbs +1 -4
  83. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/dialog-root.hbs +1 -1
  84. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/url-assertion.hbs +10 -0
  85. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  86. package/dist/generators/test-generator/code-generator.js +70 -7
  87. package/dist/generators/test-generator/code-generator.js.map +1 -1
  88. package/dist/generators/test-generator/diagnostics.d.ts +7 -1
  89. package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
  90. package/dist/generators/test-generator/diagnostics.js +6 -0
  91. package/dist/generators/test-generator/diagnostics.js.map +1 -1
  92. package/dist/generators/test-generator/recipe-expander.d.ts +1 -1
  93. package/dist/generators/test-generator/recipe-expander.d.ts.map +1 -1
  94. package/dist/generators/test-generator/recipe-expander.js +1 -1
  95. package/dist/generators/test-generator/recipe-expander.js.map +1 -1
  96. package/dist/generators/test-generator/step-mapper.d.ts +17 -0
  97. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  98. package/dist/generators/test-generator/step-mapper.js +108 -17
  99. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  100. package/dist/generators/test-generator/template-engine.d.ts +1 -0
  101. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  102. package/dist/generators/test-generator/template-engine.js +1 -1
  103. package/dist/generators/test-generator/template-engine.js.map +1 -1
  104. package/dist/generators/test-generator/utils/data-resolver.d.ts +7 -1
  105. package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
  106. package/dist/generators/test-generator/utils/data-resolver.js +8 -4
  107. package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
  108. package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts +2 -2
  109. package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts.map +1 -1
  110. package/dist/generators/test-generator/utils/runtime-data-transformer.js +56 -11
  111. package/dist/generators/test-generator/utils/runtime-data-transformer.js.map +1 -1
  112. package/dist/generators/test-generator/utils/selector-resolver.d.ts +18 -2
  113. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
  114. package/dist/generators/test-generator/utils/selector-resolver.js +29 -6
  115. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
  116. package/dist/harness/audit.d.ts.map +1 -1
  117. package/dist/harness/audit.js +8 -7
  118. package/dist/harness/audit.js.map +1 -1
  119. package/dist/harness/capability-plan.d.ts.map +1 -1
  120. package/dist/harness/capability-plan.js +2 -3
  121. package/dist/harness/capability-plan.js.map +1 -1
  122. package/dist/harness/catalog/drivers.yaml +1 -1
  123. package/dist/harness/challenge.d.ts.map +1 -1
  124. package/dist/harness/challenge.js +1 -55
  125. package/dist/harness/challenge.js.map +1 -1
  126. package/dist/harness/data-driven-lint.d.ts.map +1 -1
  127. package/dist/harness/data-driven-lint.js +14 -9
  128. package/dist/harness/data-driven-lint.js.map +1 -1
  129. package/dist/harness/depth-lint.d.ts.map +1 -1
  130. package/dist/harness/depth-lint.js +1 -3
  131. package/dist/harness/depth-lint.js.map +1 -1
  132. package/dist/harness/flow-check.d.ts.map +1 -1
  133. package/dist/harness/flow-check.js +9 -7
  134. package/dist/harness/flow-check.js.map +1 -1
  135. package/dist/harness/flow-plan.d.ts.map +1 -1
  136. package/dist/harness/flow-plan.js +4 -3
  137. package/dist/harness/flow-plan.js.map +1 -1
  138. package/dist/harness/manifest.js +2 -2
  139. package/dist/harness/manifest.js.map +1 -1
  140. package/dist/harness/parse.d.ts +12 -0
  141. package/dist/harness/parse.d.ts.map +1 -1
  142. package/dist/harness/parse.js +28 -1
  143. package/dist/harness/parse.js.map +1 -1
  144. package/dist/harness/script-check.d.ts.map +1 -1
  145. package/dist/harness/script-check.js +52 -30
  146. package/dist/harness/script-check.js.map +1 -1
  147. package/dist/harness/secret-scan.d.ts +3 -1
  148. package/dist/harness/secret-scan.d.ts.map +1 -1
  149. package/dist/harness/secret-scan.js +14 -10
  150. package/dist/harness/secret-scan.js.map +1 -1
  151. package/dist/harness/sensors.d.ts.map +1 -1
  152. package/dist/harness/sensors.js +15 -4
  153. package/dist/harness/sensors.js.map +1 -1
  154. package/dist/harness/trace.js +1 -1
  155. package/dist/harness/trace.js.map +1 -1
  156. package/dist/harness/unit-paths.d.ts +16 -0
  157. package/dist/harness/unit-paths.d.ts.map +1 -1
  158. package/dist/harness/unit-paths.js +39 -2
  159. package/dist/harness/unit-paths.js.map +1 -1
  160. package/dist/index.d.ts +1 -0
  161. package/dist/index.d.ts.map +1 -1
  162. package/dist/index.js +3 -1
  163. package/dist/index.js.map +1 -1
  164. package/dist/orchestrator/screen-manager.d.ts.map +1 -1
  165. package/dist/orchestrator/screen-manager.js +8 -2
  166. package/dist/orchestrator/screen-manager.js.map +1 -1
  167. package/dist/orchestrator/templates/ai-src/commands/dashboard.md +2 -1
  168. package/dist/orchestrator/templates/ai-src/commands/run-test.md +1 -1
  169. package/dist/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +2 -0
  170. package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +51 -2
  171. package/dist/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +6 -0
  172. package/dist/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +6 -0
  173. package/dist/orchestrator/templates/playwright.config.ts +33 -2
  174. package/dist/orchestrator/templates/specs-mock.d.ts +66 -0
  175. package/dist/orchestrator/templates/specs-mock.d.ts.map +1 -0
  176. package/dist/orchestrator/templates/specs-mock.js +137 -0
  177. package/dist/orchestrator/templates/specs-mock.js.map +1 -0
  178. package/dist/orchestrator/templates/specs-mock.ts +179 -0
  179. package/dist/orchestrator/test-data-guide.d.ts.map +1 -1
  180. package/dist/orchestrator/test-data-guide.js +4 -0
  181. package/dist/orchestrator/test-data-guide.js.map +1 -1
  182. package/package.json +3 -3
  183. package/src/capabilities/discover.ts +4 -1
  184. package/src/cli/commands/add.ts +4 -2
  185. package/src/cli/commands/dashboard.ts +10 -1
  186. package/src/cli/commands/delivery.ts +19 -1
  187. package/src/cli/commands/generate.ts +4 -1
  188. package/src/dashboard/html-renderer.ts +8 -2
  189. package/src/dashboard/snapshot-builder.ts +3 -0
  190. package/src/dashboard/types.ts +4 -0
  191. package/src/exporters/api-environment.ts +3 -20
  192. package/src/exporters/api-testcase-formatter.ts +8 -2
  193. package/src/exporters/csv-exporter.ts +47 -8
  194. package/src/exporters/env-qa.ts +41 -0
  195. package/src/exporters/feature-parser.ts +17 -38
  196. package/src/exporters/json-exporter.ts +71 -24
  197. package/src/exporters/matrix/build.ts +6 -5
  198. package/src/exporters/matrix/gates.ts +3 -2
  199. package/src/exporters/matrix/step-buckets.ts +15 -0
  200. package/src/exporters/qa-env-secret-denylist.ts +118 -0
  201. package/src/exporters/scenario-merger.ts +80 -42
  202. package/src/exporters/step-formatter.ts +67 -66
  203. package/src/exporters/types.ts +9 -8
  204. package/src/generators/test-generator/adapters/adapter-interface.ts +1 -1
  205. package/src/generators/test-generator/adapters/appium/appium-adapter.ts +1 -1
  206. package/src/generators/test-generator/adapters/appium/templates/steps/actions/keyboard-global-action.hbs +3 -1
  207. package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +1 -1
  208. package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  209. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/keyboard-global-action.hbs +3 -1
  210. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/page-assertion.hbs +1 -1
  211. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/storage-key-assertion.hbs +4 -0
  212. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/tab-order-assertion.hbs +30 -0
  213. package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/route-assertion.hbs +1 -4
  214. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/dialog-root.hbs +1 -1
  215. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/url-assertion.hbs +10 -0
  216. package/src/generators/test-generator/code-generator.ts +82 -7
  217. package/src/generators/test-generator/diagnostics.ts +7 -1
  218. package/src/generators/test-generator/recipe-expander.ts +2 -2
  219. package/src/generators/test-generator/step-mapper.ts +106 -15
  220. package/src/generators/test-generator/template-engine.ts +2 -2
  221. package/src/generators/test-generator/utils/data-resolver.ts +8 -5
  222. package/src/generators/test-generator/utils/runtime-data-transformer.ts +57 -11
  223. package/src/generators/test-generator/utils/selector-resolver.ts +29 -5
  224. package/src/harness/audit.ts +10 -9
  225. package/src/harness/capability-plan.ts +2 -2
  226. package/src/harness/catalog/drivers.yaml +1 -1
  227. package/src/harness/challenge.ts +2 -3
  228. package/src/harness/data-driven-lint.ts +13 -9
  229. package/src/harness/depth-lint.ts +2 -4
  230. package/src/harness/flow-check.ts +9 -7
  231. package/src/harness/flow-plan.ts +4 -3
  232. package/src/harness/manifest.ts +3 -3
  233. package/src/harness/parse.ts +28 -1
  234. package/src/harness/script-check.ts +52 -32
  235. package/src/harness/secret-scan.ts +11 -7
  236. package/src/harness/sensors.ts +15 -4
  237. package/src/harness/trace.ts +2 -2
  238. package/src/harness/unit-paths.ts +39 -2
  239. package/src/index.ts +1 -0
  240. package/src/orchestrator/screen-manager.ts +7 -2
  241. package/src/orchestrator/templates/ai-src/commands/dashboard.md +2 -1
  242. package/src/orchestrator/templates/ai-src/commands/run-test.md +1 -1
  243. package/src/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +2 -0
  244. package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +51 -2
  245. package/src/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +6 -0
  246. package/src/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +6 -0
  247. package/src/orchestrator/templates/playwright.config.ts +33 -2
  248. package/src/orchestrator/templates/specs-mock.ts +179 -0
  249. package/src/orchestrator/test-data-guide.ts +4 -0
@@ -1,4 +1,33 @@
1
1
  import { defineConfig, devices } from '@playwright/test';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+
5
+ /**
6
+ * Web baseURL per environment — one compiled suite runs on dev/uat/stg by
7
+ * switching SUNGEN_ENV, no config edit and no recompile.
8
+ *
9
+ * Precedence (first hit wins):
10
+ * 1. `SUNGEN_BASE_URL` from the shell/CI env
11
+ * 2. `SUNGEN_BASE_URL=` in `.env.qa.<SUNGEN_ENV>` (e.g. `.env.qa.uat`)
12
+ * 3. `SUNGEN_BASE_URL=` in `.env.qa`
13
+ * 4. the static fallback below (written by `sungen init --base-url`)
14
+ *
15
+ * Mirrors how the runtime resolves `${QA_*}` test-data refs and API/DB
16
+ * datasource URLs, so every layer of a run agrees on the environment.
17
+ */
18
+ function resolveBaseUrl(fallback: string): string {
19
+ if (process.env.SUNGEN_BASE_URL) return process.env.SUNGEN_BASE_URL;
20
+ for (const name of [`.env.qa.${process.env.SUNGEN_ENV || ''}`, '.env.qa']) {
21
+ if (name.endsWith('.')) continue;
22
+ const p = path.join(process.cwd(), name);
23
+ if (!fs.existsSync(p)) continue;
24
+ for (const line of fs.readFileSync(p, 'utf8').split('\n')) {
25
+ const m = line.match(/^\s*SUNGEN_BASE_URL\s*=\s*(.*?)\s*$/);
26
+ if (m) return m[1].replace(/^["']|["']$/g, '');
27
+ }
28
+ }
29
+ return fallback;
30
+ }
2
31
 
3
32
  /**
4
33
  * Resolve the JSON reporter output path.
@@ -61,8 +90,10 @@ export default defineConfig({
61
90
  ],
62
91
  /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
63
92
  use: {
64
- /* Base URL to use in actions like `await page.goto('')`. */
65
- baseURL: 'https://example.com',
93
+ /* Base URL to use in actions like `await page.goto('')`. The quoted value is the
94
+ static fallback (pre-filled by `sungen init --base-url`); SUNGEN_BASE_URL in the
95
+ shell or .env.qa[.<SUNGEN_ENV>] overrides it per environment. */
96
+ baseURL: resolveBaseUrl('https://example.com'),
66
97
 
67
98
  /* Per-action timeout (click, fill, etc.) */
68
99
  actionTimeout: 10_000,
@@ -0,0 +1,179 @@
1
+ /* eslint-disable */
2
+ /**
3
+ * Sungen Mock Driver — runtime network-mocking helper (auto-generated into specs/mock.ts).
4
+ *
5
+ * One module singleton (same import shape as db/api); ALL state lives per-page in a
6
+ * WeakMap<Page, MockState>, so routes and call counters follow the page's lifetime on
7
+ * both isolation paths: a fresh page per test destroys them for free, and the @serial
8
+ * shared page is reset by the compiler-emitted mock.reset(page) at the end of each
9
+ * @mock scenario (scoped — only routes THIS router installed are unrouted; a blanket
10
+ * page.unrouteAll() would also kill Background-installed mocks and third-party routes).
11
+ *
12
+ * Blocking posture (fail-on-unmocked, the project default): the first install() also
13
+ * registers a catch-all route. Playwright matches routes last-registered-first, so
14
+ * registering the catch-all FIRST makes every named mock win over it. The catch-all
15
+ * only judges fetch/XHR/websocket traffic — documents/scripts/styles/images pass, the
16
+ * app shell must still load; what is being isolated is API traffic. An unmatched API
17
+ * request aborts and throws (fails the test) unless the scenario opted out via
18
+ * @mock:passthrough (compiled to mock.setPassthrough(page)).
19
+ *
20
+ * DO NOT EDIT — regenerated by `sungen generate`.
21
+ */
22
+ import type { Page, Route } from '@playwright/test';
23
+
24
+ export interface MockConfig {
25
+ name: string;
26
+ method: string; // HTTP verb or '*'
27
+ url: string; // page.route glob, or 're:<pattern>' for a regex
28
+ status: number;
29
+ headers?: Record<string, string>;
30
+ body?: string; // pre-serialized by the compiler (JSON string)
31
+ delay_ms?: number;
32
+ }
33
+
34
+ interface InstalledRoute {
35
+ pattern: string | RegExp;
36
+ handler: (route: Route) => Promise<void>;
37
+ }
38
+
39
+ interface MockState {
40
+ counts: Map<string, number>; // per-mock handled-request counters
41
+ installed: Set<string>; // names installed in this scenario (callCount guard)
42
+ routes: InstalledRoute[]; // exactly what WE routed (scoped reset)
43
+ guardInstalled: boolean;
44
+ passthrough: boolean;
45
+ violations: string[]; // unmocked requests the guard aborted (fail-on-unmocked)
46
+ /** @serial only: routes[0..N) / names installed by Background (beforeAll) persist across
47
+ * scenarios — reset() removes only what a scenario added on top. -1 = no snapshot
48
+ * (the default per-test path), where reset() drops the whole state. */
49
+ backgroundRouteCount: number;
50
+ backgroundNames: Set<string>;
51
+ }
52
+
53
+ /** Resource types the fail-on-unmocked guard judges; everything else passes through.
54
+ * NOTE: page.route() does NOT intercept WebSocket connections (that is
55
+ * page.routeWebSocket, a separate API) — the shipped posture is fetch/XHR only. */
56
+ const GUARDED_TYPES = new Set(['fetch', 'xhr']);
57
+
58
+ class MockRouter {
59
+ private state = new WeakMap<Page, MockState>();
60
+
61
+ private stateFor(page: Page): MockState {
62
+ let st = this.state.get(page);
63
+ if (!st) {
64
+ st = { counts: new Map(), installed: new Set(), routes: [], guardInstalled: false, passthrough: false, violations: [], backgroundRouteCount: -1, backgroundNames: new Set() };
65
+ this.state.set(page, st);
66
+ }
67
+ return st;
68
+ }
69
+
70
+ /** `@mock:passthrough` — unmatched requests reach the real backend for this page/scenario.
71
+ * Also forgives violations already recorded (Background installs + goto run BEFORE the
72
+ * scenario body can set this flag — boot-time requests of an opted-out scenario must not
73
+ * fail its assertNoUnmocked verdict). The compiler additionally arms the flag inside
74
+ * beforeEach via test.info().tags, before the Background installs, so boot requests are
75
+ * not even aborted on the default path. */
76
+ setPassthrough(page: Page): void {
77
+ const st = this.stateFor(page);
78
+ st.passthrough = true;
79
+ st.violations.length = 0;
80
+ }
81
+
82
+ /** Install one catalog entry. The first install also arms the fail-on-unmocked guard. */
83
+ async install(page: Page, cfg: MockConfig): Promise<void> {
84
+ const st = this.stateFor(page);
85
+ if (!st.guardInstalled) {
86
+ st.guardInstalled = true;
87
+ const guard = async (route: Route) => {
88
+ const req = route.request();
89
+ if (!GUARDED_TYPES.has(req.resourceType()) || st.passthrough) return route.fallback();
90
+ // Record + abort — NEVER throw here: a route handler runs outside any test
91
+ // stack, so a throw surfaces as an unhandled rejection (a hard process crash
92
+ // outside @playwright/test, flaky attribution inside it). The compiler emits
93
+ // `await mock.assertNoUnmocked(page)` at the end of every @mock scenario,
94
+ // which fails the test deterministically with the FULL violation list.
95
+ st.violations.push(`${req.method()} ${req.url()}`);
96
+ await route.abort('failed');
97
+ };
98
+ await page.route('**/*', guard); // FIRST → matched last → every named mock wins
99
+ st.routes.push({ pattern: '**/*', handler: guard });
100
+ }
101
+
102
+ const pattern = cfg.url.startsWith('re:') ? new RegExp(cfg.url.slice(3)) : cfg.url;
103
+ const handler = async (route: Route) => {
104
+ if (cfg.method !== '*' && route.request().method() !== cfg.method) return route.fallback();
105
+ st.counts.set(cfg.name, (st.counts.get(cfg.name) ?? 0) + 1);
106
+ if (cfg.delay_ms) await new Promise((r) => setTimeout(r, cfg.delay_ms));
107
+ // A delayed fulfill can race a page close / reset at teardown — that race must
108
+ // not surface as an unhandled rejection from inside a route handler.
109
+ try { await route.fulfill({ status: cfg.status, headers: cfg.headers, body: cfg.body }); } catch { /* page closing */ }
110
+ };
111
+ await page.route(pattern, handler);
112
+ st.routes.push({ pattern, handler });
113
+ st.installed.add(cfg.name);
114
+ if (!st.counts.has(cfg.name)) st.counts.set(cfg.name, 0);
115
+ }
116
+
117
+ /** Handled-request count for a mock installed in THIS scenario — throws for a name
118
+ * that was never installed here (an exact 0 on an uninstalled mock must be a loud
119
+ * error, not a passing assertion). */
120
+ callCount(page: Page, name: string): number {
121
+ const st = this.state.get(page);
122
+ if (!st || !st.installed.has(name)) {
123
+ throw new Error(`mock "${name}" was never installed in this scenario — add \`Given [${name}] mock is active\``);
124
+ }
125
+ return st.counts.get(name) ?? 0;
126
+ }
127
+
128
+ /** Fail-on-unmocked verdict — emitted by the compiler as the LAST statement of every
129
+ * @mock scenario. Throws with the full list of aborted unmatched requests; a scenario
130
+ * whose app swallowed those failed fetches must not stay green for the wrong reason. */
131
+ async assertNoUnmocked(page: Page): Promise<void> {
132
+ const st = this.state.get(page);
133
+ if (!st || st.violations.length === 0) return;
134
+ const list = st.violations.slice();
135
+ st.violations.length = 0;
136
+ throw new Error(
137
+ `${list.length} unmocked request(s) hit the fail-on-unmocked guard:\n - ${list.join('\n - ')}\n` +
138
+ 'Add entries to mock/mocks.yaml, or opt out with @mock:passthrough.');
139
+ }
140
+
141
+ /** @serial only, emitted by the compiler at the END of the Background block (beforeAll):
142
+ * everything installed so far — Background mocks and the guard — persists across the
143
+ * feature's scenarios; reset() removes only what each scenario added on top. Without
144
+ * this, scenario 1's reset would kill the Background mocks and scenarios 2+ would run
145
+ * against the REAL backend with no guard — the silent-green class this driver exists
146
+ * to prevent. */
147
+ snapshotBackground(page: Page): void {
148
+ const st = this.stateFor(page);
149
+ st.backgroundRouteCount = st.routes.length;
150
+ st.backgroundNames = new Set(st.installed);
151
+ }
152
+
153
+ /** Scoped teardown for the @serial shared page: unroute exactly what THIS scenario
154
+ * installed (Background routes captured by snapshotBackground persist), zero every
155
+ * counter, clear flags/violations. Emitted at the end of each @mock scenario on the
156
+ * serial path; the default per-test path never needs it (page dies with the test). */
157
+ async reset(page: Page): Promise<void> {
158
+ const st = this.state.get(page);
159
+ if (!st) return;
160
+ if (st.backgroundRouteCount < 0) {
161
+ // No Background snapshot → drop everything (the pre-snapshot / default behavior).
162
+ for (const r of st.routes) {
163
+ try { await page.unroute(r.pattern, r.handler); } catch { /* page may be closing */ }
164
+ }
165
+ this.state.delete(page);
166
+ return;
167
+ }
168
+ for (const r of st.routes.slice(st.backgroundRouteCount)) {
169
+ try { await page.unroute(r.pattern, r.handler); } catch { /* page may be closing */ }
170
+ }
171
+ st.routes.length = st.backgroundRouteCount;
172
+ st.installed = new Set(st.backgroundNames);
173
+ st.counts = new Map();
174
+ st.violations.length = 0;
175
+ st.passthrough = false;
176
+ }
177
+ }
178
+
179
+ export const mock = new MockRouter();
@@ -129,6 +129,10 @@ const ENV_QA_EXAMPLE = `# Secret test data referenced as \${QA_*} in qa/**/test-
129
129
  # Per-environment overrides: .env.qa.<env> + run with SUNGEN_ENV=<env>.
130
130
  #
131
131
  # QA_ADMIN_PASSWORD=
132
+ #
133
+ # Web baseURL for this environment — overrides the static value in
134
+ # playwright.config.ts (declare per env: .env.qa.dev, .env.qa.uat, ...).
135
+ # SUNGEN_BASE_URL=
132
136
  `;
133
137
 
134
138
  /**