Skip to content

Commit 70248ab

Browse files
fix: prerequest correlation for various retried and cached requests (#27771)
1 parent 86d8b96 commit 70248ab

File tree

7 files changed

+101
-5
lines changed

7 files changed

+101
-5
lines changed

cli/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ _Released 09/12/2023 (PENDING)_
99

1010
**Bugfixes:**
1111

12+
- Edge cases where `cy.intercept()` would not properly intercept and asset response bodies would not properly be captured for test replay have been addressed. Addressed in [#27771](https://github.com/cypress-io/cypress/issues/27771).
1213
- Fixed an issue where `enter`, `keyup`, and `space` events where not triggering `click` events properly in some versions of Firefox. Addressed in [#27715](https://github.com/cypress-io/cypress/pull/27715).
1314

1415
**Dependency Updates:**

packages/proxy/lib/http/response-middleware.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,18 @@ const stringifyFeaturePolicy = (policy: any): string => {
145145
return pairs.map((directive) => directive.join(' ')).join('; ')
146146
}
147147

148+
const requestIdRegEx = /^(.*)-retry-([\d]+)$/
149+
const getOriginalRequestId = (requestId: string) => {
150+
let originalRequestId = requestId
151+
const match = requestIdRegEx.exec(requestId)
152+
153+
if (match) {
154+
[, originalRequestId] = match
155+
}
156+
157+
return originalRequestId
158+
}
159+
148160
const LogResponse: ResponseMiddleware = function () {
149161
this.debug('received response %o', {
150162
browserPreRequest: _.pick(this.req.browserPreRequest, 'requestId'),
@@ -674,8 +686,10 @@ const ClearCyInitialCookie: ResponseMiddleware = function () {
674686
const MaybeEndWithEmptyBody: ResponseMiddleware = function () {
675687
if (httpUtils.responseMustHaveEmptyBody(this.req, this.incomingRes)) {
676688
if (this.protocolManager && this.req.browserPreRequest?.requestId) {
689+
const requestId = getOriginalRequestId(this.req.browserPreRequest.requestId)
690+
677691
this.protocolManager.responseEndedWithEmptyBody({
678-
requestId: this.req.browserPreRequest.requestId,
692+
requestId,
679693
isCached: this.incomingRes.statusCode === 304,
680694
})
681695
}
@@ -783,9 +797,11 @@ const MaybeRemoveSecurity: ResponseMiddleware = function () {
783797

784798
const GzipBody: ResponseMiddleware = async function () {
785799
if (this.protocolManager && this.req.browserPreRequest?.requestId) {
800+
const requestId = getOriginalRequestId(this.req.browserPreRequest.requestId)
801+
786802
const span = telemetry.startSpan({ name: 'gzip:body:protocol-notification', parentSpan: this.resMiddlewareSpan, isVerbose })
787803
const resultingStream = this.protocolManager.responseStreamReceived({
788-
requestId: this.req.browserPreRequest.requestId,
804+
requestId,
789805
responseHeaders: this.incomingRes.headers,
790806
isAlreadyGunzipped: this.isGunzipped,
791807
responseStream: this.incomingResStream,

packages/proxy/lib/http/util/prerequests.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ export class PreRequests {
138138

139139
removePending (requestId: string) {
140140
this.pendingPreRequests.removeMatching(({ browserPreRequest }) => {
141-
return browserPreRequest.requestId !== requestId
141+
return (browserPreRequest.requestId.includes('-retry-') && !browserPreRequest.requestId.startsWith(`${requestId}-`)) || (!browserPreRequest.requestId.includes('-retry-') && browserPreRequest.requestId !== requestId)
142142
})
143143
}
144144

packages/proxy/test/unit/http/response-middleware.spec.ts

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1847,14 +1847,14 @@ describe('http/response-middleware', function () {
18471847
})
18481848
})
18491849

1850-
it('calls responseEndedWithEmptyBody on protocolManager if protocolManager present and request is correlated and response must have empty body and response is not cached', function () {
1850+
it('calls responseEndedWithEmptyBody on protocolManager if protocolManager present and retried request is correlated and response must have empty body and response is not cached', function () {
18511851
prepareContext({
18521852
protocolManager: {
18531853
responseEndedWithEmptyBody: responseEndedWithEmptyBodyStub,
18541854
},
18551855
req: {
18561856
browserPreRequest: {
1857-
requestId: '123',
1857+
requestId: '123-retry-1',
18581858
},
18591859
},
18601860
incomingRes: {
@@ -2285,6 +2285,47 @@ describe('http/response-middleware', function () {
22852285
})
22862286
})
22872287

2288+
it('calls responseStreamReceived on protocolManager if protocolManager present and retried request is correlated', function () {
2289+
const stream = Readable.from(['foo'])
2290+
const headers = { 'content-encoding': 'gzip' }
2291+
const res = {
2292+
on: (event, listener) => {},
2293+
off: (event, listener) => {},
2294+
}
2295+
2296+
prepareContext({
2297+
protocolManager: {
2298+
responseStreamReceived: responseStreamReceivedStub,
2299+
},
2300+
req: {
2301+
browserPreRequest: {
2302+
requestId: '123-retry-1',
2303+
},
2304+
},
2305+
res,
2306+
incomingRes: {
2307+
headers,
2308+
},
2309+
isGunzipped: true,
2310+
incomingResStream: stream,
2311+
})
2312+
2313+
return testMiddleware([GzipBody], ctx)
2314+
.then(() => {
2315+
expect(responseStreamReceivedStub).to.be.calledWith(
2316+
sinon.match(function (actual) {
2317+
expect(actual.requestId).to.equal('123')
2318+
expect(actual.responseHeaders).to.equal(headers)
2319+
expect(actual.isAlreadyGunzipped).to.equal(true)
2320+
expect(actual.responseStream).to.equal(stream)
2321+
expect(actual.res).to.equal(res)
2322+
2323+
return true
2324+
}),
2325+
)
2326+
})
2327+
})
2328+
22882329
it('does not call responseStreamReceived on protocolManager if protocolManager present and request is not correlated', function () {
22892330
const stream = Readable.from(['foo'])
22902331
const headers = { 'content-encoding': 'gzip' }

packages/proxy/test/unit/http/util/prerequests.spec.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,19 @@ describe('http/util/prerequests', () => {
9090

9191
expectPendingCounts(0, 2)
9292
})
93+
94+
it('removes a pre-request with a matching requestId with retries', () => {
95+
preRequests.addPending({ requestId: '1234', url: 'foo', method: 'GET' } as BrowserPreRequest)
96+
preRequests.addPending({ requestId: '1235', url: 'foo', method: 'GET' } as BrowserPreRequest)
97+
preRequests.addPending({ requestId: '1235-retry-1', url: 'foo', method: 'GET' } as BrowserPreRequest)
98+
preRequests.addPending({ requestId: '1235-retry-2', url: 'foo', method: 'GET' } as BrowserPreRequest)
99+
preRequests.addPending({ requestId: '1235-retry-3', url: 'foo', method: 'GET' } as BrowserPreRequest)
100+
preRequests.addPending({ requestId: '1236', url: 'foo', method: 'GET' } as BrowserPreRequest)
101+
102+
expectPendingCounts(0, 6)
103+
104+
preRequests.removePending('1235')
105+
106+
expectPendingCounts(0, 2)
107+
})
93108
})

packages/server/lib/browsers/cdp_automation.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,12 @@ export class CdpAutomation implements CDPClient {
251251
}
252252

253253
private onResponseReceived = (params: Protocol.Network.ResponseReceivedEvent) => {
254+
if (params.response.fromDiskCache) {
255+
this.automation.onRequestServedFromCache?.(params.requestId)
256+
257+
return
258+
}
259+
254260
const browserResponseReceived: BrowserResponseReceived = {
255261
requestId: params.requestId,
256262
status: params.response.status,

packages/server/test/unit/browsers/cdp_automation_spec.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,23 @@ context('lib/browsers/cdp_automation', () => {
181181
},
182182
)
183183
})
184+
185+
it('cleans up prerequests when response is cached from disk', function () {
186+
const browserResponseReceived = {
187+
requestId: '0',
188+
response: {
189+
status: 200,
190+
headers: {},
191+
fromDiskCache: true,
192+
},
193+
}
194+
195+
this.onFn
196+
.withArgs('Network.responseReceived')
197+
.yield(browserResponseReceived)
198+
199+
expect(this.automation.onRequestEvent).not.to.have.been.called
200+
})
184201
})
185202

186203
describe('.onRequestServedFromCache', function () {

0 commit comments

Comments
 (0)