@edr/freight-e2e — Cypress e2e suite for the freight system
Containerized, fully isolated e2e environment: throwaway Postgres (tmpfs), MinIO, freight-api, portal, and backoffice — plus a Cypress runner that works both headless-in-Docker and interactively from the host against the same URLs.
Stack (docker-compose.e2e.yaml, project name edr-freight-e2e)
| Service | Host port | Notes |
|---|---|---|
freight-api-e2e |
3101 | migrations + seeders run at boot |
freight-portal-e2e |
5373 | nginx static build, API baked to :3101 |
freight-backoffice-e2e |
5383 | nginx static build, API baked to :3101 |
postgres-freight-e2e |
5533 | edr_freight_e2e, tmpfs — gone on down |
minio-e2e |
9310/9311 | object storage for file features |
cypress |
(host net) | profile cypress, headless chrome |
Ports deliberately avoid the dev stack (3001/5173/5183/5433). The dev database is never touched.
Usage (from repo root)
pnpm e2e:freight:up # build + start stack, wait for healthchecks
pnpm e2e:freight:open # interactive Cypress on the host
pnpm e2e:freight:run # headless run from the host
pnpm e2e:freight:ci # headless run inside the cypress container
pnpm e2e:freight:down # teardown, drop all data
First up is slow (image builds + 240 migrations + seeders — healthcheck
allows 3 min). Requires the same root .npmrc (GitHub Packages auth for
@tria-plc) as the main compose file.
The cypress service uses network_mode: host (Linux). On macOS/Windows run
Cypress from the host (e2e:freight:open / e2e:freight:run) instead of the
container.
Test users
Inserted by Cypress itself — a global before() hook runs
cy.task("db:seedUsers"), which executes
cypress/fixtures/seed-users.sql (idempotent, pre-hashed argon2 passwords)
against the e2e database. No API code is involved; the app's user seeders
stay disabled. The API's always-on boot seeders must have run first
(org/unit/positions) — guaranteed once freight-api-e2e is healthy.
- Staff (backoffice):
linestaff|chief|director|ceo|marketer|operation|gl-et|gl-dj@edr.local— passwordpassword@tria - Customers (portal):
user@gmail.com,user2@gmail.com— password12345678
Full map in cypress/fixtures/users.json.
Conventions
- Programmatic login everywhere except the two dedicated UI-login specs:
cy.loginBackoffice(email?)/cy.loginPortal(email?)—cy.session-cached (across specs),POST /api/auth/login, sets theauth-token/refresh-tokencookies the apps read. - Origins:
baseUrlis the backoffice (5383). Portal specscy.visitthe absolute portal URL; a test that touches both apps wraps portal steps incy.origin()(different port = different origin). Cookies ignore ports — always call the matching login command right before switching apps socy.sessionrestores the right cookie snapshot. - DB access:
cy.task("db:query", { sql, params })runs SQL against the e2e database (E2E_DB_URL, defaultlocalhost:5533). Use for seeding edge-case data and asserting side effects — it can never reach the dev DB. - Spec layout:
cypress/e2e/api/— API contract viacy.request(no browser)cypress/e2e/backoffice/— staff appcypress/e2e/portal/— customer appcypress/e2e/flows/— cross-app journeys (both directions)
Extending
Deep module flows (booking wizard → staff approval → scheduling → billing)
belong in flows/. Pattern: arrange via API/db:query, act through the UI of
one app, assert through the UI of the other + a db:query cross-check. Prefer
adding data-testid attributes to app code over brittle text selectors.