Skip to content

Commit a1101e6

Browse files
AtofStrykerryanthemanuelmjhenkes
authored
feat: support snapshots and console props within multi-domain (#20949)
* Empty commit to get a new percy nonce * Implement snapshots and consoleprops within multi origin further progress with getters cleaned up log/snapshot serialization attempt to pass and hydrate value state of serialized dom elements temp commit traversal dom by some stretch of a miracle this is working... still somehow working after massive performance issues with full tree serialization, fix here is to just attach values to inputs for reifying on primary now we are cookin test WIP tests WIP working multi-domain actions snapshots tests added more tests to verify snapshots add tests and refactor certain tests to make simpler added misc snapshot tests add navigation snapshot placeholder add network request snapshot tests add shadow querying snapshot tests update test names added snapshot querying spec added screenshot snapshot test add spies,clocks, and stubs tests implement snapshot tests for traversal commands rename local storeage snapshot tests to fit convention add viewport snapshot tests rename snapshot traversal to fit naming convention add snapshot waiting tests added window snapshot tests implement navigation snapshot tests now that sinon proxy issues internal to log are now fixed refactor multi-domain snapshot tests to leverage utility method over redefining in each spec * fix lint types issues on serializationKeys * rename switchToDomain to origin (might help with failing tests... ya know?) * rename snapshot files to fit origin paradigm and fix misname on primaryDomainCommunicator * fix .tick() snapshot/consoleProps test (figure out the deal with consoleProps sometimes being a function) * rename multiDomainCommunicator to origin primaryDomainCommunicator * don't invoke functions with arguments (we need to be more explicit about the functions we are invoking * opt for my explicit serialization behavior with functions, never attempt to serialize bluebird promises * move serialization to folder and change name to index * refactor log serialization to own file, clean up code and add comments to what is going on in this 'here be dragons' code * make sure to serialize functions for snapshots * fix pause snapshot test for multi origin * refactor postprocess snapshot into own method to handle in final state snapshot processing for cross origin * update snapshot comments to be more accurate * fix renamings within tests * fix path in log.ts serialization * revert about:blank changes in aut-iframe which was breaking session * move all log/snapshot serialization magic invokations into the communicator * update typos and fix namings of preprocess and reify * further name changes * fix snapshot generator to always reify snapshot (<body>) over attempting to match in the DOM * unskip test that was fixed with explicit function serialization for logs * fix flaky screenshot test that was screensize dependent * rename a few items in the log serialization file * clean up snapshot style reification to be more straightforward and remove redundancies * refactor snapshots code to be more readable * update reifyDomElement docs to actually explain what hte method does * fix typos within the log serialization file pertaining to comments * use Cypress._ over lodash import to reduce spec bundle size * remove snapshots test folder and migrate tests into commands directory with #consoleProps context blocks for each * change removeSrcAttributeFromAUTIframe name to removeSrcAttribute as it is implied on the AUT * update log consoleProps comment to better reflect cross origin nature * remove skipped consoleProps tests that do not have a command log to test against * add createSnapshot to internal types (might need more specifics on this) * refactor multi-domain consoleProp tests to use shouldWithTimeout custom command to avoid setTimeouts on command queue event to make test implementation cleaner * simplify DOM hydration for input based elements * update preprocessedHTMLElement type * clean up some documentation and remove TS ignores. added getStyles to internal-types. * add comment to aut-iframe on src attr removal for posterity * reverse snapshot ternary for readability * add shouldWithTimeout into spec-types and refactor out of internal-types * add getAll type to cypress spec-types * compare originPolicy of top and AUT instead of just origin to make snapshots work in subdomains * add comment to _storeOriginalState for future developers and to add clarity * add some basic log serialization tests that show full pre/reification of log, as well as state hydration for innerHTML. break out object/array methods from log like serialization into own methods * update variables to metasyntactic * add renderProps assertion for cy.request * apply suggestions from code review to clean up log serializer * make snapshot serialization more generic and typesafe * work around firefox 93 issues by unsetting the document in cy state as the document is in a cross origin context in the primary, which means accessing any elements will not work * clean up code and implement suggestions in code review * remove crossOriginLog in favor of nullish coalescing if visible on the log is not set * if get is null, return null for whole snapshot Co-authored-by: Ryan Manuel <[email protected]> Co-authored-by: Matt Henkes <[email protected]>
1 parent 885541e commit a1101e6

37 files changed

+3621
-81
lines changed

packages/driver/cypress/fixtures/dom.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -437,8 +437,8 @@
437437
</select>
438438

439439
<select name="foods">
440-
<option value="Japanese">Ramen</option>
441-
<option value="Ramen">Japanese</option>
440+
<option value="Ramen">Ramen</option>
441+
<option value="Japanese">Japanese</option>
442442
</select>
443443

444444
<select name="names">

packages/driver/cypress/integration/e2e/multi-domain/commands/multi_domain_actions.spec.ts

Lines changed: 621 additions & 0 deletions
Large diffs are not rendered by default.

packages/driver/cypress/integration/e2e/multi-domain/commands/multi_domain_aliasing.spec.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { findCrossOriginLogs } from '../../../../support/utils'
2+
13
context('cy.origin aliasing', () => {
24
beforeEach(() => {
35
cy.visit('/fixtures/multi-domain.html')
@@ -10,4 +12,39 @@ context('cy.origin aliasing', () => {
1012
cy.get('@checkbox').click().should('be.checked')
1113
})
1214
})
15+
16+
context('#consoleProps', () => {
17+
let logs: Map<string, any>
18+
19+
beforeEach(() => {
20+
logs = new Map()
21+
22+
cy.on('log:changed', (attrs, log) => {
23+
logs.set(attrs.id, log)
24+
})
25+
})
26+
27+
it('.as()', () => {
28+
cy.origin('http://foobar.com:3500', () => {
29+
cy.get('#button').as('buttonAlias')
30+
})
31+
32+
cy.shouldWithTimeout(() => {
33+
const { alias, aliasType, consoleProps, $el } = findCrossOriginLogs('get', logs, 'foobar.com')
34+
35+
// make sure $el is in fact a jquery instance to keep the logs happy
36+
expect($el.jquery).to.be.ok
37+
38+
expect(alias).to.equal('buttonAlias')
39+
expect(aliasType).to.equal('dom')
40+
expect(consoleProps.Command).to.equal('get')
41+
expect(consoleProps.Elements).to.equal(1)
42+
expect(consoleProps.Selector).to.equal('#button')
43+
44+
// The Yielded value here SHOULD be correct as it will be reified from its props as it should not be found in the current DOM state
45+
expect(consoleProps.Yielded.tagName).to.equal('BUTTON')
46+
expect(consoleProps.Yielded.getAttribute('id')).to.equal('button')
47+
})
48+
})
49+
})
1350
})

packages/driver/cypress/integration/e2e/multi-domain/commands/multi_domain_assertions.spec.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { findCrossOriginLogs } from '../../../../support/utils'
2+
13
context('cy.origin assertions', () => {
24
beforeEach(() => {
35
cy.visit('/fixtures/multi-domain.html')
@@ -10,4 +12,45 @@ context('cy.origin assertions', () => {
1012
.should('not.be.checked').and('not.be.disabled')
1113
})
1214
})
15+
16+
context('#consoleProps', () => {
17+
let logs: Map<string, any>
18+
19+
beforeEach(() => {
20+
logs = new Map()
21+
22+
cy.on('log:changed', (attrs, log) => {
23+
logs.set(attrs.id, log)
24+
})
25+
})
26+
27+
it('.should() and .and()', () => {
28+
cy.origin('http://foobar.com:3500', () => {
29+
cy.get(':checkbox[name="colors"][value="blue"]')
30+
.should('not.be.checked').and('not.be.disabled')
31+
})
32+
33+
cy.shouldWithTimeout(() => {
34+
// in the case of some firefox browsers, the document state is left in a cross origin context when running these assertions
35+
// set to context to undefined to run the assertions
36+
if (Cypress.isBrowser('firefox')) {
37+
cy.state('document', undefined)
38+
}
39+
40+
const assertionLogs = findCrossOriginLogs('assert', logs, 'foobar.com')
41+
42+
expect(assertionLogs[0].consoleProps.Message).to.equal('expected <input> not to be checked')
43+
expect(assertionLogs[1].consoleProps.Message).to.equal('expected <input> not to be disabled')
44+
45+
assertionLogs.forEach(({ $el, consoleProps }) => {
46+
expect($el.jquery).to.be.ok
47+
48+
expect(consoleProps.Command).to.equal('assert')
49+
expect(consoleProps.subject[0]).to.have.property('tagName').that.equals('INPUT')
50+
expect(consoleProps.subject[0]).to.have.property('value').that.equals('blue')
51+
expect(consoleProps.subject[0].getAttribute('name')).to.equal('colors')
52+
})
53+
})
54+
})
55+
})
1356
})

packages/driver/cypress/integration/e2e/multi-domain/commands/multi_domain_connectors.spec.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { findCrossOriginLogs } from '../../../../support/utils'
2+
13
context('cy.origin connectors', () => {
24
beforeEach(() => {
35
cy.visit('/fixtures/multi-domain.html')
@@ -43,4 +45,75 @@ context('cy.origin connectors', () => {
4345
})
4446
})
4547
})
48+
49+
context('#consoleProps', () => {
50+
let logs: Map<string, any>
51+
52+
beforeEach(() => {
53+
logs = new Map()
54+
55+
cy.on('log:changed', (attrs, log) => {
56+
logs.set(attrs.id, log)
57+
})
58+
})
59+
60+
it('.its()', () => {
61+
cy.origin('http://foobar.com:3500', () => {
62+
cy.get('#by-id>input').its('length')
63+
})
64+
65+
cy.shouldWithTimeout(() => {
66+
// in the case of some firefox browsers, the document state is left in a cross origin context when running these assertions
67+
// set to context to undefined to run the assertions
68+
if (Cypress.isBrowser('firefox')) {
69+
cy.state('document', undefined)
70+
}
71+
72+
const { consoleProps, $el } = findCrossOriginLogs('its', logs, 'foobar.com')
73+
74+
expect($el.jquery).to.be.ok
75+
76+
expect(consoleProps.Command).to.equal('its')
77+
expect(consoleProps.Property).to.equal('.length')
78+
expect(consoleProps.Yielded).to.equal(3)
79+
80+
expect(consoleProps.Subject.length).to.equal(3)
81+
82+
// make sure subject elements are indexed in the correct order
83+
expect(consoleProps.Subject[0]).to.have.property('tagName').that.equals('INPUT')
84+
expect(consoleProps.Subject[0]).to.have.property('id').that.equals('input')
85+
86+
expect(consoleProps.Subject[1]).to.have.property('tagName').that.equals('INPUT')
87+
expect(consoleProps.Subject[1]).to.have.property('id').that.equals('name')
88+
89+
expect(consoleProps.Subject[2]).to.have.property('tagName').that.equals('INPUT')
90+
expect(consoleProps.Subject[2]).to.have.property('id').that.equals('age')
91+
})
92+
})
93+
94+
it('.invoke()', () => {
95+
cy.origin('http://foobar.com:3500', () => {
96+
cy.get('#button').invoke('text')
97+
})
98+
99+
cy.shouldWithTimeout(() => {
100+
// in the case of some firefox browsers, the document state is left in a cross origin context when running these assertions
101+
// set to context to undefined to run the assertions
102+
if (Cypress.isBrowser('firefox')) {
103+
cy.state('document', undefined)
104+
}
105+
106+
const { consoleProps, $el } = findCrossOriginLogs('invoke', logs, 'foobar.com')
107+
108+
expect($el.jquery).to.be.ok
109+
110+
expect(consoleProps.Command).to.equal('invoke')
111+
expect(consoleProps.Function).to.equal('.text()')
112+
expect(consoleProps.Yielded).to.equal('button')
113+
114+
expect(consoleProps.Subject).to.have.property('tagName').that.equals('BUTTON')
115+
expect(consoleProps.Subject).to.have.property('id').that.equals('button')
116+
})
117+
})
118+
})
46119
})

