mirror of
https://github.com/cypress-io/cypress.git
synced 2026-01-04 13:39:52 -06:00
feat(component-testing): changes to the driver and reporter preparing for runner-ct (#14434)
* chore: update driver with component testing * feat: bring ct changes in reporter * test: update script utils test * fix: type issue * test: add test for new ct behavior in driver runScript can now use promises instead of files. Thi test this new behavior * tests(ct): tests for the reporter runable store * fix: remove changes on event handling in driver * build: augment zip size to avoid zip errors * test: add renderin tests for reporter multispec * test: better matcher for runnableHistory Co-authored-by: Jessica Sachs <jess@jessicasachs.io> * test: make the runscripts eval tests clearer * refactor(reporter): main interface instead of type * fix(reporter): runInAction when specRunId changes * refactor(driver): remove restartRunner function Co-authored-by: Jessica Sachs <jess@jessicasachs.io>
This commit is contained in:
committed by
GitHub
parent
74f6db0fb4
commit
dd559d9862
@@ -1,3 +1,4 @@
|
||||
const Promise = require('bluebird')
|
||||
const $scriptUtils = require('@packages/driver/src/cypress/script_utils')
|
||||
const $networkUtils = require('@packages/driver/src/cypress/network_utils')
|
||||
const $sourceMapUtils = require('@packages/driver/src/cypress/source_map_utils')
|
||||
@@ -42,10 +43,19 @@ describe('src/cypress/script_utils', () => {
|
||||
it('evals each script', () => {
|
||||
return $scriptUtils.runScripts(scriptWindow, scripts)
|
||||
.then(() => {
|
||||
expect($sourceMapUtils.extractSourceMap).to.be.calledTwice
|
||||
expect($sourceMapUtils.extractSourceMap).to.be.calledWith(scripts[0], 'the script contents')
|
||||
expect($sourceMapUtils.extractSourceMap).to.be.calledWith(scripts[1], 'the script contents')
|
||||
expect(scriptWindow.eval).to.be.calledTwice
|
||||
expect(scriptWindow.eval).to.be.calledWith('the script contents\n//# sourceURL=http://localhost:3500cypress/integration/script1.js')
|
||||
expect(scriptWindow.eval).to.be.calledWith('the script contents\n//# sourceURL=http://localhost:3500cypress/integration/script2.js')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('#runPromises', () => {
|
||||
it('handles promises and doesnt try to fetch + eval manually', async () => {
|
||||
const scriptsAsPromises = [Promise.resolve(), Promise.resolve()]
|
||||
const result = await $scriptUtils.runScripts({}, scriptsAsPromises)
|
||||
|
||||
expect(result).to.have.length(scriptsAsPromises.length)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -352,7 +352,7 @@ module.exports = (Commands, Cypress, cy, state, config) => {
|
||||
|
||||
Cypress.on('test:before:run:async', () => {
|
||||
// reset any state on the backend
|
||||
Cypress.backend('reset:server:state')
|
||||
return Cypress.backend('reset:server:state')
|
||||
})
|
||||
|
||||
Cypress.on('test:before:run', reset)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const _ = require('lodash')
|
||||
const Promise = require('bluebird')
|
||||
const Bluebird = require('bluebird')
|
||||
|
||||
const $networkUtils = require('./network_utils')
|
||||
const $sourceMapUtils = require('./source_map_utils')
|
||||
@@ -28,13 +28,24 @@ const evalScripts = (specWindow, scripts = []) => {
|
||||
return null
|
||||
}
|
||||
|
||||
const runScripts = (specWindow, scripts) => {
|
||||
return Promise
|
||||
const runScriptsFromUrls = (specWindow, scripts) => {
|
||||
return Bluebird
|
||||
.map(scripts, (script) => fetchScript(specWindow, script))
|
||||
.map(extractSourceMap)
|
||||
.then((scripts) => evalScripts(specWindow, scripts))
|
||||
}
|
||||
|
||||
// Supports either scripts as objects or as async import functions
|
||||
const runScripts = (specWindow, scripts) => {
|
||||
// if scripts contains at least one promise
|
||||
if (scripts.length && typeof scripts[0].then === 'function') {
|
||||
// merge the awaiting of the promises
|
||||
return Bluebird.all(scripts)
|
||||
}
|
||||
|
||||
return runScriptsFromUrls(specWindow, scripts)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
runScripts,
|
||||
}
|
||||
|
||||
@@ -58,6 +58,21 @@ describe('runnables', () => {
|
||||
cy.percySnapshot()
|
||||
})
|
||||
|
||||
it('displays multi-spec reporters', () => {
|
||||
start({ runMode: 'multi', allSpecs: [
|
||||
{
|
||||
relative: 'fizz',
|
||||
},
|
||||
{
|
||||
relative: 'buzz',
|
||||
},
|
||||
] })
|
||||
|
||||
// ensure the page is loaded before taking snapshot
|
||||
cy.contains('buzz').should('be.visible')
|
||||
cy.percySnapshot()
|
||||
})
|
||||
|
||||
it('displays the "No test" error when there are no tests', () => {
|
||||
runnables.suites = []
|
||||
start()
|
||||
|
||||
@@ -234,6 +234,22 @@ describe('runnables store', () => {
|
||||
})
|
||||
})
|
||||
|
||||
context('#setRunningSpec', () => {
|
||||
it('sets the current runnable as the path passed', () => {
|
||||
instance.setRunnables({ tests: [createTest('1')] })
|
||||
instance.setRunningSpec('specPath')
|
||||
expect(instance.runningSpec).to.equal('specPath')
|
||||
})
|
||||
|
||||
it('add the previous path to the spec history', () => {
|
||||
instance.setRunnables({ tests: [createTest('1')] })
|
||||
instance.setRunningSpec('previousSpecPath')
|
||||
instance.setRunningSpec('nextSpecPath')
|
||||
expect(instance.runningSpec).to.equal('nextSpecPath')
|
||||
expect(instance.runnablesHistory['previousSpecPath']).not.to.be.undefined
|
||||
})
|
||||
})
|
||||
|
||||
context('#reset', () => {
|
||||
it('resets flags to default values', () => {
|
||||
instance.setRunnables({ tests: [createTest('1')] })
|
||||
@@ -259,5 +275,12 @@ describe('runnables store', () => {
|
||||
instance.reset()
|
||||
expect(instance.testById('1')).to.be.undefined
|
||||
})
|
||||
|
||||
it('resets runnablesHistory', () => {
|
||||
instance.setRunnables({ tests: [createTest('1')] })
|
||||
instance.setRunningSpec('previous')
|
||||
instance.reset()
|
||||
expect(instance.runnablesHistory).to.be.empty
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -10,13 +10,13 @@ import Controls from './controls'
|
||||
import Stats from './stats'
|
||||
import { StatsStore } from './stats-store'
|
||||
|
||||
interface Props {
|
||||
export interface ReporterHeaderProps {
|
||||
appState: AppState
|
||||
events?: Events
|
||||
statsStore: StatsStore
|
||||
}
|
||||
|
||||
const Header = observer(({ appState, events = defaultEvents, statsStore }: Props) => (
|
||||
const Header = observer(({ appState, events = defaultEvents, statsStore }: ReporterHeaderProps) => (
|
||||
<header>
|
||||
<Tooltip placement='bottom' title={<p>View All Tests <span className='kbd'>F</span></p>} wrapperClassName='focus-tests' className='cy-tooltip'>
|
||||
<button onClick={() => events.emit('focus:tests')}>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { action } from 'mobx'
|
||||
/* global Cypress, JSX */
|
||||
import { action, runInAction } from 'mobx'
|
||||
import { observer } from 'mobx-react'
|
||||
import cs from 'classnames'
|
||||
import PropTypes from 'prop-types'
|
||||
import React, { Component } from 'react'
|
||||
import { render } from 'react-dom'
|
||||
@@ -15,10 +17,10 @@ import scroller, { Scroller } from './lib/scroller'
|
||||
import statsStore, { StatsStore } from './header/stats-store'
|
||||
import shortcuts from './lib/shortcuts'
|
||||
|
||||
import Header from './header/header'
|
||||
import Header, { ReporterHeaderProps } from './header/header'
|
||||
import Runnables from './runnables/runnables'
|
||||
|
||||
export interface ReporterProps {
|
||||
interface BaseReporterProps {
|
||||
appState: AppState
|
||||
autoScrollingEnabled?: boolean
|
||||
runnablesStore: RunnablesStore
|
||||
@@ -27,11 +29,24 @@ export interface ReporterProps {
|
||||
statsStore: StatsStore
|
||||
events: Events
|
||||
error?: RunnablesErrorModel
|
||||
resetStatsOnSpecChange?: boolean
|
||||
renderReporterHeader?: (props: ReporterHeaderProps) => JSX.Element;
|
||||
spec: Cypress.Cypress['spec']
|
||||
/** Used for component testing front-end */
|
||||
specRunId?: string | null
|
||||
}
|
||||
|
||||
export interface SingleReporterProps extends BaseReporterProps{
|
||||
runMode: 'single',
|
||||
}
|
||||
|
||||
export interface MultiReporterProps extends BaseReporterProps{
|
||||
runMode: 'multi',
|
||||
allSpecs: Array<Cypress.Cypress['spec']>
|
||||
}
|
||||
|
||||
@observer
|
||||
class Reporter extends Component<ReporterProps> {
|
||||
class Reporter extends Component<SingleReporterProps | MultiReporterProps> {
|
||||
static propTypes = {
|
||||
autoScrollingEnabled: PropTypes.bool,
|
||||
error: PropTypes.shape({
|
||||
@@ -52,6 +67,7 @@ class Reporter extends Component<ReporterProps> {
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
runMode: 'single',
|
||||
appState,
|
||||
events,
|
||||
runnablesStore,
|
||||
@@ -60,27 +76,63 @@ class Reporter extends Component<ReporterProps> {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { appState } = this.props
|
||||
const {
|
||||
appState,
|
||||
runMode,
|
||||
runnablesStore,
|
||||
scroller,
|
||||
error,
|
||||
events,
|
||||
statsStore,
|
||||
renderReporterHeader = (props: ReporterHeaderProps) => <Header {...props}/>,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<div className='reporter'>
|
||||
<Header appState={appState} statsStore={this.props.statsStore} />
|
||||
<Runnables
|
||||
appState={appState}
|
||||
error={this.props.error}
|
||||
runnablesStore={this.props.runnablesStore}
|
||||
scroller={this.props.scroller}
|
||||
spec={this.props.spec}
|
||||
/>
|
||||
<div className={cs('reporter', { multiSpecs: runMode === 'multi' })}>
|
||||
{renderReporterHeader({ appState, statsStore })}
|
||||
{this.props.runMode === 'single' ? (
|
||||
<Runnables
|
||||
appState={appState}
|
||||
error={error}
|
||||
runnablesStore={runnablesStore}
|
||||
scroller={scroller}
|
||||
spec={this.props.spec}
|
||||
/>
|
||||
) : this.props.allSpecs.map((spec) => (
|
||||
<Runnables
|
||||
key={spec.relative}
|
||||
appState={appState}
|
||||
error={error}
|
||||
runnablesStore={runnablesStore}
|
||||
scroller={scroller}
|
||||
spec={spec}
|
||||
/>
|
||||
))}
|
||||
|
||||
<ForcedGcWarning
|
||||
appState={appState}
|
||||
events={this.props.events}/>
|
||||
events={events}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// this hook will only trigger if we switch spec file at runtime
|
||||
// it never happens in normal e2e but can happen in component-testing mode
|
||||
componentDidUpdate (newProps: BaseReporterProps) {
|
||||
this.props.runnablesStore.setRunningSpec(this.props.spec.relative)
|
||||
|
||||
if (
|
||||
this.props.resetStatsOnSpecChange &&
|
||||
this.props.specRunId !== newProps.specRunId
|
||||
) {
|
||||
runInAction('reporter:stats:reset', () => {
|
||||
this.props.statsStore.reset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const { appState, autoScrollingEnabled, runnablesStore, runner, scroller, statsStore } = this.props
|
||||
const { spec, appState, autoScrollingEnabled, runnablesStore, runner, scroller, statsStore } = this.props
|
||||
|
||||
action('set:scrolling', () => {
|
||||
appState.setAutoScrolling(autoScrollingEnabled)
|
||||
@@ -97,6 +149,7 @@ class Reporter extends Component<ReporterProps> {
|
||||
|
||||
shortcuts.start()
|
||||
EQ.init()
|
||||
this.props.runnablesStore.setRunningSpec(spec.relative)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
@@ -108,7 +161,7 @@ declare global {
|
||||
interface Window {
|
||||
Cypress: any
|
||||
state: AppState
|
||||
render: ((props: Partial<ReporterProps>) => void)
|
||||
render: ((props: Partial<BaseReporterProps>) => void)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,17 @@ type TestOrSuite<T> = T extends TestProps ? TestProps : SuiteProps
|
||||
class RunnablesStore {
|
||||
@observable isReady = defaults.isReady
|
||||
@observable runnables: RunnableArray = []
|
||||
/**
|
||||
* Stores a list of all the runables files where the reporter
|
||||
* has passed without any specific order.
|
||||
*
|
||||
* key: spec FilePath
|
||||
* content: RunableArray
|
||||
*/
|
||||
@observable runnablesHistory: Record<string, RunnableArray> = {}
|
||||
|
||||
runningSpec: string | null = null
|
||||
|
||||
hasTests: boolean = false
|
||||
hasSingleTest: boolean = false
|
||||
|
||||
@@ -195,9 +206,23 @@ class RunnablesStore {
|
||||
})
|
||||
|
||||
this.runnables = []
|
||||
this.runnablesHistory = {}
|
||||
this._tests = {}
|
||||
this._runnablesQueue = []
|
||||
}
|
||||
|
||||
@action
|
||||
setRunningSpec (specPath: string) {
|
||||
const previousSpec = this.runningSpec
|
||||
|
||||
this.runningSpec = specPath
|
||||
|
||||
if (!previousSpec || previousSpec === specPath) {
|
||||
return
|
||||
}
|
||||
|
||||
this.runnablesHistory[previousSpec] = this.runnables
|
||||
}
|
||||
}
|
||||
|
||||
export { RunnablesStore }
|
||||
|
||||
@@ -345,3 +345,17 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.reporter.multiSpecs {
|
||||
overflow-y: auto;
|
||||
|
||||
.container {
|
||||
flex-grow: 0;
|
||||
overflow-y: unset;
|
||||
|
||||
.wrap {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,14 +42,14 @@ const RunnablesList = observer(({ runnables }: RunnablesListProps) => (
|
||||
</div>
|
||||
))
|
||||
|
||||
interface RunnablesContentProps {
|
||||
export interface RunnablesContentProps {
|
||||
runnablesStore: RunnablesStore
|
||||
specPath: string
|
||||
error?: RunnablesErrorModel
|
||||
}
|
||||
|
||||
const RunnablesContent = observer(({ runnablesStore, specPath, error }: RunnablesContentProps) => {
|
||||
const { isReady, runnables } = runnablesStore
|
||||
const { isReady, runnables, runnablesHistory } = runnablesStore
|
||||
|
||||
if (!isReady) {
|
||||
return <Loading />
|
||||
@@ -61,10 +61,16 @@ const RunnablesContent = observer(({ runnablesStore, specPath, error }: Runnable
|
||||
error = noTestsError(specPath)
|
||||
}
|
||||
|
||||
return error ? <RunnablesError error={error} /> : <RunnablesList runnables={runnables} />
|
||||
if (error) {
|
||||
return <RunnablesError error={error} />
|
||||
}
|
||||
|
||||
const isRunning = specPath === runnablesStore.runningSpec
|
||||
|
||||
return <RunnablesList runnables={isRunning ? runnables : runnablesHistory[specPath]} />
|
||||
})
|
||||
|
||||
interface RunnablesProps {
|
||||
export interface RunnablesProps {
|
||||
error?: RunnablesErrorModel
|
||||
runnablesStore: RunnablesStore
|
||||
spec: Cypress.Cypress['spec']
|
||||
|
||||
@@ -73,7 +73,7 @@ const checkZipSize = function (zipPath) {
|
||||
const zipSize = filesize(stats.size, { round: 0 })
|
||||
|
||||
console.log(`zip file size ${zipSize}`)
|
||||
const MAX_ALLOWED_SIZE_MB = os.platform() === 'win32' ? 245 : 200
|
||||
const MAX_ALLOWED_SIZE_MB = os.platform() === 'win32' ? 265 : 218
|
||||
const MAX_ZIP_FILE_SIZE = megaBytes(MAX_ALLOWED_SIZE_MB)
|
||||
|
||||
if (stats.size > MAX_ZIP_FILE_SIZE) {
|
||||
|
||||
Reference in New Issue
Block a user