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
- What This Test Covers
- Setup and Run Commands
- End-to-End Architecture
- Flow Execution Details
- Selector Strategy
- JSON Loader Deep Dive
- Renderer Drivers Deep Dive
- Page Object Details
- Extending Support for a New Widget
- Troubleshooting
- Quick Reference
- Change Impact Map
- Known Limitations and Intentional Decisions
- Common Failure Signatures
What This Test Covers
Lifecycle test path:
- Ensure shared category exists (create-if-missing).
- Create a new template under that category.
- Import form template JSON into builder.
- Save template and publish it.
- Open renderer from View Templates and fill fields.
- Submit form.
- Open View Form Data and verify readonly values.
Primary files:
e2e/tests/dynaforms-lifecycle.test.tse2e/flows/dynaforms.flow.tse2e/pages/dynaformsPage.tse2e/pages/selectors/dynaformsSelector.tse2e/utils/dynaforms-json-loader.tse2e/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.jsone2e/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=1only 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:dynaformsnpm run test:e2e:dynaforms:headednpm 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):
categoryNameequalsE2E Shared Category,categoryUuidmatches UUID format,templateNamematches generated patternE2E Template-<timestamp>.
Flow Execution Details
File: e2e/flows/dynaforms.flow.ts
- Read CLI arg overrides from Playwright metadata (
dynaformTemplateJsonArg,dynaformFormDataJsonArg). - Load normalized test inputs from loader.
- Ensure shared category:
- target:
E2E Shared Category/E2ESHARED. - create only if missing.
- target:
- Create unique template (
Date.now()suffix). - Import template JSON file in builder.
- if blocked import shows
Auto-Fix & Import, click it and continue.
- if blocked import shows
- Save template and publish from template card.
- Open renderer popup from View Templates.
- Fill fields via jsonKey -> driver mapping.
- Submit and close popup.
- Open View Form Data and assert readonly fields.
Selector Strategy
File: e2e/pages/selectors/dynaformsSelector.ts
Rules used:
- prefer
data-idhooks 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 activationimportAutoFixButton-> auto-fix action in blocked import modal staterendererFileInput/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:
- Playwright CLI args parsed in
playwright.config.ts. - Stored in
config.metadata. - 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
childrenrecursively for container nodes, - traverses
tabs[].childrenwith inheritedtabParentInfo, - 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:
- Explicit mode:
- top-level
filland/orexpect.
- top-level
- Auto mode:
json.documentInfo/documentInfo(nested),json.flat/flat(flat map),json.fact/fact(rows withdimensions.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:
- documentInfo
- flat
- 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:
expectedByJsonKeydefaults 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
valueis primitive, map directly, - if
valueisstring[], 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 type | Fill behavior | Assert behavior | Key notes |
|---|---|---|---|
textbox, textarea, number, email, password, phone | direct input fill | exact inputValue() compare | via defaultDriver |
datepicker | keyboard commit with format conversion | date-aware compare | uses template dateFormat / showTime |
timepicker | keyboard commit | time-aware compare | respects hourFormat |
file | mode-aware (basic immediate upload, advanced explicit upload click) | uploaded names contain expected basename(s) | supports single/multi path inputs |
checkbox | click only if current state differs | checked/not checked | boolean coercion helper |
toggleswitch | click visual switch if needed | checked/not checked | uses input[role="switch"] |
radio | click input[value=...] | ensure checked | value-based, no label-text dependency |
dropdown | open overlay, pick option, wait close | label equals expected | overlay rendered under body |
autocomplete | type + blur + patched executeCurl request | exact input value | route interception patches stale bearer token |
multiselect | open panel, click each option, ESC close | label contains values OR N items selected count | works in disabled review mode |
signature | read file path -> draw image to canvas -> upload | upload disabled + data-signature-path shape | server persists new path |
boxwidget | fill per character across inputs | concat and compare | case-insensitive compare |
table | fill matrix cells, add iterative rows if needed | numeric compare for all expected cells | skips readonly/aggregate in fill |
formula | no-op fill | assert by outputType | Advanced 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:
date
- reads rendered input value,
- applies date-aware assertion helper with widget format context.
decimal
- reads input value,
- compares numerically using tolerant float comparison (
toBeCloseTo).
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
Tabto trigger blur + uniqueness check, - wait response before asserting input value.
7.6 Signature specifics (common change point)
Current signature strategy:
- Payload provides local image path.
- Driver reads file and converts to data URL.
- Driver draws that image to canvas.
- Driver clicks Upload.
- Assert checks:
- Upload button disabled,
- canvas
data-signature-pathexists 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-idhook:category-uuid-{categoryCode},
- avoids both column-index and localized-header-text coupling.
- read UUID directly from row-level
Tab flow:
- non-tab fields fill/assert first,
- grouped tab fields next,
- 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:
- Ensure renderer wrapper has stable selector:
[data-id="renderer-field-{jsonKey}"]around the field.
- Add any additional required hooks in dynaforms component HTML (
data-id). - Add driver implementation in
rendererFieldDrivers.ts. - Register driver in
rendererFieldDriversmap. - If field needs config (format/options), ensure loader extracts required config keys.
- Add sample values in form-data fixture and template fixture.
- Run lifecycle test in headed/debug mode.
- 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
tabParentInfofor 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
- 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.
- update
- If template JSON schema changes (
type,tabs, matrix metadata, formula metadata):- update
buildFieldConfigByJsonKey(...)ine2e/utils/dynaforms-json-loader.ts, - verify tab partitioning and matrix grouping behavior.
- update
- 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.
- update extraction helpers in
- 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.
- update the widget driver in
- If routing/navigation behavior changes:
- update navigation readiness checks in page methods,
- keep post-navigation primary DOM checks mandatory before actions.
- If builder import modal behavior changes (blocked/fixable path):
- update auto-fix selector hook in component HTML and selectors map,
- update
importTemplateFromFile(...)branch logic ine2e/pages/dynaformsPage.ts.
Known Limitations and Intentional Decisions
- Formula
currencyassertion is intentionally skipped due locale/currency formatting variance;dateanddecimalare asserted. - Shared category is fixed (
E2E Shared Category/E2ESHARED) by design to avoid duplicate category growth across runs. - Lifecycle flow is optimized for deterministic single-worker execution in a stateful environment.
- Renderer popup handoff is explicit: submit happens in popup; review-page checks continue on the original tab.
Common Failure Signatures
Input file not found:- verify CLI arg syntax after
--and confirm path exists.
- verify CLI arg syntax after
Form data JSON must contain ...:- verify payload includes
fill/expector at least one ofdocumentInfo/flat/fact.
- verify payload includes
- timeout on
[data-id="renderer-field-..."]:- verify template
jsonKeyand renderer wrapperdata-idmatch.
- verify template
- tab-field timeouts:
- verify tab header selector hooks and loader tab metadata extraction.
- file/signature readonly mismatches:
- verify persisted server output shape and readonly selectors.
- import modal remains open after file select:
- verify auto-fix branch selector and click path for blocked import.