RideKit
Docs/Reference/Testing — every layer, how to run it, and what each one proves

Testing — every layer, how to run it, and what each one proves

The platform ships with four test layers. Each one catches a different class of bug; none of them replaces the others.

Layer Where Runner What it proves
Backend unit/service tests backend/ Jest Business logic against a real Postgres + Redis (fares, ledger, dispatch, RBAC, …)
Admin component tests admin/ Vitest React components and helpers in isolation (jsdom)
Mobile unit tests mobile/rider, mobile/driver, mobile/partner Jest Stores, i18n catalogs, screen logic with mocked native modules
End-to-end (this doc) admin/e2e, mobile/*/e2e Playwright / Maestro A real browser (or app) driving the real stack: UI → API → database and back

Run the unit layers per surface:

cd backend && npm test          # needs Postgres + Redis (see backend README)
cd admin   && npm test
cd mobile/rider  && npm test
cd mobile/driver && npm test
cd mobile/partner && npm test

The rest of this document is about the end-to-end layer.


1. Playwright — the admin panel, end to end

admin/e2e/*.spec.ts drives a real Chromium through the real stack. Nothing is mocked: the login uses the real OTP endpoints, every table is asserted against what the API returns at that moment, and the one write flow round-trips through Postgres.

Chromium ──► Next.js admin (:3098) ──► NestJS backend (:3097) ──► Postgres + Redis

The ports are deliberately off the dev defaults (3000/3200) so the suite can run beside your normal dev servers without stealing them.

Quick start

# one-time: services + schema
#   Postgres and Redis running locally (docker-compose up -d postgres redis works)
cd backend && npm ci && npm run migration:run

# the suite
cd ../admin && npm ci
npx playwright install chromium
npm run e2e

That is the whole ritual. playwright.config.ts starts the backend and the admin itself if they are not already listening (and reuses them if they are); e2e/global-setup.ts then:

  1. waits for /api/health (real DB + Redis check),
  2. seeds deterministic data through the backend's own seed CLI (backend/src/database/seeds/e2e-seed.ts — see below),
  3. signs in once through the real OTP endpoints and saves the session the same way the login page would (localStorage tokens).

Useful variations:

npx playwright test e2e/auth.spec.ts     # one spec
npx playwright test --headed             # watch it
E2E_SKIP_SEED=1 npm run e2e              # skip the seed step (data already in place)
E2E_BACKEND_PORT=4001 E2E_ADMIN_PORT=4002 npm run e2e   # other ports
E2E_ADMIN_PHONE=+15559550001 npm run e2e # non-India install (see below)

What the suite covers (18 specs)

  • auth — interactive login by phone + dev OTP lands on the dashboard; a rider account gets an RBAC-stripped shell (and 403s from operator APIs); sign-out clears the session.
  • dashboard — the four live KPI cards equal /api/admin/stats; Revenue Today renders in the installation currency; the live-trips panel lists exactly the rides /api/admin/trips/live returns.
  • trips — list count + first row match /api/admin/trips; row click opens the detail (Trip #XXXXXXXX, full ride id); the status filter narrows the table to matching rides only.
  • dispatch — the operator console opens with the live queue (row per queue ride, awaiting-dispatch counter) and a real Leaflet map.
  • drivers — fleet table matches /api/admin/drivers; search narrows to a driver; the profile page shows that driver's real name and phone.
  • pricing — every vehicle type appears under Global fares; the Per-market fares tab renders the install market's overrides in its own currency.
  • markets — the catalog count matches /api/markets; the install's market row shows its currency and Active state.
  • parcels / organizations / safety — each queue matches its API and shows the seeded fixture (a delivered parcel, the corporate org, an open critical SOS + a dispute reachable through the kind filter).
  • settings — the Localization card shows the DB-backed values (installation_settings, not .env); the language switcher offers exactly the market's languages.
  • i18n — switching to Hindi re-renders the live UI and survives a reload; an Arabic locale flips the whole document to RTL. (The Hindi test skips itself on installs whose market does not offer Hindi.)
  • promo write flow — creates a promo through the modal, asserts it in the table AND via the API, deletes it through the confirm dialog, asserts it is gone from both. Reversible by design; an API cleanup runs even on failure.
  • website — a headline saved in Admin → Website reaches the public /site document and reset restores the default; the validator refuses markup and says which field; the accent follows the brand colour by default.
  • partners — the operator's list flags the seeded application; verifying it is a round trip through the API and reversible; opening a fleet shows its drivers, cars, trips and earnings; creating a fleet for a new owner lets that owner sign in to it.
  • partner panel — the fleet owner lands on their own fleet, never the operator console; drivers, vehicles, trips and the wallet match the API; adding a car and putting a driver on it round-trips and is undone; a driver is found by phone, shown masked, and cannot be added twice.
  • screens — every operator screen (34 routes) opens as the admin without a console error, an unhandled rejection, a failed API call or an empty page.

Deterministic data — the e2e seed CLI

The suite never hand-inserts SQL. e2e/global-setup.ts runs:

cd backend
npx ts-node -r tsconfig-paths/register src/database/seeds/e2e-seed.ts

which is an extension of the standard npm run seed -- --demo: the same bootstrap admin, the same demo drivers/riders/rides/ledger, plus the handful of rows the E2E flows assert on (two safety incidents, one corporate org, one delivered parcel). Everything is idempotent (UUIDv5 ids + ON CONFLICT DO NOTHING) and market-driven — an India install seeds ₹ and +91 phones, a Brazil install seeds R$ and +55. It also resets the OTP request throttle for the seeded demo phones (and only those) so back-to-back runs never trip the real rate limit, and purges E2ESMOKE* promo rows earlier write-flow runs soft-deleted.

Remove the E2E extras with:

npx ts-node -r tsconfig-paths/register src/database/seeds/e2e-seed.ts -- --clean
npm run seed -- --clean-demo        # and the demo layer, if you want it gone too

Non-India installs

Assertions derive currency, market name, and languages from the API at runtime, so the suite runs on any market. Two things are phone-shaped and follow the seed: the admin login number (default +919955000001) and the demo rider (+919855000001). On another market the seed derives them from that market's dial code — pass E2E_ADMIN_PHONE / E2E_RIDER_PHONE accordingly (the seed prints the admin phone when it runs).

What this layer does and does not prove

Proves: routing, auth, RBAC, i18n/RTL, the API contracts every page depends on, currency rendering, and one full write round-trip — against a real browser, backend, database and cache.

Does not prove: real SMS delivery (dev master OTP 000000 is active only while no SMS provider is configured), real payment-gateway flows (provider sandboxes need buyer credentials), websocket push timing under load, or visual pixel-perfection. Those need provider sandboxes and visual-regression tooling respectively.


2. Maestro — the mobile apps

mobile/rider/e2e/*.yaml, mobile/driver/e2e/*.yaml and mobile/partner/e2e/*.yaml are Maestro flows.

One real ride, two emulators, three apps — mobile/scripts/e2e-side-by-side.sh runs them in the only order that works (driver online → driver flow starts watching for the offer, in the background → rider books → driver accepts, arrives, starts, completes and rates → rider rates), then tours every screen of the rider and driver apps, then the fleet app (owner tour, register and assign a car, a fresh application). Between steps it asks Postgres what actually happened — the ride's status and driver, the ledger legs, both ratings — so a screen that looked right but changed nothing fails loudly. Screenshots and a RESULTS.md land in docs/media/e2e-<date>/.

The per-app flows:

  • rider — first-launch → language sheet (market languages) → OTP login → home (market-correct, e.g. the UPI chip on an India install) → destination search → a real fare quote (stops before booking) → wallet (₹ balance) → safety centre → ride history.
  • driver — OTP login → offline home → go online (a real state change the admin dashboard sees) → back offline → earnings with the seeded settlement history; 10-/11-/12- take a real offer through the whole trip and tour every menu screen.
  • partner — the seeded fleet owner signs in and every screen renders with the fleet's real numbers; a car is registered, assigned, freed and retired; a brand-new number applies to run a fleet and lands on a home screen that says the fleet is awaiting verification.

They need a simulator or device with a dev-client build of the app — that is a property of mobile E2E, not of this repo. For the side-by-side run, boot two Android emulators headless — and let them use the machine's GPU:

emulator -avd ridekit_test   -port 5554 -no-window -no-audio -no-boot-anim -gpu host -no-snapshot-save
emulator -avd ridekit_driver -port 5556 -no-window -no-audio -no-boot-anim -gpu host -no-snapshot-save
adb -s emulator-5554 emu geo fix 72.8777 19.0760   # the market centre (Mumbai in the shipped seed)
adb -s emulator-5556 emu geo fix 72.8777 19.0760

-gpu swiftshader_indirect (software rendering) is only for machines without a GPU: a live map costs four to five host cores per emulator that way, and the starved emulator drops off adb at app launch.

To run the flows by hand:

# 1. backend running + seeded (same as the Playwright section)
# 2. build & boot the app on a simulator/emulator, pointed at the backend:
cd mobile/rider
EXPO_PUBLIC_API_URL=http://<your-lan-ip>:3097 npx expo run:ios   # or run:android

# 3. the flows, in order (login first — later flows reuse the session):
curl -fsSL "https://get.maestro.mobile.dev" | bash    # once
maestro test e2e/            # runs the folder in filename order

Same for mobile/driver. On Android emulators use http://10.0.2.2:3097.

Notes:

  • The flows use the dev master OTP 000000 and the seeded demo accounts (rider +919855000001, driver +919755000001 on an India install).
  • The rider login flow makes one real OTP request; the seed CLI resets the per-phone throttle between runs, and e2e-side-by-side.sh does the same for its four phones before it starts (the limit is five an hour per phone).
  • The seed pins the demo driver's active vehicle to the market's first priced vehicle type — the option the rider's confirm screen lists first — because dispatch only offers a ride to drivers whose active vehicle is of the ride's type. A dev database full of hand-made test cars will otherwise leave the booking in requested with nobody to offer it to.
  • The seed also makes sure the market centre is inside a service zone when the install has drawn any — a zone drawn for a demo around some other city turns "serve everywhere" off, and every booking at the centre is then refused as outside the service area.
  • The rider's 0x- flows stop at the fare quote on purpose: a booking creates a live ride that needs a driver to resolve. 10-ride-request.yaml does book — run it through e2e-side-by-side.sh, which has the driver online on the other emulator first, or expect a ride left in requested.
  • Selectors: the 10-/11-/12- flows and the partner flows select by testID (Android resource-id), so they run in any app language; the older 0x- flows select by visible text and need the app in English.
  • Cold starts are slow on a headless emulator under load (close to a minute with two emulators and a browser suite running); the first-screen waits are 120 s for that reason. Run the mobile suite after the Playwright suite, not beside it.

Validation vs execution: every flow file is validated with the real Maestro CLI (maestro check-syntax) — locally during development and in the CI job below. Executing them requires the simulator setup above and is a local/device-farm activity; CI does not execute them.


3. What CI runs

.github/workflows/e2e.yml:

  • playwright — on every pull request touching admin/** or backend/**: boots Postgres (PostGIS) + Redis service containers, migrates, lets the Playwright config start the backend and admin, seeds through the e2e seed CLI, and runs the full suite headless. Traces and screenshots are uploaded on failure. CI migrates with DEFAULT_COUNTRY=IN so the fresh install matches the documented local setup.
  • maestro-flows — workflow_dispatch only: validates every mobile flow with maestro check-syntax. Executing the flows in CI needs a macOS runner with booted simulators (or a device farm such as Maestro Cloud) plus per-PR dev-client builds of both apps — infrastructure the template does not presume. The job is the hook to wire yours into.

The unit layers run in their own existing workflows (backend-ci.yml, admin-ci.yml, rider-ci.yml, driver-ci.yml, partner-ci.yml).


4. Past runs

Each full run leaves a dated report beside this file, with what was exercised, what broke and what was fixed:

  • END-TO-END-2026-09-07.md — the local stack: three apps on two emulators, the operator admin and the fleet owner's console, the sales site and the site template.
  • END-TO-END-2026-09-05.md — the live demo stack: rider and driver through real money paths (top-up, withdrawal), parcels, rentals, intercity, multi-vehicle.
source: docs/testing/README.md (ships identically in the product zip)