Skip to main content

API Reference

Sentry Testkit consists of a very simple and strait-forward API using the following functions

Methods

Capture and assertions

Awaiting asynchronously-sent data

  • waitForReports(count, options) — and its siblings waitForTransactions, waitForLogs, waitForMetrics, waitForAttachments, waitForFeedback, waitForCheckIns, waitForSessions, waitForSessionAggregates, waitForReplays, waitForSpans, waitForClientReports

Finding and filtering

Utilities

What About Nodejs? - Of Course!

Sentry Testkit has full support in both @sentry/browser and @sentry/node (as well as other @sentry/<what-ever> clients) since they have the same API and lifecycle under the hood.

Raven-Testkit is deprecated

raven-testkit is deprecated. Its legacy API documentation remains temporarily available for existing users. Migrate to sentry-testkit with Sentry JavaScript SDK v9 or v10.

Reference

reports()

Gets all existing reports.

Returns: Array - where each member of the array consists of Sentry's Report type.

See: You may refer to the definition of Report for further explanation and details.

For example

const waitForExpect = require('wait-for-expect')

test('reports example', async function() {
// Some scenario that will report the exceptions...

await waitForExpect(() => expect(testkit.reports().length).toBeGreaterThan(0))
const reports = testkit.reports()

// Do what ever you want with the reports list
})
info

Here we use wait-for-expect library to emphasize that sometimes we need to wait until the report is being sent as it is done asynchronously. You can also use the built-in waitForReports helper instead.

Each report also exposes evaluated feature flags as report.flags — an array of { flag, result } objects taken from the event's contexts.flags, or an empty array when no flags were attached:

expect(testkit.reports()[0].flags).toEqual([{ flag: 'new-checkout', result: true }])

Files sent with the event are exposed as report.attachments — see attachments() — and the array is empty when the event carried none.

When the error happened while a session replay was recording, report.replayId holds the id of that replay, and is undefined otherwise.

waitForReports(count, options)

Waits until at least count reports have been captured. This replaces "sleep then assert" patterns and third-party polling helpers — Sentry transports are asynchronous, so reports may not be captured yet when your assertion runs.

Arguments

  • count: Number - the minimum number of reports to wait for
  • options: Object (optional) - { timeout: Number }, defaults to { timeout: 1000 } (milliseconds)

Returns: Promise<Array> - resolves with the captured reports once the count is reached; rejects with a descriptive error if the timeout elapses first.

For example

test('waitForReports example', async function() {
Sentry.captureException(new Error('sentry test kit is awesome!'))

const reports = await testkit.waitForReports(1)
expect(reports[0].error.message).toEqual('sentry test kit is awesome!')
})

Sibling helpers with the same signature exist for the other captured types: waitForTransactions(count, options), waitForLogs(count, options), waitForMetrics(count, options), waitForAttachments(count, options), waitForFeedback(count, options), waitForCheckIns(count, options), waitForSessions(count, options), waitForSessionAggregates(count, options), waitForReplays(count, options) and waitForSpans(count, options).

findReport(error)

Finds a report by a given error.

Uses Array.prototype.find under the hood

Arguments

  • error: Error - An error object to look for in the reports

Returns: Report | undefined - the report object if one found. undefined otherwise.

See: You may refer to the definition of Report for further explanation and details.

For example

test('findReport example', async function() {
const err = new Error('error to look for')

// Some faulty scenario that will report err

const report = testkit.findReport(err)
expect(report).toBeDefined()
})

findReportByMessage(message)

Finds a report by its message — either a captureMessage message or a captured error's message.

Arguments

  • message: String | RegExp - exact message to match, or a regular expression to test against

Returns: Report | undefined - the first matching report. undefined otherwise.

For example

test('findReportByMessage example', async function() {
Sentry.captureException(new Error('failed to fetch user 42'))
await testkit.waitForReports(1)

expect(testkit.findReportByMessage(/user \d+/)).toBeDefined()
})

findTransaction(name)

Finds a transaction by its name.

Arguments

  • name: String | RegExp - exact transaction name to match, or a regular expression to test against

Returns: Transaction | undefined - the first matching transaction. undefined otherwise.

For example

test('findTransaction example', async function() {
Sentry.startInactiveSpan({ op: 'transaction', name: 'checkout-flow' }).end()
await testkit.waitForTransactions(1)

expect(testkit.findTransaction(/^checkout/)).toBeDefined()
})

