Skip to content

feat: capture cy.request lifecycle for Test Replay (#30575)#33732

Open
ryanthemanuel wants to merge 5 commits into
developfrom
feat/cy-request-test-replay-capture
Open

feat: capture cy.request lifecycle for Test Replay (#30575)#33732
ryanthemanuel wants to merge 5 commits into
developfrom
feat/cy-request-test-replay-capture

Conversation

@ryanthemanuel
Copy link
Copy Markdown
Collaborator

@ryanthemanuel ryanthemanuel commented May 3, 2026

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<!-- Thanks for contributing! PLEASE...

Additional details


Note

Medium Risk
Adds new protocol event hooks and instruments Node-side cy.request networking 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.request so Test Replay can display these requests alongside browser/CDP traffic.

The driver now threads correlation metadata (logId, runnableId, attempt, initiator) via requestOpts.protocolMetadata, and the server Request layer emits cyRequestWillBeSent (including per-redirect-hop redirectResponse), cyRequestResponseReceived (with an untruncated response Readable stream + timing/attempt counts), and cyRequestFailed on transport errors.

Extends protocol types and ProtocolManager to 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

ryanthemanuel and others added 2 commits May 3, 2026 11:11
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>
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ 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)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 374dac8. Configure here.


responseHookFired = true

const streamed = bodyToReadable(resp.body)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 374dac8. Configure here.

@cypress
Copy link
Copy Markdown

cypress Bot commented May 3, 2026

cypress    Run #71037

Run Properties:  status check passed Passed #71037  •  git commit 9be827ba5b: Merge remote-tracking branch 'origin/develop' into feat/cy-request-test-replay-c...
Project cypress
Branch Review feat/cy-request-test-replay-capture
Run status status check passed Passed #71037
Run duration 18m 40s
Commit git commit 9be827ba5b: Merge remote-tracking branch 'origin/develop' into feat/cy-request-test-replay-c...
Committer Ryan Manuel
View all properties for this run ↗︎

Test results
Tests that failed  Failures 0
Tests that were flaky  Flaky 6
Tests that did not run due to a developer annotating a test with .skip  Pending 1112
Tests that did not run due to a failure in a mocha hook  Skipped 0
Tests that passed  Passing 27307
View all changes introduced in this branch ↗︎
UI Coverage  62.67%
  Untested elements 28  
  Tested elements 47  
Accessibility  99.02%
  Failed rules  0 critical   3 serious   1 moderate   0 minor
  Failed elements 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
@ryanthemanuel ryanthemanuel requested a review from a team as a code owner May 22, 2026 18:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant