Cypress tests often become flaky when developers assume cy.wait('@alias') waits for every new request. It doesn’t. Aliases capture only the first match, so later waits may resolve instantly. The fix: re-intercept before each occurrence or use times: 1 to create one-shot intercepts that “consume” themselves. But the real solution is avoiding network waits altogether. Instead, rely on user-visible, accessible UI states (spinners, aria-busy, disabled buttons, status messages). This makes tests stable, realistic, and far more reliable than waiting on network events.Cypress tests often become flaky when developers assume cy.wait('@alias') waits for every new request. It doesn’t. Aliases capture only the first match, so later waits may resolve instantly. The fix: re-intercept before each occurrence or use times: 1 to create one-shot intercepts that “consume” themselves. But the real solution is avoiding network waits altogether. Instead, rely on user-visible, accessible UI states (spinners, aria-busy, disabled buttons, status messages). This makes tests stable, realistic, and far more reliable than waiting on network events.

Achieving Reliable E2E Tests in Cypress: Overcome cy.wait Pitfalls

2025/11/26 13:14

Cypress gives frontend engineers a superpower: the ability to write E2E tests that watch our app behave just like a real user would. But with great power comes… well, a lot of subtle flakiness if you’re not careful.

The cy.wait Illusion: What's Really Happening

The scenario is simple: you have a component that loads data, and after a user action, it loads new data using the same API endpoint. To ensure the new data has arrived, you intercept the request and then use cy.wait('@requestAlias') multiple times.

// A common, flawed approach: cy.intercept('GET', '/api/items/*', { fixture: 'item-1' }).as('getItems'); cy.visit('/items'); // 1. Wait for the initial load cy.wait('@getItems'); // ... User performs an action that triggers the SAME request ... // 2. Wait for the second load cy.wait('@getItems'); // <-- THIS IS THE PROBLEM

The Flaw

Cypress's cy.intercept logic is designed to capture a single match for an alias. When you call cy.wait('@getItems') for the first time, it finds the initial request, waits for its resolution, and then the alias is fulfilled.

When you call cy.wait('@getItems') a second time, Cypress does not reset the listener. Instead, it checks if a request has already been resolved with that alias. Because the first request has resolved, the second cy.wait command resolves immediately, without waiting for the new network call to finish. Your test is now racing against the network, not waiting for it.

Fix #1: Re-intercept before each expected request

(Works, explicit, but verbose)

cy.intercept('GET', '/api/items').as('getItems_1') cy.get('[data-testid=refresh]').click() cy.wait('@getItems_1') cy.intercept('GET', '/api/items').as('getItems_2') cy.get('[data-testid=load-more]').click() cy.wait('@getItems_2')

Clear, deterministic, but repetitive.

Fix #2: Use times: 1 to force Cypress to “consume” intercepts

(Cleaner: Cypress forgets the intercept after one match)

This is the missing tool many engineers don’t realize exists.

cy.intercept({ method: 'GET', pathname: '/api/items', times: 1 }).as('getItems') // trigger request 1 cy.get('[data-testid=refresh]').click() cy.wait('@getItems') cy.intercept({ method: 'GET', pathname: '/api/items', times: 1 }).as('getItems') // trigger request 2 cy.get('[data-testid=load-more]').click() cy.wait('@getItems')

Why this works:

  • times: 1 means Cypress removes the intercept after a single matching request
  • Re-declaring the intercept creates a fresh listener
  • Each cy.wait('@getItems') now truly waits for the next occurrence

This technique gives you explicit, occurrence-specific intercepts without alias clutter. For tests that must assert network behavior (payloads, headers, error flows), it’s a clean and robust pattern.

Fix #3: Stop waiting for requests altogether

(The best fix. UI > network.)

Here’s the golden rule:

That means the most stable tests assert what the user sees:

  • A loading spinner appears → disappears
  • A button becomes disabled → enabled
  • A success message appears when an action is complete.
  • The newly loaded element is now visible in the DOM.

Example with user-visible cues:

cy.get('[data-testid=refresh]').click() cy.get('[data-testid=spinner]').should('exist') cy.get('[data-testid=spinner]').should('not.exist') cy.get('[data-testid=item-list]') .children() .should('have.length.at.least', 1)

No reliance on internal network timing. No alias lifecycle. Zero flake.

Accessibility makes this even more robust

Accessible UI patterns make great Cypress hooks:

aria-busy attribute

<ul data-testid="item-list" aria-busy="true">

Test:

cy.get('[data-testid=item-list]').should('have.attr', 'aria-busy', 'false')

role="status" with live regions

<div role="status" aria-live="polite" data-testid="status"> Loading… </div>

Test:

cy.get('[data-testid=status]').should('contain', 'Loaded 10 items')

Disabled states for actions

cy.get('[data-testid=submit]').should('be.disabled') cy.get('[data-testid=submit]').should('not.be.disabled')

These patterns aid screen reader users and produce stable, deterministic E2E tests.

When waiting for requests is appropriate

There ARE valid scenarios:

  • Asserting payloads or query params
  • Mocking backend responses
  • Validating request ordering
  • Verifying retry logic
  • Testing error handling flows

For those cases: Combine times: 1 with explicit, fresh intercepts defined right before triggers.

For other cases: the test should rely on the UI state.

A combined real-world example

(Network + UI, the best of both worlds)

