An official developer portal of the Government of the Republic of Zambia · Test environmentVerify a certificate
Pamodzi Developer HubBuild, integrate and learn the e-Services framework
On this page

End-to-end testing

Every new or re-engineered service ships with an end-to-end test. The test proves the full process works: find the service, sign in, apply with the form and documents, pay, every officer step, the decision, and the issued output document with its seal and QR verification. The same run captures the screenshots for the service's documentation (see Service documentation).

The tests live in the e2e kit, a Playwright project. The kit is kept on the central documentation share, not in a git repository. The course E2E testing and documentation teaches it step by step.

What the kit boots

npm run e2e starts everything on your own machine, runs the journeys and stops it again:

  • a fresh PostgreSQL database (zcm_e2e, dropped and re-created on every run) with PostGIS, built by the backend's own

    migrations and filled by the kit's seed;

  • production builds of the citizen portal and the back office;
  • the API, with jobs off, a random session secret and a network guard that refuses every outgoing call to a non-local

    host;

  • the provider test doubles.

Nothing leaves the machine. E-mail is unconfigured and AI is off.

Provider test doubles

The doubles are the backend's own (zcm-backend/test/fixtures/providers/), so the e2e kit, the backend's contract suites and its integration tests share one set. Each is faithful to its provider's contract and has a /__control API the journeys use.

DoubleStands in for
ZamPay (behind ZamConnect)Invoices, payment, receipts, and receipt callbacks to the instance's own callback address
ZamPassCitizen sign-in (OpenID Connect): discovery, keys, persona picker, PKCE token, logout
SMS captureThe SMS provider; every text is kept for the test to read
Government gatewayThe legacy verification source
Register sourceAn external register's sync API
Development PKICertificate authority, time-stamp authority, OCSP
Signing serviceSeals issued permits (PAdES with a time-stamp)

Never point a journey at real ZamPay, ZamPass, SMS or signing services.

Issued documents are rendered with LibreOffice when it is installed. Without it, a text-only stand-in is used, and documentation made from the stand-in is not published.

Seed profiles

E2E_SEED_PROFILE chooses which installation the run stands for:

ProfileModulesStands for
mihud (default)estate, rentals, valuation and shop switched on for one ministryan installation that already runs ministry modules
neutralevery module offa new installation, or any other ministry's node

A service's journey must pass on both. Module journeys switch on the module they need themselves, for the owning ministry only, under maker-checker, so they run on either profile.

Topologies

E2E_TOPOLOGY chooses the shape of the platform:

  • combined (default): one installation does everything.
  • federated: a national hub and a ministry node, each with its own API, database and ZamPay double. The kit

    generates fresh federation signing keys, registers the node on the hub under maker-checker, and waits until the hub lists the node's services.

Run a journey in the federated topology when its service can live on a node. There the citizen applies, pays and verifies on the hub; the officers work in the node's back office.

E2E_TOPOLOGY=federated npm run e2e -- tests/23-water-permit.spec.js

Commands

Run them in the e2e folder of your workspace.

CommandWhat it does
npm run setupOnce: creates the app worktrees the kit runs against (-- --refresh re-creates them from the base branch)
npm run e2eStart the stack, run the journeys, stop the stack. Extra arguments go to Playwright (-- tests/<spec>.js --headed)
npm run stackStart only; then run npx playwright test in another terminal
npm run docs:servicesRun the documented services' journeys with capture on and publish their documentation folders
npm run docs:checkNo run: are the published documents complete and did their journeys pass?
npm run status:servicesRegenerate SERVICE-STATUS.md
npm run share:syncUpload what is new or newer to the central share; -- --dry-run lists only, -- --pull takes a fresh copy

For both profiles:

npm run e2e -- tests/<your-service>.spec.js
E2E_SEED_PROFILE=neutral npm run e2e -- tests/<your-service>.spec.js

Reports: .stack/last-summary.txt (one line per test, each failure with its screenshot and trace), npx playwright show-report, and the API log in .stack/logs/.

Writing a journey spec

A journey is a Playwright spec in tests/, numbered after the last one. Its header comment lists each actor and step. A typical shape:

const { test, expect } = require('@playwright/test');
const { cfg, signInCitizen } = require('./support/helpers');
const { serviceDocs } = require('./support/serviceDocs');

const docs = serviceDocs(test, { service: () => myService(), mda: 'EXAMPLE', key: 'example-permit' });

test.describe.serial('Example permit: apply, review, pay, decide, verify', () => {
  test('citizen: finds the service and applies', async ({ page }) => {
    await signInCitizen(page);
    await page.goto(`${cfg.urls.portal}/service/${myService().id}`);
    await expect(page.getByRole('heading', { name: 'Example Permit' })).toBeVisible();
    await docs.step(page, { actor: 'Citizen', title: 'Find the service',
      description: 'The citizen opens the service page and reads what to have ready.' });
    // ... fill the form, upload a synthetic PDF, send
  });
  test('officer: takes it on and reviews it', async ({ page }) => { /* ... */ });
});

Rules that keep journeys useful:

  • Drive the real screens. Find elements by role and visible label, as a user would. Wait for what the user would

    see; no fixed waits.

  • Check the result, not only the screen. Assert the case status, the invoice amount set by the server, the permit

    row, the seal. Include the abuse cases that matter to the service, such as an officer approving their own proposal.

  • Cover the whole process: apply, pay, every officer step with the role it names, the decision, the download, and

    verification of the seal and QR code.

  • Use the helpers in tests/support/: sign-in, the apply flow, back-office steps, arranging a case through the

    API so a later journey still runs when an earlier one fails, and federation helpers that say where a service lives.

  • Synthetic data only. Seeded accounts end in @example.test; names, NRCs (for example 000000/00/0) and

    addresses are invented. Never type real personal data into a journey.

Screenshots at desktop and 375 px

Playwright runs a desktop project at 1366 × 900 and a mobile project at 375 × 812 (specs whose file name contains mobile). The documentation capture (docs.step) takes a desktop image of every step and, for citizen steps, a 375 px image as well. The portal must work at 375 px with no horizontal scroll; the mobile journey checks this and runs an accessibility scan.

Known failures

A failing test stays failing when the application is wrong. Two mechanisms keep the rest of the run useful: tests/support/known-gaps.js parks a test only while its fix sits on an unmerged branch, and tests/support/workarounds.js (E2E_WORKAROUNDS=all) can hide a known bug so later steps can still be exercised.