@futdevpro/dynamo-e2e
v1.18.47
Published
End-to-end testing engine for the Dynamo + FDP ecosystem — auto-generated form-coverage tests, viewport audit matrix, CWV diagnostics. Settings-POJO-first.
Readme
@futdevpro/dynamo-e2e
End-to-end testing engine for the Dynamo + FDP ecosystem.
What
A Dynamo bedrock-package that turns Forms v2 Settings POJOs into Playwright test suites — auto-generated, typed, overridable, diffable.
Three layers in one package:
- Form-coverage — every field-type has a pre-curated positive + negative input matrix; the generator emits ~20-50 tests per form from a single Settings POJO.
- Viewport matrix (Wave-2) — auto-fan-out tests over 7 standard viewports + overflow/touch-target a11y detection.
- Core Web Vitals diagnostics (Wave-3) — synthetic LCP / CLS / INP / TTFB measurement + percentile aggregation + attribution.
Status
Wave-1..4 + MP-10/MP-11 (2026-06-10) — Layer A contracts, Layer B generators (form-suite + viewport-suite + CWV-suite), Layer C runtime fixtures + page-objects, Overseer CICD pipeline registered, 75%+ test coverage gate active (statements 97.84%, branches 87.13%, functions 98.55%, lines 97.84%). 104 specs, 0 failures.
Plan-net: __documentations/plans/dynamo-e2e-foundation/HYPERPLAN.md
(11 MPs, 4900+ LOC planning).
Install
pnpm add -D @futdevpro/dynamo-e2e @playwright/test@futdevpro/fsm-dynamo is a peer-dep — bring your own (or no Settings
POJO source if you only use the runtime presets).
Quick start
import { DyFM_TextFieldNew_Settings } from '@futdevpro/fsm-dynamo/ngx-models';
import { DyE2E_FormSuite_Generator } from '@futdevpro/dynamo-e2e/generators';
const settings = new DyFM_FormNew_Settings({
key: 'login',
fields: [
new DyFM_TextFieldNew_Settings({ key: 'email', /* ... */ }),
],
});
const specSource = DyE2E_FormSuite_Generator.emit({
formSettings: settings,
formRoute: '/login',
formSelector: 'dynamo-form-new',
});
// → write specSource to `e2e/tests/generated/login-form.spec.ts`Sub-exports
@futdevpro/dynamo-e2e/contracts— POJO types@futdevpro/dynamo-e2e/generators— Node-only emission engine@futdevpro/dynamo-e2e/runtime— Playwright fixtures + page-objects (Wave-4) + the live notification detector (DyE2E_NotificationDetector_Util, see below)@futdevpro/dynamo-e2e/presets— pre-built suites +DyE2E_getPlaywrightConfig/DyE2E_getGlobalSetupplaywright-config factory (fleet e2e-config SSOT)@futdevpro/dynamo-e2e/game— capability-gated deterministic game seam, failure artifacts and bidirectional journey-catalog validation@futdevpro/dynamo-e2e/cwv— Core Web Vitals (Wave-3)@futdevpro/dynamo-e2e/ui-check— scripted, per-error-class UI detectors that return VERDICTS (a control that does nothing, two-level scrolling, a checkbox that does not toggle); a growing default list, coded waivers, a safety guard for pressing controls. Catalog + proofs:__documentations/UI-CHECK-DETECTORS.md
import { DyE2E_UICheckRunner_Util } from '@futdevpro/dynamo-e2e/ui-check';
const report = await DyE2E_UICheckRunner_Util.run(page, { url: page.url(), accountKind: 'anonymous' });
DyE2E_UICheckRunner_Util.assertClean(report); // throws with every finding's measurement@futdevpro/dynamo-e2e/visual-gate— the visual regression gate: closes the TEST warning and the cookie bar through the app (DyE2E_BlockingOverlays_Util.prepareStorageState, once per language), then checks each key page in HU + EN at 1440 + 390 (DyE2E_VisualGate_Util.cases): red when an overlay still covers it, it shows (almost) nothing, it is wider than the screen, or it differs from the committed baseline (toHaveScreenshot). The look (DyE2E_VisualGateLook_Util, BFR-FUTDEVPRO-081): the "not found" screen, an empty content area, green / cyan / blue instead of the neutral grey, a text off the screen, a raw icon word, a placeholder; with areferenceUrl, the heading and paragraph sizes of production on the pages markedisOnReference. WithtestInfo, the screenshots (TEST and production) go into the report.
import { DyE2E_BlockingOverlays_Util, DyE2E_VisualGate_Util } from '@futdevpro/dynamo-e2e/visual-gate';
for (const gateCase of DyE2E_VisualGate_Util.cases({ pages: [ { name: 'login', path: '/login' } ] })) {
test(`| visual: ${gateCase.id}`, async ({ browser }) => {
const { storageState } = await DyE2E_BlockingOverlays_Util.prepareStorageState({
browser, baseUrl: BASE_URL, locale: gateCase.language.locale,
});
const context = await browser.newContext({ storageState, locale: gateCase.language.locale,
viewport: { width: gateCase.viewport.width, height: gateCase.viewport.height } });
const page = await context.newPage();
await page.goto(BASE_URL + gateCase.page.path);
await DyE2E_VisualGate_Util.assertPage(page, gateCase, {
contentSelector: 'main', notFoundSelector: 'app-a-block-message-screen',
referenceUrl: 'https://example.hu', testInfo: test.info(),
}); // layout + look first, then the production font sizes, then the screenshot comparison
});
}Playwright preset — CI timeouts and the failure budget
import { DyE2E_getPlaywrightConfig, DyE2E_ciTimeout } from '@futdevpro/dynamo-e2e/presets';
export default DyE2E_getPlaywrightConfig({
defaultBaseUrl: 'https://test.example.hu',
ciTimeoutFactor: 3, // CI only: timeout / expectTimeout / actionTimeout / navigationTimeout × 3 (default 1)
maxFailuresInCi: 1, // CI only: stop after the first failure (default: unset = unchanged)
});⚠️ A call-site
{ timeout }overrides the config.expect(x).toBeVisible({ timeout: 15_000 })ignores the preset'sexpect.timeout(and itsciTimeoutFactor). Scale those values withDyE2E_ciTimeout(15_000)— unchanged locally, ×E2E_CI_TIMEOUT_FACTOR(or 3) underCI. Measured: a shared CI runner was 2.3–4.7× slower than a local machine, and a smoke suite with hard-coded call-site timeouts stayed red for months while its config "looked right".
Live notification detector (/runtime)
Records every user-visible notification — DyNX dynamo-notification-card / dynamo-notifications-area, mat-snackbar
(red- / yellow- / blue- / green-snackbar), [role=alert] — at the moment it APPEARS, so a toast that
auto-dismissed before the end of the test is still in the report. It also snapshots the visible error banners (with a
rawTechnicalLeak flag for texts like Http failure response, /api/, [object Object]) and collects the page's
console errors/warnings and uncaught pageerrors.
import { DyE2E_NotificationDetector_Util } from '@futdevpro/dynamo-e2e/runtime';
await DyE2E_NotificationDetector_Util.attach(context); // before the first navigation
// ... the flow ...
const report = await DyE2E_NotificationDetector_Util.collect(page);
expect(report.notifications.filter((n) => n.type === 'error')).toEqual([]);
console.log(DyE2E_NotificationDetector_Util.summarize(report, 'login'));Captures survive a full-page navigation (they are reported to Node through a Playwright binding). The page-side code
is plain JavaScript, so no __name shim is needed under tsx / esbuild. collect() throws on a context without
attach() — an empty report would otherwise read as "no notifications".
Login-flow notification sweep (/runtime + CLI)
Built on the detector: opens the login page of many projects, submits it with a WRONG password (so the ERROR branch is measured without a test account) and prints one matrix row per project — notifications · error/warning · banners · modal dialogs · raw-technical leaks · console errors. Framework-level defects show up only when many projects are measured the same way.
dye2e notification-sweep --targets TEST-TARGETS.md --json sweep.json # or a JSON [{ key, loginUrl | baseUrl }]
dye2e notification-sweep --targets targets.json --only a,b --fail-on-leak # exit 1 on a raw technical leakconst outcomes = await DyE2E_NotificationSweep_Util.sweep(browser, targets, { settleMs: 5000 });
console.log(DyE2E_NotificationSweep_Util.matrix(outcomes.map((o) => o.row)));Guards in code, not in docs: the probe address must be on a reserved domain (example.com, *.test, *.invalid …)
or on one you declare with ownDomains — anything else throws before a page is opened; the form is submitted only on
a test host (test.*, *.test, localhost …, or allowSubmitHosts) — anything else is loaded but NOT submitted,
and the row says so. real: true / --real uses E2E_PERSISTENT_USER_EMAIL / _PASSWORD; without them the row says
it fell back to the probe. Modal dialogs (a "not the production system" notice, an error dialog) are RECORDED —
text + leak flag — before they are closed; a non-modal cookie bar is left alone.
Detailed game-preset contract: __documentations/game-preset.md.
