From 6e6847324a08f392e0b4c95f9987c47a2f4caf1c Mon Sep 17 00:00:00 2001 From: mjhenkes Date: Thu, 27 Jan 2022 13:41:19 -0600 Subject: [PATCH 01/23] first pass at yielding a subject --- .../e2e/multi-domain/multi_domain_spec.ts | 116 ++++++++++++++++++ packages/driver/src/cy/commands/connectors.ts | 2 + .../src/cy/multi-domain/commands_manager.ts | 9 +- packages/driver/src/cy/multi-domain/index.ts | 13 +- packages/driver/src/cypress/log.ts | 2 +- packages/driver/src/multi-domain/commands.ts | 7 +- .../driver/src/multi-domain/communicator.ts | 1 + packages/driver/src/multi-domain/domain_fn.ts | 11 +- .../driver/src/multi-domain/serializer.ts | 27 ++++ 9 files changed, 176 insertions(+), 12 deletions(-) create mode 100644 packages/driver/src/multi-domain/serializer.ts diff --git a/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts b/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts index b90b6771ea5a..84eeefa2bc94 100644 --- a/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts +++ b/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts @@ -258,4 +258,120 @@ describe('multi-domain', { experimentalSessionSupport: true, experimentalMultiDo it('short circuits the secondary domain command queue when "done()" is called early') }) + + // it.only('testing', async () => { + // cy.wrap('derp').then((testThing) => { + // return 'junk' + // }).then((result) => { + // console.log('last result:', result) + // }) + // }) + describe('yields', () => { + it.only('yields a value', async () => { + cy.switchToDomain('foobar.com', () => { + cy + .get('[data-cy="dom-check"]') + .invoke('text') + }).should('equal', 'From a secondary domain') + + // .then((result) => { + // console.log('last result:', result) + // }) + }) + + it.only('yields a value again', async () => { + cy.switchToDomain('foobar.com', () => { + cy + .get('[data-cy="dom-check"]') + .invoke('text') + }).should('equal', 'From a secondary domain') + + // .then((result) => { + // console.log('last result:', result) + // }) + }) + + it('yields synchronously ', async () => { + cy.switchToDomain('foobar.com', () => { + // const $form = cy.$$('[data-cy="dom-check"]') + + return 'From a secondary domain' + }).should('equal', 'From a secondary domain') + + // .then((result) => { + // console.log('result synchronous:', result) + + // return result + // }).should('equal', 'From a secondary domain') + }) + + it('yields undefined', async () => { + cy.switchToDomain('foobar.com', () => { + cy + .get('[data-cy="dom-check"]') + }).should('equal', undefined) + + // .then((result) => { + // console.log('result undefined:', result) + + // return result + // }).should('equal', undefined) + }) + + it('yields undefined if an object contains undefined', () => { + cy.switchToDomain('foobar.com', () => { + cy.wrap({ + key: undefined, + }) + }).should('equal', undefined) + }) + + it('yields undefined if an object contains a function', () => { + cy.switchToDomain('foobar.com', () => { + cy.wrap({ + key: () => { + return 'whoops' + }, + }) + }).should('equal', undefined) + }) + + it('yields undefined if an object contains a symbol', () => { + cy.switchToDomain('foobar.com', () => { + cy.wrap({ + key: Symbol('whoops'), + }) + }).should('equal', undefined) + }) + + it('yields an object containing valid types', () => { + cy.switchToDomain('foobar.com', () => { + cy.wrap({ + array: [ + 1, + 2, + ], + bool: true, + null: null, + number: 12, + object: { + key: 'key', + }, + string: 'string', + }) + }).should('deep.equal', { + array: [ + 1, + 2, + ], + bool: true, + null: null, + number: 12, + object: { + key: 'key', + }, + string: 'string', + }) + }) + }) }) diff --git a/packages/driver/src/cy/commands/connectors.ts b/packages/driver/src/cy/commands/connectors.ts index 730da1c3e4b6..292024405161 100644 --- a/packages/driver/src/cy/commands/connectors.ts +++ b/packages/driver/src/cy/commands/connectors.ts @@ -587,6 +587,8 @@ export default function (Commands, Cypress, cy, state) { // promises Commands.addAll({ prevSubject: 'optional' }, { then () { + console.log('in then state:', cy.state('subject')) + // eslint-disable-next-line prefer-rest-params return thenFn.apply(this, arguments) }, diff --git a/packages/driver/src/cy/multi-domain/commands_manager.ts b/packages/driver/src/cy/multi-domain/commands_manager.ts index f38a0fe72c19..9d6d29e2f2f0 100644 --- a/packages/driver/src/cy/multi-domain/commands_manager.ts +++ b/packages/driver/src/cy/multi-domain/commands_manager.ts @@ -1,5 +1,6 @@ import type { PrimaryDomainCommunicator } from '../../multi-domain/communicator' import { createDeferred, Deferred } from '../../util/deferred' +import { deserialize } from '../../multi-domain/serializer' export class CommandsManager { // these are proxy commands that represent real commands in a @@ -54,12 +55,16 @@ export class CommandsManager { Cypress.action('cy:enqueue:command', attrs) } - endCommand = ({ id }) => { + endCommand = ({ id, subject }) => { const command = this.commands[id] + const deserializedSubject = deserialize(subject) + + console.log('Parsed Obj', deserializedSubject) + if (command) { delete this.commands[id] - command.deferred.resolve() + command.deferred.resolve(deserializedSubject) } } diff --git a/packages/driver/src/cy/multi-domain/index.ts b/packages/driver/src/cy/multi-domain/index.ts index 9c6adfc09ff1..656e7519441e 100644 --- a/packages/driver/src/cy/multi-domain/index.ts +++ b/packages/driver/src/cy/multi-domain/index.ts @@ -4,6 +4,7 @@ import $errUtils from '../../cypress/error_utils' import { CommandsManager } from './commands_manager' import { LogsManager } from './logs_manager' import { Validator } from './validator' +import { deserialize } from '../../multi-domain/serializer' export function addCommands (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy, state: Cypress.State, config: Cypress.InternalConfig) { let timeoutId @@ -115,7 +116,7 @@ export function addCommands (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy, logsManager.listen() return new Bluebird((resolve, reject) => { - communicator.once('ran:domain:fn', (err) => { + communicator.once('ran:domain:fn', ({ subject, err }) => { sendReadyForDomain() if (err) { if (done) { @@ -136,11 +137,17 @@ export function addCommands (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy, // the command queue finishes in the SD. Otherwise, if no commands // are enqueued, clean up the command and log listeners. This case // is common if there are only assertions enqueued in the SD. + console.log('has commands?', commandsManager.hasCommands) + console.log('!done?', !done) if (!commandsManager.hasCommands && !done) { cleanup() - } + console.log('sync result:', subject) + const deserializedSubject = deserialize(subject) - resolve() + resolve(deserializedSubject) + } else { + resolve() + } }) // If done is NOT passed into switchToDomain, wait for the command queue diff --git a/packages/driver/src/cypress/log.ts b/packages/driver/src/cypress/log.ts index aefb48594ea7..8d963f6628ca 100644 --- a/packages/driver/src/cypress/log.ts +++ b/packages/driver/src/cypress/log.ts @@ -50,7 +50,7 @@ const toSerializedJSON = function (attrs) { return value() } - if (_.isFunction(value)) { + if (_.isFunction(value) || _.isSymbol(value)) { return value.toString() } diff --git a/packages/driver/src/multi-domain/commands.ts b/packages/driver/src/multi-domain/commands.ts index 053a30cd70c0..07dba753a2d7 100644 --- a/packages/driver/src/multi-domain/commands.ts +++ b/packages/driver/src/multi-domain/commands.ts @@ -1,5 +1,6 @@ import type { $Cy } from '../cypress/cy' import type { SpecBridgeDomainCommunicator } from './communicator' +import { serialize } from './serializer' export const handleCommands = (Cypress: Cypress.Cypress, cy: $Cy, specBridgeCommunicator: SpecBridgeDomainCommunicator) => { const onCommandEnqueued = (commandAttrs: Cypress.EnqueuedCommand) => { @@ -14,7 +15,11 @@ export const handleCommands = (Cypress: Cypress.Cypress, cy: $Cy, specBridgeComm const id = command.get('id') const name = command.get('name') - specBridgeCommunicator.toPrimary('command:end', { id, name }) + let serializedSubject = serialize(cy.state('subject')) + + console.log('state subject', serializedSubject) + + specBridgeCommunicator.toPrimary('command:end', { id, name, subject: serializedSubject }) } const onRunCommand = () => { diff --git a/packages/driver/src/multi-domain/communicator.ts b/packages/driver/src/multi-domain/communicator.ts index ba83834519a2..8083cc11f5d1 100644 --- a/packages/driver/src/multi-domain/communicator.ts +++ b/packages/driver/src/multi-domain/communicator.ts @@ -104,6 +104,7 @@ export class SpecBridgeDomainCommunicator extends EventEmitter { * @param {any} data - any meta data to be sent with the event. */ toPrimary (event: string, data?: any) { + console.log('event', event) let prefixedEvent = `${CROSS_DOMAIN_PREFIX}${event}` this.windowReference.top.postMessage({ event: prefixedEvent, data }, '*') diff --git a/packages/driver/src/multi-domain/domain_fn.ts b/packages/driver/src/multi-domain/domain_fn.ts index c5531516392e..4818ce5bda65 100644 --- a/packages/driver/src/multi-domain/domain_fn.ts +++ b/packages/driver/src/multi-domain/domain_fn.ts @@ -1,5 +1,6 @@ import type { $Cy } from '../cypress/cy' import type { SpecBridgeDomainCommunicator } from './communicator' +import { serialize } from './serializer' export const handleDomainFn = (cy: $Cy, specBridgeCommunicator: SpecBridgeDomainCommunicator) => { const doneEarly = () => { @@ -76,21 +77,21 @@ export const handleDomainFn = (cy: $Cy, specBridgeCommunicator: SpecBridgeDomain // await the eval func, whether it is a promise or not // we should not need to transpile this as our target browsers support async/await // see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function for more details - await window.eval(fnWrapper)(data) + const subject = await window.eval(fnWrapper)(data) - specBridgeCommunicator.toPrimary('ran:domain:fn') + specBridgeCommunicator.toPrimary('ran:domain:fn', { subject: serialize(subject) }) } catch (err) { // Native Error types currently cannot be cloned in Firefox when using 'postMessage'. // Please see https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm for more details // TODO: More standard serialization of Objects/Arrays within the communicator to avoid this type of logic if (err instanceof Error) { - specBridgeCommunicator.toPrimary('ran:domain:fn', { + specBridgeCommunicator.toPrimary('ran:domain:fn', { err: { name: err.name, message: err.message, stack: err.stack, - }) + } }) } else { - specBridgeCommunicator.toPrimary('ran:domain:fn', err) + specBridgeCommunicator.toPrimary('ran:domain:fn', { err }) } } finally { cy.state('done', undefined) diff --git a/packages/driver/src/multi-domain/serializer.ts b/packages/driver/src/multi-domain/serializer.ts new file mode 100644 index 000000000000..9fab2872b137 --- /dev/null +++ b/packages/driver/src/multi-domain/serializer.ts @@ -0,0 +1,27 @@ +import { isFunction, isSymbol } from 'lodash' + +const serialize = (value: any): string => { + let serializedSubject: string = undefined! + + try { + serializedSubject = JSON.stringify(value, (key, value) => { + // If we encounter any unserializable values we want to bail on the whole process. + if (isFunction(value) || isSymbol(value) || value === undefined) { + throw 'cannot serialize' + } + + return value + }) + } catch { + console.log('failed to serialize Subject') + // whateves + } + + return serializedSubject +} + +const deserialize = (value: string|undefined): string|object|boolean|number|null|any[] => { + return value ? JSON.parse(value) : value +} + +export { serialize, deserialize } From 4ce0cccef6c19ea6c5078feb2056f3e1a1847b86 Mon Sep 17 00:00:00 2001 From: mjhenkes Date: Thu, 27 Jan 2022 14:52:28 -0600 Subject: [PATCH 02/23] Remove logs and add comments --- packages/driver/src/cy/commands/connectors.ts | 2 -- .../src/cy/multi-domain/commands_manager.ts | 2 -- packages/driver/src/cy/multi-domain/index.ts | 4 +--- packages/driver/src/multi-domain/commands.ts | 4 ++-- .../driver/src/multi-domain/communicator.ts | 1 - .../driver/src/multi-domain/serializer.ts | 22 ++++++++++++++----- 6 files changed, 20 insertions(+), 15 deletions(-) diff --git a/packages/driver/src/cy/commands/connectors.ts b/packages/driver/src/cy/commands/connectors.ts index 292024405161..730da1c3e4b6 100644 --- a/packages/driver/src/cy/commands/connectors.ts +++ b/packages/driver/src/cy/commands/connectors.ts @@ -587,8 +587,6 @@ export default function (Commands, Cypress, cy, state) { // promises Commands.addAll({ prevSubject: 'optional' }, { then () { - console.log('in then state:', cy.state('subject')) - // eslint-disable-next-line prefer-rest-params return thenFn.apply(this, arguments) }, diff --git a/packages/driver/src/cy/multi-domain/commands_manager.ts b/packages/driver/src/cy/multi-domain/commands_manager.ts index 0770787a92ea..073668ea318b 100644 --- a/packages/driver/src/cy/multi-domain/commands_manager.ts +++ b/packages/driver/src/cy/multi-domain/commands_manager.ts @@ -67,8 +67,6 @@ export class CommandsManager { if (!err) { const deserializedSubject = deserialize(subject) - - console.log('Parsed Obj', deserializedSubject) return command.deferred.resolve(deserializedSubject) } diff --git a/packages/driver/src/cy/multi-domain/index.ts b/packages/driver/src/cy/multi-domain/index.ts index fa5a7aa9b3c6..b5283851ae73 100644 --- a/packages/driver/src/cy/multi-domain/index.ts +++ b/packages/driver/src/cy/multi-domain/index.ts @@ -140,11 +140,9 @@ export function addCommands (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy, // the command queue finishes in the SD. Otherwise, if no commands // are enqueued, clean up the command and log listeners. This case // is common if there are only assertions enqueued in the SD. - console.log('has commands?', commandsManager.hasCommands) - console.log('!done?', !done) if (!commandsManager.hasCommands && !done) { cleanup() - console.log('sync result:', subject) + // This handles when a subject is returned synchronously const deserializedSubject = deserialize(subject) resolve(deserializedSubject) diff --git a/packages/driver/src/multi-domain/commands.ts b/packages/driver/src/multi-domain/commands.ts index 07dba753a2d7..0322d5643d8a 100644 --- a/packages/driver/src/multi-domain/commands.ts +++ b/packages/driver/src/multi-domain/commands.ts @@ -17,8 +17,8 @@ export const handleCommands = (Cypress: Cypress.Cypress, cy: $Cy, specBridgeComm let serializedSubject = serialize(cy.state('subject')) - console.log('state subject', serializedSubject) - + // we need to seralize and send back the subject on each command because the next chained c + // command outside of the multi-domain context will not wait for the queue finished event. specBridgeCommunicator.toPrimary('command:end', { id, name, subject: serializedSubject }) } diff --git a/packages/driver/src/multi-domain/communicator.ts b/packages/driver/src/multi-domain/communicator.ts index 23f5aa3ec830..6502a8f6321e 100644 --- a/packages/driver/src/multi-domain/communicator.ts +++ b/packages/driver/src/multi-domain/communicator.ts @@ -148,7 +148,6 @@ export class SpecBridgeDomainCommunicator extends EventEmitter { * @param {any} data - any meta data to be sent with the event. */ toPrimary (event: string, data?: any, serializer?: (data: any) => any) { - console.log('event', event) let prefixedEvent = `${CROSS_DOMAIN_PREFIX}${event}` data = serializer ? serializer(data) : serializeForPostMessage(data) diff --git a/packages/driver/src/multi-domain/serializer.ts b/packages/driver/src/multi-domain/serializer.ts index 9fab2872b137..8a7b0ea2d178 100644 --- a/packages/driver/src/multi-domain/serializer.ts +++ b/packages/driver/src/multi-domain/serializer.ts @@ -1,25 +1,37 @@ import { isFunction, isSymbol } from 'lodash' +/** + * serialize. Used for serializing subject to be returned from subdomains to the primary domain. + * @param value any object to be serialized. + * @returns the serialized object as a string or undefined, if the object cannot be serialized. + */ const serialize = (value: any): string => { let serializedSubject: string = undefined! try { serializedSubject = JSON.stringify(value, (key, value) => { - // If we encounter any unserializable values we want to bail on the whole process. + // If we encounter any unserializable values we want to abort the whole process. if (isFunction(value) || isSymbol(value) || value === undefined) { - throw 'cannot serialize' + throw 'abort serialization' } return value }) - } catch { - console.log('failed to serialize Subject') - // whateves + } catch (error) { + // we only want to silence serialization errors. + if (error !== 'abort serialization') { + throw error + } } return serializedSubject } +/** + * deserialize. Companion to the above serialize function. + * @param value a string + * @returns the deserialized object + */ const deserialize = (value: string|undefined): string|object|boolean|number|null|any[] => { return value ? JSON.parse(value) : value } From a091a2150f393829c06df76186d69004cd810785 Mon Sep 17 00:00:00 2001 From: mjhenkes Date: Thu, 27 Jan 2022 14:55:12 -0600 Subject: [PATCH 03/23] remove duplicate test --- .../integration/e2e/multi-domain/multi_domain_spec.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts b/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts index 42c0bfd2ed09..b8b2d7d3578e 100644 --- a/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts +++ b/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts @@ -288,14 +288,6 @@ describe('multi-domain', { experimentalSessionSupport: true, experimentalMultiDo }).should('equal', 'From a secondary domain') }) - it('yields another value', () => { - cy.switchToDomain('foobar.com', () => { - cy - .get('[data-cy="dom-check"]') - .invoke('text') - }).should('equal', 'From a secondary domain') - }) - it('yields the cy value even if a return is present', () => { cy.switchToDomain('foobar.com', () => { cy @@ -312,7 +304,7 @@ describe('multi-domain', { experimentalSessionSupport: true, experimentalMultiDo }).should('equal', 'From a secondary domain') }) - it('yields undefined', () => { + it('yields undefined if subject cannot be serialized', () => { cy.switchToDomain('foobar.com', () => { cy .get('[data-cy="dom-check"]') From 8e14be9493d090a1288861ee68d378466a7e172f Mon Sep 17 00:00:00 2001 From: mjhenkes Date: Thu, 27 Jan 2022 15:43:32 -0600 Subject: [PATCH 04/23] Add debug logging for aborted --- packages/driver/src/multi-domain/serializer.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/driver/src/multi-domain/serializer.ts b/packages/driver/src/multi-domain/serializer.ts index 8a7b0ea2d178..2c3068f970d1 100644 --- a/packages/driver/src/multi-domain/serializer.ts +++ b/packages/driver/src/multi-domain/serializer.ts @@ -1,11 +1,14 @@ import { isFunction, isSymbol } from 'lodash' +import debugFn from 'debug' + +const debug = debugFn('cypress:driver:cypress') /** * serialize. Used for serializing subject to be returned from subdomains to the primary domain. * @param value any object to be serialized. * @returns the serialized object as a string or undefined, if the object cannot be serialized. */ -const serialize = (value: any): string => { +const serialize = (value: any): string | undefined => { let serializedSubject: string = undefined! try { @@ -19,7 +22,9 @@ const serialize = (value: any): string => { }) } catch (error) { // we only want to silence serialization errors. - if (error !== 'abort serialization') { + if (error === 'abort serialization') { + debug(`Serialization aborted for subject: ${value}`) + } else { throw error } } From c5ca5c20ced4329bd8c27f352b0433de7e7737f0 Mon Sep 17 00:00:00 2001 From: Matt Henkes Date: Fri, 28 Jan 2022 13:10:37 -0600 Subject: [PATCH 05/23] Apply suggestions from code review Co-authored-by: Bill Glesias --- .../cypress/integration/e2e/multi-domain/multi_domain_spec.ts | 2 +- packages/driver/src/multi-domain/commands.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts b/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts index b8b2d7d3578e..fa71b646c894 100644 --- a/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts +++ b/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts @@ -298,7 +298,7 @@ describe('multi-domain', { experimentalSessionSupport: true, experimentalMultiDo }).should('equal', 'From a secondary domain') }) - it('yields synchronously ', () => { + it('yields synchronously', () => { cy.switchToDomain('foobar.com', () => { return 'From a secondary domain' }).should('equal', 'From a secondary domain') diff --git a/packages/driver/src/multi-domain/commands.ts b/packages/driver/src/multi-domain/commands.ts index 0322d5643d8a..61c5dce83635 100644 --- a/packages/driver/src/multi-domain/commands.ts +++ b/packages/driver/src/multi-domain/commands.ts @@ -17,7 +17,7 @@ export const handleCommands = (Cypress: Cypress.Cypress, cy: $Cy, specBridgeComm let serializedSubject = serialize(cy.state('subject')) - // we need to seralize and send back the subject on each command because the next chained c + // we need to serialize and send back the subject on each command because the next chained // command outside of the multi-domain context will not wait for the queue finished event. specBridgeCommunicator.toPrimary('command:end', { id, name, subject: serializedSubject }) } From 40f2ad60de764a9cd8612c14f47f569276ae8740 Mon Sep 17 00:00:00 2001 From: mjhenkes Date: Mon, 31 Jan 2022 11:55:25 -0600 Subject: [PATCH 06/23] New strategy for serializing subjects --- .../e2e/multi-domain/multi_domain_spec.ts | 26 +++++++---- .../src/cy/multi-domain/commands_manager.ts | 5 +-- packages/driver/src/cy/multi-domain/index.ts | 5 +-- packages/driver/src/multi-domain/commands.ts | 7 ++- .../driver/src/multi-domain/communicator.ts | 31 ++++++++++--- packages/driver/src/multi-domain/domain_fn.ts | 5 +-- packages/driver/src/multi-domain/logs.ts | 4 +- .../driver/src/multi-domain/serializer.ts | 44 ------------------- 8 files changed, 52 insertions(+), 75 deletions(-) delete mode 100644 packages/driver/src/multi-domain/serializer.ts diff --git a/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts b/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts index fa71b646c894..86d4690c7ab4 100644 --- a/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts +++ b/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts @@ -304,6 +304,12 @@ describe('multi-domain', { experimentalSessionSupport: true, experimentalMultiDo }).should('equal', 'From a secondary domain') }) + it('yields asynchronously', () => { + cy.switchToDomain('foobar.com', async () => { + return 'From a secondary domain' + }).should('equal', 'From a secondary domain') + }) + it('yields undefined if subject cannot be serialized', () => { cy.switchToDomain('foobar.com', () => { cy @@ -311,14 +317,6 @@ describe('multi-domain', { experimentalSessionSupport: true, experimentalMultiDo }).should('equal', undefined) }) - it('yields undefined if an object contains undefined', () => { - cy.switchToDomain('foobar.com', () => { - cy.wrap({ - key: undefined, - }) - }).should('equal', undefined) - }) - it('yields undefined if an object contains a function', () => { cy.switchToDomain('foobar.com', () => { cy.wrap({ @@ -337,6 +335,16 @@ describe('multi-domain', { experimentalSessionSupport: true, experimentalMultiDo }).should('equal', undefined) }) + // This test will only work on chrome. + it.skip('yields undefined if an object contains an error', () => { + cy.switchToDomain('foobar.com', () => { + cy.wrap({ + key: new Error('Boom goes the dynamite'), + }) + }).its('key.message') + .should('equal', 'Boom goes the dynamite') + }) + it('yields an object containing valid types', () => { cy.switchToDomain('foobar.com', () => { cy.wrap({ @@ -344,6 +352,7 @@ describe('multi-domain', { experimentalSessionSupport: true, experimentalMultiDo 1, 2, ], + undefined, bool: true, null: null, number: 12, @@ -357,6 +366,7 @@ describe('multi-domain', { experimentalSessionSupport: true, experimentalMultiDo 1, 2, ], + undefined, bool: true, null: null, number: 12, diff --git a/packages/driver/src/cy/multi-domain/commands_manager.ts b/packages/driver/src/cy/multi-domain/commands_manager.ts index 073668ea318b..2ec0c0ab0620 100644 --- a/packages/driver/src/cy/multi-domain/commands_manager.ts +++ b/packages/driver/src/cy/multi-domain/commands_manager.ts @@ -1,7 +1,6 @@ import type { PrimaryDomainCommunicator } from '../../multi-domain/communicator' import { createDeferred, Deferred } from '../../util/deferred' import { correctStackForCrossDomainError } from './util' -import { deserialize } from '../../multi-domain/serializer' export class CommandsManager { // these are proxy commands that represent real commands in a @@ -66,9 +65,7 @@ export class CommandsManager { delete this.commands[id] if (!err) { - const deserializedSubject = deserialize(subject) - - return command.deferred.resolve(deserializedSubject) + return command.deferred.resolve(subject) } // If the command has failed, cast the error back to a proper Error object diff --git a/packages/driver/src/cy/multi-domain/index.ts b/packages/driver/src/cy/multi-domain/index.ts index b5283851ae73..9881d2f82467 100644 --- a/packages/driver/src/cy/multi-domain/index.ts +++ b/packages/driver/src/cy/multi-domain/index.ts @@ -3,7 +3,6 @@ import $errUtils from '../../cypress/error_utils' import { CommandsManager } from './commands_manager' import { LogsManager } from './logs_manager' import { Validator } from './validator' -import { deserialize } from '../../multi-domain/serializer' export function addCommands (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy, state: Cypress.State, config: Cypress.InternalConfig) { let timeoutId @@ -143,9 +142,7 @@ export function addCommands (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy, if (!commandsManager.hasCommands && !done) { cleanup() // This handles when a subject is returned synchronously - const deserializedSubject = deserialize(subject) - - resolve(deserializedSubject) + resolve(subject) } else { resolve() } diff --git a/packages/driver/src/multi-domain/commands.ts b/packages/driver/src/multi-domain/commands.ts index 61c5dce83635..c8f773e31f63 100644 --- a/packages/driver/src/multi-domain/commands.ts +++ b/packages/driver/src/multi-domain/commands.ts @@ -1,6 +1,5 @@ import type { $Cy } from '../cypress/cy' import type { SpecBridgeDomainCommunicator } from './communicator' -import { serialize } from './serializer' export const handleCommands = (Cypress: Cypress.Cypress, cy: $Cy, specBridgeCommunicator: SpecBridgeDomainCommunicator) => { const onCommandEnqueued = (commandAttrs: Cypress.EnqueuedCommand) => { @@ -14,12 +13,12 @@ export const handleCommands = (Cypress: Cypress.Cypress, cy: $Cy, specBridgeComm const onCommandEnd = (command: Cypress.CommandQueue) => { const id = command.get('id') const name = command.get('name') - - let serializedSubject = serialize(cy.state('subject')) + const logId = command.getLastLog()?.get('id') + const subject = cy.state('subject') // we need to serialize and send back the subject on each command because the next chained // command outside of the multi-domain context will not wait for the queue finished event. - specBridgeCommunicator.toPrimary('command:end', { id, name, subject: serializedSubject }) + specBridgeCommunicator.toPrimaryCommandEnd({ id, name, subject, logId }) } const onRunCommand = () => { diff --git a/packages/driver/src/multi-domain/communicator.ts b/packages/driver/src/multi-domain/communicator.ts index 6502a8f6321e..490748a43a73 100644 --- a/packages/driver/src/multi-domain/communicator.ts +++ b/packages/driver/src/multi-domain/communicator.ts @@ -8,11 +8,11 @@ const debug = debugFn('cypress:driver:multi-domain') const CROSS_DOMAIN_PREFIX = 'cross:domain:' -const serializeForPostMessage = (value) => { +const preprocessErrorForPostMessage = (value) => { const { isDom } = $dom if (_.isError(value)) { - const serializedError = _.mapValues(clone(value), serializeForPostMessage) + const serializedError = _.mapValues(clone(value), preprocessErrorForPostMessage) return { ... serializedError, @@ -25,7 +25,7 @@ const serializeForPostMessage = (value) => { } if (_.isArray(value)) { - return _.map(value, serializeForPostMessage) + return _.map(value, preprocessErrorForPostMessage) } if (isDom(value)) { @@ -40,7 +40,7 @@ const serializeForPostMessage = (value) => { // clone to nuke circular references // and blow away anything that throws try { - return _.mapValues(clone(value), serializeForPostMessage) + return _.mapValues(clone(value), preprocessErrorForPostMessage) } catch (err) { return null } @@ -147,10 +147,29 @@ export class SpecBridgeDomainCommunicator 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. */ - toPrimary (event: string, data?: any, serializer?: (data: any) => any) { + toPrimary (event: string, data?: any) { let prefixedEvent = `${CROSS_DOMAIN_PREFIX}${event}` - data = serializer ? serializer(data) : serializeForPostMessage(data) this.windowReference.top.postMessage({ event: prefixedEvent, data }, '*') } + + toPrimaryCommandEnd (data: {id: string, subject?: any, name: string, err?: Error, logId: string }) { + const { id, subject, name, err, logId } = data + + // We always want to make sure errors are posted, so clean it up to send. + const preProcessedError = preprocessErrorForPostMessage(err) + + try { + this.toPrimary('command:end', { id, subject, name, err: preProcessedError, logId }) + } catch (error: any) { + if (subject && error.name === 'DataCloneError') { + // If the subject threw the 'DataCloneError', the subject cannot be serialized at which point try again with an undefined subject. + this.toPrimaryCommandEnd({ id, name, subject: undefined, logId }) + } else { + // Try to send the message again, with the new error. + this.toPrimaryCommandEnd({ id, name, err: error, logId }) + throw error + } + } + } } diff --git a/packages/driver/src/multi-domain/domain_fn.ts b/packages/driver/src/multi-domain/domain_fn.ts index c1523fc379c8..fd86022a3261 100644 --- a/packages/driver/src/multi-domain/domain_fn.ts +++ b/packages/driver/src/multi-domain/domain_fn.ts @@ -1,6 +1,5 @@ import type { $Cy } from '../cypress/cy' import type { SpecBridgeDomainCommunicator } from './communicator' -import { serialize } from './serializer' export const handleDomainFn = (cy: $Cy, specBridgeCommunicator: SpecBridgeDomainCommunicator) => { const doneEarly = () => { @@ -80,7 +79,7 @@ export const handleDomainFn = (cy: $Cy, specBridgeCommunicator: SpecBridgeDomain const name = command.get('name') const logId = command.getLastLog()?.get('id') - specBridgeCommunicator.toPrimary('command:end', { id, name, err, logId }) + specBridgeCommunicator.toPrimaryCommandEnd({ id, name, err, logId }) }) try { @@ -89,7 +88,7 @@ export const handleDomainFn = (cy: $Cy, specBridgeCommunicator: SpecBridgeDomain // see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function for more details const subject = await window.eval(fnWrapper)(data) - specBridgeCommunicator.toPrimary('ran:domain:fn', { subject: serialize(subject) }) + specBridgeCommunicator.toPrimary('ran:domain:fn', { subject }) } catch (err) { specBridgeCommunicator.toPrimary('ran:domain:fn', { err }) } finally { diff --git a/packages/driver/src/multi-domain/logs.ts b/packages/driver/src/multi-domain/logs.ts index fa4f28470728..e2eec9de8e55 100644 --- a/packages/driver/src/multi-domain/logs.ts +++ b/packages/driver/src/multi-domain/logs.ts @@ -4,11 +4,11 @@ import $Log from '../cypress/log' export const handleLogs = (Cypress: Cypress.Cypress, specBridgeCommunicator: SpecBridgeDomainCommunicator) => { const onLogAdded = (attrs) => { - specBridgeCommunicator.toPrimary('log:added', attrs, $Log.toSerializedJSON) + specBridgeCommunicator.toPrimary('log:added', $Log.toSerializedJSON(attrs)) } const onLogChanged = (attrs) => { - specBridgeCommunicator.toPrimary('log:changed', attrs, $Log.toSerializedJSON) + specBridgeCommunicator.toPrimary('log:changed', $Log.toSerializedJSON(attrs)) } Cypress.on('log:added', onLogAdded) diff --git a/packages/driver/src/multi-domain/serializer.ts b/packages/driver/src/multi-domain/serializer.ts deleted file mode 100644 index 2c3068f970d1..000000000000 --- a/packages/driver/src/multi-domain/serializer.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { isFunction, isSymbol } from 'lodash' -import debugFn from 'debug' - -const debug = debugFn('cypress:driver:cypress') - -/** - * serialize. Used for serializing subject to be returned from subdomains to the primary domain. - * @param value any object to be serialized. - * @returns the serialized object as a string or undefined, if the object cannot be serialized. - */ -const serialize = (value: any): string | undefined => { - let serializedSubject: string = undefined! - - try { - serializedSubject = JSON.stringify(value, (key, value) => { - // If we encounter any unserializable values we want to abort the whole process. - if (isFunction(value) || isSymbol(value) || value === undefined) { - throw 'abort serialization' - } - - return value - }) - } catch (error) { - // we only want to silence serialization errors. - if (error === 'abort serialization') { - debug(`Serialization aborted for subject: ${value}`) - } else { - throw error - } - } - - return serializedSubject -} - -/** - * deserialize. Companion to the above serialize function. - * @param value a string - * @returns the deserialized object - */ -const deserialize = (value: string|undefined): string|object|boolean|number|null|any[] => { - return value ? JSON.parse(value) : value -} - -export { serialize, deserialize } From b2981e55b2d0b989e40e27efaea47f135ad4b771 Mon Sep 17 00:00:00 2001 From: mjhenkes Date: Mon, 31 Jan 2022 13:20:47 -0600 Subject: [PATCH 07/23] handle 'ran:domain:fn' event --- .../e2e/multi-domain/multi_domain_spec.ts | 8 +++- .../driver/src/multi-domain/communicator.ts | 42 +++++++++++-------- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts b/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts index 86d4690c7ab4..dc3b78c3f79b 100644 --- a/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts +++ b/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts @@ -306,7 +306,11 @@ describe('multi-domain', { experimentalSessionSupport: true, experimentalMultiDo it('yields asynchronously', () => { cy.switchToDomain('foobar.com', async () => { - return 'From a secondary domain' + return new Promise((resolve: (val: string) => any, reject) => { + setTimeout(() => { + resolve('From a secondary domain') + }, 1000) + }) }).should('equal', 'From a secondary domain') }) @@ -336,7 +340,7 @@ describe('multi-domain', { experimentalSessionSupport: true, experimentalMultiDo }) // This test will only work on chrome. - it.skip('yields undefined if an object contains an error', () => { + it.skip('yields an error if an object contains an error', () => { cy.switchToDomain('foobar.com', () => { cy.wrap({ key: new Error('Boom goes the dynamite'), diff --git a/packages/driver/src/multi-domain/communicator.ts b/packages/driver/src/multi-domain/communicator.ts index 490748a43a73..c20999d94921 100644 --- a/packages/driver/src/multi-domain/communicator.ts +++ b/packages/driver/src/multi-domain/communicator.ts @@ -125,6 +125,26 @@ export class PrimaryDomainCommunicator extends EventEmitter { export class SpecBridgeDomainCommunicator extends EventEmitter { private windowReference + private handleSubjectAndErr = (event, data) => { + const { subject, err, ...other } = data + + // We always want to make sure errors are posted, so clean it up to send. + const preProcessedError = preprocessErrorForPostMessage(err) + + try { + this.toPrimary(event, { subject, err: preProcessedError, ...other }) + } catch (error: any) { + if (subject && error.name === 'DataCloneError') { + // If the subject threw the 'DataCloneError', the subject cannot be serialized at which point try again with an undefined subject. + this.handleSubjectAndErr(event, { subject: undefined, ...other }) + } else { + // Try to send the message again, with the new error. + this.handleSubjectAndErr(event, { err: error, ...other }) + throw error + } + } + } + /** * Initializes the event handler to receive messages from the primary domain. * @param {Window} win - a reference to the window object in the spec bridge/iframe. @@ -153,23 +173,11 @@ export class SpecBridgeDomainCommunicator extends EventEmitter { this.windowReference.top.postMessage({ event: prefixedEvent, data }, '*') } - toPrimaryCommandEnd (data: {id: string, subject?: any, name: string, err?: Error, logId: string }) { - const { id, subject, name, err, logId } = data - - // We always want to make sure errors are posted, so clean it up to send. - const preProcessedError = preprocessErrorForPostMessage(err) + toPrimaryCommandEnd (data: {id: string, subject?: any, name: string, err?: any, logId: string }) { + this.handleSubjectAndErr('command:end', data) + } - try { - this.toPrimary('command:end', { id, subject, name, err: preProcessedError, logId }) - } catch (error: any) { - if (subject && error.name === 'DataCloneError') { - // If the subject threw the 'DataCloneError', the subject cannot be serialized at which point try again with an undefined subject. - this.toPrimaryCommandEnd({ id, name, subject: undefined, logId }) - } else { - // Try to send the message again, with the new error. - this.toPrimaryCommandEnd({ id, name, err: error, logId }) - throw error - } - } + toPrimaryRanDomainFn (data: { subject?: any, err?: any }) { + this.handleSubjectAndErr('ran:domain:fn', data) } } From bf6522c640bc3315f7aeb77b1354493f6bfc2394 Mon Sep 17 00:00:00 2001 From: mjhenkes Date: Mon, 31 Jan 2022 13:34:17 -0600 Subject: [PATCH 08/23] Fix lint --- .../cypress/integration/e2e/multi-domain/multi_domain_spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts b/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts index dc3b78c3f79b..c80ad338b008 100644 --- a/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts +++ b/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts @@ -339,7 +339,7 @@ describe('multi-domain', { experimentalSessionSupport: true, experimentalMultiDo }).should('equal', undefined) }) - // This test will only work on chrome. + // NOTE: This test will only work on chrome. it.skip('yields an error if an object contains an error', () => { cy.switchToDomain('foobar.com', () => { cy.wrap({ From 24bbf59c2349ac9061b243aeb83f76e0303f77e6 Mon Sep 17 00:00:00 2001 From: mjhenkes Date: Wed, 2 Feb 2022 10:34:39 -0600 Subject: [PATCH 09/23] Now with proxy error handling! --- .../e2e/multi-domain/multi_domain_spec.ts | 137 +++++++++++++++++- .../cypress/integration/util/queue_spec.ts | 12 ++ .../src/cy/multi-domain/commands_manager.ts | 9 +- .../failedSerializeSubjectProxy.ts | 64 ++++++++ packages/driver/src/cy/multi-domain/index.ts | 9 +- packages/driver/src/cypress/error_messages.ts | 26 ++++ packages/driver/src/multi-domain/commands.ts | 8 +- .../driver/src/multi-domain/communicator.ts | 11 +- packages/driver/src/multi-domain/domain_fn.ts | 17 ++- packages/driver/src/util/queue.ts | 12 ++ 10 files changed, 289 insertions(+), 16 deletions(-) create mode 100644 packages/driver/src/cy/multi-domain/failedSerializeSubjectProxy.ts diff --git a/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts b/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts index c80ad338b008..df5f1ab702c7 100644 --- a/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts +++ b/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts @@ -1,8 +1,17 @@ import _ from 'lodash' +const { assertLogLength } = require('../../../support/utils') // @ts-ignore / session support is needed for visiting about:blank between tests describe('multi-domain', { experimentalSessionSupport: true, experimentalMultiDomain: true }, () => { + let logs: any = [] + beforeEach(() => { + logs = [] + + cy.on('log:added', (attrs, log) => { + logs.push(log) + }) + cy.visit('/fixtures/multi-domain.html') cy.get('a[data-cy="multi-domain-secondary-link"]').click() }) @@ -289,13 +298,37 @@ describe('multi-domain', { experimentalSessionSupport: true, experimentalMultiDo }) it('yields the cy value even if a return is present', () => { + cy.switchToDomain('foobar.com', async () => { + cy + .get('[data-cy="dom-check"]') + .invoke('text') + + const p = new Promise((resolve, reject) => { + setTimeout(() => { + resolve('text') + }, 1000) + }) + + return p + }).should('equal', 'From a secondary domain') + }) + + it('errors if a cy command is present and it returns a sync value', (done) => { + cy.on('fail', (err) => { + assertLogLength(logs, 6) + expect(logs[5].get('error')).to.eq(err) + expect(err.message).to.include('`cy.switchToDomain()` failed because you are mixing up async and sync code.') + + done() + }) + cy.switchToDomain('foobar.com', () => { cy .get('[data-cy="dom-check"]') .invoke('text') return 'text' - }).should('equal', 'From a secondary domain') + }) }) it('yields synchronously', () => { @@ -314,24 +347,83 @@ describe('multi-domain', { experimentalSessionSupport: true, experimentalMultiDo }).should('equal', 'From a secondary domain') }) - it('yields undefined if subject cannot be serialized', () => { + it('succeeds if subject cannot be serialized and is not accessed synchronously', () => { + cy.switchToDomain('foobar.com', () => { + return { + symbol: Symbol(''), + } + }).then((obj) => { + return 'object not accessed' + }).should('equal', 'object not accessed') + }) + + it('throws if subject cannot be serialized and is accessed synchronously', (done) => { + cy.on('fail', (err) => { + assertLogLength(logs, 6) + expect(logs[5].get('error')).to.eq(err) + expect(err.message).to.include('`cy.switchToDomain()` could not serialize the subject due to one of it\'s properties not being supported by the structured clone algorithm.') + + done() + }) + + cy.switchToDomain('foobar.com', () => { + return { + symbol: Symbol(''), + } + }).should('equal', '') + }) + + it('succeeds if subject cannot be serialized and is not accessed', () => { cy.switchToDomain('foobar.com', () => { cy .get('[data-cy="dom-check"]') - }).should('equal', undefined) + }).then((obj) => { + return 'object not accessed' + }).should('equal', 'object not accessed') + }) + + it('throws if subject cannot be serialized and is accessed', (done) => { + cy.on('fail', (err) => { + assertLogLength(logs, 7) + expect(logs[6].get('error')).to.eq(err) + expect(err.message).to.include('`cy.switchToDomain()` could not serialize the subject due to one of it\'s properties not being supported by the structured clone algorithm.') + + done() + }) + + cy.switchToDomain('foobar.com', () => { + cy + .get('[data-cy="dom-check"]') + }).should('equal') }) - it('yields undefined if an object contains a function', () => { + it('throws if an object contains a function', (done) => { + cy.on('fail', (err) => { + assertLogLength(logs, 7) + expect(logs[6].get('error')).to.eq(err) + expect(err.message).to.include('`cy.switchToDomain()` could not serialize the subject due to one of it\'s properties not being supported by the structured clone algorithm.') + + done() + }) + cy.switchToDomain('foobar.com', () => { cy.wrap({ key: () => { return 'whoops' }, }) - }).should('equal', undefined) + }).invoke('key').should('equal', 'whoops') }) - it('yields undefined if an object contains a symbol', () => { + it('throws if an object contains a symbol', (done) => { + cy.on('fail', (err) => { + assertLogLength(logs, 7) + expect(logs[6].get('error')).to.eq(err) + expect(err.message).to.include('`cy.switchToDomain()` could not serialize the subject due to one of it\'s properties not being supported by the structured clone algorithm.') + + done() + }) + cy.switchToDomain('foobar.com', () => { cy.wrap({ key: Symbol('whoops'), @@ -339,6 +431,39 @@ describe('multi-domain', { experimentalSessionSupport: true, experimentalMultiDo }).should('equal', undefined) }) + it('throws if an object is a function', (done) => { + cy.on('fail', (err) => { + assertLogLength(logs, 7) + expect(logs[6].get('error')).to.eq(err) + expect(err.message).to.include('`cy.switchToDomain()` could not serialize the subject due functions not being supported by the structured clone algorithm.') + + done() + }) + + cy.switchToDomain('foobar.com', () => { + cy.wrap(() => { + return 'text' + }) + }).then((obj) => { + // @ts-ignore + obj() + }) + }) + + it('throws if an object is a symbol', (done) => { + cy.on('fail', (err) => { + assertLogLength(logs, 7) + expect(logs[6].get('error')).to.eq(err) + expect(err.message).to.include('`cy.switchToDomain()` could not serialize the subject due symbols not being supported by the structured clone algorithm.') + + done() + }) + + cy.switchToDomain('foobar.com', () => { + cy.wrap(Symbol('symbol')) + }).should('equal', 'symbol') + }) + // NOTE: This test will only work on chrome. it.skip('yields an error if an object contains an error', () => { cy.switchToDomain('foobar.com', () => { diff --git a/packages/driver/cypress/integration/util/queue_spec.ts b/packages/driver/cypress/integration/util/queue_spec.ts index c23a23168f0b..ecbd6a52b7d6 100644 --- a/packages/driver/cypress/integration/util/queue_spec.ts +++ b/packages/driver/cypress/integration/util/queue_spec.ts @@ -201,4 +201,16 @@ describe('src/util/queue', () => { expect(queue.stopped).to.false }) }) + + context('.last', () => { + it('returns the last item', () => { + expect(queue.last()).to.deep.equal({ id: '3' }) + + queue.add({ id: '4' }) + expect(queue.last()).to.deep.equal({ id: '4' }) + + queue.clear() + expect(queue.last()).to.equal(undefined) + }) + }) }) diff --git a/packages/driver/src/cy/multi-domain/commands_manager.ts b/packages/driver/src/cy/multi-domain/commands_manager.ts index 2ec0c0ab0620..9598aeab4b6a 100644 --- a/packages/driver/src/cy/multi-domain/commands_manager.ts +++ b/packages/driver/src/cy/multi-domain/commands_manager.ts @@ -1,6 +1,7 @@ import type { PrimaryDomainCommunicator } from '../../multi-domain/communicator' import { createDeferred, Deferred } from '../../util/deferred' import { correctStackForCrossDomainError } from './util' +import { failedToSerializeSubject } from './failedSerializeSubjectProxy' export class CommandsManager { // these are proxy commands that represent real commands in a @@ -57,7 +58,7 @@ export class CommandsManager { Cypress.action('cy:enqueue:command', attrs) } - endCommand = ({ id, subject, name, err, logId }) => { + endCommand = ({ id, subject, failedToSerializeSubjectOfType, name, err, logId }) => { const command = this.commands[id] if (!command) return @@ -65,6 +66,12 @@ export class CommandsManager { delete this.commands[id] if (!err) { + if (failedToSerializeSubjectOfType) { + return command.deferred.resolve( + failedToSerializeSubject(failedToSerializeSubjectOfType), + ) + } + return command.deferred.resolve(subject) } diff --git a/packages/driver/src/cy/multi-domain/failedSerializeSubjectProxy.ts b/packages/driver/src/cy/multi-domain/failedSerializeSubjectProxy.ts new file mode 100644 index 000000000000..46f241d4bc56 --- /dev/null +++ b/packages/driver/src/cy/multi-domain/failedSerializeSubjectProxy.ts @@ -0,0 +1,64 @@ +import $errUtils from '../../cypress/error_utils' + +/** + * Create a proxy object to fail when accessed or called. + * @param type The type of operand that failed to serialize + * @returns A proxy object that will fail when accessed. + */ +const failedToSerializeSubject = (type: string) => { + let target = {} + + // If the failed subject is a function, use a function as the target. + if (type === 'function') { + target = () => {} + } + + // Symbol note: I don't think the target can be a symbol, but we can just use an object until the symbols is accessed, then provide a different error. + + return new Proxy(target, { + /** + * Throw an error if the proxy is called like a function. + * @param target the proxy target + * @param thisArg this + * @param argumentsList args passed. + */ + apply (target, thisArg, argumentsList) { + $errUtils.throwErrByPath('switchToDomain.failed_to_serialize_function') + }, + + /** + * Throw an error if any properties besides the listed ones are accessed. + * @param target The proxy target + * @param prop The property being accessed + * @param receiver Either the proxy or an object that inherits from the proxy. + * @returns either an error or the result of the allowed get on the target. + */ + get (target, prop, receiver) { + // These properties are required to avoid failing prior to attempting to use the subject. + if ( + prop === 'then' + || prop === Symbol.isConcatSpreadable + || prop === 'jquery' + || prop === 'nodeType' + // || prop === Symbol.toStringTag // If this is passed through to the target we will not property fail the 'cy.invoke' command. + || prop === 'window' + || prop === 'document' + || prop === 'inspect' + || prop === 'isSinonProxy' + || prop === '_spreadArray' + || prop === 'selector' + ) { + return target[prop] + } + + // Provide a slightly different message if the object was meant to be a symbol. + if (type === 'symbol') { + $errUtils.throwErrByPath('switchToDomain.failed_to_serialize_symbol') + } else { + $errUtils.throwErrByPath('switchToDomain.failed_to_serialize_object') + } + }, + }) +} + +export { failedToSerializeSubject } diff --git a/packages/driver/src/cy/multi-domain/index.ts b/packages/driver/src/cy/multi-domain/index.ts index 9881d2f82467..530c7df6a875 100644 --- a/packages/driver/src/cy/multi-domain/index.ts +++ b/packages/driver/src/cy/multi-domain/index.ts @@ -3,6 +3,7 @@ import $errUtils from '../../cypress/error_utils' import { CommandsManager } from './commands_manager' import { LogsManager } from './logs_manager' import { Validator } from './validator' +import { failedToSerializeSubject } from './failedSerializeSubjectProxy' export function addCommands (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy, state: Cypress.State, config: Cypress.InternalConfig) { let timeoutId @@ -118,7 +119,7 @@ export function addCommands (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy, logsManager.listen() return new Bluebird((resolve, reject) => { - communicator.once('ran:domain:fn', ({ subject, err }) => { + communicator.once('ran:domain:fn', ({ subject, failedToSerializeSubjectOfType, err }) => { sendReadyForDomain() if (err) { if (done) { @@ -142,6 +143,12 @@ export function addCommands (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy, if (!commandsManager.hasCommands && !done) { cleanup() // This handles when a subject is returned synchronously + if (failedToSerializeSubjectOfType) { + return resolve( + failedToSerializeSubject(failedToSerializeSubjectOfType), + ) + } + resolve(subject) } else { resolve() diff --git a/packages/driver/src/cypress/error_messages.ts b/packages/driver/src/cypress/error_messages.ts index 92ff3d2c5737..ef384be8a990 100644 --- a/packages/driver/src/cypress/error_messages.ts +++ b/packages/driver/src/cypress/error_messages.ts @@ -1710,6 +1710,32 @@ export default { This is likely because the data argument specified is not serializable. Note that functions and DOM objects cannot be serialized.`, }, + callback_mixes_sync_and_async: { + message: stripIndent`\ + ${cmd('switchToDomain')} failed because you are mixing up async and sync code. + + In your callback function you invoked 1 or more cy commands but then returned a synchronous value. + + Cypress commands are asynchronous and it doesn't make sense to queue cy commands and yet return a synchronous value. + + You likely forgot to properly chain the cy commands using another \`cy.then()\`. + + The value you synchronously returned was: \`{{value}}\``, + }, + failed_to_serialize_object: { + message: stripIndent`\ + ${cmd('switchToDomain')} could not serialize the subject due to one of it's properties not being supported by the structured clone algorithm. + + To properly serialize this subject, remove or serialize any unsupported properties.`, + }, + failed_to_serialize_function: { + message: stripIndent`\ + ${cmd('switchToDomain')} could not serialize the subject due functions not being supported by the structured clone algorithm.`, + }, + failed_to_serialize_symbol: { + message: stripIndent`\ + ${cmd('switchToDomain')} could not serialize the subject due symbols not being supported by the structured clone algorithm.`, + }, }, task: { diff --git a/packages/driver/src/multi-domain/commands.ts b/packages/driver/src/multi-domain/commands.ts index c8f773e31f63..04e47332454e 100644 --- a/packages/driver/src/multi-domain/commands.ts +++ b/packages/driver/src/multi-domain/commands.ts @@ -14,7 +14,13 @@ export const handleCommands = (Cypress: Cypress.Cypress, cy: $Cy, specBridgeComm const id = command.get('id') const name = command.get('name') const logId = command.getLastLog()?.get('id') - const subject = cy.state('subject') + + let subject: string | undefined = undefined + + // Prevent serialization if this isn't the last command in the queue. + if (cy.queue.last().get('id') === id) { + subject = cy.state('subject') + } // we need to serialize and send back the subject on each command because the next chained // command outside of the multi-domain context will not wait for the queue finished event. diff --git a/packages/driver/src/multi-domain/communicator.ts b/packages/driver/src/multi-domain/communicator.ts index c20999d94921..78327aa7d974 100644 --- a/packages/driver/src/multi-domain/communicator.ts +++ b/packages/driver/src/multi-domain/communicator.ts @@ -128,15 +128,18 @@ export class SpecBridgeDomainCommunicator extends EventEmitter { private handleSubjectAndErr = (event, data) => { const { subject, err, ...other } = data - // We always want to make sure errors are posted, so clean it up to send. - const preProcessedError = preprocessErrorForPostMessage(err) - try { + // We always want to make sure errors are posted, so clean it up to send. + const preProcessedError = preprocessErrorForPostMessage(err) + this.toPrimary(event, { subject, err: preProcessedError, ...other }) } catch (error: any) { if (subject && error.name === 'DataCloneError') { + // Send the type of object that failed to serialize. + const failedToSerializeSubjectOfType = typeof subject + // If the subject threw the 'DataCloneError', the subject cannot be serialized at which point try again with an undefined subject. - this.handleSubjectAndErr(event, { subject: undefined, ...other }) + this.handleSubjectAndErr(event, { failedToSerializeSubjectOfType, ...other }) } else { // Try to send the message again, with the new error. this.handleSubjectAndErr(event, { err: error, ...other }) diff --git a/packages/driver/src/multi-domain/domain_fn.ts b/packages/driver/src/multi-domain/domain_fn.ts index fd86022a3261..c99eae6cd431 100644 --- a/packages/driver/src/multi-domain/domain_fn.ts +++ b/packages/driver/src/multi-domain/domain_fn.ts @@ -1,5 +1,7 @@ import type { $Cy } from '../cypress/cy' import type { SpecBridgeDomainCommunicator } from './communicator' +import $errUtils from '../cypress/error_utils' +import $utils from '../cypress/utils' export const handleDomainFn = (cy: $Cy, specBridgeCommunicator: SpecBridgeDomainCommunicator) => { const doneEarly = () => { @@ -86,11 +88,20 @@ export const handleDomainFn = (cy: $Cy, specBridgeCommunicator: SpecBridgeDomain // await the eval func, whether it is a promise or not // we should not need to transpile this as our target browsers support async/await // see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function for more details - const subject = await window.eval(fnWrapper)(data) + const value = window.eval(fnWrapper)(data) - specBridgeCommunicator.toPrimary('ran:domain:fn', { subject }) + // If we detect a non promise value returned + if (value && cy.queue.length > 0 && !value.then) { + $errUtils.throwErrByPath('switchToDomain.callback_mixes_sync_and_async', { + args: { value: $utils.stringify(value) }, + }) + } else { + const subject = await value + + specBridgeCommunicator.toPrimaryRanDomainFn({ subject }) + } } catch (err) { - specBridgeCommunicator.toPrimary('ran:domain:fn', { err }) + specBridgeCommunicator.toPrimaryRanDomainFn({ err }) } finally { cy.state('done', undefined) } diff --git a/packages/driver/src/util/queue.ts b/packages/driver/src/util/queue.ts index 4a1564979d3f..cb3b11f197d5 100644 --- a/packages/driver/src/util/queue.ts +++ b/packages/driver/src/util/queue.ts @@ -104,4 +104,16 @@ export class Queue { get stopped () { return this._stopped } + + /** + * Helper function to return the last item in the queue. + * @returns The last object or undefined if the queue is empty. + */ + last (): T | undefined { + if (this.length < 1) { + return undefined + } + + return this.at(this.length - 1) + } } From ffe38e73c4f733aa23643206d10d19992c6d0bbe Mon Sep 17 00:00:00 2001 From: mjhenkes Date: Wed, 2 Feb 2022 11:11:46 -0600 Subject: [PATCH 10/23] Break yields tests out into their own file. --- .../e2e/multi-domain/multi_domain_spec.ts | 230 +---------------- .../multi-domain/multi_domain_yield_spec.ts | 234 ++++++++++++++++++ 2 files changed, 235 insertions(+), 229 deletions(-) create mode 100644 packages/driver/cypress/integration/e2e/multi-domain/multi_domain_yield_spec.ts diff --git a/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts b/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts index df5f1ab702c7..a6512291f01e 100644 --- a/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts +++ b/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts @@ -1,17 +1,8 @@ import _ from 'lodash' -const { assertLogLength } = require('../../../support/utils') // @ts-ignore / session support is needed for visiting about:blank between tests -describe('multi-domain', { experimentalSessionSupport: true, experimentalMultiDomain: true }, () => { - let logs: any = [] - +describe('multi-domain yields', { experimentalSessionSupport: true, experimentalMultiDomain: true }, () => { beforeEach(() => { - logs = [] - - cy.on('log:added', (attrs, log) => { - logs.push(log) - }) - cy.visit('/fixtures/multi-domain.html') cy.get('a[data-cy="multi-domain-secondary-link"]').click() }) @@ -287,223 +278,4 @@ describe('multi-domain', { experimentalSessionSupport: true, experimentalMultiDo it('short circuits the secondary domain command queue when "done()" is called early') }) - - describe('yields', () => { - it('yields a value', () => { - cy.switchToDomain('foobar.com', () => { - cy - .get('[data-cy="dom-check"]') - .invoke('text') - }).should('equal', 'From a secondary domain') - }) - - it('yields the cy value even if a return is present', () => { - cy.switchToDomain('foobar.com', async () => { - cy - .get('[data-cy="dom-check"]') - .invoke('text') - - const p = new Promise((resolve, reject) => { - setTimeout(() => { - resolve('text') - }, 1000) - }) - - return p - }).should('equal', 'From a secondary domain') - }) - - it('errors if a cy command is present and it returns a sync value', (done) => { - cy.on('fail', (err) => { - assertLogLength(logs, 6) - expect(logs[5].get('error')).to.eq(err) - expect(err.message).to.include('`cy.switchToDomain()` failed because you are mixing up async and sync code.') - - done() - }) - - cy.switchToDomain('foobar.com', () => { - cy - .get('[data-cy="dom-check"]') - .invoke('text') - - return 'text' - }) - }) - - it('yields synchronously', () => { - cy.switchToDomain('foobar.com', () => { - return 'From a secondary domain' - }).should('equal', 'From a secondary domain') - }) - - it('yields asynchronously', () => { - cy.switchToDomain('foobar.com', async () => { - return new Promise((resolve: (val: string) => any, reject) => { - setTimeout(() => { - resolve('From a secondary domain') - }, 1000) - }) - }).should('equal', 'From a secondary domain') - }) - - it('succeeds if subject cannot be serialized and is not accessed synchronously', () => { - cy.switchToDomain('foobar.com', () => { - return { - symbol: Symbol(''), - } - }).then((obj) => { - return 'object not accessed' - }).should('equal', 'object not accessed') - }) - - it('throws if subject cannot be serialized and is accessed synchronously', (done) => { - cy.on('fail', (err) => { - assertLogLength(logs, 6) - expect(logs[5].get('error')).to.eq(err) - expect(err.message).to.include('`cy.switchToDomain()` could not serialize the subject due to one of it\'s properties not being supported by the structured clone algorithm.') - - done() - }) - - cy.switchToDomain('foobar.com', () => { - return { - symbol: Symbol(''), - } - }).should('equal', '') - }) - - it('succeeds if subject cannot be serialized and is not accessed', () => { - cy.switchToDomain('foobar.com', () => { - cy - .get('[data-cy="dom-check"]') - }).then((obj) => { - return 'object not accessed' - }).should('equal', 'object not accessed') - }) - - it('throws if subject cannot be serialized and is accessed', (done) => { - cy.on('fail', (err) => { - assertLogLength(logs, 7) - expect(logs[6].get('error')).to.eq(err) - expect(err.message).to.include('`cy.switchToDomain()` could not serialize the subject due to one of it\'s properties not being supported by the structured clone algorithm.') - - done() - }) - - cy.switchToDomain('foobar.com', () => { - cy - .get('[data-cy="dom-check"]') - }).should('equal') - }) - - it('throws if an object contains a function', (done) => { - cy.on('fail', (err) => { - assertLogLength(logs, 7) - expect(logs[6].get('error')).to.eq(err) - expect(err.message).to.include('`cy.switchToDomain()` could not serialize the subject due to one of it\'s properties not being supported by the structured clone algorithm.') - - done() - }) - - cy.switchToDomain('foobar.com', () => { - cy.wrap({ - key: () => { - return 'whoops' - }, - }) - }).invoke('key').should('equal', 'whoops') - }) - - it('throws if an object contains a symbol', (done) => { - cy.on('fail', (err) => { - assertLogLength(logs, 7) - expect(logs[6].get('error')).to.eq(err) - expect(err.message).to.include('`cy.switchToDomain()` could not serialize the subject due to one of it\'s properties not being supported by the structured clone algorithm.') - - done() - }) - - cy.switchToDomain('foobar.com', () => { - cy.wrap({ - key: Symbol('whoops'), - }) - }).should('equal', undefined) - }) - - it('throws if an object is a function', (done) => { - cy.on('fail', (err) => { - assertLogLength(logs, 7) - expect(logs[6].get('error')).to.eq(err) - expect(err.message).to.include('`cy.switchToDomain()` could not serialize the subject due functions not being supported by the structured clone algorithm.') - - done() - }) - - cy.switchToDomain('foobar.com', () => { - cy.wrap(() => { - return 'text' - }) - }).then((obj) => { - // @ts-ignore - obj() - }) - }) - - it('throws if an object is a symbol', (done) => { - cy.on('fail', (err) => { - assertLogLength(logs, 7) - expect(logs[6].get('error')).to.eq(err) - expect(err.message).to.include('`cy.switchToDomain()` could not serialize the subject due symbols not being supported by the structured clone algorithm.') - - done() - }) - - cy.switchToDomain('foobar.com', () => { - cy.wrap(Symbol('symbol')) - }).should('equal', 'symbol') - }) - - // NOTE: This test will only work on chrome. - it.skip('yields an error if an object contains an error', () => { - cy.switchToDomain('foobar.com', () => { - cy.wrap({ - key: new Error('Boom goes the dynamite'), - }) - }).its('key.message') - .should('equal', 'Boom goes the dynamite') - }) - - it('yields an object containing valid types', () => { - cy.switchToDomain('foobar.com', () => { - cy.wrap({ - array: [ - 1, - 2, - ], - undefined, - bool: true, - null: null, - number: 12, - object: { - key: 'key', - }, - string: 'string', - }) - }).should('deep.equal', { - array: [ - 1, - 2, - ], - undefined, - bool: true, - null: null, - number: 12, - object: { - key: 'key', - }, - string: 'string', - }) - }) - }) }) diff --git a/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_yield_spec.ts b/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_yield_spec.ts new file mode 100644 index 000000000000..6aa29790a33d --- /dev/null +++ b/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_yield_spec.ts @@ -0,0 +1,234 @@ +const { assertLogLength } = require('../../../support/utils') + +// @ts-ignore / session support is needed for visiting about:blank between tests +describe('multi-domain', { experimentalSessionSupport: true, experimentalMultiDomain: true }, () => { + let logs: any = [] + + beforeEach(() => { + logs = [] + + cy.on('log:added', (attrs, log) => { + logs.push(log) + }) + + cy.visit('/fixtures/multi-domain.html') + cy.get('a[data-cy="multi-domain-secondary-link"]').click() + }) + + it('yields a value', () => { + cy.switchToDomain('foobar.com', () => { + cy + .get('[data-cy="dom-check"]') + .invoke('text') + }).should('equal', 'From a secondary domain') + }) + + it('yields the cy value even if a return is present', () => { + cy.switchToDomain('foobar.com', async () => { + cy + .get('[data-cy="dom-check"]') + .invoke('text') + + const p = new Promise((resolve, reject) => { + setTimeout(() => { + resolve('text') + }, 50) + }) + + return p + }).should('equal', 'From a secondary domain') + }) + + it('errors if a cy command is present and it returns a sync value', (done) => { + cy.on('fail', (err) => { + assertLogLength(logs, 6) + expect(logs[5].get('error')).to.eq(err) + expect(err.message).to.include('`cy.switchToDomain()` failed because you are mixing up async and sync code.') + + done() + }) + + cy.switchToDomain('foobar.com', () => { + cy + .get('[data-cy="dom-check"]') + .invoke('text') + + return 'text' + }) + }) + + it('yields synchronously', () => { + cy.switchToDomain('foobar.com', () => { + return 'From a secondary domain' + }).should('equal', 'From a secondary domain') + }) + + it('yields asynchronously', () => { + cy.switchToDomain('foobar.com', async () => { + return new Promise((resolve: (val: string) => any, reject) => { + setTimeout(() => { + resolve('From a secondary domain') + }, 1000) + }) + }).should('equal', 'From a secondary domain') + }) + + it('succeeds if subject cannot be serialized and is not accessed synchronously', () => { + cy.switchToDomain('foobar.com', () => { + return { + symbol: Symbol(''), + } + }).then((obj) => { + return 'object not accessed' + }).should('equal', 'object not accessed') + }) + + it('throws if subject cannot be serialized and is accessed synchronously', (done) => { + cy.on('fail', (err) => { + assertLogLength(logs, 6) + expect(logs[5].get('error')).to.eq(err) + expect(err.message).to.include('`cy.switchToDomain()` could not serialize the subject due to one of it\'s properties not being supported by the structured clone algorithm.') + + done() + }) + + cy.switchToDomain('foobar.com', () => { + return { + symbol: Symbol(''), + } + }).should('equal', '') + }) + + it('succeeds if subject cannot be serialized and is not accessed', () => { + cy.switchToDomain('foobar.com', () => { + cy + .get('[data-cy="dom-check"]') + }).then((obj) => { + return 'object not accessed' + }).should('equal', 'object not accessed') + }) + + it('throws if subject cannot be serialized and is accessed', (done) => { + cy.on('fail', (err) => { + assertLogLength(logs, 7) + expect(logs[6].get('error')).to.eq(err) + expect(err.message).to.include('`cy.switchToDomain()` could not serialize the subject due to one of it\'s properties not being supported by the structured clone algorithm.') + + done() + }) + + cy.switchToDomain('foobar.com', () => { + cy + .get('[data-cy="dom-check"]') + }).should('equal') + }) + + it('throws if an object contains a function', (done) => { + cy.on('fail', (err) => { + assertLogLength(logs, 7) + expect(logs[6].get('error')).to.eq(err) + expect(err.message).to.include('`cy.switchToDomain()` could not serialize the subject due to one of it\'s properties not being supported by the structured clone algorithm.') + + done() + }) + + cy.switchToDomain('foobar.com', () => { + cy.wrap({ + key: () => { + return 'whoops' + }, + }) + }).invoke('key').should('equal', 'whoops') + }) + + it('throws if an object contains a symbol', (done) => { + cy.on('fail', (err) => { + assertLogLength(logs, 7) + expect(logs[6].get('error')).to.eq(err) + expect(err.message).to.include('`cy.switchToDomain()` could not serialize the subject due to one of it\'s properties not being supported by the structured clone algorithm.') + + done() + }) + + cy.switchToDomain('foobar.com', () => { + cy.wrap({ + key: Symbol('whoops'), + }) + }).should('equal', undefined) + }) + + it('throws if an object is a function', (done) => { + cy.on('fail', (err) => { + assertLogLength(logs, 7) + expect(logs[6].get('error')).to.eq(err) + expect(err.message).to.include('`cy.switchToDomain()` could not serialize the subject due functions not being supported by the structured clone algorithm.') + + done() + }) + + cy.switchToDomain('foobar.com', () => { + cy.wrap(() => { + return 'text' + }) + }).then((obj) => { + // @ts-ignore + obj() + }) + }) + + it('throws if an object is a symbol', (done) => { + cy.on('fail', (err) => { + assertLogLength(logs, 7) + expect(logs[6].get('error')).to.eq(err) + expect(err.message).to.include('`cy.switchToDomain()` could not serialize the subject due symbols not being supported by the structured clone algorithm.') + + done() + }) + + cy.switchToDomain('foobar.com', () => { + cy.wrap(Symbol('symbol')) + }).should('equal', 'symbol') + }) + + // NOTE: This test will only work on chrome. + it.skip('yields an error if an object contains an error', () => { + cy.switchToDomain('foobar.com', () => { + cy.wrap({ + key: new Error('Boom goes the dynamite'), + }) + }).its('key.message') + .should('equal', 'Boom goes the dynamite') + }) + + it('yields an object containing valid types', () => { + cy.switchToDomain('foobar.com', () => { + cy.wrap({ + array: [ + 1, + 2, + ], + undefined, + bool: true, + null: null, + number: 12, + object: { + key: 'key', + }, + string: 'string', + }) + }).should('deep.equal', { + array: [ + 1, + 2, + ], + undefined, + bool: true, + null: null, + number: 12, + object: { + key: 'key', + }, + string: 'string', + }) + }) +}) From 6a4288128655729e6a4c0739923361676bd810fa Mon Sep 17 00:00:00 2001 From: mjhenkes Date: Wed, 2 Feb 2022 11:22:53 -0600 Subject: [PATCH 11/23] updated test --- .../integration/e2e/multi-domain/multi_domain_yield_spec.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_yield_spec.ts b/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_yield_spec.ts index 6aa29790a33d..9fcc0cb22ad9 100644 --- a/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_yield_spec.ts +++ b/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_yield_spec.ts @@ -96,7 +96,11 @@ describe('multi-domain', { experimentalSessionSupport: true, experimentalMultiDo return { symbol: Symbol(''), } - }).should('equal', '') + }).then((obj) => { + // This will fail accessing the symbol + // @ts-ignore + return obj.symbol + }) }) it('succeeds if subject cannot be serialized and is not accessed', () => { From 19fb21c945df250f2d186ac893497442ce35a776 Mon Sep 17 00:00:00 2001 From: Matt Henkes Date: Wed, 2 Feb 2022 11:38:13 -0600 Subject: [PATCH 12/23] Update packages/driver/src/cy/multi-domain/failedSerializeSubjectProxy.ts Co-authored-by: Matt Schile --- .../driver/src/cy/multi-domain/failedSerializeSubjectProxy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/driver/src/cy/multi-domain/failedSerializeSubjectProxy.ts b/packages/driver/src/cy/multi-domain/failedSerializeSubjectProxy.ts index 46f241d4bc56..8e15f61fe8e2 100644 --- a/packages/driver/src/cy/multi-domain/failedSerializeSubjectProxy.ts +++ b/packages/driver/src/cy/multi-domain/failedSerializeSubjectProxy.ts @@ -40,7 +40,7 @@ const failedToSerializeSubject = (type: string) => { || prop === Symbol.isConcatSpreadable || prop === 'jquery' || prop === 'nodeType' - // || prop === Symbol.toStringTag // If this is passed through to the target we will not property fail the 'cy.invoke' command. + // || prop === Symbol.toStringTag // If this is passed through to the target we will not properly fail the 'cy.invoke' command. || prop === 'window' || prop === 'document' || prop === 'inspect' From 66f1de4d36c6c3a8f798c65805ee49325263a297 Mon Sep 17 00:00:00 2001 From: mjhenkes Date: Wed, 2 Feb 2022 11:38:06 -0600 Subject: [PATCH 13/23] add a param for the malformed should --- .../integration/e2e/multi-domain/multi_domain_yield_spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_yield_spec.ts b/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_yield_spec.ts index 9fcc0cb22ad9..6d7e956de5be 100644 --- a/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_yield_spec.ts +++ b/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_yield_spec.ts @@ -124,7 +124,7 @@ describe('multi-domain', { experimentalSessionSupport: true, experimentalMultiDo cy.switchToDomain('foobar.com', () => { cy .get('[data-cy="dom-check"]') - }).should('equal') + }).should('equal', 'object') }) it('throws if an object contains a function', (done) => { From 905eb5b172e3a07d1b2af30d4a7ce377961cfc36 Mon Sep 17 00:00:00 2001 From: Matt Henkes Date: Wed, 2 Feb 2022 11:53:31 -0600 Subject: [PATCH 14/23] Apply suggestions from code review Co-authored-by: Matt Schile --- packages/driver/src/cypress/error_messages.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/driver/src/cypress/error_messages.ts b/packages/driver/src/cypress/error_messages.ts index ef384be8a990..01665e73e251 100644 --- a/packages/driver/src/cypress/error_messages.ts +++ b/packages/driver/src/cypress/error_messages.ts @@ -1714,7 +1714,7 @@ export default { message: stripIndent`\ ${cmd('switchToDomain')} failed because you are mixing up async and sync code. - In your callback function you invoked 1 or more cy commands but then returned a synchronous value. + In your callback function you invoked one or more cy commands but then returned a synchronous value. Cypress commands are asynchronous and it doesn't make sense to queue cy commands and yet return a synchronous value. @@ -1724,17 +1724,17 @@ export default { }, failed_to_serialize_object: { message: stripIndent`\ - ${cmd('switchToDomain')} could not serialize the subject due to one of it's properties not being supported by the structured clone algorithm. + ${cmd('switchToDomain')} could not serialize the subject due to one of its properties not being supported by the structured clone algorithm. To properly serialize this subject, remove or serialize any unsupported properties.`, }, failed_to_serialize_function: { message: stripIndent`\ - ${cmd('switchToDomain')} could not serialize the subject due functions not being supported by the structured clone algorithm.`, + ${cmd('switchToDomain')} could not serialize the subject due to functions not being supported by the structured clone algorithm.`, }, failed_to_serialize_symbol: { message: stripIndent`\ - ${cmd('switchToDomain')} could not serialize the subject due symbols not being supported by the structured clone algorithm.`, + ${cmd('switchToDomain')} could not serialize the subject due to symbols not being supported by the structured clone algorithm.`, }, }, From 2fe2f7f6fa362af6dac63ad81c8d75200d29013b Mon Sep 17 00:00:00 2001 From: mjhenkes Date: Wed, 2 Feb 2022 11:54:18 -0600 Subject: [PATCH 15/23] update test to work cross browsers --- .../multi-domain/multi_domain_yield_spec.ts | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_yield_spec.ts b/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_yield_spec.ts index 6d7e956de5be..a027b7bf709e 100644 --- a/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_yield_spec.ts +++ b/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_yield_spec.ts @@ -194,14 +194,28 @@ describe('multi-domain', { experimentalSessionSupport: true, experimentalMultiDo }).should('equal', 'symbol') }) - // NOTE: This test will only work on chrome. - it.skip('yields an error if an object contains an error', () => { + // NOTE: Errors can only be serialized on chromium browsers. + it('yields an error if an object contains an error', (done) => { + const isChromium = Cypress.isBrowser({ family: 'chromium' }) + + cy.on('fail', (err) => { + if (!isChromium) { + assertLogLength(logs, 7) + expect(logs[6].get('error')).to.eq(err) + expect(err.message).to.include('`cy.switchToDomain()` could not serialize the subject due to one of it\'s properties not being supported by the structured clone algorithm.') + } + + done() + }) + cy.switchToDomain('foobar.com', () => { cy.wrap({ key: new Error('Boom goes the dynamite'), }) }).its('key.message') - .should('equal', 'Boom goes the dynamite') + .should('equal', 'Boom goes the dynamite').then(() => { + done() + }) }) it('yields an object containing valid types', () => { From 9ff8fdeea526def20e6a8c9e3d4dca09c3656df9 Mon Sep 17 00:00:00 2001 From: mjhenkes Date: Wed, 2 Feb 2022 12:09:06 -0600 Subject: [PATCH 16/23] fix test strings --- .../e2e/multi-domain/multi_domain_yield_spec.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_yield_spec.ts b/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_yield_spec.ts index a027b7bf709e..df0d5a24c6b3 100644 --- a/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_yield_spec.ts +++ b/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_yield_spec.ts @@ -87,7 +87,7 @@ describe('multi-domain', { experimentalSessionSupport: true, experimentalMultiDo cy.on('fail', (err) => { assertLogLength(logs, 6) expect(logs[5].get('error')).to.eq(err) - expect(err.message).to.include('`cy.switchToDomain()` could not serialize the subject due to one of it\'s properties not being supported by the structured clone algorithm.') + expect(err.message).to.include('`cy.switchToDomain()` could not serialize the subject due to one of its properties not being supported by the structured clone algorithm.') done() }) @@ -116,7 +116,7 @@ describe('multi-domain', { experimentalSessionSupport: true, experimentalMultiDo cy.on('fail', (err) => { assertLogLength(logs, 7) expect(logs[6].get('error')).to.eq(err) - expect(err.message).to.include('`cy.switchToDomain()` could not serialize the subject due to one of it\'s properties not being supported by the structured clone algorithm.') + expect(err.message).to.include('`cy.switchToDomain()` could not serialize the subject due to one of its properties not being supported by the structured clone algorithm.') done() }) @@ -124,14 +124,15 @@ describe('multi-domain', { experimentalSessionSupport: true, experimentalMultiDo cy.switchToDomain('foobar.com', () => { cy .get('[data-cy="dom-check"]') - }).should('equal', 'object') + }).invoke('text') + .should('equal', 'From a secondary domain') }) it('throws if an object contains a function', (done) => { cy.on('fail', (err) => { assertLogLength(logs, 7) expect(logs[6].get('error')).to.eq(err) - expect(err.message).to.include('`cy.switchToDomain()` could not serialize the subject due to one of it\'s properties not being supported by the structured clone algorithm.') + expect(err.message).to.include('`cy.switchToDomain()` could not serialize the subject due to one of its properties not being supported by the structured clone algorithm.') done() }) @@ -149,7 +150,7 @@ describe('multi-domain', { experimentalSessionSupport: true, experimentalMultiDo cy.on('fail', (err) => { assertLogLength(logs, 7) expect(logs[6].get('error')).to.eq(err) - expect(err.message).to.include('`cy.switchToDomain()` could not serialize the subject due to one of it\'s properties not being supported by the structured clone algorithm.') + expect(err.message).to.include('`cy.switchToDomain()` could not serialize the subject due to one of its properties not being supported by the structured clone algorithm.') done() }) @@ -165,7 +166,7 @@ describe('multi-domain', { experimentalSessionSupport: true, experimentalMultiDo cy.on('fail', (err) => { assertLogLength(logs, 7) expect(logs[6].get('error')).to.eq(err) - expect(err.message).to.include('`cy.switchToDomain()` could not serialize the subject due functions not being supported by the structured clone algorithm.') + expect(err.message).to.include('`cy.switchToDomain()` could not serialize the subject due to functions not being supported by the structured clone algorithm.') done() }) @@ -184,7 +185,7 @@ describe('multi-domain', { experimentalSessionSupport: true, experimentalMultiDo cy.on('fail', (err) => { assertLogLength(logs, 7) expect(logs[6].get('error')).to.eq(err) - expect(err.message).to.include('`cy.switchToDomain()` could not serialize the subject due symbols not being supported by the structured clone algorithm.') + expect(err.message).to.include('`cy.switchToDomain()` could not serialize the subject due to symbols not being supported by the structured clone algorithm.') done() }) @@ -202,7 +203,7 @@ describe('multi-domain', { experimentalSessionSupport: true, experimentalMultiDo if (!isChromium) { assertLogLength(logs, 7) expect(logs[6].get('error')).to.eq(err) - expect(err.message).to.include('`cy.switchToDomain()` could not serialize the subject due to one of it\'s properties not being supported by the structured clone algorithm.') + expect(err.message).to.include('`cy.switchToDomain()` could not serialize the subject due to one of its properties not being supported by the structured clone algorithm.') } done() From c834c9b4de835730c4169fb8d8edefab7881c49a Mon Sep 17 00:00:00 2001 From: Matt Henkes Date: Wed, 2 Feb 2022 12:09:35 -0600 Subject: [PATCH 17/23] Update packages/driver/src/util/queue.ts Co-authored-by: Matt Schile --- packages/driver/src/util/queue.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/driver/src/util/queue.ts b/packages/driver/src/util/queue.ts index cb3b11f197d5..21fa8aacc9e2 100644 --- a/packages/driver/src/util/queue.ts +++ b/packages/driver/src/util/queue.ts @@ -107,7 +107,7 @@ export class Queue { /** * Helper function to return the last item in the queue. - * @returns The last object or undefined if the queue is empty. + * @returns The last item or undefined if the queue is empty. */ last (): T | undefined { if (this.length < 1) { From ee142669b3071eb68ca32e88099de5bb121879a7 Mon Sep 17 00:00:00 2001 From: mjhenkes Date: Wed, 2 Feb 2022 12:25:39 -0600 Subject: [PATCH 18/23] optional chaining ! --- packages/driver/src/multi-domain/commands.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/driver/src/multi-domain/commands.ts b/packages/driver/src/multi-domain/commands.ts index 04e47332454e..dff130c36118 100644 --- a/packages/driver/src/multi-domain/commands.ts +++ b/packages/driver/src/multi-domain/commands.ts @@ -18,7 +18,7 @@ export const handleCommands = (Cypress: Cypress.Cypress, cy: $Cy, specBridgeComm let subject: string | undefined = undefined // Prevent serialization if this isn't the last command in the queue. - if (cy.queue.last().get('id') === id) { + if (cy.queue.last()?.get('id') === id) { subject = cy.state('subject') } From 944f08dd9269699591c354a80e7c54178616fc6b Mon Sep 17 00:00:00 2001 From: Matt Henkes Date: Wed, 2 Feb 2022 14:13:02 -0600 Subject: [PATCH 19/23] Apply suggestions from code review Co-authored-by: Bill Glesias --- .../integration/e2e/multi-domain/multi_domain_yield_spec.ts | 6 +++--- .../src/cy/multi-domain/failedSerializeSubjectProxy.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_yield_spec.ts b/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_yield_spec.ts index df0d5a24c6b3..f383275d4f98 100644 --- a/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_yield_spec.ts +++ b/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_yield_spec.ts @@ -24,7 +24,7 @@ describe('multi-domain', { experimentalSessionSupport: true, experimentalMultiDo }) it('yields the cy value even if a return is present', () => { - cy.switchToDomain('foobar.com', async () => { + cy.switchToDomain('foobar.com', () => { cy .get('[data-cy="dom-check"]') .invoke('text') @@ -64,11 +64,11 @@ describe('multi-domain', { experimentalSessionSupport: true, experimentalMultiDo }) it('yields asynchronously', () => { - cy.switchToDomain('foobar.com', async () => { + cy.switchToDomain('foobar.com', () => { return new Promise((resolve: (val: string) => any, reject) => { setTimeout(() => { resolve('From a secondary domain') - }, 1000) + }, 50) }) }).should('equal', 'From a secondary domain') }) diff --git a/packages/driver/src/cy/multi-domain/failedSerializeSubjectProxy.ts b/packages/driver/src/cy/multi-domain/failedSerializeSubjectProxy.ts index 8e15f61fe8e2..09d3547e3c3b 100644 --- a/packages/driver/src/cy/multi-domain/failedSerializeSubjectProxy.ts +++ b/packages/driver/src/cy/multi-domain/failedSerializeSubjectProxy.ts @@ -13,7 +13,7 @@ const failedToSerializeSubject = (type: string) => { target = () => {} } - // Symbol note: I don't think the target can be a symbol, but we can just use an object until the symbols is accessed, then provide a different error. + // Symbol note: I don't think the target can be a symbol, but we can just use an object until the symbol is accessed, then provide a different error. return new Proxy(target, { /** From 8ebc545c065dec97a8b656d759891b3595ac7013 Mon Sep 17 00:00:00 2001 From: mjhenkes Date: Wed, 2 Feb 2022 14:58:31 -0600 Subject: [PATCH 20/23] code review changes --- .../src/cy/multi-domain/commands_manager.ts | 8 +---- .../failedSerializeSubjectProxy.ts | 30 ++++++++++--------- packages/driver/src/cy/multi-domain/index.ts | 9 ++---- .../driver/src/multi-domain/communicator.ts | 4 +-- packages/driver/src/multi-domain/domain_fn.ts | 2 +- 5 files changed, 22 insertions(+), 31 deletions(-) diff --git a/packages/driver/src/cy/multi-domain/commands_manager.ts b/packages/driver/src/cy/multi-domain/commands_manager.ts index 9598aeab4b6a..d288c95e0daa 100644 --- a/packages/driver/src/cy/multi-domain/commands_manager.ts +++ b/packages/driver/src/cy/multi-domain/commands_manager.ts @@ -66,13 +66,7 @@ export class CommandsManager { delete this.commands[id] if (!err) { - if (failedToSerializeSubjectOfType) { - return command.deferred.resolve( - failedToSerializeSubject(failedToSerializeSubjectOfType), - ) - } - - return command.deferred.resolve(subject) + return command.deferred.resolve(failedToSerializeSubjectOfType ? failedToSerializeSubjectOfType : subject) } // If the command has failed, cast the error back to a proper Error object diff --git a/packages/driver/src/cy/multi-domain/failedSerializeSubjectProxy.ts b/packages/driver/src/cy/multi-domain/failedSerializeSubjectProxy.ts index 09d3547e3c3b..2586086265c0 100644 --- a/packages/driver/src/cy/multi-domain/failedSerializeSubjectProxy.ts +++ b/packages/driver/src/cy/multi-domain/failedSerializeSubjectProxy.ts @@ -1,5 +1,20 @@ import $errUtils from '../../cypress/error_utils' +// These properties are required to avoid failing prior to attempting to use the subject. +// If Symbol.toStringTag is passed through to the target we will not properly fail the 'cy.invoke' command. +const passThroughProps = [ + 'then', + Symbol.isConcatSpreadable, + 'jquery', + 'nodeType', + 'window', + 'document', + 'inspect', + 'isSinonProxy', + '_spreadArray', + 'selector', +] + /** * Create a proxy object to fail when accessed or called. * @param type The type of operand that failed to serialize @@ -34,20 +49,7 @@ const failedToSerializeSubject = (type: string) => { * @returns either an error or the result of the allowed get on the target. */ get (target, prop, receiver) { - // These properties are required to avoid failing prior to attempting to use the subject. - if ( - prop === 'then' - || prop === Symbol.isConcatSpreadable - || prop === 'jquery' - || prop === 'nodeType' - // || prop === Symbol.toStringTag // If this is passed through to the target we will not properly fail the 'cy.invoke' command. - || prop === 'window' - || prop === 'document' - || prop === 'inspect' - || prop === 'isSinonProxy' - || prop === '_spreadArray' - || prop === 'selector' - ) { + if (passThroughProps.includes(prop)) { return target[prop] } diff --git a/packages/driver/src/cy/multi-domain/index.ts b/packages/driver/src/cy/multi-domain/index.ts index 530c7df6a875..1016c59a2850 100644 --- a/packages/driver/src/cy/multi-domain/index.ts +++ b/packages/driver/src/cy/multi-domain/index.ts @@ -142,14 +142,9 @@ export function addCommands (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy, // is common if there are only assertions enqueued in the SD. if (!commandsManager.hasCommands && !done) { cleanup() - // This handles when a subject is returned synchronously - if (failedToSerializeSubjectOfType) { - return resolve( - failedToSerializeSubject(failedToSerializeSubjectOfType), - ) - } - resolve(subject) + // This handles when a subject is returned synchronously + resolve(failedToSerializeSubjectOfType ? failedToSerializeSubjectOfType : subject) } else { resolve() } diff --git a/packages/driver/src/multi-domain/communicator.ts b/packages/driver/src/multi-domain/communicator.ts index 78327aa7d974..f53989de8b31 100644 --- a/packages/driver/src/multi-domain/communicator.ts +++ b/packages/driver/src/multi-domain/communicator.ts @@ -12,10 +12,10 @@ const preprocessErrorForPostMessage = (value) => { const { isDom } = $dom if (_.isError(value)) { - const serializedError = _.mapValues(clone(value), preprocessErrorForPostMessage) + const serializableError = _.mapValues(clone(value), preprocessErrorForPostMessage) return { - ... serializedError, + ... serializableError, // Native Error types currently cannot be cloned in Firefox when using 'postMessage'. // Please see https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm for more details name: value.name, diff --git a/packages/driver/src/multi-domain/domain_fn.ts b/packages/driver/src/multi-domain/domain_fn.ts index c99eae6cd431..e1b378b51ce7 100644 --- a/packages/driver/src/multi-domain/domain_fn.ts +++ b/packages/driver/src/multi-domain/domain_fn.ts @@ -90,7 +90,7 @@ export const handleDomainFn = (cy: $Cy, specBridgeCommunicator: SpecBridgeDomain // see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function for more details const value = window.eval(fnWrapper)(data) - // If we detect a non promise value returned + // If we detect a non promise value with commands in queue, throw an error if (value && cy.queue.length > 0 && !value.then) { $errUtils.throwErrByPath('switchToDomain.callback_mixes_sync_and_async', { args: { value: $utils.stringify(value) }, From 0a041f65cf66ba6ceeb2373ca13ad486bda8a16e Mon Sep 17 00:00:00 2001 From: mjhenkes Date: Wed, 2 Feb 2022 15:19:38 -0600 Subject: [PATCH 21/23] Whoops --- packages/driver/src/cy/multi-domain/commands_manager.ts | 2 +- packages/driver/src/cy/multi-domain/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/driver/src/cy/multi-domain/commands_manager.ts b/packages/driver/src/cy/multi-domain/commands_manager.ts index d288c95e0daa..8639bf13ac52 100644 --- a/packages/driver/src/cy/multi-domain/commands_manager.ts +++ b/packages/driver/src/cy/multi-domain/commands_manager.ts @@ -66,7 +66,7 @@ export class CommandsManager { delete this.commands[id] if (!err) { - return command.deferred.resolve(failedToSerializeSubjectOfType ? failedToSerializeSubjectOfType : subject) + return command.deferred.resolve(failedToSerializeSubjectOfType ? failedToSerializeSubject(failedToSerializeSubjectOfType) : subject) } // If the command has failed, cast the error back to a proper Error object diff --git a/packages/driver/src/cy/multi-domain/index.ts b/packages/driver/src/cy/multi-domain/index.ts index 1016c59a2850..24f11a257ef6 100644 --- a/packages/driver/src/cy/multi-domain/index.ts +++ b/packages/driver/src/cy/multi-domain/index.ts @@ -144,7 +144,7 @@ export function addCommands (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy, cleanup() // This handles when a subject is returned synchronously - resolve(failedToSerializeSubjectOfType ? failedToSerializeSubjectOfType : subject) + resolve(failedToSerializeSubjectOfType ? failedToSerializeSubject(failedToSerializeSubjectOfType) : subject) } else { resolve() } From 78fcb01a693fa4b6612d575a39b86d1ce1e578b4 Mon Sep 17 00:00:00 2001 From: mjhenkes Date: Thu, 3 Feb 2022 08:14:30 -0600 Subject: [PATCH 22/23] whoops, renamed the wrong test file --- .../cypress/integration/e2e/multi-domain/multi_domain_spec.ts | 2 +- .../integration/e2e/multi-domain/multi_domain_yield_spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts b/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts index a6512291f01e..4b0dbcbd2e25 100644 --- a/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts +++ b/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_spec.ts @@ -1,7 +1,7 @@ import _ from 'lodash' // @ts-ignore / session support is needed for visiting about:blank between tests -describe('multi-domain yields', { experimentalSessionSupport: true, experimentalMultiDomain: true }, () => { +describe('multi-domain', { experimentalSessionSupport: true, experimentalMultiDomain: true }, () => { beforeEach(() => { cy.visit('/fixtures/multi-domain.html') cy.get('a[data-cy="multi-domain-secondary-link"]').click() diff --git a/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_yield_spec.ts b/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_yield_spec.ts index f383275d4f98..6bebcf4dd0b0 100644 --- a/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_yield_spec.ts +++ b/packages/driver/cypress/integration/e2e/multi-domain/multi_domain_yield_spec.ts @@ -1,7 +1,7 @@ const { assertLogLength } = require('../../../support/utils') // @ts-ignore / session support is needed for visiting about:blank between tests -describe('multi-domain', { experimentalSessionSupport: true, experimentalMultiDomain: true }, () => { +describe('multi-domain yields', { experimentalSessionSupport: true, experimentalMultiDomain: true }, () => { let logs: any = [] beforeEach(() => { From 9f17a9a6c364e2aa2f5319f44e35eee356debe01 Mon Sep 17 00:00:00 2001 From: Matt Henkes Date: Thu, 3 Feb 2022 09:48:55 -0600 Subject: [PATCH 23/23] Update packages/driver/src/cy/multi-domain/failedSerializeSubjectProxy.ts Co-authored-by: Matt Schile --- .../driver/src/cy/multi-domain/failedSerializeSubjectProxy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/driver/src/cy/multi-domain/failedSerializeSubjectProxy.ts b/packages/driver/src/cy/multi-domain/failedSerializeSubjectProxy.ts index 2586086265c0..1e6376da28dd 100644 --- a/packages/driver/src/cy/multi-domain/failedSerializeSubjectProxy.ts +++ b/packages/driver/src/cy/multi-domain/failedSerializeSubjectProxy.ts @@ -28,7 +28,7 @@ const failedToSerializeSubject = (type: string) => { target = () => {} } - // Symbol note: I don't think the target can be a symbol, but we can just use an object until the symbol is accessed, then provide a different error. + // Symbol note: The target can't be a symbol, but we can use an object until the symbol is accessed, then provide a different error. return new Proxy(target, { /**