chore: migrate mochaEvents integration tests from runner to app (#20678)

* mocha-events

* wip

* work on snapshots tests

* update

* lint

* remove unused code

* revert some changes

* remove old code

* migrate retries tests to app

* reduce duplication

* remove old code

* rename method

* use disparity
This commit is contained in:
Lachlan Miller
2022-03-24 15:32:25 +10:00
committed by GitHub
parent f898ea7963
commit 03fbbd7bc7
34 changed files with 11707 additions and 3576 deletions

View File

@@ -0,0 +1,136 @@
import { runSpec } from './support/spec-loader'
import { runCypressInCypressMochaEventsTest } from './support/mochaEventsUtils'
import { snapshots } from './retries.mochaEvents.snapshots'
describe('src/cypress/runner retries mochaEvents', { retries: 0 }, () => {
// NOTE: for test-retries
it('simple retry', (done) => {
const { assertMatchingSnapshot } = runCypressInCypressMochaEventsTest(
snapshots,
'src/cypress/runner retries mochaEvents simple retry #1',
done,
)
runSpec({
fileName: 'simple-fail.retries.mochaEvents.cy.js',
}).then((win) => {
assertMatchingSnapshot(win)
})
})
it('test retry with hooks', (done) => {
const { assertMatchingSnapshot } = runCypressInCypressMochaEventsTest(
snapshots,
'src/cypress/runner retries mochaEvents test retry with hooks #1',
done,
)
runSpec({
fileName: 'test-retry-with-hooks.retries.mochaEvents.cy.js',
}).then((win) => {
assertMatchingSnapshot(win)
})
})
it('test retry with hooks', (done) => {
const { assertMatchingSnapshot } = runCypressInCypressMochaEventsTest(
snapshots,
'src/cypress/runner retries mochaEvents test retry with hooks #1',
done,
)
runSpec({
fileName: 'test-retry-with-hooks.retries.mochaEvents.cy.js',
}).then((win) => {
assertMatchingSnapshot(win)
})
})
it('test retry with [only]', (done) => {
const { assertMatchingSnapshot } = runCypressInCypressMochaEventsTest(
snapshots,
'src/cypress/runner retries mochaEvents test retry with [only] #1',
done,
)
runSpec({
fileName: 'test-retry-with-only.retries.mochaEvents.cy.js',
}).then((win) => {
assertMatchingSnapshot(win)
})
})
it('can retry from [beforeEach]', (done) => {
const { assertMatchingSnapshot } = runCypressInCypressMochaEventsTest(
snapshots,
'src/cypress/runner retries mochaEvents can retry from [beforeEach] #1',
done,
)
runSpec({
fileName: 'can-retry-from-beforeEach.retries.mochaEvents.cy.js',
}).then((win) => {
assertMatchingSnapshot(win)
})
})
it('can retry from [afterEach]', (done) => {
const { assertMatchingSnapshot } = runCypressInCypressMochaEventsTest(
snapshots,
'src/cypress/runner retries mochaEvents can retry from [afterEach] #1',
done,
)
runSpec({
fileName: 'can-retry-from-afterEach.retries.mochaEvents.cy.js',
}).then((win) => {
assertMatchingSnapshot(win)
})
})
it('cant retry from [before]', (done) => {
const { assertMatchingSnapshot } = runCypressInCypressMochaEventsTest(
snapshots,
'src/cypress/runner retries mochaEvents cant retry from [before] #1',
done,
)
runSpec({
fileName: 'cant-retry-from-before.retries.mochaEvents.cy.js',
}).then((win) => {
assertMatchingSnapshot(win)
})
})
it('three tests with retry', (done) => {
const { assertMatchingSnapshot } = runCypressInCypressMochaEventsTest(
snapshots,
'src/cypress/runner retries mochaEvents three tests with retry #1',
done,
)
runSpec({
fileName: 'three-tests-with-retry.retries.mochaEvents.cy.js',
}).then((win) => {
assertMatchingSnapshot(win)
})
})
describe('cleanses errors before emitting', () => {
it('does not try to serialize error with err.actual as DOM node', (done) => {
const { assertMatchingSnapshot } = runCypressInCypressMochaEventsTest(
snapshots,
'src/cypress/runner retries mochaEvents cleanses errors before emitting does not try to serialize error with err.actual as DOM node #1',
done,
)
runSpec({
fileName: 'does-not-serialize-dom-error.cy.js',
}).then((win) => {
// should not have err.actual, expected properties since the subject is a DOM element
assertMatchingSnapshot(win)
})
})
})
})

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,125 @@
import { runSpec } from './support/spec-loader'
import { runCypressInCypressMochaEventsTest } from './support/mochaEventsUtils'
import { snapshots } from './runner.mochaEvents.snapshots'
describe('src/cypress/runner', { retries: 0 }, () => {
describe('tests finish with correct state', () => {
describe('hook failures', () => {
it('fail in [before]', (done) => {
const { assertMatchingSnapshot } = runCypressInCypressMochaEventsTest(
snapshots,
'src/cypress/runner tests finish with correct state hook failures fail in [before] #1',
done,
)
runSpec({
fileName: 'fail-with-before.mochaEvents.cy.js',
}).then((win) => {
assertMatchingSnapshot(win)
})
})
it('fail in [beforeEach]', (done) => {
const { assertMatchingSnapshot } = runCypressInCypressMochaEventsTest(
snapshots,
'src/cypress/runner tests finish with correct state hook failures fail in [beforeEach] #1',
done,
)
runSpec({
fileName: 'fail-with-beforeEach.mochaEvents.cy.js',
}).then((win) => {
assertMatchingSnapshot(win)
})
})
it('fail in [after]', (done) => {
const { assertMatchingSnapshot } = runCypressInCypressMochaEventsTest(
snapshots,
'src/cypress/runner tests finish with correct state hook failures fail in [after] #1',
done,
)
runSpec({
fileName: 'fail-with-after.mochaEvents.cy.js',
}).then((win) => {
assertMatchingSnapshot(win)
})
})
it('fail in [afterEach]', (done) => {
const { assertMatchingSnapshot } = runCypressInCypressMochaEventsTest(
snapshots,
'src/cypress/runner tests finish with correct state hook failures fail in [afterEach] #1',
done,
)
runSpec({
fileName: 'fail-with-afterEach.mochaEvents.cy.js',
}).then((win) => {
assertMatchingSnapshot(win)
})
})
})
describe('mocha grep', () => {
it('fail with [only]', (done) => {
const { assertMatchingSnapshot } = runCypressInCypressMochaEventsTest(
snapshots,
'src/cypress/runner tests finish with correct state mocha grep fail with [only] #1',
done,
)
runSpec({
fileName: 'fail-with-only.mochaEvents.cy.js',
}).then((win) => {
assertMatchingSnapshot(win)
})
})
it('pass with [only]', (done) => {
const { assertMatchingSnapshot } = runCypressInCypressMochaEventsTest(
snapshots,
'src/cypress/runner tests finish with correct state mocha grep pass with [only] #1',
done,
)
runSpec({
fileName: 'pass-with-only.mochaEvents.cy.js',
}).then((win) => {
assertMatchingSnapshot(win)
})
})
})
})
describe('mocha events', () => {
it('simple single test', (done) => {
const { assertMatchingSnapshot } = runCypressInCypressMochaEventsTest(
snapshots,
'src/cypress/runner mocha events simple single test #1',
done,
)
runSpec({
fileName: 'simple-single-test.mochaEvents.cy.js',
}).then((win) => {
assertMatchingSnapshot(win)
})
})
it('simple three tests', (done) => {
const { assertMatchingSnapshot } = runCypressInCypressMochaEventsTest(
snapshots,
'src/cypress/runner mocha events simple three tests #1',
done,
)
runSpec({
fileName: 'three-tests-with-hooks.mochaEvents.cy.js',
}).then((win) => {
assertMatchingSnapshot(win)
})
})
})
})

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,189 @@
import type { CypressInCypressMochaEvent } from '../../../../src/runner/event-manager'
import _ from 'lodash'
import type { MochaLifecycleData, SanitizedMochaLifecycleData } from './mochaTypes'
import EventEmitter from 'events'
import disparity from 'disparity'
const hooks = {
before: ['before all', 'before each'],
after: ['after each', 'after all'],
} as const
const stringifyShort = (obj: Record<string, any>) => {
const constructorName = _.get(obj, 'constructor.name')
if (constructorName && !_.includes(['Object', 'Array'], constructorName)) {
return `{${constructorName}}`
}
if (_.isArray(obj)) {
return `[Array ${obj.length}]`
}
if (_.isObject(obj)) {
return `{Object ${Object.keys(obj).length}}`
}
return obj
}
const eventCleanseMap = {
snapshots: stringifyShort,
parent: stringifyShort,
tests: stringifyShort,
commands: stringifyShort,
invocationDetails: stringifyShort,
body: () => '[body]',
wallClockStartedAt: () => 'match.date',
lifecycle: () => 'match.number',
fnDuration: () => 'match.number',
duration: () => 'match.number',
afterFnDuration: () => 'match.number',
wallClockDuration: () => 'match.number',
stack: () => 'match.string',
file: (arg: string) => arg ? 'relative/path/to/spec.js' : undefined,
message: () => '[error message]',
sourceMappedStack: () => 'match.string',
parsedStack: () => 'match.array',
name: (n: string) => n === 'Error' ? 'AssertionError' : n,
err: () => {
return {
message: '[error message]',
name: 'AssertionError',
stack: 'match.string',
sourceMappedStack: 'match.string',
parsedStack: 'match.array',
}
},
startTime: new Date(0),
start: () => 'match.date',
end: () => 'match.date',
timings: (arg: MochaLifecycleData): SanitizedMochaLifecycleData => {
let sanitizedLifecycleData: SanitizedMochaLifecycleData = {
lifecycle: 'match.number',
}
for (const hook of hooks.before) {
const hooksToSanitize = arg[hook]
if (!hooksToSanitize) {
continue
}
sanitizedLifecycleData[hook] = hooksToSanitize.map((oldHook) => {
return {
hookId: oldHook.hookId,
fnDuration: 'match.number',
afterFnDuration: 'match.number',
}
})
}
if (arg.test) {
sanitizedLifecycleData.test = {
fnDuration: 'match.number',
afterFnDuration: 'match.number',
}
}
for (const hook of hooks.after) {
const hooksToSanitize = arg[hook]
if (!hooksToSanitize) {
continue
}
sanitizedLifecycleData[hook] = hooksToSanitize.map((oldHook) => {
return {
hookId: oldHook.hookId,
fnDuration: 'match.number',
afterFnDuration: 'match.number',
}
})
}
return sanitizedLifecycleData
},
}
const keysToEliminate = ['codeFrame', '_testConfig'] as const
function removeUnusedKeysForTestSnapshot<T> (obj: T): T {
for (const key of keysToEliminate) {
delete obj[key]
}
for (const [key, value] of Object.entries(obj)) {
if (key in obj) {
const transform = eventCleanseMap[key]?.(value)
if (!transform) {
continue
}
obj[key] = transform
} else {
delete obj[key]
}
}
return obj
}
declare global {
interface Window {
bus: EventEmitter
}
}
class SnapshotError extends Error {
constructor (message: string) {
super()
this.message = `\n${message}`
}
}
export function runCypressInCypressMochaEventsTest<T> (snapshots: T, snapToCompare: keyof T, done: Mocha.Done) {
const bus = new EventEmitter()
const outerRunner = window.top!.window
outerRunner.bus = bus
// TODO: Can we automate writing the snapshots to disk?
// For some reason `cy.task('getSnapshot')` has problems when executed in
// "cypress in cypress"
bus.on('assert:cypress:in:cypress', (snapshot: CypressInCypressMochaEvent[]) => {
const expected = snapshots[snapToCompare]
const diff = disparity.unifiedNoColor(JSON.stringify(snapshot, null, 2), JSON.stringify(expected, null, 2), {})
if (diff !== '') {
/* eslint-disable no-console */
console.info('Received snapshot:', JSON.stringify(snapshot, null, 2))
throw new SnapshotError(diff)
}
done()
})
const assertMatchingSnapshot = (win: Cypress.AUTWindow) => {
win.getEventManager().on('cypress:in:cypress:run:complete', (args: CypressInCypressMochaEvent[]) => {
const data = sanitizeMochaEvents(args)
bus.emit('assert:cypress:in:cypress', data)
})
}
return { assertMatchingSnapshot }
}
function sanitizeMochaEvents (args: CypressInCypressMochaEvent[]) {
return args.map((mochaEvent) => {
return mochaEvent.map((payload) => {
if (typeof payload === 'string') {
return payload
}
return removeUnusedKeysForTestSnapshot(payload)
})
})
}

View File

@@ -0,0 +1,23 @@
export type Timing = {
hookId: `h${number}`
fnDuration: number
afterFnDuration: number
}
export type SanitizedTiming = {
hookId?: `h${number}`
fnDuration: 'match.number'
afterFnDuration: 'match.number'
}
export type MochaLifecycleBase<T> = {
'test'?: T
'before each'?: T[]
'after each'?: T[]
'before all'?: T[]
'after all'?: T[]
}
export type MochaLifecycleData = MochaLifecycleBase<Timing> & { lifecycle: number }
export type SanitizedMochaLifecycleData = MochaLifecycleBase<SanitizedTiming> & { lifecycle: string }

View File

@@ -21,7 +21,7 @@ export type LoadSpecOptions = {
hasPreferredIde?: boolean
}
export function loadSpec (options: LoadSpecOptions): void {
export function loadSpec (options: LoadSpecOptions) {
const {
fileName,
setup,
@@ -78,3 +78,23 @@ export function loadSpec (options: LoadSpecOptions): void {
// Wait for specs to complete
shouldHaveTestResults({ passCount, failCount, pendingCount })
}
export function runSpec ({ fileName }: { fileName: string }) {
cy.scaffoldProject('runner-e2e-specs')
cy.openProject('runner-e2e-specs')
cy.startAppServer()
cy.__incorrectlyVisitAppWithIntercept()
cy.findByLabelText('Search Specs').type(fileName)
// wait for virtualized spec list to update, there is a chance
// of disconnection otherwise
cy.wait(500)
cy.contains('[data-cy=spec-item]', fileName).click()
cy.location().should((location) => {
expect(location.hash).to.contain(fileName)
})
return cy.window()
}

View File

@@ -43,6 +43,7 @@
"concurrently": "^6.2.0",
"cross-env": "6.0.3",
"cypress-real-events": "1.6.0",
"disparity": "^3.0.0",
"faker": "5.5.3",
"fuzzysort": "^1.1.4",
"graphql": "^15.5.1",

View File

@@ -1,5 +1,6 @@
import type { FileDetails } from '@packages/types'
import type { ScriptError } from '../store'
import type { CypressInCypressMochaEvent } from './event-manager'
interface BeforeScreenshot {
appOnly: boolean
@@ -21,6 +22,7 @@ export type LocalBusEventMap = {
export type LocalBusEmitsMap = {
'open:file': FileDetails
'cypress:in:cypress:run:complete': CypressInCypressMochaEvent[]
}
export type SocketToDriverMap = {

View File

@@ -11,6 +11,8 @@ import { logger } from './logger'
import type { Socket } from '@packages/socket/lib/browser'
import { useRunnerUiStore } from '../store'
export type CypressInCypressMochaEvent = Array<Array<string | Record<string, any>>>
// type is default export of '@packages/driver'
// cannot import because it's not type safe and tsc throw many type errors.
type $Cypress = any
@@ -26,7 +28,7 @@ const driverToReporterEvents = 'paused session:add'.split(' ')
const driverToLocalAndReporterEvents = 'run:start run:end'.split(' ')
const driverToSocketEvents = 'backend:request automation:request mocha recorder:frame'.split(' ')
const driverTestEvents = 'test:before:run:async test:after:run'.split(' ')
const driverToLocalEvents = 'viewport:changed config stop url:changed page:loading visit:failed visit:blank'.split(' ')
const driverToLocalEvents = 'viewport:changed config stop url:changed page:loading visit:failed visit:blank cypress:in:cypress:runner:event'.split(' ')
const socketRerunEvents = 'runner:restart watched:file:changed'.split(' ')
const socketToDriverEvents = 'net:stubbing:event request:event script:error'.split(' ')
const localToReporterEvents = 'reporter:log:add reporter:log:state:changed reporter:log:remove'.split(' ')
@@ -42,6 +44,7 @@ export class EventManager {
Cypress?: $Cypress
studioRecorder: any
selectorPlaygroundModel: any
cypressInCypressMochaEvents: CypressInCypressMochaEvent[] = []
constructor (
// import '@packages/driver'
@@ -531,6 +534,22 @@ export class EventManager {
driverToLocalEvents.forEach((event) => {
Cypress.on(event, (...args: unknown[]) => {
// special case for asserting the correct mocha events + payload
// is emitted from cypress/driver when running e2e tests using
// "cypress in cypress"
if (event === 'cypress:in:cypress:runner:event') {
this.cypressInCypressMochaEvents.push(args as CypressInCypressMochaEvent[])
if (args[0] === 'mocha' && args[1] === 'end') {
this.emit('cypress:in:cypress:run:complete', this.cypressInCypressMochaEvents)
// reset
this.cypressInCypressMochaEvents = []
}
return
}
// @ts-ignore
// TODO: UNIFY-1318 - strongly typed event emitter.
return this.emit(event, ...args)

View File

@@ -72,6 +72,9 @@ interface AutomationError extends Error {
automation: boolean
}
// Are we running Cypress in Cypress? (Used for E2E Testing for Cypress in Cypress only)
const isCypressInCypress = document.defaultView !== top
class $Cypress {
cy: any
chai: any
@@ -372,6 +375,17 @@ class $Cypress {
})
}
maybeEmitCypressInCypress (...args: unknown[]) {
// emit an event if we are running a Cypress in Cypress E2E Test.
// used to assert the runner (mocha) is emitting the expected
// events/payload.
if (!isCypressInCypress) {
return
}
this.emit('cypress:in:cypress:runner:event', ...args)
}
action (eventName, ...args) {
// normalizes all the various ways
// other objects communicate intent
@@ -397,6 +411,8 @@ class $Cypress {
return
}
this.maybeEmitCypressInCypress('mocha', 'start', args[0])
if (this.config('isTextTerminal')) {
return this.emit('mocha', 'start', args[0])
}
@@ -415,6 +431,8 @@ class $Cypress {
// test:after:run events ourselves
this.emit('run:end')
this.maybeEmitCypressInCypress('mocha', 'end', args[0])
if (this.config('isTextTerminal')) {
return this.emit('mocha', 'end', args[0])
}
@@ -423,6 +441,8 @@ class $Cypress {
case 'runner:suite:start':
// mocha runner started processing a suite
this.maybeEmitCypressInCypress('mocha', 'suite', ...args)
if (this.config('isTextTerminal')) {
return this.emit('mocha', 'suite', ...args)
}
@@ -431,6 +451,8 @@ class $Cypress {
case 'runner:suite:end':
// mocha runner finished processing a suite
this.maybeEmitCypressInCypress('mocha', 'suite end', ...args)
if (this.config('isTextTerminal')) {
return this.emit('mocha', 'suite end', ...args)
}
@@ -439,6 +461,9 @@ class $Cypress {
case 'runner:hook:start':
// mocha runner started processing a hook
this.maybeEmitCypressInCypress('mocha', 'hook', ...args)
if (this.config('isTextTerminal')) {
return this.emit('mocha', 'hook', ...args)
}
@@ -447,6 +472,8 @@ class $Cypress {
case 'runner:hook:end':
// mocha runner finished processing a hook
this.maybeEmitCypressInCypress('mocha', 'hook end', ...args)
if (this.config('isTextTerminal')) {
return this.emit('mocha', 'hook end', ...args)
}
@@ -455,6 +482,8 @@ class $Cypress {
case 'runner:test:start':
// mocha runner started processing a hook
this.maybeEmitCypressInCypress('mocha', 'test', ...args)
if (this.config('isTextTerminal')) {
return this.emit('mocha', 'test', ...args)
}
@@ -462,6 +491,8 @@ class $Cypress {
break
case 'runner:test:end':
this.maybeEmitCypressInCypress('mocha', 'test end', ...args)
if (this.config('isTextTerminal')) {
return this.emit('mocha', 'test end', ...args)
}
@@ -472,6 +503,8 @@ class $Cypress {
// mocha runner calculated a pass
// this is delayed from when mocha would normally fire it
// since we fire it after all afterEach hooks have ran
this.maybeEmitCypressInCypress('mocha', 'pass', ...args)
if (this.config('isTextTerminal')) {
return this.emit('mocha', 'pass', ...args)
}
@@ -480,6 +513,8 @@ class $Cypress {
case 'runner:pending':
// mocha runner calculated a pending test
this.maybeEmitCypressInCypress('mocha', 'pending', ...args)
if (this.config('isTextTerminal')) {
return this.emit('mocha', 'pending', ...args)
}
@@ -487,6 +522,8 @@ class $Cypress {
break
case 'runner:fail': {
this.maybeEmitCypressInCypress('mocha', 'fail', ...args)
if (this.config('isTextTerminal')) {
return this.emit('mocha', 'fail', ...args)
}
@@ -497,6 +534,8 @@ class $Cypress {
// https://github.com/mochajs/mocha/commit/2a76dd7589e4a1ed14dd2a33ab89f182e4c4a050
case 'runner:retry': {
// mocha runner calculated a pass
this.maybeEmitCypressInCypress('mocha', 'retry', ...args)
if (this.config('isTextTerminal')) {
this.emit('mocha', 'retry', ...args)
}
@@ -508,6 +547,8 @@ class $Cypress {
return this.runner.onRunnableRun(...args)
case 'runner:test:before:run':
this.maybeEmitCypressInCypress('mocha', 'test:before:run', args[0])
if (this.config('isTextTerminal')) {
// needed for handling test retries
this.emit('mocha', 'test:before:run', args[0])
@@ -529,6 +570,7 @@ class $Cypress {
// this event is how the reporter knows how to display
// stats and runnable properties such as errors
this.emit('test:after:run', ...args)
this.maybeEmitCypressInCypress('mocha', 'test:after:run', args[0])
if (this.config('isTextTerminal')) {
// needed for calculating wallClockDuration

View File

@@ -158,6 +158,7 @@ export const e2eProjectDirs = [
'vueclivue3-configured',
'vueclivue3-custom-index-html',
'vueclivue3-unconfigured',
'webpack-dev-server',
'webpack-preprocessor',
'webpack-preprocessor-awesome-typescript-loader',
'webpack-preprocessor-ts-loader',

File diff suppressed because it is too large Load Diff

View File

@@ -1,23 +1,10 @@
const helpers = require('../support/helpers')
const { shouldHaveTestResults, getRunState, cleanseRunStateMap } = helpers
const { runIsolatedCypress, snapshotMochaEvents, getAutCypress } = helpers.createCypress({ config: { retries: 2, isTextTerminal: true } })
const { runIsolatedCypress, getAutCypress } = helpers.createCypress({ config: { retries: 2, isTextTerminal: true } })
const { sinon } = Cypress
const match = Cypress.sinon.match
const threeTestsWithRetry = {
suites: {
'suite 1': {
hooks: ['before', 'beforeEach', 'afterEach', 'after'],
tests: [
'test 1',
{ name: 'test 2', fail: 2 },
'test 3',
],
},
},
}
describe('src/cypress/runner retries mochaEvents', { retries: 0 }, () => {
// NOTE: for test-retries
it('can set retry config', () => {
@@ -27,142 +14,6 @@ describe('src/cypress/runner retries mochaEvents', { retries: 0 }, () => {
})
})
it('simple retry', () => {
runIsolatedCypress({
suites: {
'suite 1': {
tests: [
{ name: 'test 1',
fail: 1,
},
],
},
},
}, { config: { retries: 1 } })
.then(snapshotMochaEvents)
})
it('test retry with hooks', () => {
runIsolatedCypress({
suites: {
'suite 1': {
hooks: ['before', 'beforeEach', 'afterEach', 'after'],
tests: [{ name: 'test 1', fail: 1 }],
},
},
}, { config: { retries: 1 } })
.then(snapshotMochaEvents)
})
it('test retry with [only]', () => {
runIsolatedCypress({
suites: {
'suite 1': {
hooks: ['before', 'beforeEach', 'afterEach', 'after'],
tests: [
{ name: 'test 1' },
{ name: 'test 2', fail: 1, only: true },
{ name: 'test 3' },
],
},
},
}, { config: { retries: 1 } })
.then(snapshotMochaEvents)
})
it('can retry from [beforeEach]', () => {
runIsolatedCypress({
suites: {
'suite 1': {
hooks: [
'before',
'beforeEach',
{ type: 'beforeEach', fail: 1 },
'beforeEach',
'afterEach',
'after',
],
tests: [{ name: 'test 1' }],
},
},
}, { config: { retries: 1 } })
.then(snapshotMochaEvents)
})
it('can retry from [afterEach]', () => {
runIsolatedCypress({
hooks: [{ type: 'afterEach', fail: 1 }],
suites: {
'suite 1': {
hooks: [
'before',
'beforeEach',
'beforeEach',
'afterEach',
'after',
],
tests: [{ name: 'test 1' }, 'test 2', 'test 3'],
},
'suite 2': {
hooks: [{ type: 'afterEach', fail: 2 }],
tests: ['test 1'],
},
'suite 3': {
tests: ['test 1'],
},
},
}, { config: { retries: 2, isTextTerminal: true } })
.then(snapshotMochaEvents)
})
it('cant retry from [before]', () => {
runIsolatedCypress({
suites: {
'suite 1': {
hooks: [
{ type: 'before', fail: 1 },
'beforeEach',
'beforeEach',
'afterEach',
'afterEach',
'after',
],
tests: [{ name: 'test 1' }],
},
},
}, { config: { retries: 1 } })
.then(snapshotMochaEvents)
})
it('cant retry from [after]', () => {
runIsolatedCypress({
suites: {
'suite 1': {
hooks: [
'before',
'beforeEach',
'beforeEach',
'afterEach',
'afterEach',
{ type: 'after', fail: 1 },
],
tests: [{ name: 'test 1' }],
},
},
}, { config: { retries: 1 } })
.then(snapshotMochaEvents)
})
it('three tests with retry', () => {
runIsolatedCypress(threeTestsWithRetry, {
config: {
retries: 2,
},
})
.then(snapshotMochaEvents)
})
describe('screenshots', () => {
it('retry screenshot in test body', () => {
let onAfterScreenshot
@@ -249,20 +100,6 @@ describe('src/cypress/runner retries mochaEvents', { retries: 0 }, () => {
})
})
// https://github.com/cypress-io/cypress/issues/8363
describe('cleanses errors before emitting', () => {
it('does not try to serialize error with err.actual as DOM node', () => {
runIsolatedCypress(() => {
it('visits', () => {
cy.visit('/fixtures/dom.html')
cy.get('#button').should('not.be.visible')
})
}, { config: { defaultCommandTimeout: 200 } })
// should not have err.actual, expected properties since the subject is a DOM element
.then(snapshotMochaEvents)
})
})
describe('save/reload state', () => {
const serializeState = () => {
return getRunState(getAutCypress())

View File

@@ -3,148 +3,9 @@ const sinon = require('sinon')
const helpers = require('../support/helpers')
const { cleanseRunStateMap, shouldHaveTestResults, getRunState } = helpers
const { runIsolatedCypress, snapshotMochaEvents, getAutCypress } = helpers.createCypress({ config: { isTextTerminal: true, retries: 0 } })
const simpleSingleTest = {
suites: { 'suite 1': { tests: [{ name: 'test 1' }] } },
}
const threeTestsWithHooks = {
suites: { 'suite 1': { hooks: ['before', 'beforeEach', 'afterEach', 'after'], tests: ['test 1', 'test 2', 'test 3'] } },
}
const { runIsolatedCypress, getAutCypress } = helpers.createCypress({ config: { isTextTerminal: true, retries: 0 } })
describe('src/cypress/runner', { retries: 0 }, () => {
describe('tests finish with correct state', () => {
describe('hook failures', () => {
it('fail in [before]', () => {
runIsolatedCypress({
suites: {
'suite 1': {
hooks: [
{
type: 'before',
fail: true,
},
],
tests: [{ name: 'test 1' }],
},
},
})
.then(shouldHaveTestResults(0, 1))
.then(() => {
cy.get('.runnable-err:visible').invoke('text').should('contain', 'Because this error occurred during a before all hook')
})
.then(() => {
snapshotMochaEvents()
})
})
it('fail in [beforeEach]', () => {
runIsolatedCypress({
suites: {
'suite 1': {
hooks: [
{
type: 'beforeEach',
fail: true,
},
],
tests: [{ name: 'test 1' }],
},
},
})
.then(shouldHaveTestResults(0, 1))
.then(() => {
snapshotMochaEvents()
})
})
it('fail in [afterEach]', () => {
runIsolatedCypress({
suites: {
'suite 1': {
hooks: [
{
type: 'afterEach',
fail: true,
},
],
tests: [{ name: 'test 1' }],
},
},
})
.then(shouldHaveTestResults(0, 1))
.then(() => {
snapshotMochaEvents()
})
})
it('fail in [after]', () => {
runIsolatedCypress({
suites: {
'suite 1': {
hooks: [
{
type: 'after',
fail: true,
},
],
tests: ['test 1', 'test 2'],
},
},
})
.then(shouldHaveTestResults(1, 1))
.then(() => {
expect('foo').contain('f')
cy.get('.runnable-err:visible').invoke('text').should('contain', 'Because this error occurred during a after all hook')
})
.then(() => {
snapshotMochaEvents()
})
})
})
describe('mocha grep', () => {
it('fail with [only]', () => {
runIsolatedCypress({
suites: {
'suite 1': {
hooks: ['before', 'beforeEach', 'afterEach', 'after'],
tests: [
{ name: 'test 1', fail: true },
{ name: 'test 2', fail: true, only: true },
{ name: 'test 3', fail: true },
],
},
},
})
.then(shouldHaveTestResults(0, 1))
.then(() => {
snapshotMochaEvents()
})
})
it('pass with [only]', () => {
runIsolatedCypress({
suites: {
'suite 1': {
hooks: ['before', 'beforeEach', 'afterEach', 'after'],
tests: [
{ name: 'test 1' },
{ name: 'test 2', only: true },
{ name: 'test 3' },
],
},
},
})
.then(shouldHaveTestResults(1, 0))
.then(() => {
snapshotMochaEvents()
})
})
})
})
describe('save/reload state on top navigation', () => {
describe('serialize / load from state', () => {
const serializeState = () => {
@@ -291,22 +152,6 @@ describe('src/cypress/runner', { retries: 0 }, () => {
})
})
describe('mocha events', () => {
it('simple single test', () => {
runIsolatedCypress(simpleSingleTest)
.then(() => {
snapshotMochaEvents()
})
})
it('simple three tests', () => {
runIsolatedCypress(threeTestsWithHooks)
.then(() => {
snapshotMochaEvents()
})
})
})
describe('event listeners', () => {
// https://github.com/cypress-io/cypress/issues/8701
it('does not hang when error thrown in test:after:run', () => {

View File

@@ -0,0 +1,37 @@
let i = 0
afterEach(() => {
if (i === 0) {
i++
throw new Error('')
}
})
describe('suite 1', { retries: 2 }, () => {
before(() => {})
beforeEach(() => {})
beforeEach(() => {})
afterEach(() => {})
after(() => {})
it('test 1', () => {})
it('test 2', () => {})
it('test 3', () => {})
})
describe('suite 2', { retries: 2 }, () => {
let j = 0
afterEach(() => {
if (j === 0 || j === 1) {
j++
throw new Error('')
}
})
it('test 1', () => {})
})
describe('suite 3', { retries: 2 }, () => {
it('test 1', () => {})
})

View File

@@ -0,0 +1,20 @@
describe('suite 1', { retries: 1 }, () => {
before(() => {})
beforeEach(() => {})
let i = 0
beforeEach(() => {
if (i === 0) {
i++
throw new Error('')
}
})
beforeEach(() => {})
afterEach(() => {})
after(() => {})
it('test 1', () => {})
})

View File

@@ -0,0 +1,17 @@
describe('suite 1', { retries: 1 }, () => {
before(() => {})
beforeEach(() => {})
beforeEach(() => {})
afterEach(() => {})
afterEach(() => {})
let i = 0
after(() => {
if (i === 0) {
i++
throw new Error('')
}
})
it('test 1', () => {})
})

View File

@@ -0,0 +1,17 @@
describe('suite 1', { retries: 1 }, () => {
let i = 0
before(() => {
if (i === 0) {
i++
throw new Error('')
}
})
beforeEach(() => {})
beforeEach(() => {})
afterEach(() => {})
afterEach(() => {})
after(() => {})
it('test 1', () => {})
})

View File

@@ -0,0 +1,4 @@
it('visits', { defaultCommandTimeout: 200, retries: 2 }, () => {
cy.visit('cypress/fixtures/dom.html')
cy.get('#button').should('not.be.visible')
})

View File

@@ -0,0 +1,8 @@
describe('suite 1', () => {
after(() => {
throw new Error('Error!')
})
it('test 1', () => {})
it('test 2', () => {})
})

View File

@@ -0,0 +1,7 @@
describe('suite 1', () => {
afterEach(() => {
throw new Error('After each error')
})
it('test 1', () => {})
})

View File

@@ -0,0 +1,7 @@
describe('suite 1', () => {
before(() => {
throw new Error('before')
})
it('test 1', () => {})
})

View File

@@ -0,0 +1,7 @@
describe('suite 1', () => {
beforeEach(() => {
throw new Error('beforeEach')
})
it('test 1', () => {})
})

View File

@@ -0,0 +1,19 @@
describe('suite 1', () => {
before(() => {})
beforeEach(() => {})
after(() => {})
afterEach(() => {})
it('test 1', () => {
throw new Error('T1 fail')
})
// eslint-disable-next-line mocha/no-exclusive-tests
it.only('test 2', () => {
throw new Error('T2 fail')
})
it('test 3', () => {
throw new Error('T3 fail')
})
})

View File

@@ -0,0 +1,11 @@
describe('suite 1', () => {
before(() => {})
beforeEach(() => {})
after(() => {})
afterEach(() => {})
it('test 1', () => {})
// eslint-disable-next-line mocha/no-exclusive-tests
it.only('test 2', () => {})
it('test 3', () => {})
})

View File

@@ -0,0 +1,10 @@
describe('suite 1', { retries: 1 }, () => {
let i = 0
it('test 1', () => {
if (i === 0) {
i++
throw new Error('test 1')
}
})
})

View File

@@ -0,0 +1,3 @@
describe('suite 1', () => {
it('test 1', () => {})
})

View File

@@ -0,0 +1,15 @@
describe('suite 1', { retries: 1 }, () => {
before(() => {})
beforeEach(() => {})
after(() => {})
afterEach(() => {})
let i = 0
it('test 1', () => {
if (i === 0) {
i++
throw new Error('test 1')
}
})
})

View File

@@ -0,0 +1,20 @@
describe('suite 1', { retries: 1 }, () => {
before(() => {})
beforeEach(() => {})
after(() => {})
afterEach(() => {})
it('test 1', () => {})
let i = 0
// eslint-disable-next-line mocha/no-exclusive-tests
it.only('test 2', () => {
if (i === 0) {
i++
throw new Error('test 2')
}
})
it('test 3', () => {})
})

View File

@@ -0,0 +1,10 @@
describe('suite 1', () => {
before(() => {})
beforeEach(() => {})
after(() => {})
afterEach(() => {})
it('test 1', () => {})
it('test 2', () => {})
it('test 3', () => {})
})

View File

@@ -0,0 +1,19 @@
describe('suite 1', { retries: 2 }, () => {
before(() => {})
beforeEach(() => {})
afterEach(() => {})
after(() => {})
it('test 1', () => {})
let i = 0
it('test 2', () => {
if (i <= 1) {
i++
throw new Error('')
}
})
it('test 3', () => {})
})

View File

@@ -0,0 +1,675 @@
<!DOCTYPE html>
<html>
<head>
<title>DOM Fixture</title>
</head>
<body>
<div id="dom">
<style>
/* a small css reset stylesheet */
button,
input,
optgroup,
select,
textarea {
font-family: inherit; /* 1 */
font-size: 100%; /* 1 */
line-height: 1.15; /* 1 */
margin: 2px 0;
/* border: 0px outset; */
border: 2px outset;
padding:0
}
button,
input { /* 1 */
overflow: visible;
}
button {
padding: 1px 6px;
}
button,
select { /* 1 */
text-transform: none;
}
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
body {
display: block;
padding: 0;
margin: 8px;
}
:focus {
background-color: red;
}
#overflow-auto-container {
width: 100px;
height: 50px;
border: 1px solid #999;
overflow: auto;
}
#overflow-auto-container li {
line-height: 33px;
}
#animation-container {
overflow: hidden;
}
#button {
margin: 0;
padding: 0;
width: 100px;
height: 50px;
}
.slidein {
background-color: yellow;
border: 1px solid red;
width: 100px;
float: left;
margin: 0;
animation-timing-function: linear;
animation-name: slidein;
}
@keyframes slidein {
from {
margin-left: 100%;
}
to {
margin-left: 0%;
}
}
[contenteditable] p {
display: block
}
#contenteditable {
min-height: 20px;
min-width: 200px;
}
.content-box {
box-sizing: content-box;
}
#button {
width: 60px;
}
</style>
DOM Fixture
<div id="nested-find">
Nested Find
</div>
<div id="animation-container">
</div>
<div id="specific-contains">
<span>foo</span>
<div id="nested-div">
<span>foo</span>
</div>
</div>
<div id="edge-case-contains">
<div class="badge">5</div>
<div class="badge-multi">1</div>
<div class="badge-multi">2</div>
<div class="badge-multi">3</div>
<div class="badge-multi">4</div>
<span class="count">
<i class="fa fa-icon"></i>
1
</span>
<span class="count">
<i class="fa fa-icon"></i>
2
</span>
<span class="count">
<i class="fa fa-icon"></i>
3
</span>
<span>
<i>25</i>
</span>
<span>25</span>
</div>
<div id="fixed-nav-test">
</div>
<div id="wrapper">
<div id="upper">
<div class="item"><em>New</em> York</div>
</div>
<div id="lower">
<div class="item"><em>New</em> York</div>
</div>
</div>
<ul id="list">
<li class="item">li 0</li>
<li class="item">li 1</li>
<li class="item">li 2</li>
</ul>
<ul id="asdf">
<li>asdf 1</li>
<li>asdf 2</li>
<li>asdf 3</li>
</ul>
<ul>
<li><span>jkl 1</span></li>
<li><span>jkl 2</span></li>
<li><span>jkl 3</span></li>
</ul>
<ul>
<li><span>jkl 4</span></li>
<li><span>jkl 5</span></li>
<li><span>jkl 6</span></li>
</ul>
<div id="tabindex" tabindex="1" style="height: 50px">
el with tabindex
</div>
<button id="button">button</button>
<form id="by-id">
<input id="input" />
<input id="name" />
<input id="age" />
</form>
<form id="by-name">
<input name="name" />
<input name="age" />
<input type="radio" name="gender" value="male" />
<input type="radio" name="gender" value="female" />
<input type="checkbox" name="colors" value="blue" />
<input type="checkbox" name="colors" value="green" />
<input type="checkbox" name="colors" value="red" />
<input type="checkbox" name="dogs" value="husky" />
<input type="checkbox" name="dogs" value="poodle" />
<input type="checkbox" name="dogs" value="on" />
<input type="checkbox" name="dogs" data-no-value="true" />
<input type="tel">
</form>
<form>
<textarea id="comments"></textarea>
</form>
<form id="checkboxes">
<input type="checkbox" name="birds" value="cockatoo" checked="true" />
<input type="checkbox" name="birds" value="amazon" checked="true" />
</form>
<input type="submit" value="input contains submit" />
<form id="input-type-submit">
<a href="#">
<input type="submit" value="click me" />
</a>
</form>
<form id="button-inside-a">
<button>
<span>click button</span>
</button>
</form>
<form action="/timeout?ms=2000" id="click-me">
<input type="hidden" name="ms" value="100" />
<a href="#">
<span>click me 1</span>
</a>
<button>
<span>click me 2</span>
</button>
<input type="submit" value="click me" />
</form>
<form id="complex-contains">
<button>
<a href="#">
<label>nested contains</label>
</a>
</button>
</form>
<form id="button-text">
<a href="#">
<span>click button 1</span>
</a>
<button>
<span>click button 2</span>
</button>
</form>
<form id="anchor-text">
<a href="#">
<span>Home Page</span>
</a>
</form>
<form id="focus">
<div>
<span>
<input type="text" />
<button>focusable button</button>
</span>
</div>
</form>
<div id="form-submits">
<form id="single-input">
<div>
<input name="fname" />
</div>
</form>
<form id="no-buttons-more-than-one-input-allowing-implicit-submission">
<input /> <!-- default is type='text' -->
<input /> <!-- default is type='text' -->
</form>
<!-- https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#implicit-submission -->
<form id="no-buttons-and-only-one-input-allowing-implicit-submission">
<input type="text" />
<input type="hidden" />
</form>
<form id="one-button-type-button">
<input name="fname" />
<input name="lname" />
<button type="button">submit me</button>
</form>
<form id="multiple-inputs-and-input-submit">
<input name="fname" />
<input name="lname" />
<input type="submit" value="submit me" />
</form>
<form id="multiple-inputs-and-button-submit">
<input name="fname" />
<input name="lname" />
<button type="button">button</button>
<button type="submit">submit me</button>
</form>
<form id="multiple-inputs-and-reset-and-submit-buttons">
<input name="fname" />
<input name="lname" />
<button type="reset">reset</button>
<button type="submit">submit me</button>
</form>
<form id="multiple-inputs-and-button-with-no-type">
<input name="fname" />
<input name="lname" />
<button>submit me</button>
</form>
<form id="multiple-inputs-and-other-type-buttons-and-button-with-no-type">
<input name="fname" />
<input name="lname" />
<button type='button'>button</button>
<button type='reset'>reset</button>
<button>submit me</button>
</form>
<form id="multiple-inputs-and-multiple-submits">
<input name="fname" />
<input name="lname" />
<button>submit me</button>
<input type="submit" value ="submit me2" />
</form>
<form id="readonly">
<!-- All values are valid readonly -->
<input id="readonly-attr" readonly />
<input id="readonly-readonly" readonly="readonly" />
<input id="readonly-empty-str" readonly="" />
<!-- Although not strictly part of spec, Chrome respects any string -->
<input id="readonly-str" readonly="abc" />
<!-- readonly doesn't enforce on non typeable inputs -->
<input type="checkbox" id="readonly-checkbox" readonly />
<input type="submit" id="readonly-submit" value="readonly click" readonly />
<select name="hunter" readonly>
<option value="gon-val" readonly>gon</option>
</select>
</form>
</div>
<div id="input-types">
<input id="input-with-value" value="foo" />
<input id="input-without-value" />
<input id="number-with-value" type="number" value="12" />
<input id="number-without-value" type="number" />
<input id="email-with-value" type="email" value="brian@foo.c" />
<input id="email-without-value" type="email" />
<input id="password-with-value" type="password" value="pass" />
<input id="password-without-value" type="password" />
<input id="date-with-value" type="date" value="2016-01-01" />
<input id="date-without-value" type="date" />
<input id="month-with-value" type="month" value="2017-05" />
<input id="month-without-value" type="month" />
<input id="week-with-value" type="week" value="2017-W05" />
<input id="week-without-value" type="week" />
<input id="time-with-value" type="time" value="01:23:45" />
<input id="time-without-value" type="time" />
<input id="tel-with-value" type="tel" value="678-999-8212" />
<input id="text-with-value" type="text" value="foo" />
<input id="datetime-with-value" type="datetime" value="2018-01-01T01:00" />
<input id="datetime-local-with-value" type="datetime-local" value="2018-01-01T01:00" />
<input id="search-with-value" type="search" value="query" />
<input id="url-with-value" type="url" value="cool.biz" />
<div contenteditable="true"><br></div>
<textarea></textarea>
<input id="bad-type" type="asdf" />
</div>
<table id="table">
<thead>
header
</thead>
<tbody>
<tr>
<td>cell</td>
</tr>
<tbody>
</table>
<div id="sequential-clicks">
<a href="#">1</a>
<a href="#">2</a>
<a href="#">3</a>
<a href="#">4</a>
<a href="#">5</a>
</div>
<select id="select-maps" name="maps">
<option value="de_dust2">dust2</option>
<option value="de_aztec">inferno</option>
<option value="de_nuke">nuke</option>
<option value="de_train">train</option>
<option value="cs_italy">
italy
<br>
</option>
</select>
<select name="foods">
<option value="Japanese">Ramen</option>
<option value="Ramen">Japanese</option>
</select>
<select name="names">
<option value="bm">brian m</option>
<option value="ss">sam s</option>
<option value="bm">bryan m</option>
</select>
<select name="movies" multiple="multiple">
<option value="apoc">Apocalypse Now</option>
<option value="thc">The Human Condition</option>
<option value="br">Blade Runner</option>
<option value="2001">2001: A Space Odyssey</option>
<option value="co">Clockwork Orange</option>
<option value="twbb">There Will Be Blood</option>
<option value="gone&nbsp;with&nbsp;the&nbsp;wind">Gone with the Wind</option>
</select>
<select name="disabled" disabled>
<option value="foo">foo</option>
<option value="bar">bar</option>
</select>
<fieldset disabled>
<select name="fielset-disabled">
<option value="foo">foo</option>
<option value="bar">bar</option>
</select>
</fieldset>
<select name="optgroup-disabled">
<optgroup label="foobar" disabled>
<option value="foo">foo</option>
<option value="bar">bar</option>
</optgroup>
</select>
<select name="opt-disabled">
<option value="foo">foo</option>
<option disabled value="bar">bar</option>
</select>
<select name="starwars">
<optgroup label="Light Side">
<option value="luke">Luke</option>
<option value="leia">Leia</option>
<option value="rey">Rey</option>
</optgroup>
<optgroup label="Dark Side">
<option value="vader">Vader</option>
<option value="palpatine">Palpatine</option>
<option value="jarjar">Jar Jar</option>
</optgroup>
</select>
<select name="startrek-same">
<option value="same">Kirk</option>
<option value="same">Spock</option>
<option value="same">Uhura</option>
<option value="same">Sulu</option>
</select>
<select name="startrek-some-same">
<option value="different">Kirk</option>
<option value="same">Spock</option>
<option value="same">Uhura</option>
<option value="same">Sulu</option>
</select>
<div id="contenteditable" contenteditable="true">
content should be editable
</div>
<div id="invisible" style="display: none;">i am not visible</div>
<button class="content-box">content-box</button>
<div id="svgs">
<svg data-cy="line" width="300" height="200">
<line x1="50" y1="50" x2="250" y2="150" style="stroke:red; stroke-width:20;" />
</svg>
<svg tabindex="0" data-cy="rect" width="300" height="200">
<rect x="50" y="50" width="200" height="100" style="fill:orange; stroke:black; stroke-width:3;" />
</svg>
<svg data-cy="circle" width="300" height="200">
<circle cx="150" cy="100" r="70" style="fill:lime; stroke:black; stroke-width:3;" />
</svg>
</div>
<canvas id="canvas"></canvas>
<div id="contains-multiple-filter-match">
<ul class="k-group k-treeview-lines" role="group">
<li role="treeitem" class="k-item k-first" data-uid="82e99592-f200-4ae3-b96d-cbbd53cfd1a1">
<div class="k-top treeview-parent-wrapper">
<span class="k-icon k-minus" role="presentation"></span>
<span class="k-in">
<div class="treeview-parent pull-left">
Assessments
</div>
<div class="pull-right">
1 Folder Type
</div>
</span>
</div>
<ul class="k-group" role="group" style="display: block;">
<li role="treeitem" class="k-item k-last" data-uid="d0fd09f6-b1fd-4155-b4aa-bee3890f0029">
<div class="k-bot">
<span class="k-in">
<span class="square-small" style="background-color: #589d46;"></span>
Inspection
</span>
</div>
</li>
</ul>
</li>
<li role="treeitem" class="k-item k-last" data-uid="8a691b97-98b9-475b-b54f-50d8441e3a78">
<div class="k-bot treeview-parent-wrapper">
<span class="k-icon k-minus" role="presentation"></span>
<span class="k-in">
<div class="treeview-parent pull-left">
Folders
</div>
<div class="pull-right">
2 Folder Types
</div>
</span>
</div>
<ul class="k-group" role="group" style="display: block;">
<li role="treeitem" class="k-item active" data-uid="bfc39753-ff6b-4237-a53c-e62c4161ba99" aria-selected="true" id="treeview_tv_active">
<div class="k-top">
<span class="k-state-selected k-in">
<span class="square-small" style="background-color: #705092;"></span>
Maintenance
</span>
</div>
</li>
<li role="treeitem" class="k-item k-last" data-uid="27acc697-71da-4c19-a755-78f93931303b">
<div class="k-bot">
<span class="k-in">
<span class="square-small" style="background-color: #000000;"></span>
Quality Control
</span>
</div>
</li>
</ul>
</li>
</ul>
</div>
<foobarbazquux>custom element</foobarbazquux>
<div id="ajax-get-container">
<button id="get-json">get ajax</button>
</div>
<span id="not-hidden">my hidden content</span>
<div id="three-buttons">
<button>1</button>
<button>2</button>
<button>3</button>
</div>
<ul id="overflow-auto-container">
<li>foo</li>
<li>bar</li>
<li>baz</li>
<li>quux</li>
</ul>
<div id="overflow-link" style="width:260px"><p>
this is some text that will <span class="wrapped" style="color:rgb(0, 0, 192)">wrap to a newline</span>
and cause the click to miss
</p>
</div>
<div style="width: 30px; line-height: 2;">
<a href="#" id="overflow-link-width">
<i style="display: inline-block;"></i>
foofoofoofoofoo bar
</a>
</div>
<span id="opacity-0" style="opacity: 0;">opacity 0</span>
<input type="checkbox" style="opacity: 0;" name="opacity" value="val" />
<div style="opacity: 0;">
<span id="opacity-0-parent">parent opacity 0</span>
</div>
<div style="height:25px; width:25px; overflow: hidden;">
<span id="opacity-0-hidden" style="opacity: 0; margin-left: 30px;">opacity 0</span>
</div>
<div id="massively-long-div" style="height: 500px; width: 200px; background-color: gray;"></div>
<div id="form-header-region">
<div>
<div id="header" class="navbar navbar-green">
<div class="navbar-header">
<ul>
<li>
<span id="homebutton" data-js="done" class="btn btn-no-bg full-screen-btn pull-left navbar-btn" style="display: block;"><i class="fa fa-arrow-circle-o-left"></i> Done</span>
</li>
<li>
<span id="backbutton" data-js="back" class="btn btn-no-bg full-screen-btn pull-left navbar-btn" style="display: none;"><i class="fa fa-caret-left"></i> Back</span>
</li>
</ul>
<div class="form-template-name-wrapper">
<div class="form-template-name">
EyeProtection -
129083
</div>
</div>
</div>
</div>
</div>
</div>
<div id="diff-els-1">
<a href="">Sakura</a>
<a href="">Naruto</a>
<button>Sarada</button>
<button>Boruto</button>
</div>
<div>
iframe:<br>
<iframe id="iframe" src="/fixtures/generic.html"></iframe>
</div>
<div id="does-not-wrap-input">Text</div>
<div id="input-wrap">
<input style="width:100%" />
</div>
<div>
Cross domain iframe:<br>
<iframe id="iframe-cross-domain" src="http://localhost:3501/fixtures/generic.html"></iframe>
</div>
</div>
</body>
</html>

View File

@@ -12212,7 +12212,7 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1:
dependencies:
color-convert "^1.9.0"
ansi-styles@^4.0.0, ansi-styles@^4.1.0:
ansi-styles@^4.0.0, ansi-styles@^4.1.0, ansi-styles@^4.2.1:
version "4.3.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
@@ -19311,6 +19311,14 @@ disparity@3.0.0:
ansi-styles "^4.1.0"
diff "^4.0.1"
disparity@^3.0.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/disparity/-/disparity-3.2.0.tgz#7198eaf7a873a130f8098c93061c1df8934500f2"
integrity sha512-8cl9ouncFYE7OQsYwJNiy2e15S0xN80X1Jj/N/YkoiM+VGWSyg1YzPToecKyYx2DQiJapt5IC8yi43GW23TUHQ==
dependencies:
ansi-styles "^4.2.1"
diff "^4.0.2"
dmg-builder@22.13.1:
version "22.13.1"
resolved "https://registry.yarnpkg.com/dmg-builder/-/dmg-builder-22.13.1.tgz#5a77655e691ad7e5d28fbf008c68e819e0e2bd69"