From 1fce043f429315135e7f4322acf25ef359141045 Mon Sep 17 00:00:00 2001 From: JPeer264 Date: Tue, 19 May 2026 18:10:00 +0200 Subject: [PATCH] test(node): Fix flaky ANR tests by waiting for debugger to be ready The ANR worker thread creates an InspectorSession and connects to the main thread's debugger. Previously, tests would start blocking work immediately, but the debugger session might not be fully initialized yet, causing stack traces to capture the wrong location (processTimers instead of longWork). This adds a waitForDebuggerReady helper to @sentry-internal/test-utils that polls inspector.url() to detect when the debugger is active, then waits 200ms for the async session setup to complete before starting the blocking work. Co-Authored-By: Claude Opus 4.5 --- .../suites/anr/app-path.mjs | 5 +++-- .../suites/anr/basic-multiple.mjs | 12 +++++++----- .../suites/anr/basic-session.js | 5 +++-- .../suites/anr/basic.js | 5 +++-- .../suites/anr/basic.mjs | 13 +++++++------ .../suites/anr/forked.js | 5 +++-- .../suites/anr/indefinite.mjs | 5 +++-- .../suites/anr/app-path.mjs | 5 +++-- .../suites/anr/basic-multiple.mjs | 12 +++++++----- .../suites/anr/basic-session.js | 5 +++-- .../suites/anr/basic.js | 5 +++-- .../suites/anr/basic.mjs | 13 +++++++------ .../suites/anr/forked.js | 5 +++-- .../suites/anr/indefinite.mjs | 5 +++-- dev-packages/test-utils/src/anr.ts | 19 +++++++++++++++++++ dev-packages/test-utils/src/index.ts | 2 ++ 16 files changed, 79 insertions(+), 42 deletions(-) create mode 100644 dev-packages/test-utils/src/anr.ts diff --git a/dev-packages/node-core-integration-tests/suites/anr/app-path.mjs b/dev-packages/node-core-integration-tests/suites/anr/app-path.mjs index 28f245851b01..1c5f141021a8 100644 --- a/dev-packages/node-core-integration-tests/suites/anr/app-path.mjs +++ b/dev-packages/node-core-integration-tests/suites/anr/app-path.mjs @@ -4,6 +4,7 @@ import * as crypto from 'crypto'; import * as path from 'path'; import * as url from 'url'; import { setupOtel } from '../../utils/setupOtel.js'; +import { waitForDebuggerReady } from '@sentry-internal/test-utils'; global._sentryDebugIds = { [new Error().stack]: 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa' }; @@ -32,6 +33,6 @@ function longWork() { } } -setTimeout(() => { +waitForDebuggerReady(() => { longWork(); -}, 1000); +}); diff --git a/dev-packages/node-core-integration-tests/suites/anr/basic-multiple.mjs b/dev-packages/node-core-integration-tests/suites/anr/basic-multiple.mjs index 1caf96d3abdb..6b0e1de340dd 100644 --- a/dev-packages/node-core-integration-tests/suites/anr/basic-multiple.mjs +++ b/dev-packages/node-core-integration-tests/suites/anr/basic-multiple.mjs @@ -2,6 +2,7 @@ import * as Sentry from '@sentry/node-core'; import * as assert from 'assert'; import * as crypto from 'crypto'; import { setupOtel } from '../../utils/setupOtel.js'; +import { waitForDebuggerReady } from '@sentry-internal/test-utils'; global._sentryDebugIds = { [new Error().stack]: 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa' }; @@ -28,10 +29,11 @@ function longWork() { } } -setTimeout(() => { +waitForDebuggerReady(() => { longWork(); -}, 1000); -setTimeout(() => { - longWork(); -}, 4000); + // Second blocking event for maxAnrEvents test + setTimeout(() => { + longWork(); + }, 2000); +}); diff --git a/dev-packages/node-core-integration-tests/suites/anr/basic-session.js b/dev-packages/node-core-integration-tests/suites/anr/basic-session.js index e89a65e79ad2..229f4eb28d5b 100644 --- a/dev-packages/node-core-integration-tests/suites/anr/basic-session.js +++ b/dev-packages/node-core-integration-tests/suites/anr/basic-session.js @@ -3,6 +3,7 @@ const assert = require('assert'); const Sentry = require('@sentry/node-core'); const { setupOtel } = require('../../utils/setupOtel.js'); +const { waitForDebuggerReady } = require('@sentry-internal/test-utils'); setTimeout(() => { process.exit(); @@ -27,6 +28,6 @@ function longWork() { } } -setTimeout(() => { +waitForDebuggerReady(() => { longWork(); -}, 1000); +}); diff --git a/dev-packages/node-core-integration-tests/suites/anr/basic.js b/dev-packages/node-core-integration-tests/suites/anr/basic.js index 9010fb296c48..d02ed4f254f1 100644 --- a/dev-packages/node-core-integration-tests/suites/anr/basic.js +++ b/dev-packages/node-core-integration-tests/suites/anr/basic.js @@ -3,6 +3,7 @@ const assert = require('assert'); const Sentry = require('@sentry/node-core'); const { setupOtel } = require('../../utils/setupOtel.js'); +const { waitForDebuggerReady } = require('@sentry-internal/test-utils'); global._sentryDebugIds = { [new Error().stack]: 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa' }; @@ -29,6 +30,6 @@ function longWork() { } } -setTimeout(() => { +waitForDebuggerReady(() => { longWork(); -}, 1000); +}); diff --git a/dev-packages/node-core-integration-tests/suites/anr/basic.mjs b/dev-packages/node-core-integration-tests/suites/anr/basic.mjs index 109a201ecb98..71525c7e9ebe 100644 --- a/dev-packages/node-core-integration-tests/suites/anr/basic.mjs +++ b/dev-packages/node-core-integration-tests/suites/anr/basic.mjs @@ -2,6 +2,7 @@ import * as Sentry from '@sentry/node-core'; import * as assert from 'assert'; import * as crypto from 'crypto'; import { setupOtel } from '../../utils/setupOtel.js'; +import { waitForDebuggerReady } from '@sentry-internal/test-utils'; global._sentryDebugIds = { [new Error().stack]: 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa' }; @@ -28,11 +29,11 @@ function longWork() { } } -setTimeout(() => { +waitForDebuggerReady(() => { longWork(); -}, 1000); -// Ensure we only send one event even with multiple blocking events -setTimeout(() => { - longWork(); -}, 4000); + // Ensure we only send one event even with multiple blocking events + setTimeout(() => { + longWork(); + }, 2000); +}); diff --git a/dev-packages/node-core-integration-tests/suites/anr/forked.js b/dev-packages/node-core-integration-tests/suites/anr/forked.js index 2fd7a0678e8d..5dd6c93e82ef 100644 --- a/dev-packages/node-core-integration-tests/suites/anr/forked.js +++ b/dev-packages/node-core-integration-tests/suites/anr/forked.js @@ -3,6 +3,7 @@ const assert = require('assert'); const Sentry = require('@sentry/node-core'); const { setupOtel } = require('../../utils/setupOtel.js'); +const { waitForDebuggerReady } = require('@sentry-internal/test-utils'); setTimeout(() => { process.exit(); @@ -28,6 +29,6 @@ function longWork() { } } -setTimeout(() => { +waitForDebuggerReady(() => { longWork(); -}, 1000); +}); diff --git a/dev-packages/node-core-integration-tests/suites/anr/indefinite.mjs b/dev-packages/node-core-integration-tests/suites/anr/indefinite.mjs index db9217ec34b2..0b8d33757e90 100644 --- a/dev-packages/node-core-integration-tests/suites/anr/indefinite.mjs +++ b/dev-packages/node-core-integration-tests/suites/anr/indefinite.mjs @@ -2,6 +2,7 @@ import * as Sentry from '@sentry/node-core'; import * as assert from 'assert'; import * as crypto from 'crypto'; import { setupOtel } from '../../utils/setupOtel.js'; +import { waitForDebuggerReady } from '@sentry-internal/test-utils'; setTimeout(() => { process.exit(); @@ -27,6 +28,6 @@ function longWork() { } } -setTimeout(() => { +waitForDebuggerReady(() => { longWork(); -}, 1000); +}); diff --git a/dev-packages/node-integration-tests/suites/anr/app-path.mjs b/dev-packages/node-integration-tests/suites/anr/app-path.mjs index 5ace9ebbb3e8..e0a13f7e6869 100644 --- a/dev-packages/node-integration-tests/suites/anr/app-path.mjs +++ b/dev-packages/node-integration-tests/suites/anr/app-path.mjs @@ -3,6 +3,7 @@ import * as assert from 'assert'; import * as crypto from 'crypto'; import * as path from 'path'; import * as url from 'url'; +import { waitForDebuggerReady } from '@sentry-internal/test-utils'; global._sentryDebugIds = { [new Error().stack]: 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa' }; @@ -29,6 +30,6 @@ function longWork() { } } -setTimeout(() => { +waitForDebuggerReady(() => { longWork(); -}, 1000); +}); diff --git a/dev-packages/node-integration-tests/suites/anr/basic-multiple.mjs b/dev-packages/node-integration-tests/suites/anr/basic-multiple.mjs index d24e5d9f20d9..a8b0aa0b7c5d 100644 --- a/dev-packages/node-integration-tests/suites/anr/basic-multiple.mjs +++ b/dev-packages/node-integration-tests/suites/anr/basic-multiple.mjs @@ -1,6 +1,7 @@ import * as Sentry from '@sentry/node'; import * as assert from 'assert'; import * as crypto from 'crypto'; +import { waitForDebuggerReady } from '@sentry-internal/test-utils'; global._sentryDebugIds = { [new Error().stack]: 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa' }; @@ -25,10 +26,11 @@ function longWork() { } } -setTimeout(() => { +waitForDebuggerReady(() => { longWork(); -}, 1000); -setTimeout(() => { - longWork(); -}, 4000); + // Second blocking event for maxAnrEvents test + setTimeout(() => { + longWork(); + }, 2000); +}); diff --git a/dev-packages/node-integration-tests/suites/anr/basic-session.js b/dev-packages/node-integration-tests/suites/anr/basic-session.js index 7971d547c884..8658c465adc9 100644 --- a/dev-packages/node-integration-tests/suites/anr/basic-session.js +++ b/dev-packages/node-integration-tests/suites/anr/basic-session.js @@ -2,6 +2,7 @@ const crypto = require('crypto'); const assert = require('assert'); const Sentry = require('@sentry/node'); +const { waitForDebuggerReady } = require('@sentry-internal/test-utils'); setTimeout(() => { process.exit(); @@ -24,6 +25,6 @@ function longWork() { } } -setTimeout(() => { +waitForDebuggerReady(() => { longWork(); -}, 1000); +}); diff --git a/dev-packages/node-integration-tests/suites/anr/basic.js b/dev-packages/node-integration-tests/suites/anr/basic.js index 430058200b8f..61b394c630e2 100644 --- a/dev-packages/node-integration-tests/suites/anr/basic.js +++ b/dev-packages/node-integration-tests/suites/anr/basic.js @@ -2,6 +2,7 @@ const crypto = require('crypto'); const assert = require('assert'); const Sentry = require('@sentry/node'); +const { waitForDebuggerReady } = require('@sentry-internal/test-utils'); global._sentryDebugIds = { [new Error().stack]: 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa' }; @@ -26,6 +27,6 @@ function longWork() { } } -setTimeout(() => { +waitForDebuggerReady(() => { longWork(); -}, 1000); +}); diff --git a/dev-packages/node-integration-tests/suites/anr/basic.mjs b/dev-packages/node-integration-tests/suites/anr/basic.mjs index 19b766c1e0e4..20737c93c5f2 100644 --- a/dev-packages/node-integration-tests/suites/anr/basic.mjs +++ b/dev-packages/node-integration-tests/suites/anr/basic.mjs @@ -1,6 +1,7 @@ import * as Sentry from '@sentry/node'; import * as assert from 'assert'; import * as crypto from 'crypto'; +import { waitForDebuggerReady } from '@sentry-internal/test-utils'; global._sentryDebugIds = { [new Error().stack]: 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa' }; @@ -25,11 +26,11 @@ function longWork() { } } -setTimeout(() => { +waitForDebuggerReady(() => { longWork(); -}, 1000); -// Ensure we only send one event even with multiple blocking events -setTimeout(() => { - longWork(); -}, 4000); + // Ensure we only send one event even with multiple blocking events + setTimeout(() => { + longWork(); + }, 2000); +}); diff --git a/dev-packages/node-integration-tests/suites/anr/forked.js b/dev-packages/node-integration-tests/suites/anr/forked.js index e0e120f41256..1abed46c7b68 100644 --- a/dev-packages/node-integration-tests/suites/anr/forked.js +++ b/dev-packages/node-integration-tests/suites/anr/forked.js @@ -2,6 +2,7 @@ const crypto = require('crypto'); const assert = require('assert'); const Sentry = require('@sentry/node'); +const { waitForDebuggerReady } = require('@sentry-internal/test-utils'); setTimeout(() => { process.exit(); @@ -25,6 +26,6 @@ function longWork() { } } -setTimeout(() => { +waitForDebuggerReady(() => { longWork(); -}, 1000); +}); diff --git a/dev-packages/node-integration-tests/suites/anr/indefinite.mjs b/dev-packages/node-integration-tests/suites/anr/indefinite.mjs index 927e4e5fc4ad..849e35903e14 100644 --- a/dev-packages/node-integration-tests/suites/anr/indefinite.mjs +++ b/dev-packages/node-integration-tests/suites/anr/indefinite.mjs @@ -1,6 +1,7 @@ import * as Sentry from '@sentry/node'; import * as assert from 'assert'; import * as crypto from 'crypto'; +import { waitForDebuggerReady } from '@sentry-internal/test-utils'; setTimeout(() => { process.exit(); @@ -24,6 +25,6 @@ function longWork() { } } -setTimeout(() => { +waitForDebuggerReady(() => { longWork(); -}, 1000); +}); diff --git a/dev-packages/test-utils/src/anr.ts b/dev-packages/test-utils/src/anr.ts new file mode 100644 index 000000000000..7ad8cb097f30 --- /dev/null +++ b/dev-packages/test-utils/src/anr.ts @@ -0,0 +1,19 @@ +import * as inspector from 'inspector'; + +/** + * Waits for the ANR worker's debugger to be fully ready before executing callback. + * + * The ANR worker creates an InspectorSession and connects to the main thread's debugger. + * We poll inspector.url() to detect when the debugger is active, then wait 200ms for the + * async session setup to complete. + */ +export function waitForDebuggerReady(cb: () => void): void { + const check = (): void => { + if (inspector.url()) { + setTimeout(cb, 200); + } else { + setImmediate(check); + } + }; + setImmediate(check); +} diff --git a/dev-packages/test-utils/src/index.ts b/dev-packages/test-utils/src/index.ts index c47f46fcde5e..aaf4cc1d886a 100644 --- a/dev-packages/test-utils/src/index.ts +++ b/dev-packages/test-utils/src/index.ts @@ -26,3 +26,5 @@ export type { CDPClientOptions } from './cdp-client'; export { MemoryProfiler } from './memory-profiler'; export type { MemoryProfilerOptions, SnapshotStats, SnapshotComparisonResult } from './memory-profiler'; + +export { waitForDebuggerReady } from './anr';