Skip to content

Commit

Permalink
Merge branch 'feature-multidomain' into md-support-snapshots-and-cons…
Browse files Browse the repository at this point in the history
…ole-props
  • Loading branch information
AtofStryker committed Apr 7, 2022
2 parents 3267dba + 782d89f commit b06f58b
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 65 deletions.
4 changes: 2 additions & 2 deletions cli/types/cypress.d.ts
Expand Up @@ -1906,7 +1906,7 @@ declare namespace Cypress {
* cy.get('h1').should('equal', 'Example Domain')
* })
*/
origin(originOrDomain: string, fn: () => void): Chainable
origin(urlOrDomain: string, fn: () => void): Chainable

// TODO: when we find other options to put into the 'data' argument of cy.origin, we may want to overload this type with
// a 'data' parameter that contains all data options, including args, and one that contains all data options, excluding args.
Expand All @@ -1920,7 +1920,7 @@ declare namespace Cypress {
* expect(foo).to.equal('foo')
* })
*/
origin<T>(originOrDomain: string, options: {
origin<T>(urlOrDomain: string, options: {
args: T
}, fn: (args: T) => void): Chainable

Expand Down
Expand Up @@ -113,20 +113,38 @@ context('cy.origin navigation', () => {
cy.get('[data-cy="dom-check"]').should('have.text', 'From a secondary origin')

cy.visit('http://www.foobar.com:3500/fixtures/dom.html')
cy.location('href').should('equal', 'http://www.foobar.com:3500/fixtures/dom.html')
})
})

it('supports relative urls within secondary', () => {
cy.visit('/fixtures/multi-domain.html')

cy.get('a[data-cy="cross-origin-secondary-link"]').click()

cy.origin('http://www.foobar.com:3500', () => {
cy.visit('/fixtures/dom.html')
cy.location('href').should('equal', 'http://www.foobar.com:3500/fixtures/dom.html')
})
})

it('supports relative urls with path within secondary', () => {
cy.origin('http://www.foobar.com:3500/fixtures', () => {
cy.visit('/dom.html')
cy.location('href').should('equal', 'http://www.foobar.com:3500/fixtures/dom.html')
})
})

it('supports relative urls with hash within secondary', () => {
cy.origin('http://www.foobar.com:3500/#hash', () => {
cy.visit('/more-hash')
cy.location('href').should('equal', 'http://www.foobar.com:3500/#hash/more-hash')
})
})

it('supports relative urls with path and hash within secondary', () => {
cy.origin('http://www.foobar.com:3500/welcome/#hash', () => {
cy.visit('/more-hash')
cy.location('href').should('equal', 'http://www.foobar.com:3500/welcome/#hash/more-hash')
})
})

