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)
}
Human-readable log message.
Severity level. Defaults to LogLevel.Info when absent.
Internal component that emitted the log (e.g., "ctx", "page", "init").
Arbitrary structured fields — URLs, target IDs, error details, timing, etc.
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