@sun-asterisk/sungen 3.2.18 → 3.2.20

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 (264) 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 +10 -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 +15 -6
  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/test-data-resolver.d.ts +15 -0
  67. package/dist/exporters/test-data-resolver.d.ts.map +1 -1
  68. package/dist/exporters/test-data-resolver.js +34 -1
  69. package/dist/exporters/test-data-resolver.js.map +1 -1
  70. package/dist/exporters/types.d.ts +9 -8
  71. package/dist/exporters/types.d.ts.map +1 -1
  72. package/dist/generators/test-generator/adapters/adapter-interface.d.ts +2 -0
  73. package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
  74. package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts +2 -0
  75. package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts.map +1 -1
  76. package/dist/generators/test-generator/adapters/appium/appium-adapter.js.map +1 -1
  77. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/keyboard-global-action.hbs +3 -1
  78. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +2 -0
  79. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
  80. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
  81. package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +6 -0
  82. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/keyboard-global-action.hbs +3 -1
  83. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/page-assertion.hbs +1 -1
  84. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/storage-key-assertion.hbs +4 -0
  85. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/tab-order-assertion.hbs +30 -0
  86. package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/navigation.hbs +4 -0
  87. package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/route-assertion.hbs +1 -4
  88. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/dialog-root.hbs +1 -1
  89. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/url-assertion.hbs +15 -0
  90. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  91. package/dist/generators/test-generator/code-generator.js +78 -7
  92. package/dist/generators/test-generator/code-generator.js.map +1 -1
  93. package/dist/generators/test-generator/diagnostics.d.ts +16 -1
  94. package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
  95. package/dist/generators/test-generator/diagnostics.js +15 -0
  96. package/dist/generators/test-generator/diagnostics.js.map +1 -1
  97. package/dist/generators/test-generator/recipe-expander.d.ts +1 -1
  98. package/dist/generators/test-generator/recipe-expander.d.ts.map +1 -1
  99. package/dist/generators/test-generator/recipe-expander.js +1 -1
  100. package/dist/generators/test-generator/recipe-expander.js.map +1 -1
  101. package/dist/generators/test-generator/step-mapper.d.ts +41 -0
  102. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  103. package/dist/generators/test-generator/step-mapper.js +161 -17
  104. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  105. package/dist/generators/test-generator/template-engine.d.ts +2 -0
  106. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  107. package/dist/generators/test-generator/template-engine.js +1 -1
  108. package/dist/generators/test-generator/template-engine.js.map +1 -1
  109. package/dist/generators/test-generator/utils/data-resolver.d.ts +18 -1
  110. package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
  111. package/dist/generators/test-generator/utils/data-resolver.js +28 -4
  112. package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
  113. package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts +2 -2
  114. package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts.map +1 -1
  115. package/dist/generators/test-generator/utils/runtime-data-transformer.js +56 -11
  116. package/dist/generators/test-generator/utils/runtime-data-transformer.js.map +1 -1
  117. package/dist/generators/test-generator/utils/selector-resolver.d.ts +18 -2
  118. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
  119. package/dist/generators/test-generator/utils/selector-resolver.js +29 -6
  120. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
  121. package/dist/harness/audit.d.ts.map +1 -1
  122. package/dist/harness/audit.js +8 -7
  123. package/dist/harness/audit.js.map +1 -1
  124. package/dist/harness/capability-plan.d.ts.map +1 -1
  125. package/dist/harness/capability-plan.js +2 -3
  126. package/dist/harness/capability-plan.js.map +1 -1
  127. package/dist/harness/catalog/drivers.yaml +1 -1
  128. package/dist/harness/challenge.d.ts.map +1 -1
  129. package/dist/harness/challenge.js +1 -55
  130. package/dist/harness/challenge.js.map +1 -1
  131. package/dist/harness/data-driven-lint.d.ts.map +1 -1
  132. package/dist/harness/data-driven-lint.js +14 -9
  133. package/dist/harness/data-driven-lint.js.map +1 -1
  134. package/dist/harness/depth-lint.d.ts.map +1 -1
  135. package/dist/harness/depth-lint.js +1 -3
  136. package/dist/harness/depth-lint.js.map +1 -1
  137. package/dist/harness/flow-check.d.ts.map +1 -1
  138. package/dist/harness/flow-check.js +9 -7
  139. package/dist/harness/flow-check.js.map +1 -1
  140. package/dist/harness/flow-plan.d.ts.map +1 -1
  141. package/dist/harness/flow-plan.js +4 -3
  142. package/dist/harness/flow-plan.js.map +1 -1
  143. package/dist/harness/manifest.js +2 -2
  144. package/dist/harness/manifest.js.map +1 -1
  145. package/dist/harness/parse.d.ts +12 -0
  146. package/dist/harness/parse.d.ts.map +1 -1
  147. package/dist/harness/parse.js +28 -1
  148. package/dist/harness/parse.js.map +1 -1
  149. package/dist/harness/script-check.d.ts.map +1 -1
  150. package/dist/harness/script-check.js +61 -31
  151. package/dist/harness/script-check.js.map +1 -1
  152. package/dist/harness/secret-scan.d.ts +3 -1
  153. package/dist/harness/secret-scan.d.ts.map +1 -1
  154. package/dist/harness/secret-scan.js +27 -11
  155. package/dist/harness/secret-scan.js.map +1 -1
  156. package/dist/harness/sensors.d.ts.map +1 -1
  157. package/dist/harness/sensors.js +15 -4
  158. package/dist/harness/sensors.js.map +1 -1
  159. package/dist/harness/trace.js +1 -1
  160. package/dist/harness/trace.js.map +1 -1
  161. package/dist/harness/unit-paths.d.ts +16 -0
  162. package/dist/harness/unit-paths.d.ts.map +1 -1
  163. package/dist/harness/unit-paths.js +39 -2
  164. package/dist/harness/unit-paths.js.map +1 -1
  165. package/dist/index.d.ts +1 -0
  166. package/dist/index.d.ts.map +1 -1
  167. package/dist/index.js +3 -1
  168. package/dist/index.js.map +1 -1
  169. package/dist/orchestrator/screen-manager.d.ts.map +1 -1
  170. package/dist/orchestrator/screen-manager.js +8 -2
  171. package/dist/orchestrator/screen-manager.js.map +1 -1
  172. package/dist/orchestrator/templates/ai-src/commands/dashboard.md +2 -1
  173. package/dist/orchestrator/templates/ai-src/commands/run-test.md +1 -1
  174. package/dist/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +8 -0
  175. package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +4 -0
  176. package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +79 -2
  177. package/dist/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +7 -1
  178. package/dist/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +12 -3
  179. package/dist/orchestrator/templates/playwright.config.ts +33 -2
  180. package/dist/orchestrator/templates/specs-mock.d.ts +66 -0
  181. package/dist/orchestrator/templates/specs-mock.d.ts.map +1 -0
  182. package/dist/orchestrator/templates/specs-mock.js +137 -0
  183. package/dist/orchestrator/templates/specs-mock.js.map +1 -0
  184. package/dist/orchestrator/templates/specs-mock.ts +179 -0
  185. package/dist/orchestrator/templates/specs-url-assert.d.ts +67 -0
  186. package/dist/orchestrator/templates/specs-url-assert.d.ts.map +1 -0
  187. package/dist/orchestrator/templates/specs-url-assert.js +121 -0
  188. package/dist/orchestrator/templates/specs-url-assert.js.map +1 -0
  189. package/dist/orchestrator/templates/specs-url-assert.ts +134 -0
  190. package/dist/orchestrator/test-data-guide.d.ts.map +1 -1
  191. package/dist/orchestrator/test-data-guide.js +4 -0
  192. package/dist/orchestrator/test-data-guide.js.map +1 -1
  193. package/package.json +3 -3
  194. package/src/capabilities/discover.ts +4 -1
  195. package/src/cli/commands/add.ts +4 -2
  196. package/src/cli/commands/dashboard.ts +10 -1
  197. package/src/cli/commands/delivery.ts +19 -1
  198. package/src/cli/commands/generate.ts +9 -1
  199. package/src/dashboard/html-renderer.ts +8 -2
  200. package/src/dashboard/snapshot-builder.ts +3 -0
  201. package/src/dashboard/types.ts +4 -0
  202. package/src/exporters/api-environment.ts +3 -20
  203. package/src/exporters/api-testcase-formatter.ts +8 -2
  204. package/src/exporters/csv-exporter.ts +47 -8
  205. package/src/exporters/env-qa.ts +41 -0
  206. package/src/exporters/feature-parser.ts +17 -38
  207. package/src/exporters/json-exporter.ts +71 -24
  208. package/src/exporters/matrix/build.ts +15 -6
  209. package/src/exporters/matrix/gates.ts +3 -2
  210. package/src/exporters/matrix/step-buckets.ts +15 -0
  211. package/src/exporters/qa-env-secret-denylist.ts +118 -0
  212. package/src/exporters/scenario-merger.ts +80 -42
  213. package/src/exporters/step-formatter.ts +67 -66
  214. package/src/exporters/test-data-resolver.ts +34 -1
  215. package/src/exporters/types.ts +9 -8
  216. package/src/generators/test-generator/adapters/adapter-interface.ts +1 -1
  217. package/src/generators/test-generator/adapters/appium/appium-adapter.ts +1 -1
  218. package/src/generators/test-generator/adapters/appium/templates/steps/actions/keyboard-global-action.hbs +3 -1
  219. package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +1 -1
  220. package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +6 -0
  221. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/keyboard-global-action.hbs +3 -1
  222. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/page-assertion.hbs +1 -1
  223. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/storage-key-assertion.hbs +4 -0
  224. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/tab-order-assertion.hbs +30 -0
  225. package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/navigation.hbs +4 -0
  226. package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/route-assertion.hbs +1 -4
  227. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/dialog-root.hbs +1 -1
  228. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/url-assertion.hbs +15 -0
  229. package/src/generators/test-generator/code-generator.ts +91 -8
  230. package/src/generators/test-generator/diagnostics.ts +16 -1
  231. package/src/generators/test-generator/recipe-expander.ts +2 -2
  232. package/src/generators/test-generator/step-mapper.ts +160 -15
  233. package/src/generators/test-generator/template-engine.ts +2 -2
  234. package/src/generators/test-generator/utils/data-resolver.ts +27 -5
  235. package/src/generators/test-generator/utils/runtime-data-transformer.ts +57 -11
  236. package/src/generators/test-generator/utils/selector-resolver.ts +29 -5
  237. package/src/harness/audit.ts +10 -9
  238. package/src/harness/capability-plan.ts +2 -2
  239. package/src/harness/catalog/drivers.yaml +1 -1
  240. package/src/harness/challenge.ts +2 -3
  241. package/src/harness/data-driven-lint.ts +13 -9
  242. package/src/harness/depth-lint.ts +2 -4
  243. package/src/harness/flow-check.ts +9 -7
  244. package/src/harness/flow-plan.ts +4 -3
  245. package/src/harness/manifest.ts +3 -3
  246. package/src/harness/parse.ts +28 -1
  247. package/src/harness/script-check.ts +61 -33
  248. package/src/harness/secret-scan.ts +23 -8
  249. package/src/harness/sensors.ts +15 -4
  250. package/src/harness/trace.ts +2 -2
  251. package/src/harness/unit-paths.ts +39 -2
  252. package/src/index.ts +1 -0
  253. package/src/orchestrator/screen-manager.ts +7 -2
  254. package/src/orchestrator/templates/ai-src/commands/dashboard.md +2 -1
  255. package/src/orchestrator/templates/ai-src/commands/run-test.md +1 -1
  256. package/src/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +8 -0
  257. package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +4 -0
  258. package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +79 -2
  259. package/src/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +7 -1
  260. package/src/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +12 -3
  261. package/src/orchestrator/templates/playwright.config.ts +33 -2
  262. package/src/orchestrator/templates/specs-mock.ts +179 -0
  263. package/src/orchestrator/templates/specs-url-assert.ts +134 -0
  264. package/src/orchestrator/test-data-guide.ts +4 -0
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mock = void 0;
4
+ /** Resource types the fail-on-unmocked guard judges; everything else passes through.
5
+ * NOTE: page.route() does NOT intercept WebSocket connections (that is
6
+ * page.routeWebSocket, a separate API) — the shipped posture is fetch/XHR only. */
7
+ const GUARDED_TYPES = new Set(['fetch', 'xhr']);
8
+ class MockRouter {
9
+ constructor() {
10
+ this.state = new WeakMap();
11
+ }
12
+ stateFor(page) {
13
+ let st = this.state.get(page);
14
+ if (!st) {
15
+ st = { counts: new Map(), installed: new Set(), routes: [], guardInstalled: false, passthrough: false, violations: [], backgroundRouteCount: -1, backgroundNames: new Set() };
16
+ this.state.set(page, st);
17
+ }
18
+ return st;
19
+ }
20
+ /** `@mock:passthrough` — unmatched requests reach the real backend for this page/scenario.
21
+ * Also forgives violations already recorded (Background installs + goto run BEFORE the
22
+ * scenario body can set this flag — boot-time requests of an opted-out scenario must not
23
+ * fail its assertNoUnmocked verdict). The compiler additionally arms the flag inside
24
+ * beforeEach via test.info().tags, before the Background installs, so boot requests are
25
+ * not even aborted on the default path. */
26
+ setPassthrough(page) {
27
+ const st = this.stateFor(page);
28
+ st.passthrough = true;
29
+ st.violations.length = 0;
30
+ }
31
+ /** Install one catalog entry. The first install also arms the fail-on-unmocked guard. */
32
+ async install(page, cfg) {
33
+ const st = this.stateFor(page);
34
+ if (!st.guardInstalled) {
35
+ st.guardInstalled = true;
36
+ const guard = async (route) => {
37
+ const req = route.request();
38
+ if (!GUARDED_TYPES.has(req.resourceType()) || st.passthrough)
39
+ return route.fallback();
40
+ // Record + abort — NEVER throw here: a route handler runs outside any test
41
+ // stack, so a throw surfaces as an unhandled rejection (a hard process crash
42
+ // outside @playwright/test, flaky attribution inside it). The compiler emits
43
+ // `await mock.assertNoUnmocked(page)` at the end of every @mock scenario,
44
+ // which fails the test deterministically with the FULL violation list.
45
+ st.violations.push(`${req.method()} ${req.url()}`);
46
+ await route.abort('failed');
47
+ };
48
+ await page.route('**/*', guard); // FIRST → matched last → every named mock wins
49
+ st.routes.push({ pattern: '**/*', handler: guard });
50
+ }
51
+ const pattern = cfg.url.startsWith('re:') ? new RegExp(cfg.url.slice(3)) : cfg.url;
52
+ const handler = async (route) => {
53
+ if (cfg.method !== '*' && route.request().method() !== cfg.method)
54
+ return route.fallback();
55
+ st.counts.set(cfg.name, (st.counts.get(cfg.name) ?? 0) + 1);
56
+ if (cfg.delay_ms)
57
+ await new Promise((r) => setTimeout(r, cfg.delay_ms));
58
+ // A delayed fulfill can race a page close / reset at teardown — that race must
59
+ // not surface as an unhandled rejection from inside a route handler.
60
+ try {
61
+ await route.fulfill({ status: cfg.status, headers: cfg.headers, body: cfg.body });
62
+ }
63
+ catch { /* page closing */ }
64
+ };
65
+ await page.route(pattern, handler);
66
+ st.routes.push({ pattern, handler });
67
+ st.installed.add(cfg.name);
68
+ if (!st.counts.has(cfg.name))
69
+ st.counts.set(cfg.name, 0);
70
+ }
71
+ /** Handled-request count for a mock installed in THIS scenario — throws for a name
72
+ * that was never installed here (an exact 0 on an uninstalled mock must be a loud
73
+ * error, not a passing assertion). */
74
+ callCount(page, name) {
75
+ const st = this.state.get(page);
76
+ if (!st || !st.installed.has(name)) {
77
+ throw new Error(`mock "${name}" was never installed in this scenario — add \`Given [${name}] mock is active\``);
78
+ }
79
+ return st.counts.get(name) ?? 0;
80
+ }
81
+ /** Fail-on-unmocked verdict — emitted by the compiler as the LAST statement of every
82
+ * @mock scenario. Throws with the full list of aborted unmatched requests; a scenario
83
+ * whose app swallowed those failed fetches must not stay green for the wrong reason. */
84
+ async assertNoUnmocked(page) {
85
+ const st = this.state.get(page);
86
+ if (!st || st.violations.length === 0)
87
+ return;
88
+ const list = st.violations.slice();
89
+ st.violations.length = 0;
90
+ throw new Error(`${list.length} unmocked request(s) hit the fail-on-unmocked guard:\n - ${list.join('\n - ')}\n` +
91
+ 'Add entries to mock/mocks.yaml, or opt out with @mock:passthrough.');
92
+ }
93
+ /** @serial only, emitted by the compiler at the END of the Background block (beforeAll):
94
+ * everything installed so far — Background mocks and the guard — persists across the
95
+ * feature's scenarios; reset() removes only what each scenario added on top. Without
96
+ * this, scenario 1's reset would kill the Background mocks and scenarios 2+ would run
97
+ * against the REAL backend with no guard — the silent-green class this driver exists
98
+ * to prevent. */
99
+ snapshotBackground(page) {
100
+ const st = this.stateFor(page);
101
+ st.backgroundRouteCount = st.routes.length;
102
+ st.backgroundNames = new Set(st.installed);
103
+ }
104
+ /** Scoped teardown for the @serial shared page: unroute exactly what THIS scenario
105
+ * installed (Background routes captured by snapshotBackground persist), zero every
106
+ * counter, clear flags/violations. Emitted at the end of each @mock scenario on the
107
+ * serial path; the default per-test path never needs it (page dies with the test). */
108
+ async reset(page) {
109
+ const st = this.state.get(page);
110
+ if (!st)
111
+ return;
112
+ if (st.backgroundRouteCount < 0) {
113
+ // No Background snapshot → drop everything (the pre-snapshot / default behavior).
114
+ for (const r of st.routes) {
115
+ try {
116
+ await page.unroute(r.pattern, r.handler);
117
+ }
118
+ catch { /* page may be closing */ }
119
+ }
120
+ this.state.delete(page);
121
+ return;
122
+ }
123
+ for (const r of st.routes.slice(st.backgroundRouteCount)) {
124
+ try {
125
+ await page.unroute(r.pattern, r.handler);
126
+ }
127
+ catch { /* page may be closing */ }
128
+ }
129
+ st.routes.length = st.backgroundRouteCount;
130
+ st.installed = new Set(st.backgroundNames);
131
+ st.counts = new Map();
132
+ st.violations.length = 0;
133
+ st.passthrough = false;
134
+ }
135
+ }
136
+ exports.mock = new MockRouter();
137
+ //# sourceMappingURL=specs-mock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"specs-mock.js","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-mock.ts"],"names":[],"mappings":"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"}
@@ -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();
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Sungen URL assertion helper — auto-generated into specs/url-assert.ts. DO NOT EDIT.
3
+ *
4
+ * Every Then-side URL assertion (`see [X] page`, `is on [X] page`) compiles to one call here.
5
+ *
6
+ * Why a helper and not an inline predicate. The predicate used to be emitted inline into every
7
+ * spec, so each correction to what "the URL matches" MEANS was a breaking change for every
8
+ * generated suite in the field: regenerate everything, re-review every newly-red test. Behind a
9
+ * helper the same correction ships as a runtime patch. The generator's job shrinks to what it
10
+ * alone knows — the pathname pattern — and the semantics live in one readable place.
11
+ *
12
+ * Why a predicate and not two assertions. The shape before this one was
13
+ * await expect(page).toHaveURL(/\/vi\/search/); // unanchored: passes on /vi/search-x
14
+ * expect(page.url()).toContain('<query>'); // synchronous, one-shot: never retries
15
+ * — two half-checks, neither anchored, and nothing ever compared pathname and query together.
16
+ * `toHaveURL(predicate)` is web-first (it retries like any other assertion), receives a parsed
17
+ * URL, and needs no regex escaping of runtime values, which is what forced the split.
18
+ */
19
+ export interface UrlExpectation {
20
+ /**
21
+ * Anchored pathname pattern, compiled by the generator from the page selector's `value`
22
+ * (`:id` segments already wildcarded). Anchored against `u.pathname` ONLY: pathname never
23
+ * carries the query, so `^…$` is exact there — the same source that was unsafe to anchor
24
+ * against a full URL.
25
+ */
26
+ pathname: RegExp;
27
+ /**
28
+ * The `with {{v}}` value, raw from test-data: '?q=1' · '/vi/search?q=1' · '#tab' · ''.
29
+ * `undefined` means the step declared nothing, which is itself an assertion — see below.
30
+ */
31
+ declared?: string;
32
+ /** The test-data key behind `declared`, so a failure can name what an author must go fix. */
33
+ dataRef?: string;
34
+ }
35
+ /**
36
+ * Join a page's URL with the `with {{v}}` value for a NAVIGATION step
37
+ * (`Given User is on [X] page with {{v}}`).
38
+ *
39
+ * Why this is not a plain `base + path + value`. It used to be, and an author writing the value as a
40
+ * whole relative URL — the habit the assertion side already had to accommodate — navigated to
41
+ * `/vi/search/vi/search?q=1`: the page's own path, twice. The Then side then asserted the real path
42
+ * correctly, so the scenario could never go green.
43
+ *
44
+ * `base` (the configured baseURL, possibly '') and `path` (the page selector's value) arrive
45
+ * SEPARATELY so a value that carries its own rooted path replaces the page's path without also
46
+ * discarding a path prefix baked into the baseURL — a deployment under `https://host/tenant-a`
47
+ * would otherwise navigate to the wrong tenant, and only when a `with {{v}}` was present, which is
48
+ * exactly the kind of inconsistency nobody would think to look for.
49
+ *
50
+ * What it deliberately does NOT do is sanitize. A test may legitimately drive a MALFORMED query to
51
+ * prove the app rejects it (issue #464), so anything that is not a duplicated path is appended
52
+ * verbatim: no encoding, no normalising, no reordering.
53
+ */
54
+ export declare function joinPath(base: string, path: string, declared?: string | null): string;
55
+ /**
56
+ * Build the predicate for `expect(page).toHaveURL(...)`.
57
+ *
58
+ * Query semantics are deliberately asymmetric:
59
+ * no `with {{v}}` → the URL must carry NO query. A bare page means a bare page, so
60
+ * `/vi/search` does not pass on `/vi/search?x=1`.
61
+ * `with {{v}}` → every declared param must match; extra params are tolerated, so a
62
+ * tracking/UTM param the test never cared about cannot turn it red.
63
+ * A declared fragment is asserted too; a declared value carrying no query
64
+ * at all constrains the path only (the compiler warns: SG-W014).
65
+ */
66
+ export declare function urlMatches({ pathname, declared, dataRef }: UrlExpectation): (u: URL) => boolean;
67
+ //# sourceMappingURL=specs-url-assert.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"specs-url-assert.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-url-assert.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6FAA6F;IAC7F,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAYrF;AAeD;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,cAAc,GAAG,CAAC,CAAC,EAAE,GAAG,KAAK,OAAO,CAuC/F"}
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ /* eslint-disable */
3
+ /**
4
+ * Sungen URL assertion helper — auto-generated into specs/url-assert.ts. DO NOT EDIT.
5
+ *
6
+ * Every Then-side URL assertion (`see [X] page`, `is on [X] page`) compiles to one call here.
7
+ *
8
+ * Why a helper and not an inline predicate. The predicate used to be emitted inline into every
9
+ * spec, so each correction to what "the URL matches" MEANS was a breaking change for every
10
+ * generated suite in the field: regenerate everything, re-review every newly-red test. Behind a
11
+ * helper the same correction ships as a runtime patch. The generator's job shrinks to what it
12
+ * alone knows — the pathname pattern — and the semantics live in one readable place.
13
+ *
14
+ * Why a predicate and not two assertions. The shape before this one was
15
+ * await expect(page).toHaveURL(/\/vi\/search/); // unanchored: passes on /vi/search-x
16
+ * expect(page.url()).toContain('<query>'); // synchronous, one-shot: never retries
17
+ * — two half-checks, neither anchored, and nothing ever compared pathname and query together.
18
+ * `toHaveURL(predicate)` is web-first (it retries like any other assertion), receives a parsed
19
+ * URL, and needs no regex escaping of runtime values, which is what forced the split.
20
+ */
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.joinPath = joinPath;
23
+ exports.urlMatches = urlMatches;
24
+ /**
25
+ * Join a page's URL with the `with {{v}}` value for a NAVIGATION step
26
+ * (`Given User is on [X] page with {{v}}`).
27
+ *
28
+ * Why this is not a plain `base + path + value`. It used to be, and an author writing the value as a
29
+ * whole relative URL — the habit the assertion side already had to accommodate — navigated to
30
+ * `/vi/search/vi/search?q=1`: the page's own path, twice. The Then side then asserted the real path
31
+ * correctly, so the scenario could never go green.
32
+ *
33
+ * `base` (the configured baseURL, possibly '') and `path` (the page selector's value) arrive
34
+ * SEPARATELY so a value that carries its own rooted path replaces the page's path without also
35
+ * discarding a path prefix baked into the baseURL — a deployment under `https://host/tenant-a`
36
+ * would otherwise navigate to the wrong tenant, and only when a `with {{v}}` was present, which is
37
+ * exactly the kind of inconsistency nobody would think to look for.
38
+ *
39
+ * What it deliberately does NOT do is sanitize. A test may legitimately drive a MALFORMED query to
40
+ * prove the app rejects it (issue #464), so anything that is not a duplicated path is appended
41
+ * verbatim: no encoding, no normalising, no reordering.
42
+ */
43
+ function joinPath(base, path, declared) {
44
+ if (declared === undefined || declared === null || declared === '')
45
+ return base + path;
46
+ const value = String(declared);
47
+ // A query/fragment appends — the overwhelmingly common shape, and the only one #464 needs.
48
+ if (value.startsWith('?') || value.startsWith('#'))
49
+ return base + path + value;
50
+ // An absolute URL replaces everything: the author pasted a full address.
51
+ if (/^[a-z]+:\/\//i.test(value))
52
+ return value;
53
+ // A rooted path carries its own path, so it replaces the PAGE's path while everything the
54
+ // baseURL contributed — scheme, host, and any path prefix — is kept.
55
+ if (value.startsWith('/'))
56
+ return base + value;
57
+ // Anything else is raw input the test wants appended as-is.
58
+ return base + path + value;
59
+ }
60
+ /**
61
+ * `u.pathname` is percent-encoded; a regex built from a selector's literal `value` is not, so a
62
+ * non-ASCII route (`/読取結果一覧`) could never match. Decoding can throw on a lone `%`, in which
63
+ * case the raw form is all there is.
64
+ */
65
+ function decodedPath(pathname) {
66
+ try {
67
+ return decodeURIComponent(pathname);
68
+ }
69
+ catch {
70
+ return pathname;
71
+ }
72
+ }
73
+ /**
74
+ * Build the predicate for `expect(page).toHaveURL(...)`.
75
+ *
76
+ * Query semantics are deliberately asymmetric:
77
+ * no `with {{v}}` → the URL must carry NO query. A bare page means a bare page, so
78
+ * `/vi/search` does not pass on `/vi/search?x=1`.
79
+ * `with {{v}}` → every declared param must match; extra params are tolerated, so a
80
+ * tracking/UTM param the test never cared about cannot turn it red.
81
+ * A declared fragment is asserted too; a declared value carrying no query
82
+ * at all constrains the path only (the compiler warns: SG-W014).
83
+ */
84
+ function urlMatches({ pathname, declared, dataRef }) {
85
+ return (u) => {
86
+ if (!pathname.test(u.pathname) && !pathname.test(decodedPath(u.pathname)))
87
+ return false;
88
+ // `dataRef`, not `declared`, decides which rule applies. Both "the step declared nothing" and
89
+ // "the step declared {{v}} but the key resolved to nothing at runtime" arrive here as
90
+ // undefined, and they mean opposite things: the first is a real assertion (a bare page must be
91
+ // bare), the second is broken test-data. Only the generator knows which, and it says so by
92
+ // emitting `dataRef` exactly when the step carried a `with {{v}}`.
93
+ if (!dataRef)
94
+ return u.search === '';
95
+ // Silence is the whole bug class this assertion exists to kill, so an unusable declared value
96
+ // is an ERROR, not a quiet verdict. Returning false would read as "the app is wrong"; these
97
+ // throws say "your test-data is wrong, and here is the key". An empty value used to parse to
98
+ // an empty param list, and `[].every()` is vacuously TRUE — it passed on any query at all.
99
+ if (declared === undefined || declared === null) {
100
+ throw new Error(`url assertion: test-data key "${dataRef}" is missing — nothing to assert`);
101
+ }
102
+ if (String(declared).trim() === '') {
103
+ throw new Error(`url assertion: test-data key "${dataRef}" is empty — nothing to assert`);
104
+ }
105
+ // Parsing against the live pathname as base lets ONE expression handle every shape an author
106
+ // writes: '?q=1' (query only), '/vi/search?q=1' (a whole relative URL — its path is ignored,
107
+ // the page selector already owns the path), and '#tab' (fragment only). The predecessor
108
+ // regex-stripped the prefix instead, which silently turned a value with NO query
109
+ // ('/vi/search') into a bogus param NAME — an assertion that could never be satisfied.
110
+ const d = new URL(String(declared), 'http://sungen.invalid' + u.pathname);
111
+ // getAll, not get: a multi-select filter (?tag=a&tag=b) is the exact case this assertion is
112
+ // for, and `get()` returns only the first value — so the correct URL failed. Comparing as a
113
+ // subset per key keeps this consistent with "extra params are tolerated", and makes param
114
+ // order meaningless, which is what a URL actually means.
115
+ const queryOk = [...d.searchParams].every(([k, v]) => u.searchParams.getAll(k).includes(v));
116
+ // Only assert a fragment the author actually declared.
117
+ const hashOk = !d.hash || d.hash === u.hash;
118
+ return queryOk && hashOk;
119
+ };
120
+ }
121
+ //# sourceMappingURL=specs-url-assert.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"specs-url-assert.js","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-url-assert.ts"],"names":[],"mappings":"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"}