Skip to content

Commit

Permalink
feat: added ability to specify the default browser in cypress config (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
alexsch01 authored Nov 14, 2024
1 parent 4bf8e58 commit 82f45c5
Show file tree
Hide file tree
Showing 15 changed files with 150 additions and 8 deletions.
1 change: 1 addition & 0 deletions cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ _Released 11/19/2024 (PENDING)_
**Features:**

- Updated the protocol to be able to flex logic based on project config. Addresses [#30560](https://github.com/cypress-io/cypress/issues/30560).
- Added new [`defaultBrowser`](https://docs.cypress.io/app/references/configuration#Browser) configuration option to specify the default browser to launch. This option only affects the first browser launch; changing this option after the browser is already launched will have no effect. Addresses [#6646](https://github.com/cypress-io/cypress/issues/6646).

**Bugfixes:**

Expand Down
5 changes: 5 additions & 0 deletions cli/types/cypress.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3216,6 +3216,11 @@ declare namespace Cypress {
setupNodeEvents: (on: PluginEvents, config: PluginConfigOptions) => Promise<PluginConfigOptions | void> | PluginConfigOptions | void

indexHtmlFile: string

/**
* The default browser to launch if the "--browser" command line option is not provided.
*/
defaultBrowser: string
}

interface EndToEndConfigOptions extends Omit<CoreConfigOptions, 'indexHtmlFile'> {
Expand Down
3 changes: 3 additions & 0 deletions packages/config/__snapshots__/index.spec.ts.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ exports['config/src/index .getDefaultValues returns list of public config keys 1
'specPattern': '**/*.cy.{js,jsx,ts,tsx}',
'indexHtmlFile': 'cypress/support/component-index.html',
},
'defaultBrowser': null,
'defaultCommandTimeout': 4000,
'downloadsFolder': 'cypress/downloads',
'e2e': {
Expand Down Expand Up @@ -117,6 +118,7 @@ exports['config/src/index .getDefaultValues returns list of public config keys f
'specPattern': '**/*.cy.{js,jsx,ts,tsx}',
'indexHtmlFile': 'cypress/support/component-index.html',
},
'defaultBrowser': null,
'defaultCommandTimeout': 4000,
'downloadsFolder': 'cypress/downloads',
'e2e': {
Expand Down Expand Up @@ -206,6 +208,7 @@ exports['config/src/index .getPublicConfigKeys returns list of public config key
'chromeWebSecurity',
'clientCertificates',
'component',
'defaultBrowser',
'defaultCommandTimeout',
'downloadsFolder',
'e2e',
Expand Down
4 changes: 4 additions & 0 deletions packages/config/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ const driverConfigOptions: Array<DriverConfigOption> = [
indexHtmlFile: 'cypress/support/component-index.html',
},
validation: isValidConfig,
}, {
name: 'defaultBrowser',
defaultValue: null,
validation: validate.isString,
}, {
name: 'defaultCommandTimeout',
defaultValue: 4000,
Expand Down
2 changes: 2 additions & 0 deletions packages/config/test/project/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1054,6 +1054,7 @@ describe('config/src/project/utils', () => {
browsers: { value: [], from: 'default' },
chromeWebSecurity: { value: true, from: 'default' },
clientCertificates: { value: [], from: 'default' },
defaultBrowser: { value: null, from: 'default' },
defaultCommandTimeout: { value: 4000, from: 'default' },
downloadsFolder: { value: 'cypress/downloads', from: 'default' },
env: {},
Expand Down Expand Up @@ -1152,6 +1153,7 @@ describe('config/src/project/utils', () => {
browsers: { value: [], from: 'default' },
chromeWebSecurity: { value: true, from: 'default' },
clientCertificates: { value: [], from: 'default' },
defaultBrowser: { value: null, from: 'default' },
defaultCommandTimeout: { value: 4000, from: 'default' },
downloadsFolder: { value: 'cypress/downloads', from: 'default' },
execTimeout: { value: 60000, from: 'default' },
Expand Down
8 changes: 6 additions & 2 deletions packages/data-context/src/DataContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export interface GraphQLRequestInfo {
export class DataContext {
readonly graphqlRequestInfo?: GraphQLRequestInfo
private _config: Omit<DataContextConfig, 'modeOptions'>
private _modeOptions: Readonly<Partial<AllModeOptions>>
private _modeOptions: Partial<AllModeOptions>
private _coreData: CoreDataShape
readonly lifecycleManager: ProjectLifecycleManager

Expand Down Expand Up @@ -122,7 +122,7 @@ export class DataContext {
return new RemoteRequestDataSource()
}

get modeOptions () {
get modeOptions (): Readonly<Partial<AllModeOptions>> {
return this._modeOptions
}

Expand Down Expand Up @@ -425,4 +425,8 @@ export class DataContext {
this.#awaitingEmptyRequestCount.push(resolve)
})
}

updateModeOptionsBrowser (browser: string) {
this._modeOptions.browser = browser
}
}
5 changes: 5 additions & 0 deletions packages/data-context/src/actions/BrowserActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ export class BrowserActions {
})
}

setCliBrowser (browser: string) {
this.ctx.updateModeOptionsBrowser(browser)
this.ctx.coreData.cliBrowser = browser
}

async focusActiveBrowserWindow () {
await this.browserApi.focusActiveBrowserWindow()
}
Expand Down
19 changes: 17 additions & 2 deletions packages/data-context/src/data/ProjectLifecycleManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,10 +308,25 @@ export class ProjectLifecycleManager {
/**
* Sets the initial `activeBrowser` depending on these criteria, in order of preference:
* 1. The value of `--browser` passed via CLI.
* 2. The last browser selected in `open` mode (by name and channel) for this project.
* 3. The first browser found.
* 2. The value of `defaultBrowser` in `cypress.config`.
* 3. The last browser selected in `open` mode (by name and channel) for this project.
* 4. The first browser found.
*/
async setInitialActiveBrowser () {
const configDefaultBrowser = this.loadedFullConfig?.defaultBrowser

// if we have a default browser from the config and a CLI browser wasn't passed and the active browser hasn't been set
// set the cliBrowser to the defaultBrowser from the config since we want the defaultBrowser to behave as if it was passed via CLI
if (configDefaultBrowser && !this.ctx.modeOptions.isBrowserGivenByCli && !this.ctx.coreData.activeBrowser) {
this.ctx.actions.browser.setCliBrowser(configDefaultBrowser)
}

// if we already have an activeBrowser, that means we are reloading the browser (e.g. after a config change in open mode)
// so we need to set the CLI browser to the activeBrowser to ensure the GUI shows the correct browser
if (this.ctx.coreData.activeBrowser && !process.env.CYPRESS_INTERNAL_E2E_TESTING_SELF) {
this.ctx.actions.browser.setCliBrowser(`${this.ctx.coreData.activeBrowser.name}:${this.ctx.coreData.activeBrowser.channel}`)
}

if (this.ctx.coreData.cliBrowser) {
await this.setActiveBrowserByNameOrPath(this.ctx.coreData.cliBrowser)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,20 @@ import { expect } from 'chai'
import type { DataContext } from '../../../src'
import { createTestDataContext } from '../helper'
import sinon from 'sinon'
import { FullConfig } from '@packages/types'
import { FoundBrowser, FullConfig } from '@packages/types'

const browsers = [
{ name: 'electron', family: 'chromium', channel: 'stable', displayName: 'Electron' },
{ name: 'chrome', family: 'chromium', channel: 'stable', displayName: 'Chrome' },
{ name: 'chrome', family: 'chromium', channel: 'beta', displayName: 'Chrome Beta' },
{ name: 'firefox', family: 'firefox', channel: 'stable', displayName: 'Firefox' },
]

let ctx: DataContext

function createDataContext (modeOptions?: Parameters<typeof createTestDataContext>[1]) {
const context = createTestDataContext('open', modeOptions)

context.coreData.activeBrowser = undefined
context.coreData.cliBrowser = undefined

context._apis.browserApi.getBrowsers = sinon.stub().resolves(browsers)
context._apis.projectApi.insertProjectPreferencesToCache = sinon.stub()
context.actions.project.launchProject = sinon.stub().resolves()
Expand All @@ -42,6 +40,9 @@ describe('ProjectLifecycleManager', () => {

context('#setInitialActiveBrowser', () => {
it('falls back to browsers[0] if preferences and cliBrowser do not exist', async () => {
ctx.coreData.activeBrowser = null
ctx.coreData.cliBrowser = null

await ctx.lifecycleManager.setInitialActiveBrowser()

expect(ctx.coreData.activeBrowser).to.include({ name: 'electron' })
Expand All @@ -51,6 +52,7 @@ describe('ProjectLifecycleManager', () => {
it('uses cli --browser option if one is set', async () => {
ctx._apis.browserApi.ensureAndGetByNameOrPath = sinon.stub().withArgs('electron').resolves(browsers[0])

ctx.coreData.activeBrowser = null
ctx.coreData.cliBrowser = 'electron'

await ctx.lifecycleManager.setInitialActiveBrowser()
Expand All @@ -68,6 +70,7 @@ describe('ProjectLifecycleManager', () => {

ctx._apis.browserApi.ensureAndGetByNameOrPath = sinon.stub().withArgs('electron').resolves(browsers[0])

ctx.coreData.activeBrowser = null
ctx.coreData.cliBrowser = 'electron'

await ctx.lifecycleManager.setInitialActiveBrowser()
Expand All @@ -79,6 +82,8 @@ describe('ProjectLifecycleManager', () => {

it('uses lastBrowser if available', async () => {
ctx.project.getProjectPreferences = sinon.stub().resolves({ lastBrowser: { name: 'chrome', channel: 'beta' } })
ctx.coreData.activeBrowser = null
ctx.coreData.cliBrowser = null

await ctx.lifecycleManager.setInitialActiveBrowser()

Expand All @@ -88,12 +93,84 @@ describe('ProjectLifecycleManager', () => {

it('falls back to browsers[0] if lastBrowser does not exist', async () => {
ctx.project.getProjectPreferences = sinon.stub().resolves({ lastBrowser: { name: 'chrome', channel: 'dev' } })
ctx.coreData.activeBrowser = null
ctx.coreData.cliBrowser = null

await ctx.lifecycleManager.setInitialActiveBrowser()

expect(ctx.coreData.activeBrowser).to.include({ name: 'electron' })
expect(ctx.actions.project.launchProject).to.not.be.called
})

it('uses config defaultBrowser option if --browser is not given', async () => {
ctx = createDataContext({
project: 'foo',
testingType: 'e2e',
isBrowserGivenByCli: false,
})

ctx._apis.browserApi.ensureAndGetByNameOrPath = sinon.stub().withArgs('chrome').resolves(browsers[1])
sinon.stub(ctx.lifecycleManager, 'loadedFullConfig').get(() => ({ defaultBrowser: 'chrome' }))

expect(ctx.modeOptions.browser).to.eq(undefined)
expect(ctx.coreData.cliBrowser).to.eq(null)
expect(ctx.coreData.activeBrowser).to.eq(null)

await ctx.lifecycleManager.setInitialActiveBrowser()

expect(ctx.modeOptions.browser).to.eq('chrome')
expect(ctx.coreData.cliBrowser).to.eq('chrome')
expect(ctx.coreData.activeBrowser).to.eq(browsers[1])
})

it('doesn\'t use config defaultBrowser option if --browser is given', async () => {
ctx = createDataContext({
project: 'foo',
testingType: 'e2e',
browser: 'firefox',
isBrowserGivenByCli: true,
})

sinon.stub(ctx.lifecycleManager, 'getFullInitialConfig').resolves(fullConfig)
ctx._apis.browserApi.ensureAndGetByNameOrPath = sinon.stub().withArgs('firefox').resolves(browsers[3])
sinon.stub(ctx.lifecycleManager, 'loadedFullConfig').get(() => ({ defaultBrowser: 'chrome' }))

expect(ctx.modeOptions.browser).to.eq('firefox')
expect(ctx.coreData.cliBrowser).to.eq('firefox')
expect(ctx.coreData.activeBrowser).to.eq(null)

await ctx.lifecycleManager.setInitialActiveBrowser()

expect(ctx.modeOptions.browser).to.eq('firefox')
expect(ctx.coreData.cliBrowser).to.eq('firefox')
expect(ctx.coreData.activeBrowser).to.eq(browsers[3])
})

it('ignores the defaultBrowser if there is an active browser and updates the CLI browser to the active browser', async () => {
ctx = createDataContext({
project: 'foo',
testingType: 'e2e',
isBrowserGivenByCli: false,
})

sinon.stub(ctx.lifecycleManager, 'getFullInitialConfig').resolves(fullConfig)
ctx._apis.browserApi.ensureAndGetByNameOrPath = sinon.stub().withArgs('chrome:beta').resolves(browsers[2])
// the default browser will be ignored since we have an active browser
sinon.stub(ctx.lifecycleManager, 'loadedFullConfig').get(() => ({ defaultBrowser: 'firefox' }))

// set the active browser to chrome:beta
ctx.actions.browser.setActiveBrowser(browsers[2] as FoundBrowser)

expect(ctx.modeOptions.browser).to.eq(undefined)
expect(ctx.coreData.cliBrowser).to.eq(null)
expect(ctx.coreData.activeBrowser).to.eq(browsers[2])

await ctx.lifecycleManager.setInitialActiveBrowser()

expect(ctx.modeOptions.browser).to.eq('chrome:beta')
expect(ctx.coreData.cliBrowser).to.eq('chrome:beta')
expect(ctx.coreData.activeBrowser).to.eq(browsers[2])
})
})

context('#eventProcessPid', () => {
Expand Down
2 changes: 2 additions & 0 deletions packages/server/lib/modes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export = (mode, options) => {
return require('./smoke_test').run(options)
}

options.isBrowserGivenByCli = options.browser !== undefined

if (mode === 'run') {
_.defaults(options, {
socketId: random.id(10),
Expand Down
1 change: 1 addition & 0 deletions packages/types/src/modeOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface RunModeOptions extends CommonModeOptions {
parallel?: boolean | null
ciBuildId?: string | null
tag?: (string)[] | null
isBrowserGivenByCli: boolean
}

export type TestingType = 'e2e' | 'component'
Expand Down
1 change: 1 addition & 0 deletions system-tests/__snapshots__/results_spec.ts.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ exports['module api and after:run results'] = `
"blockHosts": null,
"chromeWebSecurity": true,
"clientCertificates": [],
"defaultBrowser": null,
"defaultCommandTimeout": 4000,
"downloadsFolder": "/path/to/downloadsFolder",
"env": {},
Expand Down
6 changes: 6 additions & 0 deletions system-tests/projects/config-defaultBrowser/cypress.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
e2e: {
supportFile: false,
},
defaultBrowser: 'chrome',
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
it('works', () => {
expect(1).to.eq(1)
})
13 changes: 13 additions & 0 deletions system-tests/test/config_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -243,4 +243,17 @@ describe('e2e config', () => {
snapshot: true,
})
})

it('launches browser using config.defaultBrowser', async function () {
await Fixtures.scaffoldProject('config-defaultBrowser')

return systemTests.exec(this, {
project: 'config-defaultBrowser',
command: 'cypress',
args: ['run', '--dev', '--project', path.resolve(process.cwd(), './projects/config-defaultBrowser')],
onStdout: (stdout) => {
expect(stdout).to.include('Browser: Chrome')
},
})
})
})

4 comments on commit 82f45c5

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 82f45c5 Nov 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the linux x64 version of the Test Runner.

Learn more about this pre-release build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/13.16.0/linux-x64/develop-82f45c51d61fcf12c0a68b0ce1f59a40af9a14ae/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 82f45c5 Nov 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the linux arm64 version of the Test Runner.

Learn more about this pre-release build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/13.16.0/linux-arm64/develop-82f45c51d61fcf12c0a68b0ce1f59a40af9a14ae/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 82f45c5 Nov 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the win32 x64 version of the Test Runner.

Learn more about this pre-release build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/13.16.0/win32-x64/develop-82f45c51d61fcf12c0a68b0ce1f59a40af9a14ae/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 82f45c5 Nov 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the darwin x64 version of the Test Runner.

Learn more about this pre-release build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/13.16.0/darwin-x64/develop-82f45c51d61fcf12c0a68b0ce1f59a40af9a14ae/cypress.tgz

Please sign in to comment.