Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ _Released 11/18/2025 (PENDING)_

- Fixed an issue where [`cy.wrap()`](https://docs.cypress.io/api/commands/wrap) would cause infinite recursion and freeze the Cypress App when called with objects containing circular references. Fixes [#24715](https://github.com/cypress-io/cypress/issues/24715). Addressed in [#32917](https://github.com/cypress-io/cypress/pull/32917).
- Fixed an issue where top changes on test retries could cause attempt numbers to show up more than one time in the reporter and cause attempts to be lost in Test Replay. Addressed in [#32888](https://github.com/cypress-io/cypress/pull/32888).
- Fixed an issue where the browser will freeze when Cypress intercepts a synchronous request and a routeHandler is used. Fixes [#32874](https://github.com/cypress-io/cypress/issues/32874). Addressed in [#32925](https://github.com/cypress-io/cypress/pull/32925).

**Misc:**

Expand Down
2 changes: 1 addition & 1 deletion packages/net-stubbing/lib/server/intercepted-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ export class InterceptedRequest {

const _emit = () => emit(this.socket, eventName, eventFrame)

if (!subscription.await) {
if (!subscription.await || this.req.isSyncRequest) {
_emit()

return
Expand Down
5 changes: 5 additions & 0 deletions packages/proxy/lib/http/request-middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,16 @@ const ExtractCypressMetadataHeaders: RequestMiddleware = function () {

this.req.isAUTFrame = !!this.req.headers['x-cypress-is-aut-frame']
this.req.isFromExtraTarget = !!this.req.headers['x-cypress-is-from-extra-target']
this.req.isSyncRequest = !!this.req.headers['x-cypress-is-sync-request']

if (this.req.headers['x-cypress-is-aut-frame']) {
delete this.req.headers['x-cypress-is-aut-frame']
}

if (this.req.headers['x-cypress-is-sync-request']) {
delete this.req.headers['x-cypress-is-sync-request']
}

span?.setAttributes({
isAUTFrame: this.req.isAUTFrame,
isFromExtraTarget: this.req.isFromExtraTarget,
Expand Down
6 changes: 6 additions & 0 deletions packages/proxy/lib/http/response-middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,12 @@ const MaybeCopyCookiesFromIncomingRes: ResponseMiddleware = async function () {
return this.next()
}

if (this.req.isSyncRequest) {
span?.end()

return this.next()
}

// we want to set the cookies via automation so they exist in the browser
// itself. however, firefox will hang if we try to use the extension
// to set cookies on a url that's in-flight, so we send the cookies down to
Expand Down
1 change: 1 addition & 0 deletions packages/proxy/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export type CypressIncomingRequest = Request & {
* Stack-ordered list of `cy.intercept()`s matching this request.
*/
matchingRoutes?: BackendRoute[]
isSyncRequest: boolean
}

export type RequestCredentialLevel = 'same-origin' | 'include' | 'omit' | boolean
Expand Down
12 changes: 12 additions & 0 deletions packages/runner/injection/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,15 @@ Cypress.on('app:timers:pause', timers.pause)
timers.wrap()

Cypress.action('app:window:before:load', window)

const originalXmlHttpRequestOpen = window.XMLHttpRequest.prototype.open

window.XMLHttpRequest.prototype.open = function (...args) {
const result = originalXmlHttpRequestOpen.apply(this, args)

if (args[2] === false) {
this.setRequestHeader('x-cypress-is-sync-request', 'true')
}

return result
}
37 changes: 25 additions & 12 deletions packages/runner/injection/patches/xmlHttpRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,35 @@ export const patchXmlHttpRequest = (window: Window) => {
// we need to store a reference here to what we need in the send method
this._url = captureFullRequestUrl(args[1], window)
} finally {
return originalXmlHttpRequestOpen.apply(this, args as any)
const result = originalXmlHttpRequestOpen.apply(this, args as any)

if (args[2] === false) {
this.setRequestHeader('x-cypress-is-sync-request', 'true')
this._isSyncRequest = true
}

return result
}
}

window.XMLHttpRequest.prototype.send = async function (...args) {
try {
// if the option is specified, communicate it to the the server to the proxy can make the request aware if it needs to potentially apply cross origin cookies
// if the option isn't set, we can imply the default as we know the "resourceType" in the proxy
await requestSentWithCredentials({
url: this._url,
resourceType: 'xhr',
credentialStatus: this.withCredentials,
})
} finally {
// if our internal logic errors for whatever reason, do NOT block the end user and continue the request
window.XMLHttpRequest.prototype.send = function (...args) {
if (this._isSyncRequest) {
return originalXmlHttpRequestSend.apply(this, args)
}

return (async () => {
try {
// if the option is specified, communicate it to the the server to the proxy can make the request aware if it needs to potentially apply cross origin cookies
// if the option isn't set, we can imply the default as we know the "resourceType" in the proxy
await requestSentWithCredentials({
url: this._url,
resourceType: 'xhr',
credentialStatus: this.withCredentials,
})
} finally {
// if our internal logic errors for whatever reason, do NOT block the end user and continue the request
return originalXmlHttpRequestSend.apply(this, args)
}
})()
}
}