findSpansByOp(op)

Finds all captured spans with a given op, across standalone spans and the spans of every captured transaction.

Arguments

  • op: String | RegExp - exact span op to match, or a regular expression to test against

Returns: Array - the matching spans (empty array when none match).

For example

test('findSpansByOp example', async function() {
// your app runs an AI agent, which reports gen_ai.* spans

await testkit.waitForSpans(1)
const chatSpans = testkit.findSpansByOp('gen_ai.chat')

expect(chatSpans).toHaveLength(1)
expect(chatSpans[0].data['gen_ai.request.model']).toEqual('gpt-4')
})

reportsWithTag(key, value)

Gets all reports carrying a given tag, optionally with a specific value.

Arguments

  • key: String - the tag key to look for
  • value: String (optional) - when provided, only reports whose tag equals this value are returned

Returns: Array - the matching reports (empty array when none match).

For example

test('reportsWithTag example', async function() {
Sentry.withScope(scope => {
scope.setTag('section', 'billing')
Sentry.captureException(new Error('tagged error'))
})
await testkit.waitForReports(1)

expect(testkit.reportsWithTag('section', 'billing')).toHaveLength(1)
})

A sibling helper with the same signature exists for transactions: transactionsWithTag(key, value).

isExist(error)

Checks whether a given error exist (i.e. has been reported)

Arguments

  • error: Error - An error object to look for in the reports

Returns: Boolean - true if the error exists. false otherwise.

For example

 test('isExist example', async function() {
const err = new Error('error to look for')
Sentry.captureException(err)
await waitForExpect(() => expect(testkit.reports()).toHaveLength(1))
expect(testkit.isExist(err)).toBe(true)
})

getExceptionAt(index)

Extracts the exception object of a report in a specific position.

Arguments

  • index : Number - index position of the report.

Returns: ReportError.

See: You may refer to the definition of ReportError for further explanation and details.

For example

test('getExceptionAt example', async function() {
Sentry.captureException(new Error('testing get exception at index 0'))
Sentry.captureException(new Error('testing get exception at index 1'))
await waitForExpect(() => expect(testkit.reports()).toHaveLength(2))
const { message } = testkit.getExceptionAt(1)
expect(message).toEqual('testing get exception at index 1')
})

transactions()

Gets all existing transactions.

Returns: Array - where each member of the array consists of a Transaction type.

See: You may refer to the definition of Transaction for further explanation and details.

For example

test('transactions example', async function() {
// Some scenario that will create a transaction...

await waitForExpect(() => expect(testkit.transactions().length).toBeGreaterThan(0))
const transactions = testkit.transactions()

// Do what ever you want with the transactions
})

spans()

Gets all captured spans, in capture order: the child spans of every captured transaction, plus the standalone spans the SDK sends as their own span envelope item - which is how AI Agent Monitoring reports its gen_ai.* spans, and how the browser SDK reports web vitals such as INP.

The spans of a transaction are the very same objects as testkit.transactions()[0].spans, so you can assert on either.

Returns: Array - where each member of the array consists of a Span type:

FieldTypeDescription
spanIdstringThe span id
traceIdstringThe trace this span belongs to
parentSpanIdstringThe parent span id, undefined for a standalone span
opstringThe span operation, e.g. db.query or gen_ai.chat
descriptionstringThe span description, i.e. the name it was started with
statusstringThe span status, e.g. ok, when the SDK sent one
originstringWhat created the span, e.g. manual or auto.http.otel
startTimestampnumberStart time in seconds
endTimestampnumberEnd time in seconds
dataObjectThe span attributes, e.g. { 'gen_ai.request.model': 'gpt-4' }
attributesObjectAlias of data, for SDK versions that send the attributes under either name
isStandalonebooleantrue for a span sent on its own, false for a span of a transaction
originalSpanObjectThe raw span payload as sent by the SDK

For example

test('spans example', async function() {
Sentry.startSpan({ name: 'checkout-flow' }, () => {
Sentry.startInactiveSpan({ name: 'select users', op: 'db.query' }).end()
})

const spans = await testkit.waitForSpans(1)
expect(spans[0].description).toEqual('select users')
expect(spans[0].op).toEqual('db.query')
expect(spans[0].isStandalone).toBe(false)
})

