From cceda5fb3167ef1c9176794b41ae8fa6e0ceadad Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 1 Jul 2026 14:27:21 +0200 Subject: [PATCH 1/2] test(node): Ensure logs are auto-printed when a test fails --- .../utils/runner/createRunner.ts | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/dev-packages/node-integration-tests/utils/runner/createRunner.ts b/dev-packages/node-integration-tests/utils/runner/createRunner.ts index 2eee8f971c19..bdc71d041d26 100644 --- a/dev-packages/node-integration-tests/utils/runner/createRunner.ts +++ b/dev-packages/node-integration-tests/utils/runner/createRunner.ts @@ -248,6 +248,32 @@ export function createRunner(...paths: string[]) { child?.kill(); } + /** + * Print everything the child process wrote to stdout/stderr. Called when a test fails or + * times out so the captured output is visible in CI logs. Skipped when `DEBUG` is set, since + * that already streams the same lines live as they arrive. + */ + function dumpCapturedLogs(): void { + // When in debug mode, this is already printed anyhow + if (process.env.DEBUG) { + return; + } + + // eslint-disable-next-line no-console + console.log(`\n--- Captured child process output for ${testPath} ---`); + if (logs.length === 0) { + // eslint-disable-next-line no-console + console.log('(no output captured)'); + } else { + for (const line of logs) { + // eslint-disable-next-line no-console + console.log(line); + } + } + // eslint-disable-next-line no-console + console.log('--- End of captured child process output ---\n'); + } + /** Called after each expect callback to check if we're complete */ function expectCallbackCalled(): void { envelopeCount++; @@ -472,9 +498,18 @@ export function createRunner(...paths: string[]) { return { completed: async function (): Promise { - await waitFor(() => isComplete, 120_000, 'Timed out waiting for test to complete'); + try { + await waitFor(() => isComplete, 120_000, 'Timed out waiting for test to complete'); + } catch (e) { + // On timeout, dump the captured child output (same info `DEBUG=1` would have streamed live) + // so CI failures are diagnosable without re-running locally with DEBUG enabled. + dumpCapturedLogs(); + throw e; + } if (completeError) { + // Same rationale as the timeout branch: surface what the child actually logged before failing. + dumpCapturedLogs(); throw completeError; } }, From 6cc20c82c30b056b989e48f2d47a5965200a4710 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 1 Jul 2026 15:13:12 +0200 Subject: [PATCH 2/2] suppress logs when expecting failure --- .../utils/runner/createEsmAndCjsTests.ts | 18 ++++++++++++++++-- .../utils/runner/createRunner.ts | 17 +++++++++++++++-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/dev-packages/node-integration-tests/utils/runner/createEsmAndCjsTests.ts b/dev-packages/node-integration-tests/utils/runner/createEsmAndCjsTests.ts index 60ef03222a59..c5395ee6351b 100644 --- a/dev-packages/node-integration-tests/utils/runner/createEsmAndCjsTests.ts +++ b/dev-packages/node-integration-tests/utils/runner/createEsmAndCjsTests.ts @@ -50,14 +50,28 @@ export function createEsmAndCjsTests( const createdRunners = new Set>(); callback( - () => trackRunner(createdRunners, createRunner(paths.esm.scenario).withFlags('--import', paths.esm.instrument)), + () => { + const runner = createRunner(paths.esm.scenario).withFlags('--import', paths.esm.instrument); + // Expected failure — don't dump the child's captured output for these. + if (options?.failsOnEsm) { + runner.suppressErrorLogs(); + } + return trackRunner(createdRunners, runner); + }, esmTestFn, 'esm', tmpDirPath, ); callback( - () => trackRunner(createdRunners, createRunner(paths.cjs.scenario).withFlags('--require', paths.cjs.instrument)), + () => { + const runner = createRunner(paths.cjs.scenario).withFlags('--require', paths.cjs.instrument); + // Expected failure — don't dump the child's captured output for these. + if (options?.failsOnCjs) { + runner.suppressErrorLogs(); + } + return trackRunner(createdRunners, runner); + }, cjsTestFn, 'cjs', tmpDirPath, diff --git a/dev-packages/node-integration-tests/utils/runner/createRunner.ts b/dev-packages/node-integration-tests/utils/runner/createRunner.ts index bdc71d041d26..3b3f57ba35c3 100644 --- a/dev-packages/node-integration-tests/utils/runner/createRunner.ts +++ b/dev-packages/node-integration-tests/utils/runner/createRunner.ts @@ -136,6 +136,10 @@ export function createRunner(...paths: string[]) { let withSentryServer = false; let dockerOptions: DockerOptions | undefined; let ensureNoErrorOutput = false; + // When set, the test using this runner expects `completed()` to reject (e.g. `test.fails` variants + // created via `createEsmAndCjsTests` with `failsOnEsm`/`failsOnCjs`). We suppress the captured-log + // dump in that case, since the failure is expected and the output would just be noise. + let suppressErrorLogs = false; const logs: string[] = []; if (testPath.endsWith('.ts')) { @@ -227,6 +231,14 @@ export function createRunner(...paths: string[]) { ensureNoErrorOutput = true; return this; }, + /** + * Mark this runner's test as expected to fail (i.e. `completed()` is expected to reject). + * Suppresses the captured-log dump so expected failures don't emit noisy output. + */ + suppressErrorLogs: function () { + suppressErrorLogs = true; + return this; + }, start: function (): StartResult { let isComplete = false; let completeError: Error | undefined; @@ -254,8 +266,9 @@ export function createRunner(...paths: string[]) { * that already streams the same lines live as they arrive. */ function dumpCapturedLogs(): void { - // When in debug mode, this is already printed anyhow - if (process.env.DEBUG) { + // Skip when the failure is expected (`test.fails` variants) — the output would just be noise. + // In debug mode the same lines are already streamed live, so skip then too. + if (process.env.DEBUG || suppressErrorLogs) { return; }