API Testing with Cypress and Playwright Instead of a Separate Tool
AI generated
PASS
expect()
Testing · Cypress · Playwright · API
API Testing with Cypress and Playwright Instead of a Separate Tool
cy.request() and APIRequestContext instead of switching to Postman

Teams that maintain API tests in a separate tool and E2E tests in Cypress or Playwright double their maintenance effort and lose context between the two worlds. With cy.request() and Playwright's APIRequestContext, test data can be built in milliseconds instead of through forms, auth sessions can be shared between API and browser, and entire test suites can be noticeably accelerated without losing reliability.

17 min read cy.request() · APIRequestContext · Auth Reuse · CI/CD Cypress 13+ · Playwright 1.4x · Magento REST API

1. Why API tests make sense inside the E2E framework

Many teams maintain two separate test ecosystems: a Postman or Insomnia collection for API tests and a separate Cypress or Playwright suite for the browser. The problem is rarely the tool itself, but the redundancy: login logic, environment variables, base URLs and test data fixtures exist twice and drift apart with every API change. An API test in Cypress or Playwright uses the same configuration, the same environment variables and the same CI job as the UI tests, leaving a single path to maintain.

The second reason is functional: both cy.request() and Playwright's APIRequestContext run in the same test context as the browser interaction. That means a test can create a user via the API, log in via the API, open the browser with the same session, and continue directly on the checkout page, without having to click through a registration form and a login screen. This combination of API setup and UI verification is the real value add, not the replacement of Postman with a new tool. Where API test logic already exists in Cypress or Playwright, the barrier to entry also drops for developers writing new tests, because they only need to know one language and one framework.

2. cy.request() in Cypress: direct HTTP calls without browser overhead

cy.request() sends an HTTP request directly from the Node process that controls Cypress, not from the rendered browser window. This eliminates any rendering, layout and JavaScript execution overhead: a request that takes several hundred milliseconds for DOM updates and reflows in the browser via XHR is often done as a cy.request() call in under 50 milliseconds. Cypress automatically waits for the response, checks the status code against 2xx by default, and returns body, headers and status as a regular, chainable object.

An important difference from fetch or axios calls in test code: cy.request() is part of the Cypress command queue and therefore automatically retry-capable when combined with assertions. In addition, cy.request() bypasses same-origin restrictions and CORS checks by default, because the request does not come from the browser context, which is convenient for backend calls against a different domain than the application under test. For assertions on JSON responses, cy.request(...).its('body').should(...) is enough, with no cy.wait() on an intercept alias needed.


// cypress/e2e/api/product-creation.cy.js
describe('Product API', () => {
  it('creates a product via REST API without touching the browser', () => {
    cy.request({
      method: 'POST',
      url: `${Cypress.env('apiUrl')}/rest/V1/products`,
      headers: { Authorization: `Bearer ${Cypress.env('adminToken')}` },
      body: {
        product: {
          sku: 'test-sku-001',
          name: 'Cypress Test Product',
          price: 19.99,
          status: 1,
          type_id: 'simple',
          attribute_set_id: 4
        }
      }
    }).then((response) => {
      expect(response.status).to.eq(200)
      expect(response.body.sku).to.eq('test-sku-001')
      // Store the id for cleanup in an after() hook
      Cypress.env('createdProductId', response.body.id)
    })
  })
})

3. Playwright APIRequestContext: its own HTTP client alongside the page

Playwright separates its API testing capabilities more clearly from the browser than Cypress does: via request.newContext() or the request parameter available in fixtures, a standalone APIRequestContext is created that exists independently of a Page. This allows pure API test files without ever starting a browser, which reduces both test runtime and resource consumption in CI. Playwright uses the same network stack for APIRequestContext as for browser requests, including automatic cookie management when the context and page share the same storageState.

A key feature is the native expect(response).toBeOK() assertion, as well as response.json() as a promise for the parsed body. Playwright also supports request.newContext({ extraHTTPHeaders }) for globally applied headers such as auth tokens, sparing you from repeatedly setting headers on every single request. Because APIRequestContext calls are awaitable just like page actions, API and UI steps can be mixed linearly within the same test, without the callback nesting typical of Cypress's command queue model.


// tests/api/cart-seeding.spec.ts
import { test, expect } from '@playwright/test'

