Overview

Browsernode supports a wide variety of ways to launch or connect to a browser:
  • Launch a new local browser using playwright/patchright chromium (the default)
  • Connect to a remote browser using CDP or WSS
  • Use an existing playwright Page, Browser, or BrowserContext object
  • Connect to a local browser already running using browserPid
Don’t want to manage your own browser infrastructure? Try ☁️ Browsernode Cloud ➡️We provide automatic CAPTCHA solving, proxies, human-in-the-loop automation, and more!

Connection Methods

Method A: Launch a New Local Browser (Default)

Launch a local browser using built-in default (playwright chromium) or a provided executablePath:
import { Agent, BrowserSession } from "browsernode";

// If no executablePath provided, uses Playwright/Patchright's built-in Chromium
const browserSession = new BrowserSession({
    // Path to a specific Chromium-based executable (optional)
    executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',  // macOS
    // For Windows: 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'
    // For Linux: '/usr/bin/google-chrome'

    // Use a specific data directory on disk (optional, set to null for incognito)
    userDataDir: '~/.config/browsernode/profiles/default',   // this is the default
    // ... any other BrowserProfile or playwright launchPersistentContext config...
    // headless: false,
})

const agent = new Agent({
    task: "Your task here",
    llm: llm,
    browserSession: browserSession,
})
We support most chromium-based browsers in executablePath, including Brave, patchright chromium, rebrowser, Edge, and more. See examples/browser/stealth.ts for more. We do not support Firefox or Safari at the moment.
As of Chrome v136, driving browsers with the default profile is no longer supported for security reasons. browsernode has transitioned to creating a new dedicated profile for agents in: ~/.config/browsernode/profiles/default. You can open this profile and log into everything you need your agent to have access to, and it will persist over time.

Method B: Connect Using Existing Playwright Objects

Pass existing Playwright Page, BrowserContext, Browser, and/or playwright API object to BrowserSession(...):
import { Agent, BrowserSession } from "browsernode";

const playwright = await import("playwright");

const browser = await playwright.chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();

const browserSession = new BrowserSession({
        page: page,
        // browserContext: context,  // all these are supported
        // browser: browser,
});

const agent = new Agent({
    task: "Your task here",
    llm: llm,
    browserSession: browserSession,
});
You can also pass page directly to Agent(...) as a shortcut.
const agent = new Agent({
    task: "Your task here",
    llm: llm,
    page: page,
})

Method C: Connect to Local Browser Using Browser PID

Connect to a browser with open --remote-debugging-port:
import { Agent, BrowserSession } from "browsernode";

// First, start Chrome with remote debugging:
// /Applications/Google Chrome.app/Contents/MacOS/Google Chrome --remote-debugging-port=9242

// Then connect using the process ID
const browserSession = new BrowserSession({ browserPid: 12345 })  // Replace with actual Chrome PID

const agent = new Agent({
    task: "Your task here",
    llm: llm,
    browserSession: browserSession,
})

Method D: Connect to remote Playwright Node.js Browser Server via WSS URL

Connect to Playwright Node.js server providers:
import { Agent, BrowserSession } from "browsernode";

// Connect to a playwright server
const browserSession = new BrowserSession({ wssUrl: "wss://your-playwright-server.com/ws" })

const agent = new Agent({
    task: "Your task here",
    llm: llm,
    browserSession: browserSession,
})

Method E: Connect to Remote Browser via CDP URL

Connect to any remote Chromium-based browser:
import { Agent, BrowserSession } from "browsernode";

// Connect to Chrome via CDP
const browserSession = new BrowserSession({ cdpUrl: "http://localhost:9222" })

const agent = new Agent({
    task: "Your task here",
    llm: llm,
    browserSession: browserSession,
})

Security Considerations

When using any browser profile, the agent will have access to:
  • All its logged-in sessions and cookies
  • Saved passwords (if autofill is enabled)
  • Browser history and bookmarks
  • Extensions and their data
