-
Notifications
You must be signed in to change notification settings - Fork 3.3k
feat: rework window bound commands to use automation clients #31862
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
feat: rework window bound commands to use automation clients #31862
Conversation
…o(), and cy.title() all to use the automation client to subvert the cross-origin boundary refactor backend client
…go changes to make it easier to test minor behavior changes bump cache
…ame tree gets stale on reload
cypress
|
Project |
cypress
|
Branch Review |
feat/use_automation_client_for_some_window_commands
|
Run status |
|
Run duration | 16m 27s |
Commit |
|
Committer | Jennifer Shehane |
View all properties for this run ↗︎ |
Test results | |
---|---|
|
2
|
|
0
|
|
10
|
|
0
|
|
608
|
View all changes introduced in this branch ↗︎ |
UI Coverage
0%
|
|
---|---|
|
4
|
|
0
|
Accessibility
97.09%
|
|
---|---|
|
0 critical
1 serious
0 moderate
0 minor
|
|
4
|
Tests for review
cypress/e2e/studio/studio.cy.ts • 2 failed tests • app-e2e
.onFirstCall().throws(new Error) | ||
.onSecondCall().returns('http://localhost:3500/baz.html') | ||
it('propagates thrown errors from CDP', (done) => { | ||
cy.on('fail', (err) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this might be better off in a vite test, but most of these tests are really functioning as some type of integration test 🤷🏻
cy.reload(true, {}).then(() => { | ||
expect(locReload).to.be.calledWith(true) | ||
cy.on('fail', () => { | ||
expect(Cypress.automation).to.be.calledWith('reload:aut:frame', { forceReload: true }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not a great way to test this besides asserting on the failure that automation client was called
@@ -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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the signature has slightly changed on the location object, but still matches the docs in https://docs.cypress.io/api/commands/location. The legacy signature (that isn't public) will still be used by Webkit as it is using the window location API
import { getUrlFromAutomation, UrlNotYetAvailableError } from '../../../../../src/cy/commands/helpers/location' | ||
import Bluebird from 'bluebird' | ||
|
||
const flushPromises = () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since most of this is internal promises, we need a function to flush the microtasks to guarantee the promises are resolved
@@ -2167,6 +2167,133 @@ describe('lib/browsers/bidi_automation', () => { | |||
}) | |||
}) | |||
|
|||
describe('get:aut:url', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these are more so integration tests at this point, but the functions themselves are so light that I felt these still made the most sense since I already had them written
@@ -18,6 +18,7 @@ _Released 07/01/2025 (PENDING)_ | |||
**Features:** | |||
|
|||
- [`tsx`](https://tsx.is/) is now used in all cases to run the Cypress config, replacing [ts-node](https://github.com/TypeStrong/ts-node) for TypeScript and Node for commonjs/ESM. This should allow for more interoperability for users who are using any variant of ES Modules. Addresses [#8090](https://github.com/cypress-io/cypress/issues/8090), [#15724](https://github.com/cypress-io/cypress/issues/15724), [#21805](https://github.com/cypress-io/cypress/issues/21805), [#22273](https://github.com/cypress-io/cypress/issues/22273), [#22747](https://github.com/cypress-io/cypress/issues/22747), [#23141](https://github.com/cypress-io/cypress/issues/23141), [#25958](https://github.com/cypress-io/cypress/issues/25958), [#25959](https://github.com/cypress-io/cypress/issues/25959), [#26606](https://github.com/cypress-io/cypress/issues/26606), [#27359](https://github.com/cypress-io/cypress/issues/27359), [#27450](https://github.com/cypress-io/cypress/issues/27450), [#28442](https://github.com/cypress-io/cypress/issues/28442), [#30318](https://github.com/cypress-io/cypress/issues/30318), [#30718](https://github.com/cypress-io/cypress/issues/30718), [#30907](https://github.com/cypress-io/cypress/issues/30907), [#30915](https://github.com/cypress-io/cypress/issues/30915), [#30925](https://github.com/cypress-io/cypress/issues/30925), [#30954](https://github.com/cypress-io/cypress/issues/30954) and [#31185](https://github.com/cypress-io/cypress/issues/31185). | |||
- [`cy.url()`](https://docs.cypress.io/api/commands/url), [`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), [`cy.title()`](https://docs.cypress.io/api/commands/title), and [`cy.location()`](https://docs.cypress.io/api/commands/location) now use the automation client (CDP for Chromium browsers and WebDriver BiDi for Firefox) to return the appropriate values from the commands to the user instead of the window object. This is to avoid cross origin issues with [`cy.origin()`](https://docs.cypress.io/api/commands/origin) so these commands can be invoked anywhere inside a Cypress test without having to worry about origin access issues. Experimental Webkit still will use the window object to retrieve these values. Also, [`cy.window()`](https://docs.cypress.io/api/commands/window) will always return the current window object, regardless of origin restrictions. Not every property from the window object will be accessible depending on the origin context. Addresses [#31196](https://github.com/cypress-io/cypress/issues/31196). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to indicate why this would be breaking at all? It's hard to understand what situation would break here and what action I would need to take as a user reading this (or maybe this would be good for the migration guide?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not a breaking change but I marked it as a feature. It's more of an improvement if anything I would think
expect(loc).to.have.property('port') | ||
expect(loc).to.have.property('protocol') | ||
expect(loc).to.have.property('search') | ||
expect(loc).to.have.property('searchParams') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this breaking the properties returned? Notice there's no test to toString: https://docs.cypress.io/api/commands/location#When-not-given-a-key-argument
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to add the toString
method to keep parity. The other keys should be covered. It's a bit bizarre because there are other undocumented properties that were being returned on the location object, like authObj
and superDomain
, which are no longer being returned but I would assume they are none breaking because they aren't documented. However, the Webkit legacy implementation is still going to use the old location object, so those properties would exist
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jennifer-shehane I am going to remove the toString
method from the public API documentation. The method just return [Object object]
and doesn't really provide any useful information
…e_window_commands
@@ -464,6 +469,40 @@ export class CdpAutomation implements CDPClient, AutomationMiddleware { | |||
return false | |||
} | |||
|
|||
private _getAutFrame = async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to see this pulled out as a helper in a future iteration
|
||
const expressionToEvaluate = (historyNumber: number) => `window.history.go(${historyNumber})` | ||
|
||
export async function cdpNavigateHistory (send: SendDebuggerCommand, contexts: Map<Protocol.Runtime.ExecutionContextId, Protocol.Runtime.ExecutionContextDescription>, frame: Protocol.Page.Frame, historyNumber: number): Promise<void> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
future pattern might look like
type AutomationImpl<T extends CommandNames, C extends CdpContext | BidiContext> = {
(params: Commands[T]['Params'], context: C) => Commands[T]['Results']
}
class AutomationCommand<
T extends CommandName,
CdpImpl extends AutomationImpl<T, CdpContext>,
BidiImpl extends AutomationImpl<T, BidiContext>
> {
constructor(
private commandName: T,
private cdp: CdpImpl,
private bidi: BidiImpl,
) {}
public exec = (params: Commands[T]['Params'], context: AutomationContext): Commands[T]['Results'] => {
if (isCdpContext(context)) {
return this.cdp(params, context)
} else if (isBidiContext(context)) {
return this.bidi(params, context)
}
}
public register (bus: AutomationEventBus) {
bus.on(this.commandName, this.exec)
}
}
...
function navigateHistoryTo (idx: number) {
return `window.history.go(${idx})`
}
const navigate = new AutomationCommand('navigate:aut:history',
function cdpNavigateHistory (historyNumber: number, context: CdpContext) {
return evaluateInFrameContext(navigateHistoryTo(historyNumber), context)
},
function bidiNavigateHistory (historyNumber: number, { webDriverClient, autContextId: context} : BidiContext) {
return webDriverClient.scriptEvaluate({
expression: expressionToEvaluate(navigateHistoryTo),
target: {
context,
},
awaitPromise: false,
}
}
}
navigate.register(automationEventBus)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My previous comments were addressed.
window
in order to avoid cross origin issues #31196Additional details
The automation clients (CDP for chromium based and BiDi for firefox) will now be used for the following commands to avoid using the window object
cy.url()
cy.hash()
cy.go()
cy.reload()
cy.title()
cy.location()
Additionally,
cy.window()
will now no longer check for cross origin accessibility. Instead, the current AUT window will always be returned, but now all properties will be accessible if the frame is in a cross origin context.Why is this needed (from #31196)
With the introduction of #30858, the goal was to prevent Cypress from getting into weird states where a command would silently fail and hang the Cypress application when invoked in a cross-origin context when it wasn't expected to be. The goal here was to avoid issues like seen in #30848 where the errors were either non-existent or cryptic. This meant enforcing origin checks additionally on the following commands:
cy.scrollTo()
cy.window()
cy.document()
cy.title()
cy.url()
cy.location()
cy.hash()
cy.go()
cy.reload()
However, since the release of Cypress 14, there have been some reported issues, in particular #31100 where users need to conditionally check the url the AUT (Application Under Test) is on in order to see if two factor authentication is needed. We typically do not recommend conditional testing, but this is an incredibly valid use case. We think the best way to solve this is to make certain commands not reference the window where possible, so we can provide users with the best error context possible while providing consistent commands suite user needs.
Steps to test
How has the user experience changed?
PR Tasks
cypress-documentation
?type definitions
?