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 |
|
|
|
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
BeforeAll
Runs once before the entire test suite.
Uses the AWS SDK AssumeRoleCommand to obtain temporary credentials:
-
Role ARN:
arn:aws:iam::267052520423:role/test-postman-qa-role -
Duration: 900 seconds (15 minutes)
The resulting AccessKeyId, SecretAccessKey, and SessionToken are stored on this.parameters so they are available to every scenario’s Before hook.
Before (per-scenario)
Runs before each individual scenario.
Reads the temporary credentials from this.parameters and constructs an AwsClient (from aws4fetch) with retries: 0.
All seven API base URLs are assigned as literal execute-api URLs on this:
| World Field | Hardcoded URL |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
The default step timeout is 60 seconds (setDefaultTimeout(60 * 1000)).
Step Definition Files
| File | Responsibility | Key Steps (representative patterns) |
|---|---|---|
|
Defines the |
(no step registrations — world definition only) |
|
Lifecycle hooks: |
(no step registrations — hooks 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— triggers the float collections batch -
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
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 collections batch processing: daily collection runs, outcome and 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