@revoloo/cypress6
Version:
Cypress.io end to end testing tool
229 lines (179 loc) • 5.97 kB
text/typescript
import _ from 'lodash'
import { action, computed, observable } from 'mobx'
import { FileDetails } from '@packages/ui-components'
import Attempt from '../attempts/attempt-model'
import Err from '../errors/err-model'
import { HookProps } from '../hooks/hook-model'
import Runnable, { RunnableProps } from '../runnables/runnable-model'
import { CommandProps } from '../commands/command-model'
import { AgentProps } from '../agents/agent-model'
import { RouteProps } from '../routes/route-model'
import { RunnablesStore, LogProps } from '../runnables/runnables-store'
export type TestState = 'active' | 'failed' | 'pending' | 'passed' | 'processing'
export type UpdateTestCallback = () => void
export interface TestProps extends RunnableProps {
state: TestState | null
err?: Err
isOpen?: boolean
agents?: Array<AgentProps>
commands?: Array<CommandProps>
routes?: Array<RouteProps>
hooks: Array<HookProps>
prevAttempts?: Array<TestProps>
currentRetry: number
retries?: number
final?: boolean
invocationDetails?: FileDetails
}
export interface UpdatableTestProps {
id: TestProps['id']
state?: TestProps['state']
err?: TestProps['err']
hookId?: string
failedFromHookId?: string
isOpen?: TestProps['isOpen']
currentRetry?: TestProps['currentRetry']
retries?: TestProps['retries']
}
export default class Test extends Runnable {
type = 'test'
_callbackAfterUpdate: UpdateTestCallback | null = null
hooks: HookProps[]
invocationDetails?: FileDetails
attempts: Attempt[] = []
_isOpen: boolean | null = null
isOpenWhenActive: Boolean | null = null
_isFinished = false
constructor (props: TestProps, level: number, private store: RunnablesStore) {
super(props, level)
this.invocationDetails = props.invocationDetails
this.hooks = [...props.hooks, {
hookId: props.id.toString(),
hookName: 'test body',
invocationDetails: props.invocationDetails,
}, {
hookId: `${props.id.toString()}-studio`,
hookName: 'studio commands',
isStudio: true,
}]
_.each(props.prevAttempts || [], (attempt) => this._addAttempt(attempt))
this._addAttempt(props)
}
get isLongRunning () {
return _.some(this.attempts, (attempt: Attempt) => {
return attempt.isLongRunning
})
}
get isOpen () {
if (this._isOpen === null) {
return Boolean(this.state === 'failed'
|| this.isLongRunning
|| this.isActive && (this.hasMultipleAttempts || this.isOpenWhenActive)
|| this.store.hasSingleTest)
}
return this._isOpen
}
get state () {
return this.lastAttempt ? this.lastAttempt.state : 'active'
}
get err () {
return this.lastAttempt ? this.lastAttempt.err : new Err({})
}
get lastAttempt () {
return _.last(this.attempts) as Attempt
}
get hasMultipleAttempts () {
return this.attempts.length > 1
}
get hasRetried () {
return this.state === 'passed' && this.hasMultipleAttempts
}
// TODO: make this an enum with states: 'QUEUED, ACTIVE, INACTIVE'
get isActive (): boolean {
return _.some(this.attempts, { isActive: true })
}
get currentRetry () {
return this.attempts.length - 1
}
get studioIsNotEmpty () {
return this._withAttempt(this.currentRetry, (attempt: Attempt) => {
return attempt.studioIsNotEmpty
})
}
isLastAttempt (attemptModel: Attempt) {
return this.lastAttempt === attemptModel
}
addLog = (props: LogProps) => {
return this._withAttempt(props.testCurrentRetry || this.currentRetry, (attempt: Attempt) => {
return attempt.addLog(props)
})
}
updateLog (props: LogProps) {
this._withAttempt(props.testCurrentRetry || this.currentRetry, (attempt: Attempt) => {
attempt.updateLog(props)
})
}
removeLog (props: LogProps) {
this._withAttempt(props.testCurrentRetry || this.currentRetry, (attempt: Attempt) => {
attempt.removeLog(props)
})
}
start (props: TestProps) {
let attempt = this.getAttemptByIndex(props.currentRetry)
if (!attempt) {
attempt = this._addAttempt(props)
}
attempt.start()
}
update (props: UpdatableTestProps, cb: UpdateTestCallback) {
if (props.isOpen != null) {
this.setIsOpenWhenActive(props.isOpen)
if (this.isOpen !== props.isOpen) {
this._callbackAfterUpdate = cb
return
}
}
if (props.err || props.state) {
this._withAttempt(this.currentRetry, (attempt: Attempt) => {
attempt.update(props)
})
}
cb()
}
// this is called to sync up the command log UI for the sake of
// screenshots, so we only ever need to open the last attempt
setIsOpenWhenActive (isOpen: boolean) {
this.isOpenWhenActive = isOpen
}
callbackAfterUpdate () {
if (this._callbackAfterUpdate) {
this._callbackAfterUpdate()
this._callbackAfterUpdate = null
}
}
finish (props: UpdatableTestProps) {
this._isFinished = !(props.retries && props.currentRetry) || props.currentRetry >= props.retries
this._withAttempt(props.currentRetry || 0, (attempt: Attempt) => {
attempt.finish(props)
})
}
getAttemptByIndex (attemptIndex: number) {
if (attemptIndex >= this.attempts.length) return
return this.attempts[attemptIndex || 0]
}
commandMatchingErr () {
return this.lastAttempt.commandMatchingErr()
}
_addAttempt = (props: TestProps) => {
props.invocationDetails = this.invocationDetails
props.hooks = this.hooks
const attempt = new Attempt(props, this)
this.attempts.push(attempt)
return attempt
}
_withAttempt<T> (attemptIndex: number, cb: (attempt: Attempt) => T) {
const attempt = this.getAttemptByIndex(attemptIndex)
if (attempt) return cb(attempt)
return null
}
}