Files
cypress/packages/server/test/integration/websockets_spec.js
T
Cacie Prins 0547d65a2a breaking: no longer inject document.domain by default (#30770)
* remove experimentalSkipDomainInjection, add and deprecate injectDocumentDomain

* remove experimentalSkipDomainInjection, add injectDocumentDomain

* begin rethreading domain injection

* complete document domain transition

* move some cookie specs to separate test run

* origin and privileged commands with default docdom inject

* fix privileged channel when injecting document domain

* rm unnecessary .getOrigin abstraction in cors lib

* move remote-states in prep for refactor Replace Conditional with Polymorphism

* refactor remote states to strategy pattern

* cookie commands work as expected w cross origin bridge on different origins

* some origin tests updated

* run tests with document domain enabled

* run tests actually

* use correct config, swap conditional

* check-ts

* inject documetn domain for webkit tests

* do not exec injectDocumetnDomain in parallel

* fix ServerBase construction in tests to include cfg now

* pass cfg to ServerBase

* improved integration tests

* remove document domain checks for all server integration specs - will add injectDocumentDomain cases

* tests for injecting document domain when configured to

* square away server integration tests

* ensure cookies are set correctly, potentially

* errors pkg snapshots

* fix config tests

* fixing config tests

* somewhat improves tests for cors policies in packages/network

* fix ts err in server-base

* enable injectDocumentDomain for cy in cy tests

* fix Policy type ref

* refactor cypress-schematic ct spec to be less prone to timeouts

* run vite-dev-server tests with injectDocumentDomain

* rm document domain assertion from page_loading system test

* add system tests that test with injectDocumentDomain and others that test with cy.origin

* fix results_spec snapshot

* update experimentalSkipDomainInjection system test

* different behavior for certain net_stubbing tests based on injectDocumentDomain or not

* fix ts

* extract origin key logic from remote states, for now

* move server-base and response-middleware over to new pattern

* WIP - reentry

* fix build, remove console.log

* check-ts

* fix spec frame injection

* remove injection for localhost

* mostly fix vite-dev-server app integration tests

* fix codeframe in certain cases in chrome

* drop internal stack frames from stacks intended for determining code frame data

* some improvements to vite ct error codeframes

* fix proxy unit tests to use document domain injection util class

* rm .only

* fix all vite ct error specs

* rm console.log

* slight refactor to util class to make easier to test

* fix refactor - missing rename in files.js

* several tests do not set testingtype in config, so just check against component instead of checking for e2e

* revert changes to getInvocationDetails to see if that breaks tests

* re-enable stack stripping in invocation details for chrome

* new snapshots with more accurate invocation details

* test for same-site cross-origin cookie behavior

* ignore window.top ts errors

* revert forcing injectDocumentDomain in vite-dev-server cy config

* fix normalized whitespace for firefox "loading_failed" error

* always trim trailing wsp from stack before appending additional content

* force normalization of whitespace to three \n when adding additional stack details

* normalize wsp between stack and additional stack to "\n  \n" in firefox

* remove stack_utils attempt at normalizing wsp

* various cleanup: remove commented console logs, add more detailed comments

* add on links to error messages

* remove experimentalSkipDomainInjection from exported type defs

* Update system-tests/test/experimental_skip_domain_injection_spec.ts

Co-authored-by: Bill Glesias <bglesias@gmail.com>

* Update packages/driver/cypress/e2e/e2e/origin/cookie_misc.cy.ts

Co-authored-by: Bill Glesias <bglesias@gmail.com>

* no need to coerce a boolean value to a booleanc

* export base config from primary cypress config in driver for use in inject-document-domain test subset

* lift experimentalSkipDomainInjection breaking option to root

* rollback config/options changes

* rm invalid comment

* use hostname instead of origin to create cookie from automation cookie

* clarify stack regex in results_spec

* lint

* take a stab at the changelog entries for this

* Update cli/CHANGELOG.md

Co-authored-by: Ryan Manuel <ryanm@cypress.io>

* Update cli/CHANGELOG.md

Co-authored-by: Ryan Manuel <ryanm@cypress.io>

* reenable locally-failing test

* changelog

* snapshot updatesfor experimental skip domain injection err msg

* remove packageManager declaration in package.json

---------

Co-authored-by: Bill Glesias <bglesias@gmail.com>
Co-authored-by: Jennifer Shehane <jennifer@cypress.io>
Co-authored-by: Ryan Manuel <ryanm@cypress.io>
2025-01-06 13:48:43 -05:00

330 lines
9.0 KiB
JavaScript

process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
require('../spec_helper')
const ws = require('ws')
const httpsProxyAgent = require('https-proxy-agent')
const evilDns = require('evil-dns')
const Promise = require('bluebird')
const socketIo = require(`@packages/socket/lib/browser`)
const httpsServer = require(`@packages/https-proxy/test/helpers/https_server`)
const config = require(`../../lib/config`)
const { ServerBase } = require(`../../lib/server-base`)
const { SocketE2E } = require(`../../lib/socket-e2e`)
const { Automation } = require(`../../lib/automation`)
const Fixtures = require('@tooling/system-tests')
const { createRoutes } = require(`../../lib/routes`)
const { getCtx } = require(`../../lib/makeDataContext`)
const cyPort = 12345
const otherPort = 55551
const wsPort = 20000
const wssPort = 8443
let ctx
describe('Web Sockets', () => {
require('mocha-banner').register()
beforeEach(async function () {
ctx = getCtx()
Fixtures.scaffold()
this.idsPath = Fixtures.projectPath('ids')
await ctx.actions.project.setCurrentProjectAndTestingTypeForTestSetup(this.idsPath)
return ctx.lifecycleManager.getFullInitialConfig({ port: cyPort })
.then((cfg) => {
this.cfg = cfg
this.ws = new ws.Server({ port: wsPort })
this.server = new ServerBase(cfg)
return this.server.open(this.cfg, {
SocketCtor: SocketE2E,
createRoutes,
testingType: 'e2e',
getCurrentBrowser: () => null,
})
.then(async () => {
const automationStub = {
use: () => { },
}
await this.server.startWebsockets(automationStub, config, {})
return httpsServer.start(wssPort)
}).then((httpsSrv) => {
this.wss = new ws.Server({ server: httpsSrv })
})
})
})
afterEach(function () {
Fixtures.remove()
evilDns.clear()
this.ws.close()
this.wss.close()
return Promise.join(
this.server.close(),
httpsServer.stop(),
)
})
context('proxying external websocket requests', () => {
it('sends back ECONNRESET when error upgrading', function (done) {
const agent = new httpsProxyAgent(`http://localhost:${cyPort}`)
this.server.remoteStates.set(`http://localhost:${otherPort}`)
const client = new ws(`ws://localhost:${otherPort}`, {
agent,
})
return client.on('error', (err) => {
expect(err.code).to.eq('ECONNRESET')
expect(err.message).to.eq('socket hang up')
return done()
})
})
it('proxies https messages', function (done) {
const agent = new httpsProxyAgent(`http://localhost:${cyPort}`, {
})
this.wss.on('connection', (c) => {
return c.on('message', (msg) => {
return c.send(`response:${msg}`)
})
})
const client = new ws(`wss://localhost:${wssPort}`, {
rejectUnauthorized: false,
agent,
})
client.on('message', (data) => {
expect(data).to.eq('response:foo')
return done()
})
return client.on('open', () => {
return client.send('foo')
})
})
it('proxies http messages through http proxy', function (done) {
// force node into legit proxy mode like a browser
const agent = new httpsProxyAgent(`http://localhost:${cyPort}`)
this.server.remoteStates.set(`http://localhost:${wsPort}`)
this.ws.on('connection', (c) => {
return c.on('message', (msg) => {
return c.send(`response:${msg}`)
})
})
const client = new ws(`ws://localhost:${wsPort}`, {
agent,
})
client.on('message', (data) => {
expect(data).to.eq('response:foo')
return done()
})
return client.on('open', () => {
return client.send('foo')
})
})
it('proxies https messages through http', function (done) {
// force node into legit proxy mode like a browser
const agent = new httpsProxyAgent({
host: 'localhost',
port: cyPort,
rejectUnauthorized: false,
})
this.server.remoteStates.set(`https://localhost:${wssPort}`)
this.wss.on('connection', (c) => {
return c.on('message', (msg) => {
return c.send(`response:${msg}`)
})
})
const client = new ws(`wss://localhost:${wssPort}`, {
agent,
})
client.on('message', (data) => {
expect(data).to.eq('response:foo')
return done()
})
return client.on('open', () => {
return client.send('foo')
})
})
it('proxies through subdomain by using host header', function (done) {
// we specifically only allow remote connections
// to ws.foobar.com since that is where the websocket
// server is mounted and this tests that we make
// a connection to the right host instead of the
// origin (which isnt ws.foobar.com)
nock.enableNetConnect('ws.foobar.com')
evilDns.add('ws.foobar.com', '127.0.0.1')
// force node into legit proxy mode like a browser
const agent = new httpsProxyAgent({
host: 'localhost',
port: cyPort,
rejectUnauthorized: false,
})
this.server.remoteStates.set(`https://foobar.com:${wssPort}`)
this.wss.on('connection', (c) => {
return c.on('message', (msg) => {
return c.send(`response:${msg}`)
})
})
const client = new ws(`wss://ws.foobar.com:${wssPort}`, {
agent,
})
client.on('message', (data) => {
expect(data).to.eq('response:foo')
return done()
})
return client.on('open', () => {
return client.send('foo')
})
})
})
context('socket.io handling', () => {
beforeEach(function () {
this.automation = new Automation({
cyNamespace: this.cfg.namespace,
cookieNamespace: this.cfg.socketIoCookie,
screenshotsFolder: this.cfg.screenshotsFolder,
})
return this.server.startWebsockets(this.automation, this.cfg, {})
})
const testSocketIo = function (wsUrl, beforeFn) {
context('behind Cy proxy', () => {
beforeEach(function (done) {
// force node into legit proxy mode like a browser
const agent = new httpsProxyAgent(`http://localhost:${cyPort}`)
if (beforeFn != null) {
beforeFn.call(this)
}
this.wsClient = socketIo.client(wsUrl || this.cfg.proxyUrl, {
agent,
path: this.cfg.socketIoRoute,
transports: ['websocket'],
rejectUnauthorized: false,
})
return this.wsClient.on('connect', () => {
return done()
})
})
afterEach(function () {
return this.wsClient.disconnect()
})
it('continues to handle socket.io requests just fine', function (done) {
return this.wsClient.emit('backend:request', 'get:fixture', 'example.json', {}, (data) => {
expect(data.response).to.deep.eq({ foo: 'bar' })
return done()
})
})
})
context('without Cy proxy', () => {
beforeEach(function () {
return (beforeFn != null ? beforeFn.call(this) : undefined)
})
afterEach(function () {
return this.wsClient.disconnect()
})
it('fails to connect via websocket', function (done) {
this.wsClient = socketIo.client(wsUrl || this.cfg.proxyUrl, {
path: this.cfg.socketIoRoute,
transports: ['websocket'],
rejectUnauthorized: false,
reconnection: false,
})
this.wsClient.on('connect', () => {
return done(new Error('should not have been able to connect'))
})
return this.wsClient.io.on('error', () => {
return done()
})
})
// TODO: this test will currently fail because we allow polling in development mode
// for webkit support. Restore this test before WebKit is available in production.
// it('fails to connect via polling', function (done) {
// this.wsClient = socketIo.client(wsUrl || this.cfg.proxyUrl, {
// path: this.cfg.socketIoRoute,
// transports: ['polling'],
// rejectUnauthorized: false,
// reconnection: false,
// })
// this.wsClient.on('connect', () => {
// return done(new Error('should not have been able to connect'))
// })
// return this.wsClient.io.on('error', () => {
// return done()
// })
// })
})
}
context('http', () => {
return testSocketIo()
})
context('when http superDomain has been set', () => {
return testSocketIo(`http://localhost:${otherPort}`, function () {
return this.server.remoteStates.set(`http://localhost:${otherPort}`)
})
})
context('when https superDomain has been set', () => {
return testSocketIo(`http://localhost:${wssPort}`, function () {
return this.server.remoteStates.set(`http://localhost:${wssPort}`)
})
})
})
})