Always review the task you’re giving to the agent and ensure it aligns with your security requirements! Use Agent(sensitiveData={'https://auth.example.com': {x_key: value}}) for any secrets, and restrict the browser with BrowserSession(allowedDomains=['https://*.example.com']).

Best Practices

  1. Use isolated profiles: Create separate Chrome profiles for different agents to limit scope of risk:
    const browserSession = new BrowserSession({
        userDataDir: '~/.config/browsernode/profiles/banking',
        // profileDirectory: 'Default'
    })
    
  2. Limit domain access: Restrict which sites the agent can visit:
    const browserSession = new BrowserSession({
        allowedDomains: ['example.com', 'http*://*.github.com'],
    })
    
  3. Enable keepAlive=true If you want to use a single BrowserSession with more than one agent:
    const browserSession = new BrowserSession({
        keepAlive: true,
        // ... other options
    })
    await browserSession.start()  // start the session yourself before passing to Agent
    // ...
    const agent = new Agent({ ..., browserSession: browserSession })
    await agent.run()
    // ...
    await browserSession.kill()   // end the session yourself, shortcut for keepAlive=false + .stop()
    

Re-Using a Browser

A BrowserSession starts when the browser is launched/connected, and ends when the browser process exits/disconnects. A session internally manages a single live playwright browser context, and is normally auto-closed by the agent when its task is complete (if the agent started the session itself). If you pass an existing BrowserSession into an Agent, or if you set BrowserSession(keepAlive=True), the session will not be closed and can be re-used between agents. Browsernode provides a number of ways to re-use profiles, sessions, and other configuration across multiple agents.
  • ✅ sequential agents can re-use a single userDataDir in new BrowserSessions
  • ✅ sequential agents can re-use a single BrowserSession without closing it
  • ❌ parallel agents cannot run separate BrowserSessions using the same userDataDir
  • ✅ parallel agents can run separate BrowserSessions using the same storageState
  • ✅ parallel agents can share a single BrowserSession, working in different tabs
  • ⚠️ parallel agents can share a single BrowserSession, working in the same tab

Sequential Agents, Same Profile, Different Browser

If you are only running one agent & browser at a time, they can re-use the same userDataDir sequentially.
import { Agent, BrowserSession } from "browsernode";

const reusedProfile = new BrowserProfile({ userDataDir: '~/.config/browsernode/profiles/default' })

const agent1 = new Agent({
    task: "The first task...",
    llm: llm,
    browserProfile: reusedProfile,    // pass the profile in, it will auto-create a session
})
await agent1.run();

const agent2 = new Agent({
    task: "The second task...",
    llm: llm,
    browserProfile: reusedProfile,    // agent will auto-create its own new session
})
await agent2.run();
Make sure to never mix different browser versions or executablePaths with the same userDataDir. Once run with a newer browser version, some migrations are applied to the dir and older browsers wont be able to read it.

Sequential Agents, Same Profile, Same Browser

If you are only running one agent at a time, they can re-use the same active BrowserSession and avoid having to relaunch chrome. Each agent will start off looking at the same tab the last agent ended off on.
import { Agent, BrowserSession } from "browsernode";

const reusedSession = new BrowserSession({
    userDataDir: '~/.config/browsernode/profiles/default',
    keepAlive: true,  // dont close browser after 1st agent.run() ends
})
await reusedSession.start()   // when keepAlive=true, session must be started manually

const agent1 = new Agent({
    task: "The first task...",
    llm: llm,
    browserSession: reusedSession,
})
await agent1.run();

