The bank8 vertical's proof that the product works for the person using it: a real browser, a real sign-up, a real org - with the same restrictions a user has. Built on the official @playwright/test runner.
The suite drives only the bank8 tenant apps (bank8-admin,
bank8-ebank, bank8-eteller) through the browser and the
BFF8 proxy - platform setup happens in tests/global-setup.ts via the
sentin8-cli binary, and Dash flows live in the platform's own e2e
repo (sentin8-e2e). Per AGENTS.md, e2e never touches the database
directly - no psql, no backdoor API access from the UI suites.
Scope (OBVERT-3): the recipient journey (the former eFintech /
eOpenCA consent flow) lives in the openfin8 vertical's own suite,
OpenFin8/openfin8-e2e -
this suite covers bank8's services and apps only.
| Suite | Path | Description |
|---|---|---|
| Lifecycle | tests/bank8_lifecycle.spec.ts |
Serial UI journey across the bank8 apps: Ben creates ledger products in Admin, Alice signs up via eBank and opens accounts, Bob finds her in eTeller and deposits, Alice verifies her balance. The recipient journey (the consent flow at eOpenCA) lives in openfin8-e2e. |
| Tenant auth flows | tests/protocol/suite4_tenant_auth_flows.spec.ts |
HTTP-only: OIDC discovery, JWKS, token, health on tenant auth hosts. |
| Tenant resolution | tests/protocol/suite6_tenant_resolution_enforcement.spec.ts |
HTTP-only: cross-tenant isolation, host resolution, 401 on cross-tenant. |
| Token exchange | tests/protocol/suite8_token_exchange.spec.ts |
HTTP-only: RFC 8693 token-exchange protocol behavior. |
All URLs the suite touches are tenant org/env URLs, derived from the
run's personas (fixtures/testdata.ts):
| Surface | Form |
|---|---|
| App runtime | https://{app-slug}.{env-slug}.{org-domain} |
| BFF8 proxy | https://bff8.{app-slug}.{env-slug}.{org-domain} |
| Tenant auth (customer) | https://auth.{org-domain} |
| Staff auth env | https://auth.{env-slug}.{org-domain} |
org-domain is the platform default {org-slug}.{SENTIN8_DOMAIN} or the
org's custom domain (the personas declare theirs, e.g. ibank8NNNN.local).
The single platform-surface exception is Sentin8:Auth's system-realm
endpoint (Auth8URL), which the CLI's device-flow sign-in in
global-setup posts against and the protocol suites probe for system-realm
issuer checks.
- A running bank8 + platform stack - from the tree root:
make infra-up && make start # platform group: infra + services cd bank8 && make start # vertical: enrolled services + apps
- Node 20+ and a package manager (npm or pnpm).
- Local CA trust -
scripts/with-ca.shinstalls the Sentin8 local root CA into the trust store and exportsNODE_EXTRA_CA_CERTSbefore launching the runner (Node's bundled store ignores the system store).
make install # pnpm install + Chromium download# Full UI lifecycle (headed by default, failfast on first failure)
./scripts/with-ca.sh make test
# Lifecycle / protocol suites individually
./scripts/with-ca.sh make test-lifecycle
./scripts/with-ca.sh make test-protocol
# Direct runner invocation (equivalent)
./scripts/with-ca.sh npx playwright test tests/bank8_lifecycle.spec.ts
# Type check / HTML report
make lint
make reportEnvironment toggles: PLAYWRIGHT_HEADLESS=true (CI), SENTIN8_DOMAIN
(overrides the default local.sentin8.dev), SENTIN8_CLI_BIN (CLI path
override), PLAYWRIGHT_MAX_FAILURES=0 (disable failfast).
The suite tests the fleet the operator booted, so the spawned
sentin8-cli must present the fleet's OWN TLS posture - the CLI is a
rustls client whose key-exchange offer is keyed off
SENTIN8_TLS_PQ_MODE (defaulting to exclusive, X25519MLKEM768-only,
when unset). A fleet booted classical (the tree-root .env posture)
rejects that offer with a silent TLS HandshakeFailure: the CLI's
signup/call fail as connection errors with no HTTP trace, the
idempotent signup leaves the persona nonexistent, and the device-flow
approval sign-in 401s. fixtures/env.ts therefore loads the fleet TLS
posture vars (SENTIN8_TLS_PQ_MODE, SENTIN8_TLS_CIPHER_PROFILE,
SENTIN8_TLS_CLIENT_AUTH, SENTIN8_MTLS_CLIENT_AUTH) from the same
tree-root .env the fleet is booted from, and fixtures/cli.ts
planeEnv() copies them into every CLI child - real-environment values
always win. This mirrors the platform suite's DESIGN §4 invariant 7.
bank8-e2e/
├── package.json / tsconfig.json / playwright.config.ts
├── fixtures/
│ ├── testdata.ts - tenant-first URL helpers + personas + listing payloads
│ ├── state.ts - cross-suite state via .e2e-state.json
│ ├── harness.ts - Flutter semantics helpers, tenant sign-in/up flows
│ ├── apiclient.ts - direct HTTP client (local CA)
│ ├── cli.ts - sentin8-cli subprocess runner + setup call helpers
│ ├── strictpage.ts - console-error / HTTP-error assertions
│ └── index.ts - barrel export
├── scripts/
│ └── with-ca.sh - CA trust wrapper
└── tests/
├── global-setup.ts - platform setup via sentin8-cli (once per run)
├── bank8_lifecycle.spec.ts - the serial UI lifecycle
└── protocol/ - HTTP-only suites (4/6/8)
Every mutation test asserts the server-side effect (through the BFF8 proxy or the CLI), not UI text. History: the ledger audit-INSERT arity bug produced exactly this blind spot - every posting rolled back server-side while the teller UI reported "$500 deposited successfully", and the suite stayed green for weeks. Two corollaries:
- Tenant data is resolved through the same surface the human uses -
e.g. the eTeller account list is fetched via the delegated BFF session
(
/proxy/bank8-ledger/v1/accounts), never with a platform-operator token (env-locking makes that path empty by design). - The suite is a product-gap detector, not a workaround engine - a state that cannot be reached through the UI/API is a backend gap to fix, never something to paper over with direct data access.
StrictPage records console errors and HTTP ≥ 400 responses during
browser interactions; any console error or HTTP failure fails the test.
- Dash/platform flows - live in the platform e2e repo (
sentin8-e2e). - Flutter unit/widget tests - in the app repos themselves.