Aliases are the syntax-level glue between cy.intercept and the rest of the test. By naming an intercepted request with .as("name"), you can pause the test on it (cy.wait("@name")), assert against it (cy.get("@name")), and chain it cleanly into multi-step flows. This lesson focuses on every aliased-wait pattern you'll need: single waits, multiple waits, repeated waits for paginated endpoints, and timeout tuning for slow services.
Aliasing an intercept
The pattern is exactly what you've seen in the previous two lessons — register an intercept and immediately call .as():
cy.intercept("GET", "/api/users").as("getUsers");@getUsers is now the name you use everywhere else. Conventions:
- Match the alias to the verb-and-noun:
getUsers,createOrder,deletePost. Do not name them after route paths or HTTP methods alone. - Keep them descriptive enough that a
cy.wait("@createOrder")reads as English. - One alias per intercept — re-using a name overwrites the previous registration.
cy.wait("@alias") — the canonical pause
The most common use of an alias is cy.wait — pause the test until that specific request fires and resolves:
cy.intercept("GET", "/api/users").as("getUsers");
cy.visit("/users");
cy.wait("@getUsers");
cy.get("[data-testid='user-row']").should("have.length", 10);cy.wait("@getUsers") is condition-based. The test continues the moment the request returns — usually under a hundred milliseconds. It also yields the captured interception object, so you can chain .then to inspect:
cy.wait("@getUsers").then((interception) => {
expect(interception.response?.statusCode).to.equal(200);
expect(interception.response?.body.users).to.have.length(10);
});This is the pattern that replaces every cy.wait(2000) in your codebase. Slow tests get faster; fast tests stop being flaky; failures point straight at the request that didn't fire.
Why aliases beat fixed waits
Three reasons, every time:
- Reliable. The wait completes when the actual request resolves — not when an arbitrary timer ends. CI machines under load and dev machines under no load both produce stable behaviour.
- Fast. A real request that takes 50ms unblocks the test in 50ms.
cy.wait(2000)always takes 2000ms. - Diagnosable. If the request never fires,
cy.wait("@alias")fails with "no matching request was made," pointing you at exactly what's wrong.cy.wait(2000)succeeds blindly and the next assertion fails for a confusing reason.
If a test has any cy.wait(<number>) calls left in it after this lesson, treat them as code smells in review.
Waiting on multiple requests at once
When a page boots and fires three independent requests simultaneously, wait on all of them in one call:
cy.intercept("GET", "/api/users/me").as("getMe");
cy.intercept("GET", "/api/notifications").as("getNotifications");
cy.intercept("GET", "/api/products").as("getProducts");
cy.visit("/dashboard");
cy.wait(["@getMe", "@getNotifications", "@getProducts"]);
cy.get("[data-testid='dashboard-loaded']").should("be.visible");cy.wait with an array yields an array of interceptions in the same order. So you can still inspect each:
cy.wait(["@getMe", "@getProducts"]).then((interceptions) => {
expect(interceptions[0].response?.body.role).to.equal("admin");
expect(interceptions[1].response?.body).to.have.length.greaterThan(0);
});This is the right shape for any page that does parallel fetches on mount.
Waiting on the same alias multiple times
When a single endpoint fires repeatedly (pagination, infinite scroll, polling), cy.wait("@alias") consumes one occurrence each time. Call it again to wait for the next:
cy.intercept("GET", "/api/products*").as("getProducts");
cy.visit("/products");
cy.wait("@getProducts"); // initial page-1 fetch
cy.get("[data-testid='next-page']").click();
cy.wait("@getProducts"); // page-2 fetch
cy.get("[data-testid='next-page']").click();
cy.wait("@getProducts"); // page-3 fetchEach cy.wait("@getProducts") waits for a new matching request. Cypress tracks how many you've consumed and pauses for the next. This is the cleanest way to test paginated lists — no cy.wait(ms) between pages, no race conditions on slow CI.
For tests where the order matters and you want to assert on a specific occurrence, you can index into the captured requests with cy.get("@alias.all") (every interception so far) or cy.get("@alias.<n>") (the nth):
cy.get("@getProducts.all").should("have.length", 3); // three page loads happened
cy.get("@getProducts.0").its("response.body.page").should("eq", 1);
cy.get("@getProducts.2").its("response.body.page").should("eq", 3);Custom timeouts on cy.wait
The default cy.wait timeout is 5 seconds (set by requestTimeout and responseTimeout in cypress.config.ts). For genuinely slow endpoints — analytics aggregations, report generation — bump it per call:
cy.intercept("POST", "/api/reports/generate").as("generateReport");
cy.get("[data-testid='generate-btn']").click();
cy.wait("@generateReport", { timeout: 30000 }); // up to 30s
cy.get("[data-testid='report-link']").should("be.visible");The same surgical-vs-global rule from the auto-waiting lesson applies: prefer per-call { timeout: ... } to bumping the global timeout. Failing tests should fail fast everywhere except the one place that genuinely needs slack.
A real three-step checkout
Stitching everything together into the kind of multi-step flow you'll write at work — a checkout that fires three distinct API calls in sequence:
describe("Three-step checkout", () => {
beforeEach(() => {
cy.intercept("POST", "/api/orders").as("createOrder");
cy.intercept("POST", "/api/payments").as("processPayment");
cy.intercept("PATCH", "/api/orders/*").as("confirmOrder");
cy.visit("/cart");
});
it("places an order through three sequential API calls", () => {
cy.get("[data-testid='checkout-btn']").click();
cy.wait("@createOrder").then((i) => {
expect(i.response?.statusCode).to.equal(201);
expect(i.response?.body).to.have.property("id");
});
cy.get("[data-testid='card-number']").type("4242424242424242");
cy.get("[data-testid='pay-btn']").click();
cy.wait("@processPayment").its("response.statusCode").should("eq", 200);
cy.get("[data-testid='confirm-btn']").click();
cy.wait("@confirmOrder").then((i) => {
expect(i.request.body).to.have.property("status", "confirmed");
expect(i.response?.statusCode).to.equal(200);
});
cy.get("[data-testid='confirmation']")
.should("contain", "Thank you for your order");
});
});Three intercepts registered up front. Three user actions. Three aliased waits, each gated on the previous step completing. The test runs as fast as the network allows — and asserts both that each request fired and that each response was right.
The aliased-wait timeline
Step 1 of 5
Register intercepts
Three cy.intercept(...).as('alias') calls in beforeEach. Cypress installs listeners for createOrder, processPayment, confirmOrder.
⚠️ Common mistakes
- Calling
cy.wait("@alias")twice without a second matching request happening. Eachcy.wait("@alias")consumes one occurrence. If you call it twice but the page only fired one request, the second wait hangs and times out. When you wait twice on the same alias, make sure the action between waits actually triggered another matching request. - Aliasing inside the action callback instead of before it. A pattern like
cy.get(...).click().then(() => cy.intercept(...).as("foo"))registers the intercept after the click that triggers the request. The intercept never fires. Always register inbeforeEach(or before the action), then act, then wait. - Mixing
cy.wait(2000)withcy.wait("@alias"). New engineers often hedge — they putcy.wait(2000)in and the aliased wait, "just in case." Drop the fixed wait. The alias is the entire mechanism; adding a sleep beside it does nothing useful and breaks the speed argument for using aliases at all.
🎯 Practice task
Build an aliased-wait test for a real multi-step flow. 25-30 minutes.
- In your scaffolded project, target a public CRUD demo such as
https://jsonplaceholder.typicode.com(it accepts POST/PUT/DELETE that return realistic responses without persisting). SetbaseUrlaccordingly. - Create
cypress/e2e/aliases.cy.tsand write a test that:- Registers three intercepts in
beforeEachforGET /posts,POST /posts, andDELETE /posts/*— alias them@listPosts,@createPost,@deletePost. - In a single
itblock, fires those three requests (you can do it viacy.requestfor now if your test app doesn't have a UI for them — but the assertion pattern is identical for app-driven requests). - Waits on each alias and asserts the correct status code at each step.
- Registers three intercepts in
- Pagination drill — visit a page that loads ten items per fetch (or stub a single intercept that fires multiple times). Click "Load more" three times and
cy.wait("@listPosts")after each click. Assert the visible item count grows by 10 each time. - Index into past requests — after the three pagination waits, use
cy.get("@listPosts.all").should("have.length", 4)to confirm the load-more triggered four total fetches (initial + 3 clicks). Inspectcy.get("@listPosts.0")and assert the first response was page 1. - Fast vs sleep comparison — pick an aliased-wait test, replicate it with
cy.wait(5000)instead, and run both 10 times. Note the duration delta. The aliased version should be 5–8x faster. - Stretch: wire a
cy.wait("@createPost", { timeout: 15000 })on an endpoint you've stubbed withdelay: 8000. Confirm it passes. Drop thetimeoutoption (defaulting back to 5000ms) and confirm it now times out. This calibrates the per-call timeout pattern in your head.
You now have aliases, intercepts, and stubs — the full network toolkit for the browser side of testing. The next lesson swaps to the other side: cy.request, the command for making your own HTTP calls from the test, used for setup, login, and direct API testing.