Use findSpansByOp(op) to pick spans out of the list, which is handy for AI agent runs:

test('ai agent spans example', async function() {
// your app runs an AI agent, which reports gen_ai.* spans

await testkit.waitForSpans(2)
const [chat] = testkit.findSpansByOp('gen_ai.chat')

expect(chat.data['gen_ai.request.model']).toEqual('gpt-4')
expect(testkit.findSpansByOp(/^gen_ai/)).toHaveLength(2)
})
note

Every span also carries its wire-format fields - span_id, trace_id, parent_span_id and id (an alias of span_id) - so assertions written against the raw span payload keep working. Prefer the camelCase fields in new tests.

logs()

Gets all captured structured logs (requires enableLogs: true in Sentry.init).

Returns: Array - where each member of the array consists of a Log type:

FieldTypeDescription
levelstringtrace | debug | info | warn | error | fatal
messagestringThe log body
attributesObjectLog attributes as plain values, e.g. { userId: 42 }
timestampnumberEpoch time in seconds
traceIdstringThe trace this log belongs to, if any
severityNumbernumberThe numeric severity, if any
originalLogObjectThe raw log item as sent by the SDK

For example

test('logs example', async function() {
Sentry.logger.info('user logged in', { userId: 42 })
await Sentry.flush()

const [log] = testkit.logs()
expect(log.level).toEqual('info')
expect(log.message).toEqual('user logged in')
expect(log.attributes.userId).toEqual(42)
})

metrics()

Gets all captured application metrics emitted via Sentry.metrics.count(...), Sentry.metrics.gauge(...) or Sentry.metrics.distribution(...) (Sentry SDK v10 and above).

Returns: Array - where each member of the array consists of a Metric type:

FieldTypeDescription
namestringThe metric name, e.g. api.requests
typestringcounter | gauge | distribution
valuenumberThe reported value
unitstringThe unit, e.g. millisecond, if provided
attributesObjectMetric attributes as plain values, e.g. { endpoint: '/api/users' }
timestampnumberEpoch time in seconds
traceIdstringThe trace this metric belongs to, if any
spanIdstringThe span this metric was emitted from, if any
originalMetricObjectThe raw metric item as sent by the SDK

Metrics are batched by the SDK, so flush before asserting on them.

For example

test('metrics example', async function() {
Sentry.metrics.count('api.requests', 1, { attributes: { endpoint: '/api/users' } })
await Sentry.flush()

const [metric] = testkit.metrics()
expect(metric.name).toEqual('api.requests')
expect(metric.type).toEqual('counter')
expect(metric.value).toEqual(1)
expect(metric.attributes.endpoint).toEqual('/api/users')
})

Alongside the attributes you set, the SDK enriches every metric with its own — sentry.release, sentry.environment, sentry.sdk.name and more — so assert on the specific attributes you care about rather than on the whole object.

attachments()

Gets all captured attachments — files sent with an event via scope.addAttachment(...) or the attachments capture option.

Returns: Array - where each member of the array consists of an Attachment type:

FieldTypeDescription
filenamestringThe attachment's filename
contentTypestringThe declared content type, if any
attachmentTypestringThe Sentry attachment type, e.g. event.attachment, if any
dataUint8ArrayThe attachment bytes, exactly as sent
textstringThe bytes decoded as UTF-8, for asserting on text attachments

Attachments travel in the same envelope as the event they belong to, so they are also exposed on that report as report.attachments:

test('attachments example', async function() {
Sentry.captureException(new Error('import failed'), {
attachments: [{ filename: 'import.csv', data: 'id,name\n1,jane', contentType: 'text/csv' }],
})

const [report] = await testkit.waitForReports(1)
expect(report.attachments).toHaveLength(1)
expect(report.attachments[0].filename).toEqual('import.csv')
expect(report.attachments[0].text).toEqual('id,name\n1,jane')
})

Use testkit.attachments() when you do not care which event an attachment belongs to:

test('scope attachment example', async function() {
Sentry.withScope(scope => {
scope.addAttachment({ filename: 'state.json', data: JSON.stringify({ cart: ['sku-1'] }) })
Sentry.captureException(new Error('checkout failed'))
})

const [attachment] = await testkit.waitForAttachments(1)
expect(JSON.parse(attachment.text)).toEqual({ cart: ['sku-1'] })
})
Binary attachments

