From 7c46558e5bcf5cb5ac872810675c0a1b1a5787b1 Mon Sep 17 00:00:00 2001 From: Snider Date: Fri, 20 Feb 2026 15:01:55 +0000 Subject: [PATCH] docs: graduate TODO/FINDINGS into production documentation Replace internal task tracking (TODO.md, FINDINGS.md) with structured documentation in docs/. Trim CLAUDE.md to agent instructions only. Co-Authored-By: Virgil --- CLAUDE.md | 36 +++--- FINDINGS.md | 25 ---- TODO.md | 35 ------ docs/architecture.md | 277 +++++++++++++++++++++++++++++++++++++++++++ docs/development.md | 213 +++++++++++++++++++++++++++++++++ docs/history.md | 87 ++++++++++++++ 6 files changed, 598 insertions(+), 75 deletions(-) delete mode 100644 FINDINGS.md delete mode 100644 TODO.md create mode 100644 docs/architecture.md create mode 100644 docs/development.md create mode 100644 docs/history.md diff --git a/CLAUDE.md b/CLAUDE.md index ca65f54..c92916e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,26 +1,27 @@ # CLAUDE.md -## What This Is - -Chrome DevTools Protocol (CDP) client for browser automation, testing, and scraping. Module: `forge.lthn.ai/core/go-webview` +Module: `forge.lthn.ai/core/go-webview` — Chrome DevTools Protocol client for browser automation. ## Commands ```bash -go test ./... # Run all tests -go test -v -run Name # Run single test +go test ./... # Run all tests (must pass before commit) +go test -v -run Name # Run a single test +gofmt -w . # Format code ``` -## Architecture +## Coding Standards -- `webview.New(webview.WithDebugURL("http://localhost:9222"))` connects to Chrome -- Navigation, DOM queries, console capture, screenshots, JS evaluation -- Angular-specific helpers for SPA testing +- UK English in all comments, docs, and commit messages +- EUPL-1.2 licence header (`// SPDX-License-Identifier: EUPL-1.2`) in every Go file +- Conventional commits: `type(scope): description` +- Co-author trailer on every commit: `Co-Authored-By: Virgil ` +- Test naming: `_Good` (happy path), `_Bad` (expected errors), `_Ugly` (panics/edge cases) ## Key API ```go -wv, _ := webview.New(webview.WithDebugURL("http://localhost:9222")) +wv, err := webview.New(webview.WithDebugURL("http://localhost:9222")) defer wv.Close() wv.Navigate("https://example.com") wv.Click("#submit") @@ -28,9 +29,14 @@ wv.Type("#input", "text") screenshot, _ := wv.Screenshot() ``` -## Coding Standards +## Docs -- UK English -- `go test ./...` must pass before commit -- Conventional commits: `type(scope): description` -- Co-Author: `Co-Authored-By: Virgil ` +- `docs/architecture.md` — CDP connection, DOM queries, console capture, Angular helpers +- `docs/development.md` — prerequisites, build/test, coding standards, adding actions +- `docs/history.md` — completed phases, known limitations, future considerations + +## Forge Push + +```bash +git push ssh://git@forge.lthn.ai:2223/core/go-webview.git HEAD:main +``` diff --git a/FINDINGS.md b/FINDINGS.md deleted file mode 100644 index f64c035..0000000 --- a/FINDINGS.md +++ /dev/null @@ -1,25 +0,0 @@ -# FINDINGS.md -- go-webview - -## 2026-02-19: Split from core/go (Virgil) - -### Origin - -Extracted from `forge.lthn.ai/core/go` `pkg/webview/` on 19 Feb 2026. - -### Architecture - -- Chrome DevTools Protocol (CDP) client over WebSocket -- Connects to Chrome's remote debugging port (default 9222) -- High-level API: `Navigate`, `Click`, `Type`, `QuerySelector`, `Evaluate`, `Screenshot` -- Console capture via `Runtime.consoleAPICalled` CDP events -- Multi-tab support via `Target.createTarget` / `Target.closeTarget` -- Angular-specific helpers for SPA testing workflows - -### Dependencies - -- `github.com/gorilla/websocket` -- WebSocket client for CDP connection - -### Notes - -- Requires a running Chrome instance with `--remote-debugging-port=9222` -- No headless Chrome launcher included -- the caller must start Chrome separately diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 4d3e6c5..0000000 --- a/TODO.md +++ /dev/null @@ -1,35 +0,0 @@ -# TODO.md -- go-webview - -## Phase 1: Test Coverage - -- [ ] Add headless Chrome tests runnable in CI (use `--headless=new` flag) -- [ ] Test navigation, click, type, screenshot in headless mode -- [ ] Add test fixtures (minimal HTML pages served by httptest) - -## Phase 2: Multi-Tab Support - -- [ ] `NewTab` / `CloseTab` exist but are not well tested -- [ ] Add tests for multi-tab lifecycle (open, switch, close) -- [ ] Verify console capture is tab-scoped (no cross-tab bleed) - -## Phase 3: Angular Helpers - -- [ ] Expand Angular-specific helpers beyond current set -- [ ] Add component detection (query by Angular selector) -- [ ] Add router integration (wait for navigation, read current route) -- [ ] Add zone.js stability detection (wait for async operations) - -## Phase 4: Screenshot Comparison - -- [ ] Add visual regression testing support -- [ ] Pixel-diff between baseline and current screenshots -- [ ] Configurable threshold for acceptable difference percentage -- [ ] Save diff images for failed comparisons - ---- - -## Workflow - -1. Virgil in core/go writes tasks here after research -2. This repo's dedicated session picks up tasks in phase order -3. Mark `[x]` when done, note commit hash diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..d9cd547 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,277 @@ +# Architecture + +Module: `forge.lthn.ai/core/go-webview` + +## Overview + +go-webview is a Chrome DevTools Protocol (CDP) client for browser automation, testing, and scraping. It provides a high-level Go API over the low-level CDP WebSocket protocol, connecting to an externally managed Chrome or Chromium instance running with the remote debugging port enabled. + +The package does not launch Chrome itself. The caller is responsible for starting a Chrome process with `--remote-debugging-port=9222` before constructing a `Webview`. + +--- + +## Package Structure + +| File | Responsibility | +|------|---------------| +| `webview.go` | `Webview` struct, public API, navigation, DOM, screenshot, JS evaluation | +| `cdp.go` | `CDPClient` — WebSocket transport, message framing, event dispatch | +| `actions.go` | `Action` interface, concrete action types, `ActionSequence` builder | +| `console.go` | `ConsoleWatcher`, `ExceptionWatcher`, log formatting | +| `angular.go` | `AngularHelper` — SPA-specific helpers for Angular 2+ and AngularJS 1.x | + +--- + +## CDP Connection + +### Initialisation + +`NewCDPClient(debugURL string)` connects to Chrome's HTTP endpoint: + +1. Issues `GET {debugURL}/json` to retrieve the list of available targets (tabs/pages). +2. Selects the first target with `type == "page"` that has a `webSocketDebuggerUrl`. +3. If no page target exists, calls `GET {debugURL}/json/new` to create one. +4. Upgrades the connection to WebSocket using `github.com/gorilla/websocket`. +5. Starts a background `readLoop` goroutine on the connection. + +### Message Protocol + +CDP uses JSON-framed messages over WebSocket. The client distinguishes two message kinds: + +- **Commands** — sent by the client with an integer `id`. Chrome responds with a matching `id` and a `result` or `error` field. +- **Events** — sent by Chrome without an `id`. They carry a `method` name and a `params` map. + +The `CDPClient` maintains a `pending` map of `id -> chan *cdpResponse`. When `Call()` sends a command it registers a channel, then blocks on that channel until the matching response arrives or the context expires. + +Events are dispatched to zero or more registered handlers via `OnEvent(method, handler)`. Each handler is called in its own goroutine so it cannot block the read loop. + +### Connection Lifecycle + +``` +New(WithDebugURL(...)) + └── NewCDPClient(url) + ├── HTTP GET /json (target discovery) + ├── websocket.Dial(wsURL) (WebSocket upgrade) + └── go readLoop() (background goroutine) + +wv.Close() + └── cancel() (signals readLoop to stop) + └── CDPClient.Close() + ├── <-done (waits for readLoop to finish) + └── conn.Close() (closes WebSocket) +``` + +--- + +## Webview Struct + +```go +type Webview struct { + mu sync.RWMutex + client *CDPClient + ctx context.Context + cancel context.CancelFunc + timeout time.Duration // default 30s + consoleLogs []ConsoleMessage + consoleLimit int // default 1000 +} +``` + +`New()` accepts functional options: + +| Option | Effect | +|--------|--------| +| `WithDebugURL(url)` | Required. Connects to Chrome at the given HTTP debug endpoint. | +| `WithTimeout(d)` | Overrides the default 30-second operation timeout. | +| `WithConsoleLimit(n)` | Maximum console messages to retain in memory (default 1000). | + +On construction, `New()` enables three CDP domains — `Runtime`, `Page`, and `DOM` — and registers a handler for `Runtime.consoleAPICalled` events to begin console capture immediately. + +--- + +## Navigation + +`Navigate(url string) error` calls `Page.navigate` then polls `document.readyState` via `Runtime.evaluate` at 100 ms intervals until the value is `"complete"` or the context deadline is exceeded. + +`Reload()`, `GoBack()`, and `GoForward()` follow the same pattern: issue a CDP command then call `waitForLoad`. + +`waitForSelector(ctx, selector)` polls `document.querySelector(selector)` at 100 ms intervals. + +--- + +## DOM Queries + +DOM queries follow a two-step pattern: + +1. Call `DOM.getDocument` to obtain the root node ID. +2. Call `DOM.querySelector` or `DOM.querySelectorAll` with that node ID and the CSS selector string. + +For each matching node, `getElementInfo` calls: +- `DOM.describeNode` — tag name and attribute list (flat alternating key/value array) +- `DOM.getBoxModel` — bounding rectangle from the `content` quad + +The returned `ElementInfo` carries: + +```go +type ElementInfo struct { + NodeID int + TagName string + Attributes map[string]string + InnerHTML string + InnerText string + BoundingBox *BoundingBox // nil if element has no layout box +} +``` + +--- + +## Click and Type + +### Click + +`click(ctx, selector)` resolves the element's bounding box, computes the centre point, then dispatches `Input.dispatchMouseEvent` for `mousePressed` then `mouseReleased`. If the element has no bounding box (e.g. a hidden element), it falls back to evaluating `document.querySelector(selector)?.click()`. + +### Type + +`typeText(ctx, selector, text)` first focuses the element via JavaScript, then dispatches `Input.dispatchKeyEvent` with `type: "keyDown"` and `type: "keyUp"` for each character in the string individually. + +`PressKeyAction` handles named keys (Enter, Tab, Escape, Backspace, Delete, arrow keys, Home, End, Page Up, Page Down) by mapping them to their CDP virtual key codes and code strings. + +--- + +## Console Capture + +Console capture is enabled in `New()` by subscribing to `Runtime.consoleAPICalled` events. + +### Basic Capture (Webview) + +The `Webview` itself accumulates messages in a slice guarded by `sync.RWMutex`. When the buffer reaches `consoleLimit`, the oldest 100 messages are dropped. + +```go +msgs := wv.GetConsole() // returns a copy +wv.ClearConsole() +``` + +### ConsoleWatcher + +`ConsoleWatcher` (constructed via `NewConsoleWatcher(wv)`) registers its own handler on the same `Runtime.consoleAPICalled` event. It adds filtering and reactive capabilities: + +- `AddFilter(ConsoleFilter)` — filter by message type and/or text pattern +- `AddHandler(ConsoleHandler)` — callback invoked for each incoming message (outside the write lock) +- `WaitForMessage(ctx, filter)` — blocks until a matching message arrives +- `WaitForError(ctx)` — convenience wrapper for `type == "error"` +- `Errors()`, `Warnings()`, `HasErrors()`, `ErrorCount()` + +### ExceptionWatcher + +`ExceptionWatcher` subscribes to `Runtime.exceptionThrown` events and captures unhandled JavaScript exceptions with full stack traces. It exposes the same reactive pattern as `ConsoleWatcher`: `AddHandler`, `WaitForException`, `HasExceptions`. + +--- + +## Screenshots + +`Screenshot()` calls `Page.captureScreenshot` with `format: "png"`. Chrome returns the image as a base64-encoded string in the `data` field of the response. The method decodes this and returns raw PNG bytes. + +--- + +## JavaScript Evaluation + +`evaluate(ctx, script)` calls `Runtime.evaluate` with `returnByValue: true`. The result is extracted from `result.result.value`. If `result.exceptionDetails` is present, the error description is returned as a Go error. + +`Evaluate(script string) (any, error)` is the public wrapper that applies the default timeout. + +`GetURL()` and `GetTitle()` are thin wrappers that evaluate `window.location.href` and `document.title` respectively. + +`GetHTML(selector string)` evaluates `outerHTML` on the matched element, or `document.documentElement.outerHTML` when the selector is empty. + +--- + +## Action System + +The `Action` interface has a single method: + +```go +type Action interface { + Execute(ctx context.Context, wv *Webview) error +} +``` + +Concrete action types cover: `Click`, `Type`, `Navigate`, `Wait`, `WaitForSelector`, `Scroll`, `ScrollIntoView`, `Focus`, `Blur`, `Clear`, `Select`, `Check`, `Hover`, `DoubleClick`, `RightClick`, `PressKey`, `SetAttribute`, `RemoveAttribute`, `SetValue`. + +`ActionSequence` provides a fluent builder: + +```go +err := NewActionSequence(). + Navigate("https://example.com"). + WaitForSelector("#login-form"). + Type("#email", "user@example.com"). + Type("#password", "secret"). + Click("#submit"). + Execute(ctx, wv) +``` + +`Execute` runs actions sequentially and returns the index and error of the first failure. + +### File Upload and Drag-and-Drop + +`UploadFile(selector, filePaths)` uses `DOM.setFileInputFiles` on the node ID of the resolved file input element. + +`DragAndDrop(sourceSelector, targetSelector)` dispatches `mousePressed`, `mouseMoved`, and `mouseReleased` events between the centre points of the two elements. + +--- + +## Angular Helpers + +`AngularHelper` (constructed via `NewAngularHelper(wv)`) provides SPA-specific utilities. All methods accept the `AngularHelper.timeout` deadline (default 30 s). + +### Application Detection + +`isAngularApp` checks for Angular 2+ via `window.getAllAngularRootElements`, the `[ng-version]` attribute, or `window.ng.probe`. It also checks for AngularJS 1.x via `window.angular.element`. + +### Zone.js Stability + +`WaitForAngular()` waits for Zone.js to report stability by checking `zone.isStable` and subscribing to `zone.onStable`. If the injector-based approach fails (production builds without debug info), it falls back to polling `window.Zone.current._inner._hasPendingMicrotasks` and `_hasPendingMacrotasks` at 50 ms intervals. + +### Router Integration + +`NavigateByRouter(path)` obtains the `Router` service from the Angular injector and calls `router.navigateByUrl(path)`, then waits for Zone.js stability. + +`GetRouterState()` returns an `AngularRouterState` with the current URL, fragment, route params, and query params. + +### Component Introspection + +`GetComponentProperty(selector, property)` and `SetComponentProperty(selector, property, value)` access component instances via `window.ng.probe(element).componentInstance`. After setting a property, `ApplicationRef.tick()` is called to trigger change detection. + +`CallComponentMethod(selector, method, args...)` invokes a method on the component instance and triggers change detection. + +`GetService(name)` retrieves a named service from the root injector and returns a JSON-serialisable representation. + +### ngModel + +`GetNgModel(selector)` reads the current value of an ngModel-bound input. `SetNgModel(selector, value)` writes the value, fires `input` and `change` events, and triggers `ApplicationRef.tick()`. + +--- + +## Multi-Tab Support + +`CDPClient.NewTab(url)` calls `GET {debugURL}/json/new?{url}` and returns a new `CDPClient` connected to the WebSocket of the newly created tab. Each tab has its own independent read loop and event handler registry, so console events and other notifications are tab-scoped. + +`CDPClient.CloseTab()` calls `Browser.close` on the tab's CDP session. + +`ListTargets(debugURL)` and `GetVersion(debugURL)` are package-level utilities that query the HTTP endpoint without requiring an active WebSocket connection. + +--- + +## Emulation + +`SetViewport(width, height int)` calls `Emulation.setDeviceMetricsOverride` with `deviceScaleFactor: 1` and `mobile: false`. + +`SetUserAgent(ua string)` calls `Emulation.setUserAgentOverride`. + +--- + +## Thread Safety + +- `CDPClient` uses `sync.RWMutex` for WebSocket writes and `sync.Mutex` for the pending-response map. Event handler registration uses a separate `sync.RWMutex`. +- `Webview` uses `sync.RWMutex` for its console log slice. +- `ConsoleWatcher` and `ExceptionWatcher` use `sync.RWMutex` for their message and handler slices. Handlers are copied before being called so they execute outside the write lock. diff --git a/docs/development.md b/docs/development.md new file mode 100644 index 0000000..092e4ff --- /dev/null +++ b/docs/development.md @@ -0,0 +1,213 @@ +# Development Guide + +## Prerequisites + +### Go + +Go 1.25 or later is required. The module path is `forge.lthn.ai/core/go-webview`. + +### Chrome or Chromium + +A running Chrome or Chromium instance with the remote debugging port enabled is required for any tests or usage that exercises the CDP connection. The package does not launch Chrome itself. + +Start Chrome with the remote debugging port: + +```bash +# macOS +/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \ + --remote-debugging-port=9222 + +# Linux +google-chrome --remote-debugging-port=9222 + +# Headless (suitable for CI) +google-chrome \ + --headless=new \ + --remote-debugging-port=9222 \ + --no-sandbox \ + --disable-gpu +``` + +The default debug URL is `http://localhost:9222`. Verify it is reachable by visiting `http://localhost:9222/json` in a browser or with `curl`. + +### Dependencies + +The only runtime dependency is `github.com/gorilla/websocket v1.5.3`, declared in `go.mod`. Fetch dependencies with: + +```bash +go mod download +``` + +--- + +## Build and Test + +### Running Tests + +```bash +go test ./... +``` + +Tests must pass before committing. There are currently no build tags that gate tests behind a Chrome connection; the integration tests in `webview_test.go` that require a live browser (`TestNew_Bad_InvalidDebugURL`) will fail gracefully because they assert that the error is non-nil when connecting to an unavailable port. + +```bash +# Run a specific test +go test -v -run TestActionSequence_Good ./... + +# Run all tests with verbose output +go test -v ./... +``` + +### Test Naming Convention + +Tests follow the `_Good`, `_Bad`, `_Ugly` suffix pattern, consistent with the broader Core Go ecosystem: + +- `_Good` — happy path, verifies correct behaviour under valid input. +- `_Bad` — expected error conditions, verifies that errors are returned and have the correct shape. +- `_Ugly` — panic/edge cases, unexpected or degenerate inputs. + +All test functions use the standard `testing.T` interface; the project does not use a test framework. + +### CI Headless Tests + +To add tests that exercise the full CDP stack in CI: + +1. Start Chrome in headless mode in the CI job before running `go test`. +2. Serve test fixtures using `net/http/httptest` so tests do not depend on external URLs. +3. Use `WithTimeout` to set conservative deadlines appropriate for the CI environment. + +--- + +## Code Organisation + +New source files belong in the root package (`package webview`). The package is intentionally a single flat package; do not create sub-packages. + +Keep separation between layers: + +- **CDP transport** — `cdp.go`. Do not put browser-level logic here. +- **High-level API** — `webview.go`. Methods here should be safe to call from application code without CDP knowledge. +- **Action types** — `actions.go`. Add new action types here; keep each action focused on a single interaction. +- **Diagnostics** — `console.go`. Console and exception capture live here. +- **SPA helpers** — `angular.go`. Framework-specific helpers belong here or in a new file named after the framework (e.g. `react.go`, `vue.go`). + +--- + +## Coding Standards + +### Language + +UK English throughout all source comments, documentation, commit messages, and identifiers where natural language appears. Use "colour", "organisation", "behaviour", "initialise", not their American equivalents. + +### Formatting + +Standard `gofmt` formatting is mandatory. Run before committing: + +```bash +gofmt -w . +``` + +### Types and Error Handling + +- All exported functions must have Go doc comments. +- Use `fmt.Errorf("context: %w", err)` for error wrapping so callers can use `errors.Is` and `errors.As`. +- Return errors; do not panic in library code. +- Use `context.Context` for all operations that involve I/O or waiting so callers can impose deadlines. + +### Concurrency + +- Protect shared mutable state with `sync.RWMutex` (read lock for reads, write lock for writes). +- Do not call handlers or callbacks while holding a lock. Copy the slice of handlers, release the lock, then call them. +- CDP WebSocket writes are serialised with a dedicated mutex in `CDPClient`; do not write to `conn` directly from outside `cdp.go`. + +### Licence Header + +Every Go source file must begin with: + +```go +// SPDX-License-Identifier: EUPL-1.2 +``` + +The project is licenced under the European Union Public Licence 1.2 (EUPL-1.2). + +--- + +## Commit Guidelines + +Use conventional commits: + +``` +type(scope): description +``` + +Common types: `feat`, `fix`, `docs`, `test`, `refactor`, `chore`. + +Example scopes: `cdp`, `angular`, `console`, `actions`. + +All commits must include the co-author trailer: + +``` +Co-Authored-By: Virgil +``` + +Full example: + +``` +feat(console): add ExceptionWatcher with stack trace capture + +Subscribes to Runtime.exceptionThrown events and exposes a reactive +WaitForException API consistent with ConsoleWatcher. + +Co-Authored-By: Virgil +``` + +--- + +## Adding a New Action Type + +1. Define a struct in `actions.go` with exported fields for the action's parameters. +2. Implement `Execute(ctx context.Context, wv *Webview) error` on the struct. +3. Add a builder method on `ActionSequence` that appends the new action. +4. Add a `_Good` test in `webview_test.go` that verifies the struct fields are set correctly. + +Example: + +```go +// SubmitAction submits a form element. +type SubmitAction struct { + Selector string +} + +func (a SubmitAction) Execute(ctx context.Context, wv *Webview) error { + script := fmt.Sprintf("document.querySelector(%q)?.submit()", a.Selector) + _, err := wv.evaluate(ctx, script) + return err +} + +func (s *ActionSequence) Submit(selector string) *ActionSequence { + return s.Add(SubmitAction{Selector: selector}) +} +``` + +--- + +## Adding a New Angular Helper + +Add methods to `AngularHelper` in `angular.go`. Follow the established pattern: + +1. Obtain a context with the helper's timeout using `context.WithTimeout`. +2. Build the JavaScript as a self-invoking function expression `(function() { ... })()`. +3. Call `ah.wv.evaluate(ctx, script)` to execute it. +4. After state-modifying operations, call `TriggerChangeDetection()` or inline `appRef.tick()`. +5. For polling-based waits, use a `time.NewTicker` at 100 ms and select over `ctx.Done()`. + +--- + +## Forge Push + +Push to the canonical remote via SSH: + +```bash +git push ssh://git@forge.lthn.ai:2223/core/go-webview.git HEAD:main +``` + +HTTPS authentication is not available on this remote. diff --git a/docs/history.md b/docs/history.md new file mode 100644 index 0000000..372e5ae --- /dev/null +++ b/docs/history.md @@ -0,0 +1,87 @@ +# Project History + +## Origin + +go-webview was extracted from the `pkg/webview/` directory of `forge.lthn.ai/core/go` on 19 February 2026 by Virgil. The extraction made the package independently importable and gave it its own module path, dependency management, and commit history. + +--- + +## Completed Phases + +### Extraction from core/go + +Commit `45f119b9ac0e0ebe34f5c8387a070a5b8bd2de6b` — 2026-02-19 + +Initial extraction. All source files were moved from `go` `pkg/webview/` into the root of this repository. The module was renamed from the internal path to `forge.lthn.ai/core/go-webview`. + +Files established at extraction: + +- `webview.go` — `Webview` struct, public API +- `cdp.go` — `CDPClient` WebSocket transport +- `actions.go` — `Action` interface and all concrete action types +- `console.go` — `ConsoleWatcher` and `ExceptionWatcher` +- `angular.go` — `AngularHelper` +- `webview_test.go` — struct and option tests + +### Dependency Fix + +Commit `56d2d476a110b740828ff66b44cc4cbd689dd969` — 2026-02-19 + +Added `github.com/gorilla/websocket v1.5.3` to `go.mod` and `go.sum`. The `go.mod` was missing the explicit `require` directive after the extraction. + +### Fleet Delegation Docs + +Commit `b752f155459c8bf610cfcc6486f4a9431ec4b7c3` — 2026-02-19 + +Added `TODO.md` and `FINDINGS.md` to record research findings and task backlog for the agent fleet. These files have since been superseded by this `docs/` directory and have been removed. + +--- + +## Known Limitations + +### No Chrome Launcher + +The package has no built-in mechanism for starting Chrome. All usage requires the caller to ensure a Chrome or Chromium process is already running with `--remote-debugging-port=9222`. This is intentional — launcher behaviour (headless vs headed, profile selection, proxy configuration) varies too widely between use cases to be handled generically. + +### Single-Tab Webview + +The `Webview` struct connects to one tab at construction time. `CDPClient.NewTab` returns a raw `CDPClient` rather than a full `Webview`, so the high-level API (navigate, click, screenshot, etc.) is not available directly on new tabs without wrapping the client manually. + +### Console Capture: Value-Only Arguments + +The console event handler extracts only arguments with a `value` field (primitives). Complex objects that Chrome serialises as `type: "object"` with a `description` rather than a `value` are silently omitted from the captured text. This means `console.log({foo: 'bar'})` will produce an empty message. + +### Angular Helper: Debug Mode Dependency + +`AngularHelper` methods that use `window.ng.probe` require the Angular application to be running in development mode (debug mode). Production builds compiled with `enableProdMode()` disable the debug utilities, making component introspection, `GetComponentProperty`, `SetComponentProperty`, `CallComponentMethod`, and `GetService` non-functional. `WaitForAngular` has a Zone.js polling fallback that works in production. + +### Angular Helper: AngularJS 1.x + +Detection of AngularJS 1.x (`window.angular.element`) is implemented in `isAngularApp`, but none of the other `AngularHelper` methods have 1.x-specific code paths. Methods beyond `WaitForAngular` are Angular 2+ only. + +### Zone Stability Timeout + +The JavaScript Promise used in `waitForZoneStability` has an internal 5-second `setTimeout` fallback. If Zone.js does not become stable within that window, the Promise resolves with the current stability value rather than rejecting. This can cause `WaitForAngular` to return successfully even when the application is still processing asynchronous work. + +### waitForLoad Polling + +`waitForLoad` polls `document.readyState` at 100 ms intervals rather than subscribing to the `Page.loadEventFired` CDP event. This works correctly but adds up to 100 ms of unnecessary latency after the load event fires. + +### Key Dispatch: Modifier Keys + +`PressKeyAction` handles named keys but does not support modifier combinations (Ctrl+A, Shift+Tab, etc.). Modifier key presses require constructing the CDP params map manually and calling `CDPClient.Call` directly. + +### CloseTab Implementation + +`CDPClient.CloseTab` calls `Browser.close`, which closes the entire browser rather than just the tab. The correct CDP command for closing a single tab is `Target.closeTarget` with the target's ID extracted from the WebSocket URL. This is a bug. + +--- + +## Future Considerations + +The following were identified as planned phases at the time of extraction. They are recorded here for context but are not committed work items. + +- **Headless CI tests** — integration tests using `net/http/httptest` fixtures and Chrome with `--headless=new`, runnable without a display. +- **Multi-tab lifecycle tests** — tests verifying that `NewTab`/`CloseTab` work correctly and that console events are scoped to their tab. +- **Angular helper expansion** — component detection by Angular selector, proper production-mode router integration. +- **Visual regression** — pixel-diff comparison between a baseline screenshot and a current screenshot, with configurable threshold and diff image output.