Skip to content

Commit

Permalink
enhance done() in switchToDomain to handle when errors occur 'i.e. do…
Browse files Browse the repository at this point in the history
…ne not defined' as well as errors being passed into done. Now executes eval functions as async and supports async switchToDomain function
  • Loading branch information
AtofStryker committed Dec 22, 2021
1 parent 5a4e36d commit 40e60bf
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 32 deletions.
28 changes: 12 additions & 16 deletions packages/driver/cypress/integration/e2e/multidomain_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,13 @@ describe('multidomain', { experimentalSessionSupport: true }, () => {

describe('window events', () => {
it('form:submitted', (done) => {
expectTextMessage('form:submitted', done)

// @ts-ignore
cy.switchToDomain('foobar.com', () => {
Cypress.once('form:submitted', () => {
top!.postMessage({ host: location.host, actual: 'form:submitted' }, '*')
const $form = cy.$$('form')

Cypress.once('form:submitted', (e) => {
expect(e.target).to.eq($form.get(0))
done()
})

cy.get('form').submit()
Expand Down Expand Up @@ -181,7 +182,7 @@ describe('multidomain', { experimentalSessionSupport: true }, () => {
})
})

it('allows users to call the "done" callback within the "switchToDomain" context', (done) => {
it('allows users to call the "done" callback within the "switchToDomain" context synchronously', (done) => {
// @ts-ignore
cy.switchToDomain('foobar.com', () => {
cy
Expand All @@ -193,20 +194,15 @@ describe('multidomain', { experimentalSessionSupport: true }, () => {
})
})

it('runs commands in secondary domain', () => {
it('allows users to call the "done" callback within the "switchToDomain" context asynchronously', (done) => {
// @ts-ignore
cy.switchToDomain('foobar.com', () => {
cy.switchToDomain('foobar.com', async () => {
cy
.get('[data-cy="dom-check"]')
.invoke('text')
.should('equal', 'From a secondary domain')
})
.get('[data-cy="cypress-check"]')

cy.log('after switchToDomain')
await setTimeout(() => undefined, 1000)
done()
})
})

//TODO: how should we implement errors on the done callback when it is not available?
// would this be more applicable for a system test?
it('throws "done is not defined" if callback is not passed into test but called in "switchToDomain"')
})
})
23 changes: 14 additions & 9 deletions packages/driver/src/cy/multidomain/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export function addCommands (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy,
// the other parts of multidomain
switchToDomain (domain, fn) {
const done = cy.state('done')
const p = cy.state('promise')

const deferredDone = createDeferred()
const invokeDone = (err) => {
//TODO: if an error comes back, should we call done immediately?
Expand Down Expand Up @@ -130,9 +132,9 @@ export function addCommands (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy,
// @ts-ignore
Cypress.multiDomainCommunicator.on('command:update', updateCommand)

return new Bluebird((resolve) => {
return new Bluebird((resolve, reject) => {
// @ts-ignore
Cypress.multiDomainCommunicator.once('run:domain:fn', resolve)
Cypress.multiDomainCommunicator.once('run:domain:fn', (err) => err ? reject(err) : resolve())

// @ts-ignore
Cypress.multiDomainCommunicator.once('queue:finished', () => {
Expand All @@ -141,6 +143,16 @@ export function addCommands (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy,
// @ts-ignore
Cypress.multiDomainCommunicator.off('command:update', updateCommand)

p.finally(() => {
// if done is to be called from the secondary domain, the 'done:called' event should
// have already been invoked in the switchToDomain function synchronously while the command queue finishes.
// Go ahead and remove the listener

// TODO: how do we handle setTimeout with a done that occurs in the secondary domain?
// @ts-ignore
Cypress.multiDomainCommunicator.off('done:called', invokeDone)
})

// By the time the command queue is finished, this promise should be settled as
// as done will be invoked within the secondary domain already, if applicable
// TODO: how does this work with errors where commands or other items prematurely fail in the secondary domain?
Expand Down Expand Up @@ -186,13 +198,6 @@ export function addCommands (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy,
// the specified domain
// @ts-ignore
Cypress.multiDomainCommunicator.emit('expect:domain', domain)
}).finally(() => {
// if done is to be called from the secondary domain, the 'done:called' event should
// have already been invoked in the switchToDomain function synchrously while the command queue finishes.
// Go ahead and remove the listener
// TODO: how do we handle async/setTimeout with a done?
// @ts-ignore
Cypress.multiDomainCommunicator.off('done:called', invokeDone)
})
},
})
Expand Down
37 changes: 30 additions & 7 deletions packages/driver/src/multidomain/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import $Commands from '../cypress/commands'
import $Log from '../cypress/log'
import $Listeners from '../cy/listeners'
import { SpecBridgeDomainCommunicator } from './communicator'
import { createDeferred } from '../util/deferred'

const specBridgeCommunicator = new SpecBridgeDomainCommunicator()

Expand Down Expand Up @@ -80,9 +81,24 @@ const setup = () => {
Cypress.on('log:added', onLogAdded)
Cypress.on('log:changed', onLogChanged)

specBridgeCommunicator.on('run:domain:fn', ({ fn, isDoneFnAvailable = false }) => {
specBridgeCommunicator.on('run:domain:fn', async ({ fn, isDoneFnAvailable = false }) => {
const deferredSwitchToDomain = createDeferred()

cy.state('switchToDomainDeferred', deferredSwitchToDomain)
const evalFn = `(${fn})()`

// await the eval func, whether it is a promise or not
const asyncWrapper = `(async () => {
const deferredSwitchToDomain = cy.state('switchToDomainDeferred')
try {
await ${evalFn}
deferredSwitchToDomain.resolve()
} catch(e){
deferredSwitchToDomain.reject(e)
}
})()`

if (isDoneFnAvailable) {
// stub out the 'done' function if available in the primary domain
// to notify the primary domain if the done() callback is invoked
Expand All @@ -100,16 +116,23 @@ const setup = () => {

const fnDoneWrapper = `(() => {
const done = cy.state('done');
${evalFn}
})`
${asyncWrapper}
})()`

window.eval(`(${fnDoneWrapper})()`)
window.eval(fnDoneWrapper)
} else {
// TODO: await this if it's a promise, or do whatever cy.then does
window.eval(evalFn)
window.eval(asyncWrapper)
}

specBridgeCommunicator.toPrimary('run:domain:fn')
try {
await deferredSwitchToDomain.promise
specBridgeCommunicator.toPrimary('run:domain:fn')
} catch (err) {
specBridgeCommunicator.toPrimary('run:domain:fn', err)
} finally {
cy.state('done', undefined)
cy.state('switchToDomainDeferred', undefined)
}
})

specBridgeCommunicator.on('run:command',
Expand Down

0 comments on commit 40e60bf

Please sign in to comment.