Skip to content

Commit

Permalink
add variables to res.local to determine if multi-domain has a registered
Browse files Browse the repository at this point in the history
callback, and if so, inject multi-domain. Also added more specific error that likely overlaps with 19900
  • Loading branch information
AtofStryker committed Mar 9, 2022
1 parent 74dea89 commit d0a30b6
Show file tree
Hide file tree
Showing 13 changed files with 326 additions and 21 deletions.
1 change: 1 addition & 0 deletions packages/driver/src/cy/commands/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ const stabilityChanged = (Cypress, state, config, stable) => {
configFile: Cypress.config('configFile'),
message: err.message,
originPolicy,
isExperimentalMultiDomain: Cypress.config('experimentalMultiDomain'),
},
})
} catch (error) {
Expand Down
7 changes: 4 additions & 3 deletions packages/driver/src/cy/multi-domain/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ export function addCommands (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy,
// @ts-ignore
const communicator = Cypress.multiDomainCommunicator

const sendReadyForDomain = () => {
const sendReadyForDomain = (hasHandler = false) => {
// lets the proxy know to allow the response for the secondary
// domain html through, so the page will finish loading
Cypress.backend('ready:for:domain')
// @ts-ignore
Cypress.backend('ready:for:domain', hasHandler)
}

communicator.on('delaying:html', () => {
Expand Down Expand Up @@ -108,7 +109,7 @@ export function addCommands (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy,
communicator.once('ran:domain:fn', (details) => {
const { subject, unserializableSubjectType, err, finished } = details

sendReadyForDomain()
sendReadyForDomain(true)

if (err) {
return _reject(err)
Expand Down
8 changes: 4 additions & 4 deletions packages/driver/src/cy/multi-domain/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { isString } from 'lodash'

export class Validator {
log: typeof $Log
onFailure: () => {}
onFailure: (hasHandler: boolean) => {}

constructor ({ log, onFailure }) {
this.log = log
Expand All @@ -16,7 +16,7 @@ export class Validator {

validate ({ callbackFn, data, domain }) {
if (!isString(domain) || domain !== 'localhost' && !isIP(domain) && !isValidDomain(domain, { allowUnicode: true, subdomain: false })) {
this.onFailure()
this.onFailure(false)

$errUtils.throwErrByPath('switchToDomain.invalid_domain_argument', {
onFail: this.log,
Expand All @@ -25,7 +25,7 @@ export class Validator {
}

if (data && !Array.isArray(data)) {
this.onFailure()
this.onFailure(false)

$errUtils.throwErrByPath('switchToDomain.invalid_data_argument', {
onFail: this.log,
Expand All @@ -34,7 +34,7 @@ export class Validator {
}

if (typeof callbackFn !== 'function') {
this.onFailure()
this.onFailure(false)

$errUtils.throwErrByPath('switchToDomain.invalid_fn_argument', {
onFail: this.log,
Expand Down
15 changes: 10 additions & 5 deletions packages/driver/src/cypress/error_messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -942,7 +942,7 @@ export default {
},

navigation: {
cross_origin ({ message, originPolicy, configFile }) {
cross_origin ({ message, originPolicy, configFile, isExperimentalMultiDomain }) {
return {
message: stripIndent`\
Cypress detected a cross origin error happened on page load:
Expand All @@ -957,12 +957,17 @@ export default {
A new URL does not match the origin policy if the 'protocol', 'port' (if specified), and/or 'host' (unless of the same superdomain) are different.
Cypress does not allow you to navigate to a different origin URL within a single test.
${isExperimentalMultiDomain ? `If cross origin navigation was intentional, ${cmd('switchToDomain')} needs to immediately follow a cross origin navigation event.` : ''}
${isExperimentalMultiDomain ? `Otherwise, ` : ''}Cypress does not allow you to navigate to a different origin URL within a single test.
${isExperimentalMultiDomain ? '' : `
You may need to restructure some of your test code to avoid this problem.
Alternatively you can also disable Chrome Web Security in Chromium-based browsers which will turn off this restriction by setting { chromeWebSecurity: false } in ${formatConfigFile(configFile)}.`,
docsUrl: 'https://on.cypress.io/cross-origin-violation',
Alternatively you can also disable Chrome Web Security in Chromium-based browsers which will turn off this restriction by setting { chromeWebSecurity: false } in ${formatConfigFile(configFile)}.,
`}`,
// TODO: audit switchToDomain docs url
docsUrl: isExperimentalMultiDomain ? 'https://on.cypress.io/switch-to-domain' : 'https://on.cypress.io/cross-origin-violation',
}
},
timed_out ({ ms, configFile }) {
Expand Down
7 changes: 4 additions & 3 deletions packages/proxy/lib/http/response-middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,9 @@ const MaybeDelayForMultiDomain: ResponseMiddleware = function () {
if (this.config.experimentalMultiDomain && isCrossDomain && isAUTFrame && (isHTML || isRenderedHTML)) {
this.debug('is cross-domain, delay until domain:ready event')

this.serverBus.once('ready:for:domain', () => {
this.debug('ready for domain, let it go')
this.serverBus.once('ready:for:domain', (hasHandler = false) => {
this.debug(`ready for domain. Does domain have a registered callback: ${hasHandler}.`)
this.res.locals.shouldInjectMultiDomain = hasHandler

this.next()
})
Expand Down Expand Up @@ -272,7 +273,7 @@ const SetInjectionLevel: ResponseMiddleware = function () {
const isHTML = resContentTypeIs(this.incomingRes, 'text/html')
const isAUTFrame = this.req.isAUTFrame

if (this.config.experimentalMultiDomain && !isReqMatchOriginPolicy && isAUTFrame && (isHTML || isRenderedHTML)) {
if (this.config.experimentalMultiDomain && !isReqMatchOriginPolicy && isAUTFrame && (isHTML || isRenderedHTML) && this.res.locals.shouldInjectMultiDomain) {
this.debug('- multi-domain injection')

return 'fullMultiDomain'
Expand Down
82 changes: 80 additions & 2 deletions packages/proxy/test/unit/http/response-middleware.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,9 @@ describe('http/response-middleware', function () {
req: {
isAUTFrame: true,
},
res: {
locals: {},
},
config: {
experimentalMultiDomain: true,
},
Expand All @@ -190,9 +193,10 @@ describe('http/response-middleware', function () {
const promise = testMiddleware([MaybeDelayForMultiDomain], ctx)

expect(ctx.serverBus.emit).to.be.calledWith('cross:domain:delaying:html')

ctx.serverBus.once.withArgs('ready:for:domain').args[0][1]()

expect(ctx.res.locals.shouldInjectMultiDomain).to.be.false

return promise
})

Expand All @@ -207,6 +211,9 @@ describe('http/response-middleware', function () {
},
isAUTFrame: true,
},
res: {
locals: {},
},
config: {
experimentalMultiDomain: true,
},
Expand All @@ -219,6 +226,34 @@ describe('http/response-middleware', function () {
ctx.serverBus.once.withArgs('ready:for:domain').args[0][1]()

expect(ctx.res.wantsInjection).to.be.undefined
expect(ctx.res.locals.shouldInjectMultiDomain).to.be.false

return promise
})

it('sets response locals "shouldInjectMultiDomain" to true if "ready:for:domain" emits true, indicating a bound domain callback exists', function () {
prepareContext({
incomingRes: {
headers: {
'content-type': 'text/html',
},
},
req: {
isAUTFrame: true,
},
res: {
locals: {},
},
config: {
experimentalMultiDomain: true,
},
})

const promise = testMiddleware([MaybeDelayForMultiDomain], ctx)

ctx.serverBus.once.withArgs('ready:for:domain').args[0][1](true)

expect(ctx.res.locals.shouldInjectMultiDomain).to.be.true

return promise
})
Expand Down Expand Up @@ -337,14 +372,19 @@ describe('http/response-middleware', function () {
})
})

it('injects "fullMultiDomain" when "experimentalMultiDomain" config flag is set to true for cross-domain html"', function () {
it('injects "fullMultiDomain" when "experimentalMultiDomain" config flag is set to true for cross-domain html and a callback handler exists', function () {
prepareContext({
req: {
proxiedUrl: 'http://foobar.com',
isAUTFrame: true,
cookies: {},
headers: {},
},
res: {
locals: {
shouldInjectMultiDomain: true,
},
},
incomingRes: {
headers: {
'content-type': 'text/html',
Expand All @@ -361,6 +401,44 @@ describe('http/response-middleware', function () {
})
})

it('injects default behavior when "experimentalMultiDomain" config flag is set to true for cross-domain html, but no callback handler exists for multi-domain', function () {
prepareContext({
renderedHTMLOrigins: {},
getRenderedHTMLOrigins () {
return this.renderedHTMLOrigins
},
req: {
proxiedUrl: 'http://foobar.com',
isAUTFrame: true,
cookies: {},
headers: {
'accept': [
'text/html',
'application/xhtml+xml',
],
},
},
res: {
locals: {
shouldInjectMultiDomain: false,
},
},
incomingRes: {
headers: {
'content-type': 'text/html',
},
},
config: {
experimentalMultiDomain: true,
},
})

return testMiddleware([SetInjectionLevel], ctx)
.then(() => {
expect(ctx.res.wantsInjection).to.equal('partial')
})
})

it('performs full injection on initial AUT html origin', function () {
prepareContext({
req: {
Expand Down
4 changes: 2 additions & 2 deletions packages/server/lib/server-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,8 @@ export abstract class ServerBase<TSocket extends SocketE2E | SocketCt> {
this._fileServer = null

this._eventBus.on('cross:domain:delaying:html', () => {
this.socket.localBus.once('ready:for:domain', () => {
this._eventBus.emit('ready:for:domain')
this.socket.localBus.once('ready:for:domain', (hasRegisteredHandler) => {
this._eventBus.emit('ready:for:domain', hasRegisteredHandler)
})

this.socket.toDriver('cross:domain:delaying:html')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
exports['e2e multi domain errors / captures the stack trace correctly for multi-domain errors to point users to their "switchToDomain" callback'] = `
====================================================================================================
(Run Starting)
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Cypress: 1.2.3 │
│ Browser: FooBrowser 88 │
│ Specs: 1 found (multi_domain_navigation_missing_callback_spec.ts) │
│ Searched: cypress/integration/multi_domain_navigation_missing_callback_spec.ts │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
────────────────────────────────────────────────────────────────────────────────────────────────────
Running: multi_domain_navigation_missing_callback_spec.ts (1 of 1)
multi-domain - navigation missing callback
✓ passes since switchToDomain callback exists
1) fails since switchToDomain callback is not registered for new cross origin domain
✓ passes since switchToDomain callback exists
2 passing
1 failing
1) multi-domain - navigation missing callback
fails since switchToDomain callback is not registered for new cross origin domain:
CypressError: Cypress detected a cross origin error happened on page load:
> [Cross origin error message]
Before the page load, you were bound to the origin policy:
> http://localhost:3500
A cross origin error happens when your application navigates to a new URL which does not match the origin policy above.
A new URL does not match the origin policy if the 'protocol', 'port' (if specified), and/or 'host' (unless of the same superdomain) are different.
If cross origin navigation was intentional, \`cy.switchToDomain()\` needs to immediately follow a cross origin navigation event.
Otherwise, Cypress does not allow you to navigate to a different origin URL within a single test.
https://on.cypress.io/switch-to-domain
[stack trace lines]
(Results)
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Tests: 3 │
│ Passing: 2 │
│ Failing: 1 │
│ Pending: 0 │
│ Skipped: 0 │
│ Screenshots: 1 │
│ Video: true │
│ Duration: X seconds │
│ Spec Ran: multi_domain_navigation_missing_callback_spec.ts │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
(Screenshots)
- /XXX/XXX/XXX/cypress/screenshots/multi_domain_navigation_missing_callback_spec.t (1280x720)
s/multi-domain - navigation missing callback -- fails since switchToDomain callb
ack is not registered for new cross origin domain (failed).png
(Video)
- Started processing: Compressing to 32 CRF
- Finished processing: /XXX/XXX/XXX/cypress/videos/multi_domain_navigation_missing (X second)
_callback_spec.ts.mp4
====================================================================================================
(Run Finished)
Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ ✖ multi_domain_navigation_missing_cal XX:XX 3 2 1 - - │
│ lback_spec.ts │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
✖ 1 of 1 failed (100%) XX:XX 3 2 1 - -
`
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// @ts-ignore / session support is needed for visiting about:blank between tests
describe('multi-domain - navigation missing callback', { experimentalSessionSupport: true }, () => {
it('passes since switchToDomain callback exists', () => {
cy.visit('/multi_domain.html')
cy.get('a[data-cy="multi_domain_secondary_link"]').click()

cy.switchToDomain('foobar.com', () => undefined)
})

it('fails since switchToDomain callback is not registered for new cross origin domain', () => {
cy.visit('/multi_domain.html')
cy.get('a[data-cy="multi_domain_secondary_link"]').click()
// give the test time for switchToDomain to timeout (currently is a 2 second timeout). Otherwise, request is cancelled
cy.wait(3000)
})

it('passes since switchToDomain callback exists', () => {
cy.visit('/multi_domain.html')
cy.get('a[data-cy="multi_domain_secondary_link"]').click()

cy.switchToDomain('foobar.com', () => undefined)
})
})
Loading

0 comments on commit d0a30b6

Please sign in to comment.