feat: capture cy.request lifecycle for Test Replay (#30575)#33732
feat: capture cy.request lifecycle for Test Replay (#30575)#33732ryanthemanuel wants to merge 5 commits into
Conversation
Adds three protocol hooks — cyRequestWillBeSent, cyRequestResponseReceived, cyRequestFailed — so cy.request calls (Node-side HTTP, no CDP coverage) flow into capture-protocol and surface in Test Replay's network panel alongside browser-issued requests. Plumbing: - packages/types/src/protocol.ts: new option types + AppCaptureProtocolCommon method declarations. Redirect chains carry a CDP-style `redirectResponse` field on each hop. - packages/server/lib/cloud/protocol.ts: ProtocolManager forwarding methods + `typeof === 'function'` guard on invokeSync/invokeAsync so older protocol scripts paired with newer Cypress no-op cleanly (no telemetry noise). - packages/server/lib/request.ts: sendPromise instrumentation. Generates a namespaced `cyrequest_<N>` requestId once at entry. Response body wraps resp.body in a Readable so capture-protocol consumes it via processAssetStream (no double-buffering — matches CDP behavior). Per redirect hop, fires cyRequestWillBeSent again with the prior hop's status and URL on `redirectResponse` — same shape CDP emits. - packages/server/lib/server-base.ts: setProtocolManager propagates to the Request instance. - packages/driver/src/cy/commands/request.ts: threads logId, runnableId, attempt, and initiator on requestOpts.protocolMetadata so capture-protocol can correlate the network row with the matching command-log entry. Tests: - packages/server/test/unit/cloud/protocol_spec.ts: dispatch + missing-method guard regression test. - packages/server/test/unit/request_spec.ts: success path with stream assertions, 4xx-with-failOnStatusCode:false, redirect chain firing one WBS per hop with redirectResponse, transport failure re-throw + attemptsUsed. - system-tests/projects/protocol/cypress/e2e/cy-request.cy.js: new fixture spec exercising success + non-2xx scenarios. - system-tests/test/protocol_spec.js: dedicated cy.request validation test + normalizeEvents extended to fuzz body hashes, wallTime, durationMs, volatile response headers, requestId, and logId. - system-tests/lib/protocol-stubs/protocolStub.ts + cloud/protocol stub fixture: implement the three new methods (stub strips non-serializable request body / response stream from recorded events). No body-size caps or per-feature privacy kill-switches are added — both were considered and rejected in favor of CDP parity (no CDP response cap) and the planned holistic protocol-wide privacy system. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 374dac8. Configure here.
| }) => { | ||
| if (!protocolManager) return | ||
|
|
||
| const normalized = normalizeRequestBody(options.body) |
There was a problem hiding this comment.
Form request body silently lost in protocol capture
Medium Severity
When options.form === true, the existing code deletes options.body and moves it to options.form before send() runs. But fireWillBeSent inside send() calls normalizeRequestBody(options.body), which now sees undefined and reports hasRequestBody: false. Form-encoded POST requests — a common cy.request pattern — will silently have their request body missing from Test Replay's network panel.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 374dac8. Configure here.
|
|
||
| responseHookFired = true | ||
|
|
||
| const streamed = bodyToReadable(resp.body) |
There was a problem hiding this comment.
JSON response body re-serialized with wrong size
Medium Severity
fireResponseReceived calls bodyToReadable(resp.body) after normalizeResponse has already parsed JSON string bodies into JavaScript objects. bodyToReadable then re-serializes them via JSON.stringify, potentially producing different bytes and a different responseBodyOriginalSize than the actual wire content. This breaks CDP parity for JSON responses — the stream content and reported size may not match the real response.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 374dac8. Configure here.
cypress
|
||||||||||||||||||||||||||||||||||||||||
| Project |
cypress
|
| Branch Review |
feat/cy-request-test-replay-capture
|
| Run status |
|
| Run duration | 18m 40s |
| Commit |
|
| Committer | Ryan Manuel |
| View all properties for this run ↗︎ | |
| Test results | |
|---|---|
|
|
0
|
|
|
6
|
|
|
1112
|
|
|
0
|
|
|
27307
|
| View all changes introduced in this branch ↗︎ | |
UI Coverage
62.67%
|
|
|---|---|
|
|
28
|
|
|
47
|
Accessibility
99.02%
|
|
|---|---|
|
|
0 critical
3 serious
1 moderate
0 minor
|
|
|
19
|
Driver-side: only attach protocolMetadata when isProtocolEnabled, matching the pattern used in log.ts, origin/index.ts, and serialization/log.ts. The backend hooks are already gated on protocolManager being set, but the field still reached the test stub on browsers without protocol. Driver test: expectOptionsToBe omits protocolMetadata before deep-equal — the test asserts cy.request argument signature, which is independent of whether the active browser supports capture-protocol. System-test normalizer: handle weak ETags (escaped quotes inside JSON string values), scrub OS-dependent user-agent, strip the "From Node.js Internals:" stack tail and any parsedStack frame whose originalFile is <embedded> or node:* (line/column numbers and function-name shapes shift across Node/libuv builds + operating systems). Snapshot pre-normalized so it matches what runs on Linux CI and macOS produce.
When a setup-pipeline trigger explicitly sets a parameter (e.g. an API trigger passing force-persist-artifacts=true), CircleCI auto-propagates that value to the same-named parameter on the continuation pipeline. Re-emitting it from generate-pipeline-parameters.sh causes the continuation API to reject the request with "Conflicting pipeline parameters." Strip publish-binary-branch and force-persist-artifacts from the continuation JSON and let auto-propagation thread them through. Drops the now-unused env vars in launch-primary-workflow.
…st-replay-capture # Conflicts: # .circleci/config.yml # .circleci/scripts/generate-pipeline-parameters.sh


Adds three protocol hooks — cyRequestWillBeSent, cyRequestResponseReceived,
cyRequestFailed — so cy.request calls (Node-side HTTP, no CDP coverage) flow
into capture-protocol and surface in Test Replay's network panel alongside
browser-issued requests.
Plumbing:
method declarations. Redirect chains carry a CDP-style
redirectResponsefield on each hop.
typeof === 'function'guard on invokeSync/invokeAsync so older protocolscripts paired with newer Cypress no-op cleanly (no telemetry noise).
namespaced
cyrequest_<N>requestId once at entry. Response body wrapsresp.body in a Readable so capture-protocol consumes it via
processAssetStream (no double-buffering — matches CDP behavior). Per
redirect hop, fires cyRequestWillBeSent again with the prior hop's status
and URL on
redirectResponse— same shape CDP emits.Request instance.
attempt, and initiator on requestOpts.protocolMetadata so capture-protocol
can correlate the network row with the matching command-log entry.
Tests:
guard regression test.
assertions, 4xx-with-failOnStatusCode:false, redirect chain firing one WBS
per hop with redirectResponse, transport failure re-throw + attemptsUsed.
spec exercising success + non-2xx scenarios.
normalizeEvents extended to fuzz body hashes, wallTime, durationMs,
volatile response headers, requestId, and logId.
fixture: implement the three new methods (stub strips non-serializable
request body / response stream from recorded events).
No body-size caps or per-feature privacy kill-switches are added — both
were considered and rejected in favor of CDP parity (no CDP response cap)
and the planned holistic protocol-wide privacy system.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com<!-- Thanks for contributing! PLEASE...
Read our contributing guidelines: https://github.com/cypress-io/cypress/blob/develop/CONTRIBUTING.md
Read our Code Review Checklist on coding standards and what needs to be done before a PR can be merged: https://github.com/cypress-io/cypress/blob/develop/CONTRIBUTING.md#Code-Review-Checklist
Mark this PR as "Draft" if it is not ready for review.
Make sure you set the correct base branch based on what packages you're changing: https://github.com/cypress-io/cypress/blob/develop/CONTRIBUTING.md#branches
-->
Closes
Additional details
Note
Medium Risk
Adds new protocol event hooks and instruments Node-side
cy.requestnetworking to emit request/response/failure events (including bodies/streams and redirect hop metadata), which could affect request execution paths and protocol compatibility if incorrect.Overview
Adds capture-protocol hooks for Node-side
cy.requestso Test Replay can display these requests alongside browser/CDP traffic.The driver now threads correlation metadata (
logId,runnableId,attempt,initiator) viarequestOpts.protocolMetadata, and the serverRequestlayer emitscyRequestWillBeSent(including per-redirect-hopredirectResponse),cyRequestResponseReceived(with an untruncated responseReadablestream + timing/attempt counts), andcyRequestFailedon transport errors.Extends protocol types and
ProtocolManagerto forward the new hooks with a forward-compat no-op guard when older protocol scripts lack methods, updates stubs/snapshots, adds unit/system coverage for the new events, and adjusts CircleCI continuation parameter generation to avoid conflicting auto-propagated pipeline parameters.Reviewed by Cursor Bugbot for commit 9be827b. Bugbot is set up for automated code reviews on this repo. Configure here.
Steps to test
How has the user experience changed?
PR Tasks
cypress-documentation?type definitions?