-
Notifications
You must be signed in to change notification settings - Fork 13k
test: add browser agent integration tests #21151
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
gsquared94
merged 13 commits into
google-gemini:main
from
kunal-10-cloud:test/browser-agent-integration-tests
Mar 5, 2026
Merged
Changes from 1 commit
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
e3d4d6d
test: add browser agent integration tests
kunal-10-cloud f74c45c
Merge branch 'main' into test/browser-agent-integration-tests
kunal-10-cloud 0b095d9
Merge branch 'main' into test/browser-agent-integration-tests
kunal-10-cloud 9754d75
fix:updated commit on gemini bot's suggestion
kunal-10-cloud e40ff1c
fix:removed wait time as per gemini bot suggestion
kunal-10-cloud 0cf2226
test: remove explicit timeout delay per PR review
kunal-10-cloud 5a17505
test: add windows support for chrome availability check
kunal-10-cloud 37b8f16
Merge branch 'main' into test/browser-agent-integration-tests
kunal-10-cloud 33532cf
fix: address maintainer feedback for browser agent tests
kunal-10-cloud 545b786
ci: temporarily enable workflow on fork for E2E verification
kunal-10-cloud f088a6a
Revert "ci: temporarily enable workflow on fork for E2E verification"
kunal-10-cloud b1aecfb
Merge branch 'main' into test/browser-agent-integration-tests
kunal-10-cloud b9d1388
chore: fix prettier formatting issues
kunal-10-cloud File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| {"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I'll open https://example.com and check the page title for you."},{"functionCall":{"name":"browser_agent","args":{"task":"Open https://example.com and get the page title"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":35,"totalTokenCount":135}}]} | ||
| {"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The page title of https://example.com is \"Example Domain\". The browser session has been completed and cleaned up successfully."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":30,"totalTokenCount":230}}]} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| {"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I'll navigate to https://example.com and analyze the links on the page."},{"functionCall":{"name":"browser_agent","args":{"task":"Go to https://example.com and find all links on the page, then describe them"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":40,"totalTokenCount":140}}]} | ||
| {"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"After analyzing https://example.com, I found the following links:\n\n1. **\"More information...\"** - This is the main link on the page that points to the IANA (Internet Assigned Numbers Authority) website for more details about reserved domains.\n\nThe page is quite minimal with just this single informational link, which is typical for example domains used in documentation."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":70,"totalTokenCount":270}}]} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| {"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I'll help you open https://example.com and analyze the page. Let me use the browser agent to navigate and capture the page information."},{"functionCall":{"name":"browser_agent","args":{"task":"Navigate to https://example.com and capture the accessibility tree to get the page title and main content"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":50,"totalTokenCount":150}}]} | ||
| {"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Based on the browser analysis of https://example.com:\n\n**Page Title**: Example Domain\n\n**Main Content**: The page contains a simple heading \"Example Domain\" and explanatory text stating \"This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.\"\n\nThe page has a clean, minimal layout typical of placeholder domains used for documentation and examples."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":80,"totalTokenCount":280}}]} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| {"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I'll navigate to https://example.com and take a screenshot for you."},{"functionCall":{"name":"browser_agent","args":{"task":"Navigate to https://example.com and take a screenshot of the page"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":40,"totalTokenCount":140}}]} | ||
| {"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I've successfully navigated to https://example.com and captured a screenshot. The page shows the familiar \"Example Domain\" header with explanatory text below it. The screenshot captures the clean, minimal layout of this demonstration website."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":50,"totalTokenCount":250}}]} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| {"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I'll help you navigate to https://example.com and perform both operations - capturing the accessibility tree and taking a screenshot."},{"functionCall":{"name":"browser_agent","args":{"task":"Navigate to https://example.com, take a snapshot of the accessibility tree, then take a screenshot"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":45,"totalTokenCount":145}}]} | ||
| {"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I've successfully completed both operations on https://example.com:\n\n**Accessibility Tree Snapshot**: The page has a clear structure with the main heading \"Example Domain\" and descriptive text about the domain's purpose for documentation examples.\n\n**Screenshot**: Captured a visual representation of the page showing the clean, minimal layout with the heading and explanatory text.\n\nBoth the accessibility data and visual screenshot confirm this is the standard example domain page used for documentation purposes."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":80,"totalTokenCount":280}}]} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,198 @@ | ||
| /** | ||
| * @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 { join } from 'node:path'; | ||
| import { execSync } from 'node:child_process'; | ||
|
|
||
| const chromeAvailable = (() => { | ||
| try { | ||
| if (process.platform === 'darwin') { | ||
| execSync('test -d "/Applications/Google Chrome.app"', { | ||
| stdio: 'ignore', | ||
| }); | ||
| } else if (process.platform === 'linux') { | ||
| execSync( | ||
| 'which google-chrome || which chromium-browser || which chromium', | ||
| { stdio: 'ignore' }, | ||
| ); | ||
| } else { | ||
| return false; | ||
| } | ||
kunal-10-cloud marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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( | ||
| import.meta.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( | ||
| import.meta.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( | ||
| import.meta.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( | ||
| import.meta.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 without timeout (indicates proper cleanup) | ||
| await new Promise((resolve) => setTimeout(resolve, 2000)); | ||
kunal-10-cloud marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| }); | ||
|
|
||
| it('should handle multiple browser operations in sequence', async () => { | ||
| rig.setup('browser-sequential', { | ||
| fakeResponsesPath: join( | ||
| import.meta.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); | ||
| }); | ||
| }); | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.