EPA Quote Forms — End-to-End Testing Approach
Purpose
This memo outlines the automated end-to-end (E2E) testing strategy in place for the EPA insurance quote platform. The test suite provides continuous quality assurance across all quote form journeys, ensuring that customers can complete quotes reliably and that business-critical outcomes are correctly handled.
Separation of Concerns — Unit Tests vs End-to-End Tests
The EPA platform has two distinct layers of automated testing, each with a different purpose and owned by a different part of the codebase.
Unit Tests — Inside the Product
Unit tests live within the flux-epa product repository itself. They test the application’s internal logic in isolation — individual functions, business rules, data transformations, and component rendering — without launching a browser or navigating a real form. Unit tests are:
- Fast — They run in milliseconds because they don’t involve a browser or network
- Narrow — Each test covers a single piece of logic (e.g. “does this function calculate the premium correctly?”)
- Developer-facing — They catch regressions in code as it is written, typically running on every commit
Unit tests answer the question: does each piece of the application work correctly on its own?
End-to-End Tests — Outside the Product
This E2E test suite is a separate project, deliberately kept outside the product codebase. It tests the application as a deployed whole — launching a real browser, navigating to a real environment, and completing quote journeys exactly as a customer would. E2E tests are:
- Slow — Each test takes seconds to minutes because it drives a real browser through multiple form pages
- Wide — A single test exercises the full stack: frontend rendering, form validation, API calls, backend processing, and quote outcome routing
- Customer-facing — They verify the experience from the customer’s perspective, not the developer’s
E2E tests answer the question: can a customer actually complete a quote and reach the right outcome?
Why Both Are Needed
Neither layer can replace the other. They catch fundamentally different types of problems:
| Unit Tests | E2E Tests | |
|---|---|---|
| Catches | Logic errors in individual functions | Broken journeys, wrong destinations, missing content |
| Misses | Integration failures between components | Internal implementation bugs |
| Speed | Milliseconds | Seconds to minutes |
| Runs against | Code in isolation | A deployed environment |
| Owned by | Product repository (flux-epa) | Test repository (epa-tests-e2e) |
A unit test might confirm that a premium calculation function returns the correct value, but it cannot tell you whether the customer actually sees that value on screen, or whether clicking “Get a Quote” sends them to the right page. Conversely, an E2E test can confirm the full journey works, but if it fails, it cannot pinpoint whether the issue is in the frontend, the API, or the business logic — that is where unit tests provide the detail.
Ownership Model
Each product has a product owner — a single developer who is responsible for that product’s E2E test suite. They maintain the component classes, step classes, test methods, and documentation, and they evolve the E2E coverage as the product grows. The product owner is the person who understands the customer journeys end to end and ensures the test suite reflects them accurately.
Other developers working on the product — building features, fixing bugs, refactoring code — are responsible for writing unit tests to cover their changes within the product repository. They are not expected to write or maintain E2E tests. However, the E2E suite is available to them as a tool:
- Validation — After completing a feature or fix, a developer can run the E2E tests against their deployed changes to confirm that the customer-facing journeys still work correctly
- Sanity checking — A quick smoke run provides confidence that a change has not broken anything obvious before handing work over for review
- Regression testing — The full suite can be run to verify that a change has not introduced side effects in other parts of the form or in other brands
This creates a clear division of responsibility:
| Product Owner | Feature/Bug Fix Developers | |
|---|---|---|
| Writes | E2E tests (components, steps, test methods) | Unit tests (within the product repo) |
| Maintains | E2E test suite, documentation, parametrised rules | Product code, unit test coverage |
| Uses E2E tests to | Validate developer work, verify releases, expand coverage | Sanity check their own changes, run regression tests |
The E2E tests become a quality gate that the product owner controls. When a developer submits work, the product owner can run the relevant E2E tests to verify that customer journeys are intact — without needing to manually click through forms. This frees developers to focus on building and unit-testing their code, while the product owner has an automated tool to confirm the end result works from the customer’s perspective.
The Separation Is Deliberate
Keeping the E2E tests in their own repository and distributing them as a package reinforces this ownership model:
- Independent release cycles — The product owner can update the E2E suite without changing the product, and vice versa
- Environment flexibility — E2E tests run against any deployed environment, not just a local development setup
- No coupling — The E2E tests interact with the application purely through its UI, the same way a customer does. They have no knowledge of internal code, database schemas, or API contracts. If a developer refactors the product’s internals but the UI behaviour remains the same, the E2E tests continue to pass without changes
- Low barrier for developers — Developers do not need to understand the E2E test codebase to benefit from it. They run the tests, review the report, and act on any failures
Scope of Testing
Products Covered
The test suite covers 6 insurance quote forms across 3 brands:
| Brand | Product | Test Cases |
|---|---|---|
| Adrian Flux | Car Insurance | 32 |
| Adrian Flux | Learner Driver Insurance | 21 |
| Sterling Insurance | Car Insurance | 34 |
| Sterling Insurance | Learner Driver Insurance | 16 |
| Bikesure | Motorcycle Insurance | 34 |
| Bikesure | Short-term Motorcycle Insurance | 32 |
| Total | 169 |
Each form consists of up to 12 steps that a customer completes to receive a quote, including vehicle details, personal information, driving history, and policy preferences.
What the Tests Verify
Tests are organised into the following categories:
- Smoke tests — Confirm that every page of the quote form loads correctly, displays the right content (titles, disclaimers, legal text), and that the core happy-path journey works end to end.
- Validation tests — Verify that form fields reject invalid or missing input and display the correct error messages to the customer.
- Quote outcome tests — Complete the full quote journey and confirm that customers are directed to the correct outcome page (buy online, request a callback, or call us) based on their details.
- Source code tests — Verify that marketing source codes are correctly passed through the entire journey and appear in callback URLs and quote outcomes, ensuring accurate attribution.
- Multi-party tests — Test the additional driver flows, including adding, editing, and removing named drivers from a policy.
Boundary: The Form, Not the Backend
These tests are scoped to the quote form itself — the customer-facing frontend application. They do not directly test connected backend services such as the quoting engine, pricing APIs, CRM systems, or payment gateways. The tests interact only with what the customer sees in the browser.
However, because the form depends on backend services to function, certain backend behaviours can be inferred from the test results. When a test completes a full quote journey and asserts the outcome, it is implicitly validating that the backend processed the submission correctly:
- Destination page assertions — When a test submits a quote and verifies the customer is redirected to the “buy” page, this confirms that the backend quoting engine received the submission, processed it, returned a quotable result, and the form routed the customer to the correct destination. A failure here could indicate a backend issue (quoting engine down, pricing rules misconfigured) even though the test itself only checks the URL the customer lands on.
- Quote reference assertions — When a test verifies that a quote reference number is displayed on the outcome page, this confirms that the backend successfully generated and returned a quote reference. The presence of this reference implies the quote was persisted in the backend system.
- Content assertions on outcome pages — Tests verify specific content on outcome pages: premium amounts, policy benefits, insurer names, excess values, and callback phone numbers. This content is populated by backend responses. If the backend returns incorrect data, these assertions will fail — surfacing a backend problem through a frontend test.
- Source code propagation — Tests verify that marketing source codes passed via URL parameters survive the full journey and appear in callback URLs and outcome pages. This validates that the form correctly passes source codes to the backend and that the backend includes them in its response data.
In this way, the E2E tests act as an early warning system for backend issues, even though they are not backend tests. A pattern of destination or content failures across multiple EPAs can indicate a shared backend service problem, while a failure isolated to a single EPA points to a product-specific configuration issue.
What these tests will not tell you is why a backend service failed — only that something in the chain produced an unexpected result from the customer’s perspective. Diagnosing the root cause requires backend logs, monitoring, and the product’s own unit and integration tests.
Architecture
The test suite is built on a three-layer architecture that separates concerns and promotes reuse. Each layer has a distinct responsibility:
┌─────────────────────────────────────────────────────┐
│ Tests │
│ Orchestrate steps into full scenarios and assert │
│ expected outcomes. │
│ e.g. test_AFC004_simple_vehicle_lookup │
├─────────────────────────────────────────────────────┤
│ Steps │
│ Represent a single page/step of the form. │
│ Compose element actions into complete scenarios │
│ for that page. │
│ e.g. VehicleLookupStep.fill() │
├─────────────────────────────────────────────────────┤
│ Components │
│ Represent a reusable form component. Expose │
│ individual user actions on that component. │
│ e.g. F058VehicleLookupCar.reg_lookup() │
└─────────────────────────────────────────────────────┘
Components — Individual Actions
At the lowest level, component classes represent a single reusable form element such as a vehicle registration lookup or a purchase details panel. Each method on a component maps to one discrete user action: entering a registration number, clicking “Find Car”, selecting a manufacturer from a dropdown, and so on.
Because components are self-contained, the same component can be shared across multiple brands and products. For example, the car vehicle lookup component is used by both Adrian Flux Car and Sterling Car forms without duplication.
Critically, the component numbering in the test suite mirrors the component numbering used in the flux-epa product itself and its documentation. The test class F058VehicleLookupCar corresponds
directly to component F058 in the EPA platform; F066VehiclePurchaseDetails corresponds to component F066, and so on. This shared naming convention means:
- Traceability — When a component is changed in the product, it is immediately clear which test component covers it
- Common language — Developers, testers, and product documentation all refer to the same component by the same number
- Incremental coverage — New actions and scenarios can be added to a component class over time without touching any existing tests. For example,
F058VehicleLookupCarcurrently covers registration lookup, manual entry, and vehicle changes. As the product evolves, additional actions (e.g. a new vehicle data source, a different lookup flow) can be added to the same class and then consumed by new or existing step scenarios. Coverage grows gradually, component by component, without requiring large-scale rewrites
The current component library is small — F001 (motorcycle lookup), F058 (car lookup), and F066 (purchase details) — but it is designed to expand. As new components are built in the test suite, they follow the same F-number convention, keeping the test layer aligned with the product layer.
Steps — Page Scenarios
The middle layer contains step classes, one per page of the quote form. A step class composes the actions from one or more components into complete scenarios for that page. For example, the entry page step brings together the vehicle lookup component and the purchase details component, calling their actions in the right order and advancing to the next page.
Each step exposes different scenarios:
- fill — the standard happy-path completion of that page
- fill_complex — an advanced path that exercises more of the page’s functionality (manual entry, changing selections, toggling options)
- error methods — deliberately trigger validation errors to test that the form rejects bad input correctly
This means the details of how a form page works are defined in one place. If the form changes, only the step class needs updating — not every test that uses it.
Tests — Orchestrated Journeys
At the top level, test methods orchestrate steps into meaningful scenarios and assert expected outcomes. A test might call a single step to verify one page works in isolation, or chain all steps together to complete a full end-to-end quote journey.
Tests are concerned with what should happen, not how to interact with the form. For example, a full quote journey test reads as a simple sequence — fill vehicle details, fill personal details, fill driving history, get a quote, verify the outcome — with each step handling its own interactions internally.
Why This Matters
This layered separation provides several practical benefits:
- Reduced duplication — Common form components are written once and reused across brands. The vehicle lookup is defined in a single component class, not copied into every test.
- Easier maintenance — When a form page changes, updates are made in one step class rather than across dozens of individual tests.
- Readability — Tests read as business-level scenarios (“complete personal details, then get a quote”) rather than low-level browser interactions (“click this button, fill this field”).
- Faster test development — New tests for existing forms can be composed from the library of steps and components that already exist.
Technology Stack
Playwright — The Engine
Playwright is the core of the test suite. It is a browser automation framework developed by Microsoft that launches and controls a real web browser (Chromium) programmatically. Playwright is responsible for everything the tests do:
- Navigating to quote form URLs
- Interacting with form elements — clicking buttons, filling text fields, selecting dropdowns, toggling radio buttons — exactly as a customer would
- Waiting intelligently for pages to load, network requests to complete, and elements to become visible before proceeding
- Asserting that the page is in the expected state — checking text content, element visibility, URL changes, and page metadata
- Recording video of every test session and capturing screenshots on failure
- Tracing detailed execution logs (DOM snapshots, network requests, console output) that can be replayed step-by-step when investigating failures
The EpaStep class wraps Playwright’s Page object with helpers specific to EPA quote forms — navigating between form steps, verifying step titles, checking field-level validation errors, and
confirming quote outcome destinations. Every component and step class ultimately delegates to Playwright for all browser interactions.
pytest — The Runner
pytest serves as the test runner and orchestration layer. It does not interact with the browser directly — that is entirely Playwright’s domain. pytest’s responsibilities are:
- Discovery — Automatically finding and collecting all test methods across the 6 product suites
- Fixtures — Managing setup and teardown (browser contexts, cookies, environment configuration, form URLs) so that each test starts in a clean, correctly configured state
- Markers — Providing the tagging system (
@pytest.mark.smoke,@pytest.mark.adrianflux, etc.) that allows selective test execution - Parametrisation — Driving data-driven tests, such as running the same quote journey with 21 different source codes or multiple email/price combinations to verify different outcomes
- Reporting — Generating the HTML test report with pass/fail results, embedded screenshots, video links, and links to test case documentation
- Plugin system — The test suite is packaged as a pytest plugin, meaning consuming projects get all fixtures, markers, and configuration automatically just by installing the package
How They Work Together
pytest Playwright
───── ──────────
Discovers tests
Resolves fixtures (URLs, cookies)
Launches browser
Sets viewport, video recording
Calls test method
Navigates to quote form
Fills fields, clicks buttons
Waits for pages to load
Asserts page content
Collects pass/fail result
Captures screenshot (on failure)
Saves video recording
Generates HTML report
Attaches screenshots & video links
In short: Playwright does the work, pytest organises and reports on it.
Automatic Evidence Capture
Every test run automatically produces:
- Video recordings of each test, viewable directly from the HTML report
- Screenshots captured at the point of any failure
- Playwright trace files for failed tests, providing a step-by-step replay of DOM state, network activity, and console output
These artifacts are compiled into a self-contained HTML report that can be opened in any browser and shared without special tooling. Each test result links directly to its video, failure screenshot, and corresponding test case documentation on GitHub.
Reports from CI runs are published to a CDN and accessible via a browser:
- Report index (all runs): https://tests-e2e-cdn.igate-test.co.uk/index.html
- Example report: https://tests-e2e-cdn.igate-test.co.uk/2026-02-13_15/report.html?sort=result
Test Environments
Tests can be targeted at any environment (development, UAT, staging, production) by changing a single configuration value. Each brand can also be pointed at a different environment independently, allowing testing to proceed in parallel across teams.
Test Execution
On-Demand via GitHub
Tests are triggered on demand through GitHub Actions. The person running the tests selects:
- The target environment (e.g. UAT, staging)
- Optionally, a subset of tests to run (e.g. only smoke tests, only a specific brand)
Results and artifacts are retained for 30 days.
Selective Execution
Tests are tagged with descriptive markers, making it straightforward to run targeted subsets:
| Marker | What It Runs |
|---|---|
smoke | Core happy-path tests across all brands |
validation | Input validation and error handling |
quoting | Full end-to-end quote journeys |
adrianflux | All Adrian Flux tests only |
sterling | All Sterling tests only |
bikesure | All Bikesure tests only |
car | Car insurance forms only |
learner | Learner driver forms only |
bike | Motorcycle forms only |
Markers can be combined — for example, running only smoke tests for Bikesure motorcycle forms.
Parametrised Tests — Validating Destination Rules at Scale
One of the most powerful features of the test suite is its use of parametrisation to validate EPA destination rules comprehensively. Rather than writing a separate test for each combination of inputs and expected outcomes, a single test method is written once and then driven by a data table. pytest automatically generates and runs a distinct test for every row in that table.
The Problem: Destination Rules Vary by EPA
Each EPA has its own business rules that determine where a customer is directed after completing a quote. Depending on the combination of quote status, premium amount, and source code, the customer may be sent to:
- Buy online — A quote was issued and the customer can purchase immediately
- Callback — The quote needs further underwriting; the customer is offered a callback
- Call us — The quote cannot be processed online; the customer is directed to call
These rules differ between EPAs. For example, a quoted customer with a premium of £1,000 might be directed to the buy page on Sterling Car, but to the callback page on Adrian Flux Car. Getting these rules wrong means customers end up on the wrong page — either unable to buy when they should be able to, or seeing incorrect messaging.
How Parametrisation Solves This
Each EPA’s destination rules are expressed as a simple data table directly in the test code:
| Email Prefix | Price | Expected Destination |
|---|---|---|
| quoted | £0 | call-us |
| quoted | £1 | callback |
| quoted | £1,999 | callback |
| quoted | £2,000 | callback |
| quoted | £3,999 | callback |
| quoted | £4,000 | call-us |
| rejected | £0 | call-us |
| rejected | £1 | call-us |
| rejected | £1,999 | call-us |
| … | … | … |
(Example: Adrian Flux Car — 12 combinations from a single test definition)
From this one table, pytest generates 12 independent tests — each running the full quote journey end to end with different inputs and verifying the customer lands on the correct outcome page. The report shows each combination as a separate pass or fail, making it immediately clear which specific rule has broken.
Scaling Across EPAs
The same pattern is applied across all 6 products, with each EPA’s table reflecting its own destination rules. This means:
- Adding a new destination rule is as simple as adding a row to the table — no new test code to write
- Changing a rule (e.g. moving the callback threshold from £2,000 to £3,000) means updating one value in the table
- Each EPA’s rules are visible at a glance in the test file, serving as living documentation of the business logic
Beyond Destinations: Source Code Attribution
The same parametrisation approach is used to validate marketing source code handling across the quote journey. A second data table combines source codes, “where did you hear” selections, expected destinations, and expected callback URL parameters — generating up to 21 test runs from a single test method per EPA.
This covers scenarios such as:
- A source code passed via URL appearing correctly in callback links
- Short-form source codes being resolved to their canonical form
- Missing or empty source codes falling back to the correct default
- Source codes surviving the full journey through all form steps and appearing on the outcome page
The Net Effect
Across all 6 products, parametrisation generates a large number of test runs from a relatively small number of test definitions. One test method with a 12-row table and another with a 21-row table produces 33 full end-to-end journeys per EPA — nearly 200 destination and source code validations across the suite, each running independently and reporting individually. Adding a new EPA’s rules means defining its data table; the test logic is already written.
Reusability — Packaged as a Shared Library
The test suite is not a standalone script — it is built and distributed as a Python package (flux-epa-e2e-tests) that can be installed into any project. This means the tests, fixtures, step
classes, and component classes are all reusable across multiple consuming applications.
How It Works
The package is registered as a pytest plugin via a standard entry point. When a consuming project installs the package, pytest automatically discovers and loads:
- All test fixtures (browser configuration, cookie setup, environment resolution)
- All test markers (smoke, quoting, adrianflux, etc.)
- All default configuration (HTML reporting, video recording, tracing, output directories)
- All test suites, step classes, and component classes
A consuming project needs only two things to run the full test suite:
pip install flux-epa-e2e-tests(or add it to their dependencies)- A
.envfile with the target environment URLs and QA bypass cookie
No additional pytest configuration, fixture definitions, or test imports are required — the plugin handles all of it. The consuming project can also override any default (report path, output directory, target URLs) through its own configuration or command line flags.
Versioned Releases
The package follows semantic versioning (currently v0.12) and is published to a private package registry (Gemfury). This ensures:
- Consuming projects pin to a known version and upgrade deliberately
- Test changes are tracked through a changelog
- Rollback to a previous version is straightforward if needed
CI/CD Pipelines
The test suite has two GitHub Actions workflows that automate execution and distribution.
1. Test Execution Pipeline
A manually triggered workflow that runs the test suite against a configured environment:
- Provisions an Ubuntu runner with Python 3.12
- Installs the test package and Playwright’s Chromium browser
- Runs tests — optionally filtered by a marker expression (e.g.
smoke,adrianflux and quoting) - Captures the pytest summary line (passed/failed counts)
- Uploads the full results directory (HTML report, videos, screenshots, traces) as a GitHub artifact retained for 30 days
- Uploads the HTML report to Cloudflare R2 cloud storage for easy sharing via a public URL
Environment URLs and secrets (QA bypass cookie, R2 credentials) are managed through GitHub repository variables and secrets, keeping sensitive values out of the codebase.
2. Package Publishing Pipeline
Triggered automatically when a GitHub release is created:
- Builds the Python package (wheel)
- Uploads it to the Gemfury private package registry
This means the release process is: tag a version, create a GitHub release, and the package is available for consuming projects to install within minutes.
Pipeline Summary
┌──────────────────────┐
Manual trigger ──►│ Run E2E Tests │
(select markers) │ - pytest on CI │
│ - HTML report │──► GitHub Artifacts (30 days)
│ - Videos & traces │──► Cloudflare R2 (shareable URL)
└──────────────────────┘
┌──────────────────────┐
GitHub release ──►│ Publish Package │
│ - Build wheel │──► Gemfury Registry
│ - Upload to Gemfury │ (pip install in other projects)
└──────────────────────┘
Test Case Numbering, Documentation, and Traceability
The Numbering System
Every test method carries a unique, sequentially numbered identifier that ties together the test code, its documentation, and its result in the HTML report. The identifier is embedded directly in the method name and docstring:
- Method name:
test_AFC009_complex_vehicle_lookup - Docstring:
AFC-009: Verify complex vehicle lookup with manual entry advances to car details.
Each product suite has its own prefix (AFC for Adrian Flux Car, BKSB for Bikesure Bike, SL for Sterling Learner, etc.) and numbers run sequentially from 001 upwards in the order the tests appear in the file. This means the case number reflects the test’s position in the suite — there are no gaps or out-of-order numbers.
Documentation with Claude Code
Test case documentation is generated and maintained using Claude Code (Anthropic’s AI coding assistant) through a set of custom slash commands built specifically for this project:
/new-test-case — When a new test is written, this command:
- Reads the test method, the step classes it calls, and the element classes those steps use
- Understands what the test does at the UI level by following the full call chain
- Assigns the next available case number and renames the test method accordingly
- Generates a plain-English documentation file describing every step the test performs
- Adds the new case to the suite’s index file
/check-test-case-order — Audits a test suite to verify that all case numbers are sequential, all documentation files exist, and all cross-references between documents are valid. If numbers have
drifted (e.g. after tests were reordered), it re-indexes the entire suite — renaming test methods, updating doc files, and correcting cross-references in a single operation.
/remove-test-case — Removes a test from the suite, deletes its documentation, and re-indexes all subsequent cases so the numbering remains gapless.
This approach means documentation is never written from scratch by hand. Claude reads the actual test code — including the step and component layers — and produces accurate, up-to-date descriptions of what each test does. When tests change, the documentation can be regenerated from the code rather than manually updated.
What a Test Case Document Looks Like
Each test case has its own markdown file (e.g. docs/adrianflux_car/AFC-009.md) containing:
- Category — Smoke, Validation, or Parametrised
- Markers — The pytest markers applied to this test
- Test method — The exact function name for traceability back to the code
- Steps — A numbered list describing every action in plain English
- Test data (for parametrised tests) — A table of input combinations and expected outcomes
There are currently 174 individual test case documents plus a summary index for each product that lists all cases in a single table.
Linked in the HTML Report
The test case documentation is not just a separate reference — it is linked directly from the HTML test report. A pytest hook inspects each test result, extracts the case ID from the test method
name (e.g. AFC009 from test_AFC009_complex_vehicle_lookup), and appends a clickable link to the corresponding documentation file on GitHub.
This means that when reviewing test results, anyone can click through from a pass or failure directly to the full plain-English description of what that test was verifying — without needing to read the test code.
HTML Report Row
┌──────────────────────────────────────────────────────────────┐
│ test_AFC009_complex_vehicle_lookup PASSED │
│ [Video] [Screenshot] [Test Case ↗] │
│ │ │
│ └─► docs/adrianflux_car/AFC-009.md
│ on GitHub │
└──────────────────────────────────────────────────────────────┘
This creates a full traceability chain: test result → test case documentation → test code → step classes → component classes, all connected by the case ID.
Quality Controls on the Test Suite Itself
The test codebase is held to the same engineering standards as production code:
- 100% documentation coverage — Every module, class, and method has a descriptive docstring, enforced by automated checks.
- Automated linting and formatting — Code style is enforced consistently across the entire suite.
- Versioned releases — The test suite is versioned (currently v0.12) and distributed as a package, ensuring reproducible test runs.
Key Benefits
- Confidence in releases — Tests can be run against any environment before or after a deployment to verify that quote journeys work correctly.
- Rapid feedback — Issues are caught early with clear evidence (video, screenshots, traces) to support diagnosis.
- Cross-brand consistency — The same testing patterns are applied uniformly across all 3 brands and 6 products.
- Marketing attribution assurance — Source code tests verify that campaign tracking remains intact through the full customer journey.
- Scalability — Adding a new brand or product follows an established template, keeping the approach consistent as the platform grows.
Applying This Architecture to Other Products
The architecture behind this test suite is not specific to insurance quote forms. The three-layer pattern (components, steps, tests), the tooling (Playwright, pytest, Claude Code documentation), and the infrastructure (CI pipelines, CDN-hosted reports, package distribution) can be applied to any multi-step web application. Adopting it for a new product does not require starting from scratch — the patterns and infrastructure are already proven and can be replicated.
What Transfers Directly
The following elements are product-agnostic and can be reused as-is or with minimal adaptation:
- The
EpaStepwrapper — The helper class that wraps Playwright’s page with convenience methods (navigate, verify titles, check errors, wait for pages) is not EPA-specific. A similar wrapper could be created for any product, or the existing one extended. - The three-layer pattern — Components (individual UI actions), steps (page scenarios), and tests (orchestrated journeys) work for any multi-page application: onboarding flows, checkout processes, claims forms, account management, or any wizard-style UI.
- Parametrised destination/outcome testing — Any product with branching outcomes based on user input (approval/rejection, pricing tiers, eligibility checks) can use the same data-table approach to validate all paths from a single test definition.
- The CI pipeline — The GitHub Actions workflows for running tests on demand, uploading reports to cloud storage, and publishing the package require only configuration changes (environment URLs, secrets) to point at a different product.
- The CDN-hosted report index — The R2 upload and index page pattern works for any test suite, giving stakeholders a single URL to find any historical test run.
- Claude Code slash commands — The
/new-test-case,/check-test-case-order, and/remove-test-casecommands are driven by naming conventions (prefix + sequential number + markdown docs). Adapting them for a new product means defining a new prefix — the commands themselves handle discovery, numbering, documentation generation, and re-indexing.
What a New Product Needs
To bring a new product into the same testing architecture, the product-specific work is:
- Component classes — One class per reusable UI component in the new product, following the F-number convention if the product uses it, or a suitable naming scheme otherwise. Each class exposes the actions a user can perform on that component.
- Step classes — One class per page or screen, composing component actions into scenarios (happy path, complex path, error paths).
- Test methods — Orchestrating steps into the journeys that matter for that product, tagged with appropriate markers.
- Fixtures — Product-specific configuration: base URLs, environment overrides, expected content (titles, disclaimers, legal text).
- Parametrised data tables — The product’s business rules expressed as input/outcome tables for destination testing.
The infrastructure, reporting, documentation tooling, and package distribution are already in place. A new product plugs into the existing system rather than building its own.
A Practical Example
Consider a hypothetical home insurance quote form. Applying this architecture would look like:
| Layer | EPA (existing) | Home Insurance (new) |
|---|---|---|
| Components | F058VehicleLookupCar | F040PropertyLookup |
F066VehiclePurchaseDetails | F041PropertyDetails | |
| Steps | S01_entry_page (vehicle reg) | S01_entry_page (postcode lookup) |
S04_policyholder_details | S03_policyholder_details | |
| Tests | test_AFC024_quote_outcomes | test_HI024_quote_outcomes |
| Parametrised rules | quoted + £1,000 → callback | standard + £500 → buy |
| Docs | AFC-024.md | HI-024.md |
The component and step classes are new — they reflect the new product’s UI. But the test structure, documentation tooling, CI pipeline, and report infrastructure are identical. The investment made in the EPA suite pays forward into every subsequent product.
Summary
The EPA E2E test suite provides structured, automated coverage of all customer-facing quote journeys across Adrian Flux, Sterling Insurance, and Bikesure. With 169 documented test cases, automatic evidence capture, and flexible execution options, the suite gives stakeholders confidence that quote forms are functioning correctly and that customers are reaching the right outcomes.