test('seeds a cart via API then verifies totals in the UI', async ({ page, request }) => {
  // Step 1: seed cart data directly through the REST API
  const cartResponse = await request.post('/rest/V1/carts/mine/items', {
    headers: { Authorization: `Bearer ${process.env.CUSTOMER_TOKEN}` },
    data: { cartItem: { sku: 'test-sku-001', qty: 2, quote_id: process.env.QUOTE_ID } }
  })
  expect(cartResponse.ok()).toBeTruthy()
  const item = await cartResponse.json()
  expect(item.qty).toBe(2)

  // Step 2: switch to the browser and verify the same state visually
  await page.goto('/checkout/cart')
  await expect(page.getByTestId('cart-item-qty')).toHaveValue('2')
  await expect(page.getByTestId('cart-subtotal')).toContainText('39.98')
})

4. Setup via API, verification through the UI

The biggest practical benefit of API testing inside the E2E suite lies in test data setup. Instead of clicking through a registration form, a product catalog and an address form to prepare a test checkout, a single cy.request() or APIRequestContext call handles customer creation, product creation and cart population in a few hundred milliseconds. The actual test then begins exactly at the point that actually needs to be verified, such as the checkout form or the order confirmation, instead of simulating preconditions that are themselves error-prone and slow.

This separation also increases test reliability: a UI setup step that breaks because a colleague changed a form field's markup can fail dozens of downstream tests, even though the functionality actually being tested has not changed. An API setup is immune to cosmetic UI changes as long as the REST or GraphQL endpoint remains stable. In practice, a clear pattern is recommended: prepare everything that is not the subject of the test via the API; execute and verify everything that actually needs to be tested through the UI. A beforeEach hook with API setup is therefore often the most effective lever for noticeably speeding up a slow suite.

5. Authentication and session handling between API and UI

The obvious but slow approach is to fill out the login form for every test. Faster and more robust is to fetch a token or session cookie via the API and inject it directly into the browser context. In Cypress this happens via cy.session() combined with a cy.request() login: the result is cached across test runs, so only the first test actually triggers a login request. In Playwright, storageState plays the same role: a state with cookies and localStorage, generated once via API or UI, is saved as a JSON file and reused by all subsequent tests.

For token-based APIs like the Magento REST API, a POST to /rest/V1/integration/customer/token or /rest/V1/integration/admin/token is enough to obtain a bearer token, which is then used both for further API calls and, via an injected localStorage entry or a cookie, for the authenticated browser session. It is important to isolate tokens per test worker when tests run in parallel, so that two parallel test runs do not log each other out. Expired tokens should be renewed in a central fixture rather than duplicating a new login call in every single test.


// cypress/support/e2e.js: reuse an API-issued token as a cached UI session
Cypress.Commands.add('loginByApi', (email, password) => {
  cy.session([email, password], () => {
    cy.request('POST', `${Cypress.env('apiUrl')}/rest/V1/integration/customer/token`, {
      username: email,
      password: password
    }).then((response) => {
      const token = response.body
      window.localStorage.setItem('mage-customer-token', token)
      cy.setCookie('mironsoft_customer_logged_in', '1')
    })
  }, {
    validate: () => {
      cy.window().its('localStorage.mage-customer-token').should('exist')
    }
  })
})

// Usage in a spec: skip the login form entirely
beforeEach(() => {
  cy.loginByApi('customer@mironsoft.de', 'Test1234!')
  cy.visit('/checkout')
})

6. Performance: API tests vs. full browser E2E tests

The speed difference between a pure API test and a full browser E2E test is, in practice, an order of magnitude. A browser test that loads a product page, renders images and fonts, initializes Alpine.js or React, and waits for visible DOM elements typically takes two to eight seconds per test step. The same state established via cy.request() or APIRequestContext runs in 50 to 300 milliseconds, depending on network latency and server response time, with no rendering cost whatsoever.

In a suite with 200 E2E tests, where each on average replaces three to five UI setup steps with API calls, that quickly adds up to several minutes of saved runtime per full test run. The distinction matters: a pure API test does not check whether the UI displays the data correctly, only whether the backend responds correctly. Anyone who mistakes API tests for a replacement of UI tests loses test coverage for rendering bugs, CSS regressions and JavaScript errors. The performance gain comes not from less test coverage, but from moving pure setup effort out of the browser and into the API layer, while the actual UI verification still happens in the browser.

