What cy.request() Is For
cy.request() sends an HTTP request directly from Cypress's Node process, bypassing the browser entirely, which makes it well suited for two distinct purposes: setting up or tearing down backend state before a UI test runs, and writing pure API tests that assert on status codes, headers, and response bodies without ever touching a page. Because it doesn't go through the browser, cy.request() automatically follows redirects, doesn't enforce CORS restrictions, and fails the test immediately on any non-2xx/3xx status code unless you explicitly pass failOnStatusCode: false.
Cricket analogy: cy.request() is like a groundstaff member walking directly onto the pitch before the match to set exact field markings, rather than relying on players to configure the field naturally during play.
Using cy.request() for Test Setup
A common pattern is calling cy.request() in a beforeEach hook to create a user, log in and grab an auth token, or seed a database record via a real API call, which is far faster and more reliable than driving the same setup through the UI on every test. Since cy.request() returns a response object synchronously chainable with .then(), you can extract a token from response.body.token and store it, then either set it as a cookie or localStorage value directly, or attach it as an Authorization header on subsequent cy.request() calls, skipping the login form entirely for tests that aren't specifically testing login.
Cricket analogy: Seeding state with cy.request() before a UI test is like a team arriving at the ground with players already changed and warmed up in the bus, rather than filming the entire pre-match routine every single day.
beforeEach(() => {
cy.request('POST', '/api/auth/login', {
email: 'qa-user@example.com',
password: 'Test1234!'
}).then((response) => {
expect(response.status).to.eq(200);
window.localStorage.setItem('authToken', response.body.token);
});
});
it('shows the dashboard for a logged-in user', () => {
cy.visit('/dashboard');
cy.contains('Welcome back').should('be.visible');
});
it('rejects a request without a valid token', () => {
cy.request({
method: 'GET',
url: '/api/orders',
failOnStatusCode: false,
headers: { Authorization: 'Bearer invalid-token' }
}).then((response) => {
expect(response.status).to.eq(401);
});
});Pure API Testing vs. UI-Supporting Requests
cy.request() can also anchor entire test files dedicated purely to API contract verification, with no cy.visit() at all, asserting directly on response.status, response.headers, and response.body.property against expected schemas or values. This differs from using cy.request() merely as UI test scaffolding: a pure API test suite typically runs faster since it skips rendering entirely, and it's valuable for catching backend regressions independent of any frontend changes, but it does not verify that the frontend actually consumes and displays that data correctly, which still requires browser-driven tests.
Cricket analogy: A pure API test is like a bowling machine calibration check done in an empty stadium, verifying the mechanism throws exactly 140kph without any batter present, versus a full net session that also checks how a real batter reacts to it.
cy.request() runs outside the browser's JavaScript sandbox, so it is not subject to CORS restrictions the way a fetch() call from your application code would be. This makes it ideal for hitting a different origin, such as a separate auth service, directly during setup without needing CORS headers configured on that service just for testing.
By default, cy.request() fails the entire test immediately if the response status is not in the 2xx or 3xx range. When intentionally testing an expected failure response, such as a 401 or 422, you must pass failOnStatusCode: false in the request options, otherwise Cypress throws before you ever get to assert on the response body.
- cy.request() makes HTTP calls directly from Cypress's Node process, bypassing the browser entirely.
- It's commonly used in beforeEach hooks to seed data or authenticate, skipping slower UI-driven setup.
- Responses are synchronously available via .then(), exposing status, headers, and body for assertions.
- cy.request() ignores CORS restrictions and automatically follows redirects, unlike a browser fetch.
- By default it fails the test on any non-2xx/3xx status unless failOnStatusCode: false is passed.
- Pure API test suites built on cy.request() run fast but don't verify the frontend actually renders the data correctly.
Practice what you learned
1. What is a key architectural difference between cy.request() and a request made via cy.intercept() during a page visit?
2. Why is cy.request() often used in a beforeEach hook for authentication?
3. What must be passed to cy.request() to prevent it from failing the test on a 401 response?
4. Why does cy.request() not run into CORS errors when hitting a different origin?
5. What is a limitation of a test suite built purely on cy.request() with no cy.visit() calls?
Was this page helpful?
You May Also Like
Intercepting Network Requests with cy.intercept()
Learn how cy.intercept() lets Cypress observe, wait on, and control the XHR and fetch traffic your application sends during a test run.
Waiting for Requests
Learn how cy.wait() synchronizes Cypress tests with network activity, avoiding both flaky race conditions and unnecessary fixed delays.
Fixtures for Test Data
Learn how Cypress fixtures store reusable static test data as JSON files and how to load them into stubs, requests, and assertions.