domain project / logistics

Logistics & Delivery

Test strategies for shipment tracking, route optimisation, driver apps, warehouse management, and delivery notification systems.

Shipment trackingRoute optimisationDriver & fleet managementWarehouse operationsDelivery notificationsReturns processing

Domain Overview

Logistics systems are event-driven and geolocation-heavy. Testing covers real-time tracking updates via SSE or WebSocket, geofence triggers that fire on driver position changes, ETAs that recalculate dynamically, and the multi-party nature of a delivery (sender, carrier, recipient). The tracking widget is typically the highest-traffic public-facing feature — often serving millions of anonymous users with no authentication.

Typical stack: Node.js or Go event bus, Redis Streams or Kafka for tracking events, PostgreSQL for shipment records, AWS SNS/SES for notifications, Google Maps Platform or HERE for routing, S3 for proof-of-delivery assets.

The low E2E share (10%) reflects that tracking logic (ETA, geofence, routing) is algorithmic and best tested at unit/integration level — E2E is for the customer-facing tracking page and returns initiation flow.

Key Test Areas

AreaWhat to testRisk
TrackingParcel lookup, status timeline, live map position, ETA accuracyCustomer distrust, support call volume
Driver appAccept delivery, mark delivered, capture signature/photo, exception reportFailed delivery records, dispute resolution
RouteStops list, resequencing, geofence entry/exit eventsMissed stops, wrong order sequence
WarehouseScan in/out, pick list generation, inventory adjustmentMis-shipped parcels, inventory discrepancy
NotificationsSMS, email, push on each status change, opt-out respectedMissed notifications, spam complaints
ReturnsQR code generation, drop-off location lookup, refund trigger on receiptDelayed refunds, lost returns

Test Pyramid for Logistics & Delivery

Unit (55%):        ETA calculation, route scoring, geofence polygon logic, parcel ID normalisation
Integration (35%): Tracking event API pipeline, notification triggers, geofence webhooks, signature upload
E2E (10%):         Customer tracking page, returns initiation, parcel lookup edge cases

Critical E2E tests (must pass before every deploy):

  1. Parcel lookup returns correct status timeline for a known tracking number
  2. Status change event triggers SMS and email notification within SLA
  3. Returns flow generates QR code and registers drop-off intent
  4. Tracking page renders correctly with no events (pre-collection state)
  5. ETA displays correctly when delivery is past the original estimate

Common Failure Scenarios

1. Tracking page shows "Out for delivery" but event webhook not delivered → stale status
   Test: post status-change event via API, reload tracking page, assert new status visible within timeout

2. ETA countdown shows negative time (delivery past ETA not handled in UI)
   Test: mock API to return pastDueEta, assert UI shows "Delayed" or a positive elapsed message, not "-00:12"

3. Signature capture image not saved (S3 upload timeout silently swallowed)
   Test: intercept S3 upload with page.route(), return a timeout, complete delivery flow, assert error shown to driver

4. Returns QR code expires before customer reaches drop-off (short TTL)
   Test: generate QR code, advance clock past TTL with page.clock.set(), attempt to scan, assert expiry error

5. Parcel ID lookup is case-sensitive (AB123 fails when user enters ab123)
   Test: look up tracking number in lowercase, mixed case, and uppercase, assert all return same result

Playwright Patterns for Logistics

// Mock geolocation API for driver position updates
test('tracking map updates when driver position changes', async ({ page, context }) => {
  await context.grantPermissions(['geolocation']);
  await context.setGeolocation({ latitude: 51.5074, longitude: -0.1278 });

  await page.goto('/track/AB123456');
  const initialPin = await page.locator('[data-testid="driver-pin"]').boundingBox();

  // Simulate driver moving
  await context.setGeolocation({ latitude: 51.5150, longitude: -0.1200 });
  await page.waitForTimeout(2000); // allow SSE update

  const updatedPin = await page.locator('[data-testid="driver-pin"]').boundingBox();
  expect(updatedPin?.x).not.toEqual(initialPin?.x);
});

// Test tracking page with intercepted SSE stream
test('tracking page reflects live status update via SSE', async ({ page }) => {
  await page.route('**/api/track/*/stream', route => {
    route.fulfill({
      headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache' },
      body: 'data: {"status":"OUT_FOR_DELIVERY","eta":"14:30"}\n\n'
    });
  });

  await page.goto('/track/AB123456');
  await expect(page.getByText('Out for delivery')).toBeVisible();
  await expect(page.getByText('14:30')).toBeVisible();
});

// Test parcel lookup case-insensitivity
test('parcel lookup is case-insensitive', async ({ page }) => {
  for (const input of ['AB123456', 'ab123456', 'Ab123456']) {
    await page.goto('/track');
    await page.getByLabel('Tracking number').fill(input);
    await page.getByRole('button', { name: 'Track' }).click();
    await expect(page.getByTestId('status-timeline')).toBeVisible();
  }
});