Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
57 changes: 57 additions & 0 deletions packages/server/lib/append_electron_switches.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import os from 'os'
import debugModule from 'debug'
import { DEFAULT_ELECTRON_FLAGS } from './util/chromium_flags'

const debug = debugModule('cypress:server:append_electron_switches')

export const appendElectronSwitches = (app: Electron.App) => {
// NOTE: errors are printed in development mode only
try {
// when running inside the electron process, we need to append the default switches immediately
// before the electron browser is launched. Otherwise, there may be some odd behavior.
debug('appending default switches for electron: %o', DEFAULT_ELECTRON_FLAGS)
DEFAULT_ELECTRON_FLAGS.forEach(({ name, value }) => {
value ? app.commandLine.appendSwitch(name, value) : app.commandLine.appendSwitch(name)
})

if (os.platform() === 'linux') {
app.disableHardwareAcceleration()
}

if (process.env.ELECTRON_EXTRA_LAUNCH_ARGS) {
// regex will be used to convert ELECTRON_EXTRA_LAUNCH_ARGS into an array, for example
// input: 'foo --ipsum=0 --bar=--baz=quux --lorem="--ipsum=dolor --sit=amet"'
// output: ['foo', '--ipsum=0', '--bar=--baz=quux', '--lorem="--ipsum=dolor --sit=amet"']
const regex = /(?:[^\s"']+|"[^"]*"|'[^']*')+/g
const electronLaunchArguments = process.env.ELECTRON_EXTRA_LAUNCH_ARGS.match(regex) || []

electronLaunchArguments.forEach((arg) => {
// arg can be just key --disable-http-cache
// or key value --remote-debugging-port=8315
// or key value with another value --foo=--bar=4196
// or key value with another multiple value --foo='--bar=4196 --baz=quux'
const [key, ...value] = arg.split('=')

// because this is an environment variable, everything is a string
// thus we don't have to worry about casting
// --foo=false for example will be "--foo", "false"
if (value.length) {
let joinedValues = value.join('=')

// check if the arg is wrapped in " or ' (unicode)
const isWrappedInQuotes = !!['\u0022', '\u0027'].find(((charAsUnicode) => joinedValues.startsWith(charAsUnicode) && joinedValues.endsWith(charAsUnicode)))

if (isWrappedInQuotes) {
joinedValues = joinedValues.slice(1, -1)
}

app.commandLine.appendSwitch(key, joinedValues)
} else {
app.commandLine.appendSwitch(key)
}
})
}
} catch (e) {
debug('environment error %s', e.message)
}
}
2 changes: 0 additions & 2 deletions packages/server/lib/cypress.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
require('./environment')

// we are not requiring everything up front
// to optimize how quickly electron boots while
// in dev or linux production. the reasoning is
Expand Down
93 changes: 0 additions & 93 deletions packages/server/lib/environment.js

This file was deleted.

29 changes: 29 additions & 0 deletions packages/server/lib/environment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import pkg from '@packages/root'
import Bluebird from 'bluebird'

export const configureLongStackTraces = (env: string | undefined) => {
// never cut off stack traces
Error.stackTraceLimit = Infinity

Bluebird.config({
// uses cancellation for automation timeouts
cancellation: true,
// enable long stack traces in dev
longStackTraces: env === 'development',
})
}

export const calculateCypressInternalEnv = () => {
// instead of setting NODE_ENV we will
// use our own separate CYPRESS_INTERNAL_ENV so
// as not to conflict with CI providers

// use env from package first
// or development as default
if (!process.env['CYPRESS_INTERNAL_ENV']) {
// @ts-expect-error
return pkg.env !== null && pkg.env !== undefined ? pkg.env : 'development'
}

return process.env['CYPRESS_INTERNAL_ENV']
}
13 changes: 13 additions & 0 deletions packages/server/start-cypress.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,25 @@ const { telemetry, OTLPTraceExporterCloud } = require('@packages/telemetry')
const { apiRoutes } = require('./lib/cloud/routes')
const encryption = require('./lib/cloud/encryption')

const { calculateCypressInternalEnv, configureLongStackTraces } = require('./lib/environment')

process.env['CYPRESS_INTERNAL_ENV'] = calculateCypressInternalEnv()
configureLongStackTraces(process.env['CYPRESS_INTERNAL_ENV'])
process.env['CYPRESS'] = 'true'

// are we in the main node process or the electron process?
const isRunningElectron = electronApp.isRunning()

const pkg = require('@packages/root')

if (isRunningElectron) {
// if we are in the electron process, we need to patch the electron switches before Cypress launches the app
// @see https://www.electronjs.org/docs/latest/api/environment-variables#electron_run_as_node
const { app } = require('electron')
const { appendElectronSwitches } = require('./lib/append_electron_switches')

appendElectronSwitches(app)

// To pass unencrypted telemetry data to an independent open telemetry endpoint,
// disable the encryption header, update the url, and add any other required headers.
// For example:
Expand Down
5 changes: 3 additions & 2 deletions packages/server/test/spec_helper.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/* eslint-disable no-console */
require('../lib/environment')

const { enable, mockElectron } = require('./mockery_helper')

const { configureLongStackTraces } = require('../lib/environment')

configureLongStackTraces()
const chai = require('chai')

chai.use(require('chai-subset'))
Expand Down
130 changes: 130 additions & 0 deletions packages/server/test/unit/append_electron_switches_spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import os from 'os'
import sinon from 'sinon'
import mockedEnv from 'mocked-env'
import { appendElectronSwitches } from '../../lib/append_electron_switches'

describe('lib/append_electron_switches', () => {
beforeEach(() => {
sinon.stub(os, 'platform').returns('linux')
})

afterEach(() => {
sinon.restore()
})

// @see https://github.com/electron/electron/issues/46538
// @see https://github.com/cypress-io/cypress/issues/32361
context('sets gtk-version=3 in Electron >= 36', () => {
it('sets launch args', async () => {
const mockApp = {
commandLine: {
appendSwitch: sinon.stub(),
},
} as unknown as Electron.App

appendElectronSwitches(mockApp)
expect(mockApp.commandLine.appendSwitch).to.have.been.calledWith('--gtk-version', '3')
})
})

context('disables hardware acceleration on Linux', () => {
it('disables hardware acceleration', async () => {
const mockApp = {
disableHardwareAcceleration: sinon.stub(),
commandLine: {
appendSwitch: sinon.stub(),
},
} as unknown as Electron.App

appendElectronSwitches(mockApp)
expect(mockApp.disableHardwareAcceleration).to.have.been.called
})
})

context('parses ELECTRON_EXTRA_LAUNCH_ARGS', () => {
let restore = null

afterEach(() => {
if (restore) {
return restore()
}
})

it('sets launch args', async () => {
restore = mockedEnv({
ELECTRON_EXTRA_LAUNCH_ARGS: '--foo --bar=baz --quux=true',
})

const mockApp = {
disableHardwareAcceleration: sinon.stub(),
commandLine: {
appendSwitch: sinon.stub(),
},
} as unknown as Electron.App

appendElectronSwitches(mockApp)
expect(mockApp.commandLine.appendSwitch).to.have.been.calledWith('--foo')
expect(mockApp.commandLine.appendSwitch).to.have.been.calledWith('--bar', 'baz')

expect(mockApp.commandLine.appendSwitch).to.have.been.calledWith('--quux', 'true')
})

it('sets launch args with zero', async () => {
restore = mockedEnv({
ELECTRON_EXTRA_LAUNCH_ARGS: '--foo --bar=baz --quux=0',
})

const mockApp = {
disableHardwareAcceleration: sinon.stub(),
commandLine: {
appendSwitch: sinon.stub(),
},
} as unknown as Electron.App

appendElectronSwitches(mockApp)
expect(mockApp.commandLine.appendSwitch).to.have.been.calledWith('--foo')
expect(mockApp.commandLine.appendSwitch).to.have.been.calledWith('--bar', 'baz')

expect(mockApp.commandLine.appendSwitch).to.have.been.calledWith('--quux', '0')
})

it('sets launch args with false', async () => {
restore = mockedEnv({
ELECTRON_EXTRA_LAUNCH_ARGS: '--foo --bar=baz --quux=false',
})

const mockApp = {
disableHardwareAcceleration: sinon.stub(),
commandLine: {
appendSwitch: sinon.stub(),
},
} as unknown as Electron.App

appendElectronSwitches(mockApp)

expect(mockApp.commandLine.appendSwitch).to.have.been.calledWith('--foo')
expect(mockApp.commandLine.appendSwitch).to.have.been.calledWith('--bar', 'baz')

expect(mockApp.commandLine.appendSwitch).to.have.been.calledWith('--quux', 'false')
})

it('sets launch args with multiple values inside quotes', async () => {
restore = mockedEnv({
ELECTRON_EXTRA_LAUNCH_ARGS: `--foo --ipsum=0 --bar=--baz=quux --lorem='--ipsum=dolor --sit=amet'`,
})

const mockApp = {
disableHardwareAcceleration: sinon.stub(),
commandLine: {
appendSwitch: sinon.stub(),
},
} as unknown as Electron.App

appendElectronSwitches(mockApp)
expect(mockApp.commandLine.appendSwitch).to.have.been.calledWith('--foo')
expect(mockApp.commandLine.appendSwitch).to.have.been.calledWith('--ipsum', '0')
expect(mockApp.commandLine.appendSwitch).to.have.been.calledWith('--bar', '--baz=quux')
expect(mockApp.commandLine.appendSwitch).to.have.been.calledWith('--lorem', '--ipsum=dolor --sit=amet')
})
})
})
Loading
Loading