@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.
- 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 +10 -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 +15 -6
- 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/test-data-resolver.d.ts +15 -0
- package/dist/exporters/test-data-resolver.d.ts.map +1 -1
- package/dist/exporters/test-data-resolver.js +34 -1
- package/dist/exporters/test-data-resolver.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 +2 -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 +2 -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 +2 -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 +6 -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/navigation.hbs +4 -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 +15 -0
- package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.js +78 -7
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/generators/test-generator/diagnostics.d.ts +16 -1
- package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
- package/dist/generators/test-generator/diagnostics.js +15 -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 +41 -0
- package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.js +161 -17
- package/dist/generators/test-generator/step-mapper.js.map +1 -1
- package/dist/generators/test-generator/template-engine.d.ts +2 -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 +18 -1
- package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.js +28 -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 +61 -31
- 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 +27 -11
- 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 +8 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +4 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +79 -2
- package/dist/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +7 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +12 -3
- 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/templates/specs-url-assert.d.ts +67 -0
- package/dist/orchestrator/templates/specs-url-assert.d.ts.map +1 -0
- package/dist/orchestrator/templates/specs-url-assert.js +121 -0
- package/dist/orchestrator/templates/specs-url-assert.js.map +1 -0
- package/dist/orchestrator/templates/specs-url-assert.ts +134 -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 +9 -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 +15 -6
- 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/test-data-resolver.ts +34 -1
- 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 +6 -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/navigation.hbs +4 -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 +15 -0
- package/src/generators/test-generator/code-generator.ts +91 -8
- package/src/generators/test-generator/diagnostics.ts +16 -1
- package/src/generators/test-generator/recipe-expander.ts +2 -2
- package/src/generators/test-generator/step-mapper.ts +160 -15
- package/src/generators/test-generator/template-engine.ts +2 -2
- package/src/generators/test-generator/utils/data-resolver.ts +27 -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 +61 -33
- package/src/harness/secret-scan.ts +23 -8
- 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 +8 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +4 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +79 -2
- package/src/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +7 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +12 -3
- package/src/orchestrator/templates/playwright.config.ts +33 -2
- package/src/orchestrator/templates/specs-mock.ts +179 -0
- package/src/orchestrator/templates/specs-url-assert.ts +134 -0
- 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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"}
|