Skip to content

spike: attempt to use url() and hash() in the automation client and u… #31783

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: release/15.0.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .circleci/cache-version.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# Bump this version to force CI to re-create the cache from scratch.

5-21-2025
6-9-2025
37 changes: 24 additions & 13 deletions packages/driver/cypress/e2e/commands/location.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,15 @@ describe('src/cy/commands/location', () => {
cy.url().should('match', /baz/).and('eq', 'http://localhost:3500/foo/bar/baz.html')
})

it('catches thrown errors', () => {
cy.stub(Cypress.utils, 'locToString')
.onFirstCall().throws(new Error)
.onSecondCall().returns('http://localhost:3500/baz.html')
it('propagates thrown errors from CDP', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.include('CDP was unable to find the AUT iframe')
done()
})

cy.stub(Cypress, 'automation').withArgs('get:aut:url').rejects(new Error('CDP was unable to find the AUT iframe'))

cy.url().should('include', '/baz.html')
cy.url()
})

// https://github.com/cypress-io/cypress/issues/17399
Expand Down Expand Up @@ -380,7 +383,16 @@ describe('src/cy/commands/location', () => {
context('#location', () => {
it('returns the location object', () => {
cy.location().then((loc) => {
expect(loc).to.have.keys(['auth', 'authObj', 'hash', 'href', 'host', 'hostname', 'pathname', 'port', 'protocol', 'search', 'origin', 'superDomainOrigin', 'superDomain', 'toString'])
expect(loc).to.have.property('hash')
expect(loc).to.have.property('host')
expect(loc).to.have.property('hostname')
expect(loc).to.have.property('href')
expect(loc).to.have.property('origin')
expect(loc).to.have.property('pathname')
expect(loc).to.have.property('port')
expect(loc).to.have.property('protocol')
expect(loc).to.have.property('search')
expect(loc).to.have.property('searchParams')
})
})

Expand All @@ -402,15 +414,13 @@ describe('src/cy/commands/location', () => {

// https://github.com/cypress-io/cypress/issues/16463
it('eventually returns a given key', function () {
cy.stub(cy, 'getRemoteLocation')
.onFirstCall().returns('')
.onSecondCall().returns({
pathname: '/my/path',
})
cy.stub(Cypress, 'automation').withArgs('get:aut:url')
.onFirstCall().resolves('http://localhost:3500')
.onSecondCall().resolves('http://localhost:3500/my/path')

cy.location('pathname').should('equal', '/my/path')
.then(() => {
expect(cy.getRemoteLocation).to.have.been.calledTwice
expect(Cypress.automation).to.have.been.calledTwice
})
})

Expand Down Expand Up @@ -614,7 +624,8 @@ describe('src/cy/commands/location', () => {
expect(_.keys(consoleProps)).to.deep.eq(['name', 'type', 'props'])
expect(consoleProps.name).to.eq('location')
expect(consoleProps.type).to.eq('command')
expect(_.keys(consoleProps.props.Yielded)).to.deep.eq(['auth', 'authObj', 'hash', 'href', 'host', 'hostname', 'origin', 'pathname', 'port', 'protocol', 'search', 'superDomainOrigin', 'superDomain', 'toString'])

expect(_.keys(consoleProps.props.Yielded)).to.deep.eq(['hash', 'host', 'hostname', 'href', 'origin', 'pathname', 'port', 'protocol', 'search', 'searchParams'])
})
})
})
Expand Down
35 changes: 12 additions & 23 deletions packages/driver/cypress/e2e/commands/navigation.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,23 @@ describe('src/cy/commands/navigation', () => {
})

it('calls into window.location.reload', () => {
const locReload = cy.spy(Cypress.utils, 'locReload')

cy.reload().then(() => {
expect(locReload).to.be.calledWith(false)
cy.on('fail', () => {
expect(Cypress.automation).to.be.calledWith('reload:aut:frame', { forceReload: false })
})
})

it('can pass forceReload', () => {
const locReload = cy.spy(Cypress.utils, 'locReload')
cy.stub(Cypress, 'automation').withArgs('reload:aut:frame', { forceReload: false }).resolves()

cy.reload(true).then(() => {
expect(locReload).to.be.calledWith(true)
})
cy.reload({ timeout: 1000 })
})

it('can pass forceReload + options', () => {
const locReload = cy.spy(Cypress.utils, 'locReload')

cy.reload(true, {}).then(() => {
expect(locReload).to.be.calledWith(true)
cy.on('fail', () => {
expect(Cypress.automation).to.be.calledWith('reload:aut:frame', { forceReload: true })
})
})

it('can pass just options', () => {
const locReload = cy.spy(Cypress.utils, 'locReload')
cy.stub(Cypress, 'automation').withArgs('reload:aut:frame', { forceReload: true }).resolves()

cy.reload({}).then(() => {
expect(locReload).to.be.calledWith(false)
})
cy.reload(true, { timeout: 1000 })
})

it('returns the window object', () => {
Expand Down Expand Up @@ -415,7 +403,7 @@ describe('src/cy/commands/navigation', () => {
const rel = cy.stub(win, 'removeEventListener')

cy.go('back').then(() => {
const unloadEvent = cy.browser.family === 'chromium' ? 'pagehide' : 'unload'
const unloadEvent = Cypress.browser.family === 'chromium' ? 'pagehide' : 'unload'

expect(rel).to.be.calledWith('beforeunload')
expect(rel).to.be.calledWith(unloadEvent)
Expand Down Expand Up @@ -600,14 +588,15 @@ describe('src/cy/commands/navigation', () => {
const { lastLog } = this

beforeunload = true
expect(lastLog.get('snapshots').length).to.eq(1)
expect(lastLog.get('snapshots').length).to.eq(2)
expect(lastLog.get('snapshots')[0].name).to.eq('before')
expect(lastLog.get('snapshots')[0].body).to.be.an('object')

return undefined
})

cy.go('back').then(function () {
// wait for the beforeunload event to be fired after the history navigation
cy.go('back').wait(100).then(function () {
const { lastLog } = this

expect(beforeunload).to.be.true
Expand Down
124 changes: 63 additions & 61 deletions packages/driver/cypress/e2e/e2e/origin/commands/actions.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,101 +188,103 @@ 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\` <commands targeting http://www.foobar.com:3500 go here>\`\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:`)
assertOriginFailure(err, done)
})

cy.get('a[data-cy="dom-link"]').click()
cy.get('#button')
})

// With Cypress 15, window() will work always without cy.origin().
// However, users may not have access to the AUT window object, so cy.window() yielded window objects
// may return cross-origin errors.
context('cross-origin AUT commands working with cy.origin()', () => {
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.window().then((win) => {
// The window is in a cross-origin state, but users are able to yield the command
// as well as basic accessible properties
expect(win.length).to.equal(2)
try {
// but cannot access cross-origin properties
win[0].location.href
} catch (e) {
expect(e.name).to.equal('SecurityError')
if (Cypress.isBrowser('firefox')) {
expect(e.message).to.include('Permission denied to get property "href" on cross-origin object')
} else {
expect(e.message).to.include('Blocked a frame with origin "http://localhost:3500" from accessing a cross-origin frame.')
}

done()
}
})
})

it('.reload()', () => {
cy.get('a[data-cy="dom-link"]').click()
cy.document()
cy.reload()
})

it('.title()', (done) => {
cy.on('fail', (err) => {
assertOriginFailure(err, done)
})

it('.url()', () => {
cy.get('a[data-cy="dom-link"]').click()
cy.title()
cy.url().then((url) => {
expect(url).to.equal('http://www.foobar.com:3500/fixtures/dom.html')
})
})

it('.url()', (done) => {
cy.on('fail', (err) => {
assertOriginFailure(err, done)
it('.hash()', () => {
cy.get('a[data-cy="dom-link"]').click()
cy.hash().then((hash) => {
expect(hash).to.equal('')
})
})

it('.location()', () => {
cy.get('a[data-cy="dom-link"]').click()
cy.url()
cy.location().then((loc) => {
expect(loc.href).to.equal('http://www.foobar.com:3500/fixtures/dom.html')
})
})

it('.hash()', (done) => {
cy.on('fail', (err) => {
assertOriginFailure(err, done)
it('.title()', () => {
cy.get('a[data-cy="dom-link"]').click()
cy.title().then((title) => {
expect(title).to.equal('DOM Fixture')
})
})

it('.go()', () => {
cy.get('a[data-cy="dom-link"]').click()
cy.hash()
cy.go('back')
})
})

it('.location()', (done) => {
cy.on('fail', (err) => {
assertOriginFailure(err, done)
})
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\` <commands targeting http://www.foobar.com:3500 go here>\`\n\`})`)

cy.get('a[data-cy="dom-link"]').click()
cy.location()
})
// 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('.go()', (done) => {
it('.get()', { defaultCommandTimeout: 50 }, (done) => {
cy.on('fail', (err) => {
expect(err.message).to.include(`Timed out retrying after 50ms:`)
assertOriginFailure(err, done)
})

cy.get('a[data-cy="dom-link"]').click()
cy.go('back')
cy.get('#button')
})

it('.reload()', (done) => {
it('.document()', (done) => {
cy.on('fail', (err) => {
assertOriginFailure(err, done)
})

cy.get('a[data-cy="dom-link"]').click()
cy.reload()
cy.document()
})

it('.scrollTo()', (done) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,19 +62,16 @@ context('cy.origin location', { browser: '!webkit' }, () => {
expect(consoleProps.name).to.equal('location')
expect(consoleProps.type).to.equal('command')

expect(consoleProps.props.Yielded).to.have.property('auth').that.is.a('string')
expect(consoleProps.props.Yielded).to.have.property('authObj').that.is.undefined
expect(consoleProps.props.Yielded).to.have.property('hash').that.is.a('string')
expect(consoleProps.props.Yielded).to.have.property('host').that.is.a('string')
expect(consoleProps.props.Yielded).to.have.property('hostname').that.is.a('string')
expect(consoleProps.props.Yielded).to.have.property('href').that.is.a('string')
expect(consoleProps.props.Yielded).to.have.property('origin').that.is.a('string')
expect(consoleProps.props.Yielded).to.have.property('superDomainOrigin').that.is.a('string')
expect(consoleProps.props.Yielded).to.have.property('pathname').that.is.a('string')
expect(consoleProps.props.Yielded).to.have.property('port').that.is.a('string')
expect(consoleProps.props.Yielded).to.have.property('protocol').that.is.a('string')
expect(consoleProps.props.Yielded).to.have.property('search').that.is.a('string')
expect(consoleProps.props.Yielded).to.have.property('superDomain').that.is.a('string')
expect(consoleProps.props.Yielded).to.have.property('searchParams').that.is.an('object')
})
})

Expand Down
Loading
Loading