data holds the exact bytes whenever the testkit receives them as bytes: transport mode (Node, browser and React) always does, and so does the network interceptor when your interceptor hands the request body over as a Buffer. The local server, Puppeteer and Playwright receive the request body as text, so a binary attachment such as a screenshot arrives UTF-8 decoded there. Assert on binary payloads in transport mode.

feedback()

Gets all captured user feedback submitted via Sentry.captureFeedback(...) or the feedback widget.

Returns: Array - where each member of the array consists of a FeedbackReport type:

FieldTypeDescription
messagestringThe feedback message
namestringThe submitter's name, if provided
contactEmailstringThe submitter's email, if provided
urlstringThe page the feedback was submitted from, if provided
associatedEventIdstringThe id of the error event this feedback is linked to, if any
sourcestringThe feedback source, if provided
replayIdstringThe associated replay id, if any
eventIdstringThe feedback event's own id
originalFeedbackObjectThe raw feedback event as sent by the SDK

For example

test('feedback example', async function() {
Sentry.captureFeedback({ message: 'the checkout page is confusing', email: 'jane@example.com' })

const [feedback] = await testkit.waitForFeedback(1)
expect(feedback.message).toEqual('the checkout page is confusing')
expect(feedback.contactEmail).toEqual('jane@example.com')
})

checkIns()

Gets all captured cron monitor check-ins reported via Sentry.captureCheckIn(...) or Sentry.withMonitor(...).

Returns: Array - where each member of the array consists of a CheckIn type:

FieldTypeDescription
checkInIdstringThe check-in id (used to correlate an in_progress with its ok/error)
monitorSlugstringThe monitor's slug
statusstringin_progress | ok | error
durationnumberThe check-in duration in seconds, for a finished check-in
releasestringThe release, if set
environmentstringThe environment, if set
originalCheckInObjectThe raw check-in payload as sent by the SDK

For example

test('check-in example', async function() {
const checkInId = Sentry.captureCheckIn({ monitorSlug: 'nightly-report', status: 'in_progress' })
Sentry.captureCheckIn({ checkInId, monitorSlug: 'nightly-report', status: 'ok', duration: 12.5 })

const checkIns = await testkit.waitForCheckIns(2)
expect(checkIns.map(c => c.status)).toEqual(['in_progress', 'ok'])
})

sessions()

Gets all captured release health sessions, reported either by automatic session tracking or by Sentry.startSession() / Sentry.captureSession() / Sentry.endSession().

Returns: Array - where each member of the array consists of a Session type:

FieldTypeDescription
sidstringThe session id
statusstringok | exited | crashed | abnormal
errorsnumberThe number of errors reported during the session
releasestringThe release, if set
environmentstringThe environment, if set
durationnumberThe session duration in seconds, for a finished session
originalSessionObjectThe raw session payload as sent by the SDK

For example

test('session example', async function() {
Sentry.startSession()
Sentry.captureException(new Error('checkout failed'))
await Sentry.flush()
Sentry.endSession()

const sessions = await testkit.waitForSessions(1)
const session = sessions[sessions.length - 1]
expect(session.status).toEqual('exited')
expect(session.errors).toEqual(1)
expect(session.release).toEqual('my-release')
})
note

The Node SDK starts a session when Sentry.init() runs, and Sentry.startSession() ends that one before starting a new one. Assert on the session you started rather than assuming a single captured session — either take the last one, or match on the sid from Sentry.getIsolationScope().getSession().

sessionAggregates()

Gets all captured aggregated session counts. Server-side SDKs report request-mode release health in sessions envelopes, which batch per-minute counts instead of individual sessions. Each time bucket in such an envelope becomes one entry.

Returns: Array - where each member of the array consists of a SessionAggregate type:

FieldTypeDescription
startedstringThe ISO timestamp of the time bucket
exitednumberThe number of sessions that exited without errors
errorednumberThe number of sessions that had errors
crashednumberThe number of crashed sessions
abnormalnumberThe number of abnormally-ended sessions
releasestringThe release, if set
environmentstringThe environment, if set
originalAggregateObjectThe raw aggregate payload as sent by the SDK

For example

test('aggregated sessions example', async function() {
// your server framework reports these while handling requests
const aggregates = await testkit.waitForSessionAggregates(1)

expect(aggregates[0].crashed).toEqual(0)
expect(aggregates[0].exited).toEqual(2)
})