7. Test data management and cleanup

API-generated test data must be cleaned up just as consistently as UI-generated data, otherwise the test environment fills up with orphaned products, orders and customer accounts that disrupt later tests through name collisions or incorrect counts. A robust pattern: every test run creates entities with a unique prefix, for example a timestamp or a test run ID in the SKU or email field, and an after() or afterAll() hook deletes all IDs created during that run again via the API.

For Magento projects, a separate database snapshot or a bin/magento reset script that restores a clean baseline before every CI run also proves valuable, rather than relying entirely on test-owned cleanup. This way, failed cleanup runs, for example caused by an aborted test process, have no long-term consequences for the environment. API cleanup directly in the test is still worthwhile because it is faster than a full database reset and does not slow down local test runs during development. A central cleanupRegistry fixture object that collects created IDs and iterates over them at the end of the test prevents forgotten deletion calls in individual test files.


// tests/fixtures/cleanup.ts: Playwright fixture that tracks and deletes created entities
import { test as base } from '@playwright/test'

type CleanupFixture = {
  registerForCleanup: (endpoint: string, id: string | number) => void
}

export const test = base.extend<CleanupFixture>({
  registerForCleanup: async ({ request }, use) => {
    const created: Array<{ endpoint: string; id: string | number }> = []
    await use((endpoint, id) => created.push({ endpoint, id }))

    // Runs after the test, regardless of pass/fail
    for (const entry of created.reverse()) {
      await request.delete(`${entry.endpoint}/${entry.id}`).catch(() => {
        // Cleanup failures are logged, not fatal, snapshot reset is the safety net
        console.warn(`Cleanup failed for ${entry.endpoint}/${entry.id}`)
      })
    }
  }
})

8. CI/CD integration patterns for API-heavy suites

Because API tests do not need a browser, they can run in CI as their own, significantly faster job stage before the full browser E2E tests. A typical pipeline pattern: a fast API smoke test job runs on every commit and checks within under a minute whether central endpoints are reachable and return the expected response structures. Only after that does the slower, more resource-intensive browser E2E job start, often only on pull requests or before a deployment, to save compute time.

For parallel execution, it is important that API setup calls run isolated per worker. Cypress's --parallel flag with the dashboard service and Playwright's --shard option distribute tests across multiple machines, which means shared test data such as a globally reused customer account can lead to race conditions. The solution is to parameterize test data by worker index or shard ID. Secrets for API access, such as admin tokens or integration keys, belong in a CI secrets manager rather than in test code, and a separate, isolated test system instead of the production API prevents accidental side effects on real data.


# .github/workflows/e2e.yml: fast API smoke stage before the full browser suite
name: E2E Tests
on: [pull_request]

jobs:
  api-smoke:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20 }
      - run: npm ci
      - run: npx playwright test tests/api --project=api
        env:
          API_BASE_URL: ${{ secrets.STAGING_API_URL }}
          ADMIN_TOKEN: ${{ secrets.STAGING_ADMIN_TOKEN }}

  browser-e2e:
    needs: api-smoke
    runs-on: ubuntu-latest
    strategy:
      matrix:
        shard: [1, 2, 3, 4]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20 }
      - run: npm ci
      - run: npx playwright install --with-deps chromium
      - run: npx playwright test --shard=${{ matrix.shard }}/4
        env:
          API_BASE_URL: ${{ secrets.STAGING_API_URL }}

9. When a dedicated API tool still makes sense

Despite all the advantages, cy.request() or APIRequestContext does not fully replace a dedicated API tool like Postman or Insomnia. For exploratory API work, where a developer quickly tries out different parameter combinations before a test is even written, a graphical interface with request history is often more productive than a test script that first has to be written and executed. Team collaboration also benefits from shared collections: Postman workspaces make it possible to share API documentation, example requests and environment variables with non-technical stakeholders such as product managers or QA testers who do not have access to the test code repository or cannot run a test suite locally.

The pragmatic answer is usually a combination: Postman or Insomnia for fast, exploratory testing of new endpoints and for documenting the API to the team, cy.request() or APIRequestContext for automated, versioned tests that run in CI and reliably catch regressions. Anyone who maintains a Postman collection anyway can partially convert it into Playwright or Cypress test cases via the Postman-to-OpenAPI export or direct JSON exports, to avoid duplicate work.

