Skip to content
Viewing: all (23)
  1. David Lowbridge From fixed values to behaviours Brand automationUX & UIDigital strategy
  2. Mike Leonard The web is fun Motion designUX & UITechnical validation
  3. Jara Santamaria Martinez Every year is a new brief PlatformsContent strategyTechnical validation
  4. Kristina Radonjic The work between the work Digital strategyMarketing
  5. Guanglun Wu Choosing the right tech stack Technical validationPlatformsDigital strategy
  6. Barry Cumberlidge Security deep dive Technical validationContent strategyDigital strategy
  7. David Lowbridge Startups are prototypes Digital strategyBrand automation
  8. Tara Heal Shapeshifting the Future UX & UIMarketing
  9. Tom Small The theme is the guardrail Brand automationPlatforms
  10. Barry Cumberlidge Your biggest asset in an RFP, your website SEOContent strategyDigital strategyMarketing
  11. Michael Gunner Cache Components: the right content at the right time PlatformsTechnical validationContent strategy
  12. David Lowbridge From static frames to a creative operating system Brand automationUX & UI
  13. Tom Small We build by leaving things out PlatformsDigital strategyUX & UI
  14. Michael Gunner Standardise the foundations, not the brand experience PlatformsBrand automation
  15. Charlie Clarke Letting type move Motion designUX & UI
  16. Andy Purbrick Putting video back where it belongs PlatformsTechnical validation
  17. Mike Leonard The cache that doubled as a coordinator PlatformsTechnical validation
  18. Andy Purbrick We test the revenue path Technical validationDigital strategy
  19. Mike Leonard Two months in Three.js, we started over Motion designTechnical validation
  20. Rhona Mackay The form we actually trust PlatformsTechnical validation
  21. Andy Purbrick The testbed nobody will see PlatformsTechnical validation
  22. Andy Purbrick Twenty-four modules, one wrapper PlatformsContent strategy
  23. Andy Purbrick We haven’t missed a Thursday in 33 weeks Content strategyMarketing

We test the revenue path

Andy Purbrick 2 min read Technical validation Digital strategy

The contact form is the only page on this site that generates leads. So that’s where the tests live.

We test the revenue path

We don’t chase coverage numbers. What we have is a targeted set of tests aimed at the things that would hurt if they broke: the forms, the pipeline that processes submissions and the CI that catches problems before production.

Dependency injection as a testing technique

Our submission pipeline accepts a dependencies object containing every external service: CRM client, file uploader, error notifier, spam verifier. Both form handlers use it.

That turns the pipeline into a pure function. In production, real services get passed in. In tests, we inject fakes directly — no mocking library, no patching globals. The function doesn’t know whether its dependencies are real.

What the unit tests cover

Five scenarios, each chosen because we saw it go wrong or imagined it going wrong in a way that costs money:

  1. Date formatting for availability labels. Garbled dates look unprofessional in client-facing notifications.
  2. File upload fails after lead creation. Losing a PDF is recoverable, losing the lead is not.
  3. Derived fields reach the CRM correctly. Silent data mismatches are the worst kind of bug.
  4. Fatal failures alert the team and rethrow. A swallowed error means a lost inquiry.
  5. Newsletter signup blocks low-scoring spam while still capturing legitimate subscribers.

What the e2e tests cover

The e2e suite tests what the user actually sees: inline validation messages appearing as they type, the success screen after a valid submission, the error state when something goes wrong. Three form variants (start-a-project, minimal and newsletter) are each tested through the real UI in a headless browser. Playwright intercepts server calls at the network layer, so the tests run without a backend but the user-facing behavior is identical to production.

Why Node’s built-in runner

We use Node’s built-in test runner — not Jest, not Vitest. The DI pattern means tests are just function calls with fake inputs, so there’s nothing to mock and no lifecycle hooks to manage. A test framework would add a dependency and a configuration layer for problems we don’t have. The built-in runner handles assertions and test grouping, which is the whole requirement here.

The same philosophy extends to e2e. Playwright intercepts network calls so the tests run against the real UI without touching real services — the suite runs anywhere node does, with nothing to spin up first.

What it caught

Since this setup went live, no form submission has been lost without the team knowing about it within minutes. Each test is still just a function call with fake inputs and a check on the output.