@futdevpro/dynamo-e2e 1.18.14 → 1.18.16
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/CHANGELOG.md +28 -0
- package/README.md +47 -1
- package/bin/dye2e.js +13 -0
- package/build/_modules/generators/_collections/cli-runner.util.d.ts.map +1 -1
- package/build/_modules/generators/_collections/cli-runner.util.js +17 -0
- package/build/_modules/generators/_collections/cli-runner.util.js.map +1 -1
- package/build/_modules/runtime/_collections/notification-detector-page-script.const.d.ts +30 -0
- package/build/_modules/runtime/_collections/notification-detector-page-script.const.d.ts.map +1 -0
- package/build/_modules/runtime/_collections/notification-detector-page-script.const.js +169 -0
- package/build/_modules/runtime/_collections/notification-detector-page-script.const.js.map +1 -0
- package/build/_modules/runtime/_collections/notification-detector.util.d.ts +47 -0
- package/build/_modules/runtime/_collections/notification-detector.util.d.ts.map +1 -0
- package/build/_modules/runtime/_collections/notification-detector.util.js +165 -0
- package/build/_modules/runtime/_collections/notification-detector.util.js.map +1 -0
- package/build/_modules/runtime/_collections/notification-sweep.util.d.ts +65 -0
- package/build/_modules/runtime/_collections/notification-sweep.util.d.ts.map +1 -0
- package/build/_modules/runtime/_collections/notification-sweep.util.js +326 -0
- package/build/_modules/runtime/_collections/notification-sweep.util.js.map +1 -0
- package/build/_modules/runtime/_models/notification-report.interface.d.ts +54 -0
- package/build/_modules/runtime/_models/notification-report.interface.d.ts.map +1 -0
- package/build/_modules/runtime/_models/notification-report.interface.js +3 -0
- package/build/_modules/runtime/_models/notification-report.interface.js.map +1 -0
- package/build/_modules/runtime/_models/notification-sweep.interface.d.ts +67 -0
- package/build/_modules/runtime/_models/notification-sweep.interface.d.ts.map +1 -0
- package/build/_modules/runtime/_models/notification-sweep.interface.js +3 -0
- package/build/_modules/runtime/_models/notification-sweep.interface.js.map +1 -0
- package/build/_modules/runtime/index.d.ts +5 -0
- package/build/_modules/runtime/index.d.ts.map +1 -1
- package/build/_modules/runtime/index.js +5 -0
- package/build/_modules/runtime/index.js.map +1 -1
- package/build/index.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,34 @@
|
|
|
3
3
|
Minden release a `dynamo-e2e-foundation` HYPERPLAN MP-N ütemezést követi.
|
|
4
4
|
Verzióformátum: `MM.mm.p` (két-számjegyes major-minor, Dynamo-konvenció).
|
|
5
5
|
|
|
6
|
+
## [01.18.16] — 2026-09-24 — Login-flow notification sweep
|
|
7
|
+
|
|
8
|
+
- Added `DyE2E_NotificationSweep_Util` to `/runtime` and the `dye2e notification-sweep` command (BFR-QA-002): on top of
|
|
9
|
+
the notification detector it opens the login page of every target, submits it with a WRONG password (the error
|
|
10
|
+
branch, no test account needed) and returns one matrix row per project — notifications · error/warning · banners ·
|
|
11
|
+
modal dialogs · raw-technical leaks · console errors. Targets: the QA workspace's `TEST-TARGETS.md` (only
|
|
12
|
+
`https://test.` rows) or JSON.
|
|
13
|
+
- Guards in code: the probe address must be on a reserved or a declared own domain (else it throws before any page is
|
|
14
|
+
opened); the form is submitted only on a test host (else loaded, not submitted, and the row says so).
|
|
15
|
+
- Modal dialogs are recorded (text + leak flag) before they are closed: on 3 of 7 TEST sites a "not the production
|
|
16
|
+
system" notice blocked the login, and on one the dialog was itself a JS error shown to the user.
|
|
17
|
+
- Detector fixes found by the live sweep: a banner's inner elements are no longer counted as more banners (one login
|
|
18
|
+
error was counted 3×), and `HTTP 401`-style status codes and user-visible JS errors (`… is not a function`,
|
|
19
|
+
`TypeError`, `this.x.`) now count as raw technical leaks.
|
|
20
|
+
|
|
21
|
+
## [01.18.15] — 2026-09-24 — Live notification detector
|
|
22
|
+
|
|
23
|
+
- Added `DyE2E_NotificationDetector_Util` to `/runtime` (BFR-QA-001): `attach(context)` puts a `MutationObserver` into
|
|
24
|
+
every document BEFORE its scripts run and records each DyNX notification card / notifications area / mat-snackbar /
|
|
25
|
+
`[role=alert]` the moment it appears, classified error / warning / success / info — so an auto-dismissed toast is
|
|
26
|
+
not lost. `collect(page)` returns those plus the visible error banners (with a raw-technical-leak flag) and the
|
|
27
|
+
page's console errors/warnings and page errors; `summarize()` prints them.
|
|
28
|
+
- Captures go to Node through a Playwright binding, so they survive a full-page navigation (the redirect after
|
|
29
|
+
login). A capture is re-read 50 / 300 ms later to pick up content Angular renders after inserting the host element.
|
|
30
|
+
- The page-side code is plain JavaScript (`DYE2E_NOTIFICATION_DETECTOR_PAGE_SCRIPT`), so no `__name` shim is needed.
|
|
31
|
+
- 12 specs, 9 of them in a real Chromium (fixture pages served by `route`); the browser specs are reported as pending
|
|
32
|
+
with the reason when no Chromium can be started.
|
|
33
|
+
|
|
6
34
|
## [01.18.14] — 2026-09-23 — The form-level empty-submit case honours `fieldErrorSelector`
|
|
7
35
|
|
|
8
36
|
- `DyE2E_FormLevelEmission_Util.emitSubmitEmpty` now uses the form's `fieldErrorSelector` (default unchanged:
|
package/README.md
CHANGED
|
@@ -68,9 +68,55 @@ const specSource = DyE2E_FormSuite_Generator.emit({
|
|
|
68
68
|
|
|
69
69
|
- `@futdevpro/dynamo-e2e/contracts` — POJO types
|
|
70
70
|
- `@futdevpro/dynamo-e2e/generators` — Node-only emission engine
|
|
71
|
-
- `@futdevpro/dynamo-e2e/runtime` — Playwright fixtures + page-objects (Wave-4)
|
|
71
|
+
- `@futdevpro/dynamo-e2e/runtime` — Playwright fixtures + page-objects (Wave-4) + the live notification detector
|
|
72
|
+
(`DyE2E_NotificationDetector_Util`, see below)
|
|
72
73
|
- `@futdevpro/dynamo-e2e/presets` — pre-built suites + `DyE2E_getPlaywrightConfig` / `DyE2E_getGlobalSetup` playwright-config factory (fleet e2e-config SSOT)
|
|
73
74
|
- `@futdevpro/dynamo-e2e/game` — capability-gated deterministic game seam, failure artifacts and bidirectional journey-catalog validation
|
|
74
75
|
- `@futdevpro/dynamo-e2e/cwv` — Core Web Vitals (Wave-3)
|
|
75
76
|
|
|
77
|
+
## Live notification detector (`/runtime`)
|
|
78
|
+
|
|
79
|
+
Records every user-visible notification — DyNX `dynamo-notification-card` / `dynamo-notifications-area`, mat-snackbar
|
|
80
|
+
(`red-` / `yellow-` / `blue-` / `green-snackbar`), `[role=alert]` — at the moment it APPEARS, so a toast that
|
|
81
|
+
auto-dismissed before the end of the test is still in the report. It also snapshots the visible error banners (with a
|
|
82
|
+
`rawTechnicalLeak` flag for texts like `Http failure response`, `/api/`, `[object Object]`) and collects the page's
|
|
83
|
+
`console` errors/warnings and uncaught `pageerror`s.
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import { DyE2E_NotificationDetector_Util } from '@futdevpro/dynamo-e2e/runtime';
|
|
87
|
+
|
|
88
|
+
await DyE2E_NotificationDetector_Util.attach(context); // before the first navigation
|
|
89
|
+
// ... the flow ...
|
|
90
|
+
const report = await DyE2E_NotificationDetector_Util.collect(page);
|
|
91
|
+
expect(report.notifications.filter((n) => n.type === 'error')).toEqual([]);
|
|
92
|
+
console.log(DyE2E_NotificationDetector_Util.summarize(report, 'login'));
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Captures survive a full-page navigation (they are reported to Node through a Playwright binding). The page-side code
|
|
96
|
+
is plain JavaScript, so no `__name` shim is needed under `tsx` / esbuild. `collect()` throws on a context without
|
|
97
|
+
`attach()` — an empty report would otherwise read as "no notifications".
|
|
98
|
+
|
|
99
|
+
## Login-flow notification sweep (`/runtime` + CLI)
|
|
100
|
+
|
|
101
|
+
Built on the detector: opens the login page of many projects, submits it with a WRONG password (so the ERROR branch
|
|
102
|
+
is measured without a test account) and prints one matrix row per project — notifications · error/warning · banners ·
|
|
103
|
+
modal dialogs · raw-technical leaks · console errors. Framework-level defects show up only when many projects are
|
|
104
|
+
measured the same way.
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
dye2e notification-sweep --targets TEST-TARGETS.md --json sweep.json # or a JSON [{ key, loginUrl | baseUrl }]
|
|
108
|
+
dye2e notification-sweep --targets targets.json --only a,b --fail-on-leak # exit 1 on a raw technical leak
|
|
109
|
+
```
|
|
110
|
+
```ts
|
|
111
|
+
const outcomes = await DyE2E_NotificationSweep_Util.sweep(browser, targets, { settleMs: 5000 });
|
|
112
|
+
console.log(DyE2E_NotificationSweep_Util.matrix(outcomes.map((o) => o.row)));
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Guards in code, not in docs: the probe address must be on a reserved domain (`example.com`, `*.test`, `*.invalid` …)
|
|
116
|
+
or on one you declare with `ownDomains` — anything else throws before a page is opened; the form is submitted only on
|
|
117
|
+
a test host (`test.*`, `*.test`, `localhost` …, or `allowSubmitHosts`) — anything else is loaded but NOT submitted,
|
|
118
|
+
and the row says so. `real: true` / `--real` uses `E2E_PERSISTENT_USER_EMAIL` / `_PASSWORD`; without them the row says
|
|
119
|
+
it fell back to the probe. Modal dialogs (a "not the production system" notice, an error dialog) are RECORDED —
|
|
120
|
+
text + leak flag — before they are closed; a non-modal cookie bar is left alone.
|
|
121
|
+
|
|
76
122
|
Detailed game-preset contract: [`__documentations/game-preset.md`](__documentations/game-preset.md).
|
package/bin/dye2e.js
CHANGED
|
@@ -403,6 +403,19 @@ function main() {
|
|
|
403
403
|
process.exit(0);
|
|
404
404
|
}
|
|
405
405
|
|
|
406
|
+
if (parsed.command === 'notification-sweep') {
|
|
407
|
+
// BFR-QA-002 — the logic is TypeScript (runtime module); needs @playwright/test in the consumer
|
|
408
|
+
const { DyE2E_NotificationSweep_Util } = require('../build/_modules/runtime/_collections/notification-sweep.util');
|
|
409
|
+
DyE2E_NotificationSweep_Util.runCli(parsed.flags).then(
|
|
410
|
+
(code) => process.exit(code),
|
|
411
|
+
(error) => {
|
|
412
|
+
process.stderr.write('[dye2e] notification-sweep failed: ' + (error && error.stack ? error.stack : error) + '\n');
|
|
413
|
+
process.exit(1);
|
|
414
|
+
},
|
|
415
|
+
);
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
|
|
406
419
|
if (parsed.command === 'bundle-summary') {
|
|
407
420
|
const bundleP = parsed.flags.bundle;
|
|
408
421
|
if (!bundleP) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli-runner.util.d.ts","sourceRoot":"","sources":["../../../../src/_modules/generators/_collections/cli-runner.util.ts"],"names":[],"mappings":"AAOA;;;;;GAKG;AACH,qBAAa,oBAAoB;IAE/B,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE;IAsBpF,MAAM,CAAC,QAAQ,IAAI,MAAM;
|
|
1
|
+
{"version":3,"file":"cli-runner.util.d.ts","sourceRoot":"","sources":["../../../../src/_modules/generators/_collections/cli-runner.util.ts"],"names":[],"mappings":"AAOA;;;;;GAKG;AACH,qBAAa,oBAAoB;IAE/B,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE;IAsBpF,MAAM,CAAC,QAAQ,IAAI,MAAM;IAyGzB;;;OAGG;IACH,MAAM,CAAC,cAAc,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IA0D/C;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE;QAAE,UAAU,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;QAAC,YAAY,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,MAAM,CAAA;KAAE,GAAG;QAC9H,YAAY,EAAE,OAAO,CAAC;QACtB,iBAAiB,EAAE,OAAO,CAAC;QAC3B,mBAAmB,EAAE,OAAO,CAAC;QAC7B,SAAS,EAAE,OAAO,CAAC;QACnB,aAAa,EAAE,OAAO,CAAC;QACvB,WAAW,EAAE,MAAM,EAAE,CAAC;KACvB;IA2CD,MAAM,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAI3C;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM;IAuB9D;;;OAGG;IACH,MAAM,CAAC,WAAW,CAChB,OAAO,EAAE,qBAAqB,GAAG,yBAAyB,GAAG,mBAAmB,EAChF,UAAU,EAAE,MAAM,GACjB,MAAM;CAaV"}
|
|
@@ -61,6 +61,23 @@ Visual QA (/vqa) commands:
|
|
|
61
61
|
generate-comparison-bundle Before/after capture sets -> comparison bundle
|
|
62
62
|
bundle-summary Print an existing bundle's summary.md
|
|
63
63
|
|
|
64
|
+
QA commands:
|
|
65
|
+
notification-sweep Login pages -> wrong-password submit -> notification / banner / leak matrix
|
|
66
|
+
|
|
67
|
+
Flags (notification-sweep):
|
|
68
|
+
--targets <path> TEST-TARGETS.md (only https://test. rows) or JSON [{ key, loginUrl | baseUrl }] (required)
|
|
69
|
+
--only <a,b> Only these target keys
|
|
70
|
+
--login-paths <k=/p,..> Per-target login path (default /landing/login)
|
|
71
|
+
--no-submit Only load the login pages
|
|
72
|
+
--real Use E2E_PERSISTENT_USER_EMAIL / _PASSWORD (else the wrong-password probe)
|
|
73
|
+
--probe-email <addr> Probe address — reserved domain or one of --own-domains ONLY
|
|
74
|
+
--own-domains <d,..> Your own e-mail domains allowed for the probe address
|
|
75
|
+
--allow-hosts <h,..> Extra hosts the form may be submitted to (default: test.*, *.test, localhost)
|
|
76
|
+
--settle-ms / --after-submit-ms <ms> Waits (default 5000 / 9000)
|
|
77
|
+
--json <out> Write rows + full reports as JSON
|
|
78
|
+
--fail-on-leak Exit 1 if any page shows a raw technical leak
|
|
79
|
+
--executable-path <exe> Chromium to launch (or DYE2E_CHROMIUM_EXECUTABLE_PATH)
|
|
80
|
+
|
|
64
81
|
Flags (preset):
|
|
65
82
|
--name <preset> Print this preset as JSON (omit to list all)
|
|
66
83
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli-runner.util.js","sourceRoot":"","sources":["../../../../src/_modules/generators/_collections/cli-runner.util.ts"],"names":[],"mappings":";;;AAAA,kEAAoE;AACpE,0EAA4E;AAC5E,uEAAyE;AAKzE;;;;;GAKG;AACH,MAAa,oBAAoB;IAE/B,MAAM,CAAC,SAAS,CAAC,IAAc;QAC7B,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtB,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;QACxC,CAAC;QACD,MAAM,OAAO,GAAW,IAAI,CAAC,CAAC,CAAC,CAAC;QAChC,MAAM,KAAK,GAA2B,EAAE,CAAC;QACzC,KAAK,IAAI,CAAC,GAAW,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC7C,MAAM,GAAG,GAAW,IAAI,CAAC,CAAC,CAAC,CAAC;YAC5B,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;gBACzB,MAAM,GAAG,GAAW,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBACjC,MAAM,IAAI,GAAuB,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;gBAC7C,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;oBACnC,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;oBAClB,CAAC,EAAE,CAAC;gBACN,CAAC;qBAAM,CAAC;oBACN,KAAK,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC;gBACtB,CAAC;YACH,CAAC;QACH,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC5B,CAAC;IAED,MAAM,CAAC,QAAQ;QACb,OAAO
|
|
1
|
+
{"version":3,"file":"cli-runner.util.js","sourceRoot":"","sources":["../../../../src/_modules/generators/_collections/cli-runner.util.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Name of the Playwright binding the page script reports every capture to (BFR-QA-001). Node keeps the
|
|
3
|
+
* captures, so a notification survives a full-page navigation (e.g. the redirect after login) and the page close.
|
|
4
|
+
*/
|
|
5
|
+
export declare const DYE2E_NOTIFICATION_BINDING_NAME: string;
|
|
6
|
+
/**
|
|
7
|
+
* Page-side notification detector — `addInitScript` input (BFR-QA-001).
|
|
8
|
+
*
|
|
9
|
+
* **PURE JavaScript** (not TypeScript), like `DYE2E_WEB_VITALS_PAGE_SCRIPT`: the browser runs it as-is, so no
|
|
10
|
+
* transpiler helper (`__name` of tsx/esbuild) can leak into the page — no shim is needed in any consumer setup.
|
|
11
|
+
*
|
|
12
|
+
* Observes the document from its very start (a `MutationObserver` on `document`, subtree) and records every DyNX
|
|
13
|
+
* notification card / notifications area / mat-snackbar / `[role=alert]` at the moment it is ADDED — so an
|
|
14
|
+
* auto-dismissed toast (DyNX error 14 s, others 7 s) is not lost. A capture is re-read 50 ms and 300 ms later to
|
|
15
|
+
* fill in what Angular renders after inserting the host element (title, message, colour class); the re-read keeps
|
|
16
|
+
* the capture id. Every capture (and re-read) goes to the Node binding, and to `window.__dye2e_notifications__`.
|
|
17
|
+
*
|
|
18
|
+
* Type classification (from `@futdevpro/ngx-dynamo` notifications): `bg-red-50` / `red-snackbar` / mat-icon `error`
|
|
19
|
+
* → error · `bg-orange-50` / `yellow-snackbar` / `warning` → warning · `bg-green-50` / `green-snackbar` /
|
|
20
|
+
* `check_circle` → success · `bg-blue-50` / `blue-snackbar` → info.
|
|
21
|
+
*/
|
|
22
|
+
export declare const DYE2E_NOTIFICATION_DETECTOR_PAGE_SCRIPT: string;
|
|
23
|
+
/**
|
|
24
|
+
* Page-side expression: the visible error banners / inline error texts at this moment (not toasts — e.g. a login
|
|
25
|
+
* form showing a raw HTTP error). Only really visible elements, deduplicated by tag + text; an element INSIDE an
|
|
26
|
+
* already counted banner is part of it, not a new one (measured: a `role=alert` box with its `.error-text` and icon
|
|
27
|
+
* was counted three times). PURE JavaScript.
|
|
28
|
+
*/
|
|
29
|
+
export declare const DYE2E_VISIBLE_ERRORS_PAGE_EXPRESSION: string;
|
|
30
|
+
//# sourceMappingURL=notification-detector-page-script.const.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notification-detector-page-script.const.d.ts","sourceRoot":"","sources":["../../../../src/_modules/runtime/_collections/notification-detector-page-script.const.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,+BAA+B,EAAE,MAAyC,CAAC;AAExF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,uCAAuC,EAAE,MA6GrD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,oCAAoC,EAAE,MA2BlD,CAAC"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DYE2E_VISIBLE_ERRORS_PAGE_EXPRESSION = exports.DYE2E_NOTIFICATION_DETECTOR_PAGE_SCRIPT = exports.DYE2E_NOTIFICATION_BINDING_NAME = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Name of the Playwright binding the page script reports every capture to (BFR-QA-001). Node keeps the
|
|
6
|
+
* captures, so a notification survives a full-page navigation (e.g. the redirect after login) and the page close.
|
|
7
|
+
*/
|
|
8
|
+
exports.DYE2E_NOTIFICATION_BINDING_NAME = '__dye2e_notification_binding__';
|
|
9
|
+
/**
|
|
10
|
+
* Page-side notification detector — `addInitScript` input (BFR-QA-001).
|
|
11
|
+
*
|
|
12
|
+
* **PURE JavaScript** (not TypeScript), like `DYE2E_WEB_VITALS_PAGE_SCRIPT`: the browser runs it as-is, so no
|
|
13
|
+
* transpiler helper (`__name` of tsx/esbuild) can leak into the page — no shim is needed in any consumer setup.
|
|
14
|
+
*
|
|
15
|
+
* Observes the document from its very start (a `MutationObserver` on `document`, subtree) and records every DyNX
|
|
16
|
+
* notification card / notifications area / mat-snackbar / `[role=alert]` at the moment it is ADDED — so an
|
|
17
|
+
* auto-dismissed toast (DyNX error 14 s, others 7 s) is not lost. A capture is re-read 50 ms and 300 ms later to
|
|
18
|
+
* fill in what Angular renders after inserting the host element (title, message, colour class); the re-read keeps
|
|
19
|
+
* the capture id. Every capture (and re-read) goes to the Node binding, and to `window.__dye2e_notifications__`.
|
|
20
|
+
*
|
|
21
|
+
* Type classification (from `@futdevpro/ngx-dynamo` notifications): `bg-red-50` / `red-snackbar` / mat-icon `error`
|
|
22
|
+
* → error · `bg-orange-50` / `yellow-snackbar` / `warning` → warning · `bg-green-50` / `green-snackbar` /
|
|
23
|
+
* `check_circle` → success · `bg-blue-50` / `blue-snackbar` → info.
|
|
24
|
+
*/
|
|
25
|
+
exports.DYE2E_NOTIFICATION_DETECTOR_PAGE_SCRIPT = `
|
|
26
|
+
(function () {
|
|
27
|
+
var w = window;
|
|
28
|
+
if (w.__dye2e_notifications__) { return; }
|
|
29
|
+
var list = [];
|
|
30
|
+
w.__dye2e_notifications__ = list;
|
|
31
|
+
var bindingName = '${exports.DYE2E_NOTIFICATION_BINDING_NAME}';
|
|
32
|
+
var perf = w.performance;
|
|
33
|
+
var origin = String(Math.round(perf && perf.timeOrigin ? perf.timeOrigin : Date.now()));
|
|
34
|
+
var counter = 0;
|
|
35
|
+
var recentKeys = {};
|
|
36
|
+
var recorded = {};
|
|
37
|
+
var checks = [
|
|
38
|
+
['dynx-card', 'dynamo-notification-card'],
|
|
39
|
+
['notif-area', 'dynamo-notifications-area'],
|
|
40
|
+
['snackbar', 'mat-snack-bar-container, .mat-mdc-snack-bar-container, simple-snack-bar, ' +
|
|
41
|
+
'.red-snackbar, .yellow-snackbar, .green-snackbar, .blue-snackbar'],
|
|
42
|
+
['role-alert', '[role="alert"], [role="alertdialog"]']
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
function nowMs() { return perf ? perf.now() : 0; }
|
|
46
|
+
function squash(text) { return (text || '').replace(/\\s+/g, ' ').trim(); }
|
|
47
|
+
function textOf(el, selector) {
|
|
48
|
+
try {
|
|
49
|
+
var node = el.querySelector(selector);
|
|
50
|
+
return node ? squash(node.textContent) : '';
|
|
51
|
+
} catch (e) { return ''; }
|
|
52
|
+
}
|
|
53
|
+
function classify(el) {
|
|
54
|
+
var html = el.outerHTML || '';
|
|
55
|
+
if (/bg-red-50|red-snackbar|>\\s*error\\s*</.test(html)) { return 'error'; }
|
|
56
|
+
if (/bg-orange-50|yellow-snackbar|>\\s*warning\\s*</.test(html)) { return 'warning'; }
|
|
57
|
+
if (/bg-green-50|green-snackbar|>\\s*check_circle\\s*</.test(html)) { return 'success'; }
|
|
58
|
+
if (/bg-blue-50|blue-snackbar/.test(html)) { return 'info'; }
|
|
59
|
+
return 'unknown';
|
|
60
|
+
}
|
|
61
|
+
function read(el) {
|
|
62
|
+
var structured = textOf(el, '.text-slate-700') || textOf(el, '.mat-mdc-snack-bar-label') ||
|
|
63
|
+
textOf(el, '[matsnackbarlabel]');
|
|
64
|
+
return {
|
|
65
|
+
type: classify(el),
|
|
66
|
+
structured: !!structured,
|
|
67
|
+
title: (structured || squash(el.textContent)).slice(0, 400),
|
|
68
|
+
message: textOf(el, '.text-slate-500').slice(0, 400),
|
|
69
|
+
html: squash(el.outerHTML).slice(0, 500)
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function emit(entry) {
|
|
73
|
+
try {
|
|
74
|
+
if (typeof w[bindingName] === 'function') { w[bindingName](entry); }
|
|
75
|
+
} catch (e) { /* the page must never break because of the detector */ }
|
|
76
|
+
}
|
|
77
|
+
function enrich(entry, state, el) {
|
|
78
|
+
if (!el.isConnected) { return; }
|
|
79
|
+
var fresh = read(el);
|
|
80
|
+
var changed = false;
|
|
81
|
+
if (entry.type === 'unknown' && fresh.type !== 'unknown') { entry.type = fresh.type; changed = true; }
|
|
82
|
+
if (fresh.title && fresh.title !== entry.title && (!entry.title || !state.structured)) {
|
|
83
|
+
entry.title = fresh.title; state.structured = fresh.structured; changed = true;
|
|
84
|
+
}
|
|
85
|
+
if (!entry.message && fresh.message) { entry.message = fresh.message; changed = true; }
|
|
86
|
+
if (changed) { entry.html = fresh.html; emit(entry); }
|
|
87
|
+
}
|
|
88
|
+
function record(kind, el) {
|
|
89
|
+
var seen = recorded[kind] || (recorded[kind] = new WeakSet());
|
|
90
|
+
if (seen.has(el)) { return; }
|
|
91
|
+
seen.add(el);
|
|
92
|
+
var data = read(el);
|
|
93
|
+
var t = nowMs();
|
|
94
|
+
var key = kind + '|' + data.type + '|' + data.title;
|
|
95
|
+
if (data.title && recentKeys[key] !== undefined && t - recentKeys[key] < 1500) { return; }
|
|
96
|
+
recentKeys[key] = t;
|
|
97
|
+
counter += 1;
|
|
98
|
+
var entry = {
|
|
99
|
+
id: origin + '-' + counter,
|
|
100
|
+
kind: kind,
|
|
101
|
+
type: data.type,
|
|
102
|
+
title: data.title,
|
|
103
|
+
message: data.message,
|
|
104
|
+
url: location.href,
|
|
105
|
+
at: Date.now(),
|
|
106
|
+
sinceDocumentStartMs: Math.round(t),
|
|
107
|
+
html: data.html
|
|
108
|
+
};
|
|
109
|
+
var state = { structured: data.structured };
|
|
110
|
+
list.push(entry);
|
|
111
|
+
emit(entry);
|
|
112
|
+
setTimeout(function () { enrich(entry, state, el); }, 50);
|
|
113
|
+
setTimeout(function () { enrich(entry, state, el); }, 300);
|
|
114
|
+
}
|
|
115
|
+
function scan(node) {
|
|
116
|
+
if (!node || node.nodeType !== 1) { return; }
|
|
117
|
+
for (var i = 0; i < checks.length; i++) {
|
|
118
|
+
try {
|
|
119
|
+
if (node.matches(checks[i][1])) { record(checks[i][0], node); }
|
|
120
|
+
var found = node.querySelectorAll(checks[i][1]);
|
|
121
|
+
for (var j = 0; j < found.length; j++) { record(checks[i][0], found[j]); }
|
|
122
|
+
} catch (e) { /* one bad selector must not stop the other checks */ }
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
new MutationObserver(function (mutations) {
|
|
127
|
+
for (var i = 0; i < mutations.length; i++) {
|
|
128
|
+
var added = mutations[i].addedNodes;
|
|
129
|
+
for (var j = 0; j < added.length; j++) { scan(added[j]); }
|
|
130
|
+
}
|
|
131
|
+
}).observe(document, { childList: true, subtree: true });
|
|
132
|
+
scan(document.documentElement);
|
|
133
|
+
})();
|
|
134
|
+
`;
|
|
135
|
+
/**
|
|
136
|
+
* Page-side expression: the visible error banners / inline error texts at this moment (not toasts — e.g. a login
|
|
137
|
+
* form showing a raw HTTP error). Only really visible elements, deduplicated by tag + text; an element INSIDE an
|
|
138
|
+
* already counted banner is part of it, not a new one (measured: a `role=alert` box with its `.error-text` and icon
|
|
139
|
+
* was counted three times). PURE JavaScript.
|
|
140
|
+
*/
|
|
141
|
+
exports.DYE2E_VISIBLE_ERRORS_PAGE_EXPRESSION = `
|
|
142
|
+
(function () {
|
|
143
|
+
var selectors = ['.error-text', '[class*="error-text"]', 'mat-error', '.mat-mdc-form-field-error',
|
|
144
|
+
'.text-red-200', '.text-red-500', '.text-red-600', '.text-red-700', '[role="alert"]'];
|
|
145
|
+
var out = [];
|
|
146
|
+
var seen = {};
|
|
147
|
+
var picked = [];
|
|
148
|
+
var found = document.querySelectorAll(selectors.join(','));
|
|
149
|
+
for (var i = 0; i < found.length; i++) {
|
|
150
|
+
var el = found[i];
|
|
151
|
+
var text = (el.textContent || '').replace(/\\s+/g, ' ').trim();
|
|
152
|
+
if (!text || text.length > 300 || !(el instanceof HTMLElement)) { continue; }
|
|
153
|
+
var rect = el.getBoundingClientRect();
|
|
154
|
+
var style = getComputedStyle(el);
|
|
155
|
+
var visible = el.offsetParent !== null && rect.width > 0 && rect.height > 0 &&
|
|
156
|
+
style.visibility !== 'hidden' && style.display !== 'none';
|
|
157
|
+
var key = el.tagName + '|' + text;
|
|
158
|
+
if (!visible || seen[key]) { continue; }
|
|
159
|
+
var inside = false;
|
|
160
|
+
for (var j = 0; j < picked.length; j++) { if (picked[j].contains(el)) { inside = true; break; } }
|
|
161
|
+
if (inside) { continue; }
|
|
162
|
+
seen[key] = true;
|
|
163
|
+
picked.push(el);
|
|
164
|
+
out.push({ tag: el.tagName.toLowerCase(), cls: String(el.className || '').slice(0, 120), text: text.slice(0, 260) });
|
|
165
|
+
}
|
|
166
|
+
return out;
|
|
167
|
+
})()
|
|
168
|
+
`;
|
|
169
|
+
//# sourceMappingURL=notification-detector-page-script.const.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notification-detector-page-script.const.js","sourceRoot":"","sources":["../../../../src/_modules/runtime/_collections/notification-detector-page-script.const.ts"],"names":[],"mappings":";;;AAAA;;;GAGG;AACU,QAAA,+BAA+B,GAAW,gCAAgC,CAAC;AAExF;;;;;;;;;;;;;;;GAeG;AACU,QAAA,uCAAuC,GAAW;;;;;;uBAMxC,uCAA+B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuGrD,CAAC;AAEF;;;;;GAKG;AACU,QAAA,oCAAoC,GAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2B3D,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { BrowserContext, Page } from '@playwright/test';
|
|
2
|
+
import { DyE2E_NotificationReport_Interface, DyE2E_VisibleError_Interface } from '../_models/notification-report.interface';
|
|
3
|
+
/**
|
|
4
|
+
* Live notification detector (BFR-QA-001) — records every user-visible notification / toast / snackbar /
|
|
5
|
+
* `[role=alert]` at the moment it APPEARS, so an auto-dismissed toast is still in the report; plus the visible
|
|
6
|
+
* error banners (with a raw-technical-leak flag) and the page's `console` error/warning + `pageerror`.
|
|
7
|
+
*
|
|
8
|
+
* Why: console mining does not see the framework's user notifications (they are DOM, without a console log), and
|
|
9
|
+
* a final screenshot loses the ones that disappeared. A QA run missed a login error toast exactly this way.
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* await DyE2E_NotificationDetector_Util.attach(context); // BEFORE the first navigation
|
|
13
|
+
* const page: Page = await context.newPage();
|
|
14
|
+
* await page.goto('/login'); // ... the flow ...
|
|
15
|
+
* const report: DyE2E_NotificationReport_Interface = await DyE2E_NotificationDetector_Util.collect(page);
|
|
16
|
+
* expect(report.notifications.filter((n) => n.type === 'error')).toEqual([]);
|
|
17
|
+
* console.log(DyE2E_NotificationDetector_Util.summarize(report, 'login'));
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare class DyE2E_NotificationDetector_Util {
|
|
21
|
+
/** Texts that expose internals to the user instead of a human message. */
|
|
22
|
+
static readonly rawTechnicalLeakPattern: RegExp;
|
|
23
|
+
private static readonly attachedContexts;
|
|
24
|
+
private static readonly pageStates;
|
|
25
|
+
/**
|
|
26
|
+
* Attaches the detector to a context: every document of every page (and frame) gets the observer BEFORE its
|
|
27
|
+
* own scripts run; pages that already exist are instrumented immediately (their current DOM is scanned).
|
|
28
|
+
* Calling it again for the same context is a no-op.
|
|
29
|
+
*/
|
|
30
|
+
static attach(context: BrowserContext): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* The report of one page: every notification captured so far (across its navigations, in order of appearance),
|
|
33
|
+
* the error banners visible NOW, and the console errors/warnings + page errors.
|
|
34
|
+
* @throws if the page's context has no detector attached — an empty report would be a false "all clear".
|
|
35
|
+
*/
|
|
36
|
+
static collect(page: Page): Promise<DyE2E_NotificationReport_Interface>;
|
|
37
|
+
/** The error banners / inline error texts visible on the page right now, with the raw-technical-leak flag. */
|
|
38
|
+
static scanVisibleErrors(page: Page): Promise<DyE2E_VisibleError_Interface[]>;
|
|
39
|
+
/** `true` when the text shows the user raw technical detail (HTTP error text, API path, `[object Object]` …). */
|
|
40
|
+
static isRawTechnicalLeak(text: string): boolean;
|
|
41
|
+
/** Short human-readable summary (CLI output / QA round log). */
|
|
42
|
+
static summarize(report: DyE2E_NotificationReport_Interface, label?: string): string;
|
|
43
|
+
/** Console + pageerror listeners, once per page. */
|
|
44
|
+
private static watchPage;
|
|
45
|
+
private static stateOf;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=notification-detector.util.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notification-detector.util.d.ts","sourceRoot":"","sources":["../../../../src/_modules/runtime/_collections/notification-detector.util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAkB,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAExE,OAAO,EAGL,kCAAkC,EAGlC,4BAA4B,EAC7B,MAAM,0CAA0C,CAAC;AAqBlD;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,+BAA+B;IAE1C,0EAA0E;IAC1E,MAAM,CAAC,QAAQ,CAAC,uBAAuB,EAAE,MAAM,CAM7C;IAEF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAA0D;IAClG,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAC2B;IAE7D;;;;OAIG;WACU,MAAM,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IA4B3D;;;;OAIG;WACU,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,kCAAkC,CAAC;IAiC7E,8GAA8G;WACjG,iBAAiB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,4BAA4B,EAAE,CAAC;IAenF,iHAAiH;IACjH,MAAM,CAAC,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIhD,gEAAgE;IAChE,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,kCAAkC,EAAE,KAAK,GAAE,MAAW,GAAG,MAAM;IA+BxF,oDAAoD;IACpD,OAAO,CAAC,MAAM,CAAC,SAAS;IAgBxB,OAAO,CAAC,MAAM,CAAC,OAAO;CAQvB"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DyE2E_NotificationDetector_Util = void 0;
|
|
4
|
+
const notification_detector_page_script_const_1 = require("./notification-detector-page-script.const");
|
|
5
|
+
/**
|
|
6
|
+
* Live notification detector (BFR-QA-001) — records every user-visible notification / toast / snackbar /
|
|
7
|
+
* `[role=alert]` at the moment it APPEARS, so an auto-dismissed toast is still in the report; plus the visible
|
|
8
|
+
* error banners (with a raw-technical-leak flag) and the page's `console` error/warning + `pageerror`.
|
|
9
|
+
*
|
|
10
|
+
* Why: console mining does not see the framework's user notifications (they are DOM, without a console log), and
|
|
11
|
+
* a final screenshot loses the ones that disappeared. A QA run missed a login error toast exactly this way.
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* await DyE2E_NotificationDetector_Util.attach(context); // BEFORE the first navigation
|
|
15
|
+
* const page: Page = await context.newPage();
|
|
16
|
+
* await page.goto('/login'); // ... the flow ...
|
|
17
|
+
* const report: DyE2E_NotificationReport_Interface = await DyE2E_NotificationDetector_Util.collect(page);
|
|
18
|
+
* expect(report.notifications.filter((n) => n.type === 'error')).toEqual([]);
|
|
19
|
+
* console.log(DyE2E_NotificationDetector_Util.summarize(report, 'login'));
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
class DyE2E_NotificationDetector_Util {
|
|
23
|
+
/** Texts that expose internals to the user instead of a human message. */
|
|
24
|
+
static rawTechnicalLeakPattern = new RegExp('(Http failure response|Unauthorized|Internal Server|undefined|\\[object Object\\]|/api/|-api/|' +
|
|
25
|
+
'status of \\d{3}|\\bHTTP \\d{3}\\b|ERR_|Cannot (GET|POST|read)|' +
|
|
26
|
+
// JS errors shown to the user (measured on a TEST site: "…: this.rendering_CS.noServer… is not a function")
|
|
27
|
+
'is not a function|is not defined|TypeError|\\bthis\\.[A-Za-z_$][\\w$]*\\.)', 'i');
|
|
28
|
+
static attachedContexts = new WeakSet();
|
|
29
|
+
static pageStates = new WeakMap();
|
|
30
|
+
/**
|
|
31
|
+
* Attaches the detector to a context: every document of every page (and frame) gets the observer BEFORE its
|
|
32
|
+
* own scripts run; pages that already exist are instrumented immediately (their current DOM is scanned).
|
|
33
|
+
* Calling it again for the same context is a no-op.
|
|
34
|
+
*/
|
|
35
|
+
static async attach(context) {
|
|
36
|
+
if (this.attachedContexts.has(context)) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
this.attachedContexts.add(context);
|
|
40
|
+
// the page reports every capture here — the Node-side store survives navigations
|
|
41
|
+
await context.exposeBinding(notification_detector_page_script_const_1.DYE2E_NOTIFICATION_BINDING_NAME, (source, entry) => {
|
|
42
|
+
if (source.page && entry && typeof entry.id === 'string') {
|
|
43
|
+
this.stateOf(source.page).notifications.set(entry.id, entry);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
await context.addInitScript({ content: notification_detector_page_script_const_1.DYE2E_NOTIFICATION_DETECTOR_PAGE_SCRIPT });
|
|
47
|
+
context.on('page', (page) => this.watchPage(page));
|
|
48
|
+
for (const page of context.pages()) {
|
|
49
|
+
this.watchPage(page);
|
|
50
|
+
try {
|
|
51
|
+
await page.evaluate(notification_detector_page_script_const_1.DYE2E_NOTIFICATION_DETECTOR_PAGE_SCRIPT);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
// the page is navigating / closed — the init script reaches its next document anyway
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The report of one page: every notification captured so far (across its navigations, in order of appearance),
|
|
60
|
+
* the error banners visible NOW, and the console errors/warnings + page errors.
|
|
61
|
+
* @throws if the page's context has no detector attached — an empty report would be a false "all clear".
|
|
62
|
+
*/
|
|
63
|
+
static async collect(page) {
|
|
64
|
+
if (!this.attachedContexts.has(page.context())) {
|
|
65
|
+
throw new Error('DyE2E_NotificationDetector_Util.collect: no detector is attached to this page\'s context — call ' +
|
|
66
|
+
'`await DyE2E_NotificationDetector_Util.attach(context)` before the first navigation. Without it the ' +
|
|
67
|
+
'report would be empty and read as "no notifications".');
|
|
68
|
+
}
|
|
69
|
+
const state = this.stateOf(page);
|
|
70
|
+
// merge the page's own list too (by id): binding calls may still be in flight
|
|
71
|
+
let inPage = [];
|
|
72
|
+
try {
|
|
73
|
+
inPage = await page.evaluate('window.__dye2e_notifications__ || []');
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
// the page closed / is navigating — the Node-side store is still complete
|
|
77
|
+
}
|
|
78
|
+
const merged = new Map(state.notifications);
|
|
79
|
+
for (const entry of inPage) {
|
|
80
|
+
merged.set(entry.id, entry);
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
notifications: [...merged.values()].sort((a, b) => a.at - b.at),
|
|
84
|
+
visibleErrors: await this.scanVisibleErrors(page),
|
|
85
|
+
consoleErrors: [...state.consoleErrors],
|
|
86
|
+
pageErrors: [...state.pageErrors],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/** The error banners / inline error texts visible on the page right now, with the raw-technical-leak flag. */
|
|
90
|
+
static async scanVisibleErrors(page) {
|
|
91
|
+
let raw = [];
|
|
92
|
+
try {
|
|
93
|
+
raw = await page.evaluate(notification_detector_page_script_const_1.DYE2E_VISIBLE_ERRORS_PAGE_EXPRESSION);
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
return raw.map((item) => ({
|
|
99
|
+
tag: item.tag,
|
|
100
|
+
cls: item.cls,
|
|
101
|
+
text: item.text,
|
|
102
|
+
rawTechnicalLeak: this.isRawTechnicalLeak(item.text),
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
/** `true` when the text shows the user raw technical detail (HTTP error text, API path, `[object Object]` …). */
|
|
106
|
+
static isRawTechnicalLeak(text) {
|
|
107
|
+
return this.rawTechnicalLeakPattern.test(text || '');
|
|
108
|
+
}
|
|
109
|
+
/** Short human-readable summary (CLI output / QA round log). */
|
|
110
|
+
static summarize(report, label = '') {
|
|
111
|
+
const byType = { error: 0, warning: 0, success: 0, info: 0, unknown: 0 };
|
|
112
|
+
report.notifications.forEach((n) => {
|
|
113
|
+
byType[n.type] = byType[n.type] + 1;
|
|
114
|
+
});
|
|
115
|
+
const lines = [];
|
|
116
|
+
lines.push('=== NOTIFICATION DETECTOR' + (label ? ' — ' + label : '') + ' ===');
|
|
117
|
+
lines.push('Notifications: ' + report.notifications.length + ' (error=' + byType.error + ' warning=' +
|
|
118
|
+
byType.warning + ' success=' + byType.success + ' info=' + byType.info + ' unknown=' + byType.unknown + ')');
|
|
119
|
+
report.notifications.forEach((n, i) => {
|
|
120
|
+
lines.push(' [' + (i + 1) + '] ' + n.type.toUpperCase() + ' <' + n.kind + '> "' + n.title.slice(0, 160) + '"' +
|
|
121
|
+
(n.message ? ' — ' + n.message.slice(0, 120) : ''));
|
|
122
|
+
});
|
|
123
|
+
const leaks = report.visibleErrors.filter((e) => e.rawTechnicalLeak).length;
|
|
124
|
+
lines.push('Visible error banners: ' + report.visibleErrors.length + ' (raw-technical-leak: ' + leaks + ')');
|
|
125
|
+
report.visibleErrors.forEach((e, i) => {
|
|
126
|
+
lines.push(' [E' + (i + 1) + '] ' + (e.rawTechnicalLeak ? 'LEAK ' : '') + '<' + e.tag + '.' +
|
|
127
|
+
(e.cls.split(' ')[0] || '') + '> "' + e.text.slice(0, 200) + '"');
|
|
128
|
+
});
|
|
129
|
+
lines.push('Console errors/warnings: ' + report.consoleErrors.length + ' | pageerrors: ' +
|
|
130
|
+
report.pageErrors.length);
|
|
131
|
+
report.consoleErrors.slice(0, 12).forEach((c) => {
|
|
132
|
+
lines.push(' console.' + c.type + ': ' + c.text.slice(0, 180));
|
|
133
|
+
});
|
|
134
|
+
report.pageErrors.slice(0, 8).forEach((p) => {
|
|
135
|
+
lines.push(' pageerror: ' + p.text.slice(0, 180));
|
|
136
|
+
});
|
|
137
|
+
return lines.join('\n');
|
|
138
|
+
}
|
|
139
|
+
/** Console + pageerror listeners, once per page. */
|
|
140
|
+
static watchPage(page) {
|
|
141
|
+
if (this.pageStates.has(page)) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const state = this.stateOf(page);
|
|
145
|
+
page.on('console', (message) => {
|
|
146
|
+
const type = message.type();
|
|
147
|
+
if (type === 'error' || type === 'warning') {
|
|
148
|
+
state.consoleErrors.push({ type: type, text: (message.text() || '').slice(0, 500), url: page.url() });
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
page.on('pageerror', (error) => {
|
|
152
|
+
state.pageErrors.push({ text: String(error?.message || error).slice(0, 500), url: page.url() });
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
static stateOf(page) {
|
|
156
|
+
let state = this.pageStates.get(page);
|
|
157
|
+
if (!state) {
|
|
158
|
+
state = { notifications: new Map(), consoleErrors: [], pageErrors: [] };
|
|
159
|
+
this.pageStates.set(page, state);
|
|
160
|
+
}
|
|
161
|
+
return state;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
exports.DyE2E_NotificationDetector_Util = DyE2E_NotificationDetector_Util;
|
|
165
|
+
//# sourceMappingURL=notification-detector.util.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notification-detector.util.js","sourceRoot":"","sources":["../../../../src/_modules/runtime/_collections/notification-detector.util.ts"],"names":[],"mappings":";;;AAUA,uGAImD;AAgBnD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAa,+BAA+B;IAE1C,0EAA0E;IAC1E,MAAM,CAAU,uBAAuB,GAAW,IAAI,MAAM,CAC1D,gGAAgG;QAChG,iEAAiE;QACjE,4GAA4G;QAC5G,4EAA4E,EAC5E,GAAG,CACJ,CAAC;IAEM,MAAM,CAAU,gBAAgB,GAA4B,IAAI,OAAO,EAAkB,CAAC;IAC1F,MAAM,CAAU,UAAU,GAChC,IAAI,OAAO,EAA+C,CAAC;IAE7D;;;;OAIG;IACH,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAuB;QACzC,IAAI,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YACvC,OAAO;QACT,CAAC;QACD,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAEnC,iFAAiF;QACjF,MAAM,OAAO,CAAC,aAAa,CACzB,yEAA+B,EAC/B,CAAC,MAAsB,EAAE,KAAmC,EAAQ,EAAE;YACpE,IAAI,MAAM,CAAC,IAAI,IAAI,KAAK,IAAI,OAAO,KAAK,CAAC,EAAE,KAAK,QAAQ,EAAE,CAAC;gBACzD,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;YAC/D,CAAC;QACH,CAAC,CACF,CAAC;QACF,MAAM,OAAO,CAAC,aAAa,CAAC,EAAE,OAAO,EAAE,iFAAuC,EAAE,CAAC,CAAC;QAElF,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAU,EAAQ,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;QAC/D,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;YACnC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YACrB,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,QAAQ,CAAC,iFAAuC,CAAC,CAAC;YAC/D,CAAC;YAAC,OAAO,KAAc,EAAE,CAAC;gBACxB,qFAAqF;YACvF,CAAC;QACH,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,IAAU;QAC7B,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;YAC/C,MAAM,IAAI,KAAK,CACb,kGAAkG;gBAClG,sGAAsG;gBACtG,uDAAuD,CACxD,CAAC;QACJ,CAAC;QACD,MAAM,KAAK,GAA0C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAExE,8EAA8E;QAC9E,IAAI,MAAM,GAAmC,EAAE,CAAC;QAChD,IAAI,CAAC;YACH,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAiC,sCAAsC,CAAC,CAAC;QACvG,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,0EAA0E;QAC5E,CAAC;QACD,MAAM,MAAM,GACV,IAAI,GAAG,CAAuC,KAAK,CAAC,aAAa,CAAC,CAAC;QACrE,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;QAC9B,CAAC;QAED,OAAO;YACL,aAAa,EAAE,CAAE,GAAG,MAAM,CAAC,MAAM,EAAE,CAAE,CAAC,IAAI,CACxC,CAAC,CAA+B,EAAE,CAA+B,EAAU,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAC1F;YACD,aAAa,EAAE,MAAM,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC;YACjD,aAAa,EAAE,CAAE,GAAG,KAAK,CAAC,aAAa,CAAE;YACzC,UAAU,EAAE,CAAE,GAAG,KAAK,CAAC,UAAU,CAAE;SACpC,CAAC;IACJ,CAAC;IAED,8GAA8G;IAC9G,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,IAAU;QACvC,IAAI,GAAG,GAAsC,EAAE,CAAC;QAChD,IAAI,CAAC;YACH,GAAG,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAoC,8EAAoC,CAAC,CAAC;QACrG,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC,IAAqC,EAAgC,EAAE,CAAC,CAAC;YACvF,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,gBAAgB,EAAE,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC;SACrD,CAAC,CAAC,CAAC;IACN,CAAC;IAED,iHAAiH;IACjH,MAAM,CAAC,kBAAkB,CAAC,IAAY;QACpC,OAAO,IAAI,CAAC,uBAAuB,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IACvD,CAAC;IAED,gEAAgE;IAChE,MAAM,CAAC,SAAS,CAAC,MAA0C,EAAE,QAAgB,EAAE;QAC7E,MAAM,MAAM,GAA2C,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;QACjH,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAA+B,EAAQ,EAAE;YACrE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACtC,CAAC,CAAC,CAAC;QACH,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,KAAK,CAAC,IAAI,CAAC,2BAA2B,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC;QAChF,KAAK,CAAC,IAAI,CAAC,iBAAiB,GAAG,MAAM,CAAC,aAAa,CAAC,MAAM,GAAG,WAAW,GAAG,MAAM,CAAC,KAAK,GAAG,WAAW;YACnG,MAAM,CAAC,OAAO,GAAG,WAAW,GAAG,MAAM,CAAC,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC,IAAI,GAAG,WAAW,GAAG,MAAM,CAAC,OAAO,GAAG,GAAG,CAAC,CAAC;QAC/G,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAA+B,EAAE,CAAS,EAAQ,EAAE;YAChF,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,GAAG,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,GAAG;gBAC5G,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;QACH,MAAM,KAAK,GAAW,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAA+B,EAAW,EAAE,CAC7F,CAAC,CAAC,gBAAgB,CAAC,CAAC,MAAM,CAAC;QAC7B,KAAK,CAAC,IAAI,CAAC,yBAAyB,GAAG,MAAM,CAAC,aAAa,CAAC,MAAM,GAAG,yBAAyB,GAAG,KAAK,GAAG,GAAG,CAAC,CAAC;QAC9G,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAA+B,EAAE,CAAS,EAAQ,EAAE;YAChF,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC,GAAG,GAAG,GAAG;gBAC1F,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC;QACtE,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,IAAI,CAAC,2BAA2B,GAAG,MAAM,CAAC,aAAa,CAAC,MAAM,GAAG,kBAAkB;YACvF,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QAC5B,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAA+B,EAAQ,EAAE;YAClF,KAAK,CAAC,IAAI,CAAC,YAAY,GAAG,CAAC,CAAC,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QAClE,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAA4B,EAAQ,EAAE;YAC3E,KAAK,CAAC,IAAI,CAAC,eAAe,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QACrD,CAAC,CAAC,CAAC;QACH,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,oDAAoD;IAC5C,MAAM,CAAC,SAAS,CAAC,IAAU;QACjC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9B,OAAO;QACT,CAAC;QACD,MAAM,KAAK,GAA0C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACxE,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,OAAuB,EAAQ,EAAE;YACnD,MAAM,IAAI,GAAW,OAAO,CAAC,IAAI,EAAE,CAAC;YACpC,IAAI,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;gBAC3C,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YACxG,CAAC;QACH,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,KAAY,EAAQ,EAAE;YAC1C,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,EAAE,OAAO,IAAI,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAClG,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,MAAM,CAAC,OAAO,CAAC,IAAU;QAC/B,IAAI,KAAK,GAAsD,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACzF,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,KAAK,GAAG,EAAE,aAAa,EAAE,IAAI,GAAG,EAAwC,EAAE,aAAa,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC;YAC9G,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACnC,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;;AAnKH,0EAoKC"}
|