Help us shape the future of Go! Take the 2026 Go Developer Survey

devtools

package
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 8, 2026 License: MIT Imports: 27 Imported by: 0

Documentation

Overview

Package devtools drives the Web UI through Chrome DevTools MCP: it starts the server, calls its tools, and reads what they print.

Index

Constants

View Source
const DefaultViewport = "1280x800x1"

DefaultViewport is the window the driver works in unless told otherwise.

View Source
const SupportedContract = "1"

SupportedContract is the Web UI contract version this driver was written for (internal/contract). A page that says another version is refused. A test keeps it equal to the contract package's, so that raising one makes you look at the other.

Variables

View Source
var ErrNoCLS = errors.New("the performance trace reported no layout shift")

ErrNoCLS is returned when the trace summary holds no layout shift figure.

View Source
var ErrNoLighthouseScores = errors.New("the Lighthouse audit printed no category scores")

ErrNoLighthouseScores is returned when the audit's output holds no scores.

View Source
var ErrNoScriptResult = errors.New("evaluate_script printed no result")

ErrNoScriptResult is returned when evaluate_script's output holds no result.

View Source
var ErrNoStopButton = errors.New("there is no Stop button on the page")

ErrNoStopButton is returned by Stop when the page shows no Stop button: the reply has finished, or the page does not offer one.

Functions

func BuildCommand

func BuildCommand(ctx context.Context, command, args, environ []string) (*exec.Cmd, error)

BuildCommand returns the process that runs Chrome DevTools MCP, or says why it must not be run: the version is not pinned, or one of the flags that keep it from sending usage statistics and from touching a real browser profile is missing (see config.CheckMCP).

The process gets a trimmed environment (see ChildEnv), and it is killed, along with everything it started, when ctx ends.

Pattern: Pinned, Trimmed, Isolated Child

func ChildEnv

func ChildEnv(environ []string) []string

ChildEnv keeps, from an environment in the form os.Environ gives, what Chrome DevTools MCP needs. The server is a package fetched from a registry and it drives a browser; it has no use for the Hugging Face token, the Web UI password or the database settings the evaluator's own environment holds, so it does not get them. A name that looks like a secret is dropped even when it would otherwise pass.

func ParseCLS

func ParseCLS(text string) (float64, error)

ParseCLS reads the lab cumulative layout shift from the summary of a stopped performance trace.

func ParseLighthouse

func ParseLighthouse(text string) (map[string]float64, error)

ParseLighthouse reads the category scores of a Lighthouse audit, by category id ("accessibility", "best-practices", "seo"...).

func ParseScriptResult

func ParseScriptResult(text string) ([]byte, error)

ParseScriptResult returns the JSON an evaluated function returned, which the tool prints between a "```json" line and a closing "```". The closing fence is the last one in the text, because the result may hold fences of its own: an answer with a code block in it, for one. A function that returns nothing prints an empty block, and the result is "null".

Types

type CallOption

type CallOption func(*callConfig)

CallOption changes one call.

func WithTimeout

func WithTimeout(d time.Duration) CallOption

WithTimeout gives one call more (or less) time than the session's default, for tools that are slow by nature, such as a Lighthouse audit.

type ConsoleMessage

type ConsoleMessage struct {
	ID   int
	Type string // log, info, warn, error, issue, ...
	Text string
}

ConsoleMessage is one line of list_console_messages.

func ParseConsole

func ParseConsole(text string) []ConsoleMessage

ParseConsole reads list_console_messages. The arguments count the tool adds ("(1 args)") and the repeat count of an issue ("(count: 2)") are left in the text, which is only ever shown, never compared.

type Driver

type Driver struct {
	// contains filtered or unexported fields
}

Driver uses the Web UI the way a person does, through Chrome DevTools MCP: it finds elements by their accessible names in the page's accessibility tree, clicks and types, and reads the page's state through the data attributes of the contract. One driver is one browser tab and is not safe for concurrent use.

func NewDriver

func NewDriver(s *Session, cfg DriverConfig) *Driver

NewDriver wraps a session. The tab is opened by Login.

func (*Driver) AwaitReply

func (d *Driver) AwaitReply(ctx context.Context) (domain.Reply, error)

AwaitReply waits until the assistant message has a final status. When ctx ends first, it returns what it knows and an error wrapping domain.ErrReplyTimeout.

func (*Driver) Click

func (d *Driver) Click(ctx context.Context, q Query) error

Click clicks the element a query finds in a fresh snapshot.

func (*Driver) Close

func (d *Driver) Close(ctx context.Context) error

Close ends the browser session.

func (*Driver) Collect

func (d *Driver) Collect(ctx context.Context) (domain.UIObservations, error)

