mirror of
https://github.com/cypress-io/cypress.git
synced 2026-04-29 19:41:16 -05:00
chore(server): reduce coupling between methods to instantiate a project (#17200)
* wip * wip: use async/await * slowly refactor * slowly refactor * use typescript * use async/await * update spec * do not use tap because bluebird sucks * refactor * update * remove bluebird * update * remove then * change to async/await to make it easier to reason about * remove more bluebird * no more bluebird * remov ts ignore * do not tap * refactor * simplify cfg * move static methods to separate file * update snapshot * comment out test * simplifying options merging * update tests * change order of options * move code out of project-base * update tests * update tests * type reporter * simplify onWatchSettings function * sep starting websockets and watching settings. reduce need to pass large cfg object * move util functions out of project class * move tests to new file * update test * move code around * update tests * remove need to pass options to getConfig * fix tests * separate get and init config * set browser warnings in initializeConfig * move Ct specific concerns to same function * do not pass config to initializeSpecStore * remove onOpen function * improve types * update typing errors * update types * types * fix types * update tests * update tests * fix tests * update tests * comment back in test * update methods * update types * add defensive code against config.clientRoute * do not use async * update tests * use same baseUrl for proxy regardless * remove comment * revert change to baseUrl
This commit is contained in:
Vendored
+1
@@ -2763,6 +2763,7 @@ declare namespace Cypress {
|
||||
clientRoute: string
|
||||
configFile: string
|
||||
cypressEnv: string
|
||||
devServerPublicPathRoute: string
|
||||
isNewProject: boolean
|
||||
isTextTerminal: boolean
|
||||
morgan: boolean
|
||||
|
||||
@@ -20,7 +20,6 @@ export async function start ({ webpackConfig: userWebpackConfig, template, optio
|
||||
debug('User did not pass in any webpack configuration')
|
||||
}
|
||||
|
||||
// @ts-expect-error ?? devServerPublicPathRoute is not a valid option of Cypress.Config
|
||||
const { projectRoot, devServerPublicPathRoute, isTextTerminal } = options.config
|
||||
|
||||
const webpackConfig = await makeWebpackConfig(userWebpackConfig || {}, {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { NetworkProxy } from '@packages/proxy'
|
||||
import { handle, serve, serveChunk } from './runner-ct'
|
||||
import xhrs from '@packages/server/lib/controllers/xhrs'
|
||||
import { SpecsStore } from '@packages/server/lib/specs-store'
|
||||
import { ProjectBase } from '../../server/lib/project-base'
|
||||
import { Cfg, ProjectBase } from '../../server/lib/project-base'
|
||||
import { getPathToDist } from '@packages/resolve-dist'
|
||||
|
||||
const debug = Debug('cypress:server:routes')
|
||||
@@ -14,7 +14,7 @@ const debug = Debug('cypress:server:routes')
|
||||
export interface InitializeRoutes {
|
||||
app: Express
|
||||
specsStore: SpecsStore
|
||||
config: Record<string, any>
|
||||
config: Cfg
|
||||
project: ProjectBase<any>
|
||||
nodeProxy: httpProxy
|
||||
networkProxy: NetworkProxy
|
||||
@@ -73,11 +73,17 @@ export const createRoutes = ({
|
||||
})
|
||||
})
|
||||
|
||||
const clientRoute = config.clientRoute
|
||||
|
||||
if (!clientRoute) {
|
||||
throw Error(`clientRoute is required. Received ${clientRoute}`)
|
||||
}
|
||||
|
||||
app.all('/__cypress/xhrs/*', (req, res, next) => {
|
||||
xhrs.handle(req, res, config, next)
|
||||
})
|
||||
|
||||
app.get(config.clientRoute, (req, res) => {
|
||||
app.get(clientRoute, (req, res) => {
|
||||
debug('Serving Cypress front-end by requested URL:', req.url)
|
||||
|
||||
serve(req, res, {
|
||||
@@ -88,13 +94,13 @@ export const createRoutes = ({
|
||||
})
|
||||
|
||||
// enables runner-ct to make a dynamic import
|
||||
app.get(`${config.clientRoute}ctChunk-*`, (req, res) => {
|
||||
app.get(`${clientRoute}ctChunk-*`, (req, res) => {
|
||||
debug('Serving Cypress front-end chunk by requested URL:', req.url)
|
||||
|
||||
serveChunk(req, res, { config })
|
||||
})
|
||||
|
||||
app.get(`${config.clientRoute}vendors~ctChunk-*`, (req, res) => {
|
||||
app.get(`${clientRoute}vendors~ctChunk-*`, (req, res) => {
|
||||
debug('Serving Cypress front-end vendor chunk by requested URL:', req.url)
|
||||
|
||||
serveChunk(req, res, { config })
|
||||
|
||||
@@ -3,11 +3,12 @@ import httpsProxy from '@packages/https-proxy'
|
||||
import { OpenServerOptions, ServerBase } from '@packages/server/lib/server-base'
|
||||
import appData from '@packages/server/lib/util/app_data'
|
||||
import { SocketCt } from './socket-ct'
|
||||
import { Cfg } from '../../server/lib/project-base'
|
||||
|
||||
type WarningErr = Record<string, any>
|
||||
|
||||
export class ServerCt extends ServerBase<SocketCt> {
|
||||
open (config: Record<string, any> = {}, options: OpenServerOptions) {
|
||||
open (config: Cfg, options: OpenServerOptions) {
|
||||
return super.open(config, { ...options, projectType: 'ct' })
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ Cannot find module '/foo/bar/.projects/e2e/node_modules/module-does-not-exist'
|
||||
Require stack:
|
||||
- lib/reporter.js
|
||||
- lib/project-base.ts
|
||||
- lib/project_static.ts
|
||||
- lib/modes/run.js
|
||||
- lib/modes/run-e2e.js
|
||||
- lib/modes/index.js
|
||||
|
||||
@@ -22,7 +22,7 @@ export class Automation {
|
||||
private cookies: Cookies
|
||||
private screenshot: { capture: (data: any, automate: any) => any }
|
||||
|
||||
constructor (cyNamespace: string, cookieNamespace: string, screenshotsFolder: string, public onBrowserPreRequest: OnBrowserPreRequest) {
|
||||
constructor (cyNamespace?: string, cookieNamespace?: string, screenshotsFolder?: string | false, public onBrowserPreRequest?: OnBrowserPreRequest) {
|
||||
this.requests = {}
|
||||
|
||||
// set the middleware
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import screenshots from '../screenshots'
|
||||
|
||||
export function Screenshot (screenshotsFolder: string) {
|
||||
export function Screenshot (screenshotsFolder?: string | false) {
|
||||
return {
|
||||
capture (data, automate) {
|
||||
return screenshots.capture(data, automate)
|
||||
|
||||
@@ -179,7 +179,7 @@ export class CdpAutomation {
|
||||
originalResourceType: params.type,
|
||||
}
|
||||
|
||||
this.automation.onBrowserPreRequest(browserPreRequest)
|
||||
this.automation.onBrowserPreRequest?.(browserPreRequest)
|
||||
}
|
||||
|
||||
private getAllCookies = (filter: CyCookieFilter) => {
|
||||
|
||||
@@ -16,7 +16,8 @@ const open = require('../util/open')
|
||||
const user = require('../user')
|
||||
const errors = require('../errors')
|
||||
const Updater = require('../updater')
|
||||
const { ProjectBase } = require('../project-base')
|
||||
const ProjectStatic = require('../project_static')
|
||||
|
||||
const openProject = require('../open_project')
|
||||
const ensureUrl = require('../util/ensure-url')
|
||||
const chromePolicyCheck = require('../util/chrome_policy_check')
|
||||
@@ -236,37 +237,37 @@ const handleEvent = function (options, bus, event, id, type, arg) {
|
||||
return send(null)
|
||||
|
||||
case 'get:orgs':
|
||||
return ProjectBase.getOrgs()
|
||||
return ProjectStatic.getOrgs()
|
||||
.then(send)
|
||||
.catch(sendErr)
|
||||
|
||||
case 'get:projects':
|
||||
return ProjectBase.getPathsAndIds()
|
||||
return ProjectStatic.getPathsAndIds()
|
||||
.then(send)
|
||||
.catch(sendErr)
|
||||
|
||||
case 'get:project:statuses':
|
||||
return ProjectBase.getProjectStatuses(arg)
|
||||
return ProjectStatic.getProjectStatuses(arg)
|
||||
.then(send)
|
||||
.catch(sendErr)
|
||||
|
||||
case 'get:project:status':
|
||||
return ProjectBase.getProjectStatus(arg)
|
||||
return ProjectStatic.getProjectStatus(arg)
|
||||
.then(send)
|
||||
.catch(sendErr)
|
||||
|
||||
case 'get:dashboard:projects':
|
||||
return ProjectBase.getDashboardProjects()
|
||||
return ProjectStatic.getDashboardProjects()
|
||||
.then(send)
|
||||
.catch(sendErr)
|
||||
|
||||
case 'add:project':
|
||||
return ProjectBase.add(arg, options)
|
||||
return ProjectStatic.add(arg, options)
|
||||
.then(send)
|
||||
.catch(sendErr)
|
||||
|
||||
case 'remove:project':
|
||||
return ProjectBase.remove(arg)
|
||||
return ProjectStatic.remove(arg)
|
||||
.then(() => {
|
||||
return send(arg)
|
||||
})
|
||||
@@ -331,12 +332,12 @@ const handleEvent = function (options, bus, event, id, type, arg) {
|
||||
.catch(sendErr)
|
||||
|
||||
case 'setup:dashboard:project':
|
||||
return openProject.createCiProject(arg)
|
||||
return ProjectStatic.createCiProject(arg, options.projectRoot)
|
||||
.then(send)
|
||||
.catch(sendErr)
|
||||
|
||||
case 'set:project:id':
|
||||
return openProject.writeProjectId(arg)
|
||||
return ProjectStatic.writeProjectId(arg, options.projectRoot)
|
||||
.then(send)
|
||||
.catch(sendErr)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ const logSymbols = require('log-symbols')
|
||||
|
||||
const recordMode = require('./record')
|
||||
const errors = require('../errors')
|
||||
const { ProjectBase } = require('../project-base')
|
||||
const ProjectStatic = require('../project_static')
|
||||
const Reporter = require('../reporter')
|
||||
const browserUtils = require('../browsers')
|
||||
const openProject = require('../open_project')
|
||||
@@ -606,20 +606,13 @@ const openProjectCreate = (projectRoot, socketId, args) => {
|
||||
onError: args.onError,
|
||||
}
|
||||
|
||||
return openProject
|
||||
.create(projectRoot, args, options)
|
||||
.catch({ portInUse: true }, (err) => {
|
||||
// TODO: this needs to move to call exitEarly
|
||||
// so we record the failure in CI
|
||||
return errors.throw('PORT_IN_USE_LONG', err.port)
|
||||
})
|
||||
return openProject.create(projectRoot, args, options)
|
||||
}
|
||||
|
||||
const createAndOpenProject = function (socketId, options) {
|
||||
const { projectRoot, projectId } = options
|
||||
|
||||
return ProjectBase
|
||||
.ensureExists(projectRoot, options)
|
||||
return ProjectStatic.ensureExists(projectRoot, options)
|
||||
.then(() => {
|
||||
// open this project without
|
||||
// adding it to the global cache
|
||||
@@ -973,10 +966,14 @@ module.exports = {
|
||||
return project.onWarning
|
||||
}
|
||||
|
||||
debug('browser launched')
|
||||
|
||||
return openProject.launch(browser, spec, browserOpts)
|
||||
},
|
||||
|
||||
navigateToNextSpec (spec) {
|
||||
debug('navigating to next spec')
|
||||
|
||||
return openProject.changeUrlToSpec(spec)
|
||||
},
|
||||
|
||||
@@ -1083,10 +1080,7 @@ module.exports = {
|
||||
// If we do not launch the browser,
|
||||
// we tell it that we are ready
|
||||
// to receive the next spec
|
||||
return this.navigateToNextSpec(options.spec)
|
||||
.tap(() => {
|
||||
debug('navigated to next spec')
|
||||
})
|
||||
return Promise.resolve(this.navigateToNextSpec(options.spec))
|
||||
}
|
||||
|
||||
return Promise.join(
|
||||
@@ -1094,10 +1088,7 @@ module.exports = {
|
||||
.tap(() => {
|
||||
debug('socket connected', { socketId })
|
||||
}),
|
||||
this.launchBrowser(options)
|
||||
.tap(() => {
|
||||
debug('browser launched')
|
||||
}),
|
||||
this.launchBrowser(options),
|
||||
)
|
||||
.timeout(browserTimeout)
|
||||
.catch(Promise.TimeoutError, (err) => {
|
||||
|
||||
+135
-110
@@ -9,6 +9,8 @@ const browsers = require('./browsers')
|
||||
const specsUtil = require('./util/specs')
|
||||
const preprocessor = require('./plugins/preprocessor')
|
||||
const runEvents = require('./plugins/run_events')
|
||||
const { getSpecUrl } = require('./project_utils')
|
||||
const errors = require('./errors')
|
||||
|
||||
const moduleFactory = () => {
|
||||
let openProject = null
|
||||
@@ -34,29 +36,31 @@ const moduleFactory = () => {
|
||||
|
||||
componentSpecsWatcher: null,
|
||||
|
||||
reset: tryToCall('reset'),
|
||||
reset: () => openProject?.reset(),
|
||||
|
||||
getConfig: tryToCall('getConfig'),
|
||||
|
||||
createCiProject: tryToCall('createCiProject'),
|
||||
|
||||
writeProjectId: tryToCall('writeProjectId'),
|
||||
|
||||
getRecordKeys: tryToCall('getRecordKeys'),
|
||||
|
||||
getRuns: tryToCall('getRuns'),
|
||||
|
||||
requestAccess: tryToCall('requestAccess'),
|
||||
|
||||
emit: tryToCall('emit'),
|
||||
|
||||
getProject () {
|
||||
return openProject
|
||||
},
|
||||
|
||||
changeUrlToSpec (spec) {
|
||||
return openProject.getSpecUrl(spec.absolute, spec.specType)
|
||||
.then((newSpecUrl) => openProject.changeToUrl(newSpecUrl))
|
||||
const newSpecUrl = getSpecUrl({
|
||||
absoluteSpecPath: spec.absolute,
|
||||
specType: spec.specType,
|
||||
browserUrl: openProject.cfg.browserUrl,
|
||||
integrationFolder: openProject.cfg.integrationFolder,
|
||||
componentFolder: openProject.cfg.componentFolder,
|
||||
projectRoot: openProject.projectRoot,
|
||||
})
|
||||
|
||||
openProject.changeToUrl(newSpecUrl)
|
||||
},
|
||||
|
||||
launch (browser, spec, options = {}) {
|
||||
@@ -67,106 +71,113 @@ const moduleFactory = () => {
|
||||
|
||||
// reset to reset server and socket state because
|
||||
// of potential domain changes, request buffers, etc
|
||||
return this.reset()
|
||||
.then(() => openProject.getSpecUrl(spec.absolute, spec.specType))
|
||||
.then((url) => {
|
||||
debug('open project url %s', url)
|
||||
this.reset()
|
||||
|
||||
return openProject.getConfig()
|
||||
.then((cfg) => {
|
||||
_.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,
|
||||
const url = getSpecUrl({
|
||||
absoluteSpecPath: spec.absolute,
|
||||
specType: spec.specType,
|
||||
browserUrl: openProject.cfg.browserUrl,
|
||||
integrationFolder: openProject.cfg.integrationFolder,
|
||||
componentFolder: openProject.cfg.componentFolder,
|
||||
projectRoot: openProject.projectRoot,
|
||||
})
|
||||
|
||||
debug('open project url %s', url)
|
||||
|
||||
return openProject.getConfig()
|
||||
.then((cfg) => {
|
||||
_.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
|
||||
|
||||
openProject.setCurrentSpecAndBrowser(spec, browser)
|
||||
|
||||
const automation = openProject.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 (!openProject || cfg.isTextTerminal || !cfg.experimentalInteractiveRunEvents) return Promise.resolve()
|
||||
|
||||
return runEvents.execute('after:spec', cfg, spec)
|
||||
}
|
||||
|
||||
const { onBrowserClose } = options
|
||||
|
||||
options.onBrowserClose = () => {
|
||||
if (spec && spec.absolute) {
|
||||
preprocessor.removeFile(spec.absolute, cfg)
|
||||
}
|
||||
|
||||
afterSpec(cfg, spec)
|
||||
.catch((err) => {
|
||||
openProject.options.onError(err)
|
||||
})
|
||||
|
||||
// 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
|
||||
if (onBrowserClose) {
|
||||
return onBrowserClose()
|
||||
}
|
||||
}
|
||||
|
||||
// set the current browser object on options
|
||||
// so we can pass it down
|
||||
options.browser = browser
|
||||
options.url = url
|
||||
options.onError = openProject.options.onError
|
||||
|
||||
openProject.setCurrentSpecAndBrowser(spec, browser)
|
||||
relaunchBrowser = () => {
|
||||
debug(
|
||||
'launching browser: %o, spec: %s',
|
||||
browser,
|
||||
spec.relative,
|
||||
)
|
||||
|
||||
const automation = openProject.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 (!openProject || cfg.isTextTerminal || !cfg.experimentalInteractiveRunEvents) return Promise.resolve()
|
||||
|
||||
return runEvents.execute('after:spec', cfg, spec)
|
||||
}
|
||||
|
||||
const { onBrowserClose } = options
|
||||
|
||||
options.onBrowserClose = () => {
|
||||
if (spec && spec.absolute) {
|
||||
preprocessor.removeFile(spec.absolute, cfg)
|
||||
return Promise.try(() => {
|
||||
if (!cfg.isTextTerminal && cfg.experimentalInteractiveRunEvents) {
|
||||
return runEvents.execute('before:spec', cfg, spec)
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
return browsers.open(browser, options, automation)
|
||||
})
|
||||
}
|
||||
|
||||
afterSpec(cfg, spec)
|
||||
.catch((err) => {
|
||||
openProject.options.onError(err)
|
||||
})
|
||||
|
||||
if (onBrowserClose) {
|
||||
return onBrowserClose()
|
||||
}
|
||||
}
|
||||
|
||||
options.onError = openProject.options.onError
|
||||
|
||||
relaunchBrowser = () => {
|
||||
debug(
|
||||
'launching browser: %o, spec: %s',
|
||||
browser,
|
||||
spec.relative,
|
||||
)
|
||||
|
||||
return Promise.try(() => {
|
||||
if (!cfg.isTextTerminal && cfg.experimentalInteractiveRunEvents) {
|
||||
return runEvents.execute('before:spec', cfg, spec)
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
return browsers.open(browser, options, automation)
|
||||
})
|
||||
}
|
||||
|
||||
return relaunchBrowser()
|
||||
})
|
||||
return relaunchBrowser()
|
||||
})
|
||||
},
|
||||
|
||||
@@ -323,15 +334,8 @@ const moduleFactory = () => {
|
||||
return this.closeOpenProjectAndBrowsers()
|
||||
},
|
||||
|
||||
create (path, args = {}, options = {}) {
|
||||
async create (path, args = {}, options = {}) {
|
||||
debug('open_project create %s', path)
|
||||
debug('and options %o', options)
|
||||
|
||||
// store the currently open project
|
||||
openProject = new Project.ProjectBase({
|
||||
projectType: args.testingType === 'component' ? 'ct' : 'e2e',
|
||||
projectRoot: path,
|
||||
})
|
||||
|
||||
_.defaults(options, {
|
||||
onReloadBrowser: () => {
|
||||
@@ -352,8 +356,29 @@ const moduleFactory = () => {
|
||||
debug('opening project %s', path)
|
||||
debug('and options %o', options)
|
||||
|
||||
return openProject.open({ ...options, testingType: args.testingType })
|
||||
.return(this)
|
||||
// store the currently open project
|
||||
openProject = new Project.ProjectBase({
|
||||
projectType: args.testingType === 'component' ? 'ct' : 'e2e',
|
||||
projectRoot: path,
|
||||
options: {
|
||||
...options,
|
||||
testingType: args.testingType,
|
||||
},
|
||||
})
|
||||
|
||||
try {
|
||||
await openProject.initializeConfig()
|
||||
await openProject.open()
|
||||
} catch (err) {
|
||||
if (err.isCypressErr && err.portInUse) {
|
||||
errors.throw(err.type, err.port)
|
||||
} else {
|
||||
// rethrow and handle elsewhere
|
||||
throw (err)
|
||||
}
|
||||
}
|
||||
|
||||
return this
|
||||
},
|
||||
|
||||
// for testing purposes
|
||||
|
||||
+463
-679
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,200 @@
|
||||
import Debug from 'debug'
|
||||
import commitInfo from '@cypress/commit-info'
|
||||
import _ from 'lodash'
|
||||
|
||||
import logger from './logger'
|
||||
import api from './api'
|
||||
import cache from './cache'
|
||||
import user from './user'
|
||||
import keys from './util/keys'
|
||||
import settings from './util/settings'
|
||||
import { ProjectBase } from './project-base'
|
||||
|
||||
const debug = Debug('cypress:server:project_static')
|
||||
|
||||
export async function getOrgs () {
|
||||
const authToken = await user.ensureAuthToken()
|
||||
|
||||
return api.getOrgs(authToken)
|
||||
}
|
||||
|
||||
export function paths () {
|
||||
return cache.getProjectRoots()
|
||||
}
|
||||
|
||||
export async function getPathsAndIds () {
|
||||
const projectRoots: string[] = await cache.getProjectRoots()
|
||||
|
||||
// this assumes that the configFile for a cached project is 'cypress.json'
|
||||
// https://git.io/JeGyF
|
||||
return Promise.all(projectRoots.map(async (projectRoot) => {
|
||||
return {
|
||||
path: projectRoot,
|
||||
id: await settings.id(projectRoot),
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
export async function getDashboardProjects () {
|
||||
const authToken = await user.ensureAuthToken()
|
||||
|
||||
debug('got auth token: %o', { authToken: keys.hide(authToken) })
|
||||
|
||||
return api.getProjects(authToken)
|
||||
}
|
||||
|
||||
export function _mergeDetails (clientProject, project) {
|
||||
return _.extend({}, clientProject, project, { state: 'VALID' })
|
||||
}
|
||||
|
||||
export function _mergeState (clientProject, state) {
|
||||
return _.extend({}, clientProject, { state })
|
||||
}
|
||||
|
||||
export async function _getProject (clientProject, authToken) {
|
||||
debug('get project from api', clientProject.id, clientProject.path)
|
||||
|
||||
try {
|
||||
const project = await api.getProject(clientProject.id, authToken)
|
||||
|
||||
debug('got project from api')
|
||||
|
||||
return _mergeDetails(clientProject, project)
|
||||
} catch (err) {
|
||||
debug('failed to get project from api', err.statusCode)
|
||||
switch (err.statusCode) {
|
||||
case 404:
|
||||
// project doesn't exist
|
||||
return _mergeState(clientProject, 'INVALID')
|
||||
case 403:
|
||||
// project exists, but user isn't authorized for it
|
||||
return _mergeState(clientProject, 'UNAUTHORIZED')
|
||||
default:
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function getProjectStatuses (clientProjects: any = []) {
|
||||
debug(`get project statuses for ${clientProjects.length} projects`)
|
||||
|
||||
const authToken = await user.ensureAuthToken()
|
||||
|
||||
debug('got auth token: %o', { authToken: keys.hide(authToken) })
|
||||
|
||||
const projects = (await api.getProjects(authToken) || [])
|
||||
|
||||
debug(`got ${projects.length} projects`)
|
||||
const projectsIndex = _.keyBy(projects, 'id')
|
||||
|
||||
return Promise.all(_.map(clientProjects, (clientProject) => {
|
||||
debug('looking at', clientProject.path)
|
||||
// not a CI project, just mark as valid and return
|
||||
if (!clientProject.id) {
|
||||
debug('no project id')
|
||||
|
||||
return _mergeState(clientProject, 'VALID')
|
||||
}
|
||||
|
||||
const project = projectsIndex[clientProject.id]
|
||||
|
||||
if (project) {
|
||||
debug('found matching:', project)
|
||||
|
||||
// merge in details for matching project
|
||||
return _mergeDetails(clientProject, project)
|
||||
}
|
||||
|
||||
debug('did not find matching:', project)
|
||||
|
||||
// project has id, but no matching project found
|
||||
// check if it doesn't exist or if user isn't authorized
|
||||
return _getProject(clientProject, authToken)
|
||||
}))
|
||||
}
|
||||
|
||||
export async function getProjectStatus (clientProject) {
|
||||
debug('get project status for client id %s at path %s', clientProject.id, clientProject.path)
|
||||
|
||||
if (!clientProject.id) {
|
||||
debug('no project id')
|
||||
|
||||
return Promise.resolve(_mergeState(clientProject, 'VALID'))
|
||||
}
|
||||
|
||||
const authToken = await user.ensureAuthToken()
|
||||
|
||||
debug('got auth token: %o', { authToken: keys.hide(authToken) })
|
||||
|
||||
return _getProject(clientProject, authToken)
|
||||
}
|
||||
|
||||
export function remove (path) {
|
||||
return cache.removeProject(path)
|
||||
}
|
||||
|
||||
export async function add (path, options) {
|
||||
// don't cache a project if a non-default configFile is set
|
||||
// https://git.io/JeGyF
|
||||
if (settings.configFile(options) !== 'cypress.json') {
|
||||
return Promise.resolve({ path })
|
||||
}
|
||||
|
||||
try {
|
||||
await cache.insertProject(path)
|
||||
const id = await getId(path)
|
||||
|
||||
return {
|
||||
id,
|
||||
path,
|
||||
}
|
||||
} catch (e) {
|
||||
return { path }
|
||||
}
|
||||
}
|
||||
|
||||
export function getId (path) {
|
||||
return new ProjectBase({ projectRoot: path, projectType: 'e2e', options: {} }).getProjectId()
|
||||
}
|
||||
|
||||
export function ensureExists (path, options) {
|
||||
// is there a configFile? is the root writable?
|
||||
return settings.exists(path, options)
|
||||
}
|
||||
|
||||
export async function writeProjectId (id: string, projectRoot: string) {
|
||||
const attrs = { projectId: id }
|
||||
|
||||
logger.info('Writing Project ID', _.clone(attrs))
|
||||
|
||||
// TODO: We need to set this
|
||||
// this.generatedProjectIdTimestamp = new Date()
|
||||
|
||||
await settings.write(projectRoot, attrs)
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
interface ProjectDetails {
|
||||
projectName: string
|
||||
orgId: string | null
|
||||
public: boolean
|
||||
}
|
||||
|
||||
export async function createCiProject (projectDetails: ProjectDetails, projectRoot: string) {
|
||||
debug('create CI project with projectDetails %o projectRoot %s', projectDetails, projectRoot)
|
||||
|
||||
const authToken = await user.ensureAuthToken()
|
||||
const remoteOrigin = await commitInfo.getRemoteOrigin(projectRoot)
|
||||
|
||||
debug('found remote origin at projectRoot %o', {
|
||||
remoteOrigin,
|
||||
projectRoot,
|
||||
})
|
||||
|
||||
const newProject = await api.createProject(projectDetails, remoteOrigin, authToken)
|
||||
|
||||
await writeProjectId(newProject.id, projectRoot)
|
||||
|
||||
return newProject
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
import Debug from 'debug'
|
||||
import path from 'path'
|
||||
|
||||
import settings from './util/settings'
|
||||
import errors from './errors'
|
||||
import { fs } from './util/fs'
|
||||
import { escapeFilenameInUrl } from './util/escape_filename'
|
||||
|
||||
const debug = Debug('cypress:server:project_utils')
|
||||
|
||||
const multipleForwardSlashesRe = /[^:\/\/](\/{2,})/g
|
||||
const backSlashesRe = /\\/g
|
||||
|
||||
const normalizeSpecUrl = (browserUrl: string, specUrl: string) => {
|
||||
const replacer = (match: string) => match.replace('//', '/')
|
||||
|
||||
return [
|
||||
browserUrl,
|
||||
'#/tests',
|
||||
escapeFilenameInUrl(specUrl),
|
||||
].join('/')
|
||||
.replace(multipleForwardSlashesRe, replacer)
|
||||
}
|
||||
|
||||
const getPrefixedPathToSpec = ({
|
||||
integrationFolder,
|
||||
componentFolder,
|
||||
projectRoot,
|
||||
type,
|
||||
pathToSpec,
|
||||
}: {
|
||||
integrationFolder: string
|
||||
componentFolder: string
|
||||
projectRoot: string
|
||||
type: string
|
||||
pathToSpec: string
|
||||
}) => {
|
||||
type ??= 'integration'
|
||||
|
||||
// for now hard code the 'type' as integration
|
||||
// but in the future accept something different here
|
||||
|
||||
// strip out the integration folder and prepend with "/"
|
||||
// example:
|
||||
//
|
||||
// /Users/bmann/Dev/cypress-app/.projects/cypress/integration
|
||||
// /Users/bmann/Dev/cypress-app/.projects/cypress/integration/foo.js
|
||||
//
|
||||
// becomes /integration/foo.js
|
||||
|
||||
const folderToUse = type === 'integration' ? integrationFolder : componentFolder
|
||||
|
||||
// To avoid having invalid urls from containing backslashes,
|
||||
// we normalize specUrls to posix by replacing backslash by slash
|
||||
// Indeed, path.realtive will return something different on windows
|
||||
// than on posix systems which can lead to problems
|
||||
const url = `/${path.join(type, path.relative(
|
||||
folderToUse,
|
||||
path.resolve(projectRoot, pathToSpec),
|
||||
)).replace(backSlashesRe, '/')}`
|
||||
|
||||
debug('prefixed path for spec %o', { pathToSpec, type, url })
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
export const getSpecUrl = ({
|
||||
absoluteSpecPath,
|
||||
specType,
|
||||
browserUrl,
|
||||
integrationFolder,
|
||||
componentFolder,
|
||||
projectRoot,
|
||||
}: {
|
||||
absoluteSpecPath?: string
|
||||
browserUrl: string
|
||||
integrationFolder: string
|
||||
componentFolder: string
|
||||
projectRoot: string
|
||||
specType: 'integration' | 'component'
|
||||
}) => {
|
||||
specType ??= 'integration'
|
||||
|
||||
debug('get spec url: %s for spec type %s', absoluteSpecPath, specType)
|
||||
|
||||
// if we don't have a absoluteSpecPath or its __all
|
||||
if (!absoluteSpecPath || (absoluteSpecPath === '__all')) {
|
||||
const url = normalizeSpecUrl(browserUrl, '/__all')
|
||||
|
||||
debug('returning url to run all specs: %s', url)
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// to handle both unit + integration tests we need
|
||||
// to figure out (based on the config) where this absoluteSpecPath
|
||||
// lives. does it live in the integrationFolder or
|
||||
// the unit folder?
|
||||
// once we determine that we can then prefix it correctly
|
||||
// with either integration or unit
|
||||
const prefixedPath = getPrefixedPathToSpec({
|
||||
integrationFolder,
|
||||
componentFolder,
|
||||
projectRoot,
|
||||
pathToSpec: absoluteSpecPath,
|
||||
type: specType,
|
||||
})
|
||||
const url = normalizeSpecUrl(browserUrl, prefixedPath)
|
||||
|
||||
debug('return path to spec %o', { specType, absoluteSpecPath, prefixedPath, url })
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
export const checkSupportFile = async ({
|
||||
supportFile,
|
||||
configFile,
|
||||
}: {
|
||||
supportFile?: string | boolean
|
||||
configFile?: string | boolean
|
||||
}) => {
|
||||
if (supportFile && typeof supportFile === 'string') {
|
||||
const found = await fs.pathExists(supportFile)
|
||||
|
||||
if (!found) {
|
||||
errors.throw('SUPPORT_FILE_NOT_FOUND', supportFile, settings.configFile({ configFile }))
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -98,7 +98,7 @@ export const createRoutes = ({
|
||||
|
||||
la(check.unemptyString(config.clientRoute), 'missing client route in config', config)
|
||||
|
||||
app.get(config.clientRoute, (req, res) => {
|
||||
app.get(`${config.clientRoute}`, (req, res) => {
|
||||
debug('Serving Cypress front-end by requested URL:', req.url)
|
||||
|
||||
runner.serve(req, res, {
|
||||
|
||||
@@ -27,7 +27,7 @@ import { SocketAllowed } from './util/socket_allowed'
|
||||
import { createInitialWorkers } from '@packages/rewriter'
|
||||
import { RunnerType, SpecsStore } from './specs-store'
|
||||
import { InitializeRoutes } from '../../server-ct/src/routes-ct'
|
||||
import { ProjectBase } from './project-base'
|
||||
import { Cfg, ProjectBase } from './project-base'
|
||||
|
||||
const ALLOWED_PROXY_BYPASS_URLS = [
|
||||
'/',
|
||||
@@ -161,13 +161,13 @@ export abstract class ServerBase<TSocket extends SocketE2E | SocketCt> {
|
||||
|
||||
abstract createServer (
|
||||
app: Express,
|
||||
config: Record<string, any>,
|
||||
config: Cfg,
|
||||
project: ProjectBase<any>,
|
||||
request: unknown,
|
||||
onWarning: unknown,
|
||||
): Bluebird<[number, WarningErr?]>
|
||||
|
||||
open (config: Record<string, any> = {}, {
|
||||
open (config: Cfg, {
|
||||
project,
|
||||
onError,
|
||||
onWarning,
|
||||
@@ -190,9 +190,8 @@ export abstract class ServerBase<TSocket extends SocketE2E | SocketCt> {
|
||||
|
||||
logger.setSettings(config)
|
||||
|
||||
// TODO: Can we just pass config.baseUrl regardless of project type?
|
||||
this._nodeProxy = httpProxy.createProxyServer({
|
||||
target: projectType === 'ct' ? config.baseUrl : undefined,
|
||||
target: config.baseUrl && projectType === 'ct' ? config.baseUrl : undefined,
|
||||
})
|
||||
|
||||
this._socket = new SocketCtor(config) as TSocket
|
||||
@@ -316,7 +315,7 @@ export abstract class ServerBase<TSocket extends SocketE2E | SocketCt> {
|
||||
return io
|
||||
}
|
||||
|
||||
createHosts (hosts = {}) {
|
||||
createHosts (hosts: string[] | null = []) {
|
||||
return _.each(hosts, (ip, host) => {
|
||||
return evilDns.add(host, ip)
|
||||
})
|
||||
|
||||
@@ -15,6 +15,7 @@ import appData from './util/app_data'
|
||||
import * as ensureUrl from './util/ensure-url'
|
||||
import headersUtil from './util/headers'
|
||||
import statusCode from './util/status_code'
|
||||
import { Cfg } from './project-base'
|
||||
|
||||
type WarningErr = Record<string, any>
|
||||
|
||||
@@ -49,7 +50,7 @@ export class ServerE2E extends ServerBase<SocketE2E> {
|
||||
this._urlResolver = null
|
||||
}
|
||||
|
||||
open (config: Record<string, any> = {}, options: OpenServerOptions) {
|
||||
open (config: Cfg, options: OpenServerOptions) {
|
||||
return super.open(config, { ...options, projectType: 'e2e' })
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ const errors = require(`${root}lib/errors`)
|
||||
const plugins = require(`${root}lib/plugins`)
|
||||
const cypress = require(`${root}lib/cypress`)
|
||||
const ProjectBase = require(`${root}lib/project-base`).ProjectBase
|
||||
const { getId } = require(`${root}lib/project_static`)
|
||||
const { ServerE2E } = require(`${root}lib/server-e2e`)
|
||||
const Reporter = require(`${root}lib/reporter`)
|
||||
const Watchers = require(`${root}lib/watchers`)
|
||||
@@ -1110,7 +1111,8 @@ describe('lib/cypress', () => {
|
||||
})
|
||||
|
||||
// TODO: handle PORT_IN_USE short integration test
|
||||
it('logs error and exits when port is in use', function () {
|
||||
it('logs error and exits when port is in use', async function () {
|
||||
sinon.stub(ProjectBase.prototype, 'getAutomation').returns({ use: () => {} })
|
||||
let server = http.createServer()
|
||||
|
||||
server = Promise.promisifyAll(server)
|
||||
@@ -1119,7 +1121,7 @@ describe('lib/cypress', () => {
|
||||
.then(() => {
|
||||
return cypress.start([`--run-project=${this.todosPath}`, '--port=5544'])
|
||||
}).then(() => {
|
||||
this.expectExitWithErr('PORT_IN_USE_LONG', '5544')
|
||||
this.expectExitWithErr('PORT_IN_USE_SHORT', '5544')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1235,7 +1237,7 @@ describe('lib/cypress', () => {
|
||||
// make sure we have no user object
|
||||
user.set({}),
|
||||
|
||||
ProjectBase.id(this.todosPath)
|
||||
getId(this.todosPath)
|
||||
.then((id) => {
|
||||
this.projectId = id
|
||||
}),
|
||||
@@ -1676,7 +1678,6 @@ describe('lib/cypress', () => {
|
||||
})
|
||||
|
||||
it('passes filtered options to Project#open and sets cli config', function () {
|
||||
const getConfig = sinon.spy(ProjectBase.prototype, 'getConfig')
|
||||
const open = sinon.stub(ServerE2E.prototype, 'open').resolves([])
|
||||
|
||||
process.env.CYPRESS_FILE_SERVER_FOLDER = 'foo'
|
||||
@@ -1706,12 +1707,12 @@ describe('lib/cypress', () => {
|
||||
|
||||
return Events.handleEvent(options, {}, {}, 123, 'open:project', this.todosPath)
|
||||
}).then(() => {
|
||||
expect(getConfig).to.be.calledWithMatch({
|
||||
port: 2121,
|
||||
pageLoadTimeout: 1000,
|
||||
report: false,
|
||||
env: { baz: 'baz' },
|
||||
})
|
||||
const projectOptions = openProject.getProject().options
|
||||
|
||||
expect(projectOptions.port).to.eq(2121)
|
||||
expect(projectOptions.pageLoadTimeout).to.eq(1000)
|
||||
expect(projectOptions.report).to.eq(false)
|
||||
expect(projectOptions.env).to.eql({ baz: 'baz' })
|
||||
|
||||
expect(open).to.be.called
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ const chromePolicyCheck = require(`${root}../lib/util/chrome_policy_check`)
|
||||
const cache = require(`${root}../lib/cache`)
|
||||
const logger = require(`${root}../lib/logger`)
|
||||
const ProjectBase = require(`${root}../lib/project-base`).ProjectBase
|
||||
const ProjectStatic = require(`${root}../lib/project_static`)
|
||||
const Updater = require(`${root}../lib/updater`)
|
||||
const user = require(`${root}../lib/user`)
|
||||
const errors = require(`${root}../lib/errors`)
|
||||
@@ -49,12 +50,12 @@ describe('lib/gui/events', () => {
|
||||
sinon.stub(electron.ipcMain, 'on')
|
||||
sinon.stub(electron.ipcMain, 'removeAllListeners')
|
||||
|
||||
this.handleEvent = (type, arg) => {
|
||||
this.handleEvent = (type, arg, bus = this.bus) => {
|
||||
const id = `${type}-${Math.random()}`
|
||||
|
||||
return Promise
|
||||
.try(() => {
|
||||
return events.handleEvent(this.options, this.bus, this.event, id, type, arg)
|
||||
return events.handleEvent(this.options, bus, this.event, id, type, arg)
|
||||
}).return({
|
||||
sendCalledWith: (data) => {
|
||||
expect(this.send).to.be.calledWith('response', { id, data })
|
||||
@@ -442,7 +443,7 @@ describe('lib/gui/events', () => {
|
||||
context('user events', () => {
|
||||
describe('get:orgs', () => {
|
||||
it('returns array of orgs', function () {
|
||||
sinon.stub(ProjectBase, 'getOrgs').resolves([])
|
||||
sinon.stub(ProjectStatic, 'getOrgs').resolves([])
|
||||
|
||||
return this.handleEvent('get:orgs').then((assert) => {
|
||||
return assert.sendCalledWith([])
|
||||
@@ -452,7 +453,7 @@ describe('lib/gui/events', () => {
|
||||
it('catches errors', function () {
|
||||
const err = new Error('foo')
|
||||
|
||||
sinon.stub(ProjectBase, 'getOrgs').rejects(err)
|
||||
sinon.stub(ProjectStatic, 'getOrgs').rejects(err)
|
||||
|
||||
return this.handleEvent('get:orgs').then((assert) => {
|
||||
return assert.sendErrCalledWith(err)
|
||||
@@ -547,7 +548,7 @@ describe('lib/gui/events', () => {
|
||||
context('project events', () => {
|
||||
describe('get:projects', () => {
|
||||
it('returns array of projects', function () {
|
||||
sinon.stub(ProjectBase, 'getPathsAndIds').resolves([])
|
||||
sinon.stub(ProjectStatic, 'getPathsAndIds').resolves([])
|
||||
|
||||
return this.handleEvent('get:projects').then((assert) => {
|
||||
return assert.sendCalledWith([])
|
||||
@@ -557,7 +558,7 @@ describe('lib/gui/events', () => {
|
||||
it('catches errors', function () {
|
||||
const err = new Error('foo')
|
||||
|
||||
sinon.stub(ProjectBase, 'getPathsAndIds').rejects(err)
|
||||
sinon.stub(ProjectStatic, 'getPathsAndIds').rejects(err)
|
||||
|
||||
return this.handleEvent('get:projects').then((assert) => {
|
||||
return assert.sendErrCalledWith(err)
|
||||
@@ -567,7 +568,7 @@ describe('lib/gui/events', () => {
|
||||
|
||||
describe('get:project:statuses', () => {
|
||||
it('returns array of projects with statuses', function () {
|
||||
sinon.stub(ProjectBase, 'getProjectStatuses').resolves([])
|
||||
sinon.stub(ProjectStatic, 'getProjectStatuses').resolves([])
|
||||
|
||||
return this.handleEvent('get:project:statuses').then((assert) => {
|
||||
return assert.sendCalledWith([])
|
||||
@@ -577,7 +578,7 @@ describe('lib/gui/events', () => {
|
||||
it('catches errors', function () {
|
||||
const err = new Error('foo')
|
||||
|
||||
sinon.stub(ProjectBase, 'getProjectStatuses').rejects(err)
|
||||
sinon.stub(ProjectStatic, 'getProjectStatuses').rejects(err)
|
||||
|
||||
return this.handleEvent('get:project:statuses').then((assert) => {
|
||||
return assert.sendErrCalledWith(err)
|
||||
@@ -587,7 +588,7 @@ describe('lib/gui/events', () => {
|
||||
|
||||
describe('get:project:status', () => {
|
||||
it('returns project returned by Project.getProjectStatus', function () {
|
||||
sinon.stub(ProjectBase, 'getProjectStatus').resolves('project')
|
||||
sinon.stub(ProjectStatic, 'getProjectStatus').resolves('project')
|
||||
|
||||
return this.handleEvent('get:project:status').then((assert) => {
|
||||
return assert.sendCalledWith('project')
|
||||
@@ -597,7 +598,7 @@ describe('lib/gui/events', () => {
|
||||
it('catches errors', function () {
|
||||
const err = new Error('foo')
|
||||
|
||||
sinon.stub(ProjectBase, 'getProjectStatus').rejects(err)
|
||||
sinon.stub(ProjectStatic, 'getProjectStatus').rejects(err)
|
||||
|
||||
return this.handleEvent('get:project:status').then((assert) => {
|
||||
return assert.sendErrCalledWith(err)
|
||||
@@ -607,7 +608,7 @@ describe('lib/gui/events', () => {
|
||||
|
||||
describe('add:project', () => {
|
||||
it('adds project + returns result', function () {
|
||||
sinon.stub(ProjectBase, 'add').withArgs('/_test-output/path/to/project', this.options).resolves('result')
|
||||
sinon.stub(ProjectStatic, 'add').withArgs('/_test-output/path/to/project', this.options).resolves('result')
|
||||
|
||||
return this.handleEvent('add:project', '/_test-output/path/to/project').then((assert) => {
|
||||
return assert.sendCalledWith('result')
|
||||
@@ -617,7 +618,7 @@ describe('lib/gui/events', () => {
|
||||
it('catches errors', function () {
|
||||
const err = new Error('foo')
|
||||
|
||||
sinon.stub(ProjectBase, 'add').withArgs('/_test-output/path/to/project', this.options).rejects(err)
|
||||
sinon.stub(ProjectStatic, 'add').withArgs('/_test-output/path/to/project', this.options).rejects(err)
|
||||
|
||||
return this.handleEvent('add:project', '/_test-output/path/to/project').then((assert) => {
|
||||
return assert.sendErrCalledWith(err)
|
||||
@@ -646,11 +647,19 @@ describe('lib/gui/events', () => {
|
||||
})
|
||||
|
||||
describe('open:project', () => {
|
||||
function busStub () {
|
||||
return {
|
||||
on: sinon.stub(),
|
||||
removeAllListeners: sinon.stub(),
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
sinon.stub(extension, 'setHostAndPath').resolves()
|
||||
sinon.stub(browsers, 'getAllBrowsersWith')
|
||||
browsers.getAllBrowsersWith.resolves([])
|
||||
browsers.getAllBrowsersWith.withArgs('/usr/bin/baz-browser').resolves([{ foo: 'bar' }])
|
||||
this.initializeConfig = sinon.stub(ProjectBase.prototype, 'initializeConfig').resolves()
|
||||
this.open = sinon.stub(ProjectBase.prototype, 'open').resolves()
|
||||
sinon.stub(ProjectBase.prototype, 'close').resolves()
|
||||
|
||||
@@ -664,7 +673,9 @@ describe('lib/gui/events', () => {
|
||||
it('open project + returns config', function () {
|
||||
return this.handleEvent('open:project', '/_test-output/path/to/project-e2e')
|
||||
.then((assert) => {
|
||||
return assert.sendCalledWith({ some: 'config' })
|
||||
expect(this.send.firstCall.args[0]).to.eq('response') // [1].id).to.match(/setup:dashboard:project-/)
|
||||
expect(this.send.firstCall.args[1].id).to.match(/open:project-/)
|
||||
expect(this.send.firstCall.args[1].data).to.eql({ some: 'config' })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -680,57 +691,57 @@ describe('lib/gui/events', () => {
|
||||
})
|
||||
|
||||
it('sends \'focus:tests\' onFocusTests', function () {
|
||||
return this.handleEvent('open:project', '/_test-output/path/to/project-e2e')
|
||||
.then(() => {
|
||||
return this.handleEvent('on:focus:tests')
|
||||
}).then((assert) => {
|
||||
this.open.lastCall.args[0].onFocusTests()
|
||||
const bus = busStub()
|
||||
|
||||
return assert.sendCalledWith(undefined)
|
||||
return this.handleEvent('open:project', '/_test-output/path/to/project-e2e', bus)
|
||||
.then(() => {
|
||||
return this.handleEvent('on:focus:tests', '', bus)
|
||||
}).then(() => {
|
||||
expect(bus.on).to.have.been.calledWith('focus:tests')
|
||||
})
|
||||
})
|
||||
|
||||
it('sends \'config:changed\' onSettingsChanged', function () {
|
||||
return this.handleEvent('open:project', '/_test-output/path/to/project-e2e')
|
||||
.then(() => {
|
||||
return this.handleEvent('on:config:changed')
|
||||
}).then((assert) => {
|
||||
this.open.lastCall.args[0].onSettingsChanged()
|
||||
const bus = busStub()
|
||||
|
||||
return assert.sendCalledWith(undefined)
|
||||
return this.handleEvent('open:project', '/_test-output/path/to/project-e2e', bus)
|
||||
.then(() => {
|
||||
return this.handleEvent('on:config:changed', '', bus)
|
||||
}).then(() => {
|
||||
expect(bus.on).to.have.been.calledWith('config:changed')
|
||||
})
|
||||
})
|
||||
|
||||
it('sends \'spec:changed\' onSpecChanged', function () {
|
||||
const bus = busStub()
|
||||
|
||||
return this.handleEvent('open:project', '/_test-output/path/to/project-e2e')
|
||||
.then(() => {
|
||||
return this.handleEvent('on:spec:changed')
|
||||
return this.handleEvent('on:spec:changed', '', bus)
|
||||
}).then((assert) => {
|
||||
this.open.lastCall.args[0].onSpecChanged('/path/to/spec.coffee')
|
||||
|
||||
return assert.sendCalledWith('/path/to/spec.coffee')
|
||||
expect(bus.on).to.have.been.calledWith('spec:changed')
|
||||
})
|
||||
})
|
||||
|
||||
it('sends \'project:warning\' onWarning', function () {
|
||||
const bus = busStub()
|
||||
|
||||
return this.handleEvent('open:project', '/_test-output/path/to/project-e2e')
|
||||
.then(() => {
|
||||
return this.handleEvent('on:project:warning')
|
||||
}).then((assert) => {
|
||||
this.open.lastCall.args[0].onWarning({ name: 'foo', message: 'foo' })
|
||||
|
||||
return assert.sendCalledWith({ name: 'foo', message: 'foo' })
|
||||
return this.handleEvent('on:project:warning', '', bus)
|
||||
}).then(() => {
|
||||
expect(bus.on).to.have.been.calledWith('project:warning')
|
||||
})
|
||||
})
|
||||
|
||||
it('sends \'project:error\' onError', function () {
|
||||
const bus = busStub()
|
||||
|
||||
return this.handleEvent('open:project', '/_test-output/path/to/project-e2e')
|
||||
.then(() => {
|
||||
return this.handleEvent('on:project:error')
|
||||
return this.handleEvent('on:project:error', '', bus)
|
||||
}).then((assert) => {
|
||||
this.open.lastCall.args[0].onError({ name: 'foo', message: 'foo' })
|
||||
|
||||
return assert.sendCalledWith({ name: 'foo', message: 'foo' })
|
||||
expect(bus.on).to.have.been.calledWith('project:error')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -913,18 +924,17 @@ describe('lib/gui/events', () => {
|
||||
})
|
||||
|
||||
describe('setup:dashboard:project', () => {
|
||||
it('returns result of openProject.createCiProject', function () {
|
||||
sinon.stub(openProject, 'createCiProject').resolves('response')
|
||||
|
||||
it('returns result of ProjectStatic.createCiProject', function () {
|
||||
return this.handleEvent('setup:dashboard:project').then((assert) => {
|
||||
return assert.sendCalledWith('response')
|
||||
expect(this.send.firstCall.args[0]).to.eq('response')
|
||||
expect(this.send.firstCall.args[1].id).to.match(/setup:dashboard:project-/)
|
||||
})
|
||||
})
|
||||
|
||||
it('catches errors', function () {
|
||||
const err = new Error('foo')
|
||||
|
||||
sinon.stub(openProject, 'createCiProject').rejects(err)
|
||||
sinon.stub(ProjectStatic, 'createCiProject').rejects(err)
|
||||
|
||||
return this.handleEvent('setup:dashboard:project').then((assert) => {
|
||||
return assert.sendErrCalledWith(err)
|
||||
|
||||
@@ -9,7 +9,6 @@ const pkg = require('@packages/root')
|
||||
const { fs } = require(`${root}../lib/util/fs`)
|
||||
const user = require(`${root}../lib/user`)
|
||||
const errors = require(`${root}../lib/errors`)
|
||||
const config = require(`${root}../lib/config`)
|
||||
const ProjectBase = require(`${root}../lib/project-base`).ProjectBase
|
||||
const browsers = require(`${root}../lib/browsers`)
|
||||
const Reporter = require(`${root}../lib/reporter`)
|
||||
@@ -21,6 +20,7 @@ const random = require(`${root}../lib/util/random`)
|
||||
const system = require(`${root}../lib/util/system`)
|
||||
const specsUtil = require(`${root}../lib/util/specs`)
|
||||
const { experimental } = require(`${root}../lib/experiments`)
|
||||
const ProjectStatic = require(`${root}../lib/project_static`)
|
||||
|
||||
describe('lib/modes/run', () => {
|
||||
beforeEach(function () {
|
||||
@@ -646,9 +646,21 @@ describe('lib/modes/run', () => {
|
||||
|
||||
context('.run browser vs video recording', () => {
|
||||
beforeEach(function () {
|
||||
const config = {
|
||||
proxyUrl: 'http://localhost:12345',
|
||||
video: true,
|
||||
videosFolder: 'videos',
|
||||
integrationFolder: '/path/to/integrationFolder',
|
||||
resolved: {
|
||||
integrationFolder: {
|
||||
integrationFolder: { value: '/path/to/integrationFolder', from: 'config' },
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
sinon.stub(electron.app, 'on').withArgs('ready').yieldsAsync()
|
||||
sinon.stub(user, 'ensureAuthToken')
|
||||
sinon.stub(ProjectBase, 'ensureExists').resolves()
|
||||
sinon.stub(ProjectStatic, 'ensureExists').resolves()
|
||||
sinon.stub(random, 'id').returns(1234)
|
||||
sinon.stub(openProject, 'create').resolves(openProject)
|
||||
sinon.stub(runMode, 'waitForSocketConnection').resolves()
|
||||
@@ -660,19 +672,9 @@ describe('lib/modes/run', () => {
|
||||
sinon.spy(runMode, 'waitForBrowserToConnect')
|
||||
sinon.stub(videoCapture, 'start').resolves()
|
||||
sinon.stub(openProject, 'launch').resolves()
|
||||
this.projectInstance.__setConfig(config)
|
||||
sinon.stub(openProject, 'getProject').resolves(this.projectInstance)
|
||||
sinon.spy(errors, 'warning')
|
||||
sinon.stub(config, 'get').resolves({
|
||||
proxyUrl: 'http://localhost:12345',
|
||||
video: true,
|
||||
videosFolder: 'videos',
|
||||
integrationFolder: '/path/to/integrationFolder',
|
||||
resolved: {
|
||||
integrationFolder: {
|
||||
integrationFolder: { value: '/path/to/integrationFolder', from: 'config' },
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
sinon.stub(specsUtil, 'find').resolves([
|
||||
{
|
||||
@@ -726,7 +728,7 @@ describe('lib/modes/run', () => {
|
||||
|
||||
sinon.stub(electron.app, 'on').withArgs('ready').yieldsAsync()
|
||||
sinon.stub(user, 'ensureAuthToken')
|
||||
sinon.stub(ProjectBase, 'ensureExists').resolves()
|
||||
sinon.stub(ProjectStatic, 'ensureExists').resolves()
|
||||
sinon.stub(random, 'id').returns(1234)
|
||||
sinon.stub(openProject, 'create').resolves(openProject)
|
||||
sinon.stub(system, 'info').resolves({ osName: 'osFoo', osVersion: 'fooVersion' })
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
require('../spec_helper')
|
||||
|
||||
const path = require('path')
|
||||
const chokidar = require('chokidar')
|
||||
const browsers = require(`${root}lib/browsers`)
|
||||
const ProjectBase = require(`${root}lib/project-base`).ProjectBase
|
||||
const openProject = require(`${root}lib/open_project`)
|
||||
const preprocessor = require(`${root}lib/plugins/preprocessor`)
|
||||
const runEvents = require(`${root}lib/plugins/run_events`)
|
||||
const Fixtures = require('../test/../support/helpers/fixtures')
|
||||
|
||||
const todosPath = Fixtures.projectPath('todos')
|
||||
|
||||
describe('lib/open_project', () => {
|
||||
beforeEach(function () {
|
||||
@@ -23,9 +27,9 @@ describe('lib/open_project', () => {
|
||||
|
||||
sinon.stub(browsers, 'get').resolves()
|
||||
sinon.stub(browsers, 'open')
|
||||
sinon.stub(ProjectBase.prototype, 'initializeConfig').resolves()
|
||||
sinon.stub(ProjectBase.prototype, 'open').resolves()
|
||||
sinon.stub(ProjectBase.prototype, 'reset').resolves()
|
||||
sinon.stub(ProjectBase.prototype, 'getSpecUrl').resolves()
|
||||
sinon.stub(ProjectBase.prototype, 'getConfig').resolves(this.config)
|
||||
sinon.stub(ProjectBase.prototype, 'getAutomation').returns(this.automation)
|
||||
sinon.stub(preprocessor, 'removeFile')
|
||||
@@ -34,7 +38,16 @@ describe('lib/open_project', () => {
|
||||
})
|
||||
|
||||
context('#launch', () => {
|
||||
beforeEach(function () {
|
||||
beforeEach(async function () {
|
||||
await openProject.create('/root')
|
||||
openProject.getProject().__setConfig({
|
||||
browserUrl: 'http://localhost:8888/__/',
|
||||
componentFolder: path.join(todosPath, 'component'),
|
||||
integrationFolder: path.join(todosPath, 'tests'),
|
||||
projectRoot: todosPath,
|
||||
specType: 'integration',
|
||||
})
|
||||
|
||||
openProject.getProject().options = {}
|
||||
|
||||
this.spec = {
|
||||
|
||||
@@ -6,6 +6,7 @@ const commitInfo = require('@cypress/commit-info')
|
||||
const chokidar = require('chokidar')
|
||||
const pkg = require('@packages/root')
|
||||
const Fixtures = require('../support/helpers/fixtures')
|
||||
const { sinon } = require('../spec_helper')
|
||||
const api = require(`${root}lib/api`)
|
||||
const user = require(`${root}lib/user`)
|
||||
const cache = require(`${root}lib/cache`)
|
||||
@@ -13,9 +14,21 @@ const config = require(`${root}lib/config`)
|
||||
const scaffold = require(`${root}lib/scaffold`)
|
||||
const { ServerE2E } = require(`${root}lib/server-e2e`)
|
||||
const ProjectBase = require(`${root}lib/project-base`).ProjectBase
|
||||
const {
|
||||
getOrgs,
|
||||
paths,
|
||||
remove,
|
||||
add,
|
||||
getId,
|
||||
getPathsAndIds,
|
||||
getProjectStatus,
|
||||
getProjectStatuses,
|
||||
createCiProject,
|
||||
writeProjectId,
|
||||
} = require(`${root}lib/project_static`)
|
||||
const ProjectUtils = require(`${root}lib/project_utils`)
|
||||
const { Automation } = require(`${root}lib/automation`)
|
||||
const savedState = require(`${root}lib/saved_state`)
|
||||
const preprocessor = require(`${root}lib/plugins/preprocessor`)
|
||||
const plugins = require(`${root}lib/plugins`)
|
||||
const runEvents = require(`${root}lib/plugins/run_events`)
|
||||
const system = require(`${root}lib/util/system`)
|
||||
@@ -62,7 +75,7 @@ describe('lib/project-base', () => {
|
||||
})
|
||||
|
||||
it('requires a projectRoot', function () {
|
||||
const fn = () => new ProjectBase()
|
||||
const fn = () => new ProjectBase({})
|
||||
|
||||
expect(fn).to.throw('Instantiating lib/project requires a projectRoot!')
|
||||
})
|
||||
@@ -74,17 +87,19 @@ describe('lib/project-base', () => {
|
||||
expect(p.projectRoot).to.eq(path.resolve('../foo/bar'))
|
||||
})
|
||||
|
||||
it('handles CT specific behaviors', function () {
|
||||
it('handles CT specific behaviors', async function () {
|
||||
sinon.stub(ServerE2E.prototype, 'open').resolves([])
|
||||
sinon.stub(ProjectBase.prototype, 'startCtDevServer').resolves({ port: 9999 })
|
||||
|
||||
const projectCt = new ProjectBase({ projectRoot: '../foo/bar', projectType: 'ct' })
|
||||
|
||||
await projectCt.initializeConfig()
|
||||
|
||||
return projectCt.open({}).then((project) => {
|
||||
expect(project._cfg.viewportHeight).to.eq(500)
|
||||
expect(project._cfg.viewportWidth).to.eq(500)
|
||||
expect(project._cfg.baseUrl).to.eq('http://localhost:9999')
|
||||
expect(project.startCtDevServer).to.have.beenCalled
|
||||
expect(projectCt._cfg.viewportHeight).to.eq(500)
|
||||
expect(projectCt._cfg.viewportWidth).to.eq(500)
|
||||
expect(projectCt._cfg.baseUrl).to.eq('http://localhost:9999')
|
||||
expect(projectCt.startCtDevServer).to.have.beenCalled
|
||||
})
|
||||
})
|
||||
|
||||
@@ -130,60 +145,57 @@ describe('lib/project-base', () => {
|
||||
})
|
||||
})
|
||||
|
||||
context('#getConfig', () => {
|
||||
context('#initializeConfig', () => {
|
||||
const integrationFolder = 'foo/bar/baz'
|
||||
|
||||
beforeEach(function () {
|
||||
this.project._cfg = undefined
|
||||
|
||||
sinon.stub(config, 'get').withArgs(this.todosPath, { foo: 'bar' }).resolves({ baz: 'quux', integrationFolder })
|
||||
sinon.stub(config, 'get').withArgs(this.todosPath, { foo: 'bar' }).resolves({ baz: 'quux', integrationFolder, browsers: [] })
|
||||
})
|
||||
|
||||
it('calls config.get with projectRoot + options + saved state', function () {
|
||||
this.project.__setOptions({ foo: 'bar' })
|
||||
|
||||
return savedState.create(this.todosPath)
|
||||
.then((state) => {
|
||||
.then(async (state) => {
|
||||
sinon.stub(state, 'get').resolves({ reporterWidth: 225 })
|
||||
|
||||
return this.project.getConfig({ foo: 'bar' })
|
||||
.then((cfg) => {
|
||||
expect(cfg).to.deep.eq({
|
||||
integrationFolder,
|
||||
isNewProject: false,
|
||||
baz: 'quux',
|
||||
state: {
|
||||
reporterWidth: 225,
|
||||
},
|
||||
})
|
||||
|
||||
this.project._cfg = cfg
|
||||
await this.project.initializeConfig()
|
||||
expect(await this.project.getConfig()).to.deep.eq({
|
||||
integrationFolder,
|
||||
browsers: [],
|
||||
isNewProject: false,
|
||||
baz: 'quux',
|
||||
state: {
|
||||
reporterWidth: 225,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('resolves if cfg is already set', function () {
|
||||
it('resolves if cfg is already set', async function () {
|
||||
this.project._cfg = {
|
||||
integrationFolder,
|
||||
foo: 'bar',
|
||||
}
|
||||
|
||||
return this.project.getConfig()
|
||||
.then((cfg) => {
|
||||
expect(cfg).to.deep.eq({
|
||||
integrationFolder,
|
||||
foo: 'bar',
|
||||
})
|
||||
expect(await this.project.getConfig()).to.deep.eq({
|
||||
integrationFolder,
|
||||
foo: 'bar',
|
||||
})
|
||||
})
|
||||
|
||||
it('sets cfg.isNewProject to false when state.showedNewProjectBanner is true', function () {
|
||||
this.project.__setOptions({ foo: 'bar' })
|
||||
|
||||
return savedState.create(this.todosPath)
|
||||
.then((state) => {
|
||||
sinon.stub(state, 'get').resolves({ showedNewProjectBanner: true })
|
||||
|
||||
return this.project.getConfig({ foo: 'bar' })
|
||||
return this.project.initializeConfig()
|
||||
.then((cfg) => {
|
||||
expect(cfg).to.deep.eq({
|
||||
integrationFolder,
|
||||
browsers: [],
|
||||
isNewProject: false,
|
||||
baz: 'quux',
|
||||
state: {
|
||||
@@ -197,23 +209,61 @@ describe('lib/project-base', () => {
|
||||
})
|
||||
|
||||
it('does not set cfg.isNewProject when cfg.isTextTerminal', function () {
|
||||
const cfg = { isTextTerminal: true }
|
||||
const cfg = { isTextTerminal: true, browsers: [] }
|
||||
|
||||
config.get.resolves(cfg)
|
||||
|
||||
sinon.stub(this.project, '_setSavedState').resolves(cfg)
|
||||
|
||||
return this.project.getConfig({ foo: 'bar' })
|
||||
return this.project.initializeConfig()
|
||||
.then((cfg) => {
|
||||
expect(cfg).not.to.have.property('isNewProject')
|
||||
})
|
||||
})
|
||||
|
||||
it('attaches warning to non-chrome browsers when chromeWebSecurity:false', async function () {
|
||||
const cfg = Object.assign({}, {
|
||||
integrationFolder,
|
||||
browsers: [{ family: 'chromium', name: 'Canary' }, { family: 'some-other-family', name: 'some-other-name' }],
|
||||
chromeWebSecurity: false,
|
||||
})
|
||||
|
||||
config.get.restore()
|
||||
sinon.stub(config, 'get').returns(cfg)
|
||||
|
||||
await this.project.initializeConfig()
|
||||
.then(() => this.project.getConfig())
|
||||
.then((config) => {
|
||||
expect(config.chromeWebSecurity).eq(false)
|
||||
expect(config.browsers).deep.eq([
|
||||
{
|
||||
family: 'chromium',
|
||||
name: 'Canary',
|
||||
},
|
||||
{
|
||||
family: 'some-other-family',
|
||||
name: 'some-other-name',
|
||||
warning: `\
|
||||
Your project has set the configuration option: \`chromeWebSecurity: false\`
|
||||
|
||||
This option will not have an effect in Some-other-name. Tests that rely on web security being disabled will not run as expected.\
|
||||
`,
|
||||
},
|
||||
])
|
||||
|
||||
expect(config).ok
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('#initializeConfig', function () {
|
||||
})
|
||||
|
||||
context('#open', () => {
|
||||
beforeEach(function () {
|
||||
sinon.stub(this.project, 'watchSettingsAndStartWebsockets').resolves()
|
||||
sinon.stub(this.project, 'checkSupportFile').resolves()
|
||||
sinon.stub(this.project, 'watchSettings')
|
||||
sinon.stub(this.project, 'startWebsockets')
|
||||
this.checkSupportFileStub = sinon.stub(ProjectUtils, 'checkSupportFile').resolves()
|
||||
sinon.stub(this.project, 'scaffold').resolves()
|
||||
sinon.stub(this.project, 'getConfig').resolves(this.config)
|
||||
sinon.stub(ServerE2E.prototype, 'open').resolves([])
|
||||
@@ -223,44 +273,63 @@ describe('lib/project-base', () => {
|
||||
sinon.stub(plugins, 'init').resolves()
|
||||
})
|
||||
|
||||
it('calls #watchSettingsAndStartWebsockets with options + config', function () {
|
||||
const opts = { changeEvents: false, onAutomationRequest () {} }
|
||||
it('calls #watchSettings with options + config', function () {
|
||||
return this.project.open().then(() => {
|
||||
expect(this.project.watchSettings).to.be.calledWith({
|
||||
configFile: undefined,
|
||||
onSettingsChanged: false,
|
||||
projectRoot: this.todosPath,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
this.project.cfg = {}
|
||||
it('calls #startWebsockets with options + config', function () {
|
||||
const onFocusTests = sinon.stub()
|
||||
|
||||
return this.project.open(opts).then(() => {
|
||||
expect(this.project.watchSettingsAndStartWebsockets).to.be.calledWith(opts, this.project.cfg)
|
||||
this.project.__setOptions({
|
||||
onFocusTests,
|
||||
})
|
||||
|
||||
return this.project.open().then(() => {
|
||||
expect(this.project.startWebsockets).to.be.calledWith({
|
||||
onReloadBrowser: undefined,
|
||||
onFocusTests,
|
||||
onSpecChanged: undefined,
|
||||
}, {
|
||||
socketIoCookie: '__socket.io',
|
||||
namespace: '__cypress',
|
||||
screenshotsFolder: '/foo/bar/cypress/screenshots',
|
||||
report: undefined,
|
||||
reporter: 'spec',
|
||||
reporterOptions: null,
|
||||
projectRoot: this.todosPath,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('calls #scaffold with server config promise', function () {
|
||||
return this.project.open({}).then(() => {
|
||||
return this.project.open().then(() => {
|
||||
expect(this.project.scaffold).to.be.calledWith(this.config)
|
||||
})
|
||||
})
|
||||
|
||||
it('calls #checkSupportFile with server config when scaffolding is finished', function () {
|
||||
return this.project.open({}).then(() => {
|
||||
expect(this.project.checkSupportFile).to.be.calledWith(this.config)
|
||||
})
|
||||
})
|
||||
|
||||
it('calls #getConfig options', function () {
|
||||
const opts = {}
|
||||
|
||||
return this.project.open(opts).then(() => {
|
||||
expect(this.project.getConfig).to.be.calledWith(opts)
|
||||
it('calls checkSupportFile with server config when scaffolding is finished', function () {
|
||||
return this.project.open().then(() => {
|
||||
expect(this.checkSupportFileStub).to.be.calledWith({
|
||||
configFile: 'cypress.json',
|
||||
supportFile: '/foo/bar/cypress/support/index.js',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('initializes the plugins', function () {
|
||||
return this.project.open({}).then(() => {
|
||||
return this.project.open().then(() => {
|
||||
expect(plugins.init).to.be.called
|
||||
})
|
||||
})
|
||||
|
||||
it('calls support.plugins with pluginsFile directory', function () {
|
||||
return this.project.open({}).then(() => {
|
||||
return this.project.open().then(() => {
|
||||
expect(scaffold.plugins).to.be.calledWith(path.dirname(this.config.pluginsFile))
|
||||
})
|
||||
})
|
||||
@@ -272,7 +341,9 @@ describe('lib/project-base', () => {
|
||||
message: 'plugin error message',
|
||||
}
|
||||
|
||||
return this.project.open({ onError }).then(() => {
|
||||
this.project.__setOptions({ onError })
|
||||
|
||||
return this.project.open().then(() => {
|
||||
const pluginsOnError = plugins.init.lastCall.args[1].onError
|
||||
|
||||
expect(pluginsOnError).to.be.a('function')
|
||||
@@ -302,36 +373,6 @@ describe('lib/project-base', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('attaches warning to non-chrome browsers when chromeWebSecurity:false', function () {
|
||||
Object.assign(this.config, {
|
||||
browsers: [{ family: 'chromium', name: 'Canary' }, { family: 'some-other-family', name: 'some-other-name' }],
|
||||
chromeWebSecurity: false,
|
||||
})
|
||||
|
||||
return this.project.open({})
|
||||
.then(() => this.project.getConfig())
|
||||
.then((config) => {
|
||||
expect(config.chromeWebSecurity).eq(false)
|
||||
expect(config.browsers).deep.eq([
|
||||
{
|
||||
family: 'chromium',
|
||||
name: 'Canary',
|
||||
},
|
||||
{
|
||||
family: 'some-other-family',
|
||||
name: 'some-other-name',
|
||||
warning: `\
|
||||
Your project has set the configuration option: \`chromeWebSecurity: false\`
|
||||
|
||||
This option will not have an effect in Some-other-name. Tests that rely on web security being disabled will not run as expected.\
|
||||
`,
|
||||
},
|
||||
])
|
||||
|
||||
expect(config).ok
|
||||
})
|
||||
})
|
||||
|
||||
it('executes before:run if in interactive mode', function () {
|
||||
const sysInfo = {
|
||||
osName: 'darwin',
|
||||
@@ -342,7 +383,7 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
this.config.experimentalInteractiveRunEvents = true
|
||||
this.config.isTextTerminal = false
|
||||
|
||||
return this.project.open({})
|
||||
return this.project.open()
|
||||
.then(() => {
|
||||
expect(runEvents.execute).to.be.calledWith('before:run', this.config, {
|
||||
config: this.config,
|
||||
@@ -357,7 +398,7 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
this.config.experimentalInteractiveRunEvents = true
|
||||
this.config.isTextTerminal = true
|
||||
|
||||
return this.project.open({})
|
||||
return this.project.open()
|
||||
.then(() => {
|
||||
expect(system.info).not.to.be.called
|
||||
expect(runEvents.execute).not.to.be.calledWith('before:run')
|
||||
@@ -369,7 +410,7 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
this.config.experimentalInteractiveRunEvents = false
|
||||
this.config.isTextTerminal = false
|
||||
|
||||
return this.project.open({})
|
||||
return this.project.open()
|
||||
.then(() => {
|
||||
expect(system.info).not.to.be.called
|
||||
expect(runEvents.execute).not.to.be.calledWith('before:run')
|
||||
@@ -383,7 +424,7 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
})
|
||||
|
||||
it('sets firstOpened and lastOpened on first open', function () {
|
||||
return this.project.open({})
|
||||
return this.project.open()
|
||||
.then(() => this.project.getConfig())
|
||||
.then((config) => {
|
||||
expect(config.state).to.eql({ firstOpened: this._time, lastOpened: this._time })
|
||||
@@ -391,11 +432,11 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
})
|
||||
|
||||
it('only sets lastOpened on subsequent opens', function () {
|
||||
return this.project.open({})
|
||||
return this.project.open()
|
||||
.then(() => {
|
||||
this._dateStub.returns(this._time + 100000)
|
||||
})
|
||||
.then(() => this.project.open({}))
|
||||
.then(() => this.project.open())
|
||||
.then(() => this.project.getConfig())
|
||||
.then((config) => {
|
||||
expect(config.state).to.eql({ firstOpened: this._time, lastOpened: this._time + 100000 })
|
||||
@@ -405,9 +446,11 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
it('updates config.state when saved state changes', function () {
|
||||
sinon.spy(this.project, 'saveState')
|
||||
|
||||
const options = {}
|
||||
const options = { onSavedStateChanged: (...args) => this.project.saveState(...args) }
|
||||
|
||||
return this.project.open(options)
|
||||
this.project.__setOptions(options)
|
||||
|
||||
return this.project.open()
|
||||
.then(() => options.onSavedStateChanged({ autoScrollingEnabled: false }))
|
||||
.then(() => this.project.getConfig())
|
||||
.then((config) => {
|
||||
@@ -492,12 +535,10 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
})
|
||||
|
||||
it('resets server + automation', function () {
|
||||
return this.project.reset()
|
||||
.then(() => {
|
||||
expect(this.project._automation.reset).to.be.calledOnce
|
||||
this.project.reset()
|
||||
expect(this.project._automation.reset).to.be.calledOnce
|
||||
|
||||
expect(this.project.server.reset).to.be.calledOnce
|
||||
})
|
||||
expect(this.project.server.reset).to.be.calledOnce
|
||||
})
|
||||
})
|
||||
|
||||
@@ -614,7 +655,7 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
})
|
||||
|
||||
it('watches cypress.json and cypress.env.json', function () {
|
||||
this.project.watchSettingsAndStartWebsockets({ onSettingsChanged () {} })
|
||||
this.project.watchSettings({ onSettingsChanged () {} }, {})
|
||||
expect(this.watch).to.be.calledTwice
|
||||
expect(this.watch).to.be.calledWith('/path/to/cypress.json')
|
||||
|
||||
@@ -622,7 +663,7 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
})
|
||||
|
||||
it('sets onChange event when {changeEvents: true}', function (done) {
|
||||
this.project.watchSettingsAndStartWebsockets({ onSettingsChanged: () => done() })
|
||||
this.project.watchSettings({ onSettingsChanged: () => done() }, {})
|
||||
|
||||
// get the object passed to watchers.watch
|
||||
const obj = this.watch.getCall(0).args[1]
|
||||
@@ -633,7 +674,7 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
})
|
||||
|
||||
it('does not call watch when {changeEvents: false}', function () {
|
||||
this.project.watchSettingsAndStartWebsockets({ onSettingsChanged: undefined })
|
||||
this.project.watchSettings({ onSettingsChanged: undefined }, {})
|
||||
|
||||
expect(this.watch).not.to.be.called
|
||||
})
|
||||
@@ -645,7 +686,7 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
|
||||
const stub = sinon.stub()
|
||||
|
||||
this.project.watchSettingsAndStartWebsockets({ onSettingsChanged: stub })
|
||||
this.project.watchSettings({ onSettingsChanged: stub }, {})
|
||||
|
||||
// get the object passed to watchers.watch
|
||||
const obj = this.watch.getCall(0).args[1]
|
||||
@@ -663,35 +704,6 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
})
|
||||
})
|
||||
|
||||
context('#checkSupportFile', () => {
|
||||
beforeEach(function () {
|
||||
sinon.stub(fs, 'pathExists').resolves(true)
|
||||
this.project = new ProjectBase({ projectRoot: '/_test-output/path/to/project-e2e', projectType: 'e2e' })
|
||||
this.project.server = { onTestFileChange: sinon.spy() }
|
||||
sinon.stub(preprocessor, 'getFile').resolves()
|
||||
|
||||
this.config = {
|
||||
projectRoot: '/path/to/root/',
|
||||
supportFile: '/path/to/root/foo/bar.js',
|
||||
}
|
||||
})
|
||||
|
||||
it('does nothing when {supportFile: false}', function () {
|
||||
const ret = this.project.checkSupportFile({ supportFile: false })
|
||||
|
||||
expect(ret).to.be.undefined
|
||||
})
|
||||
|
||||
it('throws when support file does not exist', function () {
|
||||
fs.pathExists.resolves(false)
|
||||
|
||||
return this.project.checkSupportFile(this.config)
|
||||
.catch((e) => {
|
||||
expect(e.message).to.include('The support file is missing or invalid.')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('#watchPluginsFile', () => {
|
||||
beforeEach(function () {
|
||||
sinon.stub(fs, 'pathExists').resolves(true)
|
||||
@@ -765,18 +777,20 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
})
|
||||
})
|
||||
|
||||
context('#watchSettingsAndStartWebsockets', () => {
|
||||
context('#startWebsockets', () => {
|
||||
beforeEach(function () {
|
||||
this.project = new ProjectBase({ projectRoot: '/_test-output/path/to/project-e2e', projectType: 'e2e' })
|
||||
this.project.watchers = {}
|
||||
this.project._server = { close () {}, startWebsockets: sinon.stub() }
|
||||
sinon.stub(ProjectBase.prototype, 'open').resolves()
|
||||
sinon.stub(this.project, 'watchSettings')
|
||||
})
|
||||
|
||||
it('calls server.startWebsockets with automation + config', function () {
|
||||
it('calls server.startWebsockets with automation + config', async function () {
|
||||
const c = {}
|
||||
|
||||
this.project.watchSettingsAndStartWebsockets({}, c)
|
||||
this.project.__setConfig(c)
|
||||
this.project.startWebsockets({}, c)
|
||||
|
||||
const args = this.project.server.startWebsockets.lastCall.args
|
||||
|
||||
@@ -789,7 +803,7 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
|
||||
this.project.server.startWebsockets.yieldsTo('onReloadBrowser')
|
||||
|
||||
this.project.watchSettingsAndStartWebsockets({ onReloadBrowser: fn }, {})
|
||||
this.project.startWebsockets({ onReloadBrowser: fn }, {})
|
||||
|
||||
expect(fn).to.be.calledOnce
|
||||
})
|
||||
@@ -869,99 +883,26 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
})
|
||||
|
||||
it('calls Settings.write with projectRoot and attrs', function () {
|
||||
return this.project.writeProjectId('id-123').then((id) => {
|
||||
return writeProjectId('id-123').then((id) => {
|
||||
expect(id).to.eq('id-123')
|
||||
})
|
||||
})
|
||||
|
||||
it('sets generatedProjectIdTimestamp', function () {
|
||||
return this.project.writeProjectId('id-123').then(() => {
|
||||
// TODO: This
|
||||
xit('sets generatedProjectIdTimestamp', function () {
|
||||
return writeProjectId('id-123').then(() => {
|
||||
expect(this.project.generatedProjectIdTimestamp).to.be.a('date')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('#getSpecUrl', () => {
|
||||
beforeEach(function () {
|
||||
this.project2 = new ProjectBase({ projectRoot: this.idsPath, projectType: 'e2e' })
|
||||
|
||||
this.project._cfg = {
|
||||
browserUrl: 'http://localhost:8888/__/',
|
||||
integrationFolder: path.join(this.todosPath, 'tests'),
|
||||
componentFolder: path.join(this.todosPath, 'tests'),
|
||||
projectRoot: this.todosPath,
|
||||
}
|
||||
|
||||
return settings.write(this.idsPath, { port: 2020 })
|
||||
})
|
||||
|
||||
it('returns fully qualified url when spec exists', function () {
|
||||
return this.project2.getSpecUrl('cypress/integration/bar.js')
|
||||
.then((str) => {
|
||||
expect(str).to.eq('http://localhost:2020/__/#/tests/integration/bar.js')
|
||||
})
|
||||
})
|
||||
|
||||
it('returns fully qualified url on absolute path to spec', function () {
|
||||
const todosSpec = path.join(this.todosPath, 'tests/sub/sub_test.coffee')
|
||||
|
||||
return this.project.getSpecUrl(todosSpec)
|
||||
.then((str) => {
|
||||
expect(str).to.eq('http://localhost:8888/__/#/tests/integration/sub/sub_test.coffee')
|
||||
})
|
||||
})
|
||||
|
||||
it('escapses %, &', function () {
|
||||
const todosSpec = path.join(this.todosPath, 'tests/sub/a&b%c.js')
|
||||
|
||||
return this.project.getSpecUrl(todosSpec)
|
||||
.then((str) => {
|
||||
expect(str).to.eq('http://localhost:8888/__/#/tests/integration/sub/a%26b%25c.js')
|
||||
})
|
||||
})
|
||||
|
||||
// ? is invalid in Windows, but it can be tested here
|
||||
// because it's a unit test and doesn't check the existence of files
|
||||
it('escapes ?', function () {
|
||||
const todosSpec = path.join(this.todosPath, 'tests/sub/a?.spec.js')
|
||||
|
||||
return this.project.getSpecUrl(todosSpec)
|
||||
.then((str) => {
|
||||
expect(str).to.eq('http://localhost:8888/__/#/tests/integration/sub/a%3F.spec.js')
|
||||
})
|
||||
})
|
||||
|
||||
it('escapes %, &, ? in the url dir', function () {
|
||||
const todosSpec = path.join(this.todosPath, 'tests/s%&?ub/a.spec.js')
|
||||
|
||||
return this.project.getSpecUrl(todosSpec)
|
||||
.then((str) => {
|
||||
expect(str).to.eq('http://localhost:8888/__/#/tests/integration/s%25%26%3Fub/a.spec.js')
|
||||
})
|
||||
})
|
||||
|
||||
it('returns __all spec url', function () {
|
||||
return this.project.getSpecUrl()
|
||||
.then((str) => {
|
||||
expect(str).to.eq('http://localhost:8888/__/#/tests/__all')
|
||||
})
|
||||
})
|
||||
|
||||
it('returns __all spec url with spec is __all', function () {
|
||||
return this.project.getSpecUrl('__all')
|
||||
.then((str) => {
|
||||
expect(str).to.eq('http://localhost:8888/__/#/tests/__all')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('.add', () => {
|
||||
beforeEach(function () {
|
||||
this.pristinePath = Fixtures.projectPath('pristine')
|
||||
})
|
||||
|
||||
it('inserts path into cache', function () {
|
||||
return ProjectBase.add(this.pristinePath, {})
|
||||
return add(this.pristinePath, {})
|
||||
.then(() => cache.read()).then((json) => {
|
||||
expect(json.PROJECTS).to.deep.eq([this.pristinePath])
|
||||
})
|
||||
@@ -971,7 +912,7 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
it('returns object containing path and id', function () {
|
||||
sinon.stub(settings, 'read').resolves({ projectId: 'id-123' })
|
||||
|
||||
return ProjectBase.add(this.pristinePath, {})
|
||||
return add(this.pristinePath, {})
|
||||
.then((project) => {
|
||||
expect(project.id).to.equal('id-123')
|
||||
|
||||
@@ -984,7 +925,7 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
it('returns object containing just the path', function () {
|
||||
sinon.stub(settings, 'read').rejects()
|
||||
|
||||
return ProjectBase.add(this.pristinePath, {})
|
||||
return add(this.pristinePath, {})
|
||||
.then((project) => {
|
||||
expect(project.id).to.be.undefined
|
||||
|
||||
@@ -995,7 +936,7 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
|
||||
describe('if configFile is non-default', () => {
|
||||
it('doesn\'t cache anything and returns object containing just the path', function () {
|
||||
return ProjectBase.add(this.pristinePath, { configFile: false })
|
||||
return add(this.pristinePath, { configFile: false })
|
||||
.then((project) => {
|
||||
expect(project.id).to.be.undefined
|
||||
expect(project.path).to.equal(this.pristinePath)
|
||||
@@ -1009,12 +950,14 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
})
|
||||
|
||||
context('#createCiProject', () => {
|
||||
const projectRoot = '/_test-output/path/to/project-e2e'
|
||||
|
||||
beforeEach(function () {
|
||||
this.project = new ProjectBase({ projectRoot: '/_test-output/path/to/project-e2e', projectType: 'e2e' })
|
||||
this.project = new ProjectBase({ projectRoot, projectType: 'e2e' })
|
||||
this.newProject = { id: 'project-id-123' }
|
||||
|
||||
sinon.stub(this.project, 'writeProjectId').resolves('project-id-123')
|
||||
sinon.stub(user, 'ensureAuthToken').resolves('auth-token-123')
|
||||
sinon.stub(settings, 'write').resolves('project-id-123')
|
||||
sinon.stub(commitInfo, 'getRemoteOrigin').resolves('remoteOrigin')
|
||||
sinon.stub(api, 'createProject')
|
||||
.withArgs({ foo: 'bar' }, 'remoteOrigin', 'auth-token-123')
|
||||
@@ -1022,19 +965,19 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
})
|
||||
|
||||
it('calls api.createProject with user session', function () {
|
||||
return this.project.createCiProject({ foo: 'bar' }).then(() => {
|
||||
return createCiProject({ foo: 'bar' }, projectRoot).then(() => {
|
||||
expect(api.createProject).to.be.calledWith({ foo: 'bar' }, 'remoteOrigin', 'auth-token-123')
|
||||
})
|
||||
})
|
||||
|
||||
it('calls writeProjectId with id', function () {
|
||||
return this.project.createCiProject({ foo: 'bar' }).then(() => {
|
||||
expect(this.project.writeProjectId).to.be.calledWith('project-id-123')
|
||||
return createCiProject({ foo: 'bar' }, projectRoot).then(() => {
|
||||
expect(settings.write).to.be.calledWith(projectRoot, { projectId: 'project-id-123' })
|
||||
})
|
||||
})
|
||||
|
||||
it('returns project id', function () {
|
||||
return this.project.createCiProject({ foo: 'bar' }).then((projectId) => {
|
||||
return createCiProject({ foo: 'bar' }, projectRoot).then((projectId) => {
|
||||
expect(projectId).to.eql(this.newProject)
|
||||
})
|
||||
})
|
||||
@@ -1088,15 +1031,15 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
})
|
||||
|
||||
it('calls cache.removeProject with path', () => {
|
||||
return ProjectBase.remove('/_test-output/path/to/project-e2e').then(() => {
|
||||
return remove('/_test-output/path/to/project-e2e').then(() => {
|
||||
expect(cache.removeProject).to.be.calledWith('/_test-output/path/to/project-e2e')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('.id', () => {
|
||||
context('.getId', () => {
|
||||
it('returns project id', function () {
|
||||
return ProjectBase.id(this.todosPath).then((id) => {
|
||||
return getId(this.todosPath).then((id) => {
|
||||
expect(id).to.eq(this.projectId)
|
||||
})
|
||||
})
|
||||
@@ -1109,7 +1052,7 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
})
|
||||
|
||||
it('calls api.getOrgs', () => {
|
||||
return ProjectBase.getOrgs().then((orgs) => {
|
||||
return getOrgs().then((orgs) => {
|
||||
expect(orgs).to.deep.eq([])
|
||||
expect(api.getOrgs).to.be.calledOnce
|
||||
expect(api.getOrgs).to.be.calledWith('auth-token-123')
|
||||
@@ -1123,7 +1066,7 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
})
|
||||
|
||||
it('calls cache.getProjectRoots', () => {
|
||||
return ProjectBase.paths().then((ret) => {
|
||||
return paths().then((ret) => {
|
||||
expect(ret).to.deep.eq([])
|
||||
|
||||
expect(cache.getProjectRoots).to.be.calledOnce
|
||||
@@ -1142,7 +1085,7 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
})
|
||||
|
||||
it('returns array of objects with paths and ids', () => {
|
||||
return ProjectBase.getPathsAndIds().then((pathsAndIds) => {
|
||||
return getPathsAndIds().then((pathsAndIds) => {
|
||||
expect(pathsAndIds).to.eql([
|
||||
{
|
||||
path: '/path/to/first',
|
||||
@@ -1165,7 +1108,7 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
it('gets projects from api', () => {
|
||||
sinon.stub(api, 'getProjects').resolves([])
|
||||
|
||||
return ProjectBase.getProjectStatuses([])
|
||||
return getProjectStatuses([])
|
||||
.then(() => {
|
||||
expect(api.getProjects).to.have.been.calledWith('auth-token-123')
|
||||
})
|
||||
@@ -1174,7 +1117,7 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
it('returns array of projects', () => {
|
||||
sinon.stub(api, 'getProjects').resolves([])
|
||||
|
||||
return ProjectBase.getProjectStatuses([])
|
||||
return getProjectStatuses([])
|
||||
.then((projectsWithStatuses) => {
|
||||
expect(projectsWithStatuses).to.eql([])
|
||||
})
|
||||
@@ -1183,7 +1126,7 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
it('returns same number as client projects, even if there are less api projects', () => {
|
||||
sinon.stub(api, 'getProjects').resolves([])
|
||||
|
||||
return ProjectBase.getProjectStatuses([{}])
|
||||
return getProjectStatuses([{}])
|
||||
.then((projectsWithStatuses) => {
|
||||
expect(projectsWithStatuses.length).to.eql(1)
|
||||
})
|
||||
@@ -1192,7 +1135,7 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
it('returns same number as client projects, even if there are more api projects', () => {
|
||||
sinon.stub(api, 'getProjects').resolves([{}, {}])
|
||||
|
||||
return ProjectBase.getProjectStatuses([{}])
|
||||
return getProjectStatuses([{}])
|
||||
.then((projectsWithStatuses) => {
|
||||
expect(projectsWithStatuses.length).to.eql(1)
|
||||
})
|
||||
@@ -1203,7 +1146,7 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
{ id: 'id-123', lastBuildStatus: 'passing' },
|
||||
])
|
||||
|
||||
return ProjectBase.getProjectStatuses([{ id: 'id-123', path: '/_test-output/path/to/project' }])
|
||||
return getProjectStatuses([{ id: 'id-123', path: '/_test-output/path/to/project' }])
|
||||
.then((projectsWithStatuses) => {
|
||||
expect(projectsWithStatuses[0]).to.eql({
|
||||
id: 'id-123',
|
||||
@@ -1217,7 +1160,7 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
it('returns client project when it has no id', () => {
|
||||
sinon.stub(api, 'getProjects').resolves([])
|
||||
|
||||
return ProjectBase.getProjectStatuses([{ path: '/_test-output/path/to/project' }])
|
||||
return getProjectStatuses([{ path: '/_test-output/path/to/project' }])
|
||||
.then((projectsWithStatuses) => {
|
||||
expect(projectsWithStatuses[0]).to.eql({
|
||||
path: '/_test-output/path/to/project',
|
||||
@@ -1234,7 +1177,7 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
it('marks project as invalid if api 404s', () => {
|
||||
sinon.stub(api, 'getProject').rejects({ name: '', message: '', statusCode: 404 })
|
||||
|
||||
return ProjectBase.getProjectStatuses([{ id: 'id-123', path: '/_test-output/path/to/project' }])
|
||||
return getProjectStatuses([{ id: 'id-123', path: '/_test-output/path/to/project' }])
|
||||
.then((projectsWithStatuses) => {
|
||||
expect(projectsWithStatuses[0]).to.eql({
|
||||
id: 'id-123',
|
||||
@@ -1247,7 +1190,7 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
it('marks project as unauthorized if api 403s', () => {
|
||||
sinon.stub(api, 'getProject').rejects({ name: '', message: '', statusCode: 403 })
|
||||
|
||||
return ProjectBase.getProjectStatuses([{ id: 'id-123', path: '/_test-output/path/to/project' }])
|
||||
return getProjectStatuses([{ id: 'id-123', path: '/_test-output/path/to/project' }])
|
||||
.then((projectsWithStatuses) => {
|
||||
expect(projectsWithStatuses[0]).to.eql({
|
||||
id: 'id-123',
|
||||
@@ -1260,7 +1203,7 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
it('merges in project details and marks valid if somehow project exists and is authorized', () => {
|
||||
sinon.stub(api, 'getProject').resolves({ id: 'id-123', lastBuildStatus: 'passing' })
|
||||
|
||||
return ProjectBase.getProjectStatuses([{ id: 'id-123', path: '/_test-output/path/to/project' }])
|
||||
return getProjectStatuses([{ id: 'id-123', path: '/_test-output/path/to/project' }])
|
||||
.then((projectsWithStatuses) => {
|
||||
expect(projectsWithStatuses[0]).to.eql({
|
||||
id: 'id-123',
|
||||
@@ -1276,7 +1219,7 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
|
||||
sinon.stub(api, 'getProject').rejects(error)
|
||||
|
||||
return ProjectBase.getProjectStatuses([{ id: 'id-123', path: '/_test-output/path/to/project' }])
|
||||
return getProjectStatuses([{ id: 'id-123', path: '/_test-output/path/to/project' }])
|
||||
.then(() => {
|
||||
throw new Error('should have caught error but did not')
|
||||
}).catch((err) => {
|
||||
@@ -1299,7 +1242,7 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
it('gets project from api', function () {
|
||||
sinon.stub(api, 'getProject').resolves([])
|
||||
|
||||
return ProjectBase.getProjectStatus(this.clientProject)
|
||||
return getProjectStatus(this.clientProject)
|
||||
.then(() => {
|
||||
expect(api.getProject).to.have.been.calledWith('id-123', 'auth-token-123')
|
||||
})
|
||||
@@ -1310,7 +1253,7 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
lastBuildStatus: 'passing',
|
||||
})
|
||||
|
||||
return ProjectBase.getProjectStatus(this.clientProject)
|
||||
return getProjectStatus(this.clientProject)
|
||||
.then((project) => {
|
||||
expect(project).to.eql({
|
||||
id: 'id-123',
|
||||
@@ -1326,7 +1269,7 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
|
||||
this.clientProject.id = undefined
|
||||
|
||||
return ProjectBase.getProjectStatus(this.clientProject)
|
||||
return getProjectStatus(this.clientProject)
|
||||
.then((project) => {
|
||||
expect(project).to.eql({
|
||||
id: undefined,
|
||||
@@ -1341,7 +1284,7 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
it('marks project as invalid if api 404s', function () {
|
||||
sinon.stub(api, 'getProject').rejects({ name: '', message: '', statusCode: 404 })
|
||||
|
||||
return ProjectBase.getProjectStatus(this.clientProject)
|
||||
return getProjectStatus(this.clientProject)
|
||||
.then((project) => {
|
||||
expect(project).to.eql({
|
||||
id: 'id-123',
|
||||
@@ -1354,7 +1297,7 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
it('marks project as unauthorized if api 403s', function () {
|
||||
sinon.stub(api, 'getProject').rejects({ name: '', message: '', statusCode: 403 })
|
||||
|
||||
return ProjectBase.getProjectStatus(this.clientProject)
|
||||
return getProjectStatus(this.clientProject)
|
||||
.then((project) => {
|
||||
expect(project).to.eql({
|
||||
id: 'id-123',
|
||||
@@ -1369,7 +1312,7 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
|
||||
|
||||
sinon.stub(api, 'getProject').rejects(error)
|
||||
|
||||
return ProjectBase.getProjectStatus(this.clientProject)
|
||||
return getProjectStatus(this.clientProject)
|
||||
.then(() => {
|
||||
throw new Error('should have caught error but did not')
|
||||
}).catch((err) => {
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
import Chai from 'chai'
|
||||
import { getSpecUrl, checkSupportFile } from '../../lib/project_utils'
|
||||
import Fixtures from '../support/helpers/fixtures'
|
||||
import path from 'path'
|
||||
|
||||
const todosPath = Fixtures.projectPath('todos')
|
||||
|
||||
const defaultProps = {
|
||||
browserUrl: 'http://localhost:8888/__/',
|
||||
componentFolder: path.join(todosPath, 'component'),
|
||||
integrationFolder: path.join(todosPath, 'tests'),
|
||||
projectRoot: todosPath,
|
||||
specType: 'integration',
|
||||
} as const
|
||||
|
||||
const expect = Chai.expect
|
||||
|
||||
describe('lib/project_utils', () => {
|
||||
describe('getSpecUrl', () => {
|
||||
it('normalizes to __all when absoluteSpecUrl is undefined', () => {
|
||||
const str = getSpecUrl({
|
||||
...defaultProps,
|
||||
absoluteSpecPath: undefined,
|
||||
})
|
||||
|
||||
expect(str).to.eq('http://localhost:8888/__/#/tests/__all')
|
||||
})
|
||||
|
||||
it('normalizes to __all when absoluteSpecUrl is __all', () => {
|
||||
const str = getSpecUrl({
|
||||
...defaultProps,
|
||||
absoluteSpecPath: '__all',
|
||||
})
|
||||
|
||||
expect(str).to.eq('http://localhost:8888/__/#/tests/__all')
|
||||
})
|
||||
|
||||
it('normalizes to __all when absoluteSpecUrl is __all', () => {
|
||||
const str = getSpecUrl({
|
||||
...defaultProps,
|
||||
absoluteSpecPath: '__all',
|
||||
})
|
||||
|
||||
expect(str).to.eq('http://localhost:8888/__/#/tests/__all')
|
||||
})
|
||||
|
||||
it('returns fully qualified url when spec exists', function () {
|
||||
const str = getSpecUrl({
|
||||
...defaultProps,
|
||||
absoluteSpecPath: 'cypress/integration/bar.js',
|
||||
})
|
||||
|
||||
expect(str).to.eq('http://localhost:8888/__/#/tests/cypress/integration/bar.js')
|
||||
})
|
||||
|
||||
it('returns fully qualified url on absolute path to spec', function () {
|
||||
const todosSpec = path.join(todosPath, 'tests/sub/sub_test.coffee')
|
||||
const str = getSpecUrl({
|
||||
...defaultProps,
|
||||
absoluteSpecPath: todosSpec,
|
||||
})
|
||||
|
||||
expect(str).to.eq('http://localhost:8888/__/#/tests/integration/sub/sub_test.coffee')
|
||||
})
|
||||
|
||||
it('escapses %, &', function () {
|
||||
const todosSpec = path.join(todosPath, 'tests/sub/a&b%c.js')
|
||||
const str = getSpecUrl({
|
||||
...defaultProps,
|
||||
absoluteSpecPath: todosSpec,
|
||||
})
|
||||
|
||||
expect(str).to.eq('http://localhost:8888/__/#/tests/integration/sub/a%26b%25c.js')
|
||||
})
|
||||
|
||||
// ? is invalid in Windows, but it can be tested here
|
||||
// because it's a unit test and doesn't check the existence of files
|
||||
it('escapes ?', function () {
|
||||
const todosSpec = path.join(todosPath, 'tests/sub/a?.spec.js')
|
||||
const str = getSpecUrl({
|
||||
...defaultProps,
|
||||
absoluteSpecPath: todosSpec,
|
||||
})
|
||||
|
||||
expect(str).to.eq('http://localhost:8888/__/#/tests/integration/sub/a%3F.spec.js')
|
||||
})
|
||||
|
||||
it('escapes %, &, ? in the url dir', function () {
|
||||
const todosSpec = path.join(todosPath, 'tests/s%&?ub/a.spec.js')
|
||||
const str = getSpecUrl({
|
||||
...defaultProps,
|
||||
absoluteSpecPath: todosSpec,
|
||||
})
|
||||
|
||||
expect(str).to.eq('http://localhost:8888/__/#/tests/integration/s%25%26%3Fub/a.spec.js')
|
||||
})
|
||||
})
|
||||
|
||||
describe('checkSupportFile', () => {
|
||||
it('does nothing when {supportFile: false}', async () => {
|
||||
const ret = await checkSupportFile({
|
||||
configFile: 'cypress.json',
|
||||
supportFile: false,
|
||||
})
|
||||
|
||||
expect(ret).to.be.undefined
|
||||
})
|
||||
|
||||
it('throws when support file does not exist', async () => {
|
||||
try {
|
||||
await checkSupportFile({
|
||||
configFile: 'cypress.json',
|
||||
supportFile: '/this/file/does/not/exist/foo/bar/cypress/support/index.js',
|
||||
})
|
||||
} catch (e) {
|
||||
expect(e.message).to.include('The support file is missing or invalid.')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -28,8 +28,10 @@ describe('lib/scaffold', () => {
|
||||
it('is true when integrationFolder is empty', function () {
|
||||
const pristine = new ProjectBase({ projectRoot: this.pristinePath, projectType: 'e2e' })
|
||||
|
||||
return pristine.getConfig()
|
||||
.then((cfg) => {
|
||||
return pristine.initializeConfig()
|
||||
.then(() => {
|
||||
return pristine.getConfig()
|
||||
}).then((cfg) => {
|
||||
return pristine.determineIsNewProject(cfg)
|
||||
}).then((ret) => {
|
||||
expect(ret).to.be.true
|
||||
@@ -37,10 +39,18 @@ describe('lib/scaffold', () => {
|
||||
})
|
||||
|
||||
it('is false when integrationFolder has been changed', function () {
|
||||
const pristine = new ProjectBase({ projectRoot: this.pristinePath, projectType: 'e2e' })
|
||||
const pristine = new ProjectBase({
|
||||
projectRoot: this.pristinePath,
|
||||
projectType: 'e2e',
|
||||
options: {
|
||||
integrationFolder: 'foo',
|
||||
},
|
||||
})
|
||||
|
||||
return pristine.getConfig({ integrationFolder: 'foo' })
|
||||
.then((cfg) => {
|
||||
return pristine.initializeConfig()
|
||||
.then(() => {
|
||||
return pristine.getConfig()
|
||||
}).then((cfg) => {
|
||||
return pristine.determineIsNewProject(cfg)
|
||||
}).then((ret) => {
|
||||
expect(ret).to.be.false
|
||||
@@ -53,8 +63,10 @@ describe('lib/scaffold', () => {
|
||||
|
||||
this.ids = new ProjectBase({ projectRoot: idsPath, projectType: 'e2e' })
|
||||
|
||||
return this.ids.getConfig()
|
||||
.then((cfg) => {
|
||||
return this.ids.initializeConfig()
|
||||
.then(() => {
|
||||
return this.ids.getConfig()
|
||||
}).then((cfg) => {
|
||||
return this.ids.scaffold(cfg).return(cfg)
|
||||
}).then((cfg) => {
|
||||
return this.ids.determineIsNewProject(cfg)
|
||||
@@ -68,9 +80,13 @@ describe('lib/scaffold', () => {
|
||||
|
||||
this.todos = new ProjectBase({ projectRoot: todosPath, projectType: 'e2e' })
|
||||
|
||||
return this.todos.getConfig()
|
||||
.then((cfg) => {
|
||||
return this.todos.scaffold(cfg).return(cfg)
|
||||
return this.todos.initializeConfig()
|
||||
.then(() => {
|
||||
return this.todos.getConfig()
|
||||
}).then((cfg) => {
|
||||
return this.todos.scaffold(cfg)
|
||||
}).then(() => {
|
||||
return this.todos.getConfig()
|
||||
}).then((cfg) => {
|
||||
return this.todos.determineIsNewProject(cfg)
|
||||
}).then((ret) => {
|
||||
@@ -84,9 +100,13 @@ describe('lib/scaffold', () => {
|
||||
it('is true when files, name + bytes match to scaffold', function () {
|
||||
const pristine = new ProjectBase({ projectRoot: this.pristinePath, projectType: 'e2e' })
|
||||
|
||||
return pristine.getConfig()
|
||||
.then((cfg) => {
|
||||
return pristine.scaffold(cfg).return(cfg)
|
||||
return pristine.initializeConfig()
|
||||
.then(() => {
|
||||
return pristine.getConfig()
|
||||
}).then((cfg) => {
|
||||
return pristine.scaffold(cfg)
|
||||
}).then(() => {
|
||||
return pristine.getConfig()
|
||||
}).then((cfg) => {
|
||||
return pristine.determineIsNewProject(cfg)
|
||||
}).then((ret) => {
|
||||
@@ -97,9 +117,13 @@ describe('lib/scaffold', () => {
|
||||
it('is false when bytes dont match scaffold', function () {
|
||||
const pristine = new ProjectBase({ projectRoot: this.pristinePath, projectType: 'e2e' })
|
||||
|
||||
return pristine.getConfig()
|
||||
.then((cfg) => {
|
||||
return pristine.scaffold(cfg).return(cfg)
|
||||
return pristine.initializeConfig()
|
||||
.then(() => {
|
||||
return pristine.getConfig()
|
||||
}).then((cfg) => {
|
||||
return pristine.scaffold(cfg)
|
||||
}).then(() => {
|
||||
return pristine.getConfig()
|
||||
}).then((cfg) => {
|
||||
const file = path.join(cfg.integrationFolder, '1-getting-started', 'todo.spec.js')
|
||||
|
||||
@@ -109,8 +133,10 @@ describe('lib/scaffold', () => {
|
||||
.then((str) => {
|
||||
str += 'foo bar baz'
|
||||
|
||||
return fs.writeFileAsync(file, str).return(cfg)
|
||||
return fs.writeFileAsync(file, str)
|
||||
})
|
||||
}).then(() => {
|
||||
return pristine.getConfig()
|
||||
}).then((cfg) => {
|
||||
return pristine.determineIsNewProject(cfg)
|
||||
}).then((ret) => {
|
||||
|
||||
Reference in New Issue
Block a user