Cucumber Framework
The TypeScript/Cucumber BDD framework drives end-to-end integration tests against the FloatMe QA environment. Each scenario assembles a synthetic user through a declarative Given/When/Then pipeline, calls real service APIs over SigV4-signed HTTP, and asserts on the responses.
Folder Layout
cucumber/ ├── features/ │ ├── features/ ← .feature files (imported from Jira/Xray) │ └── step-definitions/ ← TypeScript step implementations ├── import_features.sh ← pulls feature files from Xray ├── export_results.sh ← pushes results back to Xray ├── cucumber.json ← runner config (parallel, retry, loader) └── .env ← local env overrides (not committed)
World Context
customWorld extends Cucumber’s World class and carries all state that is shared across steps within a single scenario.
Every scenario starts with a fresh customWorld instance; nothing persists between scenarios.
| Field | Type | Populated By | Purpose |
|---|---|---|---|
|
|
First |
Returned by |
|
|
|
Identifies the active LOC loan for payment, collection, and assertion steps. |
|
|
|
Returned by |
|
|
|
Returned by |
|
|
|
Stored alongside |
|
|
|
aws4fetch client pre-loaded with temporary STS credentials; all service calls use |
|
|
|
Base URL for the QA simulation API ( |
|
|
|
Base URL for the Subscription service. |
|
|
|
Base URL for the Underwriting service. |
|
|
|
Base URL for the Line of Credit service. |
|
|
|
Base URL for the Payments service. |
|
|
|
Base URL for the User service. |
|
|
|
Base URL for the Float service. |
|
|
|
Accumulates the full user specification sent to |
|
|
|
Lazy-loaded from |
|
|
|
Lazy-loaded from |
|
|
|
Lazy-loaded from |
|
|
|
Lazy-loaded from |
|
|
|
Lazy-loaded from |
|
|
|
Lazy-loaded from |
|
|
|
Lazy-loaded from |
|
|
|
Lazy-loaded from |
|
|
|
Loaded from |
|
|
|
Collection history keyed by float ID, lazy-loaded per float so assertions can span more than one float. Cleared with the other float caches by every step that changes float state. |
|
|
|
HTTP status (200 full, 402 partial) plus collected, remaining, and total amounts from |
|
|
|
Per-float job results from |
|
|
|
Holds the proposed installment schedule returned by |
|
|
|
Raw fetch |
|
|
|
Captures the installment total before a late fee is assessed; used to verify the exact 5% fee calculation. |
|
|
|
Captures the loan total before any late fees are added; used by |
|
|
|
Remaining installment balance after a partial payment; available for downstream late-fee calculations. |
|
|
|
Amount of the most recent partial payment; available for downstream assertions. |
Hooks
Target environment
QA_ENV (default test) is the only per-environment setting.
environment.ts derives everything else from it: the Plaid secret is <env>/plaid, and every service URL is https://<application>.<env>.svc.floatme.io, the hostname each service creates in the infrastructure repo’s per-environment zone.
| World field | Application |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Credentials
The suite never assumes a role. Credentials and region come from the AWS SDK’s default chain, so whatever starts the suite is responsible for arriving as the environment’s Cucumber runner role, which can invoke the service APIs and read the Plaid secret.
| Entry point | Runner role | How it gets there |
|---|---|---|
CI ( |
|
|
Local |
|
|
Local runs target staging only; the shared test account is outside the organization, so Identity Center cannot reach it.
cucumber/aws.config ships with the repo, so nothing under ~/.aws needs editing.
When the SSO session has expired, sign in with aws sso login --sso-session floatme (the token cache is shared with any profile using that session name).
Keep AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY out of cucumber/.env; a profile is the only credential source.
Step Definition Files
| File | Responsibility | Key Steps (representative patterns) |
|---|---|---|
|
Defines the |
(no step registrations — world definition only) |
|
Lifecycle hooks: |
(no step registrations — hooks only) |
|
Derives the Plaid secret name and every service URL from |
(no step registrations — configuration only) |
|
TypeScript interfaces for all domain objects ( |
(no step registrations — type declarations only) |
|
Pure utility functions shared across step files: date formatting, relative-date parsing, ordinal-to-index conversion, random string/account generation. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Given / When / Then Conventions
Given Steps — customUser Builder
Every Given step mutates this.customUser — the QAIntegrationUser object that will be POSTed to the QA API.
The first Given step (Given A user) initialises the object with faker-generated defaults; all subsequent Given steps add or override specific fields.
No network calls are made during Given steps.
Given A user And They have a connected plaid account And They have valid income And They have a valid debit card And They have a "standard" "PENDING" float due "5 days from now" for amount 50 and fee 5
The accumulated customUser payload includes nested objects for user, plaid.override_accounts, floats, subscriptions, loans, income, and debit_card.
When Steps — First When Creates the User
The first When step in nearly every scenario calls createUser(), which POSTs the fully assembled customUser to POST /qa/integration/custom-user.
The response returns a user_id stored on this.userID.
A 10-second sleep follows to allow downstream async processing to complete.
When The user is created
Subsequent When steps call simulation or service endpoints using the now-populated this.userID:
-
POST /qa/simulate/subscriptions/daily— triggers the subscription collections batch for the user -
POST /qa/simulate/floats/daily— runs the Float Service collection jobs for the user’s collectable floats synchronously and returns per-float results -
POST /qa/simulate/loc/collections— triggers LOC collections (withprocessandlook_ahead_daysparams) -
POST /{userID}/loans/applications→POST /{userID}/loans/generate_loan_details→POST /{userID}/loans— three-step LOC application flow -
POST /{userID}/loans/{loanID}/payback— manual loan payment -
POST /{userID}/user/membership/upgrade|downgrade|cancel|pause|unpause— membership lifecycle actions
Then Steps — Assertion Patterns
Then steps call service read APIs directly with this.aws.fetch() (SigV4-signed) and use Chai expect or assert for assertions.
Responses are cached on the world object using the nullish-assignment pattern (??=) so multiple Then steps in the same scenario share one network round-trip.
Then The user should have 1 float(s) And The "first" float status should be "COMPLETED" And The "first" float amount should be 50
Error-path assertions use this.lastResponse (stored by steps that intentionally suppress throw):
Then The user should receive a 400 status code
Parallelism and Retry
Configuration lives in cucumber/cucumber.json:
{
"default": {
"loader": ["ts-node/esm"],
"import": ["features/step-definitions/**/*.ts"],
"parallel": 5,
"retry": 1,
"strict": false
}
}
-
parallel: 5— up to 5 scenarios run concurrently using Cucumber’s worker pool. Each worker gets its owncustomWorldinstance and its ownBeforehook invocation (separateAwsClient). -
retry: 1— a failing scenario is automatically retried once before being marked failed. This tolerates transient API latency or async processing delays. -
strict: false— undefined or pending steps are reported but do not abort the run.
The test runner is invoked with:
yarn test # expands to: cucumber-js -f json:out.json
A local run targets staging through the shipped AWS config (see Credentials):
make cucumber.staging ARGS='--tags @ENG-11943'
Results are written to out.json for Xray import.
Tagging Conventions
Each .feature file carries a feature-level tag matching its Jira issue key (e.g. @ENG-8820).
Individual scenarios may carry additional tags for grouping or filtering.
-
import_features.sh— pulls updated.featurefiles from Xray using the issue keys embedded in file names. -
export_results.sh— readsout.jsonafter a run and pushes results back to Xray, matching results to test executions by tag.
Running a single suite by tag:
cucumber-js --tags @ENG-9692
Test Suites
| Feature File | Focus Area | Jira Key |
|---|---|---|
|
Subscription billing: COMPLETED, FAILED, RETRIED, error codes, pay-ahead flows |
ENG-8820 |
|
Underwriting eligibility: income detection, balance thresholds, float approval/denial |
ENG-9250 |
|
Line of Credit: loan origination, collections, manual payments, late fees, Iterable events |
ENG-9692 |
|
User lifecycle and membership tier management: upgrade, downgrade, cancel, pause, reactivate |
ENG-11544 |
|
Float requests, status assertions, active/historical float counts |
ENG-11943 |
|
Float collection jobs (initial, daily retry, day-before ACH, prenotes): outcome, payment, and collection-history verification |
ENG-12341 |
Related Pages
-
Simulation Flows — QA API endpoints used by
Whensteps -
Integration Users —
QAIntegrationUserpayload reference -
Predefined User States — reusable
Givencombinations -
CI/CD — how the Cucumber suite is executed in GitHub Actions
-
External Integrations — Xray, Iterable, and Plaid sandbox wiring