Collect returns what the browser observed since the page was opened: the observer's record of the reply, the console, and the requests.

func (*Driver) Conversation

func (d *Driver) Conversation() string

Conversation is the id of the conversation in the tab, once it has one.

func (*Driver) DeleteChat

func (d *Driver) DeleteChat(ctx context.Context) error

DeleteChat deletes the conversation in the tab through the sidebar: the delete button of its row, then Delete in the confirmation.

func (*Driver) Eval

func (d *Driver) Eval(ctx context.Context, function string, out any) error

Eval runs a function in the page and decodes the JSON it returns into out. It is for the probes and the tests, which do things to the page that an attempt never does (scroll away, resize, reload).

func (*Driver) ExpectNewReply

func (d *Driver) ExpectNewReply(ctx context.Context)

ExpectNewReply notes the newest reply on the page, so that AwaitReply waits for the one that the next step brings (Regenerate, Retry), not for this one.

func (*Driver) Fill

func (d *Driver) Fill(ctx context.Context, q Query, value string) error

Fill puts text in the element a query finds.

func (*Driver) Login

func (d *Driver) Login(ctx context.Context, email, password string) error

Login opens a tab on the sign-in page, checks the page speaks the contract this driver knows, and signs in.

func (*Driver) PressRowButton

func (d *Driver) PressRowButton(ctx context.Context, action RowAction, conversation string) error

PressRowButton presses the rename or delete button of a conversation's row in the sidebar, however many other rows there are and whatever their titles. Every question of an evaluation opens with the same prompt, so every row has the same title and every button the same name; the page says how many come first, and that many are passed over. Pressing the first button with the name would delete another conversation, possibly one that is still being written.

func (*Driver) Progress

func (d *Driver) Progress(ctx context.Context) (status domain.ReplyStatus, chars int, found bool, err error)

Progress says how far the newest reply has got: its status and how many characters of text it shows. found is false when the page has no reply yet. A reply that is there tells which conversation it is in, so that a probe that goes no further than this can still tidy up after itself.

func (*Driver) ReadRaw

func (d *Driver) ReadRaw(ctx context.Context, reply domain.Reply) (string, error)

ReadRaw returns the reply as it is stored, the Markdown the agent wrote, from the endpoint behind the Copy button. It is the one place the evaluator asks the Web UI for data without going through what a person sees, and it does so from inside the page, with the page's own session.

func (*Driver) Reload

func (d *Driver) Reload(ctx context.Context) error

Reload loads the page again, with the observer in it, and waits for the page to show its composer.

func (*Driver) Screenshot

func (d *Driver) Screenshot(ctx context.Context, path string) error

Screenshot saves the page as a PNG at path.

func (*Driver) StartChat

func (d *Driver) StartChat(ctx context.Context) error

StartChat opens the start page, where a message starts a new conversation.

func (*Driver) Stop

func (d *Driver) Stop(ctx context.Context) error

Stop presses the Stop button.

func (*Driver) Submit

func (d *Driver) Submit(ctx context.Context, text string) error

Submit puts the text in the composer and presses Enter.

func (*Driver) Tool

func (d *Driver) Tool(ctx context.Context, name string, args map[string]any, opts ...CallOption) (Result, error)

Tool calls a Chrome DevTools MCP tool on the driver's own tab.

func (*Driver) Viewport

func (d *Driver) Viewport(ctx context.Context, spec string) error

Viewport sets the size of the window as the emulate tool spells it, such as "375x667x2,mobile,touch" or "1280x800x1".

type DriverConfig

type DriverConfig struct {
	// BaseURL is the Web UI, such as http://localhost:8300. It can carry
	// credentials: the browser is given it as it is, and an error names it as
	// redact.URL shows it.
	BaseURL string
	// HumanTyping types the prompt as keystrokes, a word at a time with a pause
	// between words, instead of putting it in the field at once.
	HumanTyping bool
	// TypingDelay is the pause per character when HumanTyping is on.
	TypingDelay time.Duration
	// Viewport is the size of the browser window as the emulate tool spells it
	// (width x height x pixel ratio); zero means a laptop, 1280x800x1. The default
	// window of a headless Chrome is taller than most replies, which would leave
	// the scroll check with nothing to do, and differs from machine to machine.
	Viewport string
	// PollInterval is how often AwaitReply looks at the reply; zero means 250 ms.
	PollInterval time.Duration
	// SettleTimeout is how long to wait for a page to show what it should after
	// a navigation or a click; zero means 15 seconds.
	SettleTimeout time.Duration
	Logger        *slog.Logger
}

DriverConfig says how a driver behaves.

type Factory

type Factory struct {
	Session Options
	Driver  DriverConfig
	// contains filtered or unexported fields
}

