mirror of
https://github.com/cypress-io/cypress.git
synced 2026-05-03 05:20:38 -05:00
misc: pass the related command log to the createSnapshot function (#30244)
* passing related log to the createSnapshot function * update changelog * update system test snapshot
This commit is contained in:
@@ -3,6 +3,10 @@
|
||||
|
||||
_Released 9/10/2024 (PENDING)_
|
||||
|
||||
**Misc:**
|
||||
|
||||
- Pass along the related log to the `createSnapshot` function for protocol usage. Addressed in [#30244](https://github.com/cypress-io/cypress/pull/30244).
|
||||
|
||||
**Dependency Updates:**
|
||||
|
||||
- Update `@cypress/request` from `3.0.1` to `3.0.4`. Addressed in [#30194](https://github.com/cypress-io/cypress/pull/30194).
|
||||
|
||||
@@ -390,7 +390,7 @@ describe('src/cypress/log', function () {
|
||||
const log = this.log({ '$el': div })
|
||||
const result = log.snapshot()
|
||||
|
||||
expect(this.cy.createSnapshot).to.be.calledWith(undefined, div)
|
||||
expect(this.cy.createSnapshot).to.be.calledWith(undefined, div, undefined, log)
|
||||
expect(result).to.equal(log)
|
||||
})
|
||||
|
||||
@@ -436,7 +436,7 @@ describe('src/cypress/log', function () {
|
||||
const log = this.log({ '$el': div })
|
||||
const result = log.snapshot()
|
||||
|
||||
expect(this.cy.createSnapshot).to.be.calledWith(undefined, div)
|
||||
expect(this.cy.createSnapshot).to.be.calledWith(undefined, div, undefined, log)
|
||||
expect(result).to.equal(log)
|
||||
})
|
||||
|
||||
@@ -450,7 +450,7 @@ describe('src/cypress/log', function () {
|
||||
const log = this.log({ '$el': div })
|
||||
const result = log.snapshot()
|
||||
|
||||
expect(this.cy.createSnapshot).to.be.calledWith(undefined, div)
|
||||
expect(this.cy.createSnapshot).to.be.calledWith(undefined, div, undefined, log)
|
||||
expect(result).to.equal(log)
|
||||
})
|
||||
|
||||
|
||||
@@ -177,41 +177,6 @@ context('cy.origin log', { browser: '!webkit' }, () => {
|
||||
.wait(1500)
|
||||
})
|
||||
|
||||
it('when run mode with protocol enabled', { numTestsKeptInMemory: 0, protocolEnabled: true }, () => {
|
||||
// Verify the log is also fired in the primary origin.
|
||||
expect(logsToVerify.length).to.eq(11)
|
||||
|
||||
expect(logsToVerify[1].get('name')).to.equal('log 1')
|
||||
expect(logsToVerify[1].get('snapshots')).to.be.undefined
|
||||
|
||||
expect(logsToVerify[2].get('name')).to.equal('log 2')
|
||||
expect(logsToVerify[2].get('snapshots')).to.have.length(1)
|
||||
|
||||
expect(logsToVerify[3].get('name')).to.equal('log 3')
|
||||
expect(logsToVerify[3].get('snapshots')).to.have.length(1)
|
||||
|
||||
expect(logsToVerify[4].get('name')).to.equal('log 4')
|
||||
expect(logsToVerify[4].get('snapshots')).to.have.length(1)
|
||||
|
||||
expect(logsToVerify[5].get('name')).to.equal('log 5')
|
||||
expect(logsToVerify[5].get('snapshots')).to.have.length(1)
|
||||
|
||||
expect(logsToVerify[6].get('name')).to.equal('log 6')
|
||||
expect(logsToVerify[6].get('snapshots')).to.be.undefined
|
||||
|
||||
expect(logsToVerify[7].get('name')).to.equal('log 7')
|
||||
expect(logsToVerify[7].get('snapshots')).to.have.length(1)
|
||||
|
||||
expect(logsToVerify[8].get('name')).to.equal('log 8')
|
||||
expect(logsToVerify[8].get('snapshots')).to.have.length(1)
|
||||
|
||||
expect(logsToVerify[9].get('name')).to.equal('log 9')
|
||||
expect(logsToVerify[9].get('snapshots')).to.have.length(1)
|
||||
|
||||
expect(logsToVerify[10].get('name')).to.equal('log 10')
|
||||
expect(logsToVerify[10].get('snapshots')).to.have.length(1)
|
||||
})
|
||||
|
||||
it('when run mode with protocol disabled', { numTestsKeptInMemory: 0, protocolEnabled: false }, () => {
|
||||
// Verify the log is also fired in the primary origin.
|
||||
expect(logsToVerify.length).to.eq(11)
|
||||
@@ -280,41 +245,6 @@ context('cy.origin log', { browser: '!webkit' }, () => {
|
||||
.wait(1500)
|
||||
})
|
||||
|
||||
it('when run mode with protocol enabled', { numTestsKeptInMemory: 0, protocolEnabled: true }, () => {
|
||||
// Verify the log is also fired in the primary origin.
|
||||
expect(logsToVerify.length).to.eq(11)
|
||||
|
||||
expect(logsToVerify[1].get('name')).to.equal('log 1')
|
||||
expect(logsToVerify[1].get('snapshots')).to.have.length(1)
|
||||
|
||||
expect(logsToVerify[2].get('name')).to.equal('log 2')
|
||||
expect(logsToVerify[2].get('snapshots')).to.have.length(1)
|
||||
|
||||
expect(logsToVerify[3].get('name')).to.equal('log 3')
|
||||
expect(logsToVerify[3].get('snapshots')).to.have.length(1)
|
||||
|
||||
expect(logsToVerify[4].get('name')).to.equal('log 4')
|
||||
expect(logsToVerify[4].get('snapshots')).to.have.length(1)
|
||||
|
||||
expect(logsToVerify[5].get('name')).to.equal('log 5')
|
||||
expect(logsToVerify[5].get('snapshots')).to.have.length(1)
|
||||
|
||||
expect(logsToVerify[6].get('name')).to.equal('log 6')
|
||||
expect(logsToVerify[6].get('snapshots')).to.have.length(1)
|
||||
|
||||
expect(logsToVerify[7].get('name')).to.equal('log 7')
|
||||
expect(logsToVerify[7].get('snapshots')).to.have.length(2)
|
||||
|
||||
expect(logsToVerify[8].get('name')).to.equal('log 8')
|
||||
expect(logsToVerify[8].get('snapshots')).to.have.length(2)
|
||||
|
||||
expect(logsToVerify[9].get('name')).to.equal('log 9')
|
||||
expect(logsToVerify[9].get('snapshots')).to.have.length(1)
|
||||
|
||||
expect(logsToVerify[10].get('name')).to.equal('log 10')
|
||||
expect(logsToVerify[10].get('snapshots')).to.have.length(1)
|
||||
})
|
||||
|
||||
it('when run mode with protocol disabled', { numTestsKeptInMemory: 0, protocolEnabled: false }, () => {
|
||||
// Verify the log is also fired in the primary origin.
|
||||
expect(logsToVerify.length).to.eq(11)
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
"@cypress/unique-selector": "0.0.5",
|
||||
"@cypress/webpack-dev-server": "0.0.0-development",
|
||||
"@cypress/webpack-preprocessor": "0.0.0-development",
|
||||
"@medv/finder": "3.1.0",
|
||||
"@packages/config": "0.0.0-development",
|
||||
"@packages/network": "0.0.0-development",
|
||||
"@packages/rewriter": "0.0.0-development",
|
||||
|
||||
@@ -4,95 +4,12 @@ import type { $Cy } from '../cypress/cy'
|
||||
import type { StateFunc } from '../cypress/state'
|
||||
import $dom from '../dom'
|
||||
import { create as createSnapshotsCSS } from './snapshots_css'
|
||||
import { finder } from '@medv/finder'
|
||||
import type { Log } from '../cypress/log'
|
||||
|
||||
export const HIGHLIGHT_ATTR = 'data-cypress-el'
|
||||
|
||||
export const FINAL_SNAPSHOT_NAME = 'final state'
|
||||
|
||||
type SelectorNode = {
|
||||
frameId?: string
|
||||
selector: string
|
||||
ownerDoc: Document | ShadowRoot
|
||||
host?: SelectorNode
|
||||
}
|
||||
|
||||
const returnShadowRootIfShadowDomNode = (node: Element): ShadowRoot | null => {
|
||||
// the shadowRoot object property only lives on the node context OUTSIDE the shadow DOM, meaning that
|
||||
// node.parentNode.host.shadowRoot works. Oddly, this is considered an instance of an Object and not
|
||||
// a ShadowRoot, so checking for the shadowRoot on the host property is likely safe.
|
||||
const isNodeShadowRoot = (n: any) => !!n?.host?.shadowRoot
|
||||
|
||||
let parent = node && node.parentNode
|
||||
|
||||
while (parent) {
|
||||
if (isNodeShadowRoot(parent)) {
|
||||
return parent as ShadowRoot
|
||||
}
|
||||
|
||||
parent = parent.parentNode
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function findSelectorForElement (elem: Element, root: Document | ShadowRoot) {
|
||||
// finder tries to find the shortest unique selector to an element,
|
||||
// but since we are more concerned with speed, we set the threshold to 1 and maxNumberOfTries to 0
|
||||
// @see https://github.com/antonmedv/finder/issues/75
|
||||
return finder(elem, { root: root as unknown as Element, threshold: 1, maxNumberOfTries: 0 })
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a recursive structure of selectors in order to re-identify during Test Replay.
|
||||
*
|
||||
* @param elem - an HTML Element that lives within the shadow DOM or the regular DOM
|
||||
* @returns SelectorNode if the selector can be discovered. For regular elements, this should only be one object deep, but for shadow DOM
|
||||
* elements, the SelectorNode tree could be N levels deep until the root is discovered
|
||||
*/
|
||||
function constructElementSelectorTree (elem: Element): SelectorNode | undefined {
|
||||
try {
|
||||
const ownerDoc = elem.ownerDocument
|
||||
const elWindow = ownerDoc.defaultView
|
||||
|
||||
if (elWindow === null) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
// finder will return a string if it can find the selector.
|
||||
// otherwise, an error will throw and we will fall back to shadowDom lookup.
|
||||
const selector = findSelectorForElement(elem, ownerDoc)
|
||||
|
||||
const frameId = elWindow['__cypressProtocolMetadata']?.frameId
|
||||
|
||||
return { selector, frameId, ownerDoc: elem.ownerDocument, host: undefined }
|
||||
} catch {
|
||||
// the element may not always be found since it's possible for the element to be removed from the DOM
|
||||
// Or maybe its in the shadow DOM.
|
||||
// If it is a shadow DOM element, return the ShadowRoot as well to relate the node to the root document
|
||||
try {
|
||||
const shadowRoot = returnShadowRootIfShadowDomNode(elem)
|
||||
|
||||
// If we have a shadow DOM element, get the frameId and unique selector of the ShadowRoot
|
||||
// see https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot
|
||||
if (shadowRoot) {
|
||||
// Look up the details of the shadowRoot to see which element the ShadowRoot is bound to, i.e. the host.
|
||||
const hostDetails = constructElementSelectorTree(shadowRoot.host)
|
||||
|
||||
// look up our element inside the context of the ShadowRoot
|
||||
const selectorFromShadowWorld = findSelectorForElement(elem, shadowRoot)
|
||||
|
||||
// gives us enough information to associate the shadow element to the ShadowRoot/host to reconstruct in Test Replay
|
||||
return { selector: selectorFromShadowWorld, frameId: undefined, ownerDoc: shadowRoot, host: hostDetails }
|
||||
}
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
export const create = ($$: $Cy['$$'], state: StateFunc) => {
|
||||
const snapshotsCss = createSnapshotsCSS($$, state)
|
||||
const snapshotsMap = new WeakMap()
|
||||
@@ -315,48 +232,7 @@ export const create = ($$: $Cy['$$'], state: StateFunc) => {
|
||||
return $dom.isElement($el) && $dom.isJquery($el)
|
||||
}
|
||||
|
||||
const buildSelectorArray = (el: HTMLElement) => {
|
||||
// flatten selector to only include selector string values, which we can imply is a shadowRoot if other values exist in the tree
|
||||
// this keeps the structure similar to axe-core
|
||||
// @see https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#results-object -> target
|
||||
const selectors: string[] | undefined = []
|
||||
let frameId: string | undefined
|
||||
const flattenElementSelectorTree = (el: SelectorNode | undefined): void => {
|
||||
if (el) {
|
||||
selectors.unshift(el?.selector)
|
||||
|
||||
if (el?.host) {
|
||||
flattenElementSelectorTree(el.host)
|
||||
} else {
|
||||
frameId = el.frameId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const elToHighlight = constructElementSelectorTree(el)
|
||||
|
||||
flattenElementSelectorTree(elToHighlight)
|
||||
|
||||
let selector: string | string[] | undefined
|
||||
|
||||
switch (selectors.length) {
|
||||
case 0:
|
||||
selector = undefined
|
||||
break
|
||||
case 1:
|
||||
selector = selectors[0]
|
||||
break
|
||||
default:
|
||||
selector = selectors
|
||||
}
|
||||
|
||||
return selector ? [{
|
||||
selector,
|
||||
frameId,
|
||||
}] : []
|
||||
}
|
||||
|
||||
const createSnapshot = (name, $elToHighlight, preprocessedSnapshot) => {
|
||||
const createSnapshot = (name?, $elToHighlight?, preprocessedSnapshot?, relatedLog?: Log) => {
|
||||
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
|
||||
@@ -370,28 +246,6 @@ export const create = ($$: $Cy['$$'], state: StateFunc) => {
|
||||
|
||||
const timestamp = performance.now() + performance.timeOrigin
|
||||
|
||||
// if the protocol has been enabled, our snapshot is just the name, timestamp, and highlighted elements,
|
||||
// also make sure numTestsKeptInMemory is 0, otherwise we will want the full snapshot
|
||||
// (the driver test's set numTestsKeptInMemory to 1 in run mode to verify the snapshots)
|
||||
if (Cypress.config('protocolEnabled') && Cypress.config('numTestsKeptInMemory') === 0) {
|
||||
const snapshot: {
|
||||
name: string
|
||||
timestamp: number
|
||||
elementsToHighlight?: {
|
||||
selector: string | string []
|
||||
frameId: string
|
||||
}[]
|
||||
} = { name, timestamp }
|
||||
|
||||
if (isJqueryElement($elToHighlight)) {
|
||||
snapshot.elementsToHighlight = $dom.unwrap($elToHighlight).flatMap((el: HTMLElement) => buildSelectorArray(el))
|
||||
}
|
||||
|
||||
Cypress.action('cy:protocol-snapshot')
|
||||
|
||||
return snapshot
|
||||
}
|
||||
|
||||
try {
|
||||
const {
|
||||
$body,
|
||||
|
||||
@@ -2,7 +2,7 @@ import _, { DebouncedFunc } from 'lodash'
|
||||
import $ from 'jquery'
|
||||
import clone from 'clone'
|
||||
|
||||
import { HIGHLIGHT_ATTR } from '../cy/snapshots'
|
||||
import { HIGHLIGHT_ATTR, type ISnapshots } from '../cy/snapshots'
|
||||
import $dom from '../dom'
|
||||
import $utils from './utils'
|
||||
import $errUtils from './error_utils'
|
||||
@@ -239,7 +239,7 @@ const defaults = function (state: StateFunc, config, obj) {
|
||||
}
|
||||
|
||||
export class Log {
|
||||
createSnapshot: Function
|
||||
createSnapshot: ISnapshots['createSnapshot']
|
||||
state: StateFunc
|
||||
config: any
|
||||
fireChangeEvent: DebouncedFunc<((log) => (void | undefined))>
|
||||
@@ -411,7 +411,7 @@ export class Log {
|
||||
this.set('next', null)
|
||||
}
|
||||
|
||||
const snapshot = this.createSnapshot(name, this.get('$el'))
|
||||
const snapshot = this.createSnapshot(name, this.get('$el'), undefined, this)
|
||||
|
||||
this.addSnapshot(snapshot, options)
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4761,11 +4761,6 @@
|
||||
lodash "^4.17.15"
|
||||
tmp-promise "^3.0.2"
|
||||
|
||||
"@medv/finder@3.1.0":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@medv/finder/-/finder-3.1.0.tgz#e157c68f166ade9f113a1314603365bf81dd8b8c"
|
||||
integrity sha512-ojkXjR3K0Zz3jnCR80tqPL+0yvbZk/lEodb6RIVjLz7W8RVA2wrw8ym/CzCpXO9SYVUIKHFUpc7jvf8UKfIM3w==
|
||||
|
||||
"@microsoft/fetch-event-source@2.0.1":
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz#9ceecc94b49fbaa15666e38ae8587f64acce007d"
|
||||
|
||||
Reference in New Issue
Block a user