Skip to content

Commit

Permalink
misc: pass the related command log to the createSnapshot function (#3…
Browse files Browse the repository at this point in the history
…0244)

* passing related log to the createSnapshot function

* update changelog

* update system test snapshot
  • Loading branch information
mschile committed Sep 16, 2024
1 parent 095b3da commit f3242ea
Show file tree
Hide file tree
Showing 8 changed files with 241 additions and 737 deletions.
4 changes: 4 additions & 0 deletions cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
6 changes: 3 additions & 3 deletions packages/driver/cypress/e2e/cypress/log.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})

Expand Down Expand Up @@ -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)
})

Expand All @@ -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)
})

Expand Down
70 changes: 0 additions & 70 deletions packages/driver/cypress/e2e/e2e/origin/commands/log.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
1 change: 0 additions & 1 deletion packages/driver/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
150 changes: 2 additions & 148 deletions packages/driver/src/cy/snapshots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand Down
6 changes: 3 additions & 3 deletions packages/driver/src/cypress/log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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))>
Expand Down Expand Up @@ -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)

Expand Down
Loading

5 comments on commit f3242ea

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on f3242ea Sep 16, 2024

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 build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/13.14.3/linux-x64/develop-f3242ea7f33aea6571c7c5b54556e3c22072bac9/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on f3242ea Sep 16, 2024

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 build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/13.14.3/linux-arm64/develop-f3242ea7f33aea6571c7c5b54556e3c22072bac9/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on f3242ea Sep 16, 2024

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 build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/13.14.3/darwin-arm64/develop-f3242ea7f33aea6571c7c5b54556e3c22072bac9/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on f3242ea Sep 16, 2024

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 build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/13.14.3/win32-x64/develop-f3242ea7f33aea6571c7c5b54556e3c22072bac9/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on f3242ea Sep 16, 2024

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 x64 version of the Test Runner.

Learn more about this pre-release build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/13.14.3/darwin-x64/develop-f3242ea7f33aea6571c7c5b54556e3c22072bac9/cypress.tgz

Please sign in to comment.