Factory makes drivers, each with a browser of its own. Use it by pointer.

func (*Factory) NewDriver

func (f *Factory) NewDriver(ctx context.Context) (resource.UIDriver, error)

NewDriver starts a browser and returns a driver for it.

func (*Factory) Open

func (f *Factory) Open(ctx context.Context) (*Driver, error)

Open is NewDriver, returning the driver itself, for the probes that use more of it than the runner does.

func (*Factory) Versions

func (f *Factory) Versions() map[string]string

Versions are the tools the drivers made so far have reported: the MCP server and Chrome. Chrome's is known once a driver has signed in.

type Image

type Image struct {
	MIMEType string
	Data     []byte
}

Image is a picture a tool returned inline.

type NetworkRequest

type NetworkRequest struct {
	ID     int
	Method string
	URL    string
	// Status is the HTTP status; 0 when the line has none (still pending).
	Status int
	// Failure is the text in brackets when it is not a status, such as
	// "failed - net::ERR_CONNECTION_REFUSED".
	Failure string
}

NetworkRequest is one line of list_network_requests.

func ParseRequests

func ParseRequests(text string) []NetworkRequest

ParseRequests reads list_network_requests.

type Node

type Node struct {
	// UID is what the tools take to say which element to act on. It is only good
	// for the snapshot it came from.
	UID  string
	Role string
	// Name is the accessible name; empty when the element has none.
	Name string
	// Attrs are the key="value" parts after the name (url, level, value...).
	Attrs map[string]string
	// Flags are the bare words after the name (focusable, focused, disabled...).
	Flags []string
}

Node is one line of a page snapshot: an element of the accessibility tree.

func (Node) Has

func (n Node) Has(flag string) bool

Has reports whether the node carries the flag.

func (Node) String

func (n Node) String() string

type NotFoundError

type NotFoundError struct {
	Query Query
	// Roles lists a few elements the page does have, for the message.
	Roles []string
}

NotFoundError says that the snapshot has no element like the one asked for.

func (*NotFoundError) Error

func (e *NotFoundError) Error() string

type Options

type Options struct {
	// Command and Args are EVAL_MCP_COMMAND and EVAL_MCP_ARGS.
	Command, Args []string
	// Environ is the environment to take the server's from; nil means this
	// process's. Only what ChildEnv lets through is passed on.
	Environ []string
	// StartTimeout bounds the start and the handshake.
	StartTimeout time.Duration
	// CallTimeout bounds one tool call, unless the call asks for another.
	CallTimeout time.Duration
	Logger      *slog.Logger
}

Options say how to start the server and how long to wait for it.

type Page

type Page struct {
	ID       int
	Title    string
	URL      string
	Selected bool
}

Page is one entry of list_pages.

func ParsePages

func ParsePages(text string) []Page

ParsePages reads the "## Pages" list that list_pages, new_page and several other tools print.

func SelectedPage

func SelectedPage(pages []Page) (Page, bool)

SelectedPage is the page the tools act on by default.

type ProbePrompts

type ProbePrompts struct {
	// Long asks for a reply that takes seconds to write, so that there is time to
	// stop it or reload the page in the middle.
	Long string
	// Short asks for a reply of a word or two, for the probes that need an
	// answer and nothing more.
	Short string
	// Flaky asks for a reply that fails the first time and succeeds the second,
	// which only the scripted agent can give. Empty means no such agent, and the
	// probe that needs one is not run.
	Flaky string
}

ProbePrompts are what the probes say to the agent.

type Probes

type Probes struct {
	Factory         *Factory
	Email, Password string
	Prompts         ProbePrompts
	Thresholds      domain.Thresholds
	// KeepChats leaves the conversations the probes start in place.
	KeepChats bool
	Logger    *slog.Logger
	// contains filtered or unexported fields
}

Probes carries out the UI probes in a browser, one conversation at a time. It opens its browser on first use and closes it with Close.

func (*Probes) AgentDown

func (p *Probes) AgentDown(ctx context.Context) (domain.ProbeOutcome, error)

AgentDown asks for a reply that the scripted agent fails to give the first time, and checks that the page says so and that Retry gets the answer.

func (*Probes) Close

func (p *Probes) Close(ctx context.Context) error

Close ends the browser session.

func (*Probes) KeyboardOnly

func (p *Probes) KeyboardOnly(ctx context.Context) (domain.ProbeOutcome, error)

KeyboardOnly sends a message, stops a reply and opens a conversation with the keyboard alone.

func (*Probes) LayoutShift

func (p *Probes) LayoutShift(ctx context.Context) (domain.ProbeOutcome, error)

LayoutShift records a performance trace while a long reply arrives and reads how much the layout shifted.

