Skip to content

Commit 859285d

Browse files
authored
Add AI SDK tests to nightly Next.js CI. (#28)
Extend the Next.js test suite to include the Firebase JS AI SDK. Additionally, removed `js-sdk-framework-tests/next/yarn.lock` as it's no longer needed.
1 parent 9c5113b commit 859285d

File tree

8 files changed

+329
-3519
lines changed

8 files changed

+329
-3519
lines changed

js-sdk-framework-tests/nextjs/app/page.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ export default async function Page() {
2525
<p />
2626
<h2>Manually test:</h2>
2727
<ul>
28+
<li>AI</li>
29+
<ul>
30+
<li><Link href="/tests/ai/web_client">AI Web SDK client-side tests</Link></li>
31+
<li><Link href="/tests/ai/web_ssr">AI Web SDK server-side tests</Link></li>
32+
</ul>
33+
<p />
2834
<li>Analytics</li>
2935
<ul>
3036
<li><Link href="/tests/analytics/web_client">Analytics Web SDK client-side tests</Link></li>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
import type { Metadata } from 'next'
18+
import ClientResults from '@/components/app_tests/ai/csr_test_runner';
19+
20+
export const metadata: Metadata = {
21+
title: 'AI Web SDK CSR test'
22+
}
23+
24+
export default function Page() {
25+
return (
26+
<>
27+
<h1>AI CSR Test results:</h1>
28+
<ClientResults />
29+
</>
30+
);
31+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
import type { Metadata } from 'next'
18+
import { testAI, TestResults } from '@/lib/app_tests/ai/test';
19+
import ResultsDisplay from '@/components/app_tests/ai/results_display';
20+
21+
export const metadata: Metadata = {
22+
title: 'AI Web SDK SSR test'
23+
}
24+
25+
export default async function Page() {
26+
const testResults: TestResults = await testAI(/*isServer=*/true);
27+
return (
28+
<>
29+
<h1>AI SSR Test results:</h1>
30+
<ResultsDisplay statusString='Tests Complete!' testResults={testResults} />
31+
</>
32+
);
33+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
'use client'
18+
19+
import { useState, useEffect } from 'react'
20+
import { testAI, initializeTestResults } from '@/lib/app_tests/ai/test';
21+
import ResultsDisplay from './results_display';
22+
23+
export default function ClientResults() {
24+
const [testStatus, setTestStatus] = useState<string>("running...");
25+
const [testResults, setTestResults] = useState(initializeTestResults());
26+
useEffect(() => {
27+
const asyncTest = async () => {
28+
setTestResults(await testAI());
29+
setTestStatus("Complete!");
30+
}
31+
asyncTest().catch((e) => {
32+
console.error("Error encountered during testing: ", e);
33+
setTestStatus("Errored!");
34+
});
35+
}, []);
36+
37+
return (
38+
<ResultsDisplay statusString={testStatus} testResults={testResults} />
39+
);
40+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
import Link from 'next/link';
18+
export default function ResultsDisplay({ statusString, testResults }) {
19+
return (
20+
<>
21+
<h2 title="testStatus">{statusString}</h2>
22+
<h4 title="initializeAppResult">initializeAppResult: {testResults.initializeAppResult}</h4>
23+
<h4 title="getAIResult">getAIResult: {testResults.getAIResult}</h4>
24+
<h4 title="getGenerativeModelResult">getGenerativeModelResult: {testResults.getGenerativeModelResult}</h4>
25+
<h4 title="startChatResult">startChatResult: {testResults.startChatResult}</h4>
26+
<h4 title="chatSendFirstMessageResult">chatSendFirstMessageResult: {testResults.chatSendFirstMessageResult}</h4>
27+
<h4 title="chatFirstResponseCheckResult">chatFirstResponseCheckResult: {testResults.chatFirstResponseCheckResult}</h4>
28+
<h4 title="chatSendSecondMessageResult">chatSendSecondMessageResult: {testResults.chatSendSecondMessageResult}</h4>
29+
<h4 title="chatSecondResponseCheckResult">chatSecondResponseCheckResult: {testResults.chatSecondResponseCheckResult}</h4>
30+
<h4 title="getHistoryResult">getHistoryResult: {testResults.getHistoryResult}</h4>
31+
<p />
32+
<Link href="/">Back to test index</Link>
33+
</>
34+
);
35+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
import { initializeApp } from 'firebase/app';
18+
import {
19+
GenerationConfig,
20+
HarmBlockThreshold,
21+
HarmCategory,
22+
Content,
23+
SafetySetting,
24+
getAI,
25+
getGenerativeModel,
26+
GoogleAIBackend
27+
} from 'firebase/ai';
28+
import { firebaseConfig } from '@/lib/app_tests/firebase';
29+
import { OK, FAILED } from '@/lib/app_tests/util';
30+
31+
export type TestResults = {
32+
initializeAppResult: string,
33+
getAIResult: string,
34+
getGenerativeModelResult: string,
35+
startChatResult: string,
36+
chatSendFirstMessageResult: string,
37+
chatFirstResponseCheckResult: string,
38+
chatSendSecondMessageResult: string,
39+
chatSecondResponseCheckResult: string,
40+
getHistoryResult: string,
41+
};
42+
43+
export function initializeTestResults(): TestResults {
44+
const testAnalyticsResult: TestResults = {
45+
initializeAppResult: FAILED,
46+
getAIResult: FAILED,
47+
getGenerativeModelResult: FAILED,
48+
startChatResult: FAILED,
49+
chatSendFirstMessageResult: FAILED,
50+
chatFirstResponseCheckResult: FAILED,
51+
chatSendSecondMessageResult: FAILED,
52+
chatSecondResponseCheckResult: FAILED,
53+
getHistoryResult: FAILED,
54+
};
55+
return testAnalyticsResult;
56+
}
57+
58+
const commonGenerationConfig: GenerationConfig = {
59+
temperature: 0,
60+
topP: 0,
61+
responseMimeType: 'text/plain'
62+
};
63+
64+
const commonSafetySettings: SafetySetting[] = [
65+
{
66+
category: HarmCategory.HARM_CATEGORY_HARASSMENT,
67+
threshold: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE
68+
},
69+
{
70+
category: HarmCategory.HARM_CATEGORY_HATE_SPEECH,
71+
threshold: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE
72+
},
73+
{
74+
category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
75+
threshold: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE
76+
},
77+
{
78+
category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
79+
threshold: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE
80+
}
81+
];
82+
83+
const commonSystemInstruction: Content = {
84+
role: 'system',
85+
parts: [
86+
{
87+
text: 'You are a friendly and helpful assistant.'
88+
}
89+
]
90+
};
91+
92+
export async function testAI(isServer: boolean = false): Promise<TestResults> {
93+
if (isServer) {
94+
console.log("Server side");
95+
}
96+
const result: TestResults = initializeTestResults();
97+
const firebaseApp = initializeApp(firebaseConfig);
98+
result.initializeAppResult = OK;
99+
100+
result.signInAnonymouslyResult = OK;
101+
102+
const ai = getAI(firebaseApp, { backend: new GoogleAIBackend() });
103+
result.getAIResult = OK;
104+
105+
const model = getGenerativeModel(ai, {
106+
model: "gemini-2.5-flash",
107+
generationConfig: commonGenerationConfig,
108+
safetySettings: commonSafetySettings,
109+
systemInstruction: commonSystemInstruction
110+
});
111+
result.getGenerativeModelResult = OK;
112+
113+
const chat = model.startChat();
114+
result.startChatResult = OK;
115+
116+
const result1 = await chat.sendMessage(
117+
'What is the capital of France?'
118+
);
119+
result.chatSendFirstMessageResult = OK;
120+
121+
const response1 = result1.response;
122+
if (response1.text().length !== 0) {
123+
result.chatFirstResponseCheckResult = OK;
124+
}
125+
126+
const result2 = await chat.sendMessage('And what about Italy?');
127+
result.chatSendSecondMessageResult = OK;
128+
129+
const response2 = result2.response;
130+
if (response2.text().length !== 0) {
131+
result.chatSecondResponseCheckResult = OK;
132+
}
133+
134+
const history = await chat.getHistory();
135+
if (history.length !== 0) {
136+
result.getHistoryResult = OK;
137+
}
138+
139+
return result;
140+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
import { test, expect } from '@playwright/test';
18+
19+
async function commonExpectations(page) {
20+
await expect(page.getByTitle('initializeAppResult')).not.toContainText("FAILED");
21+
await expect(page.getByTitle('getAIResult')).not.toContainText("FAILED");
22+
await expect(page.getByTitle('getGenerativeModelResult')).not.toContainText("FAILED");
23+
await expect(page.getByTitle('startChatResult')).not.toContainText("FAILED");
24+
await expect(page.getByTitle('chatSendFirstMessageResult')).not.toContainText("FAILED");
25+
await expect(page.getByTitle('chatFirstResponseCheckResult')).not.toContainText("FAILED");
26+
await expect(page.getByTitle('chatSendSecondMessageResult')).not.toContainText("FAILED");
27+
await expect(page.getByTitle('chatSecondResponseCheckResult')).not.toContainText("FAILED");
28+
await expect(page.getByTitle('getHistoryResult')).not.toContainText("FAILED");
29+
30+
}
31+
32+
test('ai operations should pass - client', async ({ page, baseURL }) => {
33+
await page.goto(`${baseURL}/tests/ai/web_client`);
34+
await expect(page.getByTitle('testStatus')).toContainText('Complete', { timeout: 10000 });
35+
await expect(page.locator('h1')).toContainText('AI CSR Test');
36+
await commonExpectations(page);
37+
});
38+
39+
test('ai operations should pass - server', async ({ page, baseURL }) => {
40+
await page.goto(`${baseURL}/tests/ai/web_ssr`);
41+
await expect(page.getByTitle('testStatus')).toContainText('Complete', { timeout: 10000 });
42+
await expect(page.locator('h1')).toContainText('AI SSR Test');
43+
await commonExpectations(page);
44+
});

0 commit comments

Comments
 (0)