Cypress `cy.intercept()` cannot stub WebSocket frames, so how do you test a socket-fed UI?
answer
- Traffic flows, interception does not
- Request/response versus a long-lived channel
- Move the seam somewhere you control
- Stub the handler, or push from the server
- A helper client outside the browser
basics
~20 sYou move the seam. Cypress documents socket traffic as working but not interceptable, so drive messages from somewhere you control: stub the app's own message handler, have the server push on cue, or run a helper client outside the browser.
solid answer
~50 sCypress states plainly that WebSocket connections work during a test but are **not** intercepted, and that stubbing individual frames or messages is not supported by `cy.intercept()`. So you pick a different seam. The three documented strategies are: stub the handlers the application registers on the browser WebSocket API with `cy.stub()` and invoke them with the payload you want, which keeps everything in the browser but reaches into app internals; have your own server push the message in response to something the test triggers, such as a `cy.request()` to a test-only endpoint, which exercises the real socket end to end; or run a small helper client outside the browser that holds its own connection to your socket server and exposes an HTTP surface the test can drive with `cy.request()`. For a weather dashboard's live alert feed, the server-driven option is usually the honest one.
go deeper
Know the headline: WebSocket traffic runs normally in a Cypress test, but cy.intercept() cannot stub individual messages. Do not spend an afternoon looking for the option.
Be ready to explain why request/response interception cannot address a long-lived channel, and to name the documented alternatives with one sentence each on what they cover.
Show that you pick the seam by blast radius: a stubbed handler proves rendering, a server-pushed frame proves the feature. Say which one you would put in the suite that gates a deploy.
Own the standard for real-time features across the suite: which layer the team fakes, what test-only endpoints are allowed to exist in production code, and who maintains a helper client.
## The limitation, stated exactly Cypress documents two separate facts about WebSockets, and conflating them causes most of the confusion: - **Your application's WebSocket traffic works as expected during a test.** Sockets connect, frames flow, the UI updates. Nothing about running under Cypress breaks them. - **Cypress does not intercept that traffic.** Stubbing or mocking individual WebSocket **messages or frames** is not natively supported by `cy.intercept()`. So a live-alerts panel on a weather dashboard renders fine under test as long as a real server is pushing to it. What you cannot do is write a route that fabricates the alert frame, the way you would stub a `GET /api/forecast` response. ## Why an intercept cannot reach a frame `cy.intercept()` is built around a **request/response pair**: something is sent, a rule matches it, a response is produced or observed, and the exchange is over. A WebSocket is the opposite shape — one long-lived, full-duplex channel over which either side sends messages whenever it likes, with no per-message request to match and no per-message response to supply. There is nothing for a route matcher to key on and nothing for a static response to replace. ## The three documented strategies 1. **Stub the application's callbacks.** Use `cy.stub()` to replace the handler your app registers on the browser WebSocket API — its `onmessage`, or the wrapper it puts around one — and call it directly with the payload you want to simulate. Fastest and fully deterministic, and it exercises **only** the rendering path: nothing about your socket wiring, reconnect logic, or message parsing is under test. 2. **Drive the message from your server.** Have the server send the frame in response to something the test triggers — typically a `cy.request()` to a test-only endpoint that publishes an alert. The frame travels the real channel, so the socket client, the parsing, and the render are all exercised. 3. **Introduce a helper client outside the browser.** Run a small Node process that opens its own connection to your socket server and exposes an HTTP surface; the test drives it with `cy.request()` so it can send and receive as a second participant. This is what you reach for when the scenario genuinely needs two parties, such as one station operator publishing an alert that another dashboard must receive. | Strategy | What it exercises | What it costs | |---|---|---| | Stub the app's handler | Rendering only | Couples the test to internals | | Server pushes on cue | Socket, parsing, render | A test-only endpoint to maintain | | Helper client outside the browser | A real second participant | A process to run and clean up | ## Choosing between them - If the assertion is about **what the UI does with a message** — an alert banner turns red above a wind-speed threshold — stubbing the handler is proportionate and fast. - If the assertion is about **the feature working**, use the server-driven route, because a stubbed handler stays green while the socket URL, the subprotocol, or the parsing is broken. - Reach for the helper client only for genuinely multi-party scenarios; it is the most machinery and the most to tear down between specs. - Whichever you pick, keep the **trigger** inside the test. A test that waits for a message some background job might send is not deterministic, whatever seam it uses. ## Traps worth naming - **Do not expect a route matcher to help.** Even though a request's `resourceType` has `'websocket'` among its possible values, that is a classification of a request, not a handle on the frames of a live channel — and `resourceType` is deprecated as a matcher. - **Do not model the socket as slow HTTP.** Retrying an assertion until a frame happens to arrive turns a missing trigger into a timing flake. - **Do not stub the constructor away wholesale** unless the test really is only about rendering; you lose every signal that the real channel works. - **Do not confuse "not intercepted" with "not supported".** The traffic runs; only the interception does not exist. ## What to remember The limitation is architectural, not a missing feature flag: `cy.intercept()` speaks request/response and a WebSocket is a channel. Move the seam to a place you control — the app's handler, your server, or a second client — and choose the one whose blast radius matches what the test claims to prove.
- What does a Cypress test lose by stubbing the application's message handler instead of driving the server?Everything between the wire and the handler: the socket URL, the connection lifecycle, any subprotocol negotiation, reconnect behaviour, and message parsing. The test stays green while the feed is genuinely broken, so keep that seam for assertions that are honestly about rendering.
- How do you keep a server-driven WebSocket test deterministic?Make the test the trigger. Have it call a test-only endpoint with cy.request() that publishes exactly the message the assertion needs, then assert on the rendered result. Never wait on a message that a scheduler or another user might or might not send during the run.
An intercept is a mail sorter: it can open and rewrite each envelope because each one arrives separately. A WebSocket is an open phone line, and there are no envelopes to sort.
saying these in an interview costs you the question
- Claims cy.intercept() can stub a WebSocket message
- Says WebSockets simply do not work under Cypress
- Reaches for resourceType websocket as a frame matcher
- Waits for a background job to push the frame
- Stubs the whole socket, then claims end-to-end coverage