mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-26 04:54:25 -07:00
test: add browser agent integration tests (#21151)
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Integration tests for the browser agent.
|
||||
*
|
||||
* These tests verify the complete end-to-end flow from CLI prompt through
|
||||
* browser_agent delegation to MCP/Chrome DevTools and back. Unlike the unit
|
||||
* tests in packages/core/src/agents/browser/ which mock all MCP components,
|
||||
* these tests launch real Chrome instances in headless mode.
|
||||
*
|
||||
* Tests are skipped on systems without Chrome/Chromium installed.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { TestRig, assertModelHasOutput } from './test-helper.js';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { execSync } from 'node:child_process';
|
||||
import { existsSync } from 'node:fs';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const chromeAvailable = (() => {
|
||||
try {
|
||||
if (process.platform === 'darwin') {
|
||||
execSync(
|
||||
'test -d "/Applications/Google Chrome.app" || test -d "/Applications/Chromium.app"',
|
||||
{
|
||||
stdio: 'ignore',
|
||||
},
|
||||
);
|
||||
} else if (process.platform === 'linux') {
|
||||
execSync(
|
||||
'which google-chrome || which chromium-browser || which chromium',
|
||||
{ stdio: 'ignore' },
|
||||
);
|
||||
} else if (process.platform === 'win32') {
|
||||
// Check standard Windows installation paths using Node.js fs
|
||||
const chromePaths = [
|
||||
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
|
||||
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
|
||||
`${process.env['LOCALAPPDATA'] ?? ''}\\Google\\Chrome\\Application\\chrome.exe`,
|
||||
];
|
||||
const found = chromePaths.some((p) => existsSync(p));
|
||||
if (!found) {
|
||||
// Fall back to PATH check
|
||||
execSync('where chrome || where chromium', { stdio: 'ignore' });
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
})();
|
||||
|
||||
describe.skipIf(!chromeAvailable)('browser-agent', () => {
|
||||
let rig: TestRig;
|
||||
|
||||
beforeEach(() => {
|
||||
rig = new TestRig();
|
||||
});
|
||||
|
||||
afterEach(async () => await rig.cleanup());
|
||||
|
||||
it('should navigate to a page and capture accessibility tree', async () => {
|
||||
rig.setup('browser-navigate-and-snapshot', {
|
||||
fakeResponsesPath: join(
|
||||
__dirname,
|
||||
'browser-agent.navigate-snapshot.responses',
|
||||
),
|
||||
settings: {
|
||||
agents: {
|
||||
browser_agent: {
|
||||
headless: true,
|
||||
sessionMode: 'isolated',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const result = await rig.run({
|
||||
args: 'Open https://example.com in the browser and tell me the page title and main content.',
|
||||
});
|
||||
|
||||
assertModelHasOutput(result);
|
||||
|
||||
const toolLogs = rig.readToolLogs();
|
||||
const browserAgentCall = toolLogs.find(
|
||||
(t) => t.toolRequest.name === 'browser_agent',
|
||||
);
|
||||
expect(
|
||||
browserAgentCall,
|
||||
'Expected browser_agent to be called',
|
||||
).toBeDefined();
|
||||
});
|
||||
|
||||
it('should take screenshots of web pages', async () => {
|
||||
rig.setup('browser-screenshot', {
|
||||
fakeResponsesPath: join(__dirname, 'browser-agent.screenshot.responses'),
|
||||
settings: {
|
||||
agents: {
|
||||
browser_agent: {
|
||||
headless: true,
|
||||
sessionMode: 'isolated',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const result = await rig.run({
|
||||
args: 'Navigate to https://example.com and take a screenshot.',
|
||||
});
|
||||
|
||||
const toolLogs = rig.readToolLogs();
|
||||
const browserCalls = toolLogs.filter(
|
||||
(t) => t.toolRequest.name === 'browser_agent',
|
||||
);
|
||||
expect(browserCalls.length).toBeGreaterThan(0);
|
||||
|
||||
assertModelHasOutput(result);
|
||||
});
|
||||
|
||||
it('should interact with page elements', async () => {
|
||||
rig.setup('browser-interaction', {
|
||||
fakeResponsesPath: join(__dirname, 'browser-agent.interaction.responses'),
|
||||
settings: {
|
||||
agents: {
|
||||
browser_agent: {
|
||||
headless: true,
|
||||
sessionMode: 'isolated',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const result = await rig.run({
|
||||
args: 'Go to https://example.com, find any links on the page, and describe them.',
|
||||
});
|
||||
|
||||
const toolLogs = rig.readToolLogs();
|
||||
const browserAgentCall = toolLogs.find(
|
||||
(t) => t.toolRequest.name === 'browser_agent',
|
||||
);
|
||||
expect(
|
||||
browserAgentCall,
|
||||
'Expected browser_agent to be called',
|
||||
).toBeDefined();
|
||||
|
||||
assertModelHasOutput(result);
|
||||
});
|
||||
|
||||
it('should clean up browser processes after completion', async () => {
|
||||
rig.setup('browser-cleanup', {
|
||||
fakeResponsesPath: join(__dirname, 'browser-agent.cleanup.responses'),
|
||||
settings: {
|
||||
agents: {
|
||||
browser_agent: {
|
||||
headless: true,
|
||||
sessionMode: 'isolated',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await rig.run({
|
||||
args: 'Open https://example.com in the browser and check the page title.',
|
||||
});
|
||||
|
||||
// Test passes if we reach here, relying on Vitest's timeout mechanism
|
||||
// to detect hanging browser processes.
|
||||
});
|
||||
|
||||
it('should handle multiple browser operations in sequence', async () => {
|
||||
rig.setup('browser-sequential', {
|
||||
fakeResponsesPath: join(__dirname, 'browser-agent.sequential.responses'),
|
||||
settings: {
|
||||
agents: {
|
||||
browser_agent: {
|
||||
headless: true,
|
||||
sessionMode: 'isolated',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const result = await rig.run({
|
||||
args: 'Navigate to https://example.com, take a snapshot of the accessibility tree, then take a screenshot.',
|
||||
});
|
||||
|
||||
const toolLogs = rig.readToolLogs();
|
||||
const browserCalls = toolLogs.filter(
|
||||
(t) => t.toolRequest.name === 'browser_agent',
|
||||
);
|
||||
expect(browserCalls.length).toBeGreaterThan(0);
|
||||
|
||||
// Should successfully complete all operations
|
||||
assertModelHasOutput(result);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user