diff --git a/packages/driver/cypress/e2e/e2e/origin/snapshots.cy.ts b/packages/driver/cypress/e2e/e2e/origin/snapshots.cy.ts index 49da844ba0f6..1a7aa7070f32 100644 --- a/packages/driver/cypress/e2e/e2e/origin/snapshots.cy.ts +++ b/packages/driver/cypress/e2e/e2e/origin/snapshots.cy.ts @@ -2,107 +2,156 @@ import '../../../support/utils' describe('cy.origin - snapshots', { browser: '!webkit' }, () => { - const findLog = (logMap: Map, displayName: string, url: string) => { - return Array.from(logMap.values()).find((log: any) => { - const props = log.get() + it('does not create snapshots after the document has unloaded and the AUT has navigated cross-origin', () => { + cy.visit('/fixtures/generic.html') + cy.visit('http://www.foobar.com:3500/fixtures/generic.html') + cy.then(() => { + const snapshot = cy.createSnapshot() - return props.displayName === displayName && (props?.consoleProps?.URL === url || props?.consoleProps()?.URL === url) + expect(snapshot).to.be.null }) - } - let logs: Map + }) + + it('takes snapshots from the secondary origin even after the primary AUT has been unloaded from state', () => { + const findLog = (logMap: Map, selector) => { + return Array.from(logMap.values()).find((log: any) => { + const props = log.get() - beforeEach(() => { - logs = new Map() + return (props?.consoleProps?.Selector === selector) + }) + } + let logs: Map = new Map() cy.on('log:changed', (attrs, log) => { logs.set(attrs.id, log) }) - cy.fixture('foo.bar.baz.json').then((fooBarBaz) => { - cy.intercept('GET', '/foo.bar.baz.json', { body: fooBarBaz }).as('fooBarBaz') - }) - cy.visit('/fixtures/primary-origin.html') cy.get('a[data-cy="xhr-fetch-requests-onload"]').click() - }) - // TODO: the xhr event is showing up twice in the log, which is wrong and causing flake. skipping until: https://github.com/cypress-io/cypress/issues/23840 is addressed. - it.skip('verifies XHR requests made while a secondary origin is active eventually update with snapshots of the secondary origin', () => { cy.origin('http://www.foobar.com:3500', () => { - // need to set isInteractive in the spec bridge in order to take xhr snapshots in run mode, similar to how isInteractive is set within support/defaults.js + // need to set isInteractive in the spec bridge in order to take snapshots in run mode, similar to how isInteractive is set within support/defaults.js // @ts-ignore Cypress.config('isInteractive', true) - cy.visit('http://www.foobar.com:3500/fixtures/xhr-fetch-requests.html') - cy.get(`[data-cy="assertion-header"]`).should('exist') - cy.wait('@fooBarBaz') + cy.get(`[data-cy="assertion-header"]`) }) cy.shouldWithTimeout(() => { - const xhrLogFromSecondaryOrigin = findLog(logs, 'xhr', 'http://localhost:3500/foo.bar.baz.json')?.get() - - expect(xhrLogFromSecondaryOrigin).to.not.be.undefined + const getLogFromSecondaryOrigin = findLog(logs, '[data-cy="assertion-header"]')?.get() - const snapshots = xhrLogFromSecondaryOrigin.snapshots.map((snapshot) => snapshot.body.get()[0]) + expect(getLogFromSecondaryOrigin).to.exist - expect(snapshots.length).to.equal(2) + const snapshots = getLogFromSecondaryOrigin?.snapshots?.map((snapshot) => snapshot?.body.get()[0]) || [] - // TODO: Since we have two events, one of them does not have a request snapshot + expect(snapshots.length).to.equal(1) - expect(snapshots[1].querySelector(`[data-cy="assertion-header"]`)).to.have.property('innerText').that.equals('Making XHR and Fetch Requests behind the scenes if fireOnload is true!') + expect(snapshots[0].querySelector(`[data-cy="assertion-header"]`)).to.have.property('innerText').that.equals('Making XHR and Fetch Requests behind the scenes if fireOnload is true!') }) }) - // TODO: fix flaky test https://github.com/cypress-io/cypress/issues/23437 - it.skip('verifies fetch requests made while a secondary origin is active eventually update with snapshots of the secondary origin', () => { - cy.origin('http://www.foobar.com:3500', () => { - // need to set isInteractive in the spec bridge in order to take xhr snapshots in run mode, similar to how isInteractive is set within support/defaults.js - // @ts-ignore - Cypress.config('isInteractive', true) - cy.visit('http://www.foobar.com:3500/fixtures/xhr-fetch-requests.html') - cy.get(`[data-cy="assertion-header"]`).should('exist') - cy.wait('@fooBarBaz') - }) + describe('e2e log verification', () => { + const findLog = (logMap: Map, displayName: string, url: string) => { + return Array.from(logMap.values()).find((log: any) => { + const props = log.get() - cy.shouldWithTimeout(() => { - const xhrLogFromSecondaryOrigin = findLog(logs, 'fetch', 'http://localhost:3500/foo.bar.baz.json')?.get() + return props.displayName === displayName && (props?.consoleProps?.URL === url || props?.consoleProps()?.URL === url) + }) + } + let logs: Map - expect(xhrLogFromSecondaryOrigin).to.not.be.undefined + beforeEach(() => { + logs = new Map() - const snapshots = xhrLogFromSecondaryOrigin.snapshots.map((snapshot) => snapshot.body.get()[0]) + cy.on('log:changed', (attrs, log) => { + logs.set(attrs.id, log) + }) - snapshots.forEach((snapshot) => { - expect(snapshot.querySelector(`[data-cy="assertion-header"]`)).to.have.property('innerText').that.equals('Making XHR and Fetch Requests behind the scenes if fireOnload is true!') + cy.fixture('foo.bar.baz.json').then((fooBarBaz) => { + cy.intercept('GET', '/foo.bar.baz.json', { body: fooBarBaz }).as('fooBarBaz') }) + + cy.visit('/fixtures/primary-origin.html') + cy.get('a[data-cy="xhr-fetch-requests-onload"]').click() }) - }) - it('Does not take snapshots of XHR/fetch requests from secondary origin if the wrong origin is / origin mismatch, but instead the primary origin (existing behavior)', { - defaultCommandTimeout: 50, - }, - (done) => { - cy.on('fail', () => { - const xhrLogFromSecondaryOrigin = findLog(logs, 'fetch', 'http://localhost:3500/foo.bar.baz.json')?.get() + // TODO: the xhr event is showing up twice in the log, which is wrong and causing flake. skipping until: https://github.com/cypress-io/cypress/issues/23840 is addressed. + it.skip('verifies XHR requests made while a secondary origin is active eventually update with snapshots of the secondary origin', () => { + cy.origin('http://www.foobar.com:3500', () => { + // need to set isInteractive in the spec bridge in order to take xhr snapshots in run mode, similar to how isInteractive is set within support/defaults.js + // @ts-ignore + Cypress.config('isInteractive', true) + cy.visit('http://www.foobar.com:3500/fixtures/xhr-fetch-requests.html') + cy.get(`[data-cy="assertion-header"]`).should('exist') + cy.wait('@fooBarBaz') + }) + + cy.shouldWithTimeout(() => { + const xhrLogFromSecondaryOrigin = findLog(logs, 'xhr', 'http://localhost:3500/foo.bar.baz.json')?.get() - expect(xhrLogFromSecondaryOrigin).to.not.be.undefined + expect(xhrLogFromSecondaryOrigin).to.exist - const snapshots = xhrLogFromSecondaryOrigin.snapshots.map((snapshot) => snapshot.body.get()[0]) + const snapshots = xhrLogFromSecondaryOrigin.snapshots.map((snapshot) => snapshot.body.get()[0]) - snapshots.forEach((snapshot) => { - expect(snapshot.querySelector(`[data-cy="assertion-header"]`)).to.be.null + expect(snapshots.length).to.equal(2) + + // TODO: Since we have two events, one of them does not have a request snapshot + + expect(snapshots[1].querySelector(`[data-cy="assertion-header"]`)).to.have.property('innerText').that.equals('Making XHR and Fetch Requests behind the scenes if fireOnload is true!') + }) + }) + + // TODO: fix flaky test https://github.com/cypress-io/cypress/issues/23437 + it.skip('verifies fetch requests made while a secondary origin is active eventually update with snapshots of the secondary origin', () => { + cy.origin('http://www.foobar.com:3500', () => { + // need to set isInteractive in the spec bridge in order to take xhr snapshots in run mode, similar to how isInteractive is set within support/defaults.js + // @ts-ignore + Cypress.config('isInteractive', true) + cy.visit('http://www.foobar.com:3500/fixtures/xhr-fetch-requests.html') + cy.get(`[data-cy="assertion-header"]`).should('exist') + cy.wait('@fooBarBaz') }) - done() + cy.shouldWithTimeout(() => { + const xhrLogFromSecondaryOrigin = findLog(logs, 'fetch', 'http://localhost:3500/foo.bar.baz.json')?.get() + + expect(xhrLogFromSecondaryOrigin).to.exist + + const snapshots = xhrLogFromSecondaryOrigin.snapshots.map((snapshot) => snapshot.body.get()[0]) + + snapshots.forEach((snapshot) => { + expect(snapshot.querySelector(`[data-cy="assertion-header"]`)).to.have.property('innerText').that.equals('Making XHR and Fetch Requests behind the scenes if fireOnload is true!') + }) + }) }) - cy.visit('http://www.foobar.com:3500/fixtures/xhr-fetch-requests.html') + it('Does not take snapshots of XHR/fetch requests from secondary origin if the wrong origin is visited / origin mismatch, but instead the primary origin (existing behavior)', { + defaultCommandTimeout: 50, + }, + (done) => { + cy.on('fail', () => { + const xhrLogFromSecondaryOrigin = findLog(logs, 'fetch', 'http://localhost:3500/foo.bar.baz.json')?.get() - cy.origin('http://www.barbaz.com:3500', () => { - // need to set isInteractive in the spec bridge in order to take xhr snapshots in run mode, similar to how isInteractive is set within support/defaults.js - // @ts-ignore - Cypress.config('isInteractive', true) + expect(xhrLogFromSecondaryOrigin).to.exist + + const snapshots = xhrLogFromSecondaryOrigin.snapshots.map((snapshot) => snapshot.body.get()[0]) + + snapshots.forEach((snapshot) => { + expect(snapshot.querySelector(`[data-cy="assertion-header"]`)).to.be.null + }) + + done() + }) - cy.get(`[data-cy="assertion-header"]`).should('exist') - cy.wait('@fooBarBaz') + cy.visit('http://www.foobar.com:3500/fixtures/xhr-fetch-requests.html') + + cy.origin('http://www.barbaz.com:3500', () => { + // need to set isInteractive in the spec bridge in order to take xhr snapshots in run mode, similar to how isInteractive is set within support/defaults.js + // @ts-ignore + Cypress.config('isInteractive', true) + + cy.get(`[data-cy="assertion-header"]`).should('exist') + cy.wait('@fooBarBaz') + }) }) }) }) diff --git a/packages/driver/src/cy/snapshots.ts b/packages/driver/src/cy/snapshots.ts index 707a83fd0b39..b39ec8a5c25b 100644 --- a/packages/driver/src/cy/snapshots.ts +++ b/packages/driver/src/cy/snapshots.ts @@ -229,6 +229,15 @@ export const create = ($$: $Cy['$$'], state: StateFunc) => { const createSnapshot = (name, $elToHighlight, preprocessedSnapshot) => { Cypress.action('cy:snapshot', name) + // when using cy.origin() and in a transitionary state, state('document') + // can be undefined, resulting in a bizarre snapshot of the entire Cypress + // UI. better not to take the snapshot in that case. + // https://github.com/cypress-io/cypress/issues/24506 + // also, do not take the snapshot here if it has already been taken and + // preprocessed in a spec bridge. + if (!preprocessedSnapshot && !state('document')) { + return null + } try { const { @@ -241,7 +250,16 @@ export const create = ($$: $Cy['$$'], state: StateFunc) => { const body = { get: () => { if (!attachedBody) { - // If we don't have an AUT document, use the spec bridge document + // logs streaming in from the secondary need to be cloned off a document, + // which means state("document") will be undefined in the primary + // if a cy.origin block is active + + // this could also be possible, but unlikely, if the spec bridge is taking + // snapshots before the document has loaded into state, as could be the case with logs + // generated from before:load event handlers in a spec bridge + + // in any of these cases, fall back to the root document as we only + // need the document to clone the node. const doc = state('document') || window.document attachedBody = $$(doc.adoptNode($body[0]))