Files
cypress/packages/server/lib/open_project.ts
T
Lachlan Miller 792980ac12 feat: support specPattern, deprecate integrationFolder and componentFolder (#19319)
* use new specPattern API

* remove projectApi.findSpecs

* do not require integration folder

* support --spec

* support --spec

* remove old code

* remove old code

* nuke old code

* no appvetor

* update

* correct url for ct

* work on migrating launchpad

* update ct spec url

* types

* types

* dead code

* remove old endpiont

* revert back circle.yml

* update missing config

* delete spec util

* update config

* update config

* config again

* update spec pattern

* updated vue config

* update spec pattern for ui components

* update config for vite dev server

* update snapshots

* update config

* update design system config

* fix spec pattern in reporter

* update default

* update deprecated spec snapshots

* update system tests

* update run mode output

* revert changes

* lint

* remove scaffold tests

* update angular

* fix CT

* update circle yml

* fix system tests for ct

* fix tests

* work on server unit tests

* patch package

* patch package again

* update test

* try not to rely on config async loading too much

* normalize specPattern to array

* update snapshot

* use base name

* deal with react-scripts later

* update snapshot

* hacky way to update snapshots

* new hack to update snapshots

* trying again

* hacky fix

* ci: snapshots

* ci: snapshots

* snapshots

* mas updates

* update spec API

* fix test

* fix test

* update

* update test

* fix test

* update plugin

* update spec

* webpack optinos

* Update launchpad tests

* fix screenshot paths

* updated snapshot

* change pattern

* guard

* fix smoke test

* patch code coverage

* update percy config

* fix specs

* try updating example project

* update snapshots

* remove old test

* remove snapshot hack

* add back appveyor

* remove old code

* update snapshot

* Fix tests

* wip

* revert snapshot

* reverted all snaps

* remove only

* remove commnet

* remove old code

* reverted file

* lint

* revert video compression spec

* update snapshot

* update spec path logic

* update snap

* updated snap

* snaps

* snaps

* fix spec

* rename ignoreTestFiles to ignoreSpecPattern

* update screenshot dir for runner-ct

* update deprecations

* update

* upate

* fix test

* update snaps

* update snap

* updating snap

* added missing snaps

* updated cypress run mode integration spec

* electron snapshot

* ensure newly scaffold specs are cached

* fix launchpad spec

* types

* update test

* transpile based on spec pattern

* add back example

* remove unnecessary async and nodeVersion

* removed old test

* update spec pattern and add defensive check around platform

* remove unused feature flag

* added tests

* fix react example

* update test

* update config

* fix spec finding in run mode

* fix tests

* fixing specs

* fix switching between specs

* remove invalid test

* increase timeout

Co-authored-by: estrada9166 <estrada9166@hotmail.com>
2022-01-04 14:24:24 +10:00

300 lines
7.3 KiB
TypeScript

import _ from 'lodash'
import la from 'lazy-ass'
import Debug from 'debug'
import Bluebird from 'bluebird'
import assert from 'assert'
import { ProjectBase } from './project-base'
import browsers from './browsers'
import preprocessor from './plugins/preprocessor'
import runEvents from './plugins/run_events'
import * as session from './session'
import { getSpecUrl } from './project_utils'
import errors from './errors'
import type { LaunchOpts, OpenProjectLaunchOptions, InitializeProjectOptions } from '@packages/types'
import { DataContext, getCtx } from '@packages/data-context'
import { autoBindDebug } from '@packages/data-context/src/util'
const debug = Debug('cypress:server:open_project')
export class OpenProject {
projectBase: ProjectBase<any> | null = null
relaunchBrowser: ((...args: unknown[]) => Bluebird<void>) | null = null
constructor () {
return autoBindDebug(this)
}
resetOpenProject () {
this.projectBase = null
this.relaunchBrowser = null
}
reset () {
this.resetOpenProject()
}
getConfig () {
return this.projectBase?.getConfig()
}
getProject () {
return this.projectBase
}
changeUrlToSpec (spec: Cypress.Cypress['spec']) {
if (!this.projectBase) {
return
}
const newSpecUrl = getSpecUrl({
spec,
browserUrl: this.projectBase.cfg.browserUrl,
projectRoot: this.projectBase.projectRoot,
})
this.projectBase.changeToUrl(newSpecUrl)
}
async launch (browser, spec: Cypress.Cypress['spec'], options: LaunchOpts = {
onError: () => undefined,
}) {
this._ctx = getCtx()
if (!this.projectBase && this._ctx.currentProject) {
await this.create(this._ctx.currentProject, {
...this._ctx.modeOptions,
projectRoot: this._ctx.currentProject,
testingType: this._ctx.coreData.currentTestingType!,
}, options)
}
if (!this.projectBase) {
throw Error('Cannot launch runner if projectBase is undefined!')
}
debug('resetting project state, preparing to launch browser %s for spec %o options %o',
browser.name, spec, options)
la(_.isPlainObject(browser), 'expected browser object:', browser)
// reset to reset server and socket state because
// of potential domain changes, request buffers, etc
this.projectBase!.reset()
let url = getSpecUrl({
spec,
browserUrl: this.projectBase.cfg.browserUrl,
projectRoot: this.projectBase.projectRoot,
})
debug('open project url %s', url)
const cfg = this.projectBase.getConfig()
_.defaults(options, {
browsers: cfg.browsers,
userAgent: cfg.userAgent,
proxyUrl: cfg.proxyUrl,
proxyServer: cfg.proxyServer,
socketIoRoute: cfg.socketIoRoute,
chromeWebSecurity: cfg.chromeWebSecurity,
isTextTerminal: cfg.isTextTerminal,
downloadsFolder: cfg.downloadsFolder,
})
// if we don't have the isHeaded property
// then we're in interactive mode and we
// can assume its a headed browser
// TODO: we should clean this up
if (!_.has(browser, 'isHeaded')) {
browser.isHeaded = true
browser.isHeadless = false
}
// set the current browser object on options
// so we can pass it down
options.browser = browser
options.url = url
this.projectBase.setCurrentSpecAndBrowser(spec, browser)
const automation = this.projectBase.getAutomation()
// use automation middleware if its
// been defined here
let am = options.automationMiddleware
if (am) {
automation.use(am)
}
if (!am || !am.onBeforeRequest) {
automation.use({
onBeforeRequest (message, data) {
if (message === 'take:screenshot') {
data.specName = spec.name
return data
}
},
})
}
const afterSpec = () => {
if (!this.projectBase || cfg.isTextTerminal || !cfg.experimentalInteractiveRunEvents) {
return Bluebird.resolve()
}
return runEvents.execute('after:spec', cfg, spec)
}
const { onBrowserClose } = options
options.onBrowserClose = () => {
if (spec && spec.absolute) {
preprocessor.removeFile(spec.absolute, cfg)
}
afterSpec()
.catch((err) => {
this.projectBase?.options.onError?.(err)
})
if (onBrowserClose) {
return onBrowserClose()
}
}
options.onError = this.projectBase.options.onError
this.relaunchBrowser = () => {
debug(
'launching browser: %o, spec: %s',
browser,
spec.relative,
)
return Bluebird.try(() => {
if (!cfg.isTextTerminal && cfg.experimentalInteractiveRunEvents) {
return runEvents.execute('before:spec', cfg, spec)
}
// clear all session data before each spec
session.clearSessions()
})
.then(() => {
// TODO: Stub this so we can detect it being called
if (process.env.CYPRESS_INTERNAL_E2E_TESTING_SELF) {
return
}
return browsers.open(browser, options, automation)
})
}
return this.relaunchBrowser()
}
closeBrowser () {
return browsers.close()
}
closeOpenProjectAndBrowsers () {
this.projectBase?.close().catch((e) => {
this._ctx?.logTraceError(e)
})
this.resetOpenProject()
return this.closeBrowser()
}
close () {
debug('closing opened project')
this.closeOpenProjectAndBrowsers()
}
// close existing open project if it exists, for example
// if you are switching from CT to E2E or vice versa.
// used by launchpad
async closeActiveProject () {
await this.closeOpenProjectAndBrowsers()
}
_ctx?: DataContext
async create (path: string, args: InitializeProjectOptions, options: OpenProjectLaunchOptions) {
this._ctx = getCtx()
debug('open_project create %s', path)
_.defaults(options, {
onReloadBrowser: () => {
if (this.relaunchBrowser) {
return this.relaunchBrowser()
}
return
},
})
if (!_.isUndefined(args.configFile) && !_.isNull(args.configFile)) {
options.configFile = args.configFile
}
options = _.extend({}, args.config, options, { args })
// open the project and return
// the config for the project instance
debug('opening project %s', path)
debug('and options %o', options)
assert(args.testingType)
const testingType = args.testingType === 'component' ? 'component' : 'e2e'
// store the currently open project
this.projectBase = new ProjectBase({
testingType,
projectRoot: path,
options: {
...options,
testingType,
},
})
try {
const cfg = await this.projectBase.initializeConfig()
const specPattern = options.spec || cfg[testingType].specPattern
if (!specPattern) {
throw Error('could not find pattern to load specs')
}
const specs = await this._ctx.project.findSpecs(path, testingType, specPattern)
this._ctx.actions.project.setSpecs(specs)
await this.projectBase.open()
} catch (err: any) {
if (err.isCypressErr && err.portInUse) {
errors.throw(err.type, err.port)
} else {
// rethrow and handle elsewhere
throw (err)
}
}
return this
}
// for testing purposes
__reset () {
this.resetOpenProject()
}
}
export const openProject = new OpenProject()