diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index d590b24a28e..ac00c41297a 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -3,6 +3,10 @@ _Released 01/27/2026 (PENDING)_ +**Bugfixes:** + +- Fixed an issue where `cy.wait('@alias')` could time out when the underlying network request was canceled by navigation (e.g., `cy.visit`, `cy.reload`). Fixes [#19326](https://github.com/cypress-io/cypress/issues/19326). + **Misc:** - The icon in the 'Open in IDE' button in the command log is now the correct size. Addresses [#32779](https://github.com/cypress-io/cypress/issues/32779). Addressed in [#33217](https://github.com/cypress-io/cypress/pull/33217). diff --git a/packages/driver/cypress/e2e/commands/navigation.cy.js b/packages/driver/cypress/e2e/commands/navigation.cy.js index 668e76f30a7..aa5d3089f4a 100644 --- a/packages/driver/cypress/e2e/commands/navigation.cy.js +++ b/packages/driver/cypress/e2e/commands/navigation.cy.js @@ -1884,6 +1884,28 @@ describe('src/cy/commands/navigation', () => { }) }) }) + + it('should resolve wait for a request canceled by navigation', () => { + const alias = crypto.randomUUID() + + cy.intercept(/jsonplaceholder.cypress.io/).as(alias) + + cy.visit('https://example.cypress.io/commands/network-requests') + cy.get('.network-btn').click() + + cy.visit('https://example.cypress.io/commands/network-requests') + cy.wait(`@${alias}`).then((interception) => { + const actual = JSON.parse( + JSON.stringify(interception, (_, value) => value), + ) + + cy.wrap(actual).should('deep.equal', { + ...actual, + state: 'Errored', + error: { ...interception.error }, + }) + }) + }) }) // TODO(webkit): fix+unskip for webkit release diff --git a/packages/driver/src/cy/commands/navigation.ts b/packages/driver/src/cy/commands/navigation.ts index 4fab0f6a710..d70f4275e95 100644 --- a/packages/driver/src/cy/commands/navigation.ts +++ b/packages/driver/src/cy/commands/navigation.ts @@ -175,6 +175,18 @@ const pageLoading = (bool, Cypress, state) => { Cypress.action('app:page:loading', bool) } +const markRequestAsCancelled = (request: any) => { + if ( + request && + request.state === 'Received' && + !request.response && + !request.error + ) { + request.state = 'Errored' + request.error = new Error('Request was cancelled due to navigation.') + } +} + const stabilityChanged = async (Cypress, state, config, stable) => { debug('stabilityChanged:', stable) @@ -187,6 +199,23 @@ const stabilityChanged = async (Cypress, state, config, stable) => { return } + // Mark inflight requests as canceled at navigation start. + try { + const routes = state('routes') ?? {} + + _.forEach(routes, ({ requests }) => { + _.forEach(requests, markRequestAsCancelled) + }) + + const aliasedRequests = state('aliasedRequests') ?? [] + + aliasedRequests.forEach(({ request }) => { + markRequestAsCancelled(request) + }) + } catch { + // Ignore errors. Failure to mark requests as canceled is non-critical. + } + // if we purposefully just caused the page to load // (and thus instability) don't log this out if (knownCommandCausedInstability) {