Skip to content

Commit

Permalink
feat: same origin spec bridges (#23885)
Browse files Browse the repository at this point in the history
* chore: enforce strict origin spec bridges

chore: refactor spec bridges to strictly enforce same origin

fix: wrap fullCrossOrigin injection around feature flag inside buffered response

* fix: do NOT set the initial cypress cookie inside the spec bridge as it is sending unecessary cookies

* chore: simplify the finding cypress in the injection code

* chore: change order in which callback fn is declared

* chore: add spec bridge performance issue to validation tests
  • Loading branch information
AtofStryker authored Oct 4, 2022
1 parent a8e4867 commit 695dd27
Show file tree
Hide file tree
Showing 72 changed files with 761 additions and 756 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ describe('./lib/cross-origin-callback-loader', () => {

it('is a noop when cy.origin() callback does not contain Cypress.require()', () => {
const source = `it('test', () => {
cy.origin('http://foobar.com:3500', () => {})
cy.origin('http://www.foobar.com:3500', () => {})
})`
const { originalMap, resultingSource, resultingMap, store } = callLoader(source)

Expand All @@ -74,7 +74,7 @@ describe('./lib/cross-origin-callback-loader', () => {

it('is a noop when last argument to cy.origin() is not a callback', () => {
const source = `it('test', () => {
cy.origin('http://foobar.com:3500', {})
cy.origin('http://www.foobar.com:3500', {})
})`
const { originalMap, resultingSource, resultingMap, store } = callLoader(source)

Expand All @@ -93,14 +93,14 @@ describe('./lib/cross-origin-callback-loader', () => {
it('replaces cy.origin() callback with an object', () => {
const { resultingSource, resultingMap } = callLoader(stripIndent`
it('test', () => {
cy.origin('http://foobar.com:3500', () => {
cy.origin('http://www.foobar.com:3500', () => {
Cypress.require('../support/utils')
})
})`)

expect(resultingSource).to.equal(stripIndent`
it('test', () => {
cy.origin('http://foobar.com:3500', {
cy.origin('http://www.foobar.com:3500', {
"callbackName": "__cypressCrossOriginCallback",
"outputFilePath": "/path/to/tmp/cross-origin-cb-abc123.js"
});
Expand All @@ -112,15 +112,15 @@ describe('./lib/cross-origin-callback-loader', () => {
it('replaces cy.other() when specified in commands', () => {
const { resultingSource, resultingMap } = callLoader(stripIndent`
it('test', () => {
cy.other('http://foobar.com:3500', () => {
cy.other('http://www.foobar.com:3500', () => {
Cypress.require('../support/utils')
})
})`,
['other'])

expect(resultingSource).to.equal(stripIndent`
it('test', () => {
cy.other('http://foobar.com:3500', {
cy.other('http://www.foobar.com:3500', {
"callbackName": "__cypressCrossOriginCallback",
"outputFilePath": "/path/to/tmp/cross-origin-cb-abc123.js"
});
Expand All @@ -132,7 +132,7 @@ describe('./lib/cross-origin-callback-loader', () => {
it('adds the file to the store, replacing Cypress.require() with require()', () => {
const { store } = callLoader(
`it('test', () => {
cy.origin('http://foobar.com:3500', () => {
cy.origin('http://www.foobar.com:3500', () => {
Cypress.require('../support/utils')
})
})`,
Expand All @@ -148,7 +148,7 @@ describe('./lib/cross-origin-callback-loader', () => {
it('works when callback is a function expression', () => {
const { store } = callLoader(
`it('test', () => {
cy.origin('http://foobar.com:3500', function () {
cy.origin('http://www.foobar.com:3500', function () {
Cypress.require('../support/utils')
})
})`,
Expand All @@ -163,7 +163,7 @@ describe('./lib/cross-origin-callback-loader', () => {
it('works when dep is not assigned to a variable', () => {
const { store } = callLoader(
`it('test', () => {
cy.origin('http://foobar.com:3500', () => {
cy.origin('http://www.foobar.com:3500', () => {
Cypress.require('../support/utils')
})
})`,
Expand All @@ -178,7 +178,7 @@ describe('./lib/cross-origin-callback-loader', () => {
it('works when dep is assigned to a variable', () => {
const { store } = callLoader(
`it('test', () => {
cy.origin('http://foobar.com:3500', () => {
cy.origin('http://www.foobar.com:3500', () => {
const utils = Cypress.require('../support/utils')
utils.foo()
})
Expand All @@ -196,7 +196,7 @@ describe('./lib/cross-origin-callback-loader', () => {
it('works with multiple Cypress.require()s', () => {
const { store } = callLoader(
`it('test', () => {
cy.origin('http://foobar.com:3500', () => {
cy.origin('http://www.foobar.com:3500', () => {
Cypress.require('../support/commands')
const utils = Cypress.require('../support/utils')
const _ = Cypress.require('lodash')
Expand All @@ -219,7 +219,7 @@ describe('./lib/cross-origin-callback-loader', () => {
`it('test', () => {
cy
.wrap({})
.origin('http://foobar.com:3500', () => {
.origin('http://www.foobar.com:3500', () => {
Cypress.require('../support/commands')
})
})`,
Expand All @@ -234,7 +234,7 @@ describe('./lib/cross-origin-callback-loader', () => {
it('works when result of require() is invoked', () => {
const { store } = callLoader(
`it('test', () => {
cy.origin('http://foobar.com:3500', () => {
cy.origin('http://www.foobar.com:3500', () => {
const someVar = 'someValue'
const result = Cypress.require('./fn')(someVar)
expect(result).to.equal('mutated someVar')
Expand All @@ -255,7 +255,7 @@ describe('./lib/cross-origin-callback-loader', () => {
it('works when dependencies passed into called', () => {
const { store } = callLoader(
`it('test', () => {
cy.origin('http://foobar.com:3500', { args: { foo: 'foo'}}, ({ foo }) => {
cy.origin('http://www.foobar.com:3500', { args: { foo: 'foo'}}, ({ foo }) => {
const result = Cypress.require('./fn')(foo)
expect(result).to.equal('mutated someVar')
})
Expand Down
8 changes: 4 additions & 4 deletions packages/app/src/runner/aut-iframe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,9 @@ export class AutIframe {
}

/**
* If the AUT is cross origin relative to top, a security error is thrown and the method returns false
* If the AUT is cross origin relative to top and chromeWebSecurity is false, origins of the AUT and top need to be compared and returns false
* Otherwise, if top and the AUT match origins, the method returns true.
* If the AUT is cross super domain origin relative to top, a security error is thrown and the method returns false
* If the AUT is cross super domain origin relative to top and chromeWebSecurity is false, origins of the AUT and top need to be compared and returns false
* Otherwise, if top and the AUT match super domain origins, the method returns true.
* If the AUT origin is "about://blank", that means the src attribute has been stripped off the iframe and is adhering to same origin policy
*/
doesAUTMatchTopSuperDomainOrigin = () => {
Expand Down Expand Up @@ -163,7 +163,7 @@ export class AutIframe {
})

// The iframe is in a cross origin state.
// Remove the src attribute to adhere to same super domain origin policy so we can interact with the frame. NOTE: This should only be done ONCE.
// Remove the src attribute to adhere to same super domain origin so we can interact with the frame. NOTE: This should only be done ONCE.
this.removeSrcAttribute()

return
Expand Down
20 changes: 10 additions & 10 deletions packages/app/src/runner/event-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -611,8 +611,8 @@ export class EventManager {
})

// Reflect back to the requesting origin the status of the 'duringUserTestExecution' state
Cypress.primaryOriginCommunicator.on('sync:during:user:test:execution', ({ specBridgeResponseEvent }, superDomainOrigin) => {
Cypress.primaryOriginCommunicator.toSpecBridge(superDomainOrigin, specBridgeResponseEvent, cy.state('duringUserTestExecution'))
Cypress.primaryOriginCommunicator.on('sync:during:user:test:execution', ({ specBridgeResponseEvent }, origin) => {
Cypress.primaryOriginCommunicator.toSpecBridge(origin, specBridgeResponseEvent, cy.state('duringUserTestExecution'))
})

Cypress.on('request:snapshot:from:spec:bridge', ({ log, name, options, specBridge, addSnapshot }: {
Expand Down Expand Up @@ -653,22 +653,22 @@ export class EventManager {
Cypress.primaryOriginCommunicator.toAllSpecBridges('before:unload', origin)
})

Cypress.primaryOriginCommunicator.on('expect:origin', (superDomainOrigin) => {
this.localBus.emit('expect:origin', superDomainOrigin)
Cypress.primaryOriginCommunicator.on('expect:origin', (origin) => {
this.localBus.emit('expect:origin', origin)
})

Cypress.primaryOriginCommunicator.on('viewport:changed', (viewport, superDomainOrigin) => {
Cypress.primaryOriginCommunicator.on('viewport:changed', (viewport, origin) => {
const callback = () => {
Cypress.primaryOriginCommunicator.toSpecBridge(superDomainOrigin, 'viewport:changed:end')
Cypress.primaryOriginCommunicator.toSpecBridge(origin, 'viewport:changed:end')
}

Cypress.primaryOriginCommunicator.emit('sync:viewport', viewport)
this.localBus.emit('viewport:changed', viewport, callback)
})

Cypress.primaryOriginCommunicator.on('before:screenshot', (config, superDomainOrigin) => {
Cypress.primaryOriginCommunicator.on('before:screenshot', (config, origin) => {
const callback = () => {
Cypress.primaryOriginCommunicator.toSpecBridge(superDomainOrigin, 'before:screenshot:end')
Cypress.primaryOriginCommunicator.toSpecBridge(origin, 'before:screenshot:end')
}

handleBeforeScreenshot(config, callback)
Expand Down Expand Up @@ -861,9 +861,9 @@ export class EventManager {
this.ws.emit('spec:changed', specFile)
}

notifyCrossOriginBridgeReady (superDomainOrigin) {
notifyCrossOriginBridgeReady (origin) {
// Any multi-origin event appends the origin as the third parameter and we do the same here for this short circuit
Cypress.primaryOriginCommunicator.emit('bridge:ready', undefined, superDomainOrigin)
Cypress.primaryOriginCommunicator.emit('bridge:ready', undefined, origin)
}

snapshotUnpinned () {
Expand Down
6 changes: 3 additions & 3 deletions packages/app/src/runner/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,11 +194,11 @@ export async function teardown () {
* Add a cross origin iframe for cy.origin support
*/
export function addCrossOriginIframe (location) {
const id = `Spec Bridge: ${location.superDomainOrigin}`
const id = `Spec Bridge: ${location.origin}`

// if it already exists, don't add another one
if (document.getElementById(id)) {
getEventManager().notifyCrossOriginBridgeReady(location.superDomainOrigin)
getEventManager().notifyCrossOriginBridgeReady(location.origin)

return
}
Expand All @@ -209,7 +209,7 @@ export function addCrossOriginIframe (location) {
// container since it needs to match the size of the top window for screenshots
$container: document.body,
className: 'spec-bridge-iframe',
src: `${location.superDomainOrigin}/${getRunnerConfigFromWindow().namespace}/spec-bridge-iframes`,
src: `${location.origin}/${getRunnerConfigFromWindow().namespace}/spec-bridge-iframes`,
})
}

Expand Down
8 changes: 4 additions & 4 deletions packages/driver/cypress/e2e/commands/navigation.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -1553,7 +1553,7 @@ describe('src/cy/commands/navigation', () => {
${experimentalMessage}
\`cy.visit('http://localhost:3500/fixtures/generic.html')\`
\`<commands targeting http://localhost:3500 go here>\`\n
\`cy.origin('http://foobar.com:3500', () => {\`
\`cy.origin('http://www.foobar.com:3500', () => {\`
\` cy.visit('http://www.foobar.com:3500/fixtures/generic.html')\`
\` <commands targeting http://www.foobar.com:3500 go here>\`
\`})\`\n
Expand Down Expand Up @@ -2241,10 +2241,10 @@ describe('src/cy/commands/navigation', () => {
expect(err.message).to.contain(stripIndent`\
Cypress detected a cross origin error happened on page load:\n
> ${error}\n
Before the page load, you were bound to the origin policy:\n
Before the page load, you were bound to the origin:\n
> http://localhost:3500\n
A cross origin error happens when your application navigates to a new URL which does not match the origin policy above.\n
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.\n
A cross origin error happens when your application navigates to a new URL which does not match the origin above.\n
A new URL does not match the origin if the 'protocol', 'port' (if specified), and/or 'host' are different.\n
Cypress does not allow you to navigate to a different origin URL within a single test.\n
You may need to restructure some of your test code to avoid this problem.\n
Alternatively you can also disable Chrome Web Security in Chromium-based browsers which will turn off this restriction by setting { chromeWebSecurity: false }`)
Expand Down
34 changes: 17 additions & 17 deletions packages/driver/cypress/e2e/e2e/origin/basic_login.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ describe('basic login', () => {
it('logs in with idp redirect', () => {
cy.visit('/fixtures/auth/index.html') // Establishes primary origin
cy.get('[data-cy="login-idp"]').click() // Takes you to idp.com
cy.origin('http://idp.com:3500', () => {
cy.origin('http://www.idp.com:3500', () => {
cy.get('[data-cy="username"]').type('BJohnson')
cy.get('[data-cy="login"]').click()
})
Expand All @@ -23,7 +23,7 @@ describe('basic login', () => {
win.location.href = 'http://www.idp.com:3500/fixtures/auth/idp.html'
})

cy.origin('http://idp.com:3500', () => {
cy.origin('http://www.idp.com:3500', () => {
cy.get('[data-cy="username"]').type('FJohnson')
cy.get('[data-cy="login"]').click()
})
Expand All @@ -37,7 +37,7 @@ describe('basic login', () => {
it('visits foobar first', () => {
cy.visit('http://www.foobar.com:3500/fixtures/auth/index.html') // Establishes primary origin
cy.get('[data-cy="login-idp"]').click() // Takes you to idp.com
cy.origin('http://idp.com:3500', () => {
cy.origin('http://www.idp.com:3500', () => {
cy.get('[data-cy="username"]').type('BJohnson')
cy.get('[data-cy="login"]').click()
})
Expand All @@ -60,7 +60,7 @@ describe('basic login', () => {
// Primary established via base url
// TODO: baseUrl does not establish primary without a visit
it.skip('logs in with primary set via baseurl', { baseUrl: 'http://localhost:3500' }, () => {
cy.origin('http://idp.com:3500', () => { // primary origin is localhost
cy.origin('http://www.idp.com:3500', () => { // primary origin is localhost
cy.visit('http://www.idp.com:3500/fixtures/auth/idp.html')
cy.get('[data-cy="username"]').type('FJohnson')
cy.get('[data-cy="login"]').click()
Expand All @@ -77,7 +77,7 @@ describe('basic login', () => {

it('logs in with primary set via visit', () => {
cy.visit('/fixtures/auth/index.html')
cy.origin('http://idp.com:3500', () => { // primary origin is localhost
cy.origin('http://www.idp.com:3500', () => { // primary origin is localhost
cy.visit('http://www.idp.com:3500/fixtures/auth/idp.html')
cy.get('[data-cy="username"]').type('FJohnson')
cy.get('[data-cy="login"]').click()
Expand All @@ -94,7 +94,7 @@ describe('basic login', () => {
const login = (name) => {
cy.session(name, () => {
// Note, this assumes localhost is the primary origin, ideally we'd be able to specify this directly.
cy.origin('http://idp.com:3500', { args: name }, (name) => {
cy.origin('http://www.idp.com:3500', { args: name }, (name) => {
cy.visit('http://www.idp.com:3500/fixtures/auth/idp.html')
cy.get('[data-cy="username"]').type(name)
cy.get('[data-cy="login"]').click()
Expand Down Expand Up @@ -154,9 +154,9 @@ describe('Multi-step Auth', () => {
cy.visit('/fixtures/auth/index.html')
cy.get('[data-cy="login-with-approval"]').click() // takes you to foobar.com.../approval
cy.url() //fail
cy.origin('http://foobar.com:3500', () => { // Parent origin is localhost
cy.origin('http://www.foobar.com:3500', () => { // Parent origin is localhost
cy.get('[data-cy="approve-orig"]').click() // takes you to idp.com
cy.origin('http://idp.com:3500', () => { // Parent origin is foobar.com
cy.origin('http://www.idp.com:3500', () => { // Parent origin is foobar.com
cy.get('[data-cy="username"]').type('MarkyMark')
cy.get('[data-cy="login"]').click() // Takes you back to localhost
}) // Does not wait on foobar.com because there are no subsequent commands (would wait forever)
Expand All @@ -172,11 +172,11 @@ describe('Multi-step Auth', () => {
it.skip('final-auth redirects back to localhost - flat', () => {
cy.visit('/fixtures/auth/index.html')
cy.get('[data-cy="login-with-approval"]').click() // takes you to foobar.com.../approval
cy.origin('http://foobar.com:3500', () => { // Parent origin is localhost
cy.origin('http://www.foobar.com:3500', () => { // Parent origin is localhost
cy.get('[data-cy="approve-orig"]').click() // takes you to idp.com
}) // Exits and moves on to the next command

cy.origin('http://idp.com:3500', () => { // Parent origin is localhost
cy.origin('http://www.idp.com:3500', () => { // Parent origin is localhost
cy.get('[data-cy="username"]').type('MarkyMark')
cy.get('[data-cy="login"]').click() // Takes you back to localhost
}) // Exits and moves on to the next command
Expand All @@ -189,10 +189,10 @@ describe('Multi-step Auth', () => {

// TODO: cy.origin does not work in cy.origin yet.
it.skip('final auth redirects back to localhost - nested - approval first', () => {
cy.origin('http://foobar.com:3500', () => { // parent origin is localhost
cy.origin('http://www.foobar.com:3500', () => { // parent origin is localhost
cy.visit('http://www.foobar.com:3500/fixtures/auth/approval.html')
cy.get('[data-cy="approve-orig"]').click() // takes you to idp.com
cy.origin('http://idp.com:3500', () => { // parent origin is foobar.com
cy.origin('http://www.idp.com:3500', () => { // parent origin is foobar.com
cy.get('[data-cy="username"]').type('MarkyMark')
cy.get('[data-cy="login"]').click() // Takes you back to localhost
}) // Does not wait on foobar.com because there are no subsequent commands (would wait forever)
Expand All @@ -208,9 +208,9 @@ describe('Multi-step Auth', () => {
it.skip('final auth redirects back to approval page - nested', () => {
cy.visit('/fixtures/auth/index.html')
cy.get('[data-cy="login-with-approval"]').click() // takes you to foobar.com.../approval
cy.origin('http://foobar.com:3500', () => { // parent origin is localhost
cy.origin('http://www.foobar.com:3500', () => { // parent origin is localhost
cy.get('[data-cy="approve-me"]').click() // takes you to idp.com
cy.origin('http://idp.com:3500', () => { // parent origin is foobar.com
cy.origin('http://www.idp.com:3500', () => { // parent origin is foobar.com
cy.get('[data-cy="username"]').type('MarkyMark')
cy.get('[data-cy="login"]').click() // Takes you back to foobar.com.../approval
}) // Exits and moves on to the next command
Expand All @@ -227,16 +227,16 @@ describe('Multi-step Auth', () => {
it('final auth redirects back to approval page - flat', () => {
cy.visit('/fixtures/auth/index.html')
cy.get('[data-cy="login-with-approval"]').click() // takes you to foobar.com.../approval
cy.origin('http://foobar.com:3500', () => { // parent origin is localhost
cy.origin('http://www.foobar.com:3500', () => { // parent origin is localhost
cy.get('[data-cy="approve-me"]').click() // takes you to idp.com
}) // waits on localhost forever, this breaks

cy.origin('http://idp.com:3500', () => { // parent origin is localhost
cy.origin('http://www.idp.com:3500', () => { // parent origin is localhost
cy.get('[data-cy="username"]').type('MarkyMark')
cy.get('[data-cy="login"]').click() // Takes you back to foobar.com.../approval
}) // Exits and moves on to the next command

cy.origin('http://foobar.com:3500', () => { // parent origin is localhost
cy.origin('http://www.foobar.com:3500', () => { // parent origin is localhost
cy.get('[data-cy="login-success"]').click() // Takes you back to localhost
}) // Exits and moves on to the next command

Expand Down
Loading

5 comments on commit 695dd27

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 695dd27 Oct 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the linux x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/10.10.0/linux-x64/develop-695dd275bcca75543fccefb92afe6bc7700f15ef/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 695dd27 Oct 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the linux arm64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/10.10.0/linux-arm64/develop-695dd275bcca75543fccefb92afe6bc7700f15ef/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 695dd27 Oct 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the darwin arm64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/10.10.0/darwin-arm64/develop-695dd275bcca75543fccefb92afe6bc7700f15ef/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 695dd27 Oct 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the darwin x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/10.10.0/darwin-x64/develop-695dd275bcca75543fccefb92afe6bc7700f15ef/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 695dd27 Oct 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the win32 x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/10.10.0/win32-x64/develop-695dd275bcca75543fccefb92afe6bc7700f15ef/cypress.tgz

Please sign in to comment.