This commit is contained in:
Emily Rohrbough
2022-08-29 16:16:18 -05:00
parent 71dff8c56b
commit 192ae6ce75
14 changed files with 134 additions and 88 deletions

View File

@@ -1,10 +1,10 @@
import Bluebird from 'bluebird'
import { EventEmitter } from 'events'
import type { MobxRunnerStore } from '@packages/app/src/store/mobx-runner-store'
import type { RunState } from '@packages/types/src/driver'
import type MobX from 'mobx'
import type { LocalBusEmitsMap, LocalBusEventMap, DriverToLocalBus, SocketToDriverMap } from './event-manager-types'
import type { AutomationElementId, FileDetails } from '@packages/types'
import type { ServerRunState, CachedTestState, AutomationElementId, FileDetails, ReporterStartInfo, ReporterRunState } from '@packages/types'
import { logger } from './logger'
import type { Socket } from '@packages/socket/lib/browser'
@@ -405,46 +405,50 @@ export class EventManager {
return Cypress.initialize({
$autIframe,
onSpecReady: () => {
// get the current runnable in case we reran mid-test due to a visit
// to a new domain
this.ws.emit('get:existing:run:state', (state: RunState = {}) => {
// get the current runnable states and cached test state
// in case we reran mid-test due to a visit to a new domain
this.ws.emit('get:cached:test:state', (runState: ServerRunState = {}, testState: CachedTestState) => {
if (runState === null) {
runState = {}
}
if (!Cypress.runner) {
// the tests have been reloaded
return
}
this.studioRecorder.initialize(config, state)
this.studioRecorder.initialize(config, runState)
const runnables = Cypress.runner.normalizeAll(state.tests)
const runnables = Cypress.runner.normalizeAll(runState.tests)
const run = () => {
performance.mark('initialize-end')
performance.measure('initialize', 'initialize-start', 'initialize-end')
this._runDriver(state)
this._runDriver(runState, testState)
}
this.reporterBus.emit('runnables:ready', runnables)
if (state?.numLogs) {
Cypress.runner.setNumLogs(state.numLogs)
if (runState?.numLogs) {
Cypress.runner.setNumLogs(runState.numLogs)
}
if (state.startTime) {
Cypress.runner.setStartTime(state.startTime)
if (runState.startTime) {
Cypress.runner.setStartTime(runState.startTime)
}
if (config.isTextTerminal && !state.currentId) {
if (config.isTextTerminal && !runState.currentId) {
// we are in run mode and it's the first load
// store runnables in backend and maybe send to dashboard
return this.ws.emit('set:runnables:and:maybe:record:tests', runnables, run)
}
if (state.currentId) {
if (runState.currentId) {
// if we have a currentId it means
// we need to tell the Cypress to skip
// ahead to that test
Cypress.runner.resumeAtTest(state.currentId, state.emissions)
Cypress.runner.resumeAtTest(runState.currentId, runState.emissions)
}
return run()
@@ -470,7 +474,7 @@ export class EventManager {
}
return new Bluebird((resolve) => {
this.reporterBus.emit('reporter:collect:run:state', (reporterState) => {
this.reporterBus.emit('reporter:collect:run:state', (reporterState: ReporterRunState) => {
resolve({
...reporterState,
studio: this.studioRecorder.state,
@@ -726,23 +730,23 @@ export class EventManager {
window.top.addEventListener('message', crossOriginOnMessageRef, false)
}
_runDriver (state) {
_runDriver (runState: ServerRunState, testState: CachedTestState) {
performance.mark('run-s')
Cypress.run(() => {
Cypress.run(testState, () => {
performance.mark('run-e')
performance.measure('run', 'run-s', 'run-e')
})
this.reporterBus.emit('reporter:start', {
startTime: Cypress.runner.getStartTime(),
numPassed: state.passed,
numFailed: state.failed,
numPending: state.pending,
autoScrollingEnabled: state.autoScrollingEnabled,
isSpecsListOpen: state.isSpecsListOpen,
scrollTop: state.scrollTop,
numPassed: runState.passed,
numFailed: runState.failed,
numPending: runState.pending,
autoScrollingEnabled: runState.autoScrollingEnabled,
isSpecsListOpen: runState.isSpecsListOpen,
scrollTop: runState.scrollTop,
studioActive: this.studioRecorder.hasRunnableId,
})
} as ReporterStartInfo)
}
stop () {

View File

@@ -9,6 +9,8 @@ import { LogUtils, Log } from '../../cypress/log'
import { bothUrlsMatchAndOneHasHash } from '../navigation'
import { $Location, LocationObject } from '../../cypress/location'
import type { RunState, ReporterRunState, StudioRecorderState } from '@packages/types'
import debugFn from 'debug'
const debug = debugFn('cypress:driver:navigation')
@@ -1160,7 +1162,7 @@ export default (Commands, Cypress, cy, state, config) => {
// tell our backend we're changing origins
// TODO: add in other things we want to preserve
// state for like scrollTop
let s: Record<string, any> = {
let s: RunState = {
currentId: id,
tests: Cypress.runner.getTestsState(),
startTime: Cypress.runner.getStartTime(),
@@ -1172,8 +1174,12 @@ export default (Commands, Cypress, cy, state, config) => {
s.pending = Cypress.runner.countByTestState(s.tests, 'pending')
s.numLogs = LogUtils.countLogsByTests(s.tests)
type ReporterRunRecords = ReporterRunState & {
studio?: StudioRecorderState
}
return Cypress.action('cy:collect:run:state')
.then((a = []) => {
.then((a: ReporterRunRecords[] = []) => {
// merge all the states together holla'
s = _.reduce(a, (memo, obj) => {
return _.extend(memo, obj)

View File

@@ -1,6 +1,6 @@
import _ from 'lodash'
import { $Location } from '../../../cypress/location'
import type { ServerSessionData } from '@packages/types'
import {
getCurrentOriginStorage,
setPostMessageLocalStorage,
@@ -8,7 +8,10 @@ import {
} from './utils'
type ActiveSessions = Cypress.Commands.Session.ActiveSessions
type SessionData = Cypress.Commands.Session.SessionData
type SessionData = ServerSessionData & {
setup: () => null
validate: () => null
}
const getLogProperties = (displayName) => {
return {

View File

@@ -1,9 +1,16 @@
import _ from 'lodash'
import $ from 'jquery'
import { $Location } from '../../../cypress/location'
import Bluebird from 'bluebird'
import { $Location } from '../../../cypress/location'
type SessionData = Cypress.Commands.Session.SessionData
import type { ServerSessionData } from '@packages/types'
export type SessionData = ServerSessionData & {
setup: () => null
validate: () => null
}
export type ActiveSessions = Record<string, SessionData>
const getSessionDetails = (sessState: SessionData) => {
return {

View File

@@ -40,6 +40,8 @@ import $Keyboard from './cy/keyboard'
import * as resolvers from './cypress/resolvers'
import { PrimaryOriginCommunicator, SpecBridgeCommunicator } from './cross-origin/communicator'
import type { CachedTestState } from '@packages/types'
const debug = debugFn('cypress:driver:cypress')
declare global {
@@ -264,11 +266,6 @@ class $Cypress {
// @ts-ignore
this.ProxyLogging = new ProxyLogging(this)
this.backend('get:cached:test:state')
.then((cachedTestState: Record<string, any>) => {
this.state(cachedTestState)
})
return this.action('cypress:config', config)
}
@@ -281,11 +278,13 @@ class $Cypress {
}
}
run (fn) {
run (cachedTestState: CachedTestState, fn) {
if (!this.runner) {
$errUtils.throwErrByPath('miscellaneous.no_runner')
}
this.state(cachedTestState)
return this.runner.run(fn)
}

View File

@@ -3,12 +3,7 @@ import { action, computed, observable } from 'mobx'
import { TestState } from '../test/test-model'
import { IntervalID } from '../lib/types'
export interface StatsStoreStartInfo {
startTime: string
numPassed?: number
numFailed?: number
numPending?: number
}
import type { StatsStoreStartInfo } from '@packages/types'
const defaults = {
numPassed: 0,

View File

@@ -2,11 +2,13 @@ import { EventEmitter } from 'events'
import { action } from 'mobx'
import appState, { AppState } from './app-state'
import runnablesStore, { RunnablesStore, RootRunnable, LogProps } from '../runnables/runnables-store'
import statsStore, { StatsStore, StatsStoreStartInfo } from '../header/stats-store'
import statsStore, { StatsStore } from '../header/stats-store'
import scroller, { Scroller } from './scroller'
import TestModel, { UpdatableTestProps, UpdateTestCallback, TestProps } from '../test/test-model'
import { SessionProps } from '../sessions/sessions-model'
import type { ReporterStartInfo, ReporterRunState } from '@packages/types'
const localBus = new EventEmitter()
interface InitEvent {
@@ -33,16 +35,7 @@ export interface Events {
__off: (() => void)
}
interface StartInfo extends StatsStoreStartInfo {
autoScrollingEnabled: boolean
scrollTop: number
studioActive: boolean
}
type CollectRunStateCallback = (arg: {
autoScrollingEnabled: boolean
scrollTop: number
}) => void
type CollectRunStateCallback = (arg: ReporterRunState) => void
const events: Events = {
appState,
@@ -93,7 +86,7 @@ const events: Events = {
}
}))
runner.on('reporter:start', action('start', (startInfo: StartInfo) => {
runner.on('reporter:start', action('start', (startInfo: ReporterStartInfo) => {
appState.temporarilySetAutoScrolling(startInfo.autoScrollingEnabled)
runnablesStore.setInitialScrollTop(startInfo.scrollTop)
appState.setStudioActive(startInfo.studioActive)

View File

@@ -193,7 +193,7 @@ export class StudioRecorder {
return this._previousMouseEvent && $(el).is(this._previousMouseEvent.element)
}
@action initialize = (config, state) => {
@action initialize = (config, state = {}) => {
const { studio } = state
if (studio) {

View File

@@ -1,15 +1,4 @@
import type { CyCookie } from './browsers/cdp_automation'
interface SessionData {
cookies: CyCookie[]
id: string
cacheAcrossSpecs: boolean
localStorage: Array<Record<string, string>>
sessionStorage: Array<Record<string, string>>
setup: string
}
export type StoredSessions = Record<string, SessionData>
import type { ServerSessionData, StoredSessions } from '@packages/types'
type State = {
globalSessions: StoredSessions
@@ -21,7 +10,7 @@ const state: State = {
specSessions: {},
}
export function saveSession (data: SessionData): void {
export function saveSession (data: ServerSessionData): void {
if (!data.id) throw new Error('session data had no id')
if (data.cacheAcrossSpecs) {
@@ -37,7 +26,7 @@ export function getActiveSessions (): StoredSessions {
return state.globalSessions
}
export function getSession (id: string): SessionData {
export function getSession (id: string): ServerSessionData {
const session = state.globalSessions[id] || state.specSessions[id]
if (!session) throw new Error(`session with id "${id}" not found`)

View File

@@ -23,6 +23,8 @@ import path from 'path'
import { getCtx } from '@packages/data-context'
import { handleGraphQLSocketRequest } from '@packages/graphql/src/makeGraphQLServer'
import type { RunState, CachedTestState } from '@packages/types'
type StartListeningCallbacks = {
onSocketConnection: (socket: any) => void
}
@@ -131,7 +133,7 @@ export class SocketBase {
options,
callbacks: StartListeningCallbacks,
) {
let existingState = null
let runState: RunState = {}
_.defaults(options, {
socketId: null,
@@ -146,7 +148,7 @@ export class SocketBase {
onChromiumRun () {},
onReloadBrowser () {},
checkForAppErrors () {},
onSavedStateChanged () {},
onSavedAppStateChanged () {},
onTestFileChange () {},
onCaptureVideoFrames () {},
})
@@ -368,8 +370,7 @@ export class SocketBase {
})
}
// retry for up to data.timeout
// or 1 second
// retry for up to data.timeout or 1 second
return Bluebird
.try(tryConnected)
.timeout(data.timeout != null ? data.timeout : 1000)
@@ -394,7 +395,7 @@ export class SocketBase {
switch (eventName) {
case 'preserve:run:state':
existingState = args[0]
runState = args[0]
return null
case 'resolve:url': {
@@ -437,10 +438,6 @@ export class SocketBase {
return session.clearSessions(args[0])
case 'get:session':
return session.getSession(args[0])
case 'get:cached:test:state':
return {
activeSessions: session.getActiveSessions(),
}
case 'reset:cached:test:state':
cookieJar.removeAllCookies()
session.clearSessions()
@@ -471,20 +468,22 @@ export class SocketBase {
})
})
socket.on('get:existing:run:state', (cb) => {
const s = existingState
socket.on('get:cached:test:state', (cb: (runState: RunState | null, testState: CachedTestState) => void) => {
const s = runState
if (s) {
existingState = null
return cb(s)
runState = null
}
return cb()
const cachedTestState: CachedTestState = {
activeSessions: session.getActiveSessions(),
}
return cb(runState, cachedTestState)
})
socket.on('save:app:state', (state, cb) => {
options.onSavedStateChanged(state)
options.onSavedAppStateChanged(state)
// we only use the 'ack' here in tests
if (cb) {

View File

@@ -1,8 +1,8 @@
export interface RunState {
startTime?: number
currentId?: number
currentId?: number | null
emissions?: Emissions
tests?: unknown
tests?: Record<string, Cypress.ObjectLike>
passed?: number
failed?: number
pending?: number
@@ -13,3 +13,23 @@ export interface Emissions {
started: Record<string, boolean>
ended: Record<string, boolean>
}
interface HtmlWebStorage {
origin: string
value: Record<string, any>
}
export interface ServerSessionData {
id: string
cacheAcrossSpecs: boolean
cookies: Cypress.Cookie[] | null
localStorage: Array<HtmlWebStorage> | null
sessionStorage: Array<HtmlWebStorage> | null
setup: string
}
export type StoredSessions = Record<string, ServerSessionData>
export interface CachedTestState {
activeSessions: StoredSessions
}

View File

@@ -28,6 +28,8 @@ export {
RESOLVED_FROM,
} from './config'
export * from './reporter'
export * from './server'
export * from './util'

View File

@@ -0,0 +1,23 @@
export interface StudioRecorderState {
suiteId?: string
testId?: string
url?: string
}
export interface ReporterRunState {
autoScrollingEnabled?: boolean
scrollTop?: number
}
export interface StatsStoreStartInfo {
startTime: string
numPassed?: number
numFailed?: number
numPending?: number
}
export interface ReporterStartInfo extends StatsStoreStartInfo {
autoScrollingEnabled: boolean
scrollTop: number
studioActive: boolean
}

View File

@@ -1,5 +1,7 @@
import type { FoundBrowser } from './browser'
import type { PlatformName } from './platform'
import type { RunState } from './driver'
import type { ReporterRunState, StudioRecorderState } from './reporter'
export interface LaunchOpts {
browser?: FoundBrowser
@@ -83,3 +85,7 @@ export interface AddProject {
open?: boolean | null
path: string
}
export type ServerRunState = RunState & ReporterRunState & {
studio?: StudioRecorderState
}