Skip to content

Commit e8f4c42

Browse files
chargomeclaude
andauthored
test(e2e): Add span streaming test app for next 16 (#20648)
Duplicates our existing test suite on next-16 to span streaming. closes #20668 --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 80cb35a commit e8f4c42

68 files changed

Lines changed: 1261 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.*
7+
.yarn/*
8+
!.yarn/patches
9+
!.yarn/plugins
10+
!.yarn/releases
11+
!.yarn/versions
12+
13+
# testing
14+
/coverage
15+
16+
# next.js
17+
/.next/
18+
/out/
19+
20+
# production
21+
/build
22+
23+
# misc
24+
.DS_Store
25+
*.pem
26+
27+
# debug
28+
npm-debug.log*
29+
yarn-debug.log*
30+
yarn-error.log*
31+
.pnpm-debug.log*
32+
33+
# env files (can opt-in for committing if needed)
34+
.env*
35+
36+
# vercel
37+
.vercel
38+
39+
# typescript
40+
*.tsbuildinfo
41+
next-env.d.ts
42+
43+
# Sentry Config File
44+
.env.sentry-build-plugin
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { PropsWithChildren } from 'react';
2+
3+
export const dynamic = 'force-dynamic';
4+
5+
export default function Layout({ children }: PropsWithChildren<{}>) {
6+
return (
7+
<div>
8+
<p>Layout</p>
9+
{children}
10+
</div>
11+
);
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { PropsWithChildren } from 'react';
2+
3+
export const dynamic = 'force-dynamic';
4+
5+
export default function Layout({ children }: PropsWithChildren<{}>) {
6+
return (
7+
<div>
8+
<p>DynamicLayout</p>
9+
{children}
10+
</div>
11+
);
12+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export const dynamic = 'force-dynamic';
2+
3+
export default async function Page() {
4+
return (
5+
<div>
6+
<p>Dynamic Page</p>
7+
</div>
8+
);
9+
}
10+
11+
export async function generateMetadata() {
12+
return {
13+
title: 'I am dynamic page generated metadata',
14+
};
15+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { PropsWithChildren } from 'react';
2+
3+
export const dynamic = 'force-dynamic';
4+
5+
export default function Layout({ children }: PropsWithChildren<{}>) {
6+
return (
7+
<div>
8+
<p>Layout</p>
9+
{children}
10+
</div>
11+
);
12+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export const dynamic = 'force-dynamic';
2+
3+
export default function Page() {
4+
return <p>Hello World!</p>;
5+
}
6+
7+
export async function generateMetadata() {
8+
return {
9+
title: 'I am generated metadata',
10+
};
11+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { generateText } from 'ai';
2+
import { MockLanguageModelV1 } from 'ai/test';
3+
import { z } from 'zod';
4+
import * as Sentry from '@sentry/nextjs';
5+
6+
export const dynamic = 'force-dynamic';
7+
8+
// Error trace handling in tool calls
9+
async function runAITest() {
10+
const result = await generateText({
11+
experimental_telemetry: { isEnabled: true },
12+
model: new MockLanguageModelV1({
13+
doGenerate: async () => ({
14+
rawCall: { rawPrompt: null, rawSettings: {} },
15+
finishReason: 'tool-calls',
16+
usage: { promptTokens: 15, completionTokens: 25 },
17+
text: 'Tool call completed!',
18+
toolCalls: [
19+
{
20+
toolCallType: 'function',
21+
toolCallId: 'call-1',
22+
toolName: 'getWeather',
23+
args: '{ "location": "San Francisco" }',
24+
},
25+
],
26+
}),
27+
}),
28+
tools: {
29+
getWeather: {
30+
parameters: z.object({ location: z.string() }),
31+
execute: async args => {
32+
throw new Error('Tool call failed');
33+
},
34+
},
35+
},
36+
prompt: 'What is the weather in San Francisco?',
37+
});
38+
}
39+
40+
export default async function Page() {
41+
await Sentry.startSpan({ op: 'function', name: 'ai-error-test' }, async () => {
42+
return await runAITest();
43+
});
44+
45+
return (
46+
<div>
47+
<h1>AI Test Results</h1>
48+
</div>
49+
);
50+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { generateText } from 'ai';
2+
import { MockLanguageModelV1 } from 'ai/test';
3+
import { z } from 'zod';
4+
import * as Sentry from '@sentry/nextjs';
5+
6+
export const dynamic = 'force-dynamic';
7+
8+
async function runAITest() {
9+
// First span - telemetry should be enabled automatically but no input/output recorded when sendDefaultPii: true
10+
const result1 = await generateText({
11+
model: new MockLanguageModelV1({
12+
doGenerate: async () => ({
13+
rawCall: { rawPrompt: null, rawSettings: {} },
14+
finishReason: 'stop',
15+
usage: { promptTokens: 10, completionTokens: 20 },
16+
text: 'First span here!',
17+
}),
18+
}),
19+
prompt: 'Where is the first span?',
20+
});
21+
22+
// Second span - explicitly enabled telemetry, should record inputs/outputs
23+
const result2 = await generateText({
24+
experimental_telemetry: { isEnabled: true },
25+
model: new MockLanguageModelV1({
26+
doGenerate: async () => ({
27+
rawCall: { rawPrompt: null, rawSettings: {} },
28+
finishReason: 'stop',
29+
usage: { promptTokens: 10, completionTokens: 20 },
30+
text: 'Second span here!',
31+
}),
32+
}),
33+
prompt: 'Where is the second span?',
34+
});
35+
36+
// Third span - with tool calls and tool results
37+
const result3 = await generateText({
38+
model: new MockLanguageModelV1({
39+
doGenerate: async () => ({
40+
rawCall: { rawPrompt: null, rawSettings: {} },
41+
finishReason: 'tool-calls',
42+
usage: { promptTokens: 15, completionTokens: 25 },
43+
text: 'Tool call completed!',
44+
toolCalls: [
45+
{
46+
toolCallType: 'function',
47+
toolCallId: 'call-1',
48+
toolName: 'getWeather',
49+
args: '{ "location": "San Francisco" }',
50+
},
51+
],
52+
}),
53+
}),
54+
tools: {
55+
getWeather: {
56+
parameters: z.object({ location: z.string() }),
57+
execute: async args => {
58+
return `Weather in ${args.location}: Sunny, 72°F`;
59+
},
60+
},
61+
},
62+
prompt: 'What is the weather in San Francisco?',
63+
});
64+
65+
// Fourth span - explicitly disabled telemetry, should not be captured
66+
const result4 = await generateText({
67+
experimental_telemetry: { isEnabled: false },
68+
model: new MockLanguageModelV1({
69+
doGenerate: async () => ({
70+
rawCall: { rawPrompt: null, rawSettings: {} },
71+
finishReason: 'stop',
72+
usage: { promptTokens: 10, completionTokens: 20 },
73+
text: 'Third span here!',
74+
}),
75+
}),
76+
prompt: 'Where is the third span?',
77+
});
78+
79+
return {
80+
result1: result1.text,
81+
result2: result2.text,
82+
result3: result3.text,
83+
result4: result4.text,
84+
};
85+
}
86+
87+
export default async function Page() {
88+
const results = await Sentry.startSpan({ op: 'function', name: 'ai-test' }, async () => {
89+
return await runAITest();
90+
});
91+
92+
return (
93+
<div>
94+
<h1>AI Test Results</h1>
95+
<pre id="ai-results">{JSON.stringify(results, null, 2)}</pre>
96+
</div>
97+
);
98+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export const dynamic = 'force-dynamic';
2+
3+
export async function GET() {
4+
throw new Error('Cron job error');
5+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { NextResponse } from 'next/server';
2+
3+
export const dynamic = 'force-dynamic';
4+
5+
export async function GET() {
6+
// Simulate some work
7+
await new Promise(resolve => setTimeout(resolve, 100));
8+
return NextResponse.json({ message: 'Cron job executed successfully' });
9+
}

0 commit comments

Comments
 (0)