From 249cfde3e20343a918cb36e2dfaf4b930f978b19 Mon Sep 17 00:00:00 2001 From: Bill Glesias Date: Mon, 13 Jan 2025 15:57:32 -0500 Subject: [PATCH] fix: make sure scrollTo, window, document, title, go, reload, location, hash, and url commands can communicate with the AUT (#30858) --- cli/CHANGELOG.md | 1 + .../e2e/e2e/origin/commands/actions.cy.ts | 104 ++++++++++++++++-- .../e2e/e2e/origin/commands/viewport.cy.ts | 6 +- .../cypress/e2e/e2e/origin/navigation.cy.ts | 10 +- .../driver/src/cy/commands/actions/scroll.ts | 3 + packages/driver/src/cy/commands/location.ts | 9 ++ packages/driver/src/cy/commands/navigation.ts | 7 ++ packages/driver/src/cy/commands/window.ts | 7 ++ packages/graphql/schemas/cloud.graphql | 6 +- .../__snapshots__/web_security_spec.js | 39 +++++-- 10 files changed, 160 insertions(+), 32 deletions(-) diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 79ff8a0fd828..3f9a6008c0ff 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -56,6 +56,7 @@ in this [GitHub issue](https://github.com/cypress-io/cypress/issues/30447). Addr - Elements whose parent elements has `overflow: clip` and no height/width will now correctly show as hidden. Fixed in [#29778](https://github.com/cypress-io/cypress/pull/29778). Fixes [#23852](https://github.com/cypress-io/cypress/issues/23852). - The CSS pseudo-class `:dir()` is now supported when testing in Electron. Addresses [#29766](https://github.com/cypress-io/cypress/issues/29766). - Fixed an issue where the spec filename was not updating correctly when changing specs in `open` mode. Fixes [#30852](https://github.com/cypress-io/cypress/issues/30852). +- `cy.origin()` now correctly errors when the [`cy.window()`](https://docs.cypress.io/api/commands/window), [`cy.document()`](https://docs.cypress.io/api/commands/document), [`cy.title()`](https://docs.cypress.io/api/commands/title), [`cy.url()`](https://docs.cypress.io/api/commands/url), [`cy.location()`](https://docs.cypress.io/api/commands/location) ,[`cy.hash()`](https://docs.cypress.io/api/commands/hash), [`cy.go()`](https://docs.cypress.io/api/commands/go), [`cy.reload()`](https://docs.cypress.io/api/commands/reload), and [`cy.scrollTo()`](https://docs.cypress.io/api/commands/scrollTo) commands are used outside of the `cy.origin()` command after the AUT has navigated away from the primary origin. Fixes [#30848](https://github.com/cypress-io/cypress/issues/30848). Fixed in [#30858](https://github.com/cypress-io/cypress/pull/30858). **Misc:** diff --git a/packages/driver/cypress/e2e/e2e/origin/commands/actions.cy.ts b/packages/driver/cypress/e2e/e2e/origin/commands/actions.cy.ts index 446ac4dcc2d0..3210ddda03cc 100644 --- a/packages/driver/cypress/e2e/e2e/origin/commands/actions.cy.ts +++ b/packages/driver/cypress/e2e/e2e/origin/commands/actions.cy.ts @@ -190,23 +190,109 @@ context('cy.origin actions', { browser: '!webkit' }, () => { context('cross-origin AUT errors', () => { // We only need to check .get here because the other commands are chained off of it. + // the exceptions are window(), document(), title(), url(), hash(), location(), go(), reload(), and scrollTo() + const assertOriginFailure = (err: Error, done: () => void) => { + expect(err.message).to.include(`The command was expected to run against origin \`http://localhost:3500\` but the application is at origin \`http://www.foobar.com:3500\`.`) + expect(err.message).to.include(`This commonly happens when you have either not navigated to the expected origin or have navigated away unexpectedly.`) + expect(err.message).to.include(`Using \`cy.origin()\` to wrap the commands run on \`http://www.foobar.com:3500\` will likely fix this issue.`) + expect(err.message).to.include(`cy.origin('http://www.foobar.com:3500', () => {\`\n\` \`\n\`})`) + + // make sure that the secondary origin failures do NOT show up as spec failures or AUT failures + expect(err.message).not.to.include(`The following error originated from your test code, not from Cypress`) + expect(err.message).not.to.include(`The following error originated from your application code, not from Cypress`) + done() + } + it('.get()', { defaultCommandTimeout: 50 }, (done) => { cy.on('fail', (err) => { expect(err.message).to.include(`Timed out retrying after 50ms:`) - expect(err.message).to.include(`The command was expected to run against origin \`http://localhost:3500\` but the application is at origin \`http://www.foobar.com:3500\`.`) - expect(err.message).to.include(`This commonly happens when you have either not navigated to the expected origin or have navigated away unexpectedly.`) - expect(err.message).to.include(`Using \`cy.origin()\` to wrap the commands run on \`http://www.foobar.com:3500\` will likely fix this issue.`) - expect(err.message).to.include(`cy.origin('http://www.foobar.com:3500', () => {\`\n\` \`\n\`})`) - - // make sure that the secondary origin failures do NOT show up as spec failures or AUT failures - expect(err.message).not.to.include(`The following error originated from your test code, not from Cypress`) - expect(err.message).not.to.include(`The following error originated from your application code, not from Cypress`) - done() + assertOriginFailure(err, done) }) cy.get('a[data-cy="dom-link"]').click() cy.get('#button') }) + + it('.window()', (done) => { + cy.on('fail', (err) => { + assertOriginFailure(err, done) + }) + + cy.get('a[data-cy="dom-link"]').click() + cy.window() + }) + + it('.document()', (done) => { + cy.on('fail', (err) => { + assertOriginFailure(err, done) + }) + + cy.get('a[data-cy="dom-link"]').click() + cy.document() + }) + + it('.title()', (done) => { + cy.on('fail', (err) => { + assertOriginFailure(err, done) + }) + + cy.get('a[data-cy="dom-link"]').click() + cy.title() + }) + + it('.url()', (done) => { + cy.on('fail', (err) => { + assertOriginFailure(err, done) + }) + + cy.get('a[data-cy="dom-link"]').click() + cy.url() + }) + + it('.hash()', (done) => { + cy.on('fail', (err) => { + assertOriginFailure(err, done) + }) + + cy.get('a[data-cy="dom-link"]').click() + cy.hash() + }) + + it('.location()', (done) => { + cy.on('fail', (err) => { + assertOriginFailure(err, done) + }) + + cy.get('a[data-cy="dom-link"]').click() + cy.location() + }) + + it('.go()', (done) => { + cy.on('fail', (err) => { + assertOriginFailure(err, done) + }) + + cy.get('a[data-cy="dom-link"]').click() + cy.go('back') + }) + + it('.reload()', (done) => { + cy.on('fail', (err) => { + assertOriginFailure(err, done) + }) + + cy.get('a[data-cy="dom-link"]').click() + cy.reload() + }) + + it('.scrollTo()', (done) => { + cy.on('fail', (err) => { + assertOriginFailure(err, done) + }) + + cy.get('a[data-cy="dom-link"]').click() + cy.scrollTo('bottom') + }) }) context('#consoleProps', () => { diff --git a/packages/driver/cypress/e2e/e2e/origin/commands/viewport.cy.ts b/packages/driver/cypress/e2e/e2e/origin/commands/viewport.cy.ts index bf0a18581245..c80845df6902 100644 --- a/packages/driver/cypress/e2e/e2e/origin/commands/viewport.cy.ts +++ b/packages/driver/cypress/e2e/e2e/origin/commands/viewport.cy.ts @@ -154,10 +154,10 @@ context('cy.origin viewport', { browser: '!webkit' }, () => { cy.window().its('innerHeight').should('eq', 480) cy.window().its('innerWidth').should('eq', 320) - }) - cy.window().then((win) => { - win.location.href = 'http://www.idp.com:3500/fixtures/primary-origin.html' + cy.window().then((win) => { + win.location.href = 'http://www.idp.com:3500/fixtures/primary-origin.html' + }) }) cy.origin('http://www.idp.com:3500', () => { diff --git a/packages/driver/cypress/e2e/e2e/origin/navigation.cy.ts b/packages/driver/cypress/e2e/e2e/origin/navigation.cy.ts index 0890dd42414e..d8bd44e16e70 100644 --- a/packages/driver/cypress/e2e/e2e/origin/navigation.cy.ts +++ b/packages/driver/cypress/e2e/e2e/origin/navigation.cy.ts @@ -192,11 +192,11 @@ describe('event timing', { browser: '!webkit' }, () => { cy.origin('http://www.foobar.com:3500', () => { cy.log('inside cy.origin foobar') - }) - - // This command is run from localhost against the cross-origin aut. Updating href is one of the few allowed commands. See https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy#location - cy.window().then((win) => { - win.location.href = 'http://www.idp.com:3500/fixtures/primary-origin.html' + // Updating href is one of the few allowed commands. See https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy#location + // However, not everything on the window is accessible. Therefore, we force window() to only run on the same origin as the AUT context + cy.window().then((win) => { + win.location.href = 'http://www.idp.com:3500/fixtures/primary-origin.html' + }) }) cy.origin('http://www.idp.com:3500', () => { diff --git a/packages/driver/src/cy/commands/actions/scroll.ts b/packages/driver/src/cy/commands/actions/scroll.ts index 04b7f31fa958..0529c27f7b88 100644 --- a/packages/driver/src/cy/commands/actions/scroll.ts +++ b/packages/driver/src/cy/commands/actions/scroll.ts @@ -325,6 +325,9 @@ export default (Commands, Cypress, cy, state) => { const subjectChain = cy.subjectChain() const ensureScrollability = () => { + // Make sure the scroll command can communicate with the AUT + Cypress.ensure.commandCanCommunicateWithAUT(cy) + try { subject = cy.getSubjectFromChain(subjectChain) diff --git a/packages/driver/src/cy/commands/location.ts b/packages/driver/src/cy/commands/location.ts index 911bd19eb57a..d68d93eb59aa 100644 --- a/packages/driver/src/cy/commands/location.ts +++ b/packages/driver/src/cy/commands/location.ts @@ -4,6 +4,9 @@ import $errUtils from '../../cypress/error_utils' export default (Commands, Cypress, cy) => { Commands.addQuery('url', function url (options: Partial = {}) { + // Make sure the url command can communicate with the AUT. + // otherwise, it yields an empty string + Cypress.ensure.commandCanCommunicateWithAUT(cy) this.set('timeout', options.timeout) Cypress.log({ message: '', hidden: options.log === false, timeout: options.timeout }) @@ -16,6 +19,8 @@ export default (Commands, Cypress, cy) => { }) Commands.addQuery('hash', function url (options: Partial = {}) { + // Make sure the hash command can communicate with the AUT. + Cypress.ensure.commandCanCommunicateWithAUT(cy) this.set('timeout', options.timeout) Cypress.log({ message: '', hidden: options.log === false, timeout: options.timeout }) @@ -26,6 +31,10 @@ export default (Commands, Cypress, cy) => { Commands.addQuery('location', function location (key, options: Partial = {}) { // normalize arguments allowing key + options to be undefined // key can represent the options + + // Make sure the location command can communicate with the AUT. + // otherwise the command just yields 'null' and the reason may be unclear to the user. + Cypress.ensure.commandCanCommunicateWithAUT(cy) if (_.isObject(key)) { options = key } diff --git a/packages/driver/src/cy/commands/navigation.ts b/packages/driver/src/cy/commands/navigation.ts index ce2956e95e5b..5283a1ecee70 100644 --- a/packages/driver/src/cy/commands/navigation.ts +++ b/packages/driver/src/cy/commands/navigation.ts @@ -616,6 +616,10 @@ export default (Commands, Cypress, cy, state, config) => { cleanup() } + // Make sure the reload command can communicate with the AUT. + // if we failed for any other reason, we need to display the correct error to the user. + Cypress.ensure.commandCanCommunicateWithAUT(cy) + return null }) }, @@ -700,6 +704,9 @@ export default (Commands, Cypress, cy, state, config) => { cleanup() } + // Make sure the go command can communicate with the AUT. + Cypress.ensure.commandCanCommunicateWithAUT(cy) + return null }) } diff --git a/packages/driver/src/cy/commands/window.ts b/packages/driver/src/cy/commands/window.ts index 87d8c074f92f..a42ef1b98472 100644 --- a/packages/driver/src/cy/commands/window.ts +++ b/packages/driver/src/cy/commands/window.ts @@ -89,6 +89,9 @@ export default (Commands, Cypress, cy, state) => { } Commands.addQuery('title', function title (options: Partial = {}) { + // Make sure the window command can communicate with the AUT. + // otherwise, it yields an empty string + Cypress.ensure.commandCanCommunicateWithAUT(cy) this.set('timeout', options.timeout) Cypress.log({ timeout: options.timeout, hidden: options.log === false }) @@ -96,6 +99,8 @@ export default (Commands, Cypress, cy, state) => { }) Commands.addQuery('window', function windowFn (options: Partial = {}) { + // Make sure the window command can communicate with the AUT. + Cypress.ensure.commandCanCommunicateWithAUT(cy) this.set('timeout', options.timeout) Cypress.log({ hidden: options.log === false, @@ -114,6 +119,8 @@ export default (Commands, Cypress, cy, state) => { }) Commands.addQuery('document', function documentFn (options: Partial = {}) { + // Make sure the document command can communicate with the AUT. + Cypress.ensure.commandCanCommunicateWithAUT(cy) this.set('timeout', options.timeout) Cypress.log({ hidden: options.log === false, diff --git a/packages/graphql/schemas/cloud.graphql b/packages/graphql/schemas/cloud.graphql index c8297ec9b5e2..4edac353bd6a 100644 --- a/packages/graphql/schemas/cloud.graphql +++ b/packages/graphql/schemas/cloud.graphql @@ -333,7 +333,7 @@ type CloudProjectNotFound { } union CloudProjectResult = - CloudProject + | CloudProject | CloudProjectNotFound | CloudProjectUnauthorized @@ -456,7 +456,7 @@ type CloudProjectSpec implements Node { } union CloudProjectSpecFlakyResult = - CloudFeatureNotEnabled + | CloudFeatureNotEnabled | CloudProjectSpecFlakyStatus type CloudProjectSpecFlakyStatus { @@ -500,7 +500,7 @@ type CloudProjectSpecNotFound { } union CloudProjectSpecResult = - CloudProjectSpec + | CloudProjectSpec | CloudProjectSpecNotFound | CloudProjectUnauthorized diff --git a/system-tests/__snapshots__/web_security_spec.js b/system-tests/__snapshots__/web_security_spec.js index 4be0bb77e096..16be6dac9785 100644 --- a/system-tests/__snapshots__/web_security_spec.js +++ b/system-tests/__snapshots__/web_security_spec.js @@ -30,32 +30,47 @@ exports['e2e web security / when enabled / fails'] = ` 1) web security fails when clicking to another origin: + CypressError: The command was expected to run against origin \`http://localhost:4466\` but the application is at origin \`https://www.foo.com:44665\`. - Timed out retrying after 4000ms - + expected - actual +This commonly happens when you have either not navigated to the expected origin or have navigated away unexpectedly. - +'https://www.foo.com:44665/cross_origin' - +Using \`cy.origin()\` to wrap the commands run on \`https://www.foo.com:44665\` will likely fix this issue. + +\`cy.origin('https://www.foo.com:44665', () => {\` +\` \` +\`})\` + +https://on.cypress.io/cy-visit-succeeded-but-commands-fail [stack trace lines] 2) web security fails when submitted a form and being redirected to another origin: + CypressError: The command was expected to run against origin \`http://localhost:4466\` but the application is at origin \`https://www.foo.com:44665\`. + +This commonly happens when you have either not navigated to the expected origin or have navigated away unexpectedly. - Timed out retrying after 4000ms - + expected - actual +Using \`cy.origin()\` to wrap the commands run on \`https://www.foo.com:44665\` will likely fix this issue. - +'https://www.foo.com:44665/cross_origin' - +\`cy.origin('https://www.foo.com:44665', () => {\` +\` \` +\`})\` + +https://on.cypress.io/cy-visit-succeeded-but-commands-fail [stack trace lines] 3) web security fails when using a javascript redirect to another origin: + CypressError: The command was expected to run against origin \`http://localhost:4466\` but the application is at origin \`https://www.foo.com:44665\`. + +This commonly happens when you have either not navigated to the expected origin or have navigated away unexpectedly. + +Using \`cy.origin()\` to wrap the commands run on \`https://www.foo.com:44665\` will likely fix this issue. - Timed out retrying after 4000ms - + expected - actual +\`cy.origin('https://www.foo.com:44665', () => {\` +\` \` +\`})\` - +'https://www.foo.com:44665/cross_origin' - +https://on.cypress.io/cy-visit-succeeded-but-commands-fail [stack trace lines] 4) web security