Skip to content

Commit cffcb7f

Browse files
committed
spike: attempt to use url() and hash() in the automation client and unbind it from the window
1 parent 0e74d2c commit cffcb7f

File tree

8 files changed

+102
-51
lines changed

8 files changed

+102
-51
lines changed

cli/types/cypress.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4007,7 +4007,7 @@ declare namespace Cypress {
40074007
/**
40084008
* Options to change the default behavior of .url()
40094009
*/
4010-
interface UrlOptions extends Loggable, Timeoutable {
4010+
interface UrlOptions extends Loggable {
40114011
/**
40124012
* Whether the url is decoded
40134013
*

packages/driver/cypress/e2e/e2e/origin/commands/actions.cy.ts

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,49 @@ context('cy.origin actions', { browser: '!webkit' }, () => {
188188
})
189189
})
190190

191+
// With Cypress 15, window(), url(), and hash() all work without cy.origin().
192+
// However, users may not have access to the AUT window object, so cy.window() yielded window objects
193+
// may return cross-origin errors. However, url() and hash() will work without cy.origin() as they now use the automation client.
194+
context('cross-origin AUT commands without cy.origin()', () => {
195+
it('.window()', (done) => {
196+
cy.get('a[data-cy="dom-link"]').click()
197+
cy.window().then((win) => {
198+
// The window is in a cross-origin state, but users are able to yield the command
199+
// as well as basic accessible properties
200+
expect(win.length).to.equal(2)
201+
try {
202+
// but cannot access cross-origin properties
203+
win[0].location.href
204+
} catch (e) {
205+
expect(e.name).to.equal('SecurityError')
206+
expect(e.message).to.include('Blocked a frame with origin "http://localhost:3500" from accessing a cross-origin frame.')
207+
done()
208+
}
209+
})
210+
})
211+
212+
it('.url()', () => {
213+
cy.get('a[data-cy="dom-link"]').click()
214+
cy.url().then((url) => {
215+
expect(url).to.equal('http://www.foobar.com:3500/fixtures/dom.html')
216+
})
217+
})
218+
219+
it('.hash()', () => {
220+
cy.get('a[data-cy="cross-origin-secondary-link"]').click()
221+
cy.origin('http://www.foobar.com:3500', () => {
222+
cy.get('a[data-cy="hashChange"]').click()
223+
cy.hash().then((hash) => {
224+
expect(hash).to.equal('#hashChange')
225+
})
226+
})
227+
228+
cy.hash().then((hash) => {
229+
expect(hash).to.equal('#hashChange')
230+
})
231+
})
232+
})
233+
191234
context('cross-origin AUT errors', () => {
192235
// We only need to check .get here because the other commands are chained off of it.
193236
// the exceptions are window(), document(), title(), url(), hash(), location(), go(), reload(), and scrollTo()
@@ -213,15 +256,6 @@ context('cy.origin actions', { browser: '!webkit' }, () => {
213256
cy.get('#button')
214257
})
215258

216-
it('.window()', (done) => {
217-
cy.on('fail', (err) => {
218-
assertOriginFailure(err, done)
219-
})
220-
221-
cy.get('a[data-cy="dom-link"]').click()
222-
cy.window()
223-
})
224-
225259
it('.document()', (done) => {
226260
cy.on('fail', (err) => {
227261
assertOriginFailure(err, done)
@@ -240,24 +274,6 @@ context('cy.origin actions', { browser: '!webkit' }, () => {
240274
cy.title()
241275
})
242276

243-
it('.url()', (done) => {
244-
cy.on('fail', (err) => {
245-
assertOriginFailure(err, done)
246-
})
247-
248-
cy.get('a[data-cy="dom-link"]').click()
249-
cy.url()
250-
})
251-
252-
it('.hash()', (done) => {
253-
cy.on('fail', (err) => {
254-
assertOriginFailure(err, done)
255-
})
256-
257-
cy.get('a[data-cy="dom-link"]').click()
258-
cy.hash()
259-
})
260-
261277
it('.location()', (done) => {
262278
cy.on('fail', (err) => {
263279
assertOriginFailure(err, done)

packages/driver/src/cy/commands/location.ts

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,16 @@ import _ from 'lodash'
33
import $errUtils from '../../cypress/error_utils'
44

55
export default (Commands, Cypress, cy) => {
6-
Commands.addQuery('url', function url (options: Partial<Cypress.UrlOptions> = {}) {
7-
// Make sure the url command can communicate with the AUT.
8-
// otherwise, it yields an empty string
9-
Cypress.ensure.commandCanCommunicateWithAUT(cy)
10-
this.set('timeout', options.timeout)
11-
12-
Cypress.log({ message: '', hidden: options.log === false, timeout: options.timeout })
13-
14-
return () => {
15-
const href = cy.getRemoteLocation('href')
6+
Commands.add('url', function url (options: Partial<Cypress.UrlOptions> = {}) {
7+
Cypress.log({ message: '', hidden: options.log === false })
168

17-
return options.decode ? decodeURI(href) : href
18-
}
9+
return Cypress.automation('get:aut:url', {})
1910
})
2011

21-
Commands.addQuery('hash', function url (options: Partial<Cypress.Loggable & Cypress.Timeoutable> = {}) {
22-
// Make sure the hash command can communicate with the AUT.
23-
Cypress.ensure.commandCanCommunicateWithAUT(cy)
24-
this.set('timeout', options.timeout)
25-
26-
Cypress.log({ message: '', hidden: options.log === false, timeout: options.timeout })
12+
Commands.add('hash', function url (options: Partial<Cypress.Loggable> = {}) {
13+
Cypress.log({ message: '', hidden: options.log === false })
2714

28-
return () => cy.getRemoteLocation('hash')
15+
return Cypress.automation('get:aut:url:hash', {})
2916
})
3017

3118
Commands.addQuery('location', function location (key, options: Partial<Cypress.Loggable & Cypress.Timeoutable> = {}) {

packages/driver/src/cy/commands/window.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@ export default (Commands, Cypress, cy, state) => {
100100

101101
Commands.addQuery('window', function windowFn (options: Partial<Cypress.Loggable & Cypress.Timeoutable> = {}) {
102102
// Make sure the window command can communicate with the AUT.
103-
Cypress.ensure.commandCanCommunicateWithAUT(cy)
104103
this.set('timeout', options.timeout)
105104
Cypress.log({
106105
hidden: options.log === false,

packages/server/lib/browsers/bidi_automation.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,22 @@ export class BidiAutomation {
659659
}
660660

661661
return
662+
case 'get:aut:url':
663+
{
664+
const { contexts: autContext } = await this.webDriverClient.browsingContextGetTree({
665+
root: this.autContextId,
666+
})
667+
668+
return autContext ? autContext[0].url : ''
669+
}
670+
case 'get:aut:url:hash':
671+
{
672+
const { contexts: autContext } = await this.webDriverClient.browsingContextGetTree({
673+
root: this.autContextId,
674+
})
675+
676+
return autContext ? new URL(autContext[0].url).hash : ''
677+
}
662678
default:
663679
debug('BiDi automation not implemented for message: %s', message)
664680
throw new AutomationNotImplemented(message, 'BiDiAutomation')

packages/server/lib/browsers/cdp_automation.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,33 @@ export class CdpAutomation implements CDPClient, AutomationMiddleware {
464464
return false
465465
}
466466

467+
private _getAutFrame = async () => {
468+
try {
469+
const frameTree = (await this.sendDebuggerCommandFn('Page.getFrameTree')).frameTree
470+
const frame = _.find(frameTree?.childFrames || [], ({ frame }) => {
471+
return frame?.name?.startsWith('Your project:')
472+
}) as HasFrame | undefined
473+
474+
return frame?.frame
475+
} catch (err) {
476+
debugVerbose('failed to get aut frame:', err.stack)
477+
478+
return undefined
479+
}
480+
}
481+
482+
private _getAutUrl = async () => {
483+
const frame = await this._getAutFrame()
484+
485+
return frame?.url || ''
486+
}
487+
488+
private _getAutUrlHash = async () => {
489+
const frame = await this._getAutFrame()
490+
491+
return frame?.urlFragment || ''
492+
}
493+
467494
_handlePausedRequests = async (client: CriClient) => {
468495
// NOTE: only supported in chromium based browsers
469496
await client.send('Fetch.enable', {
@@ -611,6 +638,10 @@ export class CdpAutomation implements CDPClient, AutomationMiddleware {
611638
}
612639

613640
return cdpKeyPress(data, this.sendDebuggerCommandFn, this.executionContexts, (await this.send('Page.getFrameTree')).frameTree)
641+
case 'get:aut:url':
642+
return this._getAutUrl()
643+
case 'get:aut:url:hash':
644+
return this._getAutUrlHash()
614645
default:
615646
throw new Error(`No automation handler registered for: '${message}'`)
616647
}

packages/types/src/server.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ export interface AutomationCommands {
9191
'remote:debugger:protocol': CommandSignature
9292
'response:received': CommandSignature
9393
'key:press': CommandSignature<KeyPressParams, void>
94+
'get:aut:url': CommandSignature<{ url: string }, string>
95+
'get:aut:url:hash': CommandSignature<{ hash: string }, string>
9496
}
9597

9698
export type OnRequestEvent = <T extends keyof AutomationCommands>(message: T, data: AutomationCommands[T]['dataType']) => Promise<AutomationCommands[T]['returnType']>

system-tests/__snapshots__/web_security_spec.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ exports['e2e web security / when enabled / fails'] = `
3030
3131
1) web security
3232
fails when clicking <a> to another origin:
33-
CypressError: The command was expected to run against origin \`http://localhost:4466\` but the application is at origin \`https://www.foo.com:44665\`.
33+
CypressError: Timed out retrying after 4000ms: The command was expected to run against origin \`http://localhost:4466\` but the application is at origin \`https://www.foo.com:44665\`.
3434
3535
This commonly happens when you have either not navigated to the expected origin or have navigated away unexpectedly.
3636
@@ -45,7 +45,7 @@ https://on.cypress.io/cy-visit-succeeded-but-commands-fail
4545
4646
2) web security
4747
fails when submitted a form and being redirected to another origin:
48-
CypressError: The command was expected to run against origin \`http://localhost:4466\` but the application is at origin \`https://www.foo.com:44665\`.
48+
CypressError: Timed out retrying after 4000ms: The command was expected to run against origin \`http://localhost:4466\` but the application is at origin \`https://www.foo.com:44665\`.
4949
5050
This commonly happens when you have either not navigated to the expected origin or have navigated away unexpectedly.
5151
@@ -60,7 +60,7 @@ https://on.cypress.io/cy-visit-succeeded-but-commands-fail
6060
6161
3) web security
6262
fails when using a javascript redirect to another origin:
63-
CypressError: The command was expected to run against origin \`http://localhost:4466\` but the application is at origin \`https://www.foo.com:44665\`.
63+
CypressError: Timed out retrying after 4000ms: The command was expected to run against origin \`http://localhost:4466\` but the application is at origin \`https://www.foo.com:44665\`.
6464
6565
This commonly happens when you have either not navigated to the expected origin or have navigated away unexpectedly.
6666

0 commit comments

Comments
 (0)