const agent2 = new Agent({
    task: "The second task...",
    llm: llm,
    browserSession: reusedSession,      // re-use the same session
)
await agent2.run();

await reusedSession.close();

Parallel Agents, Same Browser, Multiple Tabs

import { Agent, BrowserSession } from "browsernode";
import { chromium } from "playwright";


const browser = await chromium.launch();
const context = await browser.newContext();
const page1 = await context.newPage();
const page2 = await context.newPage();

const agent1 = new Agent(
    task: "The first task...",
    llm: llm,
    page: page1,
    )
const agent2 = new Agent(
    task: "The second task...",
    llm: llm,
    page: page2,
)
await Promise.all([agent1.run(), agent2.run()]); // run in parallel

Parallel Agents, Same Browser, Same Tab

⚠️ This mode is not recommended. Agents are not yet optimized to share the same tab in the same browser, they may interfere with each other or cause errors.
import { Agent, BrowserSession } from "browsernode";
import { chromium } from "playwright";

const playwright = await import("playwright");

const browser = await playwright.chromium.launch();
const context = await browser.newContext();
const sharedPage = await context.newPage();
await sharedPage.goto('https://example.com', waitUntil='domcontentloaded')

const sharedSession = new BrowserSession({ page: sharedPage, keepAlive: true })
await sharedSession.start()

const agent1 = new Agent({
    task: "Fill out the form in section A...",
    llm: llm,
    browserSession: sharedSession,
})
const agent2 = new Agent({
    task: "Fill out the form in section B...",
    llm: llm,
    browserSession: sharedSession,
})
await Promise.all([agent1.run(), agent2.run()]); // run in parallel

await sharedSession.kill();

Parallel Agents, Same Profile, Different Browsers

This mode is the recommended default.
To share a single set of configuration or cookies, but still have agents working in their own browser sessions (potentially in parallel), use our provided BrowserProfile object. The recommended way to re-use cookies and localStorage state between separate parallel sessions is to use the storageState option.
# open a browser to log into sites you want the Agent to have access to
playwright open https://example.com/ --save-storage=/tmp/auth.json
playwright open https://example.com/ --load-storage=/tmp/auth.json
import { Agent, BrowserProfile, BrowserSession } from "browsernode";

const sharedProfile = new BrowserProfile({
    headless: true,
    userDataDir: null,               // use dedicated tmp userDataDir per session
    storageState: '/tmp/auth.json',   // load/save cookies to/from json file
    keepAlive: true,                  // don't close the browser after the agent finishes
})

const window1 = new BrowserSession({ browserProfile: sharedProfile })
await window1.start()
const agent1 = new Agent({ browserSession: window1 })

const window2 = new BrowserSession({ browserProfile: sharedProfile })
await window2.start()
const agent2 = new Agent({ browserSession: window2 })

await Promise.all([agent1.run(), agent2.run()]);  // run in parallel
await window1.saveStorageState();  // write storage state (cookies, localStorage, etc.) to auth.json
await window2.saveStorageState();  // you must decide when to save manually

// can also reload the cookies from the file into the active session if they change
await window1.loadStorageState()
await window1.close()
await window2.close()

Troubleshooting

Chrome Won’t Connect

If you’re having trouble connecting:
  1. Close all Chrome instances before trying to launch with a custom profile
  2. Check if Chrome is running with debugging port:
    ps aux | grep chrome | grep remote-debugging-port
    
  3. Verify the executable path is correct for your system
  4. Check profile permissions - ensure your user has read/write access

Profile Lock Issues

If you get a “profile is already in use” error:
  1. Close all Chrome instances
  2. The profile will automatically be unlocked when BrowserSession starts
  3. Alternatively, manually delete the SingletonLock file in the profile directory
For more configuration options, see the Browser Settings documentation.

Profile Version Issues

The browser version you run must always be equal to or greater than the version used to create the userDataDir. If you see errors like Failed to parse Extensions when launching, you’re likely attempting to run an older browser with an incompatible userDataDir that’s already been migrated to a newer Chrome version. Playwright ships a version of chromium that’s newer than the default stable Google Chrome release channel, so this can happen if you try to use a profile created by the default playwright chromium (e.g. userDataDir='~/.config/browsernode/profiles/default') with an older local browser like executablePath='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'.