diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 4245aefcf5c..e485be121ef 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -1,8 +1,12 @@ -## 15.3.1 +## 15.4.0 _Released 10/07/2025 (PENDING)_ +**Features:** + +- Added the `--posix-exit-codes` flag for the `run` command. When this flag is passed, Cypress will exit with 1 if any tests fail, rather than the number of failed tests. Addresses [#32605](https://github.com/cypress-io/cypress/issues/32605) and [#24695](https://github.com/cypress-io/cypress/issues/24695). Addressed in [#32609](https://github.com/cypress-io/cypress/pull/32609). + **Bugfixes:** - Fixed a regression introduced in [`15.0.0`](https://docs.cypress.io/guides/references/changelog#15-0-0) where `dbus` connection error messages appear in docker containers when launching Cypress. Fixes [#32290](https://github.com/cypress-io/cypress/issues/32290). 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/exec/run.ts b/cli/lib/exec/run.ts index 577118aed52..bf295b8bf2e 100644 --- a/cli/lib/exec/run.ts +++ b/cli/lib/exec/run.ts @@ -110,6 +110,10 @@ const processRunOptions = (options: any = {}): string[] => { args.push('--parallel') } + if (options.posixExitCodes) { + args.push('--posix-exit-codes') + } + if (options.port) { args.push('--port', options.port) } 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/__snapshots__/cli.spec.ts.snap b/cli/test/lib/__snapshots__/cli.spec.ts.snap index dab8ac6a9d4..af9c3a3dff0 100644 --- a/cli/test/lib/__snapshots__/cli.spec.ts.snap +++ b/cli/test/lib/__snapshots__/cli.spec.ts.snap @@ -538,6 +538,7 @@ exports[`cli > unknown option > shows help for run command 1`] = ` --parallel enables concurrent runs and automatic load balancing of specs across multiple machines or processes -p, --port runs Cypress on a specific port. overrides any value in cypress.config.{js,ts,mjs,cjs}. -P, --project path to the project + --posix-exit-codes use POSIX exit codes for error handling -q, --quiet run quietly, using only the configured reporter --record [bool] records the run. sends test results, screenshots and videos to Cypress Cloud. -r, --reporter runs a specific mocha reporter. pass a path to use a custom reporter. defaults to "spec" diff --git a/cli/test/lib/cli.spec.ts b/cli/test/lib/cli.spec.ts index 3b4ee7dcc3a..53506b88fb9 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('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/cli/test/lib/exec/run.spec.ts b/cli/test/lib/exec/run.spec.ts index da705967071..35cdf242118 100644 --- a/cli/test/lib/exec/run.spec.ts +++ b/cli/test/lib/exec/run.spec.ts @@ -142,6 +142,10 @@ describe('exec run', () => { it('throws if --config-file is false', () => { expect(() => run.processRunOptions({ configFile: 'false' })).toThrow() }) + + it('adds --posix-exit-codes', () => { + expect(run.processRunOptions({ posixExitCodes: true })).toEqual(expect.arrayContaining(['--posix-exit-codes'])) + }) }) describe('.start', () => { diff --git a/cli/types/cypress-npm-api.d.ts b/cli/types/cypress-npm-api.d.ts index 2181f864000..3d613dae5db 100644 --- a/cli/types/cypress-npm-api.d.ts +++ b/cli/types/cypress-npm-api.d.ts @@ -68,6 +68,10 @@ declare namespace CypressCommandLine { * Override default port */ port: number + /** + * Use POSIX exit codes for error handling + */ + posixExitCodes: boolean /** * Run quietly, using only the configured reporter */ 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..bf67ffe50c4 --- /dev/null +++ b/system-tests/test/posix_exit_codes_spec.ts @@ -0,0 +1,37 @@ +import systemTests from '../lib/system-tests' + +describe('posix exit codes', () => { + systemTests.setup() + + describe('when posix exit codes are enabled', () => { + const posixExitCodes = true + + systemTests.it('returns 1 when there are multiple failing tests', { + spec: 'simple_failing.cy.js', + posixExitCodes, + expectedExitCode: 1, + 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', + }) + }) + + describe('when posix exit codes are disabled', () => { + const posixExitCodes = false + + systemTests.it('returns 2 when there are 2 failing tests', { + spec: 'simple_failing.cy.js', + posixExitCodes, + expectedExitCode: 2, + browser: ['electron'], + project: 'e2e', + }) + }) +})