@genesislcap/bdd-ui-automation
v0.1.27
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\"]"
},
"uiScanner": {
"language": "react-tsx",
"include": [".tsx", ".ts"],
"exclude": [".test.", ".spec.", ".stories.", "\\node_modules\\"]
},
"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.
Generate a starter selector inventory from a consumer UI codebase:
npx bdd-ui-init-selectors --ui-path "C:\\work\\code\\sales-trader-hub\\client" --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.
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 scroll to element "ROLL"
When I choose "Ireland" from "Country"
Then "Country" should have value "Ireland"
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\"]"
}
}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"
Then "country-dropdown" should have value "Ireland"
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.
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
The 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:
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
