chrome

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 16, 2026 License: MIT Imports: 27 Imported by: 0

Documentation

Overview

Package chrome connects to Chrome over CDP (via chromedp) and exposes the Browser port the CLI commands drive. Keeping commands behind this interface lets the command boundary be tested in-process with a fake.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Reachable

func Reachable(wsURL string) bool

Reachable reports whether the loopback debug port is actually listening (used by `doctor` so a stale port file isn't reported as ready).

Types

type Browser

type Browser interface {
	List(ctx context.Context) ([]target.Info, error)
	Open(ctx context.Context, url string) (map[string]any, error)
	Navigate(ctx context.Context, targetID, url string) (map[string]any, error)
	Eval(ctx context.Context, targetID, expr string) (any, error)
	Snapshot(ctx context.Context, targetID string, opts SnapOpts) (any, error)
	Click(ctx context.Context, targetID, selector string, q QueryOpts) (map[string]any, error)
	Select(ctx context.Context, targetID, field, option string, opts SelectOpts) (map[string]any, error)
	Grid(ctx context.Context, targetID, selector string, q QueryOpts) (any, error)
	Scroll(ctx context.Context, targetID, selector string, opts ScrollOpts) (map[string]any, error)
	Type(ctx context.Context, targetID, selector, text string, q QueryOpts) (map[string]any, error)
	Fill(ctx context.Context, targetID, selector, value string, q QueryOpts) (map[string]any, error)
	HTML(ctx context.Context, targetID, selector string, inner bool, q QueryOpts) (map[string]any, error)
	Text(ctx context.Context, targetID, selector string, q QueryOpts) (map[string]any, error)
	Value(ctx context.Context, targetID, selector string, q QueryOpts) (map[string]any, error)
	Values(ctx context.Context, targetID, selector string, q QueryOpts) (map[string]any, error)
	AttrGet(ctx context.Context, targetID, selector, name string, q QueryOpts) (map[string]any, error)
	AttrList(ctx context.Context, targetID, selector string, q QueryOpts) (map[string]any, error)
	AttrSet(ctx context.Context, targetID, selector, name, value string, q QueryOpts) (map[string]any, error)
	AttrRemove(ctx context.Context, targetID, selector, name string, q QueryOpts) (map[string]any, error)
	SetHeaders(ctx context.Context, targetID string, headers map[string]string) (map[string]any, error)
	EmulateViewport(ctx context.Context, targetID string, width, height int64) (map[string]any, error)
	EmulateGeo(ctx context.Context, targetID string, lat, lon float64) (map[string]any, error)
	EmulateReset(ctx context.Context, targetID string) (map[string]any, error)
	Frames(ctx context.Context, targetID string) (any, error)
	Wait(ctx context.Context, targetID string, cond WaitCond) (map[string]any, error)
	Screenshot(ctx context.Context, targetID string) ([]byte, error)
	PDF(ctx context.Context, targetID string) ([]byte, error)
	CookieList(ctx context.Context, targetID string) (any, error)
	CookieSet(ctx context.Context, targetID, name, value, domain, path string) (map[string]any, error)
	CookieDelete(ctx context.Context, targetID, name string) (map[string]any, error)
	CookieClear(ctx context.Context, targetID string) (map[string]any, error)
	Raw(ctx context.Context, targetID, method string, params json.RawMessage) (any, error)
	Close() error
}

Browser is the set of Chrome operations the CLI commands need. The real implementation is CDP (chromedp-backed); tests use a fake.

type CDP

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

CDP is the chromedp-backed Browser.

The allocator/base/tab contexts are rooted at context.Background(), NOT at a per-command context — otherwise a command's deferred cancel would tear the whole tree down and (in attach mode) CloseTarget the user's real tab. Tabs are attached once and reused; per-command timeouts are applied as short-lived child contexts around each action (see run). The shared daemon is the eventual home for this attach-and-hold model.

func Connect

func Connect(_ context.Context, opts Options) (*CDP, error)

Connect walks the connection ladder (mirroring browser.DecideConnection):

  • a reachable DevToolsActivePort endpoint -> attach (Path B)
  • a running but non-debug Chrome -> ConnectError{not_debug_enabled} (never shadow the user's session with a second browser)
  • nothing running, --no-launch -> ConnectError{connection_failed}
  • nothing running -> launch a managed Chrome (Path A)

The ctx is intentionally not used to root the browser: the allocator lives at context.Background() so a command's deferred cancel can never tear down (and, in attach mode, CloseTarget) the user's real tabs. The decision (attach / instruct / launch) is delegated to the tested browser.DecideConnection so there is exactly one authored copy of the ladder.

func (*CDP) AttrGet

func (c *CDP) AttrGet(ctx context.Context, id, selector, name string, q QueryOpts) (map[string]any, error)

func (*CDP) AttrList

func (c *CDP) AttrList(ctx context.Context, id, selector string, q QueryOpts) (map[string]any, error)

func (*CDP) AttrRemove

func (c *CDP) AttrRemove(ctx context.Context, id, selector, name string, q QueryOpts) (map[string]any, error)

func (*CDP) AttrSet

func (c *CDP) AttrSet(ctx context.Context, id, selector, name, value string, q QueryOpts) (map[string]any, error)

func (*CDP) Click

func (c *CDP) Click(ctx context.Context, id, selector string, q QueryOpts) (map[string]any, error)

Click resolves the selector and clicks it at its live, occlusion-verified centre via a coordinate pointer sequence (the same primitive as `select`), so it lands on a background/inactive tab where chromedp's box-model node-click would poll until timeout. bringToFront reactivates the tab first.

func (*CDP) Close

func (c *CDP) Close() error

Close tears down the connection. Managed Chrome is fully closed. An attached real Chrome is left alone — cancelling any context would CloseTarget a real tab, so we let the WebSocket drop with the process instead.

func (*CDP) CookieClear

func (c *CDP) CookieClear(ctx context.Context, id string) (map[string]any, error)

func (*CDP) CookieDelete

func (c *CDP) CookieDelete(ctx context.Context, id, name string) (map[string]any, error)

func (*CDP) CookieList

func (c *CDP) CookieList(ctx context.Context, id string) (any, error)

func (*CDP) CookieSet

func (c *CDP) CookieSet(ctx context.Context, id, name, value, domain, path string) (map[string]any, error)

func (*CDP) EmulateGeo

func (c *CDP) EmulateGeo(ctx context.Context, id string, lat, lon float64) (map[string]any, error)

func (*CDP) EmulateReset

func (c *CDP) EmulateReset(ctx context.Context, id string) (map[string]any, error)

func (*CDP) EmulateViewport

func (c *CDP) EmulateViewport(ctx context.Context, id string, width, height int64) (map[string]any, error)

func (*CDP) Eval

func (c *CDP) Eval(ctx context.Context, id, expr string) (any, error)

func (*CDP) Fill

func (c *CDP) Fill(ctx context.Context, id, selector, value string, q QueryOpts) (map[string]any, error)

Fill sets a field to value, replacing (not appending to) any existing content: it triple-clicks the field to select all its text, then types value as real keystrokes over the selection — the reliable way to set a pre-filled cell (e.g. a timesheet "0" hour cell) to a new value in one call.

func (*CDP) Frames

func (c *CDP) Frames(ctx context.Context, id string) (any, error)

Frames enumerates the tab's frame tree (Page.getFrameTree).

func (*CDP) Grid

func (c *CDP) Grid(ctx context.Context, id, selector string, q QueryOpts) (any, error)

Grid reads a table/grid as structured {headers, rows} from the accessibility tree — replacing hand-parsing the a11y snapshot (or a screenshot) for the calendar / task-list / timesheet grids. selector optionally picks the grid by ARIA accessible name (with q); empty selects the first grid/table in the tree.

func (*CDP) HTML

func (c *CDP) HTML(ctx context.Context, id, selector string, inner bool, q QueryOpts) (map[string]any, error)

func (*CDP) List

func (c *CDP) List(_ context.Context) ([]target.Info, error)

func (*CDP) Navigate

func (c *CDP) Navigate(ctx context.Context, id, url string) (map[string]any, error)

func (*CDP) Open

func (c *CDP) Open(ctx context.Context, url string) (map[string]any, error)

Open creates a new tab at url (browser-level Target.createTarget, which navigates it) and returns the new tab's id — replacing a raw Target.createTarget for the common "start from a fresh tab on X" case.

func (*CDP) PDF

func (c *CDP) PDF(ctx context.Context, id string) ([]byte, error)

PDF prints the page to PDF (no chromedp Action; raw page.PrintToPDF).

func (*CDP) Raw

func (c *CDP) Raw(ctx context.Context, id, method string, params json.RawMessage) (any, error)

Raw sends any CDP method by string via the executor — full coverage, no per-method registry. An empty id targets the browser-level executor, so Browser.* / Target.* methods are reachable.

func (*CDP) Screenshot

func (c *CDP) Screenshot(ctx context.Context, id string) ([]byte, error)

func (*CDP) Scroll

func (c *CDP) Scroll(ctx context.Context, id, selector string, opts ScrollOpts) (map[string]any, error)

Scroll scrolls a selector into view (Into), dispatches a real mouse wheel (Wheel), or — by default — scrolls by (Dx, Dy) via JS scrollBy on the selector's scroll box (or the window when selector is empty).

func (*CDP) Select

func (c *CDP) Select(ctx context.Context, id, field, option string, opts SelectOpts) (map[string]any, error)

Select chooses an option in a prompt / combobox / cascade widget, or a native <select>. It exists because a plain `click` cannot drive Workday's portal-rendered menus and React cascade prompts: those open on a real pointer sequence, mount briefly collapsed (a zero-scale transform) then animate open, render options in a detached subtree with delegated (capture-phase) handlers, and change geometry between the open and the option click. See .scratch/cdp-ergonomics/e3-rootcause.md.

The whole choreography runs inside ONE held CDP action (a single c.run), so geometry is re-read between steps against the live DOM — no multi-command staleness. Each interaction is a coordinate Input.dispatchMouseEvent (mouseMoved→pressed→released) at the element's live, occlusion-verified centre, which is what actually drives the delegated widgets where chromedp's node-click (one box-model read, no settle/occlusion check) misses.

func (*CDP) SetHeaders

func (c *CDP) SetHeaders(ctx context.Context, id string, headers map[string]string) (map[string]any, error)

func (*CDP) Snapshot

func (c *CDP) Snapshot(ctx context.Context, id string, opts SnapOpts) (any, error)

func (*CDP) Text

func (c *CDP) Text(ctx context.Context, id, selector string, q QueryOpts) (map[string]any, error)

func (*CDP) Type

func (c *CDP) Type(ctx context.Context, id, selector, text string, q QueryOpts) (map[string]any, error)

Type coordinate-clicks the selector to focus it (robust on a background tab), then sends the text as real keystrokes to the focused element.

func (*CDP) Value

func (c *CDP) Value(ctx context.Context, id, selector string, q QueryOpts) (map[string]any, error)

func (*CDP) Values

func (c *CDP) Values(ctx context.Context, id, selector string, q QueryOpts) (map[string]any, error)

Values reads the value (or text) of EVERY element matching a CSS selector, in document order — one round trip instead of an eval per field (e.g. reading a whole row of timesheet hour cells or a set of selected pills). Uses querySelectorAll, so it works even on a background/hidden tab.

func (*CDP) Wait

func (c *CDP) Wait(ctx context.Context, id string, cond WaitCond) (map[string]any, error)

Wait blocks until a condition holds: the target URL contains cond.URL, or a selector becomes visible / is gone. The caller's context deadline bounds it.

type ConnectError

type ConnectError struct {
	Code    string
	Message string
}

ConnectError carries a stable error.code (matching the result contract) so the CLI can distinguish "not debug-enabled" from a generic connection failure.

func (*ConnectError) Error

func (e *ConnectError) Error() string

type Options

type Options struct {
	PortFile   string // override DevToolsActivePort location (else OS candidates)
	ProfileDir string // managed-launch profile dir (else CHROME_CDP_PROFILE / default)
	Port       int    // explicit debug port to attach to / launch with (0 = auto)
	NoLaunch   bool   // don't fall back to launching a managed Chrome
	Headless   bool   // headless for the managed-launch fallback (tests use this)
}

Options controls how CDP connects.

type QueryOpts

type QueryOpts struct {
	By     string // css (default) | id | search | jspath | css-all | name
	Wait   string // visible (default) | ready | enabled
	Pierce bool   // reach into shadow DOM / iframes (via DevTools search)

	// Accessible-name addressing (By == "name"): the selector is the ARIA
	// accessible name; Role optionally constrains the ARIA role, and Nth picks
	// the 1-based match among the visible candidates (0 = first).
	Role string
	Nth  int

	// Match controls how the accessible name is compared (By == "name"):
	// "" / "exact" (default), "contains" (case-insensitive substring), or
	// "regex". Lets a caller click "Review" without knowing its verbose full
	// accessible name.
	Match string

	// InRow scopes an accessible-name match (By == "name") to the table row
	// whose text contains this string — so `click --by name "Delete" --in-row
	// "TEST entry"` hits the Delete button in that row, not the first of many.
	// It resolves via the DOM (not the throttled a11y tree), so it also works on
	// a backgrounded tab. Requires By == "name".
	InRow string

	// OnDialog, set on an action verb (click/type/fill), auto-handles a native
	// JavaScript dialog (alert/confirm/prompt) that opens DURING the action —
	// "accept" or "dismiss" — instead of letting it wedge the CDP connection.
	// The handled dialogs are reported back in the result envelope.
	OnDialog string
}

QueryOpts controls how a selector is interpreted (By), what state to wait for (Wait), and whether to pierce shadow DOM / iframes (Pierce).

type ScrollOpts

type ScrollOpts struct {
	Dx    float64
	Dy    float64
	Into  bool
	Wheel bool
	Query QueryOpts
}

ScrollOpts controls the `scroll` verb:

  • Into: scroll Selector into view.
  • otherwise: scroll by (Dx, Dy) pixels — Selector's scroll box, or the window when Selector is empty. This is a JS scrollBy (deterministic; it fires the scroll events virtualized grids render on).
  • Wheel: instead dispatch a real mouse-wheel of (Dx, Dy) at Selector's centre (viewport centre if empty), for grids that listen for wheel specifically.

type SelectOpts

type SelectOpts struct {
	// Query addresses the FIELD control (usually By=="name" with a Role like
	// textbox/combobox to disambiguate a same-named column header from the input).
	Query QueryOpts

	// OptionMatch controls how each option segment is compared against rendered
	// option text: "" / "contains" (default — Workday labels are verbose),
	// "exact", or "regex".
	OptionMatch string

	// Filter is optional text typed into the prompt after it opens, to narrow a
	// long option list before the option is clicked.
	Filter string

	// Sep splits Option into cascade levels (default ">"): e.g.
	// "Project Plan Tasks > ShiftLeft: Qwiet" drills the category, then the leaf.
	Sep string
}

SelectOpts controls the `select` verb — choosing an option in a prompt / combobox / cascade widget (Workday-style portal popups, or a native <select>).

type SnapOpts

type SnapOpts struct {
	Role   string // only nodes with this ARIA role
	Grep   string // only nodes whose accessible name matches this regex
	Region string // only nodes within the subtree of a container whose name contains this
	Dedupe bool   // collapse identical role+name (keep first) — for virtualized grids
}

SnapOpts filters an accessibility snapshot server-side, so a read returns just the relevant nodes instead of the whole tree. Alerts/focused stay page-wide.

type WaitCond

type WaitCond struct {
	URL     string
	Visible string
	Gone    string
	Text    string // until the accessibility tree contains this text (e.g. a "Success" toast)
	Stable  bool   // until the accessibility tree stops changing (the page settled)
	Idle    bool   // until network activity settles (no in-flight requests for a window)
	Query   QueryOpts
}

WaitCond is a condition for the `wait` verb: settle until the target URL contains URL, or a selector becomes Visible / is Gone. Exactly one is set; Query carries the selector options for Visible/Gone. A fixed --for duration is handled in the CLI (no browser round-trip needed).

Jump to

Keyboard shortcuts

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