MD.OFFICE
FAL

Dynaforms Playwright E2E Guide

Last updated: 2026-04-23
Scope: e2e/tests/dynaforms-lifecycle.test.ts full lifecycle flow

This guide is for developers extending or debugging Dynaforms E2E. It explains:

  • setup and run modes (headed, debug, default),
  • how template/form-data JSON files are loaded,
  • complete control flow from test -> flow -> page -> drivers,
  • how each renderer driver fills/asserts fields,
  • how to add new widget support safely.

Table of Contents

  1. What This Test Covers
  2. Setup and Run Commands
  3. End-to-End Architecture
  4. Flow Execution Details
  5. Selector Strategy
  6. JSON Loader Deep Dive
  7. Renderer Drivers Deep Dive
  8. Page Object Details
  9. Extending Support for a New Widget
  10. Troubleshooting
  11. Quick Reference
  12. Change Impact Map
  13. Known Limitations and Intentional Decisions
  14. Common Failure Signatures

What This Test Covers

Lifecycle test path:

  1. Ensure shared category exists (create-if-missing).
  2. Create a new template under that category.
  3. Import form template JSON into builder.
  4. Save template and publish it.
  5. Open renderer from View Templates and fill fields.
  6. Submit form.
  7. Open View Form Data and verify readonly values.

Primary files:

  • e2e/tests/dynaforms-lifecycle.test.ts
  • e2e/flows/dynaforms.flow.ts
  • e2e/pages/dynaformsPage.ts
  • e2e/pages/selectors/dynaformsSelector.ts
  • e2e/utils/dynaforms-json-loader.ts
  • e2e/drivers/dynaforms/rendererFieldDrivers.ts

Setup and Run Commands

Prerequisites:

  • app is running and reachable at Config.baseUrl (e2e/utils/config.ts),
  • login setup test works (e2e/tests/login.setup.ts),
  • selector hooks (data-id) exist in dynaforms UI where needed.

2.1 Default run (uses default fixture files)

npm run test:e2e:dynaforms

Default files:

  • e2e/resources/dynaforms/form-template.json
  • e2e/resources/dynaforms/form-data.json

2.2 Headed run (watch browser)

npm run test:e2e:dynaforms:headed

Direct command equivalent: npx playwright test e2e/tests/dynaforms-lifecycle.test.ts --headed

2.3 Override fixture files from command args

Important: custom args are passed after --.

npx playwright test e2e/tests/dynaforms-lifecycle.test.ts --headed -- \
  --dynaform-template-json=e2e/resources/dynaforms/samples/arg-template.json \
  --dynaform-form-data-json=e2e/resources/dynaforms/samples/arg-form-data.json

2.4 Interactive debugging

Use Playwright debug mode directly:

  • npm run test:e2e:dynaforms:debug
  • with custom files:
    • npx playwright test e2e/tests/dynaforms-lifecycle.test.ts --debug -- --dynaform-template-json=... --dynaform-form-data-json=...

2.5 Optional worker pinning

  • Use --workers=1 only when you want strict serial execution during debugging or flaky-state diagnosis.
  • Normal local runs can use the default worker setting.
  • Chromium project selection is already configured in Playwright config for this suite.

2.6 Available npm scripts for lifecycle test

  • npm run test:e2e:dynaforms
  • npm run test:e2e:dynaforms:headed
  • npm run test:e2e:dynaforms:debug

End-to-End Architecture

Layer responsibilities:

  • Test: thin entrypoint + final assertions.
  • Flow: orchestration + dynamic test data + lifecycle sequencing.
  • Page: navigation and UI actions/assertions.
  • Selectors: centralized locator contracts.
  • Loader: normalize JSON files into executable fill/expect maps.
  • Drivers: widget-specific fill/assert logic.

Top-level test contract assertions (in addition to deep flow/page checks):

  • categoryName equals E2E Shared Category,
  • categoryUuid matches UUID format,
  • templateName matches generated pattern E2E Template-<timestamp>.

Flow Execution Details

File: e2e/flows/dynaforms.flow.ts

  1. Read CLI arg overrides from Playwright metadata (dynaformTemplateJsonArg, dynaformFormDataJsonArg).
  2. Load normalized test inputs from loader.
  3. Ensure shared category:
    • target: E2E Shared Category / E2ESHARED.
    • create only if missing.
  4. Create unique template (Date.now() suffix).
  5. Import template JSON file in builder.
    • if blocked import shows Auto-Fix & Import, click it and continue.
  6. Save template and publish from template card.
  7. Open renderer popup from View Templates.
  8. Fill fields via jsonKey -> driver mapping.
  9. Submit and close popup.
  10. Open View Form Data and assert readonly fields.

Selector Strategy

