Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/l-xiaoshen/handstage/llms.txt

Use this file to discover all available pages before exploring further.

Handstage throws typed errors so you can handle specific failure modes without parsing error messages. All errors extend HandstagesError, which in turn extends the native Error class. Catching HandstagesError matches any Handstage-specific error.

Catching errors

import {
  V3,
  HandstagesError,
  TimeoutError,
  HandstagesElementNotFoundError,
  PageNotFoundError,
} from "@handstage/core"

try {
  const page = await browser.context.activePage()
  await page.locator("button.missing").click()
} catch (err) {
  if (err instanceof TimeoutError) {
    console.error("Operation timed out:", err.message)
  } else if (err instanceof HandstagesElementNotFoundError) {
    console.error("Element not found:", err.message)
  } else if (err instanceof HandstagesError) {
    // Any other Handstage error
    console.error("Handstage error:", err.message, err.cause)
  } else {
    throw err // re-throw unexpected errors
  }
}

Error classes

HandstagesError

Base class for all Handstage SDK errors. Catch this to handle any SDK error without importing every subclass.
class HandstagesError extends Error {
  readonly cause?: unknown
}
All error class names are set to the constructor name, so err.name always matches the class (e.g., "TimeoutError", "HandstagesElementNotFoundError").

TimeoutError

Thrown when an operation does not complete within the allowed time.
import { TimeoutError } from "@handstage/core"

// Constructor: TimeoutError(operation: string, timeoutMs: number)
// Message example: "goto timed out after 15000ms"
Common sources: page.goto, page.waitForLoadState, context.awaitActivePage, context.newPage.

PageNotFoundError

Thrown when a page cannot be resolved by a given identifier — for example, when awaitActivePage times out with no pages available.
import { PageNotFoundError } from "@handstage/core"

// Constructor: PageNotFoundError(identifier: string)
// Message example: "No Page found for awaitActivePage: no page available"

HandstagesElementNotFoundError

Thrown when a CSS or XPath selector resolves to no elements in the DOM.
import { HandstagesElementNotFoundError } from "@handstage/core"

// Constructor: HandstagesElementNotFoundError(xpaths: string[])
// Message example: "Could not find an element for the given xPath(s): button.missing"

ElementNotVisibleError

Thrown when a locator resolves an element but the element has no box model — it exists in the DOM but is not rendered (e.g., display: none).
import { ElementNotVisibleError } from "@handstage/core"

// Constructor: ElementNotVisibleError(selector: string)
// Message example: "Element not visible (no box model): button.hidden"

HandstagesLocatorError

Thrown when a locator action (such as fill) fails for an element-specific reason reported by the in-page script.
import { HandstagesLocatorError } from "@handstage/core"

// Constructor: HandstagesLocatorError(action: string, selector: string, message: string)
// Message example: "Error Filling Element with selector: input#email Reason: element is disabled"

HandstagesEvalError

Thrown when a JavaScript expression evaluated in the page context throws an exception.
import { HandstagesEvalError } from "@handstage/core"

// Constructor: HandstagesEvalError(message: string)
// Message example: "HandstagesEvalError: ReferenceError: foo is not defined"

HandstagesInvalidArgumentError

Thrown when a method receives an argument that fails a precondition check — for example, calling locator().nth() with a negative index, or passing a non-file-input element to setInputFiles.
import { HandstagesInvalidArgumentError } from "@handstage/core"

// Constructor: HandstagesInvalidArgumentError(message: string)
// Message example: "InvalidArgumentError: locator().nth() expects a non-negative index"

CookieSetError

Thrown when the browser rejects a Storage.setCookies call — for example, if a cookie has an invalid domain or conflicting secure/sameSite attributes.
import { CookieSetError } from "@handstage/core"

// Constructor: CookieSetError(message: string)

HandstagesSetExtraHTTPHeadersError

Thrown when one or more CDP sessions fail to enable the Network domain or to apply the requested headers via setExtraHTTPHeaders.
import { HandstagesSetExtraHTTPHeadersError } from "@handstage/core"

// Constructor: HandstagesSetExtraHTTPHeadersError(failures: string[])
// .failures contains per-session error strings

try {
  await page.setExtraHTTPHeaders({ "X-Token": "abc" })
} catch (err) {
  if (err instanceof HandstagesSetExtraHTTPHeadersError) {
    console.error("Failed sessions:", err.failures)
  }
}
Instance properties:
  • failures: string[] — An array of per-session error descriptions, each formatted as "session=<id> error=<message>".

HandstagesSnapshotError

Thrown when page.snapshot() fails to capture or process the accessibility tree.
import { HandstagesSnapshotError } from "@handstage/core"

// Constructor: HandstagesSnapshotError(cause?: unknown)
// Message example: "error taking snapshot: <underlying error message>"

Logging

Handstage emits structured log lines through a configurable sink. You can control verbosity with LogLevel and supply a custom logger when creating a V3 instance.

LogLevel enum

enum LogLevel {
  Error = "error", // Errors only — quietest
  Info  = "info",  // Errors + informational messages (default)
  Debug = "debug", // Everything — most verbose
}
LogLevel values are strings, so they serialize cleanly to JSON and log aggregation systems.

LogLine type

Every log line passed to your logger has this shape:
type LogLine = {
  message: string
  level?: LogLevel
  category?: string
  id?: string
  timestamp?: string
  attributes?: Record<string, unknown>
  traceId?: string   // W3C trace id (32 hex chars)
  spanId?: string    // W3C span id (16 hex chars)
  traceFlags?: string // W3C trace flags (2 hex chars)
}
message
string
required
Human-readable log message.
level
LogLevel
Severity level. Defaults to LogLevel.Info when absent.
category
string
Internal component that emitted the log (e.g., "ctx", "page", "init").
attributes
Record<string, unknown>
Arbitrary structured fields — URLs, target IDs, error details, timing, etc.
traceId
string
W3C-compatible trace ID for correlation with distributed tracing systems.

Custom logger example

Pass a logger function to any connect method to redirect Handstage’s log output to your own system:
import { V3, LogLevel, type LogLine } from "@handstage/core"

function myLogger(line: LogLine): void {
  const level = line.level ?? LogLevel.Info
  const msg = `[handstage:${line.category ?? "sdk"}] ${line.message}`
  if (level === LogLevel.Error) {
    console.error(msg, line.attributes)
  } else if (level === LogLevel.Debug) {
    console.debug(msg, line.attributes)
  } else {
    console.info(msg, line.attributes)
  }
}

const browser = await V3.connectLocal({
  verbose: LogLevel.Debug,
  logger: myLogger,
})
You can also update verbosity at runtime by setting browser.verbose:
import { LogLevel } from "@handstage/core"

// Increase verbosity after connecting
browser.verbose = LogLevel.Debug

// Suppress all but errors
browser.verbose = LogLevel.Error

Build docs developers (and LLMs) love