replays()

Gets all captured session replay segments. A replay is recorded in segments, and each one is sent as a replay_event item paired with a replay_recording item in the same envelope — so each captured segment is one entry here, and a single replay usually produces several.

Returns: Array - where each member of the array consists of a Replay type:

FieldTypeDescription
replayIdstringThe replay id, shared by all segments of the same replay
segmentIdnumberThe zero-based index of this segment within the replay
replayTypestringsession for a sampled session, buffer for a replay flushed because an error occurred
traceIdsArray<string>The traces recorded during the segment
errorIdsArray<string>The ids of the error events recorded during the segment
urlsArray<string>The urls visited during the segment
timestampnumberThe segment timestamp, in seconds
releasestringThe release, if set
environmentstringThe environment, if set
recordingUint8ArrayThe raw recording payload of the segment, undefined when the envelope carried none
originalReplayObjectThe raw replay event payload as sent by the SDK

For example

test('replay example', async function() {
// your app runs with Sentry.replayIntegration() enabled

const replays = await testkit.waitForReplays(1)
expect(replays[0].replayType).toEqual('session')
expect(replays[0].urls).toContain('https://example.com/checkout')
})

The error events recorded while a replay is running carry its id, so you can assert that an error is linked to a replay:

test('errors are linked to the replay they happened in', async function() {
const [report] = await testkit.waitForReports(1)
const [replay] = await testkit.waitForReplays(1)

expect(report.replayId).toEqual(replay.replayId)
})
note

recording holds the segment payload exactly as it was sent: its own {"segment_id":n} header line followed by the rrweb events, gzipped whenever the SDK has a compression worker available. Assert on the metadata fields rather than on the recording contents.

clientReports()

Gets all captured client reports — the SDK's own account of events it dropped client-side instead of sending, because a beforeSend hook returned null, an ignoreErrors entry matched, a sample rate discarded them, the queue overflowed, or the SDK was rate limited.

This is how you assert on what was not sent.

Returns: Array - where each member of the array consists of a ClientReport type:

FieldTypeDescription
timestampnumberThe time the report was created, in seconds
discardedEventsArrayOne entry per reason and category pair, each with a reason (before_send, event_processor, sample_rate, queue_overflow, ratelimit_backoff, ...), a category (error, transaction, span, replay, log_item, ...) and the quantity of items dropped for that pair
originalClientReportObjectThe raw client report payload as sent by the SDK

For example

test('beforeSend filters out the errors it is meant to filter', async function() {
Sentry.captureException(new Error('a noisy error you filter out'))
await Sentry.flush()
await Sentry.flush()

const [clientReport] = await testkit.waitForClientReports(1)
expect(clientReport.discardedEvents).toEqual([
{ reason: 'before_send', category: 'error', quantity: 1 },
])
expect(testkit.reports()).toHaveLength(0)
})
Why two flushes

The Node SDK sends the outcomes it has accumulated at the start of a flush, and records the outcome of a dropped event only while that same flush drains the event pipeline. A client report therefore goes out on the flush after the one that dropped the event. Dropping decisions made synchronously, such as a transaction discarded by tracesSampleRate, need only a single flush.

The browser SDK does not flush outcomes on Sentry.flush() at all — it sends them when the page becomes hidden. In a browser test environment, dispatch that yourself:

Sentry.captureException(new Error('a noisy error you filter out'))
await Sentry.flush()

Object.defineProperty(document, 'visibilityState', { value: 'hidden', configurable: true })
document.dispatchEvent(new Event('visibilitychange'))

const [clientReport] = await testkit.waitForClientReports(1)
note

Client reports are only sent when sendClientReports is enabled in Sentry.init — it defaults to true. Outcomes are batched, so a single report usually covers several dropped events, and the same reason and category pair arrives as one entry with its quantity summed rather than as repeated entries.

reset()

Resets the testkit state and clear all existing reports.

For example

test('reset example', async function() {
Sentry.captureException(new Error('Sentry test kit is awesome!'))
await waitForExpect(() => expect(testkit.reports()).toHaveLength(1))
expect(testkit.reports()).toHaveLength(1)
testkit.reset()
expect(testkit.reports()).toHaveLength(0)
})
tip

Calling reset() is very useful to run between tests, see more info and examples here