## Summary
Teach the `click` tool to handle accessibility snapshot targets that
point at an `option` inside a native, single-select `<select>` element.
When the target is a native select option, `click` now selects that
option through the owning `<select>` element instead of asking Puppeteer
to click an option node that has no clickable box while the dropdown is
collapsed. Other option-like targets, including custom ARIA options,
continue through the normal click path.
## Motivation
Fixes#1941.
The text snapshot can expose native `<option>` nodes with stable uids.
This makes it natural for an agent to call `click` on an option uid
after seeing the desired option in the snapshot.
However, a collapsed native `<select>` does not expose its child
`<option>` nodes as directly clickable page boxes. In that state, the
option can exist in the DOM and accessibility tree while still having no
clickable geometry. Puppeteer therefore waits for the option to become
interactive and eventually times out.
The existing `fill` tool already handles selecting native `<select>`
options. This change keeps that guidance explicit in the `click` tool
description, while also making `click(option_uid)` robust for the native
select case that appears in the snapshot.
## Changes
- Added a constrained native select fallback in `click`:
- Only runs for single-click requests.
- Only runs when the snapshot node role is `option`.
- Only handles real `HTMLOptionElement` nodes owned by a native
`<select>`.
- Does not handle disabled selects, disabled options, disabled
optgroups, or multi-selects.
- Falls back to the existing locator click behavior for all other
targets.
- Dispatches `input` and `change` events when selecting a different
native option.
- Updated the `click` tool description to steer agents toward `fill` for
native `<select>` option selection.
- Regenerated CLI/tool reference docs with `npm run gen`.
- Added regression coverage for:
- Clicking an option uid in a collapsed native `<select>`.
- Clicking an option uid inside a native `<optgroup>`.
- Clicking a custom ARIA `role="option"` element through the normal
click path.
## Test Plan
Passed:
```bash
npm run test -- tests/tools/input.test.ts
npm run check-format
```
Also attempted:
```bash
npm run test
```
The full suite hit unrelated local failures outside this change area:
- `tests/tools/network.test.ts`: snapshot ordering difference for
redirected requests.
- `tests/tools/screenshot.test.ts`: `Page.captureScreenshot` failed for
the large full-page screenshot case with `Page is too large`.
The changed input tool tests pass locally.
## Risk
Low. The fallback is intentionally narrow:
- It is gated by the accessibility role being `option`.
- It verifies the DOM node is an actual `HTMLOptionElement`.
- It only applies to native, non-disabled, single-select `<select>`
controls.
- It does not reinterpret custom combobox/listbox implementations.
- It preserves the existing locator click path for non-native option
targets.
The main behavior change is that `click(option_uid)` can now succeed for
native collapsed dropdowns that previously timed out.
## Related Issue
Closes#1941.
## Maintainer Context
This targets a small but high-impact mismatch between the snapshot
representation and browser interaction semantics.
The snapshot correctly exposes native options because they exist in the
accessibility tree. The click implementation previously treated that uid
like any other clickable element, but collapsed native options do not
have normal clickable layout boxes. This PR makes the native select case
explicit without expanding `click` into a general custom dropdown
heuristic.
The tool description still recommends `fill` for native `<select>`
selection so agents are guided toward the more direct tool. The code
fallback exists for the common case where an agent already selected an
option uid from the snapshot.
This PR changes the implementation to allow access to tmpdir even if the
client is not configured it explicitly because several tools default to
the tmpdir for outputs. Many clients like gemini-cli/claude do not
configure the tmp dir as a root by default.
This is the first part of adding tests for checking tool behavior when a
dialog is already open (related to #1069)
In future CL, I will be focusing on tools which currently get blocked
due to open dialogs. As part of those CLs, proactive rejection of tool
execution will be implemented.
This is a better measurement as if object self size is X and the
retainerSize Y. X will always be smaller then Y, there Y will be the
actual size on the Heap.
## Summary
Group consecutive identical console messages in `list_console_messages`,
similar to Chrome DevTools' console grouping behavior.
Fixes#904
## Changes
- Introduce `GroupedConsoleFormatter` subclass that extends
`ConsoleFormatter` and overrides `toString()` / `toJSON()` for
count-aware formatting
- Add `ConsoleFormatter.groupConsecutive()` static method that groups
consecutive messages with the same type, text, and argument count
- Apply grouping **before pagination** so grouped counts are accurate
and page sizes reflect the collapsed view
- Add unit tests for grouping logic, string formatting, and JSON output
## Key design decisions
- **`GroupedConsoleFormatter` subclass**: Keeps the existing formatter
interface clean — no new methods added to `ConsoleFormatter`.
`ConsoleFormatter` and `GroupedConsoleFormatter` are interchangeable via
the same interface.
- **Grouping before pagination** (not at format time): This was the
feedback on #963 and #1025 — grouping at format time breaks pagination
counts. This implementation groups in `McpResponse` before calling
`paginate()`.
- **No `lastId`**: Since grouped messages are truly identical, only the
first message's ID is needed.
- **`argCount` matching**: Prevents false grouping of messages with the
same text but different argument counts.
## Output example
```
msgid=1 [log] hello world (1 args) [5 times]
```
## Testing
- Unit tests in `tests/formatters/ConsoleFormatterGrouping.test.ts`
- Manual verification: identical messages (×5), mixed pattern (A,A,B,A,A
→ A×2, B×1, A×2)
Improvements for handling in-page tool responses. In order to
successfully pass an in-page tool response from the page context to the
MCP server, the response needs to be serializable. The code walks the
response object an performs the following changes:
- DOM elements are stashed onto the window object and replaced with an
ID. On the MCP server side this ID is used to map back to the
corresponding UID in the page snapshot generated from the accessibility
tree.
- Circular references are replaced with a string.
- Class instances (which can be complex or non-serializable) are
replaced with a string.
- Functions are replaced with a string.
If the in-page tool response contains DOM elements which are not part of
the page snapshot, a new snapshot is created add the missing elements
are added explicitly.
This PR adds an experimental allowlist for the page navigate tool call
(requires `--experimentalNavigationAllowlist`, off by default). The
feature uses a list of URLPatterns to decline navigations that land on
disallowed URLs. If that happens, the client can update the allowlist
and re-try. The purpose of the this feature is to offer additional
guardrails on top of the MCP server. It does not restrict subresources
or JS/iframe navigations in any way. The performance impact is minimized
by turning off interception as soon as the navigation request is done.
This refactors the code to extract the Id logic from the PageCollector
and provide it in the heapsnapshot.
We need to use an UID as we need the internal ClassKey to query the
heapsnapshot, but that is a strange string (usually looking like
`,ClassName`) which may get the LLM confused as we use comma separated
output.
## Summary
- fix the WebP MIME type match in daemon response handling
- save responses with a suffix instead of falling back to
- add a regression test for WebP image handling
## Testing
- npm run build
- node --test --test-name-pattern="parsing"
build/tests/daemon/client.test.js
Fixes#1898
Provide a argument to LLM to handle dialogs that come up during code
execution.
---------
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
This is the second most highly touched metric areas - so let's automate
this portion first. The approach is similar to what we have for tool
call args. The append-only logic will be added in the follow-up PR:
#1882
## Summary
- ignore Audits `PerformanceIssue` events before passing them to the
DevTools IssuesManager mapper, which currently has no handler for that
issue code
- preserve the existing mapper/logging path for other issue codes
- add regression coverage that `PerformanceIssue` is ignored without
collecting an issue or writing a console warning
Fixes#1850
## Testing
- `npm run build`
- `npm run test:no-build -- tests/PageCollector.test.ts`
- `npm run check-format`
Signed-off-by: Asish Kumar <officialasishkumar@gmail.com>
Previously, we would show the notification if the local version string
was *different* from the latest version string published to npm.
With this patch, we actually check if the npm-published version is newer
and avoid showing the notification otherwise.
Closes#1886
This fixes the casing of the tool call params. We don't need any server
side fix since they are already converted to snake case in the proto
definition. This is needed nevertheless since the sanitizeParams()
function will be called when we log the params (see the next PR: #1863
1863).
This PR adds a CLI flag to enable redacting network headers in the same
way they are redacted in DevTools. Note that sometimes it might prevent
the agent from properly analysing network issues. Pass
`--redact-headers=false` to revert to the previous behavior.
Both `chrome-devtools` and `chrome-devtools-mcp` now log a notification
when a newer version is detected to be available.
This detection is implemented as follows:
1. Read the latest version from a local 24-hour cache
(`~/.cache/chrome-devtools-mcp/latest.json`).
2. If the cache is stale or missing, spawn a detached background process
to fetch the latest version from the npm registry and update the cache
file.
DOM elements are non-serializable and therefore cannot be directly sent
between the inspected page and the MCP server. JSONSchema also has no
native type for DOM elements.
If an in-page tool expects a DOM element as an input parameter, it
should specify this in its input schema by adding `'x-mcp-type':
'HTMLElement'` to the object it expects to be a DOM element.
The MCP server internally refers to DOM elements by a UID (UIDs are
assigned when generating a page snapshot which is based on the page's
accessibility tree).
This change provides the mapping between DOM element and UID in both
directions:
1) The tool's input schema is rewritten internally, adding a required
UID attribute to objects with `'x-mcp-type': 'HTMLElement'`. This allows
the MCP server to call the in-page tool with UIDs where the tool expects
DOM elements.
2) In the page context, the UIDs are replaced with the corresponding DOM
elements, before the actual in-page tool is called. This means that the
in-page tool receives DOM elements as parameters where it expects them.
With this change new tools are added to the very back of the json file.
Any removed tools will receive a isDeprecated flag in the existing
entry. And the same with tool arguments.
This adds a script that generates a json file that summarizes all tool
calls and arguments for each.
- The arguments run through the blocklist filtering so arguments
containing high entropy ids are filtered out (e.g. "uid", "msgid" etc).
- It uses existing functions from clearcut logger module to transform
the arg name and value (i.e. take the length of the string, take the
size of the array, and rename the string to be "string_length", and
array "array_count" etc).
- These functions from the clearcut logger module will be later used to
sanitize the params as we start to log them.
This doesn't include the append only / deprecation logic just yet (i.e.
it's doesn't handle the case when new tools are added / removed, or
arguments of the existing tools are modified). This will be added in
following PRs.
The parent PR is #1250.
This moves the stored `ToolGroup` from `McpContext` to `McpPage`, where
per-page state should be stored in order for pageId-based routing to
work correctly.
When appending the list of in-page tools to a response, the list now
corresponds to the response's `McpPage`, and only falls back to the
selected `McpPage` for tools which are not page-specific.
---------
Co-authored-by: browser-automation-bot <133232582+browser-automation-bot@users.noreply.github.com>
Co-authored-by: Alex Rudenko <alexrudenko@chromium.org>
Co-authored-by: Tolgahan Demirbaş <49946947+bcfmtolgahan@users.noreply.github.com>
Splits out from #1244 per review feedback.
'waitForEventsAfterAction' previously lived in 'McpContext' and always
used the selected page's CPU/network throttling settings. With pageId
routing, a tool can target a different page than the selected one,
meaning wrong throttling multipliers were applied.
Moving the method to 'McpPage' fixes this: each tool now calls
'page.waitForEventsAfterAction(...)' and gets the correct page's
emulation settings.
'getNetworkMultiplierFromString' is extracted to 'WaitForHelper.ts' to
avoid a circular import (McpContext → McpPage already exists).
Unblocks #1777.
## Summary
Fix `chrome-devtools start` so it no longer implicitly enables
`isolated` when `--userDataDir` is provided.
Previously, the CLI wrapper always defaulted `isolated` to `true` for
`start`, which caused `userDataDir` and `isolated` to conflict even when
the user only specified `--userDataDir`. This made it impossible to
start the CLI daemon against a persistent browser profile.
## Changes
- Update `chrome-devtools start` default handling in
`src/bin/chrome-devtools.ts`
- only default `isolated=true` when `userDataDir` is not set
- Clarify the `isolated` CLI description to document the conditional
default
- Update `docs/cli.md` to reflect that:
- `headless` is enabled by default
- `isolated` is enabled by default unless `--userDataDir` is provided
- Fix a small error message typo
## Why
This matches the intended semantics of the flags:
- `--isolated` means use a temporary user data dir
- `--userDataDir` means use a persistent, explicit user data dir
If the user passes `--userDataDir`, the CLI should not also implicitly
enable `isolated`.
## Testing
- Ran:
- `npm test -- tests/cli.test.ts`
- `npm test -- tests/e2e/chrome-devtools.test.ts`
Added an e2e regression test in `tests/e2e/chrome-devtools.test.ts` to
verify that:
- `chrome-devtools start --userDataDir <temp dir>` succeeds
- the CLI no longer fails with `Arguments userDataDir and isolated are
mutually exclusive`
- the daemon starts successfully when `userDataDir` is provided
This allows the MCP server to call the in-page tools provided by the
inspected page.
Handling of (non-serializable) DOM elements as tool parameters or tool
output will be added in follow-ups.
This adds functions to sanitize the tool call parameters. They are not
called as of now since we don't have server side changes landed yet to
support these.