-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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
chore: [Multi-domain]: Support multiple multi-domain commands in a single test #20354
Conversation
Thanks for taking the time to open a PR!
|
hookId: state('hookId'), | ||
}, | ||
communicator.once('bridge:ready', (_data, bridgeReadyDomain) => { | ||
if (bridgeReadyDomain === domain) { |
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.
We can verify the bridge is ready for the correct domain now, not sure if this is ever not the same or if we should do something more drastic, but it's theoretically possible.
communicator.once('bridge:ready', (_data, bridgeReadyDomain) => { | ||
if (bridgeReadyDomain === domain) { | ||
// now that the spec bridge is ready, instantiate Cypress with the current app config and environment variables for initial sync when creating the instance | ||
communicator.toSpecBridge(domain, 'initialize:cypress', { |
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.
Tell the domain we just created to initialize cypress
// once the secondary domain page loads, send along the | ||
// user-specified callback to run in that domain | ||
try { | ||
communicator.toSpecBridge(domain, 'run:domain:fn', { |
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.
Just run the domain in question
@@ -534,7 +534,7 @@ export class $Cy extends EventEmitter2 implements ITimeouts, IStability, IAssert | |||
// not utilized | |||
try { | |||
this.Cypress.action('app:window:load', this.state('window')) | |||
this.Cypress.multiDomainCommunicator.toSpecBridge('window:load', { url: this.getRemoteLocation('href') }) | |||
this.Cypress.multiDomainCommunicator.toAllSpecBridges('window:load', { url: this.getRemoteLocation('href') }) |
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.
Any domain could be anticipating a window load, so tell them all
@@ -1527,7 +1527,7 @@ export default { | |||
} | |||
|
|||
cy.state('duringUserTestExecution', false) | |||
Cypress.multiDomainCommunicator.toSpecBridge('sync:state', { 'duringUserTestExecution': false }) | |||
Cypress.multiDomainCommunicator.toAllSpecBridges('sync:state', { 'duringUserTestExecution': false }) |
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.
Keep all domains in sync
private windowReference | ||
private crossDomainDriverWindow | ||
private windowReference: Window | undefined | ||
private crossDomainDriverWindows: {[key: string]: Window} = {} |
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 is now a map of domains to windows
@@ -105,9 +105,21 @@ export class PrimaryDomainCommunicator extends EventEmitter { | |||
* @param {string} event - the name of the event to be sent. | |||
* @param {any} data - any meta data to be sent with the event. | |||
*/ | |||
toSpecBridge (event: string, data?: any) { | |||
toAllSpecBridges (event: string, data?: any) { |
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.
Opinions on function naming? this is kinda awkward, but also clear
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.
Works for me.
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.
Could go with some sort of broadcast
naming.
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 was thinking some sort of broadcast, but wasn't sure if that was more clear or just a more enjoyable word to say.
@@ -522,13 +522,13 @@ export const eventManager = { | |||
Cypress.emit('internal:window:load', { type: 'cross:domain', url }) | |||
}) | |||
|
|||
Cypress.multiDomainCommunicator.on('expect:domain', (domain) => { | |||
Cypress.on('expect:domain', (domain) => { |
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 changed this because it made more sense to me that cypress is sending the message than the multi-domain communicator
@@ -513,7 +513,7 @@ export const eventManager = { | |||
}) | |||
|
|||
Cypress.on('test:before:run', (...args) => { | |||
Cypress.multiDomainCommunicator.toSpecBridge('test:before:run', ...args) | |||
Cypress.multiDomainCommunicator.toAllSpecBridges('test:before:run', ...args) |
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.
all domains should be notified before the test runs
if (options.syncConfig) this.syncConfigEnvToPrimary() | ||
|
||
this.handleSubjectAndErr(data, (data: any) => { | ||
this.windowReference.top.postMessage({ | ||
event: `${CROSS_DOMAIN_PREFIX}${event}`, | ||
data, | ||
domain: location.hostname, |
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 am now appending the domain to ALL messages sent to the primary.
} | ||
|
||
this.emit(messageName, data.data) | ||
this.emit(messageName, data.data, data.domain) |
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 Domain the message originated from is now attached to all emitted messages
@@ -105,9 +105,21 @@ export class PrimaryDomainCommunicator extends EventEmitter { | |||
* @param {string} event - the name of the event to be sent. | |||
* @param {any} data - any meta data to be sent with the event. | |||
*/ | |||
toSpecBridge (event: string, data?: any) { | |||
toAllSpecBridges (event: string, data?: any) { | |||
debug('=> to all spec bridges', event, data) |
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 added these debug messages on eventing, I find that when i'm debugging multi-domain I'm always setting these to see what communication is happening.
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.
Funny story: to enable these debug messages you have to add a local storage variable localStorage.debug = 'cypress:*'
, but with session enabled, we clear local storage after each test. 🤦♂️
const callback = () => { | ||
Cypress.multiDomainCommunicator.toSpecBridge('before:screenshot:end') | ||
Cypress.multiDomainCommunicator.toSpecBridge(domain, 'before:screenshot:end') |
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 we get the originating domain we handily also know where to send the message back
if (!Cypress) { | ||
throw new Error('Something went terribly wrong and we cannot proceed. We expected to find the global \ | ||
Cypress in the spec bridge window but it is missing. This should never happen and likely is a bug. Please open an issue.') | ||
Cypress in the spec bridge window but it is missing.') |
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 could legitimately happen if a click causes several redirects and finally lands on the specified domain. That shouldn't be an error.
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.
So do we even want this error? Seems odd to say something went terribly wrong for an expected scenario.
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 think we will want to remove this error in the long run. Not finding cypress may or may not be a problem depending if this is the last domain loaded and I don't think we can know that. Even if it is the last domain loaded this error isn't bubbled up to the primary domain to display and the test times out.
I think the onus lies on the primary domain and ideally, the command that interacts with the dom to timeout if the correct domain hasn't loaded in an appropriate amount of time.
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.
+1, I somehow missed the comment you added above the line
|
||
try { | ||
// If Cypress is defined and we haven't gotten a cross domain error we have found the correct bridge. | ||
if (frame.Cypress) { |
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 enough, I don't think there will be other frames on the same domain that also have cypress defined, it should just be the bridge.
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.
Yeah I think it's safe to assume.
} | ||
} catch (error) { | ||
// Catch DOMException: Blocked a frame from accessing a cross-origin frame. | ||
if (error.name !== 'SecurityError') { |
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.
Narrowing the exception we're catching as much as possible.
Test summaryRun details
View run in Cypress Dashboard ➡️ Flakiness
This comment has been generated by cypress-bot as a result of this project's GitHub integration settings. You can manage this integration in this project's settings in the Cypress Dashboard |
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.
Would you mind updating the FIXME tests here, now that multiple switchToDomains are supported.
|
||
try { | ||
// If Cypress is defined and we haven't gotten a cross domain error we have found the correct bridge. | ||
if (frame.Cypress) { |
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.
Yeah I think it's safe to assume.
@@ -105,9 +105,21 @@ export class PrimaryDomainCommunicator extends EventEmitter { | |||
* @param {string} event - the name of the event to be sent. | |||
* @param {any} data - any meta data to be sent with the event. | |||
*/ | |||
toSpecBridge (event: string, data?: any) { | |||
toAllSpecBridges (event: string, data?: any) { |
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.
Works for me.
if (!Cypress) { | ||
throw new Error('Something went terribly wrong and we cannot proceed. We expected to find the global \ | ||
Cypress in the spec bridge window but it is missing. This should never happen and likely is a bug. Please open an issue.') | ||
Cypress in the spec bridge window but it is missing.') |
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.
So do we even want this error? Seems odd to say something went terribly wrong for an expected scenario.
if (options.syncConfig) this.syncConfigEnvToPrimary() | ||
|
||
this.handleSubjectAndErr(data, (data: any) => { | ||
this.windowReference.top.postMessage({ | ||
event: `${CROSS_DOMAIN_PREFIX}${event}`, | ||
data, | ||
domain: location.hostname, |
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 host name is converted into punycode, which will not match the domain specified in the command. To circumvent this and avoid adding a dependency to convert punycode to unicode I have attached the unicode domain name to the window via the server.
@@ -7,6 +7,7 @@ | |||
<body> | |||
<script type="text/javascript"> | |||
document.domain = '{{domain}}'; |
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 browser converts document.domain to punycode by default, how helpful, but doesn't provide any API's to convert it back to unicode. So I attach the domain to window below.
User facing changelog
n/a
Additional details
This PR introduces the concept of running multiple multi-domain command in a single test. To accomplish this, I have added logic to:
What this does not fix:
How has the user experience changed?
PR Tasks
cypress-documentation
?type definitions
?cypress.schema.json
?