@genesislcap/bdd-ui-automation
v0.1.37
Published
Reusable Cucumber, Playwright, and Allure BDD UI step library.
Maintainers
Readme
BDD UI Automation
Reusable Cucumber, Playwright, and Allure BDD UI step library.
This package is designed for consumer applications that only want to write feature files and environment config. Consumers install the npm package, configure Cucumber to load the compiled support and step entrypoints, and use the shared Gherkin vocabulary.
Consumer setup
Install the package:
npm install @genesislcap/bdd-ui-automationInitialize a consumer project structure:
npx bdd-ui-init-resourcesTo include sample files as a starting point:
npx bdd-ui-init-resources --include-samplesThe initializer creates:
resources/
1-payload/
2-actual/
3-expected/
4-config/
bdd-ui.config.json
5-input/
features/
support/
cucumber.jsThen add or review cucumber.js:
module.exports = {
default: {
require: [
'./node_modules/@genesislcap/bdd-ui-automation/dist/support/index.js',
'./node_modules/@genesislcap/bdd-ui-automation/dist/steps/index.js'
],
paths: ['resources/features/**/*.feature'],
format: [
'progress',
'allure-cucumberjs/reporter'
],
publishQuiet: true
}
};Add resources/4-config/bdd-ui.config.json:
{
"baseUrl": "https://qa.example.com",
"browser": "chromium",
"headless": true,
"timeout": 30000,
"trace": true,
"screenshotOnFailure": true,
"viewport": {
"width": 1440,
"height": 900
},
"resourcePaths": {
"payload": "resources/1-payload",
"actual": "resources/2-actual",
"expected": "resources/3-expected",
"config": "resources/4-config",
"input": "resources/5-input",
"features": "resources/features"
},
"jsonComparison": {
"engine": "jar",
"output": "json"
},
"grids": {
"trade": "[data-testid=\"trade-grid\"]"
},
"selectors": {
"login-form": "[data-test-id=\"login-form\"]",
"qte-form": "[data-test-id=\"qte-form\"]",
"open-full-ticket-button": "[data-test-id=\"open-full-ticket-button\"]"
},
"selectorSources": [
"resources/4-config/selectors/**/*.json"
],
"uiScanner": {
"language": "react-tsx",
"include": [".tsx", ".ts"],
"exclude": [".test.", ".spec.", ".stories.", "\\node_modules\\"],
"selectorOutput": {
"mode": "split",
"strategy": "by-folder",
"generatedDir": "resources/4-config/selectors",
"stripPathPrefixes": ["src"]
}
},
"screenshots": {
"expectedDir": "resources/3-expected/screenshots",
"actualDir": "resources/2-actual/screenshots",
"diffDir": "resources/2-actual/screenshots-diff",
"mode": "compare",
"threshold": 0.1,
"maxDiffPixels": 0
},
"fieldVerification": {
"Notional": {
"mode": "normalized"
},
"Counterparty": {
"mode": "contains"
},
"Premium BPS": {
"mode": "pattern",
"expectedPattern": "^\\d+\\.\\d{2}$"
},
"Trade Date": {
"mode": "formatted",
"expectedPattern": "^\\d{2}/\\d{2}/\\d{4}$"
},
"Internal Notes": {
"mode": "none"
}
},
"auth": {
"loginPath": "/login",
"successUrlContains": "/home"
},
"credentials": {
"standardUser": {
"username": "qa-user",
"password": "secret"
}
}
}The generated starter config also includes a minimal jsonComparison section so new consumer projects are ready for
the bundled comparator flow without needing to invent a config shape later.
When screenshotOnFailure is enabled, the framework attaches a full-page screenshot only for failing steps. Passing
steps do not produce screenshot evidence automatically.
Generate a starter selector inventory from a consumer UI codebase:
npx bdd-ui-init-selectors --ui-path "C:\\work\\code\\sales-trader-hub\\client" --config-path "C:\\work\\code\\sales-trader-hub-qa-automation\\client-test-automation\\resources\\4-config\\bdd-ui.config.json" --writeBy default the generator reads uiScanner.language from bdd-ui.config.json, scans React/TSX source files, merges
stable data-test-id / data-testid values into selectors, promotes obvious grid selectors into grids, and
prints dynamic selector expressions that it skipped. When --write targets an existing bdd-ui.config.json, the
generator creates a timestamped backup before writing the merged file.
For larger UI projects, you can keep selector aliases in multiple files instead of one large selectors block. Add
selectorSources to load selector catalogs, then configure uiScanner.selectorOutput so the generator writes new
selector files into that catalog:
{
"selectorSources": [
"resources/4-config/selectors/**/*.json"
],
"uiScanner": {
"selectorOutput": {
"mode": "split",
"strategy": "by-folder",
"generatedDir": "resources/4-config/selectors",
"stripPathPrefixes": ["src"]
}
}
}With that setup:
- existing inline
selectorsstill work - selector files are merged first
- inline
selectorsoverride duplicate aliases from selector files gridsstill stay in the mainbdd-ui.config.json
The split generator supports these grouping rules:
single-file: write everything togenerated.jsonby-folder: write one JSON file per source folder, for examplesrc/pages/rfq/Ticket.tsxbecomespages/rfq.jsonby-file: write one JSON file per source file, for examplesrc/pages/rfq/Ticket.tsxbecomespages/rfq/Ticket.json
stripPathPrefixes removes leading path segments before grouping, which is useful when most consumer UIs live under
src/.
For projects using the recommended numbered resources/... layout, prefer passing --config-path explicitly so the
scanner updates:
resources/4-config/bdd-ui.config.jsonIf --config-path is omitted, the generator defaults to a root-level bdd-ui.config.json in the current working
directory.
Write feature files:
Feature: Login
Scenario: Log in to the application
Given I log in as "standardUser"
Then the current URL should contain "/home"For richer component workflows, the framework also supports custom dropdowns, scrolling, tab navigation, and value assertions for custom controls:
Scenario: Work with custom UI controls
Given I log in as "standardUser"
When I open the "Main" tab
When I open the "Main" tab in "workspace-panel"
When I scroll to element "ROLL"
When I choose "Ireland" from "Country"
Then "Country" should have value "Ireland"
Then "Notional" should contain value "1,000"
Then the "Main" tab should be activeThe framework can also read dropdown contents and compare them with expected values:
Scenario: Verify country dropdown values
Given I log in as "standardUser"
When I open the "Main" tab
Then the dropdown "Country" should contain exactly:
| Ireland |
| France |
| Germany |Use selector aliases from bdd-ui.config.json whenever a consumer app has stable data-test-id hooks:
{
"selectors": {
"country-dropdown": "[data-test-id=\"country-dropdown\"]",
"main-tab": "[data-test-id=\"main-tab\"]",
"roll-field": "[data-test-id=\"ROLL\"]",
"workspace-panel": "[data-test-id=\"workspace-panel\"]"
}
}Then feature files can stay readable while still targeting custom wrappers:
When I choose "Ireland" from "country-dropdown"
When I scroll to element "roll-field"
When I open the "main-tab" tab in "workspace-panel"
Then "country-dropdown" should have value "Ireland"
Then "country-dropdown" should contain value "Ire"
Then the "main-tab" tab should be activeThe dropdown-content assertion steps use the same alias resolution and also support zero-based indexed dropdowns when the same field appears more than once.
The recommended consumer layout mirrors the API automation project:
resources/
1-payload/
2-actual/
3-expected/
4-config/
5-input/
features/Keep the structure flat inside each feature-name folder:
resources/features/
login.feature
extract-visible-grid.feature
qte-acceptance-criteria.feature
resources/5-input/
qte-acceptance-criteria/
qte-ac-core-valid.json
qte-ac-premium-valid.json
qte-global-field-synchronization/
qte-global-sync.json
resources/3-expected/
extract-visible-grid/
qte-grid-list.json
qte-acceptance-criteria/
qte-created.json
qte-success.json
resources/2-actual/
extract-visible-grid/
qte-grid-list.json
qte-acceptance-criteria/
qte-created.jsonUse 5-input for UI scenario data such as form JSON. The filename passed in the step is resolved inside the current
feature-name folder, so the step should use the plain file name:
When I fill the form from "qte-ac-core-valid.json"That path resolves under:
resources/5-input/<feature-name>/qte-ac-core-valid.jsonFor plain field fills and JSON-driven form fills, the framework now fails fast when a control does not retain the value that was just entered. That means steps such as:
When I fill "Username" with "test.user"
When I fill the form from "qte-ac-core-valid.json"do not stop at the interaction itself. After filling an input-style control, the runtime reads the value back and raises a clear mismatch error if the field still reports something else.
When a field intentionally transforms or expands the entered value, configure fieldVerification in
bdd-ui.config.json to loosen or redirect the readback check:
{
"fieldVerification": {
"Notional": {
"mode": "normalized"
},
"Counterparty": {
"mode": "contains"
},
"Premium BPS": {
"mode": "pattern",
"expectedPattern": "^\\d+\\.\\d{2}$"
},
"Trade Date": {
"mode": "formatted",
"expectedPattern": "^\\d{2}/\\d{2}/\\d{4}$"
},
"Internal Notes": {
"mode": "none"
}
}
}Fields not listed in fieldVerification use exact verification by default.
When a field name is mapped through selectors in bdd-ui.config.json, the framework now prefers that configured
selector for plain fill steps before falling back to label-based lookup. This helps Genesis/Foundation-style wrapped
controls target the actual editable input instead of a labelled container such as *-wrapper.
Locator strategy
The framework is not limited to getByText or getByLabel. Generic steps resolve targets through a layered lookup
strategy so the same Gherkin vocabulary can work across native elements and custom component wrappers.
For a target such as "Save", "country-dropdown", or "primary-cta", the runtime can use:
- selector aliases from
bdd-ui.config.json - accessible labels
- exact text
- placeholders
- role-based lookup for buttons, links, radios, and tabs
- common attributes such as
data-test-id,data-testid,name,aria-label,col-id, androw-id idandclass- raw CSS selectors
Class-based lookup is supported in two forms:
- pass the class token, for example
"primary-cta" - pass the explicit CSS selector, for example
".primary-cta"
Internally the framework can match selectors such as:
.primary-cta
[class~="primary-cta"]That means an element like this can still be targeted by class:
<button class="btn primary-cta large">Save</button>If multiple elements match, the plain step uses the first match by default. Use indexed steps when the same label/class/text appears more than once:
When I click ".primary-cta" at index 1Recommended team guidance:
- prefer
data-test-id/data-testidfor stable automation hooks - map those hooks through
selectorsinbdd-ui.config.json - use class selectors only as a fallback when no stable test hook exists
- use indexed steps when a target appears more than once
Assertion steps also support indexed text checks and read-only checks when repeated content or non-editable fields are part of the workflow:
Then I should see text "Quick Entry" at index 0
Then I should not see text "Validation failed" at index 1
Then "Premium Amount" should be read only
Then "Premium Amount" at index 1 should be read only
Then "Notional" should contain value "1,000"
Then "Desk" at index 1 should contain value "LDN"Tab steps also support scoped lookup and avoid redundant clicks on an already-active tab. After a tab click, the framework waits for the same tab to report an active state before continuing:
When I open the "Main" tab
When I open the "Detailed" tab at index 1
When I open the "Main" tab in "workspace-panel"
When I open the "Main" tab in "workspace-panel" at index 1The same distinction also applies to waits:
- use
When I wait for text "..."when you truly mean visible text - use
When I wait for element "..."when you want alias/config-aware element resolution
Viewport and screenshots
The framework now uses a controlled viewport by default:
{
"viewport": {
"width": 1440,
"height": 900
}
}If the consumer project does not set a viewport, 1440x900 is used automatically. Consumer projects can override it
in bdd-ui.config.json when they need a different standard resolution.
Screenshot comparison settings also live in config:
{
"screenshots": {
"expectedDir": "resources/3-expected/screenshots",
"actualDir": "resources/2-actual/screenshots",
"diffDir": "resources/2-actual/screenshots-diff",
"mode": "compare",
"threshold": 0.1,
"maxDiffPixels": 0
}
}Those roots are designed for nested relative screenshot paths such as:
home/dashboard/default
qte/quick-entry/form
home/blotter/trade-gridThe runtime writes actual and diff artifacts under matching subfolders, rejects unsafe path traversal such as ..,
and scrolls element targets into view before element screenshot capture. Alias-based masking is also supported through
the same selectors map used by the rest of the framework. Screenshot comparison steps also attach the actual PNG to
the Cucumber/Allure report, and attach the diff PNG when a mismatch produces one, so teams can inspect failures
without leaving the report.
Screenshot comparison steps:
Then the screen should match screenshot "dashboard/home-default"
Then element "qte-form" should match screenshot "qte-acceptance-criteria/qte-form-default"
Then element "tile" at index 1 should match screenshot "dashboard/tiles/second-tile"
Then element "qte-grid-list" should match screenshot "extract-visible-grid/qte-grid-list" masking "price-ticker,clock"For example:
Then element "qte-form" should match screenshot "qte-acceptance-criteria/qte-form-default"expects the baseline image at:
resources/3-expected/screenshots/qte-acceptance-criteria/qte-form-default.pngResponse comparison design
The clean design for backend response comparison is to bundle the Genesis comparator JAR inside the published
@genesislcap/bdd-ui-automation package and call it from reusable UI steps. That keeps the runtime self-contained for
consumer projects: the UI suite needs the npm package and Java, but it does not need Gradle or a pre-populated local
Maven cache at execution time.
The intended comparison flow is:
- capture the backend response during a user action
- write the captured JSON to
resources/2-actual/<feature-name>/<file> - compare it with
resources/3-expected/<feature-name>/<file> - delete the actual file on pass and keep it on failure
The planned Gherkin vocabulary is:
When I submit the form and capture response matching "EVENT_RFQ_TRADE_INSERT"
When I click "save-button" and capture response matching "EVENT_RFQ_TRADE_INSERT"
When I click "save-button" at index 1 and capture response matching "EVENT_RFQ_TRADE_INSERT"
Then I compare response with expected result "qte-create-ack.json"
Then I compare response with expected result "qte-create-ack.json" with Parent Object "REPLY"
Then I compare response with expected result "qte-create-ack.json" with Primary Key "TRADE_ID"
Then I compare response with expected result "qte-create-ack.json" and ignore "RFQ_ID,TIMESTAMP"
Then I compare response with expected result "qte-create-ack.json" with Parent Object "REPLY" with Primary Key "ID" for specific fields "STATUS,ID"
Then I compare response with expected result "qte-create-ack.json" and fail on extra "true"The bundled comparator CLI options that shape this design are:
--expected <path>--actual <path>--output <text|json>--parent-object <nameOrPath>--primary-key <field[,field2]>--ignore <field[,field2]>--fields <field[,field2]>--threshold-config <path>--health-check--fail-on-extra <true|false>
The comparator reports these exit codes:
0pass1comparison failed2extra-only differences when--fail-on-extra true3invalid CLI usage4input missing or unreadable5internal comparator error
docs/step-reference.md and docs/configuration.md describe the same contract in more detail so consumer teams can
align their feature files and project structure before the runtime integration lands.
Framework development
Framework developers should add reusable steps in src/steps/, shared Cucumber setup in src/support/, and internal Playwright helpers in src/runtime/.
Read:
docs/structure.mddocs/development.mddocs/configuration.mddocs/step-reference.md
License
Note: this project provides front-end dependencies and uses licensed components listed in the next section; thus, licenses for those components are required during development. Contact Genesis Global for more details.
Licensed components
Genesis low-code platform