packages/driver/cypress/integration/e2e/multi-domain/commands/multi_domain_cookies.spec.ts

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { findCrossOriginLogs } from '../../../../support/utils'
2+
13
context('cy.origin cookies', () => {
24
beforeEach(() => {
35
cy.visit('/fixtures/multi-domain.html')
@@ -34,4 +36,142 @@ context('cy.origin cookies', () => {
3436
cy.getCookies().should('be.empty')
3537
})
3638
})
39+
40+
context('#consoleProps', () => {
41+
const { _ } = Cypress
42+
let logs: Map<string, any>
43+
44+
beforeEach(() => {
45+
logs = new Map()
46+
47+
cy.on('log:changed', (attrs, log) => {
48+
logs.set(attrs.id, log)
49+
})
50+
})
51+
52+
it('.getCookie()', () => {
53+
cy.origin('http://foobar.com:3500', () => {
54+
cy.getCookies().should('be.empty')
55+
cy.setCookie('foo', 'bar')
56+
cy.getCookie('foo')
57+
})
58+
59+
cy.shouldWithTimeout(() => {
60+
const { consoleProps } = findCrossOriginLogs('getCookie', logs, 'foobar.com')
61+
62+
expect(consoleProps.Command).to.equal('getCookie')
63+
expect(consoleProps.Yielded).to.have.property('domain').that.includes('foobar.com')
64+
expect(consoleProps.Yielded).to.have.property('expiry').that.is.a('number')
65+
expect(consoleProps.Yielded).to.have.property('httpOnly').that.equals(false)
66+
expect(consoleProps.Yielded).to.have.property('secure').that.equals(false)
67+
expect(consoleProps.Yielded).to.have.property('name').that.equals('foo')
68+
expect(consoleProps.Yielded).to.have.property('value').that.equals('bar')
69+
expect(consoleProps.Yielded).to.have.property('path').that.is.a('string')
70+
})
71+
})
72+
73+
it('.getCookies()', () => {
74+
cy.origin('http://foobar.com:3500', () => {
75+
cy.getCookies().should('be.empty')
76+
77+
cy.setCookie('foo', 'bar')
78+
cy.getCookies()
79+
})
80+
81+
cy.shouldWithTimeout(() => {
82+
// get the last 'getCookies' command, which is the one we care about for this test
83+
const allGetCookieLogs = findCrossOriginLogs('getCookies', logs, 'foobar.com')
84+
85+
const { consoleProps } = allGetCookieLogs.pop() as any
86+
87+
expect(consoleProps.Command).to.equal('getCookies')
88+
expect(consoleProps['Num Cookies']).to.equal(1)
89+
90+
// can't exactly assert on length() as this is a array proxy object
91+
expect(consoleProps.Yielded.length).to.equal(1)
92+
expect(consoleProps.Yielded[0]).to.have.property('expiry').that.is.a('number')
93+
expect(consoleProps.Yielded[0]).to.have.property('httpOnly').that.equals(false)
94+
expect(consoleProps.Yielded[0]).to.have.property('secure').that.equals(false)
95+
expect(consoleProps.Yielded[0]).to.have.property('name').that.equals('foo')
96+
expect(consoleProps.Yielded[0]).to.have.property('value').that.equals('bar')
97+
expect(consoleProps.Yielded[0]).to.have.property('path').that.is.a('string')
98+
})
99+
})
100+
101+
it('.setCookie()', () => {
102+
cy.origin('http://foobar.com:3500', () => {
103+
cy.getCookies().should('be.empty')
104+
105+
cy.setCookie('foo', 'bar')
106+
})
107+
108+
cy.shouldWithTimeout(() => {
109+
const { consoleProps } = findCrossOriginLogs('setCookie', logs, 'foobar.com')
110+
111+
expect(consoleProps.Command).to.equal('setCookie')
112+
expect(consoleProps.Yielded).to.have.property('domain').that.includes('foobar.com')
113+
expect(consoleProps.Yielded).to.have.property('expiry').that.is.a('number')
114+
expect(consoleProps.Yielded).to.have.property('httpOnly').that.equals(false)
115+
expect(consoleProps.Yielded).to.have.property('secure').that.equals(false)
116+
expect(consoleProps.Yielded).to.have.property('name').that.equals('foo')
117+
expect(consoleProps.Yielded).to.have.property('value').that.equals('bar')
118+
expect(consoleProps.Yielded).to.have.property('path').that.is.a('string')
119+
})
120+
})
121+
122+
it('.clearCookie()', () => {
123+
cy.origin('http://foobar.com:3500', () => {
124+
cy.setCookie('foo', 'bar')
125+
cy.getCookie('foo').should('not.be.null')
126+
cy.clearCookie('foo')
127+
})
128+
129+
cy.shouldWithTimeout(() => {
130+
const { consoleProps } = findCrossOriginLogs('clearCookie', logs, 'foobar.com')
131+
132+
expect(consoleProps.Command).to.equal('clearCookie')
133+
expect(consoleProps.Yielded).to.equal('null')
134+
expect(consoleProps['Cleared Cookie']).to.have.property('domain').that.includes('foobar.com')
135+
expect(consoleProps['Cleared Cookie']).to.have.property('expiry').that.is.a('number')
136+
expect(consoleProps['Cleared Cookie']).to.have.property('httpOnly').that.equals(false)
137+
expect(consoleProps['Cleared Cookie']).to.have.property('secure').that.equals(false)
138+
expect(consoleProps['Cleared Cookie']).to.have.property('name').that.equals('foo')
139+
expect(consoleProps['Cleared Cookie']).to.have.property('value').that.equals('bar')
140+
expect(consoleProps['Cleared Cookie']).to.have.property('path').that.is.a('string')
141+
})
142+
})
143+
144+
it('.clearCookies()', () => {
145+
cy.origin('http://foobar.com:3500', () => {
146+
cy.setCookie('foo', 'bar')
147+
cy.setCookie('faz', 'baz')
148+
149+
cy.getCookies().should('have.length', 2)
150+
cy.clearCookies()
151+
})
152+
153+
cy.shouldWithTimeout(() => {
154+
const { consoleProps } = findCrossOriginLogs('clearCookies', logs, 'foobar.com')
155+
156+
expect(consoleProps.Command).to.equal('clearCookies')
157+
expect(consoleProps['Num Cookies']).to.equal(2)
158+
159+
expect(consoleProps.Yielded).to.equal('null')
160+
161+
expect(consoleProps['Cleared Cookies'].length).to.equal(2)
162+
163+
expect(consoleProps['Cleared Cookies'][0]).to.have.property('name').that.equals('foo')
164+
expect(consoleProps['Cleared Cookies'][0]).to.have.property('value').that.equals('bar')
165+
166+
expect(consoleProps['Cleared Cookies'][1]).to.have.property('name').that.equals('faz')
167+
expect(consoleProps['Cleared Cookies'][1]).to.have.property('value').that.equals('baz')
168+
169+
_.forEach(consoleProps['Cleared Cookies'], (clearedCookie) => {
170+
expect(clearedCookie).to.have.property('httpOnly').that.equals(false)
171+
expect(clearedCookie).to.have.property('secure').that.equals(false)
172+
expect(clearedCookie).to.have.property('path').that.is.a('string')
173+
})
174+
})
175+
})
176+
})
37177
})

