Skip to content

Commit

Permalink
chore: (cross-origin) add support for redirecting back to primary (#2…
Browse files Browse the repository at this point in the history
  • Loading branch information
mschile committed Apr 21, 2022
1 parent f02d750 commit d7ce865
Show file tree
Hide file tree
Showing 18 changed files with 371 additions and 219 deletions.
57 changes: 21 additions & 36 deletions packages/driver/cypress/integration/commands/navigation_spec.js
Expand Up @@ -1414,10 +1414,10 @@ describe('src/cy/commands/navigation', () => {
\`cy.visit()\` failed because you are attempting to visit a URL that is of a different origin.\n
You likely forgot to use \`cy.origin()\`:\n
\`cy.visit('http://localhost:3500/fixtures/generic.html')\`
\`<other commands targeting http://localhost:3500 go here>\`\n
\`<commands targeting http://localhost:3500 go here>\`\n
\`cy.origin('http://localhost:3501', () => {\`
\` cy.visit('http://localhost:3501/fixtures/generic.html')\`
\` <other commands targeting http://localhost:3501 go here>\`
\` <commands targeting http://localhost:3501 go here>\`
\`})\`\n
The new URL is considered a different origin because the following parts of the URL are different:\n
> port\n
Expand Down Expand Up @@ -1446,18 +1446,18 @@ describe('src/cy/commands/navigation', () => {
\`cy.visit()\` failed because you are attempting to visit a URL that is of a different origin.\n
You likely forgot to use \`cy.origin()\`:\n
\`cy.visit('http://localhost:3500/fixtures/generic.html')\`
\`<other commands targeting http://localhost:3500 go here>\`\n
\`cy.origin('https://localhost:3500', () => {\`
\` cy.visit('https://localhost:3500/fixtures/generic.html')\`
\` <other commands targeting https://localhost:3500 go here>\`
\`<commands targeting http://localhost:3500 go here>\`\n
\`cy.origin('https://localhost:3502', () => {\`
\` cy.visit('https://localhost:3502/fixtures/generic.html')\`
\` <commands targeting https://localhost:3502 go here>\`
\`})\`\n
The new URL is considered a different origin because the following parts of the URL are different:\n
> protocol\n
> protocol, port\n
You may only \`cy.visit()\` same-origin URLs within a single test.\n
The previous URL you visited was:\n
> 'http://localhost:3500'\n
You're attempting to visit this URL:\n
> 'https://localhost:3500'`)
> 'https://localhost:3502'`)

expect(err.docsUrl).to.eq('https://on.cypress.io/cannot-visit-different-origin-domain')
assertLogLength(this.logs, 2)
Expand All @@ -1467,7 +1467,7 @@ describe('src/cy/commands/navigation', () => {
})

cy.visit('http://localhost:3500/fixtures/generic.html')
cy.visit('https://localhost:3500/fixtures/generic.html')
cy.visit('https://localhost:3502/fixtures/generic.html')
})

it('throws when attempting to visit a 2nd domain on different superdomain', function (done) {
Expand All @@ -1478,18 +1478,18 @@ describe('src/cy/commands/navigation', () => {
\`cy.visit()\` failed because you are attempting to visit a URL that is of a different origin.\n
You likely forgot to use \`cy.origin()\`:\n
\`cy.visit('http://localhost:3500/fixtures/generic.html')\`
\`<other commands targeting http://localhost:3500 go here>\`\n
\`cy.origin('http://google.com:3500', () => {\`
\` cy.visit('http://google.com:3500/fixtures/generic.html')\`
\` <other commands targeting http://google.com:3500 go here>\`
\`<commands targeting http://localhost:3500 go here>\`\n
\`cy.origin('http://foobar.com:3500', () => {\`
\` cy.visit('http://www.foobar.com:3500/fixtures/generic.html')\`
\` <commands targeting http://www.foobar.com:3500 go here>\`
\`})\`\n
The new URL is considered a different origin because the following parts of the URL are different:\n
> superdomain\n
You may only \`cy.visit()\` same-origin URLs within a single test.\n
The previous URL you visited was:\n
> 'http://localhost:3500'\n
You're attempting to visit this URL:\n
> 'http://google.com:3500'`)
> 'http://www.foobar.com:3500'`)

expect(err.docsUrl).to.eq('https://on.cypress.io/cannot-visit-different-origin-domain')
assertLogLength(this.logs, 2)
Expand All @@ -1499,7 +1499,7 @@ describe('src/cy/commands/navigation', () => {
})

cy.visit('http://localhost:3500/fixtures/generic.html')
cy.visit('http://google.com:3500/fixtures/generic.html')
cy.visit('http://www.foobar.com:3500/fixtures/generic.html')
})

it('throws attempting to visit 2 unique ip addresses', function (done) {
Expand All @@ -1510,18 +1510,18 @@ describe('src/cy/commands/navigation', () => {
\`cy.visit()\` failed because you are attempting to visit a URL that is of a different origin.\n
You likely forgot to use \`cy.origin()\`:\n
\`cy.visit('http://127.0.0.1:3500/fixtures/generic.html')\`
\`<other commands targeting http://127.0.0.1:3500 go here>\`\n
\`cy.origin('http://126.0.0.1:3500', () => {\`
\` cy.visit('http://126.0.0.1:3500/fixtures/generic.html')\`
\` <other commands targeting http://126.0.0.1:3500 go here>\`
\`<commands targeting http://127.0.0.1:3500 go here>\`\n
\`cy.origin('http://0.0.0.0:3500', () => {\`
\` cy.visit('http://0.0.0.0:3500/fixtures/generic.html')\`
\` <commands targeting http://0.0.0.0:3500 go here>\`
\`})\`\n
The new URL is considered a different origin because the following parts of the URL are different:\n
> superdomain\n
You may only \`cy.visit()\` same-origin URLs within a single test.\n
The previous URL you visited was:\n
> 'http://127.0.0.1:3500'\n
You're attempting to visit this URL:\n
> 'http://126.0.0.1:3500'`)
> 'http://0.0.0.0:3500'`)

expect(err.docsUrl).to.eq('https://on.cypress.io/cannot-visit-different-origin-domain')
assertLogLength(this.logs, 2)
Expand All @@ -1532,22 +1532,7 @@ describe('src/cy/commands/navigation', () => {

cy
.visit('http://127.0.0.1:3500/fixtures/generic.html')
.visit('http://126.0.0.1:3500/fixtures/generic.html')
})

it('does not call resolve:url when throws attempting to visit a 2nd domain', (done) => {
const backend = cy.spy(Cypress, 'backend')

cy.on('fail', (err) => {
expect(backend).to.be.calledWithMatch('resolve:url', 'http://localhost:3500/fixtures/generic.html')
expect(backend).not.to.be.calledWithMatch('resolve:url', 'http://google.com:3500/fixtures/generic.html')

done()
})

cy
.visit('http://localhost:3500/fixtures/generic.html')
.visit('http://google.com:3500/fixtures/generic.html')
.visit('http://0.0.0.0:3500/fixtures/generic.html')
})

it('displays loading_network_failed when _resolveUrl throws', function (done) {
Expand Down
Expand Up @@ -178,11 +178,11 @@ context('cy.origin navigation', () => {
You likely forgot to use \`cy.origin()\`:\n
\`cy.origin('http://foobar.com:3500', () => {\`
\` cy.visit('http://www.foobar.com:3500/fixtures/multi-domain-secondary.html')\`
\` <other commands targeting http://www.foobar.com:3500 go here>\`
\` <commands targeting http://www.foobar.com:3500 go here>\`
\`})\`\n
\`cy.origin('http://idp.com:3500', () => {\`
\` cy.visit('http://www.idp.com:3500/fixtures/dom.html')\`
\` <other commands targeting http://www.idp.com:3500 go here>\`
\` <commands targeting http://www.idp.com:3500 go here>\`
\`})\`\n
The new URL is considered a different origin because the following parts of the URL are different:\n
> superdomain\n
Expand Down Expand Up @@ -211,10 +211,10 @@ context('cy.origin navigation', () => {
\`cy.visit()\` failed because you are attempting to visit a URL that is of a different origin.\n
In order to visit a different origin, you can enable the \`experimentalSessionAndOrigin\` flag and use \`cy.origin()\`:\n
\`cy.visit('http://localhost:3500/fixtures/multi-domain.html')\`
\`<other commands targeting http://localhost:3500 go here>\`\n
\`<commands targeting http://localhost:3500 go here>\`\n
\`cy.origin('http://foobar.com:3500', () => {\`
\` cy.visit('http://www.foobar.com:3500/fixtures/dom.html')\`
\` <other commands targeting http://www.foobar.com:3500 go here>\`
\` <commands targeting http://www.foobar.com:3500 go here>\`
\`})\`\n
The new URL is considered a different origin because the following parts of the URL are different:\n
> superdomain\n
Expand Down Expand Up @@ -312,7 +312,7 @@ context('cy.origin navigation', () => {
})
})

it('supports visit redirects', () => {
it('supports redirecting from primary to secondary in cy.origin', () => {
cy.visit('/fixtures/multi-domain.html')

cy.origin('http://www.foobar.com:3500', () => {
Expand All @@ -321,6 +321,79 @@ context('cy.origin navigation', () => {
})
})

it('supports redirecting from secondary to primary outside of cy.origin', () => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('http://www.foobar.com:3500/redirect?href=http://localhost:3500/fixtures/generic.html')
})

it('errors when trying to redirect from secondary to primary in cy.origin', (done) => {
cy.on('fail', (e) => {
expect(e.message).to.equal(stripIndent`
\`cy.visit()\` failed because you are attempting to visit a URL from a previous origin inside of \`cy.origin()\`.\n
Instead of placing the \`cy.visit()\` inside of \`cy.origin()\`, the \`cy.visit()\` should be placed outside of the \`cy.origin()\` block.\n
\`<commands targeting http://localhost:3500 go here>\`\n
\`cy.origin('http://foobar.com:3500', () => {\`
\` <commands targeting http://foobar.com:3500 go here>\`
\`})\`\n
\`cy.visit('http://www.foobar.com:3500/redirect?href=http://localhost:3500/fixtures/generic.html')\``)

