Skip to content

Commit

Permalink
fix: only take snapshots if the AUT document is in state (#24519)
Browse files Browse the repository at this point in the history
* fix: only take snapshots if the AUT document is in state

* test: add tests for snapshot behavior

* Update packages/driver/src/cy/snapshots.ts

Co-authored-by: Chris Breiding <[email protected]>

* chore: add comment to explain fallback document when cloning snapshot body

* chore: add more context to xhr test name

* fix: update not.to.be.undefined to to exist in snapshots.cy.ts

* fix: attempt to patch test to prevent nullish failures

* fix: turn on interactive flag in spec bridge to get snapshots

Co-authored-by: Chris Breiding <[email protected]>
  • Loading branch information
AtofStryker and chrisbreiding authored Nov 7, 2022
1 parent 12f9e8a commit 3cdbeca
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 62 deletions.
171 changes: 110 additions & 61 deletions packages/driver/cypress/e2e/e2e/origin/snapshots.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,107 +2,156 @@
import '../../../support/utils'

describe('cy.origin - snapshots', { browser: '!webkit' }, () => {
const findLog = (logMap: Map<string, any>, 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<string, any>
})

it('takes snapshots from the secondary origin even after the primary AUT has been unloaded from state', () => {
const findLog = (logMap: Map<string, any>, 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<string, any> = 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<string, any>, 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<string, any>

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')
})
})
})
})
20 changes: 19 additions & 1 deletion packages/driver/src/cy/snapshots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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]))
Expand Down

4 comments on commit 3cdbeca

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 3cdbeca Nov 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the linux arm64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/11.0.0/linux-arm64/develop-3cdbeca59d4871453d044701f2f3100cb9fbc8af/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 3cdbeca Nov 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the linux x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/11.0.0/linux-x64/develop-3cdbeca59d4871453d044701f2f3100cb9fbc8af/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 3cdbeca Nov 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the darwin arm64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/11.0.0/darwin-arm64/develop-3cdbeca59d4871453d044701f2f3100cb9fbc8af/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 3cdbeca Nov 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the win32 x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/11.0.0/win32-x64/develop-3cdbeca59d4871453d044701f2f3100cb9fbc8af/cypress.tgz

Please sign in to comment.