diff --git a/cli/lib/cli.ts b/cli/lib/cli.ts index cd4d05f81e6..199ca7865c4 100644 --- a/cli/lib/cli.ts +++ b/cli/lib/cli.ts @@ -127,6 +127,7 @@ const descriptions: any = { parallel: 'enables concurrent runs and automatic load balancing of specs across multiple machines or processes', port: 'runs Cypress on a specific port. overrides any value in cypress.config.{js,ts,mjs,cjs}.', project: 'path to the project', + posixExitCodes: 'use POSIX exit codes for error handling', quiet: 'run quietly, using only the configured reporter', record: 'records the run. sends test results, screenshots and videos to Cypress Cloud.', reporter: 'runs a specific mocha reporter. pass a path to use a custom reporter. defaults to "spec"', @@ -259,6 +260,7 @@ const addCypressRunCommand = (program: any): any => { .option('--parallel', text('parallel')) .option('-p, --port ', text('port')) .option('-P, --project ', text('project')) + .option('--posix-exit-codes', text('posixExitCodes')) .option('-q, --quiet', text('quiet')) .option('--record [bool]', text('record'), coerceFalse) .option('-r, --reporter ', text('reporter')) diff --git a/cli/lib/util.ts b/cli/lib/util.ts index 5794312dba6..0224842e9e2 100644 --- a/cli/lib/util.ts +++ b/cli/lib/util.ts @@ -201,6 +201,7 @@ const parseOpts = (opts: any): any => { 'path', 'parallel', 'port', + 'posixExitCodes', 'project', 'quiet', 'reporter', @@ -449,7 +450,7 @@ const util = { async function _getRealArch (): Promise { const osPlatform = os.platform() - // eslint-disable-next-line no-restricted-syntax + const osArch = os.arch() debug('detecting arch %o', { osPlatform, osArch }) @@ -474,7 +475,6 @@ const util = { if (['aarch64_be', 'aarch64', 'armv8b', 'armv8l'].includes(stdout)) return 'arm64' } - // eslint-disable-next-line no-restricted-syntax const pkgArch = arch() if (pkgArch === 'x86') return 'ia32' diff --git a/cli/test/lib/cli.spec.ts b/cli/test/lib/cli.spec.ts index 3b4ee7dcc3a..048ad767258 100644 --- a/cli/test/lib/cli.spec.ts +++ b/cli/test/lib/cli.spec.ts @@ -651,6 +651,11 @@ describe('cli', () => { expect(run.start).toBeCalledWith({ runnerUi: false }) }) + it.only('calls run with --posix-exit-codes', async () => { + await exec('run --posix-exit-codes') + expect(run.start).toBeCalledWith({ posixExitCodes: true }) + }) + describe('component-testing', () => { it('passes to run.start the correct args for component-testing', async () => { await exec('run --component --dev') diff --git a/packages/server/lib/cypress.ts b/packages/server/lib/cypress.ts index 427b6abeb5e..a6d06a0b6aa 100644 --- a/packages/server/lib/cypress.ts +++ b/packages/server/lib/cypress.ts @@ -266,6 +266,10 @@ export = { } } + if (options.posixExitCodes) { + return results.totalFailed ? 1 : 0 + } + return results.totalFailed }) .then(exit) diff --git a/packages/server/lib/util/args.js b/packages/server/lib/util/args.js index f948f709c82..014e4234e7f 100644 --- a/packages/server/lib/util/args.js +++ b/packages/server/lib/util/args.js @@ -37,6 +37,7 @@ const allowList = [ 'parallel', 'ping', 'port', + 'posixExitCodes', 'project', 'proxySource', 'quiet', @@ -366,6 +367,7 @@ module.exports = { 'run-project': 'runProject', 'smoke-test': 'smokeTest', 'testing-type': 'testingType', + 'posix-exit-codes': 'posixExitCodes', } // takes an array of args and converts diff --git a/packages/types/src/modeOptions.ts b/packages/types/src/modeOptions.ts index f12a99399f2..213a0b56f29 100644 --- a/packages/types/src/modeOptions.ts +++ b/packages/types/src/modeOptions.ts @@ -25,6 +25,7 @@ export interface RunModeOptions extends CommonModeOptions { ciBuildId?: string | null tag?: (string)[] | null isBrowserGivenByCli: boolean + posixExitCodes?: boolean | null } export type TestingType = 'e2e' | 'component' diff --git a/system-tests/lib/system-tests.ts b/system-tests/lib/system-tests.ts index 82dd6c4af30..217d1ad62bb 100644 --- a/system-tests/lib/system-tests.ts +++ b/system-tests/lib/system-tests.ts @@ -238,6 +238,10 @@ type ExecOptions = { * Run Cypress with a custom user node version. */ userNodeVersion?: string + /** + * Run Cypress with POSIX exit codes. + */ + posixExitCodes?: boolean } type Server = { @@ -764,6 +768,12 @@ const systemTests = { args.push(`--userNodeVersion=${options.userNodeVersion}`) } + debug('posixExitCodes', options.posixExitCodes) + + if (options.posixExitCodes) { + args.push('--posix-exit-codes') + } + return args }, diff --git a/system-tests/test/posix_exit_codes_spec.ts b/system-tests/test/posix_exit_codes_spec.ts new file mode 100644 index 00000000000..383761109e9 --- /dev/null +++ b/system-tests/test/posix_exit_codes_spec.ts @@ -0,0 +1,31 @@ +import systemTests from '../lib/system-tests' + +describe('posix exit codes', () => { + systemTests.setup() + + describe('when posix exit codes are enabled', () => { + systemTests.it('returns 1 when there are failing tests', { + spec: 'simple_failing.cy.js', + posixExitCodes: true, + expectedExitCode: 1, + browser: ['electron'], + project: 'e2e', + }) + + systemTests.it('returns 2 when there are 2 failing tests and posix is disabled', { + spec: 'simple_failing.cy.js', + posixExitCodes: false, + expectedExitCode: 2, + browser: ['electron'], + project: 'e2e', + }) + + systemTests.it('returns 0 when there are no failing tests', { + spec: 'simple_passing.cy.js', + posixExitCodes: true, + expectedExitCode: 0, + browser: ['electron'], + project: 'e2e', + }) + }) +})