done()
})

cy.visit('http://localhost:3500/fixtures/multi-domain.html')

cy.origin('http://www.foobar.com:3500', () => {
cy.visit('/redirect?href=http://localhost:3500/fixtures/generic.html')
})
})

it('errors when trying to visit primary in cy.origin', (done) => {
cy.on('fail', (e) => {
expect(e.message).to.equal(stripIndent`
\`cy.visit()\` failed because you are attempting to visit a URL from a previous origin inside of \`cy.origin()\`.\n
Instead of placing the \`cy.visit()\` inside of \`cy.origin()\`, the \`cy.visit()\` should be placed outside of the \`cy.origin()\` block.\n
\`<commands targeting http://localhost:3500 go here>\`\n
\`cy.origin('http://foobar.com:3500', () => {\`
\` <commands targeting http://foobar.com:3500 go here>\`
\`})\`\n
\`cy.visit('http://localhost:3500/fixtures/generic.html')\``)

done()
})

cy.visit('http://localhost:3500/fixtures/multi-domain.html')

cy.origin('http://www.foobar.com:3500', () => {
cy.visit('http://localhost:3500/fixtures/generic.html')
})
})

it('errors when trying to redirect from primary to secondary outside of cy.origin', (done) => {
cy.on('fail', (e) => {
expect(e.message).to.equal(stripIndent`\
\`cy.visit()\` failed because you are attempting to visit a URL that is of a different origin.\n
You likely forgot to use \`cy.origin()\`:\n
\`cy.visit('http://localhost:3500/fixtures/multi-domain.html')\`
\`<commands targeting http://localhost:3500 go here>\`\n
\`cy.origin('http://foobar.com:3500', () => {\`
\` cy.visit('http://localhost:3500/redirect?href=http://www.foobar.com:3500/fixtures/generic.html')\`
\` <commands targeting http://www.foobar.com:3500 go here>\`
\`})\`\n
The new URL is considered a different origin because the following parts of the URL are different:\n
> superdomain\n
You may only \`cy.visit()\` same-origin URLs within a single test.\n
The previous URL you visited was:\n
> 'http://localhost:3500'\n
You're attempting to visit this URL:\n
> 'http://www.foobar.com:3500'`)

done()
})

cy.visit('/fixtures/multi-domain.html')
cy.visit('http://localhost:3500/redirect?href=http://www.foobar.com:3500/fixtures/generic.html')
})

