Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: only take snapshots if the AUT document is in state #24519

Merged
merged 12 commits into from
Nov 7, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 109 additions & 63 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,153 @@
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)
Copy link
Member

Choose a reason for hiding this comment

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

What log is this associated with?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In this case ,we are just digging out the [data-cy="assertion-header"] get log to make sure the snapshot is sent back over and not omitted with our new logic changes

Copy link
Member

Choose a reason for hiding this comment

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

Is that associated with the origin command log or an xhr log?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

origin command log since the get is invoked inside the cy.origin

})
}
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
// @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()
const getLogFromSecondaryOrigin = findLog(logs, '[data-cy="assertion-header"]')?.get()

expect(xhrLogFromSecondaryOrigin).to.not.be.undefined
expect(getLogFromSecondaryOrigin).to.not.be.undefined
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
expect(getLogFromSecondaryOrigin).to.not.be.undefined
expect(getLogFromSecondaryOrigin).to.be.defined

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am not sure why but I cannot get this type to show up in a chai assertion. I don't think it is actually valid 🙁

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, doesn't seem to be a bundled chai assertion, but we add on to chai with various libs, so it's possible it ends up as an assertion somehow, since we do use to.be.defined in a few other tests. I think the "correct" assertion is .to.exist.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

updated to to.exist in 17d37f5


const snapshots = xhrLogFromSecondaryOrigin.snapshots.map((snapshot) => snapshot.body.get()[0])
const snapshots = getLogFromSecondaryOrigin.snapshots.map((snapshot) => snapshot.body.get()[0])

expect(snapshots.length).to.equal(2)
expect(snapshots.length).to.equal(1)

// 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!')
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.not.be.undefined

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', () => {
Copy link
Member

Choose a reason for hiding this comment

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

Are we able to unskip these tests? Maybe add some retries?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let me revisit this and see what the current state is

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So I can get the tests to pass, but from the flake report the flake is fairly rare, like 2%. I think by default we already retry twice in CI, so I doubt it will fix the problem immediately. @mjhenkes and I were able to reproduce this a few months back, and if I remember correctly there were collisions with the proxy logging on xhr request. I could be completely wrong though. It's been a while 😅

Copy link
Member

Choose a reason for hiding this comment

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

it's possible this gets resolved by removing server/route, but i'm not 100%. I vote we revisit the flake tests after 12.0.0

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.not.be.undefined

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 / origin mismatch, but instead the primary origin (existing behavior)', {
Copy link
Member

Choose a reason for hiding this comment

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

I read this 2x and am a little confused

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this is missing the word visited in the name. I can add it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

addressed in 1d43234

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.not.be.undefined

cy.get(`[data-cy="assertion-header"]`).should('exist')
cy.wait('@fooBarBaz')
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.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')
Copy link
Member

Choose a reason for hiding this comment

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

Is this failing because the alias does not exist?

Copy link
Member

Choose a reason for hiding this comment

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

NVM coming from beforeEach

})
})
})
})
5 changes: 5 additions & 0 deletions packages/driver/src/cy/snapshots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,11 @@ export const create = ($$: $Cy['$$'], state: StateFunc) => {

const createSnapshot = (name, $elToHighlight, preprocessedSnapshot) => {
Cypress.action('cy:snapshot', name)
// only take a snapshot if the AUT document is in state to prevent cypress-in-cypress like snapshots
// or if the snapshot has been taken in a spec bridge and has already been preprocessed
AtofStryker marked this conversation as resolved.
Show resolved Hide resolved
if (!preprocessedSnapshot && !state('document')) {
return null
}
Comment on lines +238 to +240
Copy link
Member

Choose a reason for hiding this comment

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

Does this fix just mean there are more missing screenshots? Can you share a few scenarios this is currently broken? I'd like to test the before & after locally. Does the comment on line 249 no long valid? This is where we are falling back to window.document....would it be more appropriate to query for the AUT iframe?

Copy link
Member

@emilyrohrbough emilyrohrbough Nov 4, 2022

Choose a reason for hiding this comment

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

I am also wondering if we should update some of our origin tests to verify there are snapshots / no snapshots for various scenarios.

I didn't realize the updated tests were specific to origin tests for snapshot. Will be exicted to merge these someday 😄

Copy link
Contributor

Choose a reason for hiding this comment

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

Does the comment on line 249 no long valid? This is where we are falling back to window.document

That's a good question. Seems like these lines should be updated/removed.

would it be more appropriate to query for the AUT iframe?

I think if there's no state('document'), it won't be possible because that means we don't have synchronous access to the AUT.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This actually brings up a good point, but I think it needs to stay. Main reason is logs streaming in from the secondary need to be cloned off a document, which in this case the primary document state is unloaded. But the backup isn't the AUT document in this case, rather the actual top document, which is OK since we only using the window to clone the node. If anything, we should probably use window.document here instead of state("document")

I'll play around with this to make sure it works as expect and will add a comment to explain this logic

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Does this fix just mean there are more missing screenshots? Can you share a few scenarios this is currently broken? I'd like to test the before & after locally.

@emilyrohrbough you can run the snapshot tests from the driver locally, but the impact isn't exactly super noticeable there. Do you have cypress-origin-providers set up at all?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added comments in 228cef0. I kept the current logic of using loaded document in state first before the specbridge/root document because of how many edge cases exist in snapshot code that could potentially break, but not be caught by this change. Here be 🐉 s.

Copy link
Member

Choose a reason for hiding this comment

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

I can easily setup cypress-origin-providers but even if I run the driver tests, what would I be looking for to verify this was acutally fixed? xhr requests?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If you run the snapshots.cy.ts inside the origin driver tests, hover over the (page load) or (new url) logs to see no snapshot taken, where as before it would look something like this:
Screen Shot 2022-11-04 at 1 07 54 PM


try {
const {
Expand Down
4 changes: 2 additions & 2 deletions tooling/v8-snapshot/cache/dev-darwin/snapshot-meta.cache.json
Original file line number Diff line number Diff line change
Expand Up @@ -1101,7 +1101,6 @@
"./node_modules/ansi_up/ansi_up.js",
"./node_modules/any-base/index.js",
"./node_modules/any-base/src/converter.js",
"./node_modules/anymatch/index.js",
"./node_modules/archiver-utils/file.js",
"./node_modules/archiver-utils/index.js",
"./node_modules/archiver-utils/node_modules/glob/common.js",
Expand Down Expand Up @@ -3285,6 +3284,7 @@
"./node_modules/yn/lenient.js",
"./packages/data-context/node_modules/@babel/code-frame/lib/index.js",
"./packages/data-context/node_modules/@babel/parser/lib/index.js",
"./packages/data-context/node_modules/anymatch/index.js",
"./packages/data-context/node_modules/cross-spawn/index.js",
"./packages/data-context/node_modules/cross-spawn/lib/enoent.js",
"./packages/data-context/node_modules/cross-spawn/lib/parse.js",
Expand Down Expand Up @@ -3528,5 +3528,5 @@
"./tooling/v8-snapshot/cache/dev-darwin/snapshot-entry.js"
],
"deferredHashFile": "yarn.lock",
"deferredHash": "66156a5424fbd0d70c750ef2b16c22b0084a30220c67b632cff9212a7de7a226"
"deferredHash": "9fe6e763921f20d9fa851fee03e5b5e7c334c2bd85a88bcdf0ed49cde252e095"
}