chore: Update switchToDomain signature with args key (#20722)

* chore: update data argument to be object containing args key over being an array

* chore: remove done_reference_mismatch in error_messages as done is now removed

* fix: alias args as data going through communicator to keep common interface and exclude user serialized data

* rename data references to options in switchToDomain

* use isPlainObject to simplify conditional in validator

* refactor switchToDomain options validation to check for invalid keys in options argument over missing the args key

Co-authored-by: Matt Henkes <mjhenkes@gmail.com>
This commit is contained in:
Bill Glesias
2022-03-24 10:50:04 -04:00
committed by GitHub
parent c7aa1806e7
commit 90d1eb7386
13 changed files with 147 additions and 114 deletions

View File

@@ -1912,16 +1912,22 @@ declare namespace Cypress {
* })
*/
switchToDomain(originOrDomain: string, fn: () => void): Chainable
// TODO: when we find other options to put into the 'data' argument of switchToDomain, we may want to overload this type with
// a 'data' paramater that contains all data options, including args, and one that contains all data options, excluding args.
// This will provide better typings support for whatever args is set to as opposed to an optional undefined
/**
* Enables running Cypress commands in a secondary domain
* @see https://on.cypress.io/switchToDomain
* @example
* cy.switchToDomain('example.com', [{ key: 'value' }, 'foo'], ([{ key }, foo]) => {
* cy.switchToDomain('example.com', { args: { key: 'value', foo: 'foo' } }, ({ key, foo }) => {
* expect(key).to.equal('value')
* expect(foo).to.equal('foo')
* })
*/
switchToDomain<T>(originOrDomain: string, data: T[], fn: (data: T[]) => void): Chainable
switchToDomain<T>(originOrDomain: string, options: {
args: T
}, fn: (args: T) => void): Chainable
/**
* Run a task in Node via the plugins file.

View File

@@ -830,17 +830,18 @@ namespace CypressKeyboardTests {
namespace CypressMultiDomainTests {
cy.switchToDomain('example.com', () => {})
cy.switchToDomain('example.com', [{}], (value: object[]) => {})
cy.switchToDomain('example.com', [], (value: any[]) => {})
cy.switchToDomain('example.com', [1, 'value', {}, true], (value: Array<string | number | boolean | {}>) => {})
cy.switchToDomain('example.com', ['value'], (value: string[]) => {})
cy.switchToDomain('example.com', [1], (value: number[]) => {})
cy.switchToDomain('example.com', [true], (value: boolean[]) => {})
cy.switchToDomain('example.com', { args: {}}, (value: object) => {})
cy.switchToDomain('example.com', { args: { one: 1, key: 'value', bool: true } }, (value: { one: number, key: string, bool: boolean}) => {})
cy.switchToDomain('example.com', { args: [1, 'value', true ] }, (value: Array<(number | string | boolean)>) => {})
cy.switchToDomain('example.com', { args : 'value'}, (value: string) => {})
cy.switchToDomain('example.com', { args: 1 }, (value: number) => {})
cy.switchToDomain('example.com', { args: true }, (value: boolean) => {})
cy.switchToDomain() // $ExpectError
cy.switchToDomain('example.com') // $ExpectError
cy.switchToDomain(true) // $ExpectError
cy.switchToDomain('example.com', {}) // $ExpectError
cy.switchToDomain('example.com', {}, {}) // $ExpectError
cy.switchToDomain('example.com', ['value'], (value: boolean[]) => {}) // $ExpectError
cy.switchToDomain('example.com', { args: ['value'] }, (value: boolean[]) => {}) // $ExpectError
cy.switchToDomain('example.com', {}, (value: undefined) => {}) // $ExpectError
}

View File

@@ -113,7 +113,7 @@ describe('basic login', { experimentalSessionSupport: true }, () => {
const login = (name) => {
cy.session(name, () => {
// Note, this assumes localhost is the primary domain, ideally we'd be able to specify this directly.
cy.switchToDomain('http://idp.com:3500', [name], ([name]) => {
cy.switchToDomain('http://idp.com:3500', { args: name }, (name) => {
cy.visit('http://www.idp.com:3500/fixtures/auth/idp.html')
cy.get('[data-cy="username"]').type(name)
cy.get('[data-cy="login"]').click()

View File

@@ -22,7 +22,7 @@ context('multi-domain screenshot', { experimentalSessionSupport: true }, () => {
})
it('captures the fullPage', () => {
cy.switchToDomain('http://foobar.com:3500', [this.serverResult], ([serverResult]) => {
cy.switchToDomain('http://foobar.com:3500', { args: this.serverResult }, (serverResult) => {
const automationStub = cy.stub(Cypress, 'automation').withArgs('take:screenshot').resolves(serverResult)
cy.screenshot({ capture: 'fullPage' })
@@ -36,7 +36,7 @@ context('multi-domain screenshot', { experimentalSessionSupport: true }, () => {
})
it('captures the runner', () => {
cy.switchToDomain('http://foobar.com:3500', [this.serverResult], ([serverResult]) => {
cy.switchToDomain('http://foobar.com:3500', { args: this.serverResult }, (serverResult) => {
const automationStub = cy.stub(Cypress, 'automation').withArgs('take:screenshot').resolves(serverResult)
cy.screenshot({ capture: 'runner' })
@@ -48,7 +48,7 @@ context('multi-domain screenshot', { experimentalSessionSupport: true }, () => {
})
it('captures the viewport', () => {
cy.switchToDomain('http://foobar.com:3500', [this.serverResult], ([serverResult]) => {
cy.switchToDomain('http://foobar.com:3500', { args: this.serverResult }, (serverResult) => {
const automationStub = cy.stub(Cypress, 'automation').withArgs('take:screenshot').resolves(serverResult)
cy.screenshot({ capture: 'viewport' })
@@ -80,7 +80,7 @@ context('multi-domain screenshot', { experimentalSessionSupport: true }, () => {
})
it('supports multiple titles', () => {
cy.switchToDomain('http://foobar.com:3500', [this.serverResult], ([serverResult]) => {
cy.switchToDomain('http://foobar.com:3500', { args: this.serverResult }, (serverResult) => {
const automationStub = cy.stub(Cypress, 'automation').withArgs('take:screenshot').resolves(serverResult)
cy.screenshot()
@@ -91,7 +91,7 @@ context('multi-domain screenshot', { experimentalSessionSupport: true }, () => {
})
it('supports the blackout option', () => {
cy.switchToDomain('http://foobar.com:3500', [this.serverResult], ([serverResult]) => {
cy.switchToDomain('http://foobar.com:3500', { args: this.serverResult }, (serverResult) => {
cy.stub(Cypress, 'automation').withArgs('take:screenshot').resolves(serverResult)
cy.screenshot({
@@ -109,7 +109,7 @@ context('multi-domain screenshot', { experimentalSessionSupport: true }, () => {
})
it('supports element screenshots', () => {
cy.switchToDomain('http://foobar.com:3500', [this.serverResult], ([serverResult]) => {
cy.switchToDomain('http://foobar.com:3500', { args: this.serverResult }, (serverResult) => {
const automationStub = cy.stub(Cypress, 'automation').withArgs('take:screenshot').resolves(serverResult)
cy.get('.tall-element').screenshot()
@@ -121,7 +121,7 @@ context('multi-domain screenshot', { experimentalSessionSupport: true }, () => {
})
it('supports screenshot retrying with appropriate naming', () => {
cy.switchToDomain('http://foobar.com:3500', [this.serverResult], ([serverResult]) => {
cy.switchToDomain('http://foobar.com:3500', { args: this.serverResult }, (serverResult) => {
const automationStub = cy.stub(Cypress, 'automation').withArgs('take:screenshot').resolves(serverResult)
cy.state('runnable')._currentRetry = 2
@@ -134,7 +134,7 @@ context('multi-domain screenshot', { experimentalSessionSupport: true }, () => {
})
it('calls the onBeforeScreenshot callback', () => {
cy.switchToDomain('http://foobar.com:3500', [this.serverResult], ([serverResult]) => {
cy.switchToDomain('http://foobar.com:3500', { args: this.serverResult }, (serverResult) => {
cy.stub(Cypress, 'automation').withArgs('take:screenshot').resolves(serverResult)
const onBeforeScreenshot = cy.stub()
@@ -144,7 +144,7 @@ context('multi-domain screenshot', { experimentalSessionSupport: true }, () => {
})
it('calls the onAfterScreenshot callback', () => {
cy.switchToDomain('http://foobar.com:3500', [this.serverResult], ([serverResult]) => {
cy.switchToDomain('http://foobar.com:3500', { args: this.serverResult }, (serverResult) => {
cy.stub(Cypress, 'automation').withArgs('take:screenshot').resolves(serverResult)
const onAfterScreenshot = cy.stub()
@@ -154,7 +154,7 @@ context('multi-domain screenshot', { experimentalSessionSupport: true }, () => {
})
it('supports the Cypress.screenshot callbacks', () => {
cy.switchToDomain('http://foobar.com:3500', [this.serverResult], ([serverResult]) => {
cy.switchToDomain('http://foobar.com:3500', { args: this.serverResult }, (serverResult) => {
cy.stub(Cypress, 'automation').withArgs('take:screenshot').resolves(serverResult)
const onAfterScreenshot = cy.stub()
const onBeforeScreenshot = cy.stub()
@@ -171,7 +171,7 @@ context('multi-domain screenshot', { experimentalSessionSupport: true }, () => {
})
it('supports pausing timers', () => {
cy.switchToDomain('http://foobar.com:3500', [this.serverResult], ([serverResult]) => {
cy.switchToDomain('http://foobar.com:3500', { args: this.serverResult }, (serverResult) => {
cy.stub(Cypress, 'automation').withArgs('take:screenshot').returns(Cypress.Promise.delay(500, serverResult))
cy.window().then((win) => {
@@ -204,7 +204,7 @@ context('multi-domain screenshot', { experimentalSessionSupport: true }, () => {
})
it('does not pause timers when disableTimersAndAnimations is false', () => {
cy.switchToDomain('http://foobar.com:3500', [this.serverResult], ([serverResult]) => {
cy.switchToDomain('http://foobar.com:3500', { args: this.serverResult }, (serverResult) => {
cy.stub(Cypress, 'automation').withArgs('take:screenshot').returns(Cypress.Promise.delay(500, serverResult))
cy.window().then((win) => {
@@ -234,7 +234,7 @@ context('multi-domain screenshot', { experimentalSessionSupport: true }, () => {
expect(err.message).to.include('setTimeout error after screenshot')
})
cy.switchToDomain('http://foobar.com:3500', [this.serverResult], ([serverResult]) => {
cy.switchToDomain('http://foobar.com:3500', { args: this.serverResult }, (serverResult) => {
cy.stub(Cypress, 'automation').withArgs('take:screenshot').returns(Cypress.Promise.delay(100, serverResult))
cy.window().then((win) => {
@@ -259,7 +259,7 @@ context('multi-domain screenshot', { experimentalSessionSupport: true }, () => {
expect(err.docsUrl).to.deep.eq(['https://on.cypress.io/uncaught-exception-from-application'])
})
cy.switchToDomain('http://foobar.com:3500', [this.serverResult], ([serverResult]) => {
cy.switchToDomain('http://foobar.com:3500', { args: this.serverResult }, (serverResult) => {
cy.stub(Cypress, 'automation').withArgs('take:screenshot').returns(Cypress.Promise.delay(100, serverResult))
cy.window().then((win) => {

View File

@@ -42,8 +42,7 @@
context('serializable', () => {
it(`syncs initial Cypress.${fnName}() from the primary domain to the secondary (synchronously)`, () => {
Cypress[fnName](USED_KEYS.foo, 'bar')
cy.switchToDomain('http://foobar.com:3500', [fnName, USED_KEYS], ([fnName, USED_KEYS]) => {
// @ts-ignore
cy.switchToDomain('http://foobar.com:3500', { args: { fnName, USED_KEYS } }, ({ fnName, USED_KEYS }) => {
const bar = Cypress[fnName](USED_KEYS.foo)
expect(bar).to.equal('bar')
@@ -52,8 +51,7 @@
it(`syncs serializable values in the Cypress.${fnName}() again to the secondary even after spec bridge is created`, () => {
Cypress[fnName](USED_KEYS.foo, 'baz')
cy.switchToDomain('http://foobar.com:3500', [fnName, USED_KEYS], ([fnName, USED_KEYS]) => {
// @ts-ignore
cy.switchToDomain('http://foobar.com:3500', { args: { fnName, USED_KEYS } }, ({ fnName, USED_KEYS }) => {
const baz = Cypress[fnName](USED_KEYS.foo)
expect(baz).to.equal('baz')
@@ -61,8 +59,7 @@
})
it(`syncs serializable Cypress.${fnName}() values outwards from secondary (synchronously)`, () => {
cy.switchToDomain('http://foobar.com:3500', [fnName, USED_KEYS], ([fnName, USED_KEYS]) => {
// @ts-ignore
cy.switchToDomain('http://foobar.com:3500', { args: { fnName, USED_KEYS } }, ({ fnName, USED_KEYS }) => {
Cypress[fnName](USED_KEYS.bar, 'baz')
}).then(() => {
const baz = Cypress[fnName](USED_KEYS.bar)
@@ -72,11 +69,9 @@
})
it(`syncs serializable Cypress.${fnName}() values outwards from secondary even if the value is undefined`, () => {
// @ts-ignore
Cypress[fnName](USED_KEYS.foo, 'bar')
cy.switchToDomain('http://foobar.com:3500', [fnName, USED_KEYS], ([fnName, USED_KEYS]) => {
// @ts-ignore
cy.switchToDomain('http://foobar.com:3500', { args: { fnName, USED_KEYS } }, ({ fnName, USED_KEYS }) => {
Cypress[fnName](USED_KEYS.foo, undefined)
}).then(() => {
expect(Cypress[fnName]('foo')).to.be.undefined
@@ -84,9 +79,8 @@
})
it(`syncs serializable Cypress.${fnName}() values outwards from secondary (commands/async)`, () => {
cy.switchToDomain('http://foobar.com:3500', [fnName, USED_KEYS], ([fnName, USED_KEYS]) => {
cy.switchToDomain('http://foobar.com:3500', { args: { fnName, USED_KEYS } }, ({ fnName, USED_KEYS }) => {
cy.then(() => {
// @ts-ignore
Cypress[fnName](USED_KEYS.bar, 'quux')
})
}).then(() => {
@@ -97,8 +91,7 @@
})
it(`persists Cypress.${fnName}() changes made in the secondary, assuming primary has not overwritten with a serializable value`, () => {
cy.switchToDomain('http://foobar.com:3500', [fnName, USED_KEYS], ([fnName, USED_KEYS]) => {
// @ts-ignore
cy.switchToDomain('http://foobar.com:3500', { args: { fnName, USED_KEYS } }, ({ fnName, USED_KEYS }) => {
const quux = Cypress[fnName](USED_KEYS.bar)
expect(quux).to.equal('quux')
@@ -112,14 +105,12 @@
},
}, () => {
return new Promise<void>((resolve) => {
cy.switchToDomain('http://foobar.com:3500', [fnName, USED_KEYS], ([fnName, USED_KEYS]) => {
cy.switchToDomain('http://foobar.com:3500', { args: { fnName, USED_KEYS } }, ({ fnName, USED_KEYS }) => {
setTimeout(() => {
// this value STILL gets set, but will be blown away on the next switchToDomain with whatever exists in the primary
// @ts-ignore
Cypress[fnName](USED_KEYS.baz, 'qux')
}, 100)
// @ts-ignore
Cypress[fnName](USED_KEYS.baz, 'quux')
}).then(() => {
const quux = Cypress[fnName](USED_KEYS.baz)
@@ -131,16 +122,14 @@
})
it('overwrites different values in secondary if one exists in the primary', {
// @ts-ignore
[USED_KEYS.baz]: 'quux',
env: {
[USED_KEYS.baz]: 'quux',
},
}, () => {
cy.switchToDomain('http://foobar.com:3500', [fnName, USED_KEYS], ([fnName, USED_KEYS]) => {
cy.switchToDomain('http://foobar.com:3500', { args: { fnName, USED_KEYS } }, ({ fnName, USED_KEYS }) => {
// in previous test, 'baz' was set to 'qux' after the callback window was closed.
// this should be overwritten by 'quux' that exists in the primary
// @ts-ignore
const quux = Cypress[fnName](USED_KEYS.baz)
expect(quux).to.equal('quux')
@@ -148,14 +137,12 @@
})
it(`overwrites different values in secondary, even if the Cypress.${fnName}() value does not exist in the primary`, {
// @ts-ignore
[USED_KEYS.baz]: undefined,
env: {
[USED_KEYS.baz]: undefined,
},
}, () => {
cy.switchToDomain('http://foobar.com:3500', [fnName, USED_KEYS], ([fnName, USED_KEYS]) => {
// @ts-ignore
cy.switchToDomain('http://foobar.com:3500', { args: { fnName, USED_KEYS } }, ({ fnName, USED_KEYS }) => {
const isNowUndefined = Cypress[fnName](USED_KEYS.baz)
expect(isNowUndefined).to.be.undefined
@@ -168,8 +155,7 @@
it('does not sync unserializable values from the primary to the secondary', () => {
Cypress[fnName](USED_KEYS.unserializable, unserializableFunc)
cy.switchToDomain('http://foobar.com:3500', [fnName, USED_KEYS], ([fnName, USED_KEYS]) => {
// @ts-ignore
cy.switchToDomain('http://foobar.com:3500', { args: { fnName, USED_KEYS } }, ({ fnName, USED_KEYS }) => {
const isUndefined = Cypress[fnName](USED_KEYS.unserializable)
expect(isUndefined).to.be.undefined
@@ -187,8 +173,7 @@
it('overwrites unserializable values in the primary when serializable values of same key exist in secondary', () => {
Cypress[fnName](USED_KEYS.unserializable, unserializableFunc)
cy.switchToDomain('http://foobar.com:3500', [fnName, USED_KEYS], ([fnName, USED_KEYS]) => {
// @ts-ignore
cy.switchToDomain('http://foobar.com:3500', { args: { fnName, USED_KEYS } }, ({ fnName, USED_KEYS }) => {
Cypress[fnName](USED_KEYS.unserializable, undefined)
}).then(() => {
const isNowUndefined = Cypress[fnName](USED_KEYS.unserializable)
@@ -198,17 +183,15 @@
})
it('overwrites unserializable values in the secondary when serializable values of same key exist in primary', () => {
cy.switchToDomain('http://foobar.com:3500', [fnName, USED_KEYS], ([fnName, USED_KEYS]) => {
cy.switchToDomain('http://foobar.com:3500', { args: { fnName, USED_KEYS } }, ({ fnName, USED_KEYS }) => {
const unserializableFuncSecondary = () => undefined
// @ts-ignore
Cypress[fnName](USED_KEYS.unserializable, unserializableFuncSecondary)
}).then(() => {
Cypress[fnName](USED_KEYS.unserializable, undefined)
})
cy.switchToDomain('http://foobar.com:3500', [fnName, USED_KEYS], ([fnName, USED_KEYS]) => {
// @ts-ignore
cy.switchToDomain('http://foobar.com:3500', { args: { fnName, USED_KEYS } }, ({ fnName, USED_KEYS }) => {
const isUndefined = Cypress[fnName](USED_KEYS.unserializable)
expect(isUndefined).to.be.undefined
@@ -221,10 +204,9 @@
it('does not overwrite unserializable values in the primary when unserializable values of same key exist in secondary', () => {
Cypress[fnName](USED_KEYS.unserializable, unserializableFunc)
cy.switchToDomain('http://foobar.com:3500', [fnName, USED_KEYS], ([fnName, USED_KEYS]) => {
cy.switchToDomain('http://foobar.com:3500', { args: { fnName, USED_KEYS } }, ({ fnName, USED_KEYS }) => {
const unserializableFuncSecondary = () => undefined
// @ts-ignore
Cypress[fnName](USED_KEYS.unserializable, unserializableFuncSecondary)
}).then(() => {
const isFunc = Cypress[fnName](USED_KEYS.unserializable)
@@ -241,13 +223,11 @@
Cypress[fnName](USED_KEYS.unserializable, partiallyUnserializableObject)
cy.switchToDomain('http://foobar.com:3500', [fnName, USED_KEYS], ([fnName, USED_KEYS]) => {
// @ts-ignore
cy.switchToDomain('http://foobar.com:3500', { args: { fnName, USED_KEYS } }, ({ fnName, USED_KEYS }) => {
const doesNotContainPartialAProperty = Cypress[fnName](USED_KEYS.unserializable)
expect(doesNotContainPartialAProperty?.a).to.be.undefined
// @ts-ignore
Cypress[fnName](USED_KEYS.unserializable, {
a: 3,
c: document.createElement('h1'),
@@ -266,8 +246,7 @@
}, function () {
Cypress[fnName](USED_KEYS.error, new Error('error'))
cy.switchToDomain('http://foobar.com:3500', [fnName, USED_KEYS], ([fnName, USED_KEYS]) => {
// @ts-ignore
cy.switchToDomain('http://foobar.com:3500', { args: { fnName, USED_KEYS } }, ({ fnName, USED_KEYS }) => {
const isUndefined = Cypress[fnName](USED_KEYS.error)
expect(isUndefined).to.be.undefined
@@ -280,8 +259,7 @@
}, () => {
Cypress[fnName](USED_KEYS.error, new Error('error'))
cy.switchToDomain('http://foobar.com:3500', [fnName, USED_KEYS], ([fnName, USED_KEYS]) => {
// @ts-ignore
cy.switchToDomain('http://foobar.com:3500', { args: { fnName, USED_KEYS } }, ({ fnName, USED_KEYS }) => {
const isErrorObj = Cypress[fnName](USED_KEYS.error)
// We preserve the error structure, but on preprocessing to the spec bridge, the error is converted to a flat object

View File

@@ -45,7 +45,7 @@ describe('multi-domain Cypress API', { experimentalSessionSupport: true }, () =>
keystrokeDelay: 30,
})
cy.switchToDomain('http://foobar.com:3500', [defaults], ([primaryKeyboardDefaults]) => {
cy.switchToDomain('http://foobar.com:3500', { args: defaults }, (primaryKeyboardDefaults) => {
const multiDomainKeyboardDefaults = Cypress.Keyboard.defaults({})
expect(multiDomainKeyboardDefaults).to.not.deep.equal(primaryKeyboardDefaults)
@@ -141,37 +141,37 @@ describe('multi-domain Cypress API', { experimentalSessionSupport: true }, () =>
context('properties', () => {
it('has arch property synced from primary', () => {
cy.switchToDomain('http://foobar.com:3500', [Cypress.arch], ([theArch]) => {
cy.switchToDomain('http://foobar.com:3500', { args: Cypress.arch }, (theArch) => {
expect(Cypress.arch).to.equal(theArch)
})
})
it('has browser property synced from primary', () => {
cy.switchToDomain('http://foobar.com:3500', [Cypress.browser], ([theBrowser]) => {
cy.switchToDomain('http://foobar.com:3500', { args: Cypress.browser }, (theBrowser) => {
expect(Cypress.browser).to.deep.equal(theBrowser)
})
})
it('has currentTest property synced from primary', () => {
cy.switchToDomain('http://foobar.com:3500', [Cypress.currentTest], ([theCurrentTest]) => {
cy.switchToDomain('http://foobar.com:3500', { args: Cypress.currentTest }, (theCurrentTest) => {
expect(Cypress.currentTest).to.deep.equal(theCurrentTest)
})
})
it('has platform property synced from primary', () => {
cy.switchToDomain('http://foobar.com:3500', [Cypress.platform], ([thePlatform]) => {
cy.switchToDomain('http://foobar.com:3500', { args: Cypress.platform }, (thePlatform) => {
expect(Cypress.platform).to.equal(thePlatform)
})
})
it('has testingType property synced from primary', () => {
cy.switchToDomain('http://foobar.com:3500', [Cypress.testingType], ([theTestingType]) => {
cy.switchToDomain('http://foobar.com:3500', { args: Cypress.testingType }, (theTestingType) => {
expect(Cypress.testingType).to.deep.equal(theTestingType)
})
})
it('has spec property synced from primary', () => {
cy.switchToDomain('http://foobar.com:3500', [Cypress.spec], ([theSpec]) => {
cy.switchToDomain('http://foobar.com:3500', { args: Cypress.spec }, (theSpec) => {
expect(Cypress.spec).to.deep.equal(theSpec)
})
})
@@ -185,7 +185,7 @@ describe('multi-domain Cypress API', { experimentalSessionSupport: true }, () =>
})
it('isBrowser()', () => {
cy.switchToDomain('http://foobar.com:3500', [Cypress.browser], ([theBrowser]) => {
cy.switchToDomain('http://foobar.com:3500', { args: Cypress.browser }, (theBrowser) => {
expect(Cypress.isBrowser(theBrowser.name)).to.equal(true)
})
})

View File

@@ -12,7 +12,7 @@ describe('multi-domain', { experimentalSessionSupport: true }, () => {
cy.visit('/fixtures/multi-domain.html')
cy.get('a[data-cy="multi-domain-secondary-link"]').click()
cy.switchToDomain('http://foobar.com:3500', [expectedViewport], ([expectedViewport]) => {
cy.switchToDomain('http://foobar.com:3500', { args: expectedViewport }, (expectedViewport) => {
const secondaryViewport = [cy.state('viewportWidth'), cy.state('viewportHeight')]
expect(secondaryViewport).to.deep.equal(expectedViewport)
@@ -72,7 +72,7 @@ describe('multi-domain', { experimentalSessionSupport: true }, () => {
ctx: {},
}
cy.switchToDomain('http://foobar.com:3500', [expectedRunnable], ([expectedRunnable]) => {
cy.switchToDomain('http://foobar.com:3500', { args: expectedRunnable }, (expectedRunnable) => {
const actualRunnable = cy.state('runnable')
expect(actualRunnable.titlePath()).to.deep.equal(expectedRunnable.titlePath)
@@ -112,39 +112,39 @@ describe('multi-domain', { experimentalSessionSupport: true }, () => {
describe('data argument', () => {
it('passes object to callback function', () => {
cy.switchToDomain('http://foobar.com:3500', [{ foo: 'foo', bar: 'bar' }], ([{ foo, bar }]) => {
cy.switchToDomain('http://foobar.com:3500', { args: { foo: 'foo', bar: 'bar' } }, ({ foo, bar }) => {
expect(foo).to.equal('foo')
expect(bar).to.equal('bar')
})
})
it('passes array to callback function', () => {
cy.switchToDomain('http://foobar.com:3500', ['foo', 'bar'], ([foo, bar]) => {
cy.switchToDomain('http://foobar.com:3500', { args: ['foo', 'bar'] }, ([foo, bar]) => {
expect(foo).to.equal('foo')
expect(bar).to.equal('bar')
})
})
it('passes string to callback function', () => {
cy.switchToDomain('http://foobar.com:3500', ['foo'], ([foo]) => {
cy.switchToDomain('http://foobar.com:3500', { args: 'foo' }, (foo) => {
expect(foo).to.equal('foo')
})
})
it('passes number to callback function', () => {
cy.switchToDomain('http://foobar.com:3500', [1], ([num]) => {
cy.switchToDomain('http://foobar.com:3500', { args: 1 }, (num) => {
expect(num).to.equal(1)
})
})
it('passes boolean to callback function', () => {
cy.switchToDomain('http://foobar.com:3500', [true], ([bool]) => {
cy.switchToDomain('http://foobar.com:3500', { args: true }, (bool) => {
expect(bool).to.be.true
})
})
it('passes mixed types to callback function', () => {
cy.switchToDomain('http://foobar.com:3500', ['foo', 1, true], ([foo, num, bool]) => {
cy.switchToDomain('http://foobar.com:3500', { args: { foo: 'foo', num: 1, bool: true } }, ({ foo, num, bool }) => {
expect(foo).to.equal('foo')
expect(num).to.equal(1)
expect(bool).to.be.true
@@ -204,7 +204,7 @@ describe('multi-domain', { experimentalSessionSupport: true }, () => {
done()
})
cy.switchToDomain('http://foobar.com:3500', [timeout], ([timeout]) => {
cy.switchToDomain('http://foobar.com:3500', { args: timeout }, (timeout) => {
cy.get('#doesnt-exist', {
timeout,
})

View File

@@ -179,7 +179,7 @@ describe('multi-domain', { experimentalSessionSupport: true }, () => {
it('errors passing non-array to callback function', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.equal('`cy.switchToDomain()` requires the \'data\' argument to be an array. You passed: `foo`')
expect(err.message).to.equal('`cy.switchToDomain()` requires the \'options\' argument to be an object. You passed: `foo`')
done()
})
@@ -188,9 +188,27 @@ describe('multi-domain', { experimentalSessionSupport: true }, () => {
cy.switchToDomain('foobar.com', 'foo', () => {})
})
it('errors if passed a non-serializable data value', (done) => {
it('errors passing in invalid config object to callback function', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.include('data argument specified is not serializable')
expect(err.message).to.include('`cy.switchToDomain()` detected extraneous keys in your options configuration.')
expect(err.message).to.include('The extraneous keys detected were:')
expect(err.message).to.include('> `foo, bar`')
expect(err.message).to.include('Valid keys include the following:')
expect(err.message).to.include('> `args`')
done()
})
cy.switchToDomain('foobar.com', {
// @ts-ignore
foo: 'foo',
bar: 'bar',
}, () => {})
})
it('errors if passed a non-serializable args value', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.include('arguments specified are not serializable')
if (Cypress.browser.family === 'chromium') {
expect(err.message).to.include('HTMLDivElement object could not be cloned')
@@ -203,7 +221,7 @@ describe('multi-domain', { experimentalSessionSupport: true }, () => {
const el = document.createElement('div')
cy.switchToDomain('foobar.com', ['foo', '1', el], () => {})
cy.switchToDomain('foobar.com', { args: ['foo', '1', el] }, () => {})
})
it('errors if last argument is absent', (done) => {

View File

@@ -45,7 +45,7 @@ export function addCommands (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy,
})
Commands.addAll({
switchToDomain<T> (originOrDomain: string, dataOrFn: T[] | (() => {}), fn?: (data?: T[]) => {}) {
switchToDomain<T> (originOrDomain: string, optionsOrFn: { args: T } | (() => {}), fn?: (args?: T) => {}) {
// store the invocation stack in the case that `switchToDomain` errors
communicator.userInvocationStack = state('current').get('userInvocationStack')
@@ -58,15 +58,17 @@ export function addCommands (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy,
$errUtils.throwErrByPath('switchToDomain.experiment_not_enabled')
}
let data
let options
let callbackFn
if (fn) {
callbackFn = fn
data = dataOrFn
options = optionsOrFn
} else {
callbackFn = dataOrFn
data = []
callbackFn = optionsOrFn
options = {
args: undefined,
}
}
const log = Cypress.log({
@@ -83,7 +85,7 @@ export function addCommands (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy,
validator.validate({
callbackFn,
data,
options,
originOrDomain,
})
@@ -173,7 +175,7 @@ export function addCommands (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy,
// user-specified callback to run in that domain
try {
communicator.toSpecBridge(domain, 'run:domain:fn', {
data,
args: options?.args || undefined,
fn: callbackFn.toString(),
// let the spec bridge version of Cypress know if config read-only values can be overwritten since window.top cannot be accessed in cross-origin iframes
// this should only be used for internal testing. Cast to boolean to guarantee serialization

View File

@@ -1,6 +1,8 @@
import $utils from '../../cypress/utils'
import $errUtils from '../../cypress/error_utils'
import { isString } from 'lodash'
import { difference, isPlainObject, isString } from 'lodash'
const validOptionKeys = Object.freeze(['args'])
export class Validator {
log: Cypress.Log
@@ -11,7 +13,7 @@ export class Validator {
this.onFailure = onFailure
}
validate ({ callbackFn, data, originOrDomain }) {
validate ({ callbackFn, options, originOrDomain }) {
if (!isString(originOrDomain)) {
this.onFailure()
@@ -21,13 +23,29 @@ export class Validator {
})
}
if (data && !Array.isArray(data)) {
this.onFailure()
if (options) {
if (!isPlainObject(options)) {
this.onFailure()
$errUtils.throwErrByPath('switchToDomain.invalid_data_argument', {
onFail: this.log,
args: { arg: $utils.stringify(data) },
})
$errUtils.throwErrByPath('switchToDomain.invalid_options_argument', {
onFail: this.log,
args: { arg: $utils.stringify(options) },
})
}
const extraneousKeys = difference(Object.keys(options), validOptionKeys)
if (extraneousKeys.length) {
this.onFailure()
$errUtils.throwErrByPath('switchToDomain.extraneous_options_argument', {
onFail: this.log,
args: {
extraneousKeys: extraneousKeys.join(', '),
validOptionKeys: validOptionKeys.join(', '),
},
})
}
}
if (typeof callbackFn !== 'function') {

View File

@@ -1712,17 +1712,27 @@ export default {
switchToDomain: {
docsUrl: 'https://on.cypress.io/switchToDomain',
done_reference_mismatch: {
message: `${cmd('switchToDomain')} must have done as its second argument when three or more arguments are used.`,
},
experiment_not_enabled: {
message: `${cmd('switchToDomain')} requires enabling the experimentalMultiDomain flag`,
},
invalid_origin_argument: {
message: `${cmd('switchToDomain')} requires the first argument to be either an origin ('https://app.example.com') or a domain name ('example.com'). The origin or domain name must not contain a path, hash, or query parameters. You passed: \`{{arg}}\``,
},
invalid_data_argument: {
message: `${cmd('switchToDomain')} requires the 'data' argument to be an array. You passed: \`{{arg}}\``,
invalid_options_argument: {
message: `${cmd('switchToDomain')} requires the 'options' argument to be an object. You passed: \`{{arg}}\``,
},
extraneous_options_argument ({ extraneousKeys, validOptionKeys }) {
return stripIndent`\
${cmd('switchToDomain')} detected extraneous keys in your options configuration.
The extraneous keys detected were:
> \`${extraneousKeys}\`
Valid keys include the following:
> \`${validOptionKeys}\`
`
},
invalid_fn_argument: {
message: `${cmd('switchToDomain')} requires the last argument to be a function. You passed: \`{{arg}}\``,
@@ -1733,7 +1743,7 @@ export default {
> {{error}}
This is likely because the data argument specified is not serializable. Note that functions and DOM objects cannot be serialized.`,
This is likely because the arguments specified are not serializable. Note that functions and DOM objects cannot be serialized.`,
},
callback_mixes_sync_and_async: {
message: stripIndent`\

View File

@@ -63,9 +63,9 @@ export class PrimaryDomainCommunicator extends EventEmitter {
const preprocessedData = preprocessForSerialization<any>(data)
// if user defined data is passed in, do NOT sanitize it.
if (data?.data) {
preprocessedData.data = data.data
// if user defined arguments are passed in, do NOT sanitize it.
if (data?.args) {
preprocessedData.args = data.args
}
// If there is no crossDomainDriverWindow, there is no need to send the message.
@@ -82,9 +82,9 @@ export class PrimaryDomainCommunicator extends EventEmitter {
const preprocessedData = preprocessForSerialization<any>(data)
// if user defined data is passed in, do NOT sanitize it.
if (data?.data) {
preprocessedData.data = data.data
// if user defined arguments are passed in, do NOT sanitize it.
if (data?.args) {
preprocessedData.args = data.args
}
// If there is no crossDomainDriverWindow, there is no need to send the message.

View File

@@ -7,7 +7,7 @@ import { LogUtils } from '../cypress/log'
interface RunDomainFnOptions {
config: Cypress.Config
data: any[]
args: any
env: Cypress.ObjectLike
fn: string
skipConfigValidation: boolean
@@ -83,7 +83,7 @@ export const handleDomainFn = (Cypress: Cypress.Cypress, cy: $Cy) => {
}
Cypress.specBridgeCommunicator.on('run:domain:fn', async (options: RunDomainFnOptions) => {
const { config, data, env, fn, state, skipConfigValidation, logCounter } = options
const { config, args, env, fn, state, skipConfigValidation, logCounter } = options
let queueFinished = false
@@ -114,7 +114,7 @@ export const handleDomainFn = (Cypress: Cypress.Cypress, cy: $Cy) => {
})
try {
const value = window.eval(`(${fn})`)(data)
const value = window.eval(`(${fn})`)(args)
// If we detect a non promise value with commands in queue, throw an error
if (value && cy.queue.length > 0 && !value.then) {