it('supports auth options and adding auth to subsequent requests', () => {
cy.origin('http://foobar.com:3500', () => {
cy.visit('http://www.foobar.com:3500/basic_auth', {
Expand Down
61 changes: 36 additions & 25 deletions packages/driver/src/cy/commands/navigation.ts
Expand Up @@ -90,7 +90,7 @@ const timedOutWaitingForPageLoad = (ms, log) => {
}
}

const cannotVisitDifferentOrigin = ({ remote, existing, previousUrlVisited, log, isCrossOriginSpecBridge = false }) => {
const cannotVisitDifferentOrigin = ({ remote, existing, originalUrl, previousUrlVisited, log, isCrossOriginSpecBridge = false }) => {
const differences: string[] = []

if (remote.protocol !== existing.protocol) {
Expand All @@ -111,6 +111,7 @@ const cannotVisitDifferentOrigin = ({ remote, existing, previousUrlVisited, log,
differences: differences.join(', '),
previousUrl: previousUrlVisited,
attemptedUrl: remote,
originalUrl,
isCrossOriginSpecBridge,
experimentalSessionAndOrigin: Cypress.config('experimentalSessionAndOrigin'),
},
Expand All @@ -122,6 +123,22 @@ const cannotVisitDifferentOrigin = ({ remote, existing, previousUrlVisited, log,
$errUtils.throwErrByPath('visit.cannot_visit_different_origin', errOpts)
}

const cannotVisitPreviousOrigin = ({ remote, originalUrl, previousUrlVisited, log }) => {
const errOpts = {
onFail: log,
args: {
attemptedUrl: remote,
previousUrl: previousUrlVisited,
originalUrl,
},
errProps: {
isCrossOrigin: true,
},
}

$errUtils.throwErrByPath('origin.cannot_visit_previous_origin', errOpts)
}

const specifyFileByRelativePath = (url, log) => {
$errUtils.throwErrByPath('visit.specify_file_by_relative_path', {
onFail: log,
Expand Down Expand Up @@ -494,6 +511,7 @@ const normalizeOptions = (options) => {
.extend({
timeout: options.responseTimeout,
isCrossOrigin: Cypress.isCrossOriginSpecBridge,
hasAlreadyVisitedUrl: options.hasAlreadyVisitedUrl,
})
.value()
}
Expand Down Expand Up @@ -833,6 +851,8 @@ export default (Commands, Cypress, cy, state, config) => {
onLoad () {},
})

options.hasAlreadyVisitedUrl = !!previousUrlVisited

if (!_.isUndefined(options.qs) && !_.isObject(options.qs)) {
$errUtils.throwErrByPath('visit.invalid_qs', { args: { qs: String(options.qs) } })
}
Expand Down Expand Up @@ -1026,17 +1046,6 @@ export default (Commands, Cypress, cy, state, config) => {
const existingHash = remote.hash || ''
const existingAuth = remote.auth || ''

if (previousUrlVisited && (remote.originPolicy !== existing.originPolicy)) {
// if we've already visited a new superDomain
// then die else we'd be in a terrible endless loop
// we also need to disable retries to prevent the endless loop
$utils.getTestFromRunnable(state('runnable'))._retries = 0

const params = { remote, existing, previousUrlVisited, log: options._log }

return cannotVisitDifferentOrigin(params)
}

// in a cross origin spec bridge, the window may not have been set yet if nothing has been loaded in the secondary origin,
// it's also possible for a new test to start and for a cross-origin failure to occur if the win is set but
// the AUT hasn't yet navigated to the secondary origin
Expand Down Expand Up @@ -1082,7 +1091,7 @@ export default (Commands, Cypress, cy, state, config) => {

return requestUrl(url, options)
.then((resp: any = {}) => {
let { url, originalUrl, cookies, redirects, filePath } = resp
let { url, originalUrl, cookies, redirects, filePath, isPrimaryOrigin } = resp

// reapply the existing hash
url += existingHash
Expand Down Expand Up @@ -1114,7 +1123,6 @@ export default (Commands, Cypress, cy, state, config) => {

// if the origin currently matches
// then go ahead and change the iframe's src
// and we're good to go
if (remote.originPolicy === existing.originPolicy) {
previousUrlVisited = remote

Expand All @@ -1126,22 +1134,25 @@ export default (Commands, Cypress, cy, state, config) => {
})
}

// if we are in a cross origin spec bridge and the origin policies weren't the same,
// we need to throw an error since the user tried to visit a new
// origin which isn't allowed within a cy.origin block
if (Cypress.isCrossOriginSpecBridge && win) {
const existingAutOrigin = $Location.create(win.location.href)
const params = { remote, existing, previousUrlVisited: existingAutOrigin, log: options._log, isCrossOriginSpecBridge: true }
// if we've already cy.visit'ed in the test and we are visiting a new origin,
// throw an error, else we'd be in a endless loop,
// we also need to disable retries to prevent the endless loop
if (previousUrlVisited) {
$utils.getTestFromRunnable(state('runnable'))._retries = 0

const params = { remote, existing, originalUrl, previousUrlVisited, log: options._log }

return cannotVisitDifferentOrigin(params)
}

// if we've already visited a new origin
// then die else we'd be in a terrible endless loop
if (previousUrlVisited) {
const params = { remote, existing, previousUrlVisited, log: options._log }
// if we are in a cross origin spec bridge and the origin policies weren't the same,
// we need to throw an error since the user tried to visit a new
// origin which isn't allowed within a cy.origin block
if (Cypress.isCrossOriginSpecBridge) {
const existingAutOrigin = win ? $Location.create(win.location.href) : $Location.create(Cypress.state('currentActiveOriginPolicy'))
const params = { remote, existing, originalUrl, previousUrlVisited: existingAutOrigin, log: options._log, isCrossOriginSpecBridge: true, isPrimaryOrigin }

return cannotVisitDifferentOrigin(params)
return isPrimaryOrigin ? cannotVisitPreviousOrigin(params) : cannotVisitDifferentOrigin(params)
}

// tell our backend we're changing origins
Expand Down

3 comments on commit d7ce865

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on d7ce865 Apr 21, 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/9.6.0/linux-x64/feature-multidomain-d7ce86541db67561a51bc20d165144e27b5b1423/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on d7ce865 Apr 21, 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/9.6.0/darwin-x64/feature-multidomain-d7ce86541db67561a51bc20d165144e27b5b1423/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on d7ce865 Apr 21, 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/9.6.0/win32-x64/feature-multidomain-d7ce86541db67561a51bc20d165144e27b5b1423/cypress.tgz

Please sign in to comment.