End-to-end run — 2026-09-07
Everything that ships was exercised against one local stack in a single overnight run: the three apps on two Android emulators side by side, the operator admin and the fleet owner's web console through Playwright, the sales site and the operator's marketing-site template, plus every unit and integration suite. Bugs found on the way were fixed in the same sitting and are listed below with the commit that carries them.
The stack under test
| Piece | How it ran |
|---|---|
| Backend | NestJS build (node dist/main.js) on :3000, Postgres 17 + PostGIS, Redis, dev OTP 000000, e2e seed (src/database/seeds/e2e-seed.ts) |
| Admin | next dev on :3200 against the backend above |
| Rider + partner apps | release APKs on a headless Pixel-class emulator (emulator-5554), GPS fixed to Mumbai, API at 10.0.2.2:3000 |
| Driver app | same, on a second emulator (emulator-5556) |
| Sales site / site-template | next build + the test suites, then a crawl of every route |
Results
Mobile — two emulators, one real ride (mobile/scripts/e2e-side-by-side.sh)
| Step | Result | Proof in the database |
|---|---|---|
| Driver signs in and goes online | ✅ | driver_profiles.availability = online |
| Rider signs in, searches "Bandra Kurla Complex", books a Bike | ✅ | a new rides row, requested |
| Driver gets the offer, accepts, arrives, starts, completes | ✅ | ride completed with this driver; driver credit + commission postings for the ride |
| Driver rates the rider, rider rates the driver | ✅ | two ride_ratings rows |
| Rider screen tour (16 screens, ends in Hindi) | ✅ | screenshots rider-20 … rider-35 |
| Driver screen tour (14 screens, ends in Hindi) | ✅ (stand-alone re-run after the menu fix) | screenshots driver-20 … driver-33 |
| Partner tour (15 screens) | ✅ | screenshots partner-01 … partner-15 |
| Partner registers, assigns, frees and retires a car | ✅ | the plate is gone from live vehicles |
| A new number applies to run a fleet | ✅ | an unverified organizations row with applied_at |
Twelve runs were needed to get here; every one of the earlier failures is in
the table below, and the fix for each is in the tree. The last two runs were
slowed by something outside the code: the Mac's idle-sleep timer (set to one
minute) put the whole stack to sleep several times, and caffeinate did not
hold it awake with the lid closed. A step that shows an hour in the log was
asleep, not slow.
Admin — Playwright (admin/e2e)
18 spec files, 67 tests, run as the operator (and, for the partner panel, as the fleet owner) against the seeded stack:
| Run | Result |
|---|---|
| Full suite, two workers in parallel | 62 passed, 5 failed |
| The five failures re-run one at a time | 4 of them pass untouched — they had tripped over each other in parallel (a language switch persisted for the same admin user while another spec read English; the fleet application toggled verified under another spec's feet) |
| The remaining two | one real bug (#21, the Add-vehicle modal) and one stale assertion (#22, the Markets scope) — both fixed, both green on re-run |
Everything in admin/e2e is green serially. Making the suite safe to run in
parallel (a per-worker admin user, a per-spec fleet) is a follow-up; CI runs
it with the config's default workers, so it belongs on the list below.
Unit and integration suites
| Suite | Result |
|---|---|
| Backend Jest (against the real Postgres + Redis) | 103 suites, 1424 passed, 0 failed |
| Admin Vitest | 21 files, 396 passed |
| Partner app Jest (stores, formatting, catalog parity) | 56 passed |
| Sales site Vitest | 32 passed |
| site-template content tests | 10 passed |
Backend tsc, admin tsc |
clean |
Sales site and site-template
site: 32 vitest tests green;next buildclean; every one of the 24 routes answers 200 in a crawl; the API routes refuse what they should.site-template: 10 content tests green after the defaults fix;next buildclean.
Bugs found and fixed on the way
| # | Where | What was wrong | Fixed in |
|---|---|---|---|
| 1 | Admin → Partners | The list never returned isVerified, the settlement terms or the driver/vehicle counts, so every fleet rendered as unverified with "0 drivers · 0 vehicles". |
97faa01 |
| 2 | Admin shell | A fleet owner's token could open the operator console by URL — an RBAC-empty shell where every call 403s. The dashboard layout now routes anyone whose landing page is not /dashboard there. |
97faa01 |
| 3 | Admin lists | On drivers, riders, trips, parcels and vehicles the filter → URL sync (router.replace) could cancel a row's Open navigation that had just started; found by the Playwright specs clicking Open. Now history.replaceState, which the router never sees. |
97faa01 |
| 4 | Partner app | Every fleet owner was told they were not one whenever the fleet check failed for any reason; a 403 now leads to the application form and keeps the session, any other failure says what it is. | 403a742 |
| 5 | Backend RBAC | Nothing had ever granted the fleet_owner role, so the partner surfaces answered 403 to the very people they were built for. Granted on application, operator creation and ownership change; existing owners backfilled by migration. |
95812ca |
| 6 | Admin e2e | Four specs were written against the pre-redesign login and shell (OTP field, sign-out, language menu, confirm dialog) and had been failing quietly. | d841713 |
| 7 | site-template | Tests still carried a copy of the section defaults that had moved into the backend, and the colour fallback was a different teal from everything else. | b1b54ce |
| 8 | Driver flow | Google Play services' one-time "Location Accuracy" dialog sat over the map on a fresh image and swallowed the first tap on Go online; the Maestro flows now dismiss it (optionally, so a real device without it is unaffected). | this run |
| 9 | Mobile flows | Under load (two emulators, Playwright and next dev at once) a cold start takes close to a minute; the first-screen waits were 45 s and the rider flow timed out with the welcome screen half a second away. Raised to 120 s and the mobile run now goes after the browser run, not beside it. |
this run |
| 10 | e2e seed | Re-running the partner specs left "E2E Fleet …" organisations behind, and the OTP throttle from the previous run blocked the next sign-in. The seed now purges the smoke fleets and resets the throttle for every seeded phone. | 97faa01 |
| 11 | e2e seed / dispatch | The demo driver's active vehicle was an "Auto Rickshaw" — a type with no fare in the market — so the rider's booking (a Bike, the first option on the confirm screen) was never offered to them and sat in requested. Dispatch was right; the data was not. The seed now pins the demo driver's active vehicle to the market's first priced type and hands any other driver left on that car one of their own back. |
this run |
| 12 | Rider flow | Google's "Location Accuracy" consent lands on top of the rider's home the moment it asks for a position, so the flow waited for Where to behind a dialog. It now accepts the dialog first (optionally), as the driver flow does. | this run |
| 13 | Mobile script | Four sign-ins per run against a per-phone OTP limit of five an hour meant the second run after a fix was refused with 429. The script now resets the throttle for its four test phones before it starts, the way the seed does. | this run |
| 14 | e2e seed / service area | Two service zones drawn by hand for an analytics demo (Bengaluru, Indore) were still active in the dev database. Once any service zone exists the platform stops serving everywhere, so every booking at the market centre (Mumbai) was refused with "Point is outside the service area" — correct enforcement, stale data. The seed now adds an "E2E service area" around the market centre when the zones drawn leave it out, and removes it on clean; the hand-drawn zones are untouched. | this run |
| 15 | Mobile script | Dispatch offers a ride to one driver at a time with a 15 s window and marks it no_drivers when the list is exhausted — there is no second round. The script ran the driver's accept flow after the rider's booking flow had finished, so the offer had come and gone a minute before the driver app was even watching. The driver flow now starts first, in the background, and waits up to five minutes for the offer the rider's booking produces. A headless emulator dropping off adb mid-flow (seen twice) is retried once. |
this run |
| 16 | Emulators | Booted headless with -gpu swiftshader_indirect, the driver emulator burned four to five host cores rendering the live map in software, and the other emulator kept dropping off adb ("device offline") at every app launch. Rebooting both with -gpu host (Metal on Apple silicon) halved that; the flows' launch retries stopped firing. |
this run |
| 17 | Rider flow | The rating flow restarted the app to reach the rating screen; a restart lands on home once the ride is complete (see the observation below), so it now brings the app forward without restarting it and the tracking screen hands over to rating as designed. | this run |
| 18 | Screen tours | Account-menu rows below the fold (Help, Settings, Legal …) were tapped without scrolling; both tours now scroll each row into view first. | this run |
| 19 | Partner tour | Maestro's text: selector is a regex, and the seeded address "Fleet pickup 1 (E2E)" has parentheses in it — the group matched nothing. Escaped. The application flow's fleet is now removed before each run, like the Playwright smoke fleets. |
this run |
| 20 | Fleet console (backend) | A fleet could never register a plate that had belonged to a retired car: the clash check read every vehicle ever, while the database's unique index is partial on deleted_at IS NULL (cars change hands; retire-and-re-add is normal). Found by the partner vehicle flow on its second run. Both the create and the update check now look at live vehicles only. |
this run |
| 21 | Admin fleet console | The Add-vehicle modal read the vehicle-type catalogue once, when it opened; a click that beat the catalogue request left the select showing the first type while the form held none, and Add stayed disabled for good. Caught by the Playwright partner-panel spec on a fast run. The form now takes the first type when the catalogue arrives. | this run |
| 22 | Admin e2e | The Markets page opens on the installation's own country group; the spec counted the whole catalogue against that chip. It now switches to "All countries" first. | this run |
Not a bug in the product, but worth knowing: port 3000 was held by a runaway
next-server from an unrelated project (~/website/apps/web, 185 % CPU); it
was killed so the backend could bind. The supervisor scripts used for the
run restart the backend and admin if they die.
What the mobile run covers
mobile/scripts/e2e-side-by-side.sh drives both emulators in the order a
real ride happens, and asks the database after each step whether it really
happened:
- Driver goes online (
driver/e2e/10-go-online.yaml) →driver_profiles.availability = online. - Rider books (
rider/e2e/10-ride-request.yaml) — sign in, home, search "Bandra Kurla Complex", confirm, dispatch searching → aridesrow inrequested|accepted|arrived. - Driver accepts, arrives, completes, rates (
driver/e2e/11-accept-and-complete.yaml) → ridecompletedwith this driver, driver credit + commission postings in the ledger, oneride_ratingsrow. - Rider rates (
rider/e2e/11-ride-rate.yaml) → two ratings on the ride. - Rider screen tour (
rider/e2e/12-screens-tour.yaml) — parcel, rental, intercity, menu, history, wallet, places, referral, scheduled, safety, help, settings, parcels, profile, language → Hindi. - Driver screen tour (
driver/e2e/12-screens-tour.yaml) — menu, earnings, history, wallet, quests, heatmap, destination, profile, vehicles, documents, settings, help, legal, Hindi. - Partner tour (
partner/e2e/01-login-tour.yaml) — welcome, home, drivers, driver detail + terms, vehicles, vehicle detail, trips, trip detail, earnings, wallet + withdraw sheet, account, Hindi, legal. - Partner manages a car (
partner/e2e/02-manage-vehicle.yaml) — add "MH 02 E2E 7777", assign Vinod Yadav, free, retire → the plate is gone fromvehicles. - Partner applies (
partner/e2e/03-apply.yaml) — a fresh account applies → an unverified organisation withapplied_at.
Screenshots of every screen are in docs/media/e2e-2026-09-07/
and the step log is RESULTS.md in the same folder.
What the admin run covers
admin/e2e — 18 specs. Beyond the feature specs (auth, dashboard, dispatch,
drivers, trips, parcels, markets, organizations, pricing, promos, safety,
settings, i18n, website), two are new for the partner work and one is a net:
partners.spec.ts— the operator's list flags the application, verify is a round trip through the API and reversible, the operator sees a fleet's drivers / cars / trips / earnings, and creating a fleet for a new owner lets that owner sign in to it.partner-panel.spec.ts— the owner lands on their own fleet (never the operator console), the tables match the API, adding a car and assigning a driver round-trips and is undone, and a driver is found by phone, shown masked, and cannot be added twice.screens.spec.ts— opens all 34 operator screens and fails on any console error, unhandled rejection, failed API call or empty page.
Observations worth a product decision
- Rating after a restart. If the rider closes the app between the driver completing the trip and the rating screen, the next open lands on home and nothing asks for the rating; the trip can only be rated from its history entry. Most ride apps put a "rate your last trip" card on the next open. Not a defect — the ride settles either way — but a gap in the loop. (The rating flow keeps the app alive between steps for exactly this reason.)
Follow-ups (engineering, not urgent)
- Parallel-safe admin e2e — the i18n and partners specs share one admin user and one seeded fleet; give each worker its own, or pin the suite to one worker in CI.
- "Rate your last trip" on next open — see the observation above.
- Emulator sleep — a long unattended run needs the Mac's idle sleep off
(
pmset) or the lid open;caffeinatealone was not enough.
Open items for the morning (owner decisions, not defects)
- Sales site copy —
TODO(owner)markers are still visible on/demo,/faqand/legal/*: the legal entity name, what support covers, and the Expo / store links. The crawl passes; the words are yours to choose. - Pricing — the plans on the sales site are placeholders until the numbers are decided.
- Selling on other platforms — to discuss; nothing in the code assumes one channel.