Files
edr-platform/e2e/freight

@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 — password password@tria
  • Customers (portal): user@gmail.com, user2@gmail.com — password 12345678

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 the auth-token / refresh-token cookies the apps read.
  • Origins: baseUrl is the backoffice (5383). Portal specs cy.visit the absolute portal URL; a test that touches both apps wraps portal steps in cy.origin() (different port = different origin). Cookies ignore ports — always call the matching login command right before switching apps so cy.session restores the right cookie snapshot.
  • DB access: cy.task("db:query", { sql, params }) runs SQL against the e2e database (E2E_DB_URL, default localhost: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 via cy.request (no browser)
    • cypress/e2e/backoffice/ — staff app
    • cypress/e2e/portal/ — customer app
    • cypress/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.