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 offers four connection modes, ranging from zero-configuration local launch to full control over the underlying CDP transport. In most cases you call V3.connectLocal() and Handstage handles everything else. The other modes exist for CI pipelines, cloud browsers, and embedding Handstage inside a larger framework.

Modes at a glance

connectLocal()

Launch a fresh Chrome process. The most common path — Handstage picks the binary, creates a temporary profile, and cleans up on close.

connectLocal({ cdpUrl })

Attach to a Chrome you already started by connecting to its CDP WebSocket endpoint.

connectTransport()

Supply a custom CDPTransport object (for proxied or embedded CDP connections).

connectSession()

Wrap an existing ExternalCDPSession (useful when another tool owns the CDP session).

Launch Chrome automatically

Call V3.connectLocal() with no arguments to launch Chrome with default settings. Handstage creates a temporary profile directory, sets a sensible viewport, and kills the process when you call close().
import { V3 } from "@handstage/core"

const handstage = await V3.connectLocal()
const page = handstage.context.activePage()

await page.goto("https://example.com")
// ... interact with the page ...

await handstage.close()

Connection modes

import { V3 } from "@handstage/core"

const handstage = await V3.connectLocal({
  localBrowserLaunchOptions: {
    headless: true,
    viewport: { width: 1440, height: 900 },
    userDataDir: "/tmp/my-profile",
  },
})

await handstage.close()

LocalBrowserLaunchOptions reference

These options are passed inside localBrowserLaunchOptions when calling V3.connectLocal().
OptionTypeDescription
headlessbooleanRun without a visible window.
executablePathstringPath to the Chrome binary. Defaults to the system-installed Chrome.
portnumberRemote debugging port.
viewport{ width, height }Initial window size in CSS pixels. Defaults to 1288×711.
argsstring[]Extra Chrome command-line flags appended after the defaults.
userDataDirstringPath to a Chrome profile directory (see note below).
preserveUserDataDirbooleanKeep the profile directory after close() even if Handstage created it.
proxy{ server, bypass?, username?, password? }HTTP/HTTPS proxy settings.
devtoolsbooleanAuto-open DevTools for every tab.
localestringSets --lang flag (e.g. "fr-FR").
ignoreHTTPSErrorsbooleanPass --ignore-certificate-errors to Chrome.
ignoreDefaultArgsboolean | string[]Skip all default flags (true) or remove specific ones by substring.
chromiumSandboxbooleanEnable the Chromium sandbox (disabled by default in many environments).
deviceScaleFactornumberOverride the device pixel ratio via --force-device-scale-factor.
hasTouchbooleanEnable touch events via --touch-events=enabled.
cdpUrlstringConnect to an existing browser instead of launching one.
cdpHeadersRecord<string, string>Headers sent with the CDP WebSocket upgrade (requires cdpUrl).
connectTimeoutMsnumberTimeout for establishing the CDP connection.
downloadsPathstringDirectory where downloads are saved.
acceptDownloadsbooleanAllow (true) or deny (false) file downloads.

Shared options

All connection methods accept a shared options object with these fields:
OptionTypeDescription
keepAlivebooleanWhen true, calling close() does not kill the Chrome process. SIGINT handling is also relaxed so your Node process can exit while Chrome keeps running.
sessionIdstringOptional identifier for this session. Falls back to an internal UUID.
verboseLogLevelMinimum log level to emit: Error, Info (default), or Debug.
logger(line: LogLine) => voidCustom log sink. Defaults to createConsoleLogger().
For connectTransport and connectSession, you can also pass viewport, deviceScaleFactor, downloadsPath, and acceptDownloads directly on the options object (not nested under localBrowserLaunchOptions).

The HEADLESS environment variable

When the HEADLESS environment variable is set but its value is not exactly "true", Handstage removes the variable from process.env before launching Chrome. This means setting HEADLESS=false or HEADLESS=0 in your environment is the same as not setting it at all — Chrome will be launched in headed (visible window) mode. Only HEADLESS=true causes Handstage to pass the headless flag.

Persisting a browser profile

By default Handstage creates a temporary profile directory and deletes it when you call close(). To keep state between runs (cookies, localStorage, cached credentials), point userDataDir at a directory you control:
const handstage = await V3.connectLocal({
  localBrowserLaunchOptions: {
    userDataDir: "/home/user/.handstage-profile",
    preserveUserDataDir: true,
  },
})
Use preserveUserDataDir: true together with a custom userDataDir when you want Handstage to manage the directory name but not delete it on exit.

Keep Chrome running after your script exits

Set keepAlive: true to leave Chrome running after close(). This is useful when you want to inspect the browser state manually or hand off a profile to another process:
const handstage = await V3.connectLocal({ keepAlive: true })
// ... do work ...
await handstage.close() // CDP connection closed; Chrome stays open

Creating isolated contexts

After connecting, call handstage.create() to open an additional browser context (similar to an incognito profile). Each context has its own cookies and storage but shares the same Chrome process:
const handstage = await V3.connectLocal()
const isolated = await handstage.create({ disposeOnDetach: true })
const page = await isolated.newPage("https://example.com")
await isolated.close()
await handstage.close()

Build docs developers (and LLMs) love