packages/driver/cypress/integration/e2e/multi-domain/commands/multi_domain_files.spec.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { findCrossOriginLogs } from '../../../../support/utils'
2+
13
context('cy.origin files', () => {
24
beforeEach(() => {
35
cy.visit('/fixtures/multi-domain.html')
@@ -44,4 +46,51 @@ context('cy.origin files', () => {
4446
})
4547
})
4648
})
49+
50+
context('#consoleProps', () => {
51+
let logs: Map<string, any>
52+
53+
beforeEach(() => {
54+
logs = new Map()
55+
56+
cy.on('log:changed', (attrs, log) => {
57+
logs.set(attrs.id, log)
58+
})
59+
})
60+
61+
it('.readFile()', () => {
62+
cy.origin('http://foobar.com:3500', () => {
63+
cy.readFile('cypress/fixtures/example.json')
64+
})
65+
66+
cy.shouldWithTimeout(() => {
67+
const { consoleProps } = findCrossOriginLogs('readFile', logs, 'foobar.com')
68+
69+
expect(consoleProps.Command).to.equal('readFile')
70+
expect(consoleProps['File Path']).to.include('cypress/fixtures/example.json')
71+
expect(consoleProps.Contents).to.deep.equal({ example: true })
72+
})
73+
})
74+
75+
it('.writeFile()', () => {
76+
cy.origin('http://foobar.com:3500', () => {
77+
const contents = JSON.stringify({ foo: 'bar' })
78+
79+
cy.stub(Cypress, 'backend').resolves({
80+
contents,
81+
filePath: 'foo.json',
82+
})
83+
84+
cy.writeFile('foo.json', contents)
85+
})
86+
87+
cy.shouldWithTimeout(() => {
88+
const { consoleProps } = findCrossOriginLogs('writeFile', logs, 'foobar.com')
89+
90+
expect(consoleProps.Command).to.equal('writeFile')
91+
expect(consoleProps['File Path']).to.equal('foo.json')
92+
expect(consoleProps.Contents).to.equal('{"foo":"bar"}')
93+
})
94+
})
95+
})
4796
})

0 commit comments

Comments
 (0)