# 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:

```bash
npm install @genesislcap/bdd-ui-automation
```

Initialize a consumer project structure:

```bash
npx bdd-ui-init-resources
```

To include sample files as a starting point:

```bash
npx bdd-ui-init-resources --include-samples
```

The initializer creates:

```text
resources/
  1-payload/
  2-actual/
  3-expected/
  4-config/
    bdd-ui.config.json
  5-input/
  features/
support/
cucumber.js
```

Then add or review `cucumber.js`:

```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`:

```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:

```bash
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" --write
```

By 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:

```json
{
  "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 `selectors` still work
- selector files are merged first
- inline `selectors` override duplicate aliases from selector files
- `grids` still stay in the main `bdd-ui.config.json`

The split generator supports these grouping rules:
- `single-file`: write everything to `generated.json`
- `by-folder`: write one JSON file per source folder, for example `src/pages/rfq/Ticket.tsx` becomes `pages/rfq.json`
- `by-file`: write one JSON file per source file, for example `src/pages/rfq/Ticket.tsx` becomes `pages/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:

```text
resources/4-config/bdd-ui.config.json
```

If `--config-path` is omitted, the generator defaults to a root-level `bdd-ui.config.json` in the current working
directory.

Write feature files:

```gherkin
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:

```gherkin
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 active
```

The framework can also read dropdown contents and compare them with expected values:

```gherkin
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:

```json
{
  "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:

```gherkin
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 active
```

The 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:

```text
resources/
  1-payload/
  2-actual/
  3-expected/
  4-config/
  5-input/
  features/
```

Keep the structure flat inside each feature-name folder:

```text
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.json
```

Use `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:

```gherkin
When I fill the form from "qte-ac-core-valid.json"
```

That path resolves under:

```text
resources/5-input/<feature-name>/qte-ac-core-valid.json
```

For 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:

```gherkin
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:

```json
{
  "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`, and `row-id`
- `id` and `class`
- 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:

```text
.primary-cta
[class~="primary-cta"]
```

That means an element like this can still be targeted by class:

```html
<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:

```gherkin
When I click ".primary-cta" at index 1
```

Recommended team guidance:
- prefer `data-test-id` / `data-testid` for stable automation hooks
- map those hooks through `selectors` in `bdd-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:

```gherkin
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:

```gherkin
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 1
```

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:

```json
{
  "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:

```json
{
  "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:

```text
home/dashboard/default
qte/quick-entry/form
home/blotter/trade-grid
```

The 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:

```gherkin
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:

```gherkin
Then element "qte-form" should match screenshot "qte-acceptance-criteria/qte-form-default"
```

expects the baseline image at:

```text
resources/3-expected/screenshots/qte-acceptance-criteria/qte-form-default.png
```

## Response 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:

1. capture the backend response during a user action
2. write the captured JSON to `resources/2-actual/<feature-name>/<file>`
3. compare it with `resources/3-expected/<feature-name>/<file>`
4. delete the actual file on pass and keep it on failure

The planned Gherkin vocabulary is:

```gherkin
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:
- `0` pass
- `1` comparison failed
- `2` extra-only differences when `--fail-on-extra true`
- `3` invalid CLI usage
- `4` input missing or unreadable
- `5` internal 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.md`
- `docs/development.md`
- `docs/configuration.md`
- `docs/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
