@revoloo/cypress6
Version:
Cypress.io end to end testing tool
898 lines (718 loc) • 33.3 kB
JavaScript
const { _ } = Cypress
const { each, flow, get, isString, join, map, merge, set, sortBy, toPairs } = require('lodash/fp')
describe('Settings', () => {
beforeEach(function () {
cy.fixture('user').as('user')
cy.fixture('config').as('config')
cy.fixture('projects').as('projects')
cy.fixture('projects_statuses').as('projectStatuses')
cy.fixture('specs').as('specs')
cy.fixture('runs').as('runs')
cy.fixture('keys').as('keys')
this.goToSettings = () => {
cy.get('.navbar-default')
cy.get('a').contains('Settings').click()
// make sure the common sections are shown
cy.get('.settings-config')
cy.get('.settings-proxy')
}
cy.visitIndex().then(function (win) {
let start = win.App.start
this.win = win
this.ipc = win.App.ipc
cy.stub(this.ipc, 'getOptions').resolves({ projectRoot: '/foo/bar' })
cy.stub(this.ipc, 'getCurrentUser').resolves(this.user)
cy.stub(this.ipc, 'updaterCheck').resolves(false)
cy.stub(this.ipc, 'getSpecs').yields(null, this.specs)
cy.stub(this.ipc, 'closeBrowser').resolves()
cy.stub(this.ipc, 'closeProject').resolves()
cy.stub(this.ipc, 'pingApiServer').resolves()
cy.stub(this.ipc, 'onConfigChanged')
cy.stub(this.ipc, 'onFocusTests')
cy.stub(this.ipc, 'externalOpen')
cy.stub(this.ipc, 'setClipboardText')
this.openProject = this.util.deferred()
cy.stub(this.ipc, 'openProject').returns(this.openProject.promise)
this.getProjectStatus = this.util.deferred()
cy.stub(this.ipc, 'getProjectStatus').returns(this.getProjectStatus.promise)
this.getRecordKeys = this.util.deferred()
cy.stub(this.ipc, 'getRecordKeys').returns(this.getRecordKeys.promise)
start()
})
})
describe('general functionality', () => {
beforeEach(function () {
this.openProject.resolve(this.config)
this.projectStatuses[0].id = this.config.projectId
this.getProjectStatus.resolve(this.projectStatuses[0])
this.goToSettings()
})
it('navigates to settings page', () => {
cy.contains('Configuration')
})
it('highlight settings nav', () => {
cy.contains('a', 'Settings').should('have.class', 'active')
})
it('collapses panels by default', function () {
cy.contains('Your project\'s configuration is displayed').should('not.exist')
cy.contains('Record Keys allow you to').should('not.exist')
cy.contains(this.config.projectId).should('not.exist')
cy.percySnapshot()
})
context('on:focus:tests clicked', () => {
beforeEach(function () {
this.ipc.onFocusTests.yield()
})
it('routes to specs page', () => {
cy.shouldBeOnProjectSpecs()
})
})
})
/**
* Opens "Configuration" panel of the Settings tab
* and checks that configuration element is fully visible.
* This helps to ensure no flake down the line
*/
const openConfiguration = () => {
cy.contains('Configuration').click()
cy.get('.config-vars').should('be.visible')
.invoke('height').should('be.gt', 400)
}
describe('configuration panel', () => {
describe('displays config', () => {
beforeEach(function () {
this.openProject.resolve(this.config)
this.projectStatuses[0].id = this.config.projectId
this.getProjectStatus.resolve(this.projectStatuses[0])
this.goToSettings()
openConfiguration()
})
it('displays config section', () => {
cy.contains('Your project\'s configuration is displayed')
})
it('displays browser information which is collapsed by default', () => {
cy.contains('.config-vars', 'browsers')
cy.get('.config-vars').invoke('text')
.should('not.contain', '0:Chrome')
cy.contains('span', 'browsers').parents('div').first().find('span').first().click()
cy.get('.config-vars').invoke('text')
.should('contain', '0:Chrome')
// make sure the main collapsible content
// has finished animating and that it has
// an empty inline style attribute
cy.get('.rc-collapse-content')
.should('not.have.class', 'rc-collapse-anim')
.should('have.attr', 'style', '')
cy.percySnapshot()
})
it('removes the summary list of values once a key is expanded', () => {
cy.contains('span', 'browsers').parents('div').first().find('span').first().click()
cy.get('.config-vars').invoke('text')
.should('not.contain', 'Chrome, Chromium')
cy.get('.config-vars').invoke('text')
.should('contain', '0:Chrome')
})
it('distinguishes between Arrays and Objects when expanded', () => {
cy.get('.config-vars').invoke('text')
.should('not.contain', 'browsers: Array (4)')
cy.contains('span', 'browsers').parents('div').first().find('span').first().click()
cy.get('.config-vars').invoke('text')
.should('contain', 'browsers: Array (4)')
})
it('applies the same color treatment to expanded key values as the root key', () => {
cy.contains('span', 'browsers').parents('div').first().find('span').first().click()
cy.get('.config-vars').as('config-vars')
.contains('span', 'Chrome').parent('span').should('have.class', 'plugin')
cy.get('@config-vars')
.contains('span', 'Chromium').parent('span').should('have.class', 'plugin')
cy.get('@config-vars')
.contains('span', 'Canary').parent('span').should('have.class', 'plugin')
cy.get('@config-vars')
.contains('span', 'Electron').parent('span').should('have.class', 'plugin')
cy.contains('span', 'blockHosts').parents('div').first().find('span').first().click()
cy.get('@config-vars')
.contains('span', 'www.google-analytics.com').parent('span').should('have.class', 'config')
cy.get('@config-vars')
.contains('span', 'hotjar.com').parent('span').should('have.class', 'config')
cy.contains('span', 'hosts').parents('div').first().find('span').first().click()
cy.get('@config-vars')
.contains('span', '127.0.0.1').parent('span').should('have.class', 'config')
cy.get('@config-vars')
.contains('span', '127.0.0.2').parent('span').should('have.class', 'config')
cy.get('@config-vars')
.contains('span', 'Electron').parents('div').first().find('span').first().click()
cy.get('@config-vars').contains('span', 'electron').parents('li').eq(1).find('.line .plugin').should('have.length', 6)
})
it('displays string values as quoted strings', () => {
cy.get('.config-vars').invoke('text')
.should('contain', 'baseUrl:"http://localhost:8080"')
})
it('displays undefined and null without quotations', () => {
cy.get('.config-vars').invoke('text')
.should('not.contain', '"undefined"')
.should('not.contain', '"null"')
})
it('does not show the root config label', () => {
cy.get('.config-vars').find('> ol > li > div').should('have.css', 'display', 'none')
})
it('displays legend in table', () => {
cy.get('table>tbody>tr').should('have.length', 6)
})
it('displays "true" values', () => {
cy.get('.line').contains('true')
})
it('displays "null" values', () => {
cy.get('.line').contains('null')
})
it('displays "object" values for env and hosts', () => {
cy.get('.line').contains('www.google-analytics.com, hotjar.com')
cy.get('.line').contains('*.foobar.com, *.bazqux.com')
})
it('displays "array" values for blockHosts', () => {
cy.contains('.line', 'blockHosts').contains('www.google-analytics.com, hotjar.com')
})
it('opens help link on click', () => {
cy.get('.settings-config .learn-more').click().then(function () {
expect(this.ipc.externalOpen).to.be.calledWithMatch({ url: 'https://on.cypress.io/guides/configuration' })
})
})
it('displays null when env settings are empty or not defined', function () {
this.ipc.openProject.resolves(setConfigEnv(this.config, undefined))
this.ipc.onConfigChanged.yield()
cy.contains('.line', 'env:null').then(() => {
this.ipc.openProject.resolves(this.config)
this.ipc.onConfigChanged.yield()
cy.contains('.line', 'env:fileServerFolder')
.then(() => {
this.ipc.openProject.resolves(setConfigEnv(this.config, null))
this.ipc.onConfigChanged.yield()
cy.contains('.line', 'env:null').then(() => {
this.ipc.openProject.resolves(this.config)
this.ipc.onConfigChanged.yield()
cy.contains('.line', 'env:fileServerFolder')
.then(() => {
this.ipc.openProject.resolves(setConfigEnv(this.config, {}))
this.ipc.onConfigChanged.yield()
cy.contains('.line', 'env:null')
})
})
})
})
})
it('displays env settings', () => {
cy.get('@config').then(({ resolved }) => {
const getEnvKeys = flow([
get('env'),
toPairs,
map(([key]) => key),
sortBy(get('')),
])
const assertKeyExists = each((key) => cy.contains('.line', key))
const assertKeyValuesExists = flow([
map((key) => {
return flow([
get(['env', key, 'value']),
(v) => {
if (isString(v)) {
return `"${v}"`
}
return v
},
])(resolved)
}),
each((v) => {
cy.contains('.key-value-pair-value', v)
}),
])
const assertFromTooltipsExist = flow([
map((key) => {
return [key,
flow([
get(['env', key, 'from']),
(from) => `.${from}`,
])(resolved)]
}),
each(([key, fromTooltipClassName]) => {
cy.contains(key).parents('.line').first().find(fromTooltipClassName)
}),
])
cy.contains('.line', 'env').contains(flow([getEnvKeys, join(', ')])(resolved))
cy.contains('.line', 'env').click()
flow([getEnvKeys, assertKeyExists])(resolved)
flow([getEnvKeys, assertKeyValuesExists])(resolved)
flow([getEnvKeys, assertFromTooltipsExist])(resolved)
})
})
})
context('on config changes', () => {
beforeEach(function () {
this.projectStatuses[0].id = this.config.projectId
this.getProjectStatus.resolve(this.projectStatuses[0])
const newConfig = this.util.deepClone(this.config)
newConfig.clientUrl = 'http://localhost:8888'
newConfig.clientUrlDisplay = 'http://localhost:8888'
newConfig.browsers = this.browsers
this.openProject.resolve(newConfig)
this.goToSettings()
openConfiguration()
})
it('displays updated config', function () {
const newConfig = this.util.deepClone(this.config)
newConfig.resolved.baseUrl.value = 'http://localhost:7777'
this.ipc.openProject.onCall(1).resolves(newConfig)
this.ipc.onConfigChanged.yield()
cy.contains('http://localhost:7777')
})
})
context('when configFile is false', () => {
beforeEach(function () {
this.openProject.resolve(Cypress._.assign({
configFile: false,
}, this.config))
this.goToSettings()
openConfiguration()
})
it('notes that cypress.json is disabled', () => {
cy.contains('set from cypress.json file (currently disabled by --config-file false)')
})
})
context('when configFile is set', function () {
beforeEach(function () {
this.openProject.resolve(Cypress._.assign({
configFile: 'special-cypress.json',
}, this.config))
this.goToSettings()
openConfiguration()
})
it('notes that a custom config is in use', () => {
cy.contains('set from custom config file special-cypress.json')
})
})
})
describe('project id panel', () => {
beforeEach(function () {
this.openProject.resolve(this.config)
this.projectStatuses[0].id = this.config.projectId
this.getProjectStatus.resolve(this.projectStatuses[0])
this.goToSettings()
cy.contains('Project ID').click()
})
it('displays project id section', function () {
cy.contains(this.config.projectId)
cy.percySnapshot()
})
it('shows tooltip on hover of copy to clipboard', () => {
cy.get('.action-copy').trigger('mouseover')
cy.get('.cy-tooltip').should('contain', 'Copy to clipboard')
})
it('copies project id config to clipboard', function () {
cy.get('.action-copy').click()
.then(() => {
const expectedJsonConfig = {
projectId: this.config.projectId,
}
const expectedCopyCommand = JSON.stringify(expectedJsonConfig, null, 2)
expect(this.ipc.setClipboardText).to.be.calledWith(expectedCopyCommand)
})
})
})
describe('record key panel', () => {
context('when project is set up and you have access', () => {
beforeEach(function () {
this.openProject.resolve(this.config)
this.projectStatuses[0].id = this.config.projectId
this.getProjectStatus.resolve(this.projectStatuses[0])
this.goToSettings()
cy.contains('Record Key').click()
})
it('displays record key section', () => {
cy.contains('A Record Key sends')
})
it('opens ci guide when learn more is clicked', () => {
cy.get('.settings-record-key').contains('Learn more').click().then(function () {
expect(this.ipc.externalOpen).to.be.calledWithMatch({ url: 'https://on.cypress.io/what-is-a-record-key' })
})
})
it('loads the projects record key', function () {
expect(this.ipc.getRecordKeys).to.be.called
})
it('shows spinner', () => {
cy.get('.settings-record-key .fa-spinner')
})
describe('when record key loads', () => {
beforeEach(function () {
this.getRecordKeys.resolve(this.keys)
})
it('displays first Record Key', function () {
cy.get('.loading-record-keys').should('not.exist')
cy.get('.settings-record-key')
.contains(`cypress run --record --key ${this.keys[0].id}`)
cy.percySnapshot()
})
it('shows tooltip on hover of copy to clipboard', () => {
cy.get('.settings-record-key').find('.action-copy').trigger('mouseover')
cy.get('.cy-tooltip').should('contain', 'Copy to clipboard')
})
it('copies record key command to clipboard', function () {
cy.get('.settings-record-key').find('.action-copy').click()
.then(() => {
expect(this.ipc.setClipboardText).to.be.calledWith(`cypress run --record --key ${this.keys[0].id}`)
})
})
it('opens admin project settings when record key link is clicked', () => {
cy.get('.settings-record-key').contains('You can change').click().then(function () {
expect(this.ipc.externalOpen).to.be.calledWith(`https://on.cypress.io/dashboard/projects/${this.config.projectId}/settings`)
})
})
})
describe('when there are no keys', () => {
beforeEach(function () {
this.getRecordKeys.resolve([])
})
it('displays empty message', () => {
cy.get('.settings-record-key .empty-well').should('contain', 'This project has no record keys')
cy.percySnapshot()
})
it('opens dashboard project settings when clicking \'Dashboard\'', () => {
cy.get('.settings-record-key .empty-well a').click().then(function () {
expect(this.ipc.externalOpen).to.be.calledWith(`https://on.cypress.io/dashboard/projects/${this.config.projectId}/settings`)
})
})
})
describe('when the user is logged out', () => {
beforeEach(function () {
this.getRecordKeys.resolve([])
cy.logOut()
})
it('shows message that user must be logged in to view record keys', () => {
// turn off animation to ensure panel is fully expanded in time for percy snapshot
cy.get('body').then(($body) => {
$body.append('<style>.rc-collapse-anim-active { transition: none !important; }<style>')
})
cy.get('.empty-well').should('contain', 'must be logged in')
cy.percySnapshot()
})
it('opens login modal after clicking \'Log In\'', () => {
cy.get('.empty-well button').click()
cy.get('.login')
})
it('re-loads and shows the record key when user logs in', function () {
cy.stub(this.ipc, 'beginAuth').resolves(this.user)
this.ipc.getRecordKeys.onCall(1).resolves(this.keys)
// turn off animation to ensure panel is fully expanded in time for percy snapshot
cy.get('body').then(($body) => {
$body.append('<style>.rc-collapse-anim-active { transition: none !important; }<style>')
})
cy.get('.empty-well button').click()
cy.contains('Log In to Dashboard').click().should(() => {
expect(this.ipc.getRecordKeys).to.be.calledTwice
})
cy.get('.settings-record-key')
.contains(`cypress run --record --key ${this.keys[0].id}`)
// extra insurance that panel in background is fully expanded
cy.contains('You can change this key')
cy.percySnapshot()
})
})
})
context('when project is not set up for CI', () => {
it('does not show ci Keys section when project has no id', function () {
const newConfig = this.util.deepClone(this.config)
newConfig.projectId = null
this.openProject.resolve(newConfig)
this.getProjectStatus.resolve(this.projectStatuses)
this.goToSettings()
cy.contains('h5', 'Record Keys').should('not.exist')
cy.percySnapshot()
})
it('does not show ci Keys section when project is invalid', function () {
this.openProject.resolve(this.config)
this.projectStatuses[0].state = 'INVALID'
this.getProjectStatus.resolve(this.projectStatuses[0])
this.goToSettings()
cy.contains('h5', 'Record Keys').should('not.exist')
cy.percySnapshot()
})
})
context('when you are not a user of this projects org', () => {
beforeEach(function () {
this.openProject.resolve(this.config)
})
it('does not show record key', function () {
this.projectStatuses[0].state = 'UNAUTHORIZED'
this.getProjectStatus.resolve(this.projectStatuses[0])
this.goToSettings()
cy.contains('h5', 'Record Keys').should('not.exist')
})
})
})
describe('node version panel', () => {
const bundledNodeVersion = '1.2.3'
const systemNodePath = '/foo/bar/node'
const systemNodeVersion = '4.5.6'
beforeEach(function () {
this.navigateWithConfig = function (config) {
this.openProject.resolve(_.defaults(config, this.config))
this.projectStatuses[0].id = this.config.projectId
this.getProjectStatus.resolve(this.projectStatuses[0])
this.goToSettings()
}
})
it('with bundled node informs user we\'re using bundled node', function () {
this.navigateWithConfig({})
cy.contains(`Node.js Version (${bundledNodeVersion})`).click()
cy.get('.node-version')
.should('contain', 'bundled with Cypress')
.should('not.contain', systemNodePath)
.should('not.contain', systemNodeVersion)
cy.percySnapshot()
})
it('with custom node displays path to custom node', function () {
this.navigateWithConfig({
resolvedNodePath: systemNodePath,
resolvedNodeVersion: systemNodeVersion,
})
cy.contains(`Node.js Version (${systemNodeVersion})`).click()
cy.get('.node-version')
.should('contain', systemNodePath)
.should('contain', systemNodeVersion)
.should('not.contain', bundledNodeVersion)
})
})
describe('proxy settings panel', () => {
beforeEach(function () {
this.openProject.resolve(this.config)
this.config.resolved.baseUrl.value = 'http://localhost:7777'
this.projectStatuses[0].id = this.config.projectId
this.getProjectStatus.resolve(this.projectStatuses[0])
this.goToSettings()
cy.contains('Proxy Settings').click()
})
it('with no proxy config set informs the user no proxy configuration is active', () => {
cy.get('.settings-proxy').should('contain', 'There is no active proxy configuration.')
})
it('opens help link on click', () => {
cy.get('.settings-proxy .learn-more').click().then(function () {
expect(this.ipc.externalOpen).to.be.calledWithMatch({ url: 'https://on.cypress.io/proxy-configuration' })
})
})
it('with Windows proxy settings indicates proxy and the source', () => {
cy.setAppStore({
projectRoot: '/foo/bar',
proxySource: 'win32',
proxyServer: 'http://foo-bar.baz',
proxyBypassList: 'a,b,c,d',
})
cy.get('.settings-proxy').should('contain', 'from Windows system settings')
cy.get('.settings-proxy tr:nth-child(1) > td > code').should('contain', 'http://foo-bar.baz')
cy.get('.settings-proxy tr:nth-child(2) > td > code').should('contain', 'a, b, c, d')
cy.percySnapshot()
})
it('with environment proxy settings indicates proxy and the source', () => {
cy.setAppStore({
projectRoot: '/foo/bar',
proxyServer: 'http://foo-bar.baz',
proxyBypassList: 'a,b,c,d',
})
})
it('with no bypass list but a proxy set shows \'none\' in bypass list', () => {
cy.setAppStore({
projectRoot: '/foo/bar',
proxyServer: 'http://foo-bar.baz',
})
cy.get('.settings-proxy tr:nth-child(2) > td').should('contain', 'none')
})
})
describe('experiments panel', () => {
const hasNoExperimentsPanel = () => {
// there are several settings panels,
// let's make sure they are loaded
cy.get('[class*=settings-]').should('have.length.gt', 1)
// but the experiments panel should not be there at all
cy.get('.settings-experiments').should('not.exist')
}
describe('no experimental features turned on', () => {
beforeEach(function () {
this.openProject.resolve(this.config)
this.projectStatuses[0].id = this.config.projectId
this.getProjectStatus.resolve(this.projectStatuses[0])
this.goToSettings()
})
it('displays panel with no experiments', () => {
hasNoExperimentsPanel()
cy.percySnapshot()
})
})
describe('unknown experiments', () => {
beforeEach(function () {
this.config.experimentalFoo = true
this.config.resolved.experimentalFoo = {
value: true,
}
this.openProject.resolve(this.config)
this.projectStatuses[0].id = this.config.projectId
this.getProjectStatus.resolve(this.projectStatuses[0])
this.goToSettings()
})
it('are not shown', () => {
hasNoExperimentsPanel()
})
})
describe('experimental feature exists', () => {
beforeEach(function () {
// do not overwrite the shared object reference -
// because it is used by the app's code.
this.win.experimental.names.experimentalCoolFeature = 'Cool Feature'
this.win.experimental.summaries.experimentalCoolFeature = 'Enables super cool feature from Cypress where you can see the cool feature'
})
const hasLearnMoreLink = () => {
cy.get('[data-cy=experiments]').contains('a', 'Learn more').click()
.then(function () {
expect(this.ipc.externalOpen).to.be.calledWithMatch({ url: 'https://on.cypress.io/experiments' })
})
}
context('enabled', () => {
beforeEach(function () {
this.config.experimentalCoolFeature = true
this.config.resolved.experimentalCoolFeature = {
value: true,
}
this.openProject.resolve(this.config)
this.projectStatuses[0].id = this.config.projectId
this.getProjectStatus.resolve(this.projectStatuses[0])
this.goToSettings()
cy.contains('Experiments').click()
})
it('has learn more link', hasLearnMoreLink)
it('displays experiment', () => {
cy.get('.settings-experiments').contains('Cool Feature')
cy.get('.experiment-status-sign')
.should('have.class', 'enabled')
.and('have.text', 'enabled')
cy.percySnapshot()
})
})
context('disabled', () => {
beforeEach(function () {
this.config.experimentalCoolFeature = false
this.config.resolved.experimentalCoolFeature = {
value: false,
from: 'default',
}
this.openProject.resolve(this.config)
this.projectStatuses[0].id = this.config.projectId
this.getProjectStatus.resolve(this.projectStatuses[0])
this.goToSettings()
cy.contains('Experiments').click()
})
it('displays experiment', () => {
cy.get('.settings-experiments').contains('Cool Feature')
cy.get('.experiment-status-sign')
.should('have.class', 'disabled')
.and('have.text', 'disabled')
cy.percySnapshot()
})
})
})
})
describe('file preference panel', () => {
const availableEditors = [
{ id: 'atom', name: 'Atom', isOther: false, openerId: 'atom' },
{ id: 'vim', name: 'Vim', isOther: false, openerId: 'vim' },
{ id: 'sublime', name: 'Sublime Text', isOther: false, openerId: 'sublime' },
{ id: 'vscode', name: 'Visual Studio Code', isOther: false, openerId: 'vscode' },
{ id: 'other', name: 'Other', isOther: true, openerId: '' },
]
beforeEach(function () {
this.getUserEditor = this.util.deferred()
cy.stub(this.ipc, 'getUserEditor').returns(this.getUserEditor.promise)
cy.stub(this.ipc, 'setUserEditor').resolves()
this.openProject.resolve(this.config)
this.projectStatuses[0].id = this.config.projectId
this.getProjectStatus.resolve(this.projectStatuses[0])
this.goToSettings()
cy.contains('File Opener Preference').click()
})
it('displays file preference section', () => {
cy.contains('Your preference is used to open files')
})
it('opens file preference guide when learn more is clicked', () => {
cy.get('.file-preference').contains('Learn more').click().then(function () {
expect(this.ipc.externalOpen).to.be.calledWithMatch({ url: 'https://on.cypress.io/file-opener-preference' })
})
})
it('loads preferred editor and available editors', function () {
expect(this.ipc.getUserEditor).to.be.called
})
it('shows spinner', () => {
cy.get('.loading-editors')
})
describe('when editors load with preferred editor', () => {
beforeEach(function () {
this.getUserEditor.resolve({ availableEditors, preferredOpener: availableEditors[3] })
})
it('displays available editors with preferred one selected', () => {
cy.get('.loading-editors').should('not.exist')
cy.contains('Atom')
cy.contains('Other')
cy.contains('Visual Studio Code').closest('li').should('have.class', 'is-selected')
})
it('sets editor through ipc when a different editor is selected', function () {
cy.contains('Atom').click()
.closest('li').should('have.class', 'is-selected')
cy.wrap(this.ipc.setUserEditor).should('be.calledWith', availableEditors[0])
cy.percySnapshot()
})
})
describe('when editors load without preferred editor', () => {
beforeEach(function () {
this.getUserEditor.resolve({ availableEditors })
})
it('does not select an editor', () => {
cy.get('.loading-editors').should('not.exist')
cy.get('.editor-picker li').should('not.have.class', 'is-selected')
})
})
})
describe('errors', () => {
const errorText = 'An unexpected error occurred'
beforeEach(function () {
this.err = {
message: 'Port \'2020\' is already in use.',
name: 'Error',
port: 2020,
portInUse: true,
stack: '[object Object]↵ at Object.API.get (/Users/jennifer/Dev/Projects/cypress-app/lib/errors.coffee:55:15)↵ at Object.wrapper [as get] (/Users/jennifer/Dev/Projects/cypress-app/node_modules/lodash/lodash.js:4414:19)↵ at Server.portInUseErr (/Users/jennifer/Dev/Projects/cypress-app/lib/server.coffee:58:16)↵ at Server.onError (/Users/jennifer/Dev/Projects/cypress-app/lib/server.coffee:86:19)↵ at Server.g (events.js:273:16)↵ at emitOne (events.js:90:13)↵ at Server.emit (events.js:182:7)↵ at emitErrorNT (net.js:1253:8)↵ at _combinedTickCallback (internal/process/next_tick.js:74:11)↵ at process._tickDomainCallback (internal/process/next_tick.js:122:9)↵From previous event:↵ at fn (file:///Users/jennifer/Dev/Projects/cypress-core-desktop-gui/dist/app.js:57919:14)↵ at Object.appIpc [as ipc] (file:///Users/jennifer/Dev/Projects/cypress-core-desktop-gui/dist/app.js:57939:10)↵ at openProject (file:///Users/jennifer/Dev/Projects/cypress-core-desktop-gui/dist/app.js:59135:24)↵ at new Project (file:///Users/jennifer/Dev/Projects/cypress-core-desktop-gui/dist/app.js:58848:34)↵ at ReactCompositeComponentMixin._constructComponentWithoutOwner (file:///Users/jennifer/Dev/Projects/cypress-core-desktop-gui/dist/app.js:44052:27)↵ at ReactCompositeComponentMixin._constructComponent (file:///Users/jennifer/Dev/Projects/cypress-core-desktop-gui/dist/app.js:44034:21)↵ at ReactCompositeComponentMixin.mountComponent (file:///Users/jennifer/Dev/Projects/cypress-core-desktop-gui/dist/app.js:43953:21)↵ at Object.ReactReconciler.mountComponent (file:///Users/jennifer/Dev/Projects/cypress-core-desktop-gui/dist/app.js:51315:35)↵ at ReactCompositeComponentMixin.performInitialMount (file:///Users/jennifer/Dev/Projects/cypress-core-desktop-gui/dist/app.js:44129:34)↵ at ReactCompositeComponentMixin.mountComponent (file:///Users/jennifer/Dev/Projects/cypress-core-desktop-gui/dist/app.js:44016:21)↵ at Object.ReactReconciler.mountComponent (file:///Users/jennifer/Dev/Projects/cypress-core-desktop-gui/dist/app.js:51315:35)↵ at ReactDOMComponent.ReactMultiChild.Mixin._mountChildAtIndex (file:///Users/jennifer/Dev/Projects/cypress-core-desktop-gui/dist/app.js:50247:40)↵ at ReactDOMComponent.ReactMultiChild.Mixin._updateChildren (file:///Users/jennifer/Dev/Projects/cypress-core-desktop-gui/dist/app.js:50163:43)↵ at ReactDOMComponent.ReactMultiChild.Mixin.updateChildren (file:///Users/jennifer/Dev/Projects/cypress-core-desktop-gui/dist/app.js:50123:12)↵ at ReactDOMComponent.Mixin._updateDOMChildren (file:///Users/jennifer/Dev/Projects/cypress-core-desktop-gui/dist/app.js:45742:12)↵ at ReactDOMComponent.Mixin.updateComponent (file:///Users/jennifer/Dev/Projects/cypress-core-desktop-gui/dist/app.js:45571:10)↵ at ReactDOMComponent.Mixin.receiveComponent (file:///Users/jennifer/Dev/Projects/cypress-core-desktop-gui/dist/app.js:45527:10)↵ at Object.ReactReconciler.receiveComponent (file:///Users/jennifer/Dev/Projects/cypress-core-desktop-gui/dist/app.js:51396:22)↵ at ReactCompositeComponentMixin._updateRenderedComponent (file:///Users/jennifer/Dev/Projects/cypress-core-desktop-gui/dist/app.js:44547:23)',
type: 'PORT_IN_USE_SHORT',
}
this.config.resolved.baseUrl.value = 'http://localhost:7777'
this.projectStatuses[0].id = this.config.projectId
this.getProjectStatus.resolve(this.projectStatuses[0])
this.openProject.resolve(this.config)
this.goToSettings()
openConfiguration()
cy.contains('http://localhost:7777').then(() => {
this.ipc.openProject.onCall(1).rejects(this.err)
this.ipc.onConfigChanged.yield()
})
})
it('displays errors', () => {
cy.contains(errorText)
cy.percySnapshot()
})
it('displays config after error is fixed', function () {
cy.contains(errorText).then(() => {
this.ipc.openProject.onCall(1).resolves(this.config)
this.ipc.onConfigChanged.yield()
})
cy.contains('Configuration')
})
})
})
// --
function setConfigEnv (config, v) {
return flow([
merge(config),
set('resolved.env', v),
])({})
}