Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ability to specify the default browser in cypress config #30517

Merged
merged 49 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
44e0c99
feat: ability to specify the default browser in cypress config file
alexsch01 Nov 1, 2024
24e3dda
don't need setActiveBrowserByNameOrPath for run mode
alexsch01 Nov 1, 2024
f8789ab
restore readonly on attribute
alexsch01 Nov 1, 2024
2f82876
simplify cypress open version
alexsch01 Nov 1, 2024
11bd244
put isBrowserGivenByCli on modeOptions (#4)
alexsch01 Nov 3, 2024
c1fc1b2
Update cypress.d.ts
alexsch01 Nov 3, 2024
66d26b1
use isBrowserGivenByCli only for run mode (#5)
alexsch01 Nov 3, 2024
d4a68ba
make defaultBrowser more conformant
alexsch01 Nov 3, 2024
8028e51
Update CHANGELOG.md
alexsch01 Nov 3, 2024
77ffd7a
Update CHANGELOG.md
alexsch01 Nov 3, 2024
5d6202d
Update CHANGELOG.md
alexsch01 Nov 3, 2024
17c40b3
Merge branch 'develop' into defaultBrowser-config
jennifer-shehane Nov 4, 2024
d036d96
oops (#6)
alexsch01 Nov 4, 2024
08fb3a3
Merge branch 'develop' into defaultBrowser-config
jennifer-shehane Nov 5, 2024
7aaf2a9
changes (#7)
alexsch01 Nov 6, 2024
b7cb459
more test fixes (#8)
alexsch01 Nov 6, 2024
ea67832
Update utils.spec.ts
alexsch01 Nov 6, 2024
4f1cd4d
Update packages/config/src/options.ts
alexsch01 Nov 6, 2024
7154055
Update cli/CHANGELOG.md
alexsch01 Nov 6, 2024
9a740c4
Update cli/types/cypress.d.ts
alexsch01 Nov 6, 2024
2dddf0f
Update packages/config/src/options.ts
alexsch01 Nov 6, 2024
5f1d6b9
Update packages/data-context/src/data/ProjectLifecycleManager.ts
alexsch01 Nov 6, 2024
adec731
Update DataContext.ts
alexsch01 Nov 6, 2024
4425433
Update DataContext.ts
alexsch01 Nov 6, 2024
6933638
final oops
alexsch01 Nov 6, 2024
2a53ba0
remove requireRestartOnChange for defaultBrowser, prevent bad open mo…
alexsch01 Nov 8, 2024
fd9c123
oops
alexsch01 Nov 8, 2024
4465303
organization changes (#10)
alexsch01 Nov 12, 2024
072ca63
fixed
alexsch01 Nov 12, 2024
9e88d67
if activeBrowser is already set, make sure the label is correct
alexsch01 Nov 12, 2024
acad76a
oops
alexsch01 Nov 12, 2024
973932c
Update ProjectLifecycleManager.spec.ts
alexsch01 Nov 12, 2024
61071cf
Update ProjectLifecycleManager.spec.ts
alexsch01 Nov 12, 2024
683d2a9
Merge branch 'develop' into defaultBrowser-config
alexsch01 Nov 12, 2024
1beecad
Update CHANGELOG.md
alexsch01 Nov 12, 2024
ea704aa
Update CHANGELOG.md
alexsch01 Nov 12, 2024
fb000df
Update CHANGELOG.md
alexsch01 Nov 12, 2024
e3c754d
hopefully fix lint, add to test
alexsch01 Nov 12, 2024
f550b4c
Update ProjectLifecycleManager.spec.ts
alexsch01 Nov 12, 2024
75c30d2
Update ProjectLifecycleManager.spec.ts
alexsch01 Nov 12, 2024
337bfb5
Update ProjectLifecycleManager.spec.ts
alexsch01 Nov 12, 2024
d6f72b6
more accurate
alexsch01 Nov 12, 2024
3371d54
Update CHANGELOG.md
alexsch01 Nov 13, 2024
92fc352
pr updates
mschile Nov 13, 2024
6b8fad3
Merge branch 'develop' into defaultBrowser-config
alexsch01 Nov 13, 2024
c859819
a few more updates
mschile Nov 13, 2024
959efeb
fix cy-in-cy tests
mschile Nov 14, 2024
3d5cd9d
can once again pass --browser CLI in open mode
alexsch01 Nov 14, 2024
1bed710
Update CHANGELOG.md
alexsch01 Nov 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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).

## 13.15.2

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,
alexsch01 marked this conversation as resolved.
Show resolved Hide resolved
}, {
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')
},
})
})
})