feat(agent): replace the runtime npx for browser agent chrome devtool mcp with pre-built bundle (#22213)

Co-authored-by: Gaurav Ghosh <gaghosh@google.com>
Co-authored-by: Gaurav <39389231+gsquared94@users.noreply.github.com>
This commit is contained in:
cynthialong0-0
2026-03-16 01:05:38 -07:00
committed by GitHub
parent b9e25b5adc
commit 3520f33a80
12 changed files with 763 additions and 252 deletions
@@ -39,6 +39,7 @@ vi.mock('@modelcontextprotocol/sdk/client/stdio.js', () => ({
vi.mock('../../utils/debugLogger.js', () => ({
debugLogger: {
log: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
},
}));
@@ -47,6 +48,20 @@ vi.mock('./automationOverlay.js', () => ({
injectAutomationOverlay: vi.fn().mockResolvedValue(undefined),
}));
vi.mock('node:fs', async (importOriginal) => {
const actual = await importOriginal<typeof import('node:fs')>();
return {
...actual,
existsSync: vi.fn((p: string) => {
if (p.endsWith('bundled/chrome-devtools-mcp.mjs')) {
return false; // Default
}
return actual.existsSync(p);
}),
};
});
import * as fs from 'node:fs';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
@@ -96,6 +111,40 @@ describe('BrowserManager', () => {
vi.restoreAllMocks();
});
describe('MCP bundled path resolution', () => {
it('should use bundled path if it exists (handles bundled CLI)', async () => {
vi.mocked(fs.existsSync).mockReturnValue(true);
const manager = new BrowserManager(mockConfig);
await manager.ensureConnection();
expect(StdioClientTransport).toHaveBeenCalledWith(
expect.objectContaining({
command: 'node',
args: expect.arrayContaining([
expect.stringMatching(/bundled\/chrome-devtools-mcp\.mjs$/),
]),
}),
);
});
it('should fall back to development path if bundled path does not exist', async () => {
vi.mocked(fs.existsSync).mockReturnValue(false);
const manager = new BrowserManager(mockConfig);
await manager.ensureConnection();
expect(StdioClientTransport).toHaveBeenCalledWith(
expect.objectContaining({
command: 'node',
args: expect.arrayContaining([
expect.stringMatching(
/(dist\/)?bundled\/chrome-devtools-mcp\.mjs$/,
),
]),
}),
);
});
});
describe('getRawMcpClient', () => {
it('should ensure connection and return raw MCP client', async () => {
const manager = new BrowserManager(mockConfig);
@@ -222,10 +271,9 @@ describe('BrowserManager', () => {
// Verify StdioClientTransport was created with correct args
expect(StdioClientTransport).toHaveBeenCalledWith(
expect.objectContaining({
command: process.platform === 'win32' ? 'npx.cmd' : 'npx',
command: 'node',
args: expect.arrayContaining([
'-y',
expect.stringMatching(/chrome-devtools-mcp@/),
expect.stringMatching(/chrome-devtools-mcp\.mjs$/),
'--experimental-vision',
]),
}),
@@ -235,6 +283,7 @@ describe('BrowserManager', () => {
?.args as string[];
expect(args).not.toContain('--isolated');
expect(args).not.toContain('--autoConnect');
expect(args).not.toContain('-y');
// Persistent mode should set the default --userDataDir under ~/.gemini
expect(args).toContain('--userDataDir');
const userDataDirIndex = args.indexOf('--userDataDir');
@@ -294,7 +343,7 @@ describe('BrowserManager', () => {
expect(StdioClientTransport).toHaveBeenCalledWith(
expect.objectContaining({
command: process.platform === 'win32' ? 'npx.cmd' : 'npx',
command: 'node',
args: expect.arrayContaining(['--headless']),
}),
);
@@ -319,7 +368,7 @@ describe('BrowserManager', () => {
expect(StdioClientTransport).toHaveBeenCalledWith(
expect.objectContaining({
command: process.platform === 'win32' ? 'npx.cmd' : 'npx',
command: 'node',
args: expect.arrayContaining(['--userDataDir', '/path/to/profile']),
}),
);