diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 35c52b9fce55..83c36686124b 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -7,6 +7,7 @@ _Released 10/03/2023 (PENDING)_ - Fixed an issue where requests were correlated in the wrong order in the proxy. This could cause an issue where the wrong request is used for `cy.intercept` or assets (e.g. stylesheets or images) may not properly be available in Test Replay. Addressed in [#27892](https://github.com/cypress-io/cypress/pull/27892). - Fixed an issue where a crashed Chrome renderer can cause the Test Replay recorder to hang. Addressed in [#27909](https://github.com/cypress-io/cypress/pull/27909). +- Fixed an issue where multiple responses yielded from calls to `cy.wait()` would sometimes be out of order. Fixes [#27337](https://github.com/cypress-io/cypress/issues/27337). ## 13.3.0 diff --git a/packages/driver/cypress/e2e/commands/waiting.cy.js b/packages/driver/cypress/e2e/commands/waiting.cy.js index 7291e3f195b7..9edeb9e5d652 100644 --- a/packages/driver/cypress/e2e/commands/waiting.cy.js +++ b/packages/driver/cypress/e2e/commands/waiting.cy.js @@ -760,6 +760,77 @@ describe('src/cy/commands/waiting', () => { expect(xhr2.response.body).to.deep.eq(resp2) }) }) + + it('all responses returned in correct order - unique aliases', () => { + const resp1 = { value: 'alpha' } + const resp2 = { value: 'beta' } + const resp3 = { value: 'gamma' } + const resp4 = { value: 'delta' } + const resp5 = { value: 'epsilon' } + + cy.intercept(/alpha/, resp1).as('getAlpha') + cy.intercept(/beta/, resp2).as('getBeta') + cy.intercept(/gamma/, resp3).as('getGamma') + cy.intercept(/delta/, resp4).as('getDelta') + cy.intercept(/epsilon/, resp5).as('getEpsilon') + + cy.window().then((win) => { + xhrGet(win, '/epsilon') + xhrGet(win, '/beta') + xhrGet(win, '/gamma') + xhrGet(win, '/delta') + xhrGet(win, '/alpha') + + return null + }) + + cy.wait(['@getAlpha', '@getBeta', '@getGamma', '@getDelta', '@getEpsilon']).then((responses) => { + expect(responses[0]?.response?.body.value).to.eq('alpha') + expect(responses[1]?.response?.body.value).to.eq('beta') + expect(responses[2]?.response?.body.value).to.eq('gamma') + expect(responses[3]?.response?.body.value).to.eq('delta') + expect(responses[4]?.response?.body.value).to.eq('epsilon') + }) + }) + + it('all responses returned in correct order - duplicate aliases', () => { + let alphaCount = 0 + let betaCount = 0 + let gammaCount = 0 + + cy.intercept(/alpha/, (req) => { + req.reply({ value: `alpha-${alphaCount}` }) + alphaCount++ + }).as('getAlpha') + + cy.intercept(/beta/, (req) => { + req.reply({ value: `beta-${betaCount}` }) + betaCount++ + }).as('getBeta') + + cy.intercept(/gamma/, (req) => { + req.reply({ value: `gamma-${gammaCount}` }) + gammaCount++ + }).as('getGamma') + + cy.window().then((win) => { + xhrGet(win, '/alpha') + xhrGet(win, '/beta') + xhrGet(win, '/gamma') + xhrGet(win, '/alpha') + xhrGet(win, '/gamma') + + return null + }) + + cy.wait(['@getGamma', '@getBeta', '@getGamma', '@getAlpha', '@getAlpha']).then((responses) => { + expect(responses[0]?.response?.body.value).to.eq('gamma-0') + expect(responses[1]?.response?.body.value).to.eq('beta-0') + expect(responses[2]?.response?.body.value).to.eq('gamma-1') + expect(responses[3]?.response?.body.value).to.eq('alpha-0') + expect(responses[4]?.response?.body.value).to.eq('alpha-1') + }) + }) }) describe('multiple separate alias waits', () => { diff --git a/packages/driver/src/cy/commands/waiting.ts b/packages/driver/src/cy/commands/waiting.ts index 538698b4745f..45c50b62e44f 100644 --- a/packages/driver/src/cy/commands/waiting.ts +++ b/packages/driver/src/cy/commands/waiting.ts @@ -250,7 +250,24 @@ export default (Commands, Cypress, cy, state) => { .then((responses) => { // if we only asked to wait for one alias // then return that, else return the array of xhr responses - const ret = responses.length === 1 ? responses[0] : responses + const ret = responses.length === 1 ? responses[0] : ((resp) => { + const respMap = new Map() + const respSeq = resp.map((r) => r.routeId) + + // responses are sorted in the order the user specified them. if there are multiples of the same + // alias awaited, they're sorted in execution order + resp.sort((a, b) => { + // sort responses based on browser request ID + const requestIdSuffixA = a.browserRequestId.split('.').length > 1 ? a.browserRequestId.split('.')[1] : a.browserRequestId + const requestIdSuffixB = b.browserRequestId.split('.').length > 1 ? b.browserRequestId.split('.')[1] : b.browserRequestId + + return parseInt(requestIdSuffixA) < parseInt(requestIdSuffixB) ? -1 : 1 + }).forEach((r) => { + respMap.get(r.routeId)?.push(r) ?? respMap.set(r.routeId, [r]) + }) + + return respSeq.map((routeId) => respMap.get(routeId)?.shift()) + })(responses) if (log) { log.set('consoleProps', () => {