Criterion API test in Cypress/Playwright Dedicated tool (Postman/Insomnia) Pure browser E2E test
Speed Very high, no rendering High, manual execution Low, full browser overhead
CI integration Native, same job as UI tests Possible via Newman/CLI, separate job Native, but resource-intensive
Collaboration with non-devs Requires code access Graphical interface, shared collections Requires code access
Debugging Stack trace in test code Instant visual response view DOM snapshot, video, trace
Catches UI rendering errors No No Yes

The table shows that none of the three tools fully replaces the other two. The strongest test architecture combines all three deliberately: API tests inside the E2E framework for fast, automated setup and regression protection, a dedicated tool for exploratory work and team communication, and browser E2E tests wherever the actual visible user experience needs to be verified.

Mironsoft

E2E test automation, API testing and CI/CD for Magento and Hyvä stores

Ready to move your test suite onto API testing?

We analyze your existing Cypress or Playwright suite, identify slow UI setup steps, and build API-backed test data fixtures with clean auth reuse and CI integration.

Test audit

Analysis of existing suites for slow UI setup steps and redundancies

API fixtures

cy.request() and APIRequestContext fixtures for setup, auth and cleanup

CI integration

Sharding, parallel runs and fast API smoke stages in the pipeline

10. Summary

The combination of cy.request() in Cypress and APIRequestContext in Playwright solves a concrete problem: slow, error-prone UI setup that eats into actual test time meant for verification. API calls made directly from the E2E framework build test data in milliseconds instead of seconds, share auth sessions between API and browser via cy.session() or storageState, and run in the same CI job as the UI tests, without duplicate configuration. The performance gain is real and, in suites with hundreds of tests, noticeable in minutes, not just milliseconds.

A dedicated API tool like Postman does not lose its purpose as a result, it shifts into a different role: exploratory work, API documentation and collaboration with non-technical stakeholders remain domains where a graphical interface with shared collections is superior. The most robust strategy deliberately separates what should run automated and versioned in the test suite from what is discussed manually and exploratively within the team, rather than a blanket replacement in either direction.

API testing with Cypress and Playwright: the essentials at a glance

cy.request() and APIRequestContext

Direct HTTP calls without browser rendering, often under 300 milliseconds instead of several seconds per UI step.

Setup via API, verification through UI

Generate test data via the API, run the actual verification in the browser. This reduces runtime and increases stability.

Auth reuse

cy.session() and storageState share tokens between API calls and the browser session, without repeated logins.

Dedicated tool stays relevant

Postman/Insomnia for exploratory work and team collaboration, E2E framework for automated, versioned tests.

11. FAQ: API Testing with Cypress and Playwright

1What is the difference between cy.request() and a normal browser request?
cy.request() sends the request directly from Cypress's Node process, without rendering overhead, and bypasses same-origin as well as CORS restrictions.
2Can I write API tests in Playwright without starting a browser?
Yes, via request.newContext() or the request fixture, a standalone APIRequestContext is created without a Page.
3How do I share a login session between API and UI?
Cypress: cy.session() with a cy.request() login. Playwright: storageState. Both avoid repeated logins per test.
4How much faster are API tests than browser E2E tests?
API calls run in 50-300 ms, comparable UI setup steps take 2-8 seconds. Across hundreds of tests, that adds up to minutes.
5Does API testing replace full browser tests?
No. API tests only check the backend, not the UI rendering. Rendering bugs stay undetected without real browser tests.
6Do I still need Postman as well?
For exploratory work and team collaboration, yes. For automated CI regression tests, the E2E framework is the better choice.
7How do I clean up test data created via the API?
Unique prefixes per test run, an after() hook to delete via the API, plus a database snapshot reset before every CI run as a safety net.
8How do I integrate API tests into the CI/CD pipeline?
As a fast job stage before the browser tests, running on every commit and catching backend problems in under a minute.
9How do I avoid race conditions with parallel API tests?
Parameterize test data by worker index or shard ID instead of using shared accounts or entities across parallel runs.
10Can I reuse a Postman collection?
Partially, via OpenAPI or JSON export, to carry over endpoints, headers and example payloads into the E2E test cases.