项目文件夹

文件
dependabot[bot] 20021469dc chore(deps-dev): bump puppeteer from 24.43.0 to 25.0.2 in the bundled group across 1 directory (#2045)
Bumps the bundled group with 1 update in the / directory:
[puppeteer](https://github.com/puppeteer/puppeteer).

Updates `puppeteer` from 24.43.0 to 25.0.2
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/puppeteer/puppeteer/releases">puppeteer's
releases</a>.</em></p>
<blockquote>
<h2>puppeteer-core: v25.0.2</h2>
<h2><a
href="https://github.com/puppeteer/puppeteer/compare/puppeteer-core-v25.0.1...puppeteer-core-v25.0.2">25.0.2</a>
(2026-05-15)</h2>
<h3>🛠️ Fixes</h3>
<ul>
<li>update docs text (<a
href="https://redirect.github.com/puppeteer/puppeteer/issues/14992">#14992</a>)
(<a
href="https://github.com/puppeteer/puppeteer/commit/36527b8b9dd856e11a372b46cba23409e5613573">36527b8</a>)</li>
</ul>
<h3>Dependencies</h3>
<ul>
<li>The following workspace dependencies were updated
<ul>
<li>dependencies
<ul>
<li><code>@​puppeteer/browsers</code> bumped from 3.0.1 to 3.0.2</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2>puppeteer: v25.0.2</h2>
<h2><a
href="https://github.com/puppeteer/puppeteer/compare/puppeteer-v25.0.1...puppeteer-v25.0.2">25.0.2</a>
(2026-05-15)</h2>
<h3>♻️ Chores</h3>
<ul>
<li><strong>puppeteer:</strong> Synchronize puppeteer versions</li>
</ul>
<h3>Dependencies</h3>
<ul>
<li>The following workspace dependencies were updated
<ul>
<li>dependencies
<ul>
<li><code>@​puppeteer/browsers</code> bumped from 3.0.1 to 3.0.2</li>
<li>puppeteer-core bumped from 25.0.1 to 25.0.2</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2>puppeteer-core: v25.0.1</h2>
<h2><a
href="https://github.com/puppeteer/puppeteer/compare/puppeteer-core-v25.0.0...puppeteer-core-v25.0.1">25.0.1</a>
(2026-05-13)</h2>
<h3>🛠️ Fixes</h3>
<ul>
<li>enabled features should take precedence over disabled features (<a
href="https://redirect.github.com/puppeteer/puppeteer/issues/14985">#14985</a>)
(<a
href="https://github.com/puppeteer/puppeteer/commit/f6fd7c29f92e6183b2fd48f0571957306c58d440">f6fd7c2</a>)</li>
<li>roll to Chrome 148.0.7778.167 (<a
href="https://redirect.github.com/puppeteer/puppeteer/issues/14980">#14980</a>)
(<a
href="https://github.com/puppeteer/puppeteer/commit/84c46fede6b02a1e9a00104b97b28c263f0c84df">84c46fe</a>)</li>
<li>roll to Firefox 150.0.3 (<a
href="https://redirect.github.com/puppeteer/puppeteer/issues/14983">#14983</a>)
(<a
href="https://github.com/puppeteer/puppeteer/commit/872f77822d6b6b9b6510752d838107a967241bdb">872f778</a>)</li>
</ul>
<h3>Dependencies</h3>
<ul>
<li>The following workspace dependencies were updated
<ul>
<li>dependencies
<ul>
<li><code>@​puppeteer/browsers</code> bumped from 3.0.0 to 3.0.1</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2>puppeteer: v25.0.1</h2>
<h2><a
href="https://github.com/puppeteer/puppeteer/compare/puppeteer-v25.0.0...puppeteer-v25.0.1">25.0.1</a>
(2026-05-13)</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/puppeteer/puppeteer/blob/main/CHANGELOG.md">puppeteer's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/puppeteer/puppeteer/compare/puppeteer-v25.0.1...puppeteer-v25.0.2">25.0.2</a>
(2026-05-15)</h2>
<h3>♻️ Chores</h3>
<ul>
<li><strong>puppeteer:</strong> Synchronize puppeteer versions</li>
</ul>
<h3>Dependencies</h3>
<ul>
<li>The following workspace dependencies were updated
<ul>
<li>dependencies
<ul>
<li><code>@​puppeteer/browsers</code> bumped from 3.0.1 to 3.0.2</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3>🛠️ Fixes</h3>
<ul>
<li>update docs text (<a
href="https://redirect.github.com/puppeteer/puppeteer/issues/14992">#14992</a>)
(<a
href="https://github.com/puppeteer/puppeteer/commit/36527b8b9dd856e11a372b46cba23409e5613573">36527b8</a>)</li>
</ul>
<h2><a
href="https://github.com/puppeteer/puppeteer/compare/puppeteer-v25.0.0...puppeteer-v25.0.1">25.0.1</a>
(2026-05-13)</h2>
<h3>♻️ Chores</h3>
<ul>
<li><strong>puppeteer:</strong> Synchronize puppeteer versions</li>
</ul>
<h3>Dependencies</h3>
<ul>
<li>The following workspace dependencies were updated
<ul>
<li>dependencies
<ul>
<li><code>@​puppeteer/browsers</code> bumped from 3.0.0 to 3.0.1</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3>🛠️ Fixes</h3>
<ul>
<li>enabled features should take precedence over disabled features (<a
href="https://redirect.github.com/puppeteer/puppeteer/issues/14985">#14985</a>)
(<a
href="https://github.com/puppeteer/puppeteer/commit/f6fd7c29f92e6183b2fd48f0571957306c58d440">f6fd7c2</a>)</li>
<li>roll to Chrome 148.0.7778.167 (<a
href="https://redirect.github.com/puppeteer/puppeteer/issues/14980">#14980</a>)
(<a
href="https://github.com/puppeteer/puppeteer/commit/84c46fede6b02a1e9a00104b97b28c263f0c84df">84c46fe</a>)</li>
<li>roll to Firefox 150.0.3 (<a
href="https://redirect.github.com/puppeteer/puppeteer/issues/14983">#14983</a>)
(<a
href="https://github.com/puppeteer/puppeteer/commit/872f77822d6b6b9b6510752d838107a967241bdb">872f778</a>)</li>
</ul>
<h2><a
href="https://github.com/puppeteer/puppeteer/compare/puppeteer-v24.43.1...puppeteer-v25.0.0">25.0.0</a>
(2026-05-12)</h2>
<h3>⚠ BREAKING CHANGES</h3>
<ul>
<li>remove deprecated Puppeteer.product (<a
href="https://redirect.github.com/puppeteer/puppeteer/issues/14977">#14977</a>)</li>
<li>bump min NodeJS to 22 (<a
href="https://redirect.github.com/puppeteer/puppeteer/issues/14973">#14973</a>)</li>
<li>return a Promise for executablePath, defaultArgs (<a
href="https://redirect.github.com/puppeteer/puppeteer/issues/14965">#14965</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/puppeteer/puppeteer/commit/3aadc38c533caa8df87f381868291ede870883e2"><code>3aadc38</code></a>
chore: release main (<a
href="https://redirect.github.com/puppeteer/puppeteer/issues/14993">#14993</a>)</li>
<li><a
href="https://github.com/puppeteer/puppeteer/commit/36527b8b9dd856e11a372b46cba23409e5613573"><code>36527b8</code></a>
fix: update docs text (<a
href="https://redirect.github.com/puppeteer/puppeteer/issues/14992">#14992</a>)</li>
<li><a
href="https://github.com/puppeteer/puppeteer/commit/3ea7bd54b97965fb0e1b83a2c017720162785c92"><code>3ea7bd5</code></a>
fix: capitalize &quot;Chrome&quot; in troubleshooting.md (<a
href="https://redirect.github.com/puppeteer/puppeteer/issues/14991">#14991</a>)</li>
<li><a
href="https://github.com/puppeteer/puppeteer/commit/ae72fdbec9720c982fc2ed818036d7be9b2aa1dc"><code>ae72fdb</code></a>
chore: Tiny punctuation update in troubleshooting.md (<a
href="https://redirect.github.com/puppeteer/puppeteer/issues/14990">#14990</a>)</li>
<li><a
href="https://github.com/puppeteer/puppeteer/commit/1597fde55de1e7b3c31032aa43dce0607b4307d3"><code>1597fde</code></a>
chore: release main (<a
href="https://redirect.github.com/puppeteer/puppeteer/issues/14982">#14982</a>)</li>
<li><a
href="https://github.com/puppeteer/puppeteer/commit/f6fd7c29f92e6183b2fd48f0571957306c58d440"><code>f6fd7c2</code></a>
fix: enabled features should take precedence over disabled features (<a
href="https://redirect.github.com/puppeteer/puppeteer/issues/14985">#14985</a>)</li>
<li><a
href="https://github.com/puppeteer/puppeteer/commit/bf05fb9bf5bac997e365eda2e31c6067e5ec1677"><code>bf05fb9</code></a>
fix: update browsers to trigger release (<a
href="https://redirect.github.com/puppeteer/puppeteer/issues/14984">#14984</a>)</li>
<li><a
href="https://github.com/puppeteer/puppeteer/commit/872f77822d6b6b9b6510752d838107a967241bdb"><code>872f778</code></a>
fix: roll to Firefox 150.0.3 (<a
href="https://redirect.github.com/puppeteer/puppeteer/issues/14983">#14983</a>)</li>
<li><a
href="https://github.com/puppeteer/puppeteer/commit/84c46fede6b02a1e9a00104b97b28c263f0c84df"><code>84c46fe</code></a>
fix: roll to Chrome 148.0.7778.167 (<a
href="https://redirect.github.com/puppeteer/puppeteer/issues/14980">#14980</a>)</li>
<li><a
href="https://github.com/puppeteer/puppeteer/commit/ee655d8292f37cc33f279746ef72613e5ccb5db8"><code>ee655d8</code></a>
chore(main): release ng-schematics 0.8.0 (<a
href="https://redirect.github.com/puppeteer/puppeteer/issues/14978">#14978</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/puppeteer/puppeteer/compare/puppeteer-v24.43.0...puppeteer-v25.0.2">compare
view</a></li>
</ul>
</details>
<br />

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alex Rudenko <alexrudenko@chromium.org>
2026-05-17 06:34:22 +00:00

373 行
10 KiB
TypeScript

/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import assert from 'node:assert';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import {describe, it} from 'node:test';
import {Client} from '@modelcontextprotocol/sdk/client/index.js';
import {StdioClientTransport} from '@modelcontextprotocol/sdk/client/stdio.js';
import {
ListRootsRequestSchema,
RootsListChangedNotificationSchema,
type ClientCapabilities,
type TextContent,
} from '@modelcontextprotocol/sdk/types.js';
import {executablePath} from 'puppeteer';
import type {ToolCategory} from '../src/tools/categories.js';
import {OFF_BY_DEFAULT_CATEGORIES} from '../src/tools/categories.js';
import type {ToolDefinition} from '../src/tools/ToolDefinition.js';
describe('e2e', () => {
async function withClient(
cb: (client: Client) => Promise<void>,
extraArgs: string[] = [],
options: {capabilities?: ClientCapabilities} = {},
) {
const transport = new StdioClientTransport({
command: 'node',
args: [
'build/src/bin/chrome-devtools-mcp.js',
'--headless',
'--isolated',
'--executable-path',
await executablePath(),
...extraArgs,
],
env: {...process.env, CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS: 'true'},
});
const client = new Client(
{
name: 'e2e-test',
version: '1.0.0',
},
{
capabilities: options.capabilities ?? {},
},
);
try {
await client.connect(transport);
await cb(client);
} finally {
await client.close();
}
}
it('calls a tool', async t => {
await withClient(async client => {
const result = await client.callTool({
name: 'list_pages',
arguments: {},
});
t.assert.snapshot?.(JSON.stringify(result.content));
});
});
it('calls a tool multiple times', async t => {
await withClient(async client => {
let result = await client.callTool({
name: 'list_pages',
arguments: {},
});
result = await client.callTool({
name: 'list_pages',
arguments: {},
});
t.assert.snapshot?.(JSON.stringify(result.content));
});
});
it('has all tools with off by default categories', async () => {
await withClient(
async client => {
const {tools} = await client.listTools();
const exposedNames = tools.map(t => t.name).sort();
const definedNames = await getToolsWithFilteredCategories();
definedNames.sort();
assert.deepStrictEqual(exposedNames, definedNames);
},
OFF_BY_DEFAULT_CATEGORIES.map(category => `--category-${category}`),
);
});
it('has all tools', async () => {
await withClient(async client => {
const {tools} = await client.listTools();
const exposedNames = tools.map(t => t.name).sort();
const definedNames = await getToolsWithFilteredCategories(
OFF_BY_DEFAULT_CATEGORIES,
);
definedNames.sort();
assert.deepStrictEqual(exposedNames, definedNames);
});
});
it('has experimental third-party developer tools', async () => {
await withClient(
async client => {
const {tools} = await client.listTools();
const listThirdPartyDeveloperTools = tools.find(
t => t.name === 'list_3p_developer_tools',
);
assert.ok(listThirdPartyDeveloperTools);
},
['--category-experimental-third-party'],
);
});
it('has experimental extensions tools', async () => {
await withClient(
async client => {
const {tools} = await client.listTools();
const installExtension = tools.find(
t => t.name === 'install_extension',
);
assert.ok(installExtension);
},
['--category-extensions'],
);
});
it('has experimental vision tools', async () => {
await withClient(
async client => {
const {tools} = await client.listTools();
const clickAt = tools.find(t => t.name === 'click_at');
assert.ok(clickAt);
},
['--experimental-vision'],
);
});
it('has experimental interop tools', async () => {
await withClient(
async client => {
const {tools} = await client.listTools();
const getTabId = tools.find(t => t.name === 'get_tab_id');
assert.ok(getTabId);
},
['--experimental-interop-tools'],
);
});
it('has experimental webmcp', async () => {
await withClient(
async client => {
const {tools} = await client.listTools();
const listWebMcpTools = tools.find(t => t.name === 'list_webmcp_tools');
const executeWebMcpTool = tools.find(
t => t.name === 'execute_webmcp_tool',
);
assert.ok(listWebMcpTools);
assert.ok(executeWebMcpTool);
},
['--categoryExperimentalWebmcp'],
);
});
it('updates roots when client notifies', async () => {
const roots = [{uri: 'file:///test-root', name: 'test-root'}];
let resolvePromise: () => void;
const promise = new Promise<void>(resolve => {
resolvePromise = resolve;
});
await withClient(
async client => {
client.setRequestHandler(ListRootsRequestSchema, () => {
resolvePromise();
return {roots};
});
await client.notification({
method: RootsListChangedNotificationSchema.shape.method.value,
});
// Wait for the server to process the notification and request roots
await promise;
},
[],
{
capabilities: {
roots: {listChanged: true},
},
},
);
});
it('denies file access if roots list is empty', async () => {
await withClient(
async client => {
client.setRequestHandler(ListRootsRequestSchema, () => {
return {roots: []};
});
const result = await client.callTool({
name: 'take_screenshot',
arguments: {
filePath: path.resolve(os.homedir(), 'test.png'),
},
});
assert.strictEqual(result.isError, true);
const content = result.content as TextContent[];
assert.match(content[0].text, /Access denied/);
},
[],
{
capabilities: {
roots: {listChanged: true},
},
},
);
});
it('allows file access if roots capability is missing', async () => {
await withClient(
async client => {
const result = await client.callTool({
name: 'take_screenshot',
arguments: {
filePath: '/tmp/test.png',
},
});
assert.strictEqual(result.isError, undefined);
const content = result.content as TextContent[];
assert.match(content[0].text, /Saved screenshot to/);
},
[],
{
capabilities: {},
},
);
});
describe('Dialogs', () => {
async function createNewPageAndTriggerDialog(client: Client) {
// Navigate to a page with a button that triggers a dialog on click
await client.callTool({
name: 'new_page',
arguments: {
url: `data:text/html,<button id="test" onclick="alert('test dialog')">Click me</button>`,
},
});
const snapshotResult = await client.callTool({
name: 'take_snapshot',
arguments: {},
});
const snapshotText = (snapshotResult.content as TextContent[])[0].text;
const match = snapshotText.match(/uid=(\d+_\d+)\s+button "Click me"/);
const uid = match ? match[1] : '1_1';
// Trigger the dialog
const result = await client.callTool({
name: 'click',
arguments: {
uid,
},
});
return result;
}
it('returns blocked message when dialog is opened during tool execution', async t => {
await withClient(async client => {
const result = await createNewPageAndTriggerDialog(client);
t.assert.snapshot?.(JSON.stringify(result));
});
});
it('when dialog is open and tool is blocked, returns an error', async t => {
await withClient(async client => {
await createNewPageAndTriggerDialog(client);
const result = await client.callTool({
name: 'take_screenshot',
arguments: {
filePath: '/tmp/test.png',
},
});
t.assert.snapshot?.(JSON.stringify(result));
});
});
it('when dialog is open and tool is not blocked, executes tool', async t => {
await withClient(async client => {
await createNewPageAndTriggerDialog(client);
const result = await client.callTool({
name: 'new_page',
arguments: {
url: `data:text/html,<h1>New</h1>`,
},
});
t.assert.snapshot?.(JSON.stringify(result));
});
});
});
});
async function getToolsWithFilteredCategories(
filterOutCategories: ToolCategory[] = [],
): Promise<string[]> {
const files = fs.readdirSync('build/src/tools');
const definedNames = [];
for (const file of files) {
if (
!file.endsWith('.js') ||
file === 'ToolDefinition.js' ||
file === 'tools.js' ||
file === 'slim'
) {
continue;
}
const fileTools = await import(`../src/tools/${file}`);
for (const maybeTool of Object.values<unknown>(fileTools)) {
let tool;
if (typeof maybeTool === 'function') {
tool = (maybeTool as (val: boolean) => ToolDefinition)(false);
} else {
tool = maybeTool as ToolDefinition;
}
// Skipping all files that are not tool files
if (tool === null || typeof tool !== 'object' || !('name' in tool)) {
continue;
}
if (toolShouldBeSkipped(tool, filterOutCategories)) {
continue;
}
definedNames.push(tool.name);
}
}
return definedNames;
}
function toolShouldBeSkipped(
tool: ToolDefinition,
filteredOutCategories: ToolCategory[],
) {
if (tool.annotations?.conditions) {
return true;
}
if (
tool.annotations?.category &&
filteredOutCategories.includes(tool.annotations?.category)
) {
return true;
}
return false;
}