func (*Probes) Lighthouse

func (p *Probes) Lighthouse(ctx context.Context) (domain.ProbeOutcome, error)

Lighthouse audits the page, on a desktop and on a phone, and reads the scores for accessibility and best practices.

func (*Probes) Mobile

func (p *Probes) Mobile(ctx context.Context) (domain.ProbeOutcome, error)

Mobile looks at the page in a window the size of a phone's.

func (*Probes) Regenerate

func (p *Probes) Regenerate(ctx context.Context) (domain.ProbeOutcome, error)

Regenerate asks for a new answer to the last message of a conversation of two replies, and checks that only that reply was replaced.

func (*Probes) ReloadMidStream

func (p *Probes) ReloadMidStream(ctx context.Context) (domain.ProbeOutcome, error)

ReloadMidStream loads the page again while a reply is being written.

func (*Probes) RenameDelete

func (p *Probes) RenameDelete(ctx context.Context) (domain.ProbeOutcome, error)

RenameDelete renames a conversation and deletes it, and checks that both hold after the page is loaded again.

func (*Probes) Stop

func (p *Probes) Stop(ctx context.Context) (domain.ProbeOutcome, error)

Stop presses Stop in the middle of a reply and times how long it takes to work.

func (*Probes) Theme

func (p *Probes) Theme(ctx context.Context) (domain.ProbeOutcome, error)

Theme presses the theme switch and reloads the page to see whether the choice was kept.

type Query

type Query struct {
	Role string
	// Name must equal the accessible name.
	Name string
	// NameContains must be part of the accessible name.
	NameContains string
	// NameMatches is a regular expression the accessible name must match.
	NameMatches *regexp.Regexp
	// Flag must be one of the node's flags.
	Flag string
}

Query picks nodes. Every field that is set must match.

func (Query) String

func (q Query) String() string

type Result

type Result struct {
	// Text is the text content, one block per line.
	Text   string
	Images []Image
}

Result is what a tool returned.

type RowAction

type RowAction string

RowAction is a button on a conversation's row in the sidebar.

const (
	RowRename RowAction = "rename"
	RowDelete RowAction = "delete"
)

The buttons of a row.

type ServerInfo

type ServerInfo struct{ Name, Version string }

ServerInfo is what the server says about itself in the handshake.

type Session

type Session struct {
	// contains filtered or unexported fields
}

Session is a connection to one Chrome DevTools MCP server, and so to one headless Chrome. It is for one driver: calls to it should not overlap.

func Open

func Open(ctx context.Context, opts Options) (*Session, error)

Open starts the server and completes the handshake. The server is not tied to ctx, which only bounds the start: it runs until Close.

func (*Session) Call

func (s *Session) Call(ctx context.Context, tool string, args map[string]any, opts ...CallOption) (Result, error)

Call runs a tool.

Errors are told apart by what the caller can do about them: ToolError means the page refused and the session is fine; the caller's context ending is returned as that context's error; anything that means the server or the browser is gone, or stopped answering, wraps domain.ErrDriverDied, and the session says why in its log and refuses further calls. A tool that does not answer in time counts as gone: a browser stuck in the middle of a call is not one to carry on with.

Pattern: Three-Outcome Call

func (*Session) Close

func (s *Session) Close(ctx context.Context) error

Close ends the session: the server is asked to stop by closing its input, and if ctx ends first it, and the browser it started, are killed. It is safe to call more than once.

func (*Session) Info

func (s *Session) Info() ServerInfo

Info returns what the server called itself and its version.

type Snapshot

type Snapshot struct {
	Nodes []Node
}

Snapshot is the accessibility tree of a page, in document order.

func ParseSnapshot

func ParseSnapshot(text string) Snapshot

ParseSnapshot reads the text take_snapshot returns. Lines that are not elements (the heading, a note about truncation) are ignored. The result has no nodes when the text holds none; the caller decides whether that is an error.

func (Snapshot) Find

func (s Snapshot) Find(q Query) (Node, bool)

Find returns the first node that matches.

func (Snapshot) FindAll

func (s Snapshot) FindAll(q Query) []Node

FindAll returns every node that matches, in document order.

func (Snapshot) Require

func (s Snapshot) Require(q Query) (Node, error)

Require is Find, with an error that says what the page did have.

func (Snapshot) RequireNth

func (s Snapshot) RequireNth(q Query, skip int) (Node, error)

RequireNth is Require for the node that comes after skip others that match, for pages whose elements share a name.

type ToolError

type ToolError struct {
	Tool    string
	Message string
}

ToolError is a tool that ran and said no: the element is not there, the script threw. The browser is fine; the call was not.

func (*ToolError) Error

func (e *ToolError) Error() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL