@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.
- package/dist/capabilities/discover.d.ts.map +1 -1
- package/dist/capabilities/discover.js +4 -1
- package/dist/capabilities/discover.js.map +1 -1
- package/dist/cli/commands/add.d.ts.map +1 -1
- package/dist/cli/commands/add.js +4 -2
- package/dist/cli/commands/add.js.map +1 -1
- package/dist/cli/commands/dashboard.d.ts.map +1 -1
- package/dist/cli/commands/dashboard.js +10 -1
- package/dist/cli/commands/dashboard.js.map +1 -1
- package/dist/cli/commands/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +19 -1
- package/dist/cli/commands/delivery.js.map +1 -1
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +4 -1
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/dashboard/html-renderer.d.ts.map +1 -1
- package/dist/dashboard/html-renderer.js +7 -2
- package/dist/dashboard/html-renderer.js.map +1 -1
- package/dist/dashboard/snapshot-builder.d.ts.map +1 -1
- package/dist/dashboard/snapshot-builder.js +3 -0
- package/dist/dashboard/snapshot-builder.js.map +1 -1
- package/dist/dashboard/types.d.ts +4 -0
- package/dist/dashboard/types.d.ts.map +1 -1
- package/dist/exporters/api-environment.d.ts.map +1 -1
- package/dist/exporters/api-environment.js +4 -24
- package/dist/exporters/api-environment.js.map +1 -1
- package/dist/exporters/api-testcase-formatter.d.ts +6 -0
- package/dist/exporters/api-testcase-formatter.d.ts.map +1 -1
- package/dist/exporters/api-testcase-formatter.js +11 -4
- package/dist/exporters/api-testcase-formatter.js.map +1 -1
- package/dist/exporters/csv-exporter.d.ts.map +1 -1
- package/dist/exporters/csv-exporter.js +40 -9
- package/dist/exporters/csv-exporter.js.map +1 -1
- package/dist/exporters/env-qa.d.ts +13 -0
- package/dist/exporters/env-qa.d.ts.map +1 -0
- package/dist/exporters/env-qa.js +82 -0
- package/dist/exporters/env-qa.js.map +1 -0
- package/dist/exporters/feature-parser.d.ts.map +1 -1
- package/dist/exporters/feature-parser.js +16 -33
- package/dist/exporters/feature-parser.js.map +1 -1
- package/dist/exporters/json-exporter.d.ts.map +1 -1
- package/dist/exporters/json-exporter.js +57 -20
- package/dist/exporters/json-exporter.js.map +1 -1
- package/dist/exporters/matrix/build.d.ts.map +1 -1
- package/dist/exporters/matrix/build.js +6 -5
- package/dist/exporters/matrix/build.js.map +1 -1
- package/dist/exporters/matrix/gates.d.ts.map +1 -1
- package/dist/exporters/matrix/gates.js +3 -2
- package/dist/exporters/matrix/gates.js.map +1 -1
- package/dist/exporters/matrix/step-buckets.d.ts +13 -0
- package/dist/exporters/matrix/step-buckets.d.ts.map +1 -0
- package/dist/exporters/matrix/step-buckets.js +7 -0
- package/dist/exporters/matrix/step-buckets.js.map +1 -0
- package/dist/exporters/qa-env-secret-denylist.d.ts +43 -0
- package/dist/exporters/qa-env-secret-denylist.d.ts.map +1 -0
- package/dist/exporters/qa-env-secret-denylist.js +143 -0
- package/dist/exporters/qa-env-secret-denylist.js.map +1 -0
- package/dist/exporters/scenario-merger.d.ts +20 -13
- package/dist/exporters/scenario-merger.d.ts.map +1 -1
- package/dist/exporters/scenario-merger.js +55 -30
- package/dist/exporters/scenario-merger.js.map +1 -1
- package/dist/exporters/step-formatter.d.ts +42 -33
- package/dist/exporters/step-formatter.d.ts.map +1 -1
- package/dist/exporters/step-formatter.js +68 -68
- package/dist/exporters/step-formatter.js.map +1 -1
- package/dist/exporters/types.d.ts +9 -8
- package/dist/exporters/types.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts +1 -0
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts +1 -0
- package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/appium/appium-adapter.js.map +1 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/keyboard-global-action.hbs +3 -1
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +1 -0
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/keyboard-global-action.hbs +3 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/page-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/storage-key-assertion.hbs +4 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/tab-order-assertion.hbs +30 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/route-assertion.hbs +1 -4
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/dialog-root.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/url-assertion.hbs +10 -0
- package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.js +70 -7
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/generators/test-generator/diagnostics.d.ts +7 -1
- package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
- package/dist/generators/test-generator/diagnostics.js +6 -0
- package/dist/generators/test-generator/diagnostics.js.map +1 -1
- package/dist/generators/test-generator/recipe-expander.d.ts +1 -1
- package/dist/generators/test-generator/recipe-expander.d.ts.map +1 -1
- package/dist/generators/test-generator/recipe-expander.js +1 -1
- package/dist/generators/test-generator/recipe-expander.js.map +1 -1
- package/dist/generators/test-generator/step-mapper.d.ts +17 -0
- package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.js +108 -17
- package/dist/generators/test-generator/step-mapper.js.map +1 -1
- package/dist/generators/test-generator/template-engine.d.ts +1 -0
- package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
- package/dist/generators/test-generator/template-engine.js +1 -1
- package/dist/generators/test-generator/template-engine.js.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.d.ts +7 -1
- package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.js +8 -4
- package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
- package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts +2 -2
- package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/runtime-data-transformer.js +56 -11
- package/dist/generators/test-generator/utils/runtime-data-transformer.js.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.d.ts +18 -2
- package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.js +29 -6
- package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
- package/dist/harness/audit.d.ts.map +1 -1
- package/dist/harness/audit.js +8 -7
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/capability-plan.d.ts.map +1 -1
- package/dist/harness/capability-plan.js +2 -3
- package/dist/harness/capability-plan.js.map +1 -1
- package/dist/harness/catalog/drivers.yaml +1 -1
- package/dist/harness/challenge.d.ts.map +1 -1
- package/dist/harness/challenge.js +1 -55
- package/dist/harness/challenge.js.map +1 -1
- package/dist/harness/data-driven-lint.d.ts.map +1 -1
- package/dist/harness/data-driven-lint.js +14 -9
- package/dist/harness/data-driven-lint.js.map +1 -1
- package/dist/harness/depth-lint.d.ts.map +1 -1
- package/dist/harness/depth-lint.js +1 -3
- package/dist/harness/depth-lint.js.map +1 -1
- package/dist/harness/flow-check.d.ts.map +1 -1
- package/dist/harness/flow-check.js +9 -7
- package/dist/harness/flow-check.js.map +1 -1
- package/dist/harness/flow-plan.d.ts.map +1 -1
- package/dist/harness/flow-plan.js +4 -3
- package/dist/harness/flow-plan.js.map +1 -1
- package/dist/harness/manifest.js +2 -2
- package/dist/harness/manifest.js.map +1 -1
- package/dist/harness/parse.d.ts +12 -0
- package/dist/harness/parse.d.ts.map +1 -1
- package/dist/harness/parse.js +28 -1
- package/dist/harness/parse.js.map +1 -1
- package/dist/harness/script-check.d.ts.map +1 -1
- package/dist/harness/script-check.js +52 -30
- package/dist/harness/script-check.js.map +1 -1
- package/dist/harness/secret-scan.d.ts +3 -1
- package/dist/harness/secret-scan.d.ts.map +1 -1
- package/dist/harness/secret-scan.js +14 -10
- package/dist/harness/secret-scan.js.map +1 -1
- package/dist/harness/sensors.d.ts.map +1 -1
- package/dist/harness/sensors.js +15 -4
- package/dist/harness/sensors.js.map +1 -1
- package/dist/harness/trace.js +1 -1
- package/dist/harness/trace.js.map +1 -1
- package/dist/harness/unit-paths.d.ts +16 -0
- package/dist/harness/unit-paths.d.ts.map +1 -1
- package/dist/harness/unit-paths.js +39 -2
- package/dist/harness/unit-paths.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/orchestrator/screen-manager.d.ts.map +1 -1
- package/dist/orchestrator/screen-manager.js +8 -2
- package/dist/orchestrator/screen-manager.js.map +1 -1
- package/dist/orchestrator/templates/ai-src/commands/dashboard.md +2 -1
- package/dist/orchestrator/templates/ai-src/commands/run-test.md +1 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +2 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +51 -2
- package/dist/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +6 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +6 -0
- package/dist/orchestrator/templates/playwright.config.ts +33 -2
- package/dist/orchestrator/templates/specs-mock.d.ts +66 -0
- package/dist/orchestrator/templates/specs-mock.d.ts.map +1 -0
- package/dist/orchestrator/templates/specs-mock.js +137 -0
- package/dist/orchestrator/templates/specs-mock.js.map +1 -0
- package/dist/orchestrator/templates/specs-mock.ts +179 -0
- package/dist/orchestrator/test-data-guide.d.ts.map +1 -1
- package/dist/orchestrator/test-data-guide.js +4 -0
- package/dist/orchestrator/test-data-guide.js.map +1 -1
- package/package.json +3 -3
- package/src/capabilities/discover.ts +4 -1
- package/src/cli/commands/add.ts +4 -2
- package/src/cli/commands/dashboard.ts +10 -1
- package/src/cli/commands/delivery.ts +19 -1
- package/src/cli/commands/generate.ts +4 -1
- package/src/dashboard/html-renderer.ts +8 -2
- package/src/dashboard/snapshot-builder.ts +3 -0
- package/src/dashboard/types.ts +4 -0
- package/src/exporters/api-environment.ts +3 -20
- package/src/exporters/api-testcase-formatter.ts +8 -2
- package/src/exporters/csv-exporter.ts +47 -8
- package/src/exporters/env-qa.ts +41 -0
- package/src/exporters/feature-parser.ts +17 -38
- package/src/exporters/json-exporter.ts +71 -24
- package/src/exporters/matrix/build.ts +6 -5
- package/src/exporters/matrix/gates.ts +3 -2
- package/src/exporters/matrix/step-buckets.ts +15 -0
- package/src/exporters/qa-env-secret-denylist.ts +118 -0
- package/src/exporters/scenario-merger.ts +80 -42
- package/src/exporters/step-formatter.ts +67 -66
- package/src/exporters/types.ts +9 -8
- package/src/generators/test-generator/adapters/adapter-interface.ts +1 -1
- package/src/generators/test-generator/adapters/appium/appium-adapter.ts +1 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/keyboard-global-action.hbs +3 -1
- package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/keyboard-global-action.hbs +3 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/page-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/storage-key-assertion.hbs +4 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/tab-order-assertion.hbs +30 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/route-assertion.hbs +1 -4
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/dialog-root.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/url-assertion.hbs +10 -0
- package/src/generators/test-generator/code-generator.ts +82 -7
- package/src/generators/test-generator/diagnostics.ts +7 -1
- package/src/generators/test-generator/recipe-expander.ts +2 -2
- package/src/generators/test-generator/step-mapper.ts +106 -15
- package/src/generators/test-generator/template-engine.ts +2 -2
- package/src/generators/test-generator/utils/data-resolver.ts +8 -5
- package/src/generators/test-generator/utils/runtime-data-transformer.ts +57 -11
- package/src/generators/test-generator/utils/selector-resolver.ts +29 -5
- package/src/harness/audit.ts +10 -9
- package/src/harness/capability-plan.ts +2 -2
- package/src/harness/catalog/drivers.yaml +1 -1
- package/src/harness/challenge.ts +2 -3
- package/src/harness/data-driven-lint.ts +13 -9
- package/src/harness/depth-lint.ts +2 -4
- package/src/harness/flow-check.ts +9 -7
- package/src/harness/flow-plan.ts +4 -3
- package/src/harness/manifest.ts +3 -3
- package/src/harness/parse.ts +28 -1
- package/src/harness/script-check.ts +52 -32
- package/src/harness/secret-scan.ts +11 -7
- package/src/harness/sensors.ts +15 -4
- package/src/harness/trace.ts +2 -2
- package/src/harness/unit-paths.ts +39 -2
- package/src/index.ts +1 -0
- package/src/orchestrator/screen-manager.ts +7 -2
- package/src/orchestrator/templates/ai-src/commands/dashboard.md +2 -1
- package/src/orchestrator/templates/ai-src/commands/run-test.md +1 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +2 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +51 -2
- package/src/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +6 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +6 -0
- package/src/orchestrator/templates/playwright.config.ts +33 -2
- package/src/orchestrator/templates/specs-mock.ts +179 -0
- 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
|
-
|
|
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
|
/**
|