it('supports hash change within secondary', () => {
cy.visit('/fixtures/multi-domain.html')

Expand Down
Expand Up @@ -42,7 +42,7 @@ describe('cy.origin', () => {
})

it('succeeds on a complete origin', () => {
cy.origin('http://foobar1.com:3500', () => {})
cy.origin('http://foobar1.com:3500', () => undefined)
cy.then(() => {
const expectedSrc = `http://foobar1.com:3500/__cypress/multi-domain-iframes`
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ http://foobar1.com:3500') as HTMLIFrameElement
Expand All @@ -52,7 +52,7 @@ describe('cy.origin', () => {
})

it('succeeds on a complete origin using https', () => {
cy.origin('https://foobar2.com:3500', () => {})
cy.origin('https://foobar2.com:3500', () => undefined)
cy.then(() => {
const expectedSrc = `https://foobar2.com:3500/__cypress/multi-domain-iframes`
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://foobar2.com:3500') as HTMLIFrameElement
Expand All @@ -62,7 +62,7 @@ describe('cy.origin', () => {
})

it('succeeds on a hostname and port', () => {
cy.origin('foobar3.com:3500', () => {})
cy.origin('foobar3.com:3500', () => undefined)
cy.then(() => {
const expectedSrc = `https://foobar3.com:3500/__cypress/multi-domain-iframes`
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://foobar3.com:3500') as HTMLIFrameElement
Expand All @@ -72,7 +72,7 @@ describe('cy.origin', () => {
})

it('succeeds on a protocol and hostname', () => {
cy.origin('http://foobar4.com', () => {})
cy.origin('http://foobar4.com', () => undefined)
cy.then(() => {
const expectedSrc = `http://foobar4.com/__cypress/multi-domain-iframes`
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ http://foobar4.com') as HTMLIFrameElement
Expand All @@ -82,7 +82,7 @@ describe('cy.origin', () => {
})

it('succeeds on a subdomain', () => {
cy.origin('app.foobar5.com', () => {})
cy.origin('app.foobar5.com', () => undefined)
cy.then(() => {
const expectedSrc = `https://foobar5.com/__cypress/multi-domain-iframes`
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://foobar5.com') as HTMLIFrameElement
Expand All @@ -101,8 +101,68 @@ describe('cy.origin', () => {
})
})

it('succeeds on a url with path', () => {
cy.origin('http://www.foobar7.com/login', () => undefined)
cy.then(() => {
const expectedSrc = `http://foobar7.com/__cypress/multi-domain-iframes`
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ http://foobar7.com') as HTMLIFrameElement

expect(iframe.src).to.equal(expectedSrc)
})
})

it('succeeds on a url with a hash', () => {
cy.origin('http://www.foobar8.com/#hash', () => undefined)
cy.then(() => {
const expectedSrc = `http://foobar8.com/__cypress/multi-domain-iframes`
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ http://foobar8.com') as HTMLIFrameElement

expect(iframe.src).to.equal(expectedSrc)
})
})

it('succeeds on a url with a path and hash', () => {
cy.origin('http://www.foobar9.com/login/#hash', () => undefined)
cy.then(() => {
const expectedSrc = `http://foobar9.com/__cypress/multi-domain-iframes`
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ http://foobar9.com') as HTMLIFrameElement

expect(iframe.src).to.equal(expectedSrc)
})
})

it('succeeds on a domain with path', () => {
cy.origin('foobar10.com/login', () => undefined)
cy.then(() => {
const expectedSrc = `https://foobar10.com/__cypress/multi-domain-iframes`
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://foobar10.com') as HTMLIFrameElement

expect(iframe.src).to.equal(expectedSrc)
})
})

it('succeeds on a domain with a hash', () => {
cy.origin('foobar11.com/#hash', () => undefined)
cy.then(() => {
const expectedSrc = `https://foobar11.com/__cypress/multi-domain-iframes`
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://foobar11.com') as HTMLIFrameElement

expect(iframe.src).to.equal(expectedSrc)
})
})

it('succeeds on a domain with a path and hash', () => {
cy.origin('foobar12.com/login/#hash', () => undefined)
cy.then(() => {
const expectedSrc = `https://foobar12.com/__cypress/multi-domain-iframes`
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://foobar12.com') as HTMLIFrameElement

expect(iframe.src).to.equal(expectedSrc)
})
})

it('succeeds on a public suffix with a subdomain', () => {
cy.origin('app.foobar.herokuapp.com', () => {})
cy.origin('app.foobar.herokuapp.com', () => undefined)
cy.then(() => {
const expectedSrc = `https://foobar.herokuapp.com/__cypress/multi-domain-iframes`
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://foobar.herokuapp.com') as HTMLIFrameElement
Expand All @@ -122,7 +182,7 @@ describe('cy.origin', () => {
})

it('finds the right spec bridge with a subdomain', () => {
cy.visit('/fixtures/auth/index.html') // Establishes primary origin
cy.visit('/fixtures/auth/index.html')
cy.window().then((win) => {
win.location.href = 'http://baz.foobar.com:3500/fixtures/auth/idp.html'
})
Expand All @@ -138,14 +198,13 @@ describe('cy.origin', () => {
})

it('uses cy.origin twice', () => {
cy.visit('/fixtures/auth/index.html') // Establishes primary origin
cy.get('[data-cy="login-idp"]').click() // Takes you to idp.com
cy.visit('/fixtures/auth/index.html')
cy.get('[data-cy="login-idp"]').click()
cy.origin('http://idp.com:3500', () => {
cy.get('[data-cy="username"]').type('BJohnson')
cy.get('[data-cy="login"]').click()
}) // Trailing edge wait, waiting to return to the primary origin
})

// Verify that the user has logged in on /siteA
cy.get('[data-cy="welcome"]')
.invoke('text')
.should('equal', 'Welcome BJohnson')
Expand All @@ -159,24 +218,23 @@ describe('cy.origin', () => {
cy.origin('http://foobar.com:3500', () => {
cy.get('[data-cy="username"]').type('TJohnson')
cy.get('[data-cy="login"]').click()
}) // Trailing edge wait, waiting to return to the primary origin
})

// Verify that the user has logged in on /siteA
cy.get('[data-cy="welcome"]')
.invoke('text')
.should('equal', 'Welcome TJohnson')
})

it('creates a spec bridge for https://idp.com:3502', () => {
cy.visit('/fixtures/auth/index.html') // Establishes primary origin
cy.visit('/fixtures/auth/index.html')
cy.origin('idp.com:3502', () => {
cy.visit('https://www.idp.com:3502/fixtures/auth/index.html')
cy.get('[data-cy="login-idp"]').invoke('text').should('equal', 'Login IDP')
})
})

it('creates a spec bridge for http://idp.com:3500', () => {
cy.visit('/fixtures/auth/index.html') // Establishes primary origin
cy.visit('/fixtures/auth/index.html')
cy.origin('http://idp.com:3500', () => {
cy.visit('http://www.idp.com:3500/fixtures/auth/index.html')
cy.get('[data-cy="login-idp"]').invoke('text').should('equal', 'Login IDP')
Expand All @@ -199,7 +257,7 @@ describe('cy.origin', () => {

it('errors if passed a non-string for the origin argument', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.equal('`cy.origin()` requires the first argument to be either an origin (\'https://app.example.com\') or a domain name (\'example.com\'). The origin or domain name must not contain a path, hash, or query parameters. You passed: ``')
expect(err.message).to.equal('`cy.origin()` requires the first argument to be either a url (`https://www.example.com/path`) or a domain name (`example.com`). Query parameters are not allowed. You passed: ``')

done()
})
Expand All @@ -210,32 +268,12 @@ describe('cy.origin', () => {

it('errors if query params are provided', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.equal('`cy.origin()` requires the first argument to be either an origin (\'https://app.example.com\') or a domain name (\'example.com\'). The origin or domain name must not contain a path, hash, or query parameters. You passed: `foobar.com?foo=bar`')

done()
})

cy.origin('foobar.com?foo=bar', () => undefined)
})

it('errors if passed a domain name with a path', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.equal('`cy.origin()` requires the first argument to be either an origin (\'https://app.example.com\') or a domain name (\'example.com\'). The origin or domain name must not contain a path, hash, or query parameters. You passed: `foobar.com/login`')

done()
})

cy.origin('foobar.com/login', () => undefined)
})

it('errors if passed a domain name with a hash', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.equal('`cy.origin()` requires the first argument to be either an origin (\'https://app.example.com\') or a domain name (\'example.com\'). The origin or domain name must not contain a path, hash, or query parameters. You passed: `foobar.com/#hash`')
expect(err.message).to.equal('`cy.origin()` requires the first argument to be either a url (`https://www.example.com/path`) or a domain name (`example.com`). Query parameters are not allowed. You passed: `http://www.foobar.com?foo=bar`')

done()
})

cy.origin('foobar.com/#hash', () => undefined)
cy.origin('http://www.foobar.com?foo=bar', () => undefined)
})

it('errors passing non-array to callback function', (done) => {
Expand Down
4 changes: 4 additions & 0 deletions packages/driver/cypress/plugins/server.js
Expand Up @@ -37,6 +37,10 @@ const createApp = (port) => {
return res.sendStatus(200)
})

app.get('/', (req, res) => {
return res.send('<html><body>root page</body></html>')
})

app.get('/timeout', (req, res) => {
return Promise
.delay(req.query.ms || 0)
Expand Down
20 changes: 10 additions & 10 deletions packages/driver/src/cy/multi-domain/index.ts
Expand Up @@ -9,12 +9,12 @@ import { LogUtils } from '../../cypress/log'

const reHttp = /^https?:\/\//

const normalizeOrigin = (originOrDomain) => {
let origin = originOrDomain
const normalizeOrigin = (urlOrDomain) => {
let origin = urlOrDomain

// If just a domain, convert it to an origin by adding the protocol
if (!reHttp.test(originOrDomain)) {
origin = `https://${originOrDomain}`
if (!reHttp.test(urlOrDomain)) {
origin = `https://${urlOrDomain}`
}

return $Location.normalize(origin)
Expand Down Expand Up @@ -50,7 +50,7 @@ export function addCommands (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy,
})

Commands.addAll({
origin<T> (originOrDomain: string, optionsOrFn: { args: T } | (() => {}), fn?: (args?: T) => {}) {
origin<T> (urlOrDomain: string, optionsOrFn: { args: T } | (() => {}), fn?: (args?: T) => {}) {
// store the invocation stack in the case that `cy.origin` errors
communicator.userInvocationStack = state('current').get('userInvocationStack')

Expand Down Expand Up @@ -79,7 +79,7 @@ export function addCommands (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy,
const log = Cypress.log({
name: 'origin',
type: 'parent',
message: originOrDomain,
message: urlOrDomain,
end: true,
})

Expand All @@ -93,14 +93,14 @@ export function addCommands (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy,
validator.validate({
callbackFn,
options,
originOrDomain,
urlOrDomain,
})

// use URL to ensure unicode characters are correctly handled
const url = new URL(normalizeOrigin(originOrDomain)).toString()
const url = new URL(normalizeOrigin(urlOrDomain)).toString()
const location = $Location.create(url)

validator.validateLocation(location, originOrDomain)
validator.validateLocation(location, urlOrDomain)

const originPolicy = location.originPolicy

Expand Down Expand Up @@ -221,7 +221,7 @@ export function addCommands (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy,
runnable: serializeRunnable(Cypress.state('runnable')),
duringUserTestExecution: Cypress.state('duringUserTestExecution'),
hookId: Cypress.state('hookId'),
originCommandBaseUrl: location.origin,
originCommandBaseUrl: location.href,
parentOriginPolicies: [cy.getRemoteLocation('originPolicy')],
isStable: Cypress.state('isStable'),
autOrigin: Cypress.state('autOrigin'),
Expand Down
18 changes: 9 additions & 9 deletions packages/driver/src/cy/multi-domain/validator.ts
Expand Up @@ -13,13 +13,13 @@ export class Validator {
this.onFailure = onFailure
}

validate ({ callbackFn, options, originOrDomain }) {
if (!isString(originOrDomain)) {
validate ({ callbackFn, options, urlOrDomain }) {
if (!isString(urlOrDomain)) {
this.onFailure()

$errUtils.throwErrByPath('origin.invalid_origin_argument', {
$errUtils.throwErrByPath('origin.invalid_url_argument', {
onFail: this.log,
args: { arg: $utils.stringify(originOrDomain) },
args: { arg: $utils.stringify(urlOrDomain) },
})
}

Expand Down Expand Up @@ -58,14 +58,14 @@ export class Validator {
}
}

validateLocation (location, originOrDomain) {
// we don't support query params, hashes, or paths (except for '/')
if (location.search.length > 0 || location.pathname.length > 1 || location.hash.length > 0) {
validateLocation (location, urlOrDomain) {
// we don't support query params
if (location.search.length > 0) {
this.onFailure()

$errUtils.throwErrByPath('origin.invalid_origin_argument', {
$errUtils.throwErrByPath('origin.invalid_url_argument', {
onFail: this.log,
args: { arg: $utils.stringify(originOrDomain) },
args: { arg: $utils.stringify(urlOrDomain) },
})
}
}
Expand Down
4 changes: 2 additions & 2 deletions packages/driver/src/cypress/error_messages.ts
Expand Up @@ -1730,8 +1730,8 @@ export default {
experiment_not_enabled: {
message: `${cmd('origin')} requires enabling the experimentalLoginFlows flag`,
},
invalid_origin_argument: {
message: `${cmd('origin')} requires the first argument to be either an origin ('https://app.example.com') or a domain name ('example.com'). The origin or domain name must not contain a path, hash, or query parameters. You passed: \`{{arg}}\``,
invalid_url_argument: {
message: `${cmd('origin')} requires the first argument to be either a url (\`https://www.example.com/path\`) or a domain name (\`example.com\`). Query parameters are not allowed. You passed: \`{{arg}}\``,
},
invalid_options_argument: {
message: `${cmd('origin')} requires the 'options' argument to be an object. You passed: \`{{arg}}\``,
Expand Down

0 comments on commit b06f58b

Please sign in to comment.