File: e2e/pages/selectors/dynaformsSelector.ts

Rules used:

  • prefer data-id hooks over business labels,
  • keep selectors centralized (no ad-hoc scattered selectors),
  • expose dynamic helpers for jsonKey, category code, tab key/value.

Notable helpers:

  • getRendererFieldByJsonKey(jsonKey) -> [data-id="renderer-field-{jsonKey}"]
  • getTabHeaderByKeyAndValue(tabJsonKey, tabValue) -> tab activation
  • importAutoFixButton -> auto-fix action in blocked import modal state
  • rendererFileInput / rendererFileUploadButton / rendererUploadedFileName
  • signature hooks: signatureUploadButton, signatureClearButton

JSON Loader Deep Dive

File: e2e/utils/dynaforms-json-loader.ts

6.1 Input resolution and override chain

Source of override values:

  1. Playwright CLI args parsed in playwright.config.ts.
  2. Stored in config.metadata.
  3. Passed test -> flow -> loader as DynaformInputOverrides.

Fallback behavior:

  • if override missing, loader uses default fixture files.

Fail-fast behavior:

  • if resolved path does not exist, loader throws immediately (Input file not found...).

Why this chain exists:

  • page layer stays pure UI interaction (no config parsing),
  • flow owns data orchestration,
  • loader remains deterministic and testable.

6.2 Template normalization and field config extraction

  • accepts either:
    • root array, or
    • { form_template_definition: [...] }.
  • extracts field config map keyed by jsonKey:
    • type, date/time formatting flags,
    • formula output config (outputType, outputCurrency),
    • matrix metadata (matrixKey, iterativeTable),
    • tab parent info (tabJsonKey, tabValue) for tab activation.

buildFieldConfigByJsonKey(...) details:

  • traverses children recursively for container nodes,
  • traverses tabs[].children with inherited tabParentInfo,
  • creates one map entry per node with non-empty jsonKey,
  • this map is the only source of field type metadata used by driver dispatch.

6.3 Form-data normalization pipeline

Supported shapes:

  1. Explicit mode:
    • top-level fill and/or expect.
  2. Auto mode:
    • json.documentInfo / documentInfo (nested),
    • json.flat / flat (flat map),
    • json.fact / fact (rows with dimensions.jsonKey).

Important: these three auto-mode sources are composable, not exclusive. A single payload can provide any one source, any two sources, or all three (documentInfo, flat, fact) together. The loader merges whatever is present using the precedence below.

Merge precedence in auto mode:

  1. documentInfo
  2. flat
  3. fact (wins on key collisions)

If nothing recognized, loader throws fail-fast error.

Important typing behavior:

  • primitives: string | number | boolean | null,
  • list values: string[] (multiselect, multi-file payloads),
  • matrix cells initially enter as primitive facts and are grouped later.

Map contracts produced:

  • fillByJsonKey: authoritative fill map for renderer phase,
  • expectedByJsonKey: authoritative assert map for review phase.

If explicit expect missing:

  • expectedByJsonKey defaults to the computed fill map.

6.3.1 documentInfo flattening logic

documentInfo can be deeply nested by parent keys (div/tab/section).
Loader recursively walks nested objects and lifts only valid leaf payloads:

  • accepts primitive leaves,
  • accepts string[] leaves,
  • continues recursion for nested objects,
  • ignores unsupported leaf shapes.

6.3.2 fact extraction logic

fact[] rows are interpreted via dimensions.jsonKey.

  • if value is primitive, map directly,
  • if value is string[], keep as list value,
  • ignore invalid/missing keys.

This is where most runtime patch payloads originate.

6.4 Matrix grouping

Matrix facts initially look like flat keys (matrix_<key>_...). Drivers need grouped cell maps under table jsonKey.

groupMatrixCells():

  • scans template for type === "table" nodes,
  • builds matrixKey -> tableJsonKey,
  • moves matching flat keys under that table key as MatrixCellMap.

Mutation semantics:

  • function mutates provided map in place,
  • deletes flat matrix cell keys after grouping,
  • merges into existing grouped entry if already present.

Reason:

  • page loop targets wrapper by table jsonKey,
  • table driver needs all cell values in one payload object.

Renderer Drivers Deep Dive

File: e2e/drivers/dynaforms/rendererFieldDrivers.ts

Dispatch entrypoints:

  • fillRendererFieldByType(...)
  • assertRendererFieldByType(...)

Registry:

  • rendererFieldDrivers[fieldType]
  • fallback: defaultDriver

7.1 Driver contract

Every driver implements:

  • fill(wrapper, value, config?)
  • assert(wrapper, expected, config?)

wrapper always points to [data-id="renderer-field-{jsonKey}"].