// UI-driven loading signal cy.get('[data-testid=create]').click() cy.get('[data-testid=spinner]').should('exist') // Network contract check cy.intercept({ method: 'POST', pathname: '/api/items', times: 1 }).as('postItem') cy.get('[data-testid=create]').click() cy.wait('@postItem') .its('request.body') .should('deep.include', { title: 'New item' }) // Final user-visible assertion cy.get('[data-testid=status]').should('contain', 'Item created')

The network part is accurate. The UI part is resilient. The test is rock-solid.

Final checklist

For accessible, deterministic, non-flaky Cypress tests

  • Prefer user-visible UI state, not network events
  • Use aria-busy, role="status", aria-live, and disabled states
  • When waiting for requests:
  • Re-intercept before each occurrence, OR
  • Use times: 1 to auto-expire the intercept
  • Avoid global, long-lived intercepts
  • Never assume cy.wait('@alias') waits “for the next request”
  • Make loading and completion states accessible (good for tests, good for users)

\

Market Opportunity
Threshold Logo
Threshold Price(T)
$0.00937
$0.00937$0.00937
-3.50%
USD
Threshold (T) Live Price Chart
Disclaimer: The articles reposted on this site are sourced from public platforms and are provided for informational purposes only. They do not necessarily reflect the views of MEXC. All rights remain with the original authors. If you believe any content infringes on third-party rights, please contact [email protected] for removal. MEXC makes no guarantees regarding the accuracy, completeness, or timeliness of the content and is not responsible for any actions taken based on the information provided. The content does not constitute financial, legal, or other professional advice, nor should it be considered a recommendation or endorsement by MEXC.

You May Also Like

The Channel Factories We’ve Been Waiting For

The Channel Factories We’ve Been Waiting For

The post The Channel Factories We’ve Been Waiting For appeared on BitcoinEthereumNews.com. Visions of future technology are often prescient about the broad strokes while flubbing the details. The tablets in “2001: A Space Odyssey” do indeed look like iPads, but you never see the astronauts paying for subscriptions or wasting hours on Candy Crush.  Channel factories are one vision that arose early in the history of the Lightning Network to address some challenges that Lightning has faced from the beginning. Despite having grown to become Bitcoin’s most successful layer-2 scaling solution, with instant and low-fee payments, Lightning’s scale is limited by its reliance on payment channels. Although Lightning shifts most transactions off-chain, each payment channel still requires an on-chain transaction to open and (usually) another to close. As adoption grows, pressure on the blockchain grows with it. The need for a more scalable approach to managing channels is clear. Channel factories were supposed to meet this need, but where are they? In 2025, subnetworks are emerging that revive the impetus of channel factories with some new details that vastly increase their potential. They are natively interoperable with Lightning and achieve greater scale by allowing a group of participants to open a shared multisig UTXO and create multiple bilateral channels, which reduces the number of on-chain transactions and improves capital efficiency. Achieving greater scale by reducing complexity, Ark and Spark perform the same function as traditional channel factories with new designs and additional capabilities based on shared UTXOs.  Channel Factories 101 Channel factories have been around since the inception of Lightning. A factory is a multiparty contract where multiple users (not just two, as in a Dryja-Poon channel) cooperatively lock funds in a single multisig UTXO. They can open, close and update channels off-chain without updating the blockchain for each operation. Only when participants leave or the factory dissolves is an on-chain transaction…
Share
BitcoinEthereumNews2025/09/18 00:09
SOLANA NETWORK Withstands 6 Tbps DDoS Without Downtime

SOLANA NETWORK Withstands 6 Tbps DDoS Without Downtime

The post SOLANA NETWORK Withstands 6 Tbps DDoS Without Downtime appeared on BitcoinEthereumNews.com. In a pivotal week for crypto infrastructure, the Solana network
Share
BitcoinEthereumNews2025/12/16 20:44
Why The Green Bay Packers Must Take The Cleveland Browns Seriously — As Hard As That Might Be

Why The Green Bay Packers Must Take The Cleveland Browns Seriously — As Hard As That Might Be

The post Why The Green Bay Packers Must Take The Cleveland Browns Seriously — As Hard As That Might Be appeared on BitcoinEthereumNews.com. Jordan Love and the Green Bay Packers are off to a 2-0 start. Getty Images The Green Bay Packers are, once again, one of the NFL’s better teams. The Cleveland Browns are, once again, one of the league’s doormats. It’s why unbeaten Green Bay (2-0) is a 8-point favorite at winless Cleveland (0-2) Sunday according to betmgm.com. The money line is also Green Bay -500. Most expect this to be a Packers’ rout, and it very well could be. But Green Bay knows taking anyone in this league for granted can prove costly. “I think if you look at their roster, the paper, who they have on that team, what they can do, they got a lot of talent and things can turn around quickly for them,” Packers safety Xavier McKinney said. “We just got to kind of keep that in mind and know we not just walking into something and they just going to lay down. That’s not what they going to do.” The Browns certainly haven’t laid down on defense. Far from. Cleveland is allowing an NFL-best 191.5 yards per game. The Browns gave up 141 yards to Cincinnati in Week 1, including just seven in the second half, but still lost, 17-16. Cleveland has given up an NFL-best 45.5 rushing yards per game and just 2.1 rushing yards per attempt. “The biggest thing is our defensive line is much, much improved over last year and I think we’ve got back to our personality,” defensive coordinator Jim Schwartz said recently. “When we play our best, our D-line leads us there as our engine.” The Browns rank third in the league in passing defense, allowing just 146.0 yards per game. Cleveland has also gone 30 straight games without allowing a 300-yard passer, the longest active streak in the NFL.…
Share
BitcoinEthereumNews2025/09/18 00:41