In Cypress 16, why is `req.httpVersion` undefined inside a `cy.intercept()` handler in Chrome?
answer
- Cypress left the connection
- It described the wrong hop
- Not knowable while the request is paused
- Chrome, Chromium and Edge only
- Assert on the body, not the encoding
basics
~20 sBecause Chrome, Chromium and Edge now intercept on the native browser network. The browser negotiates the protocol with your server directly, and that is not known when the request is paused, so Cypress reports no value rather than a wrong one.
solid answer
~50 sAs of Cypress 16, Chrome, Chromium and Edge intercept test traffic **on the native browser network**: the application connects to your server directly and negotiates HTTP/1.1, HTTP/2 or HTTP/3 exactly as it does in production. Before 16, every request went through the legacy network path, which only spoke HTTP/1.1, so `req.httpVersion` was always `'1.1'` — a value that described the hop between the browser and Cypress, not the protocol your server used. With Cypress out of the connection, the negotiated protocol is not known at the moment the request is intercepted, so `req.httpVersion` is `undefined` and `res.httpVersion` is `null` rather than a fabricated value. The same change removes compression headers from an intercepted response: `content-encoding`, `content-length` and `transfer-encoding` are absent, and the body you get is already decoded. Firefox, WebKit and Electron stay on the legacy path and still report `'1.1'`.
code
javascript · 13 lines// Cypress 16: assert on what the application asked for
cy.intercept('GET', '**/api/forecast*', (req) => {
expect(req.method).to.equal('GET')
expect(req.headers).to.have.property('accept')
}).as('forecast')
cy.visit('/dashboard')
cy.wait('@forecast').then(({ response }) => {
// compression headers are not reported on the native browser network
expect(response.headers).to.not.have.property('content-encoding')
expect(response.body.days).to.have.length(7)
})go deeper
Recall that Cypress 16 stopped reporting req.httpVersion in Chrome, Chromium and Edge, and that an assertion on it is the thing to delete rather than to debug.
Be ready to explain who holds the connection on each network path, why an unknown value is reported as absent, and which other transport details went with httpVersion.
An interviewer expects you to migrate a real suite: find the transport assertions, replace them with application-level ones, and gate the few that must survive by browser rather than switching the whole run back.
Own the position that tests assert on application behaviour, not transport metadata, and decide how long a deprecated compatibility switch is allowed to stand in for that work.
## What changed in Cypress 16 Before Cypress 16, every request the application made was routed through the **legacy network path** — the connection Cypress itself held between the browser and your server. That path is how Cypress observed, stubbed and recorded traffic, and it spoke **only HTTP/1.1**. A request your server would have answered over HTTP/2 or HTTP/3 in production was downgraded for the duration of the run. In Cypress 16, **Chrome, Chromium and Edge intercept on the native browser network**. The browser connects to your server itself, negotiates whatever protocol that server supports, and validates your origin's real certificate. `cy.intercept()` still matches, stubs, modifies and waits with the same API, and most suites need no edits — but a handful of *transport* details are no longer Cypress's to report. ## Why the value is absent rather than guessed `req.httpVersion` on the legacy path was always `'1.1'`, and that was honest only in a narrow sense: it described the **browser-to-Cypress hop**, not the protocol your server actually spoke. On the native browser network there is no such hop, and the protocol the browser negotiates with the origin is **not known at the point the request is paused for your handler**. Cypress therefore reports nothing rather than a value that could be wrong: - `req.httpVersion` is `undefined` in Chrome, Chromium and Edge. - `res.httpVersion` is `null` there. - Firefox, WebKit and Electron remain on the legacy network path and still report `'1.1'`. ## The other transport details that went with it The same architectural move removes several adjacent values, all for the same reason — they described the Cypress connection, not your application: - **Compression headers on a response.** `content-encoding`, `content-length` and `transfer-encoding` are not present on an intercepted response. Compression is negotiated between the browser and your server, and the body Cypress hands you is already decoded, so the headers now match the body that accompanies them instead of describing bytes you never saw. - **Request `content-length`.** The browser computes it when it sends the request, which happens *after* your handler runs, so it does not exist yet. Your server still receives an accurate value, and Cypress no longer recalculates it when a handler replaces `req.body`. - **Responses the browser rejects.** If the browser's network stack refuses a response before delivering it, the request is still recorded but `interception.response` is `undefined`; the status and headers stay inside the browser. | Value | Legacy network path | Native browser network | |---|---|---| | `req.httpVersion` | `'1.1'` | `undefined` | | `res.httpVersion` | `'1.1'` | `null` | | Response `content-encoding` | Present (`gzip`, `br`) | Absent | | Request `content-length` | Present | Absent | None of this narrows what `cy.intercept()` can do. Matching, stubbing, modifying a request or response, aliasing and waiting all behave exactly as they did; what disappeared is a small set of read-only values that were only ever true of the connection Cypress used to hold. A suite that never asserted on them upgrades with no edits at all. ## What to assert instead The rule of thumb is simple: **assert on things that describe your application, not on transport metadata.** Those assertions pass on both network paths and in every browser. 1. Replace `req.httpVersion` checks with assertions on `req.method`, `req.url`, `req.headers` or `req.body` — the properties that say what your application asked for. 2. Replace compression-header checks with an assertion on the decoded `response.body`, which is what the header was standing in for anyway. 3. Replace a request `content-length` assertion with an assertion on the body you set. If a cross-browser suite genuinely needs a transport assertion for the browsers still on the legacy path, gate it with `Cypress.isBrowser({ family: 'chromium', name: '!electron' })`. Excluding Electron **by name** is what makes the check match the network path: Electron is Chromium-based, so a `family: 'chromium'` filter alone also matches Electron and puts it on the wrong side of the gate. ## What to remember - The change is about **who holds the connection**, not about `cy.intercept()` losing features. - Absent values are a deliberate choice: no value beats a value that describes the wrong hop. - Chrome, Chromium and Edge are affected; Firefox, WebKit and Electron are not. - `forceHttp1: true` routes every browser back through the legacy path, but it is deprecated at introduction and is a migration aid, not a destination.
- A suite runs Chrome and Firefox and needs one transport assertion. How do you keep both green?Gate it with Cypress.isBrowser({ family: 'chromium', name: '!electron' }) and run the assertion only on the legacy network path. Excluding Electron by name matters: Electron is Chromium-based but still uses the legacy path, so a family filter alone would put it on the wrong side of the gate.
- Why is an intercepted response's body readable even though no content-encoding header is reported?Because compression is negotiated between the browser and your server and the body has already been decoded by the time Cypress hands it to you. In Cypress 15 the header survived while the body was decoded, so the header described bytes you never received; now the headers match the body that accompanies them.
saying these in an interview costs you the question
- Says req.httpVersion still reports the negotiated protocol
- Treats the absent value as a Cypress bug to report
- Asserts on content-encoding to prove compression works
- Sets forceHttp1 permanently instead of fixing assertions