config usage pattern:

  • ignored for simple fields,
  • required for date/time/formula/matrix nuanced behavior.

7.2 Driver behavior matrix

Field typeFill behaviorAssert behaviorKey notes
textbox, textarea, number, email, password, phonedirect input fillexact inputValue() comparevia defaultDriver
datepickerkeyboard commit with format conversiondate-aware compareuses template dateFormat / showTime
timepickerkeyboard committime-aware comparerespects hourFormat
filemode-aware (basic immediate upload, advanced explicit upload click)uploaded names contain expected basename(s)supports single/multi path inputs
checkboxclick only if current state differschecked/not checkedboolean coercion helper
toggleswitchclick visual switch if neededchecked/not checkeduses input[role="switch"]
radioclick input[value=...]ensure checkedvalue-based, no label-text dependency
dropdownopen overlay, pick option, wait closelabel equals expectedoverlay rendered under body
autocompletetype + blur + patched executeCurl requestexact input valueroute interception patches stale bearer token
multiselectopen panel, click each option, ESC closelabel contains values OR N items selected countworks in disabled review mode
signatureread file path -> draw image to canvas -> uploadupload disabled + data-signature-path shapeserver persists new path
boxwidgetfill per character across inputsconcat and comparecase-insensitive compare
tablefill matrix cells, add iterative rows if needednumeric compare for all expected cellsskips readonly/aggregate in fill
formulano-op fillassert by outputTypeAdvanced Formula: date + decimal asserted; currency intentionally skipped

7.3 Advanced Formula behavior

Formula fields are engine-computed and readonly, so fill is intentionally no-op.

Assert branch by config.outputType:

  1. date
  • reads rendered input value,
  • applies date-aware assertion helper with widget format context.
  1. decimal
  • reads input value,
  • compares numerically using tolerant float comparison (toBeCloseTo).
  1. currency
  • currently skipped intentionally.
  • reason: robust currency assertion requires locale + symbol + format contract, and current fixture data is raw numeric. Hard assertion would create false negatives.

If formula behavior changes in product:

  • update loader extraction for formula metadata first,
  • then update formula driver assert branch accordingly.

7.4 File driver internals (basic + advanced)

Mode detection:

  • checks if advanced root exists ([data-id^="renderer-fileupload-"]).

Basic mode path:

  • set input files on basic hidden file input,
  • rely on component immediate upload behavior.

Advanced mode path:

  • set input files inside advanced p-fileupload,
  • wait upload button visible + enabled,
  • click upload button explicitly.

Completion check (shared):

  • poll rendered uploaded file name/link nodes,
  • wait until uploaded count reaches expected file path count.

Assert:

  • expected basenames must be found in rendered uploaded file names.

7.5 Autocomplete internals (token patch path)

Why special handling exists:

  • backend call body contains stale embedded curl auth tokens.

Driver behavior:

  • intercept **/thirdPartyService/executeCurl,
  • extract live bearer token from current request headers,
  • rewrite stale token fragments in request body,
  • continue request with patched body.

Commit strategy:

  • type with delay to trigger debounce pipeline,
  • press Tab to trigger blur + uniqueness check,
  • wait response before asserting input value.

7.6 Signature specifics (common change point)

Current signature strategy:

  1. Payload provides local image path.
  2. Driver reads file and converts to data URL.
  3. Driver draws that image to canvas.
  4. Driver clicks Upload.
  5. Assert checks:
    • Upload button disabled,
    • canvas data-signature-path exists and matches signature filename pattern.

Why not assert exact same source file path?

  • backend returns a new persisted signature path/name,
  • correct invariant is uploaded/persisted state, not equality to local source filename.

7.7 Matrix driver internals

Fill phase:

  • parse MatrixCellMap,
  • if iterative table, infer highest row index and click add-row button as needed,
  • iterate sorted keys and fill non-readonly cells,
  • skip readonly/aggregate/formula cells in fill.

Assert phase:

  • short settle wait for computed aggregates,
  • read every expected matrix input by data-id,
  • compare numerically with tolerance.

Page Object Details

Key patterns:

  • always navigate through BasePage.navigateTo(...),
  • explicit readiness checks after navigation (toBeVisible, waitForSelector),
  • actionability wait before clicks,
  • popup management for renderer tab,
  • tab-aware field loops:
    • partitionByTab(...)
    • activateTab(...)
  • dynamic onboarding grid UUID extraction:
    • read UUID directly from row-level data-id hook:
      • category-uuid-{categoryCode},
    • avoids both column-index and localized-header-text coupling.

Tab flow:

  1. non-tab fields fill/assert first,
  2. grouped tab fields next,
  3. before tab group interactions, click header by tabJsonKey + tabValue.

Popup flow note:

  • renderer opens in popup from View Templates,
  • after submit redirect confirmation, popup is closed,
  • control returns to original tab for View Form Data verification.

Extending Support for a New Widget

Checklist:

  1. Ensure renderer wrapper has stable selector:
    • [data-id="renderer-field-{jsonKey}"] around the field.
  2. Add any additional required hooks in dynaforms component HTML (data-id).
  3. Add driver implementation in rendererFieldDrivers.ts.
  4. Register driver in rendererFieldDrivers map.
  5. If field needs config (format/options), ensure loader extracts required config keys.
  6. Add sample values in form-data fixture and template fixture.
  7. Run lifecycle test in headed/debug mode.
  8. Update this doc + docs/dynaforms-history.md.

Troubleshooting

CLI override not used

  • ensure custom args come after --.
  • use one-line arg tokens; avoid accidental line breaks in argument names.
  • intentional bad path should fail fast with Input file not found.

Field not found by jsonKey

  • verify template jsonKey,
  • verify renderer has wrapper data-id="renderer-field-{jsonKey}",
  • confirm field value exists in normalized loader output.

Tab fields timeout

  • confirm tab headers use expected data-id="tab-header-{tabJsonKey}-{tabValue}",
  • confirm loader builds tabParentInfo for those fields.

Signature mismatch confusion

  • expected input path is local source file,
  • asserted path is persisted server-side signature path (not identical by design).

Autocomplete passes manually but fails in E2E

  • verify request interception still targets **/thirdPartyService/executeCurl,
  • token replacement regex may need update if request payload format changes.

Import modal does not close after file upload

  • check whether blocked-import state is shown with Auto-Fix & Import,
  • verify selector [data-id="import-auto-fix"] exists and is clickable,
  • confirm E2E import flow applies auto-fix before waiting for modal close.

Quick Reference

  • Methodology: docs/testing/methodology/playwright-e2e.md
  • Entry test: e2e/tests/dynaforms-lifecycle.test.ts
  • Orchestration: e2e/flows/dynaforms.flow.ts
  • UI actions: e2e/pages/dynaformsPage.ts
  • Selectors: e2e/pages/selectors/dynaformsSelector.ts
  • Loader: e2e/utils/dynaforms-json-loader.ts
  • Drivers: e2e/drivers/dynaforms/rendererFieldDrivers.ts

Change Impact Map

  1. If renderer DOM changes (data-id, wrappers, popup/button structure):
    • update e2e/pages/selectors/dynaformsSelector.ts,
    • update affected methods in e2e/pages/dynaformsPage.ts,
    • re-run lifecycle test in --headed (and optionally --debug) mode.
  2. If template JSON schema changes (type, tabs, matrix metadata, formula metadata):
    • update buildFieldConfigByJsonKey(...) in e2e/utils/dynaforms-json-loader.ts,
    • verify tab partitioning and matrix grouping behavior.
  3. If form-data export shape changes (documentInfo/flat/fact):
    • update extraction helpers in e2e/utils/dynaforms-json-loader.ts,
    • preserve merge semantics and array support.
  4. If a widget behavior changes (input mechanics or readonly rendering):
    • update the widget driver in e2e/drivers/dynaforms/rendererFieldDrivers.ts,
    • keep fill and assert paths symmetric.
  5. If routing/navigation behavior changes:
    • update navigation readiness checks in page methods,
    • keep post-navigation primary DOM checks mandatory before actions.
  6. If builder import modal behavior changes (blocked/fixable path):
    • update auto-fix selector hook in component HTML and selectors map,
    • update importTemplateFromFile(...) branch logic in e2e/pages/dynaformsPage.ts.

Known Limitations and Intentional Decisions

  1. Formula currency assertion is intentionally skipped due locale/currency formatting variance; date and decimal are asserted.
  2. Shared category is fixed (E2E Shared Category / E2ESHARED) by design to avoid duplicate category growth across runs.
  3. Lifecycle flow is optimized for deterministic single-worker execution in a stateful environment.
  4. Renderer popup handoff is explicit: submit happens in popup; review-page checks continue on the original tab.

Common Failure Signatures

  1. Input file not found:
    • verify CLI arg syntax after -- and confirm path exists.
  2. Form data JSON must contain ...:
    • verify payload includes fill/expect or at least one of documentInfo/flat/fact.
  3. timeout on [data-id="renderer-field-..."]:
    • verify template jsonKey and renderer wrapper data-id match.
  4. tab-field timeouts:
    • verify tab header selector hooks and loader tab metadata extraction.
  5. file/signature readonly mismatches:
    • verify persisted server output shape and readonly selectors.
  6. import modal remains open after file select:
    • verify auto-fix branch selector and click path for blocked import.