rgoussu@goussu: ~/library/java/testing
~/library/java/testing cat end-to-end-testing.md

End-to-end testing — REST Assured, browsers & contracts

# Whole-system testing with REST Assured, Selenium/Playwright and Karate, contract testing as the leaner alternative, and flakiness discipline.

Conceptsaved 2026-08-09 #java#testing#e2e#contracts#ci

Overview

End-to-end tests exercise the deployed system through its public surface — HTTP API or browser UI — with real infrastructure underneath. They give the highest-fidelity signal and the worst economics: slow, environment-dependent, and the natural home of flakiness. The discipline is to keep this layer thin and deliberate, push checks down to integration tests where possible, and replace cross-service E2E with consumer-driven contracts.

Key points

  • REST Assured for API-level E2E: fluent given/when/then DSL over HTTP, JSON/XML path assertions, auth support; the standard for black-box API tests (and bundled as the test convention in Quarkus).
  • Playwright over Selenium for new browser suites: auto-waiting, network interception, trace viewer, and bundled browsers cut the classic flake sources; Selenium WebDriver remains the W3C-standard incumbent with the broadest grid/browser ecosystem.
  • Karate combines API testing, mocks and load in a Gherkin-like DSL — tests are plain text, no Java glue per step; attractive for mixed-skill teams, opinionated by design.
  • Contract testing replaces most cross-service E2E: Pact (consumer-driven) verifies each consumer's expectations against the provider in its own build — no shared environment, failures name the exact broken interaction.
  • Spring Cloud Contract is the provider-driven variant for Spring shops: contracts in the provider repo generate provider tests and consumer stubs (via Stubrunner).
  • Flakiness is a defect, not weather: quarantine-and-fix, never blind @RepeatedTest retries; forbid Thread.sleep in favour of explicit waits/polling (Awaitility).
  • A failing E2E test should page someone or block the release — if a failure can be shrugged off, the test shouldn't exist at this layer.

Details

What belongs at this layer

  • A handful of user-critical journeys (sign-up, checkout, the money path).
  • Smoke tests post-deployment: is the system up, wired and answering.
  • Everything else — validation rules, error mapping, edge cases — belongs in unit or integration tests where it is cheap and deterministic.

Contract testing in one pass

  1. Consumer writes a Pact test against a mock provider → generates a pact file.
  2. Pact is published to a Pact Broker (versioned, tagged per branch/env).
  3. Provider's build replays every consumer pact against the real provider (@Provider/@PactBroker JUnit 5 support) and publishes verification results.
  4. can-i-deploy gates releases on matrix compatibility.

Spring Cloud Contract inverts authorship (contracts live provider-side, in Groovy/YAML) and hands consumers generated WireMock stubs. Both beat a shared staging environment: verification is per-build, isolated and attributable.

E2E in CI

Approach How Trade-off
Docker Compose in the pipeline docker compose up app + deps, run suite against localhost Simple, portable; drifts from prod topology
Testcontainers-orchestrated Compose module or containers from the test JVM One tool for integration and E2E; same drift caveat
Ephemeral environment Spin a namespace per PR (k8s, preview envs), deploy real manifests Highest fidelity; needs platform investment

Keep E2E suites in a separate CI stage with its own budget (e.g. under 15 minutes), parallelised, producing artefacts on failure (screenshots, Playwright traces, HTTP logs).

Examples

@Test
void createdOrderIsRetrievable() {
    String id =
        given()
            .contentType(ContentType.JSON)
            .body(new CreateOrder("widget", 3))
        .when()
            .post("/orders")
        .then()
            .statusCode(201)
            .extract().path("id");

    when()
        .get("/orders/{id}", id)
    .then()
        .statusCode(200)
        .body("items[0].sku", equalTo("widget"),
              "items[0].quantity", equalTo(3));
}

Related