Skip to content
Open
8 changes: 8 additions & 0 deletions cli/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
<!-- See the ../guides/writing-the-cypress-changelog.md for details on writing the changelog. -->
## 15.4.1

_Released 10/21/2025 (PENDING)_

**Bugfixes:**

- When running `cypress` in Cypress development environments, or when `ELECTRON_ENABLE_LOGGING` is otherwise set to 1, certain messages written to `stderr` will no longer be bracketed with verbose tags. Addresses [#32569](https://github.com/cypress-io/cypress/issues/32569). Addressed in [#32674](https://github.com/cypress-io/cypress/pull/32674).

## 15.4.0

_Released 10/7/2025_
Expand Down
10 changes: 5 additions & 5 deletions packages/stderr-filtering/lib/Filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@ import { START_TAG, END_TAG } from './constants'
import { FilterPrefixedContent } from './FilterPrefixedContent'
import { FilterTaggedContent } from './FilterTaggedContent'
import { WriteToDebug } from './WriteToDebug'
import { tagsDisabled } from './tagsDisabled'

const DISABLE_TAGS = process.env.ELECTRON_ENABLE_LOGGING === '1'

export function filter (stderr: Writable, debug: Debugger, prefix: RegExp, disableTags: boolean = false): Writable {
export function filter (stderr: Writable, debug: Debugger, prefix: RegExp): Writable {
const prefixTx = new FilterPrefixedContent(prefix, stderr)
const tagTx = new FilterTaggedContent(START_TAG, END_TAG, stderr)
const debugWriter = new WriteToDebug(debug)

if (DISABLE_TAGS || disableTags) {
if (tagsDisabled()) {
prefixTx.pipe(debugWriter)
} else {
const tagTx = new FilterTaggedContent(START_TAG, END_TAG, stderr)

prefixTx.pipe(tagTx).pipe(debugWriter)
}

Expand Down
16 changes: 11 additions & 5 deletions packages/stderr-filtering/lib/TagStream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import { Transform } from 'stream'
import { START_TAG, END_TAG } from './constants'
import { StringDecoder } from 'string_decoder'
import Debug from 'debug'
import { tagsDisabled } from './tagsDisabled'

const debug = Debug('cypress:stderr-filtering:TagStream')
const debugVerbose = Debug('cypress-verbose:stderr-filtering:TagStream')

/**
* A Transform stream that wraps input data with start and end tags.
Expand Down Expand Up @@ -66,13 +68,13 @@ export class TagStream extends Transform {
const out = chunk instanceof Buffer ?
this.initializedDecoder.write(chunk) :
chunk
const transformed = `${this.startTag}${out}${this.endTag}`
const transformed = out ? this.tag(out) : Buffer.from('')

debug(`transformed: "${transformed.replaceAll('\n', '\\n')}"`)
const canWrite = this.push(out ? Buffer.from(transformed) : '')
debugVerbose(`transformed: "${transformed.toString().replaceAll('\n', '\\n')}"`)
const canWrite = this.push(transformed)

if (!canWrite) {
debug('waiting for drain')
debugVerbose('waiting for drain')
await new Promise((resolve) => this.once('drain', resolve))
}

Expand All @@ -95,6 +97,10 @@ export class TagStream extends Transform {
debug('flushing')
const out = this.initializedDecoder.end()

callback(undefined, Buffer.from(`${this.startTag}${out}${this.endTag}`))
callback(undefined, out ? this.tag(out) : Buffer.from(''))
}

private tag (str: string): Buffer {
return tagsDisabled() ? Buffer.from(str) : Buffer.from(`${this.startTag}${str}${this.endTag}`)
}
}
46 changes: 20 additions & 26 deletions packages/stderr-filtering/lib/__spec__/Filter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ vi.mock('../FilterPrefixedContent')
vi.mock('../FilterTaggedContent')
vi.mock('../WriteToDebug')

// Mock process.env
const originalEnv = process.env

describe('Filter', () => {
let mockStderr: any
let mockDebug: any
Expand All @@ -21,9 +18,6 @@ describe('Filter', () => {
let mockWriteToDebug: any

beforeEach(() => {
// Reset environment
process.env = { ...originalEnv }

// Create mock objects
mockStderr = {
write: vi.fn(),
Expand Down Expand Up @@ -54,48 +48,38 @@ describe('Filter', () => {

afterEach(() => {
vi.clearAllMocks()
process.env = originalEnv
vi.unstubAllEnvs()
})

describe('when disableTags is false', () => {
beforeEach(() => {
process.env.ELECTRON_ENABLE_LOGGING = '0'
})

describe('when tags are enabled', () => {
it('pipes prefixTx -> tagTx -> debugWriter', () => {
const result = filter(mockStderr, mockDebug, DEBUG_PREFIX, false)
vi.stubEnv('ELECTRON_ENABLE_LOGGING', '0')
vi.stubEnv('CYPRESS_INTERNAL_ENV', 'production')

const result = filter(mockStderr, mockDebug, DEBUG_PREFIX)

// Verify FilterPrefixedContent was created with correct args
expect(FilterPrefixedContent).toHaveBeenCalledWith(DEBUG_PREFIX, mockStderr)

// Verify FilterTaggedContent was created with correct args
expect(FilterTaggedContent).toHaveBeenCalledWith(START_TAG, END_TAG, mockStderr)

// Verify WriteToDebug was created with correct args
expect(WriteToDebug).toHaveBeenCalledWith(mockDebug)

// Verify the pipe chain: prefixTx -> tagTx -> debugWriter
expect(mockFilterPrefixedContent.pipe).toHaveBeenCalledWith(mockFilterTaggedContent)
expect(mockFilterTaggedContent.pipe).toHaveBeenCalledWith(mockWriteToDebug)

// Verify the result is the prefixTx
expect(result).toBe(mockFilterPrefixedContent)
})
})

describe('when disableTags parameter is true', () => {
beforeEach(() => {
process.env.ELECTRON_ENABLE_LOGGING = '0'
})

it('should pipe prefixTx -> debugWriter (skip tagTx)', () => {
const result = filter(mockStderr, mockDebug, DEBUG_PREFIX, true)
describe('disabling tags', () => {
function expectNoTags () {
const result = filter(mockStderr, mockDebug, DEBUG_PREFIX)

// Verify FilterPrefixedContent was created with correct args
expect(FilterPrefixedContent).toHaveBeenCalledWith(DEBUG_PREFIX, mockStderr)

// Verify FilterTaggedContent was created with correct args
expect(FilterTaggedContent).toHaveBeenCalledWith(START_TAG, END_TAG, mockStderr)
expect(FilterTaggedContent).not.toHaveBeenCalled()

// Verify WriteToDebug was created with correct args
expect(WriteToDebug).toHaveBeenCalledWith(mockDebug)
Expand All @@ -106,6 +90,16 @@ describe('Filter', () => {

// Verify the result is the prefixTx
expect(result).toBe(mockFilterPrefixedContent)
}

it('does not add tags when ELECTRON_ENABLE_LOGGING is enabled', () => {
vi.stubEnv('ELECTRON_ENABLE_LOGGING', '1')
expectNoTags()
})

it('does not add tags when CYPRESS_INTERNAL_ENV is development', () => {
vi.stubEnv('CYPRESS_INTERNAL_ENV', 'development')
expectNoTags()
})
})
})
61 changes: 60 additions & 1 deletion packages/stderr-filtering/lib/__spec__/TagStream.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ describe('TagStream', () => {
const cb = vi.fn()

await tagStream.transform(bufInput, 'buffer', cb)
expect(tagStream.push).toHaveBeenCalledWith('')
expect(tagStream.push).toHaveBeenCalledWith(Buffer.from(''))
expect(cb).toHaveBeenCalled()
})
})
Expand All @@ -115,4 +115,63 @@ describe('TagStream', () => {
})
})
})

describe('disabling tags', () => {
afterEach(() => {
vi.unstubAllEnvs()
})

it('passes on the string without the tags in CYPRESS_INTERNAL_ENV development mode', async () => {
vi.stubEnv('CYPRESS_INTERNAL_ENV', 'development')
const cb = vi.fn()

await tagStream.transform(strInput, 'utf-8', cb)
expect(tagStream.push).toHaveBeenCalledWith(Buffer.from(strInput))
expect(cb).toHaveBeenCalled()
})

describe('when ELECTRON_ENABLE_LOGGING is enabled', () => {
beforeEach(() => {
vi.stubEnv('ELECTRON_ENABLE_LOGGING', '1')
})

it('does not add the tags when transforming', async () => {
const cb = vi.fn()

await tagStream.transform(strInput, 'utf-8', cb)
expect(tagStream.push).toHaveBeenCalledWith(Buffer.from(strInput))
expect(cb).toHaveBeenCalled()
})

it('does not add the tags when flushing', async () => {
const cb = vi.fn()

mockStringDecoder.end.mockReturnValue(strInput)
await tagStream.flush(cb)
expect(cb).toHaveBeenCalledWith(undefined, Buffer.from(strInput))
})
})

describe('when CYPRESS_INTERNAL_ENV is development', () => {
beforeEach(() => {
vi.stubEnv('CYPRESS_INTERNAL_ENV', 'development')
})

it('does not add the tags when transforming', async () => {
const cb = vi.fn()

await tagStream.transform(strInput, 'utf-8', cb)
expect(tagStream.push).toHaveBeenCalledWith(Buffer.from(strInput))
expect(cb).toHaveBeenCalled()
})

it('does not add the tags when flushing', async () => {
const cb = vi.fn()

mockStringDecoder.end.mockReturnValue(strInput)
await tagStream.flush(cb)
expect(cb).toHaveBeenCalledWith(undefined, Buffer.from(strInput))
})
})
})
})
17 changes: 17 additions & 0 deletions packages/stderr-filtering/lib/__spec__/logError.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ describe('logError', () => {
afterEach(() => {
// Restore the original console.error
consoleErrorSpy.mockRestore()
vi.unstubAllEnvs()
})

describe('START_TAG and END_TAG constants', () => {
Expand Down Expand Up @@ -105,6 +106,22 @@ describe('logError', () => {
expect(consoleErrorSpy).toHaveBeenCalledTimes(1)
expect(consoleErrorSpy).toHaveBeenCalledWith(START_TAG, error, END_TAG)
})

it('does not add tags in CYPRESS_INTERNAL_ENV development mode', () => {
vi.stubEnv('CYPRESS_INTERNAL_ENV', 'development')
logError('Test error')

expect(consoleErrorSpy).toHaveBeenCalledTimes(1)
expect(consoleErrorSpy).toHaveBeenCalledWith('Test error')
})

it('does not add tags in ELECTRON_ENABLE_LOGGING enabled', () => {
vi.stubEnv('ELECTRON_ENABLE_LOGGING', '1')
logError('Test error')

expect(consoleErrorSpy).toHaveBeenCalledTimes(1)
expect(consoleErrorSpy).toHaveBeenCalledWith('Test error')
})
})

describe('integration with console.error', () => {
Expand Down
7 changes: 3 additions & 4 deletions packages/stderr-filtering/lib/logError.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/**
* Standard error logging tags used for stderr filtering.a
* Standard error logging tags used for stderr filtering
*/
import { START_TAG, END_TAG } from './constants'
import { tagsDisabled } from './tagsDisabled'

/**
* Logs error messages with special tags for stderr filtering.
Expand All @@ -14,12 +15,10 @@ import { START_TAG, END_TAG } from './constants'
* @param args The arguments to log as an error message
*/

const DISABLE_TAGS = process.env.ELECTRON_ENABLE_LOGGING === '1'

export const logError = (...args: any[]) => {
// When electron debug is enabled, the output will not be filtered, so
// these tags are not needed.
if (DISABLE_TAGS) {
if (tagsDisabled()) {
// eslint-disable-next-line no-console
console.error(...args)
} else {
Expand Down
3 changes: 3 additions & 0 deletions packages/stderr-filtering/lib/tagsDisabled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function tagsDisabled () {
return process.env.ELECTRON_ENABLE_LOGGING === '1' || process.env.CYPRESS_INTERNAL_ENV === 'development'
}
Loading