Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { MockLanguageModelV3 } from 'ai/test';

export default Sentry.withSentry(
(env: Env) => ({
traceLifecycle: 'static',
dsn: env.E2E_TEST_DSN,
environment: 'qa',
tunnel: 'http://localhost:3031/',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { expect, test } from '@playwright/test';
import { getSpanOp, waitForStreamedSpans } from '@sentry-internal/test-utils';

test('captures Vercel AI v7 spans with nodejs_compat using tracing channels', async ({ baseURL }) => {
// gen_ai spans are extracted into a separate span v2 envelope item
const genAiSpansPromise = waitForStreamedSpans('cloudflare-vercelai-v7-compat', spans =>
spans.some(span => getSpanOp(span) === 'gen_ai.invoke_agent'),
);
Expand All @@ -13,30 +12,44 @@ test('captures Vercel AI v7 spans with nodejs_compat using tracing channels', as

const genAiSpans = await genAiSpansPromise;

expect(genAiSpans).toEqual(
expect.arrayContaining([
expect.objectContaining({
name: 'invoke_agent',
attributes: expect.objectContaining({
'gen_ai.operation.name': { value: 'invoke_agent', type: 'string' },
'gen_ai.usage.input_tokens': { value: 10, type: 'integer' },
'gen_ai.usage.output_tokens': { value: 20, type: 'integer' },
'gen_ai.usage.total_tokens': { value: 30, type: 'integer' },
'sentry.op': { value: 'gen_ai.invoke_agent', type: 'string' },
'sentry.origin': { value: 'auto.vercelai.channel', type: 'string' },
}),
}),
expect.objectContaining({
name: 'generate_content mock-model-id',
attributes: expect.objectContaining({
'gen_ai.operation.name': { value: 'generate_content', type: 'string' },
'gen_ai.usage.input_tokens': { value: 10, type: 'integer' },
'gen_ai.usage.output_tokens': { value: 20, type: 'integer' },
'gen_ai.usage.total_tokens': { value: 30, type: 'integer' },
'sentry.op': { value: 'gen_ai.generate_content', type: 'string' },
'sentry.origin': { value: 'auto.vercelai.channel', type: 'string' },
}),
}),
]),
);
const invokeAgentSpan = genAiSpans.find(span => getSpanOp(span) === 'gen_ai.invoke_agent');
const generateContentSpan = genAiSpans.find(span => getSpanOp(span) === 'gen_ai.generate_content');
Comment on lines +15 to +16

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The test assumes invoke_agent and generate_content spans arrive in the same envelope. With streaming enabled, they might arrive separately, causing generateContentSpan to be undefined and the test to fail.
Severity: MEDIUM

Suggested Fix

To make the test robust against spans arriving in different envelopes, replace the waitForStreamedSpans function with collectStreamedSpans. The collectStreamedSpans function is designed to accumulate all spans for a trace across multiple envelopes, ensuring that both invoke_agent and generate_content spans are available for assertion regardless of how they are flushed.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location:
dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7/tests/index.test.ts#L15-L16

Potential issue: The test logic at this location has been updated to assert on streamed
spans. However, it uses the `waitForStreamedSpans` function, which only captures spans
from a single data envelope. With the removal of `traceLifecycle: 'static'`, the
Cloudflare SDK's `SpanStreaming` integration is enabled by default. This integration may
flush the `invoke_agent` and `generate_content` spans in separate envelopes. If this
occurs, `waitForStreamedSpans` will only capture the first envelope, likely containing
just the `invoke_agent` span. As a result, the `generateContentSpan` variable will be
`undefined`, causing the test assertion that checks its `parent_span_id` to fail.

Did we get this right? 👍 / 👎 to inform future reviews.


expect(invokeAgentSpan).toEqual({
trace_id: expect.stringMatching(/^[a-f0-9]{32}$/),
parent_span_id: expect.stringMatching(/^[a-f0-9]{16}$/),
span_id: expect.stringMatching(/^[a-f0-9]{16}$/),
name: 'invoke_agent',
start_timestamp: expect.any(Number),
end_timestamp: expect.any(Number),
status: 'ok',
is_segment: false,
attributes: expect.objectContaining({
'gen_ai.operation.name': { value: 'invoke_agent', type: 'string' },
'gen_ai.usage.input_tokens': { value: 10, type: 'integer' },
'gen_ai.usage.output_tokens': { value: 20, type: 'integer' },
'gen_ai.usage.total_tokens': { value: 30, type: 'integer' },
'sentry.op': { value: 'gen_ai.invoke_agent', type: 'string' },
'sentry.origin': { value: 'auto.vercelai.channel', type: 'string' },
}),
});

expect(generateContentSpan).toEqual({
trace_id: invokeAgentSpan!.trace_id,
parent_span_id: invokeAgentSpan!.span_id,
span_id: expect.stringMatching(/^[a-f0-9]{16}$/),
name: 'generate_content mock-model-id',
start_timestamp: expect.any(Number),
end_timestamp: expect.any(Number),
status: 'ok',
is_segment: false,
attributes: expect.objectContaining({
'gen_ai.operation.name': { value: 'generate_content', type: 'string' },
'gen_ai.usage.input_tokens': { value: 10, type: 'integer' },
'gen_ai.usage.output_tokens': { value: 20, type: 'integer' },
'gen_ai.usage.total_tokens': { value: 30, type: 'integer' },
'sentry.op': { value: 'gen_ai.generate_content', type: 'string' },
'sentry.origin': { value: 'auto.vercelai.channel', type: 'string' },
}),
});
});
Loading