← Automation Arsenal
Field notes Stage 03 · Verify APIs
Case study · API testing

Postmancontract lab

The UI is only as stable as the APIs underneath it. I use Postman to pin down every critical contract (status, shape, speed and auth) and run those checks as a gate in CI, so a broken API fails the build before a UI test has to find it.

What it is
API client and test platform
Test language
JavaScript: pm.* with Chai
Runs in CI via
Newman or the Postman CLI
Best for
Contracts, smoke checks and shared API docs
How it works

From a request to a quality gate

Four layers turn one-off API calls into a regression suite the whole team can run.

1

Collections

Requests grouped by feature, so the suite reads like the API's own map.

2

Environments

{{baseUrl}}, tokens and IDs swap per environment, so one suite covers dev, staging and prod.

3

Test scripts

pm.test assertions on status, schema, timing and headers, and they chain values into the next call.

4

CI gate

Newman runs the collection on every pull request and publishes a report. A red run blocks the merge.

Interactive · assertion lab

Break the API. Watch the tests catch it.

Pick a failure, send the request, and see which assertion stops it. Each of these is a bug I'd want caught before a UI test ever runs.

users-api · contract suiteSimulated responses

Inject a fault

GET {{baseUrl}}/users/42

              
0 / 6

    Press Send to run the suite.

    The code

    What sits behind those checks

    Post-response scripts for the assertions, a schema to lock the contract, and one CI step to enforce it.

    
    // Post-response script on GET /users/:id
    pm.test("status is 200", () => {
      pm.response.to.have.status(200);
    });
    
    pm.test("responds in under 800 ms", () => {
      pm.expect(pm.response.responseTime).to.be.below(800);
    });
    
    pm.test("content-type is JSON", () => {
      pm.response.to.have.header("Content-Type", /application\/json/);
    });
    
    pm.test("user matches the contract", () => {
      const user = pm.response.json();
      pm.expect(user).to.have.all.keys("id", "name", "email", "role");
      pm.expect(user.email).to.match(/^[^@\s]+@[^@\s]+$/);
    });
    
    // Chain: hand the id to the next request in the collection
    pm.collectionVariables.set("userId", pm.response.json().id);
    
    Small, named assertions: when one fails, the report tells you exactly what broke.
    Where it fits

    Stage 03 of my quality pipeline

    API checks run before the slower UI suites. A contract failure is cheaper to find here.

    Trade-offs

    Where it shines, and where I reach for something else

    Strengths

    • Fast feedback. Contract checks run in seconds, long before a browser starts.
    • Living documentation. The collection doubles as up-to-date API docs for devs and PMs.
    • Mock servers let UI work and UI tests continue while a service is still being built.
    • Easy to gate. One Newman step turns the suite into a merge blocker.

    Watch-outs

    • Exported JSON diffs poorly in code review. I keep collections small and focused per feature.
    • Script sprawl. Heavy logic in scripts is hard to maintain, so complex flows move to code (REST Assured, or Playwright's request API).
    • Not a load tool. Performance budgets here are smoke-level; real load testing needs a dedicated tool.
    My rule of thumb

    Test the contract where it lives.

    If a bug can be caught at the API layer, catching it in a UI test is slower, flakier and harder to debug.

    • Every critical endpoint has status, schema, timing and auth checks.
    • Environments keep secrets out of the collection file.
    • The suite runs on every pull request and blocks the merge when red.
    • Heavy logic lives in code, not in giant test scripts.