QA Interview Hub · Knowledge base · Quizzes
Cypress
Network: cy.intercept
interception and mocks
What is cy.intercept and where exactly does the interception happen?
cy.intercept intercepts the HTTP requests the page makes. Interception happens not in the browser but in the Cypress Node process, through which all the traffic passes as through a proxy. So it also sees requests from third-party scripts.
Two modes: spy, only to observe and check the request and response; stub, to replace the response. cy.intercept doesn't intercept WebSocket messages, only HTTP.
cy.intercept('GET', '/api/products*').as('products'); // spy
cy.intercept('GET', '/api/products*', { fixture: 'products.json' }); // stub
In an interview: Intercept intercepts the page's HTTP requests, and does it in the Cypress Node process, because all the traffic goes through its proxy. You can just observe a request and check it, or you can replace the response with a fixture or any data. It doesn't intercept WebSocket, only HTTP.
And in Playwright:page.route() intercepts requests at the browser protocol level, not through a proxy. A route is bound to a page or a context. Unlike Cypress, WebSocket can also be intercepted and mocked through page.routeWebSocket().
How do you wait for a request and check it through an alias?
You register an intercept, give it an alias with .as(), perform the action, and then cy.wait('@alias'). It yields an interception object with request and response, so you can check what exactly the front end sent and received.
cy.intercept('POST', '/api/orders').as('createOrder');
cy.get('[data-cy=checkout]').click();
cy.wait('@createOrder').then(({ request, response }) => {
expect(request.body.items).to.have.length(2);
expect(response.statusCode).to.eq(201);
});
In an interview: I register an intercept with an alias, perform the action that triggers the request, and do cy.wait on that alias. It returns both the request and the response, so I check that the front end sent the right body and that the backend responded with the expected status.
And in Playwright:There are no aliases: the wait for a request is created before the action and awaited after it.
const orderResp = page.waitForResponse('**/api/orders');
await page.getByTestId('checkout').click();
const resp = await orderResp;
expect(resp.status()).toBe(201);
expect(resp.request().postDataJSON().items).toHaveLength(2);
How do you replace or change a server response?
A static stub goes as the third argument: a fixture, a body, a status, a delay. A dynamic one goes through the req handler: you can change the request before sending, respond completely with req.reply, or let the request through to the server and change the real response in req.continue.
// a server error
cy.intercept('GET', '/api/stats', { statusCode: 500, body: { error: 'boom' } });
// a slow network
cy.intercept('GET', '/api/stats', { fixture: 'stats.json', delay: 2000 });
// the real response, but with one field replaced
cy.intercept('GET', '/api/stats', (req) => {
req.continue((res) => {
res.body.total = 0;
});
});
In an interview: For simple cases I pass the response as the third argument: a fixture, a status code, a delay. That is how I check how the UI behaves on a five hundred error or on a slow network. If I need to change a real response precisely, I write a handler: I let the request through to the server with req.continue and change one field in the response.
And in Playwright:Replacing is route.fulfill(), passing through is route.continue(), aborting is route.abort(). Modifying a real response is route.fetch() and fulfill with a new body.
await page.route('**/api/stats', async (route) => {
const response = await route.fetch();
const json = await response.json();
json.total = 0;
await route.fulfill({ response, json });
});
Why must an intercept be set before the action, and what happens if there are several on one URL?
An intercept fires only on requests that happened after it was registered. If you set it after cy.visit or a click, the request may slip through, and cy.wait will fail on timeout. A classic cause of flaky tests.
If several intercepts match one request, the one defined later fires first. Handlers can pass the request on, and the option times: 1 limits an intercept to one response, which is convenient for the scenario "an error first, then success". All intercepts are reset before every test.
cy.intercept('GET', '/api/user', { statusCode: 200, fixture: 'user.json' });
cy.intercept('GET', '/api/user', { times: 1 }, { statusCode: 503 });
// the first request → 503 (defined later, fires first)
// the following requests → 200
In an interview: An intercept sees only the requests that happen after it is registered, so I always set it before visit or a click. Otherwise the request slips through and wait fails, which is a frequent cause of flakiness. If there are several intercepts on one URL, the last one defined fires first, and with times one I can build a scenario where an error comes first and then a success.
And in Playwright:The rules are similar: a route must be registered before the action, the last registered handler fires first, a request can be passed on through route.fallback(), and the option { times: 1 } exists too.
cy.intercept
cy.intercept intercepts the application's HTTP requests by method and URL pattern. It is used for waiting on requests, stubbing responses and checking what exactly the application sent. The interception must be declared before the action that triggers the request.
For waiting: cy.intercept('GET', '/api/users').as('getUsers'), then an action and cy.wait('@getUsers'). After that you can check the request or response body and the status code. For a stub you pass a static response or a fixture, for example cy.intercept('GET', '/api/users', { fixture: 'users.json' }).
You can simulate errors and a slow network: statusCode: 500, forceNetworkError: true, delay. You can also change a request or response on the fly through a callback. This lets you test the UI in isolation from an unstable backend.