Files
cypress/packages/server/test/integration/server_spec.js
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

1645 lines
55 KiB
JavaScript

require('../spec_helper')
const _ = require('lodash')
const http = require('http')
const rp = require('@cypress/request-promise')
const Promise = require('bluebird')
const evilDns = require('evil-dns')
const { setupFullConfigWithDefaults } = require('@packages/config')
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 Fixtures = require('@tooling/system-tests')
const { createRoutes } = require(`../../lib/routes`)
const { getCtx } = require('../../lib/makeDataContext')
const s3StaticHtmlUrl = 'https://s3.amazonaws.com/internal-test-runner-assets.cypress.io/index.html'
const expectToEqDetails = function (actual, expected) {
actual = _.omit(actual, 'totalTime')
expect(actual).to.deep.eq(expected)
}
describe('Server', () => {
require('mocha-banner').register()
beforeEach(() => {
return sinon.stub(ServerBase.prototype, 'reset')
})
context('resolving url', () => {
beforeEach(function () {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
nock.enableNetConnect()
this.automationRequest = sinon.stub()
this.automationRequest.withArgs('get:cookies').resolves([])
this.automationRequest.withArgs('set:cookie').resolves({})
this.setup = (initialUrl, obj = {}) => {
if (_.isObject(initialUrl)) {
obj = initialUrl
initialUrl = null
}
// get all the config defaults
// and allow us to override them
// for each test
return setupFullConfigWithDefaults(obj, getCtx().file.getFilesByGlob)
.then((cfg) => {
// use a jar for each test
// but reset it automatically
// between test
const jar = rp.jar()
// use a custom request promise
// to automatically backfill these
// options including our proxy
this.rp = (options = {}) => {
let url
if (_.isString(options)) {
url = options
options = {}
}
_.defaults(options, {
url,
proxy: this.proxy,
jar,
simple: false,
followRedirect: false,
resolveWithFullResponse: true,
})
return rp(options)
}
return Promise.all([
// open our https server
httpsServer.start(8443),
// and open our cypress server
(this.server = new ServerBase(cfg)),
this.server.open(cfg, {
SocketCtor: SocketE2E,
createRoutes,
testingType: 'e2e',
getCurrentBrowser: () => null,
})
.spread(async (port) => {
const automationStub = {
use: () => { },
}
await this.server.startWebsockets(automationStub, config, {})
if (initialUrl) {
this.server.remoteStates.set(initialUrl)
}
this.srv = this.server.getHttpServer()
this.proxy = `http://localhost:${port}`
this.buffers = this.server._networkProxy.http.buffers
this.fileServer = this.server._fileServer.address()
}),
])
})
}
})
afterEach(function () {
nock.cleanAll()
evilDns.clear()
return Promise.join(
this.server.close(),
httpsServer.stop(),
)
})
describe('file', () => {
beforeEach(function () {
Fixtures.scaffold('no-server')
return this.setup({
projectRoot: Fixtures.projectPath('no-server'),
config: {
port: 2000,
fileServerFolder: 'dev',
supportFile: false,
},
})
})
it('can serve static assets', async function () {
const obj = await this.server._onResolveUrl('/index.html', {}, this.automationRequest)
await expectToEqDetails(obj, {
isOkStatusCode: true,
isPrimarySuperDomainOrigin: true,
isHtml: true,
contentType: 'text/html',
url: 'http://localhost:2000/index.html',
originalUrl: '/index.html',
filePath: Fixtures.projectPath('no-server/dev/index.html'),
status: 200,
statusText: 'OK',
redirects: [],
cookies: [],
})
const res = await this.rp('http://localhost:2000/index.html')
expect(res.statusCode).to.eq(200)
expect(res.headers['etag']).to.exist
expect(res.headers['set-cookie']).not.to.match(/initial=;/)
expect(res.headers['cache-control']).to.eq('no-cache, no-store, must-revalidate')
expect(res.body).to.include('index.html content')
expect(res.body).to.include('.action("app:window:before:load",window)')
expect(res.body).to.include('</script>\n </head>')
})
it('sends back the content type', async function () {
const obj = await this.server._onResolveUrl('/assets/foo.json', {}, this.automationRequest)
await expectToEqDetails(obj, {
isOkStatusCode: true,
isPrimarySuperDomainOrigin: true,
isHtml: false,
contentType: 'application/json',
url: 'http://localhost:2000/assets/foo.json',
originalUrl: '/assets/foo.json',
filePath: Fixtures.projectPath('no-server/dev/assets/foo.json'),
status: 200,
statusText: 'OK',
redirects: [],
cookies: [],
})
})
it('buffers the response', function () {
sinon.spy(this.server.request, 'sendStream')
return this.server._onResolveUrl('/index.html', {}, this.automationRequest)
.then((obj = {}) => {
expectToEqDetails(obj, {
isOkStatusCode: true,
isPrimarySuperDomainOrigin: true,
isHtml: true,
contentType: 'text/html',
url: 'http://localhost:2000/index.html',
originalUrl: '/index.html',
filePath: Fixtures.projectPath('no-server/dev/index.html'),
status: 200,
statusText: 'OK',
redirects: [],
cookies: [],
})
expect(this.buffers.buffer).to.include({ url: 'http://localhost:2000/index.html' })
}).then(() => {
return this.server._onResolveUrl('/index.html', {}, this.automationRequest)
.then((obj = {}) => {
expectToEqDetails(obj, {
isOkStatusCode: true,
isPrimarySuperDomainOrigin: true,
isHtml: true,
contentType: 'text/html',
url: 'http://localhost:2000/index.html',
originalUrl: '/index.html',
filePath: Fixtures.projectPath('no-server/dev/index.html'),
status: 200,
statusText: 'OK',
redirects: [],
cookies: [],
})
expect(this.server.request.sendStream).to.be.calledTwice
})
}).then(() => {
return this.rp('http://localhost:2000/index.html')
.then((res) => {
expect(res.statusCode).to.eq(200)
expect(res.body).to.include('Cypress')
expect(this.buffers.buffer).to.be.undefined
})
})
})
it('can follow static file redirects', function () {
return this.server._onResolveUrl('/sub', {}, this.automationRequest)
.then((obj = {}) => {
return expectToEqDetails(obj, {
isOkStatusCode: true,
isPrimarySuperDomainOrigin: true,
isHtml: true,
contentType: 'text/html',
url: 'http://localhost:2000/sub/',
originalUrl: '/sub',
filePath: Fixtures.projectPath('no-server/dev/sub/'),
status: 200,
statusText: 'OK',
redirects: ['301: http://localhost:2000/sub/'],
cookies: [],
})
}).then(() => {
return this.rp('http://localhost:2000/sub/')
.then((res) => {
expect(res.statusCode).to.eq(200)
expect(this.server.remoteStates.current()).to.deep.eq({
auth: undefined,
origin: 'http://localhost:2000',
strategy: 'file',
domainName: 'localhost',
fileServer: this.fileServer,
props: null,
})
})
})
})
it('gracefully handles 404', function () {
return this.server._onResolveUrl('/does-not-exist', {}, this.automationRequest)
.then((obj = {}) => {
return expectToEqDetails(obj, {
isOkStatusCode: false,
isPrimarySuperDomainOrigin: true,
isHtml: true,
contentType: 'text/html',
url: 'http://localhost:2000/does-not-exist',
originalUrl: '/does-not-exist',
filePath: Fixtures.projectPath('no-server/dev/does-not-exist'),
status: 404,
statusText: 'Not Found',
redirects: [],
cookies: [],
})
}).then(() => {
return this.rp({
url: 'http://localhost:2000/does-not-exist',
headers: {
'Accept-Encoding': 'identity',
},
})
.then((res) => {
expect(res.statusCode).to.eq(404)
expect(res.body).to.include('Cypress errored trying to serve this file from your system:')
expect(res.body).to.include('does-not-exist')
expect(res.body).to.include('The file was not found')
})
})
})
it('handles urls with hashes', function () {
return this.server._onResolveUrl('/index.html#/foo/bar', {}, this.automationRequest)
.then((obj = {}) => {
expectToEqDetails(obj, {
isOkStatusCode: true,
isPrimarySuperDomainOrigin: true,
isHtml: true,
contentType: 'text/html',
url: 'http://localhost:2000/index.html',
originalUrl: '/index.html',
filePath: Fixtures.projectPath('no-server/dev/index.html'),
status: 200,
statusText: 'OK',
redirects: [],
cookies: [],
})
expect(this.buffers.buffer).to.include({ url: 'http://localhost:2000/index.html' })
}).then(() => {
return this.rp('http://localhost:2000/index.html')
.then((res) => {
expect(res.statusCode).to.eq(200)
expect(this.buffers.buffer).to.be.undefined
})
})
})
})
describe('http', () => {
beforeEach(function () {
return this.setup({
projectRoot: '/foo/bar/',
config: {
port: 2000,
supportFile: false,
},
})
})
context('only having one request in flight at a time', () => {
beforeEach(function (done) {
this.httpServer = http.createServer((req, res) => {
const [path, ms] = req.url.split('/').slice(1)
switch (path) {
case 'pause-before-body':
res.writeHead(200, { 'content-type': 'text/html' })
return setTimeout(() => {
res.write('ok')
return res.end()
}
, Number(ms))
case 'pause-before-headers':
return setTimeout(() => {
res.writeHead(200, { 'content-type': 'text/html' })
res.write('ok')
return res.end()
}
, Number(ms))
default:
}
})
this.httpServer.listen(() => {
this.httpPort = this.httpServer.address().port
return done()
})
this.runOneReqTest = (path) => {
// put the first request in flight
const p1 = this.server._onResolveUrl(`http://localhost:${this.httpPort}/${path}/1000`, {}, this.automationRequest)
return Promise.delay(100)
.then(() => {
// the p1 should not have a current promise phase or reqStream until it's canceled
expect(p1).not.to.have.property('currentPromisePhase')
expect(p1).not.to.have.property('reqStream')
// fire the 2nd request now that the first one has had some time to reach out
return this.server._onResolveUrl(`http://localhost:${this.httpPort}/${path}/100`, {}, this.automationRequest)
}).then((obj) => {
expectToEqDetails(obj, {
isOkStatusCode: true,
isPrimarySuperDomainOrigin: true,
isHtml: true,
contentType: 'text/html',
url: `http://localhost:${this.httpPort}/${path}/100`,
originalUrl: `http://localhost:${this.httpPort}/${path}/100`,
status: 200,
statusText: 'OK',
redirects: [],
cookies: [],
})
expect(p1.isCancelled()).to.be.true
expect(p1).to.have.property('currentPromisePhase')
expect(p1.reqStream.aborted).to.be.true
})
}
})
it('cancels and aborts the 1st request when it hasn\'t loaded headers and a 2nd request is made', function () {
return this.runOneReqTest('pause-before-headers')
})
it('cancels and aborts the 1st request when it hasn\'t loaded body and a 2nd request is made', function () {
return this.runOneReqTest('pause-before-body')
})
})
it('can serve http requests', function () {
nock('http://getbootstrap.com')
.matchHeader('user-agent', 'foobarbaz')
.matchHeader('accept', 'text/html,*/*')
.get('/')
.reply(200, '<html>content</html>', {
'X-Foo-Bar': 'true',
'Content-Type': 'text/html',
'Cache-Control': 'public, max-age=3600',
})
const userAgent = 'foobarbaz'
return this.server._onResolveUrl('http://getbootstrap.com/', userAgent, this.automationRequest)
.then((obj = {}) => {
return expectToEqDetails(obj, {
isOkStatusCode: true,
isPrimarySuperDomainOrigin: true,
isHtml: true,
contentType: 'text/html',
url: 'http://getbootstrap.com/',
originalUrl: 'http://getbootstrap.com/',
status: 200,
statusText: 'OK',
redirects: [],
cookies: [],
})
}).then(() => {
return this.rp('http://getbootstrap.com/')
.then((res) => {
expect(res.statusCode).to.eq(200)
expect(res.headers['set-cookie']).not.to.match(/initial=;/)
expect(res.headers['x-foo-bar']).to.eq('true')
expect(res.headers['cache-control']).to.eq('no-cache, no-store, must-revalidate')
expect(res.body).to.include('content')
expect(res.body).to.include('.action("app:window:before:load",window)')
expect(res.body).to.include('</head>content</html>')
})
})
})
it('sends back the content type', function () {
nock('http://getbootstrap.com')
.get('/user.json')
.reply(200, {})
return this.server._onResolveUrl('http://getbootstrap.com/user.json', {}, this.automationRequest)
.then((obj = {}) => {
return expectToEqDetails(obj, {
isOkStatusCode: true,
isPrimarySuperDomainOrigin: false,
isHtml: false,
contentType: 'application/json',
url: 'http://getbootstrap.com/user.json',
originalUrl: 'http://getbootstrap.com/user.json',
status: 200,
statusText: 'OK',
redirects: [],
cookies: [],
})
})
})
// @see https://github.com/cypress-io/cypress/issues/8506
it('yields isHtml true for unconventional HTML content-types', async function () {
const scope = nock('http://example.com')
.get('/a').reply(200, 'notHtml')
.get('/b').reply(200, 'notHtml', { 'content-type': 'Text/Html' })
.get('/c').reply(200, 'notHtml', { 'content-type': 'text/html;charset=utf-8' })
// invalid, but let's be tolerant
.get('/d').reply(200, 'notHtml', { 'content-type': 'text/html;' })
.get('/e').reply(200, 'notHtml', { 'content-type': 'application/xhtml+xml' })
const bad = await this.server._onResolveUrl('http://example.com/a', {}, this.automationRequest)
expect(bad.isHtml).to.be.false
for (const path of ['/b', '/c', '/d', '/e']) {
const details = await this.server._onResolveUrl(`http://example.com${path}`, {}, this.automationRequest)
expect(details.isHtml).to.be.true
}
scope.done()
})
it('yields isHtml true for HTML-shaped responses', function () {
nock('http://example.com')
.get('/')
.reply(200, '<html>foo</html>')
return this.server._onResolveUrl('http://example.com', {}, this.automationRequest)
.then((obj = {}) => {
return expectToEqDetails(obj, {
isOkStatusCode: true,
isPrimarySuperDomainOrigin: true,
isHtml: true,
contentType: undefined,
url: 'http://example.com/',
originalUrl: 'http://example.com/',
status: 200,
statusText: 'OK',
redirects: [],
cookies: [],
})
})
})
it('yields isHtml false for non-HTML-shaped responses', function () {
nock('http://example.com')
.get('/')
.reply(200, '{ foo: "bar" }')
return this.server._onResolveUrl('http://example.com', {}, this.automationRequest)
.then((obj = {}) => {
return expectToEqDetails(obj, {
isOkStatusCode: true,
isPrimarySuperDomainOrigin: false,
isHtml: false,
contentType: undefined,
url: 'http://example.com/',
originalUrl: 'http://example.com/',
status: 200,
statusText: 'OK',
redirects: [],
cookies: [],
})
})
})
it('can follow multiple http redirects', function () {
nock('http://espn.com')
.get('/')
.reply(301, undefined, {
'Location': '/foo',
})
.get('/foo')
.reply(302, undefined, {
'Location': 'http://espn.go.com/',
})
nock('http://espn.go.com')
.get('/')
.reply(200, '<html>content</html>', {
'Content-Type': 'text/html',
})
return this.server._onResolveUrl('http://espn.com/', {}, this.automationRequest)
.then((obj = {}) => {
return expectToEqDetails(obj, {
isOkStatusCode: true,
isPrimarySuperDomainOrigin: true,
isHtml: true,
contentType: 'text/html',
url: 'http://espn.go.com/',
originalUrl: 'http://espn.com/',
status: 200,
statusText: 'OK',
cookies: [],
redirects: [
'301: http://espn.com/foo',
'302: http://espn.go.com/',
],
})
}).then(() => {
return this.rp('http://espn.go.com/')
.then((res) => {
expect(res.statusCode).to.eq(200)
expect(res.body).to.include('content')
expect(res.body).to.include('.action("app:window:before:load",window)')
expect(res.body).to.include('</head>content</html>')
expect(this.server.remoteStates.current()).to.deep.eq({
auth: undefined,
origin: 'http://espn.go.com',
strategy: 'http',
domainName: 'go.com',
fileServer: null,
props: {
domain: 'go',
tld: 'com',
port: '80',
subdomain: 'espn',
protocol: 'http:',
},
})
})
})
})
it('buffers the http response', function () {
sinon.spy(this.server.request, 'sendStream')
nock('http://espn.com')
.get('/')
.times(2)
.reply(301, undefined, {
'Location': '/foo',
})
.get('/foo')
.times(2)
.reply(302, undefined, {
'Location': 'http://espn.go.com/',
})
nock('http://espn.go.com')
.get('/')
.times(2)
.reply(200, '<html><head></head><body>espn</body></html>', {
'Content-Type': 'text/html',
})
return this.server._onResolveUrl('http://espn.com/', {}, this.automationRequest)
.then((obj = {}) => {
expectToEqDetails(obj, {
isOkStatusCode: true,
isPrimarySuperDomainOrigin: true,
isHtml: true,
contentType: 'text/html',
url: 'http://espn.go.com/',
originalUrl: 'http://espn.com/',
status: 200,
statusText: 'OK',
cookies: [],
redirects: [
'301: http://espn.com/foo',
'302: http://espn.go.com/',
],
})
expect(this.buffers.buffer).to.include({ url: 'http://espn.go.com/' })
}).then(() => {
return this.server._onResolveUrl('http://espn.com/', {}, this.automationRequest)
.then((obj = {}) => {
expectToEqDetails(obj, {
isOkStatusCode: true,
isPrimarySuperDomainOrigin: true,
isHtml: true,
contentType: 'text/html',
url: 'http://espn.go.com/',
originalUrl: 'http://espn.com/',
status: 200,
statusText: 'OK',
cookies: [],
redirects: [
'301: http://espn.com/foo',
'302: http://espn.go.com/',
],
})
expect(this.server.request.sendStream).to.be.calledTwice
})
}).then(() => {
return this.rp('http://espn.go.com/')
.then((res) => {
expect(res.statusCode).to.eq(200)
expect(res.body).to.include('.action("app:window:before:load",window)')
expect(res.body).to.include('</script></head><body>espn</body></html>')
expect(this.buffers.buffer).to.be.undefined
})
})
})
it('does not buffer \'bad\' responses', function () {
sinon.spy(this.server.request, 'sendStream')
nock('http://espn.com')
.get('/')
.reply(404, undefined)
.get('/')
.reply(301, undefined, {
'Location': '/foo',
})
.get('/foo')
.reply(301, undefined, {
'Location': 'http://espn.go.com/',
})
nock('http://espn.go.com')
.get('/')
.reply(200, 'content', {
'Content-Type': 'text/html',
})
return this.server._onResolveUrl('http://espn.com/', {}, this.automationRequest)
.then((obj = {}) => {
expectToEqDetails(obj, {
isOkStatusCode: false,
isPrimarySuperDomainOrigin: false,
isHtml: false,
contentType: undefined,
url: 'http://espn.com/',
originalUrl: 'http://espn.com/',
status: 404,
statusText: 'Not Found',
cookies: [],
redirects: [],
})
return this.server._onResolveUrl('http://espn.com/', {}, this.automationRequest)
.then((obj = {}) => {
expectToEqDetails(obj, {
isOkStatusCode: true,
isPrimarySuperDomainOrigin: true,
isHtml: true,
contentType: 'text/html',
url: 'http://espn.go.com/',
originalUrl: 'http://espn.com/',
status: 200,
statusText: 'OK',
cookies: [],
redirects: [
'301: http://espn.com/foo',
'301: http://espn.go.com/',
],
})
expect(this.server.request.sendStream).to.be.calledTwice
})
})
})
it('gracefully handles 500', function () {
nock('http://mlb.com')
.get('/')
.reply(307, undefined, {
'Location': 'http://mlb.mlb.com/',
})
nock('http://mlb.mlb.com')
.get('/')
.reply(500, undefined, {
'Content-Type': 'text/html',
})
return this.server._onResolveUrl('http://mlb.com/', {}, this.automationRequest)
.then((obj = {}) => {
return expectToEqDetails(obj, {
isOkStatusCode: false,
isPrimarySuperDomainOrigin: false,
isHtml: true,
contentType: 'text/html',
url: 'http://mlb.mlb.com/',
originalUrl: 'http://mlb.com/',
status: 500,
statusText: 'Internal Server Error',
cookies: [],
redirects: ['307: http://mlb.mlb.com/'],
})
})
})
it('gracefully handles http errors', function () {
return this.server._onResolveUrl('http://localhost:64646', {}, this.automationRequest)
.catch((err) => {
expect(err.message).to.eq('connect ECONNREFUSED 127.0.0.1:64646')
expect(err.port).to.eq(64646)
expect(err.code).to.eq('ECONNREFUSED')
})
})
it('handles url hashes', function () {
nock('http://getbootstrap.com')
.get('/')
.reply(200, 'content page', {
'Content-Type': 'text/html',
})
return this.server._onResolveUrl('http://getbootstrap.com/#/foo', {}, this.automationRequest)
.then((obj = {}) => {
expectToEqDetails(obj, {
isOkStatusCode: true,
isPrimarySuperDomainOrigin: true,
isHtml: true,
contentType: 'text/html',
url: 'http://getbootstrap.com/',
originalUrl: 'http://getbootstrap.com/',
status: 200,
statusText: 'OK',
redirects: [],
cookies: [],
})
expect(this.buffers.buffer).to.include({ url: 'http://getbootstrap.com/' })
}).then(() => {
return this.rp('http://getbootstrap.com/')
.then((res) => {
expect(res.statusCode).to.eq(200)
expect(this.buffers.buffer).to.be.undefined
})
})
})
it('can serve non 2xx status code requests when option set', function () {
nock('http://cypress.io')
.matchHeader('user-agent', 'foobarbaz')
.matchHeader('accept', 'text/html,*/*')
.get('/foo')
.reply(404, '<html>content</html>', {
'X-Foo-Bar': 'true',
'Content-Type': 'text/html',
'Cache-Control': 'public, max-age=3600',
})
const userAgent = 'foobarbaz'
return this.server._onResolveUrl('http://cypress.io/foo', userAgent, this.automationRequest, { failOnStatusCode: false })
.then((obj = {}) => {
return expectToEqDetails(obj, {
isOkStatusCode: true,
isPrimarySuperDomainOrigin: true,
isHtml: true,
contentType: 'text/html',
url: 'http://cypress.io/foo',
originalUrl: 'http://cypress.io/foo',
status: 404,
statusText: 'Not Found',
redirects: [],
cookies: [],
})
}).then(() => {
return this.rp('http://cypress.io/foo')
.then((res) => {
expect(res.statusCode).to.eq(404)
expect(res.headers['set-cookie']).not.to.match(/initial=;/)
expect(res.headers['x-foo-bar']).to.eq('true')
expect(res.headers['cache-control']).to.eq('no-cache, no-store, must-revalidate')
expect(res.body).to.include('content')
expect(res.body).to.include('.action("app:window:before:load",window)')
expect(res.body).to.include('</head>content</html>')
})
})
})
it('passes auth through', function () {
const username = 'u'
const password = 'p'
const base64 = Buffer.from(`${username}:${password}`).toString('base64')
const auth = {
username,
password,
}
nock('http://google.com')
.get('/index')
.matchHeader('authorization', `Basic ${base64}`)
.reply(200, '<html>content</html>', {
'Content-Type': 'text/html',
})
.get('/index2')
.matchHeader('authorization', `Basic ${base64}`)
.reply(200, '<html>content</html>', {
'Content-Type': 'text/html',
})
const userAgent = 'foobarbaz'
return this.server._onResolveUrl('http://google.com/index', userAgent, this.automationRequest, { auth })
.then((obj = {}) => {
return expectToEqDetails(obj, {
isOkStatusCode: true,
isPrimarySuperDomainOrigin: true,
isHtml: true,
contentType: 'text/html',
url: 'http://google.com/index',
originalUrl: 'http://google.com/index',
status: 200,
statusText: 'OK',
redirects: [],
cookies: [],
})
}).then(() => {
return this.rp('http://google.com/index2')
.then((res) => {
expect(res.statusCode).to.eq(200)
})
}).then(() => {
expect(this.server.remoteStates.current()).to.deep.eq({
auth,
origin: 'http://google.com',
strategy: 'http',
domainName: 'google.com',
fileServer: null,
props: {
domain: 'google',
tld: 'com',
port: '80',
subdomain: null,
protocol: 'http:',
},
})
})
})
context('cross-origin', () => {
it('adds a remote state and buffers the response when the request is from within cy.origin and the origins match', function () {
nock('http://www.cypress.io/')
.get('/')
.reply(200, '<html>content</html>', {
'Content-Type': 'text/html',
})
expect(this.server.remoteStates.current()).to.deep.eq({
auth: undefined,
props: null,
origin: 'http://localhost:2000',
strategy: 'file',
domainName: 'localhost',
fileServer: this.fileServer,
})
this.server.remoteStates.set('http://cypress.io', {}, false)
expect(this.server.remoteStates.current()).to.deep.eq({
auth: undefined,
props: {
domain: 'cypress',
port: '80',
tld: 'io',
subdomain: null,
protocol: 'http:',
},
origin: 'http://cypress.io',
strategy: 'http',
domainName: 'cypress.io',
fileServer: null,
})
expect(this.server.remoteStates.isPrimarySuperDomainOrigin('http://cypress.io')).to.be.false
return this.server._onResolveUrl('http://www.cypress.io/', {}, this.automationRequest, { isFromSpecBridge: true })
.then((obj = {}) => {
expectToEqDetails(obj, {
isOkStatusCode: true,
isPrimarySuperDomainOrigin: false,
isHtml: true,
contentType: 'text/html',
url: 'http://www.cypress.io/',
originalUrl: 'http://www.cypress.io/',
status: 200,
statusText: 'OK',
cookies: [],
redirects: [],
})
// Verify the cross origin request was buffered
const buffer = this.buffers.take('http://www.cypress.io/')
expect(buffer).to.not.be.empty
expect(buffer.urlDoesNotMatchPolicyBasedOnDomain).to.be.true
// Verify the secondary remote state is returned
expect(this.server.remoteStates.current()).to.deep.eq({
auth: undefined,
props: {
domain: 'cypress',
port: '80',
tld: 'io',
subdomain: 'www',
protocol: 'http:',
},
origin: 'http://www.cypress.io',
strategy: 'http',
domainName: 'cypress.io',
fileServer: null,
})
})
})
it('adds a remote state and buffers the response when a url has already been visited and the origins match', function () {
nock('http://localhost:3500/')
.get('/')
.reply(200, '<html>content</html>', {
'Content-Type': 'text/html',
})
// this will be the current origin
this.server.remoteStates.set('http://localhost:3500/')
return this.server._onResolveUrl('http://localhost:3500/', {}, this.automationRequest, { hasAlreadyVisitedUrl: true })
.then((obj = {}) => {
// Verify the cross origin request was buffered
const buffer = this.buffers.take('http://localhost:3500/')
expect(buffer).to.not.be.empty
// Verify the secondary remote state is returned
expect(this.server.remoteStates.current()).to.deep.eq({
auth: undefined,
props: {
domain: '',
port: '3500',
tld: 'localhost',
subdomain: null,
protocol: 'http:',
},
origin: 'http://localhost:3500',
strategy: 'http',
domainName: 'localhost',
fileServer: null,
})
})
})
it('does set a remote state and buffer the response when a url has already been visited and the origins don\'t match', function () {
nock('http://localhost:3500/')
.get('/')
.reply(200, '<html>content</html>', {
'Content-Type': 'text/html',
})
this.server.remoteStates.set('http://localhost:3500/')
// this will be the current origin
this.server.remoteStates.set('http://cypress.io', {}, false)
return this.server._onResolveUrl('http://localhost:3500/', {}, this.automationRequest, { hasAlreadyVisitedUrl: true })
.then(() => {
// Verify the remote state was not updated
expect(this.server.remoteStates.current()).to.deep.eq({
auth: undefined,
props: {
domain: '',
port: '3500',
tld: 'localhost',
subdomain: null,
protocol: 'http:',
},
origin: 'http://localhost:3500',
strategy: 'http',
domainName: 'localhost',
fileServer: null,
})
// Verify the cross origin request was buffered
const buffer = this.buffers.take('http://localhost:3500/')
expect(buffer).to.not.be.empty
})
})
it('does set a remote state or buffer the response when the request is from within cy.origin and the origins don\'t match', function () {
nock('http://localhost:3500/')
.get('/')
.reply(200, '<html>content</html>', {
'Content-Type': 'text/html',
})
this.server.remoteStates.set('http://localhost:3500/')
// this will be the current origin
this.server.remoteStates.set('http://cypress.io', {}, false)
return this.server._onResolveUrl('http://localhost:3500/', {}, this.automationRequest, { isFromSpecBridge: true })
.then(() => {
// Verify the remote state was not updated
expect(this.server.remoteStates.current()).to.deep.eq({
auth: undefined,
props: {
domain: '',
port: '3500',
tld: 'localhost',
subdomain: null,
protocol: 'http:',
},
origin: 'http://localhost:3500',
strategy: 'http',
domainName: 'localhost',
fileServer: null,
})
// Verify the cross origin request was buffered
const buffer = this.buffers.take('http://localhost:3500/')
expect(buffer).to.not.be.empty
})
})
})
})
describe('http with injectDocumentDomain enabled', () => {
const superDomain = 'cypress.io'
const secondSuperDomain = 'google.com'
const origin = `http://www.${superDomain}`
const sameSuperdomainOrigin = `http://docs.${superDomain}`
const differentSuperdomainOrigin = `http://www.${secondSuperDomain}`
const statusCode = 200
const contentText = 'content'
const path = '/'
beforeEach(async function () {
await this.setup(origin, {
projectRoot: '/foo/bar/',
config: {
port: 2000,
supportFile: false,
injectDocumentDomain: true,
},
})
;[origin, sameSuperdomainOrigin, differentSuperdomainOrigin].forEach((originToMock) => {
nock(originToMock).get(path).reply(statusCode, `<html>${contentText}</html>`, {
'Content-Type': 'text/html',
})
})
})
describe('when navigating to a different subdomain of the same superdomain without cy.origin', function () {
it('injects document.domain', async function () {
const url = `${sameSuperdomainOrigin}${path}`
await this.server._onResolveUrl(url, {}, this.automationRequest)
const res = await this.rp(url)
expect(res.body).to.include(`document.domain = \'${superDomain}\'`)
})
})
describe('when navigating to a different superdomain with cy.origin', function () {
it('injects document.domain', async function () {
const url = `${differentSuperdomainOrigin}${path}`
this.server.remoteStates.set(differentSuperdomainOrigin, {}, false)
await this.server._onResolveUrl(url, {}, this.automationRequest)
const res = await this.rp(url)
expect(res.body).to.include(`document.domain = \'${secondSuperDomain}\'`)
})
})
describe('when navigating to the same origin', function () {
it('injects document.domain', async function () {
const url = `${origin}${path}`
this.server.remoteStates.set(differentSuperdomainOrigin)
await this.server._onResolveUrl(url, {}, this.automationRequest)
const res = await this.rp(url)
expect(res.body).to.include(`document.domain = \'${superDomain}\'`)
})
})
})
describe('both', () => {
beforeEach(function () {
Fixtures.scaffold('no-server')
return this.setup({
projectRoot: Fixtures.projectPath('no-server'),
config: {
port: 2000,
fileServerFolder: 'dev',
supportFile: false,
},
})
})
it('can go from file -> http -> file', function () {
nock('http://www.cypress.io')
.get('/')
.reply(200, 'content page', {
'Content-Type': 'text/html',
})
return this.server._onResolveUrl('/index.html', {}, this.automationRequest)
.then((obj = {}) => {
return expectToEqDetails(obj, {
isOkStatusCode: true,
isPrimarySuperDomainOrigin: true,
isHtml: true,
contentType: 'text/html',
url: 'http://localhost:2000/index.html',
originalUrl: '/index.html',
filePath: Fixtures.projectPath('no-server/dev/index.html'),
status: 200,
statusText: 'OK',
redirects: [],
cookies: [],
})
}).then(() => {
return this.rp('http://localhost:2000/index.html')
.then((res) => {
expect(res.statusCode).to.eq(200)
})
}).then(() => {
return this.server._onResolveUrl('http://www.cypress.io/', {}, this.automationRequest)
}).then((obj = {}) => {
return expectToEqDetails(obj, {
isOkStatusCode: true,
isPrimarySuperDomainOrigin: true,
isHtml: true,
contentType: 'text/html',
url: 'http://www.cypress.io/',
originalUrl: 'http://www.cypress.io/',
status: 200,
statusText: 'OK',
redirects: [],
cookies: [],
})
}).then(() => {
return this.rp('http://www.cypress.io/')
.then((res) => {
expect(res.statusCode).to.eq(200)
})
}).then(() => {
expect(this.server.remoteStates.current()).to.deep.eq({
auth: undefined,
origin: 'http://www.cypress.io',
strategy: 'http',
domainName: 'cypress.io',
fileServer: null,
props: {
domain: 'cypress',
tld: 'io',
port: '80',
subdomain: 'www',
protocol: 'http:',
},
})
}).then(() => {
return this.server._onResolveUrl('/index.html', {}, this.automationRequest)
.then((obj = {}) => {
return expectToEqDetails(obj, {
isOkStatusCode: true,
isPrimarySuperDomainOrigin: true,
isHtml: true,
contentType: 'text/html',
url: 'http://localhost:2000/index.html',
originalUrl: '/index.html',
filePath: Fixtures.projectPath('no-server/dev/index.html'),
status: 200,
statusText: 'OK',
redirects: [],
cookies: [],
})
})
}).then(() => {
return this.rp('http://localhost:2000/index.html')
.then((res) => {
expect(res.statusCode).to.eq(200)
})
}).then(() => {
expect(this.server.remoteStates.current()).to.deep.eq({
auth: undefined,
origin: 'http://localhost:2000',
strategy: 'file',
domainName: 'localhost',
fileServer: this.fileServer,
props: null,
})
})
})
it('can go from http -> file -> http', function () {
nock('http://www.cypress.io')
.get('/')
.reply(200, '<html><head></head><body>cypress</body></html>', {
'Content-Type': 'text/html',
})
.get('/')
.reply(200, '<html><head></head><body>cypress</body></html>', {
'Content-Type': 'text/html',
})
return this.server._onResolveUrl('http://www.cypress.io/', {}, this.automationRequest)
.then((obj = {}) => {
return expectToEqDetails(obj, {
isOkStatusCode: true,
isPrimarySuperDomainOrigin: true,
isHtml: true,
contentType: 'text/html',
url: 'http://www.cypress.io/',
originalUrl: 'http://www.cypress.io/',
status: 200,
statusText: 'OK',
redirects: [],
cookies: [],
})
}).then(() => {
return this.rp('http://www.cypress.io/')
.then((res) => {
expect(res.statusCode).to.eq(200)
expect(res.body).to.include('.action("app:window:before:load",window)')
expect(res.body).to.include('</script></head><body>cypress</body></html>')
})
}).then(() => {
expect(this.server.remoteStates.current()).to.deep.eq({
auth: undefined,
origin: 'http://www.cypress.io',
strategy: 'http',
domainName: 'cypress.io',
fileServer: null,
props: {
domain: 'cypress',
tld: 'io',
port: '80',
subdomain: 'www',
protocol: 'http:',
},
})
}).then(() => {
return this.server._onResolveUrl('/index.html', {}, this.automationRequest)
.then((obj = {}) => {
return expectToEqDetails(obj, {
isOkStatusCode: true,
isPrimarySuperDomainOrigin: true,
isHtml: true,
contentType: 'text/html',
url: 'http://localhost:2000/index.html',
originalUrl: '/index.html',
filePath: Fixtures.projectPath('no-server/dev/index.html'),
status: 200,
statusText: 'OK',
redirects: [],
cookies: [],
})
})
}).then(() => {
return this.rp('http://localhost:2000/index.html')
.then((res) => {
expect(res.statusCode).to.eq(200)
expect(res.body).to.include('.action("app:window:before:load",window)')
})
}).then(() => {
expect(this.server.remoteStates.current()).to.deep.eq({
auth: undefined,
origin: 'http://localhost:2000',
strategy: 'file',
domainName: 'localhost',
fileServer: this.fileServer,
props: null,
})
}).then(() => {
return this.server._onResolveUrl('http://www.cypress.io/', {}, this.automationRequest)
.then((obj = {}) => {
return expectToEqDetails(obj, {
isOkStatusCode: true,
isPrimarySuperDomainOrigin: true,
isHtml: true,
contentType: 'text/html',
url: 'http://www.cypress.io/',
originalUrl: 'http://www.cypress.io/',
status: 200,
statusText: 'OK',
redirects: [],
cookies: [],
})
}).then(() => {
return this.rp('http://www.cypress.io/')
.then((res) => {
expect(res.statusCode).to.eq(200)
expect(res.body).to.include('.action("app:window:before:load",window)')
expect(res.body).to.include('</script></head><body>cypress</body></html>')
})
}).then(() => {
expect(this.server.remoteStates.current()).to.deep.eq({
auth: undefined,
origin: 'http://www.cypress.io',
strategy: 'http',
domainName: 'cypress.io',
fileServer: null,
props: {
domain: 'cypress',
tld: 'io',
port: '80',
subdomain: 'www',
protocol: 'http:',
},
})
})
})
})
it('can go from https -> file -> https', function () {
evilDns.add('*.foobar.com', '127.0.0.1')
return this.server._onResolveUrl('https://www.foobar.com:8443/', {}, this.automationRequest)
.then((obj = {}) => {
return expectToEqDetails(obj, {
isOkStatusCode: true,
isPrimarySuperDomainOrigin: true,
isHtml: true,
contentType: 'text/html',
url: 'https://www.foobar.com:8443/',
originalUrl: 'https://www.foobar.com:8443/',
status: 200,
statusText: 'OK',
redirects: [],
cookies: [],
})
}).then(() => {
return this.rp('https://www.foobar.com:8443/')
.then((res) => {
expect(res.statusCode).to.eq(200)
expect(res.body).to.include('.action("app:window:before:load",window)')
expect(res.body).to.include('</script></head><body>https server</body></html>')
})
}).then(() => {
expect(this.server.remoteStates.current()).to.deep.eq({
auth: undefined,
origin: 'https://www.foobar.com:8443',
strategy: 'http',
domainName: 'foobar.com',
fileServer: null,
props: {
domain: 'foobar',
tld: 'com',
port: '8443',
subdomain: 'www',
protocol: 'https:',
},
})
}).then(() => {
return this.server._onResolveUrl('/index.html', {}, this.automationRequest)
.then((obj = {}) => {
return expectToEqDetails(obj, {
isOkStatusCode: true,
isPrimarySuperDomainOrigin: true,
isHtml: true,
contentType: 'text/html',
url: 'http://localhost:2000/index.html',
originalUrl: '/index.html',
filePath: Fixtures.projectPath('no-server/dev/index.html'),
status: 200,
statusText: 'OK',
redirects: [],
cookies: [],
})
})
}).then(() => {
return this.rp('http://localhost:2000/index.html')
.then((res) => {
expect(res.statusCode).to.eq(200)
expect(res.body).to.include('.action("app:window:before:load",window)')
})
}).then(() => {
expect(this.server.remoteStates.current()).to.deep.eq({
auth: undefined,
origin: 'http://localhost:2000',
strategy: 'file',
domainName: 'localhost',
fileServer: this.fileServer,
props: null,
})
}).then(() => {
return this.server._onResolveUrl('https://www.foobar.com:8443/', {}, this.automationRequest)
.then((obj = {}) => {
return expectToEqDetails(obj, {
isOkStatusCode: true,
isPrimarySuperDomainOrigin: true,
isHtml: true,
contentType: 'text/html',
url: 'https://www.foobar.com:8443/',
originalUrl: 'https://www.foobar.com:8443/',
status: 200,
statusText: 'OK',
redirects: [],
cookies: [],
})
}).then(() => {
return this.rp('https://www.foobar.com:8443/')
.then((res) => {
expect(res.statusCode).to.eq(200)
expect(res.body).to.include('.action("app:window:before:load",window)')
expect(res.body).to.include('</script></head><body>https server</body></html>')
})
}).then(() => {
expect(this.server.remoteStates.current()).to.deep.eq({
auth: undefined,
origin: 'https://www.foobar.com:8443',
strategy: 'http',
fileServer: null,
domainName: 'foobar.com',
props: {
domain: 'foobar',
tld: 'com',
port: '8443',
subdomain: 'www',
protocol: 'https:',
},
})
})
})
})
it('can go from https -> file -> https without a port', function () {
this.timeout(5000)
return this.server._onResolveUrl(s3StaticHtmlUrl, {}, this.automationRequest)
.then((obj = {}) => {
return expectToEqDetails(obj, {
isOkStatusCode: true,
isPrimarySuperDomainOrigin: true,
isHtml: true,
contentType: 'text/html',
url: s3StaticHtmlUrl,
originalUrl: s3StaticHtmlUrl,
status: 200,
statusText: 'OK',
redirects: [],
cookies: [],
})
}).then(() => {
// @server.onRequest (req, res) ->
// console.log "ON REQUEST!!!!!!!!!!!!!!!!!!!!!!"
// nock("https://s3.amazonaws.com")
// .get("/internal-test-runner-assets.cypress.io/index.html")
// .reply 200, "<html><head></head><body>jsonplaceholder</body></html>", {
// "Content-Type": "text/html"
// }
return this.rp(s3StaticHtmlUrl)
.then((res) => {
expect(res.statusCode).to.eq(200)
expect(res.body).to.include('Cypress')
})
}).then(() => {
expect(this.server.remoteStates.current()).to.deep.eq({
auth: undefined,
origin: 'https://s3.amazonaws.com',
strategy: 'http',
domainName: 's3.amazonaws.com',
fileServer: null,
props: {
domain: '',
tld: 's3.amazonaws.com',
port: '443',
subdomain: null,
protocol: 'https:',
},
})
}).then(() => {
return this.server._onResolveUrl('/index.html', {}, this.automationRequest)
.then((obj = {}) => {
return expectToEqDetails(obj, {
isOkStatusCode: true,
isPrimarySuperDomainOrigin: true,
isHtml: true,
contentType: 'text/html',
url: 'http://localhost:2000/index.html',
originalUrl: '/index.html',
filePath: Fixtures.projectPath('no-server/dev/index.html'),
status: 200,
statusText: 'OK',
redirects: [],
cookies: [],
})
})
}).then(() => {
return this.rp('http://localhost:2000/index.html')
.then((res) => {
expect(res.statusCode).to.eq(200)
expect(res.body).to.include('.action("app:window:before:load",window)')
})
}).then(() => {
expect(this.server.remoteStates.current()).to.deep.eq({
auth: undefined,
origin: 'http://localhost:2000',
strategy: 'file',
domainName: 'localhost',
fileServer: this.fileServer,
props: null,
})
}).then(() => {
return this.server._onResolveUrl(s3StaticHtmlUrl, {}, this.automationRequest)
.then((obj = {}) => {
return expectToEqDetails(obj, {
isOkStatusCode: true,
isPrimarySuperDomainOrigin: true,
isHtml: true,
contentType: 'text/html',
url: s3StaticHtmlUrl,
originalUrl: s3StaticHtmlUrl,
status: 200,
statusText: 'OK',
redirects: [],
cookies: [],
})
}).then(() => {
// @server.onNextRequest (req, res) ->
// nock("https://s3.amazonaws.com")
// .get("/internal-test-runner-assets.cypress.io/index.html")
// .reply 200, "<html><head></head><body>jsonplaceholder</body></html>", {
// "Content-Type": "text/html"
// }
return this.rp(s3StaticHtmlUrl)
.then((res) => {
expect(res.statusCode).to.eq(200)
expect(res.body).to.include('Cypress')
})
}).then(() => {
expect(this.server.remoteStates.current()).to.deep.eq({
auth: undefined,
origin: 'https://s3.amazonaws.com',
strategy: 'http',
fileServer: null,
domainName: 's3.amazonaws.com',
props: {
domain: '',
tld: 's3.amazonaws.com',
port: '443',
subdomain: null,
protocol: 'https:',
},
})
})
})
})
})
})
})