How to Create Persistent Browser Profiles Using `launch_persistent_context` in CloakBrowser

Use the launchPersistentContext function exported from js/src/playwright.ts and provide a userDataDir path to store cookies, localStorage, and session state across browser launches.

CloakBrowser is a stealth-focused wrapper around Playwright that enables persistent browser sessions through its launchPersistentContext implementation. Unlike ephemeral incognito contexts, this function creates real Chrome profiles on disk, allowing you to maintain authentication state and avoid anti-bot detection systems that flag fresh browser instances. The core implementation handles timezone normalization, proxy configuration, and WebRTC spoofing before delegating to Playwright's underlying Chromium launcher.

What Is launchPersistentContext and Why Use It?

The launchPersistentContext function in CloakBrowser wraps Playwright's native chromium.launchPersistentContext() to provide a stealth-enabled browser context that persists data between runs. When you use standard launch() or incognito contexts, every session starts completely fresh, which sophisticated anti-bot services can detect. By specifying a userDataDir, CloakBrowser stores the full browser profile—including cookies, localStorage, IndexedDB, and extension data—allowing you to resume exactly where you left off.

According to the CloakBrowser source code in js/src/playwright.ts, this approach applies all stealth arguments (such as --fingerprint-webrtc-ip and locale spoofing) while avoiding the "incognito penalty" that automation detection systems look for.

Inside the launchPersistentContext Implementation

Understanding how CloakBrowser assembles your persistent context helps you configure it correctly. The implementation at lines 186-214 of js/src/playwright.ts performs several preparation steps before calling Playwright.

Timezone and Locale Normalization

Before launch, the wrapper calls resolveTimezone to normalize timezone and timezoneId fields from your options. This ensures the browser reports consistent locale information that matches your proxy location, a critical factor for evading geographic fingerprinting.

Binary Path Resolution

The function uses ensureBinary() to locate the bundled Chromium binary, or respects the CLOAKBROWSER_BINARY_PATH environment variable if you need to override the executable. This happens at lines 84-88 of the source file.

Stealth Argument Construction

CloakBrowser builds the final argument list through several stages:

  • maybeResolveGeoip and resolveWebrtcArgs add flags like --fingerprint-webrtc-ip to prevent IP leakage
  • buildArgs merges user-provided arguments with stealth defaults
  • filterStealthCtxOptions removes detectable Chrome DevTools Protocol (CDP) emulation patterns that anti-bot scripts scan for

The Launch Call

Ultimately, the function invokes Playwright's underlying launcher with your persistent directory:

const context = await chromium.launchPersistentContext(options.userDataDir, {
  executablePath: binaryPath,
  headless: options.headless ?? true,
  args,
  ignoreDefaultArgs: IGNORE_DEFAULT_ARGS,
  ...(proxyOption ? { proxy: proxyOption } : {}),
  ...filterStealthCtxOptions(options.contextOptions),
  viewport: options.viewport === undefined ? DEFAULT_VIEWPORT : options.viewport,
});

Human Behavior Patching

If you set humanize: true in your options, CloakBrowser patches the returned context with realistic mouse movements and keyboard timing patterns after launch, making automation behavior indistinguishable from genuine user interaction.

Creating Your First Persistent Browser Profile

To create a persistent profile, you need only specify a directory path for userDataDir. Below is a complete example from the repository at js/examples/persistent-context.ts that demonstrates writing data in one session and retrieving it in another:

import { launchPersistentContext } from "../src/index.js";

const PROFILE_DIR = "./my-profile";

/* Session 1 – set persistent state */
let ctx = await launchPersistentContext({
  userDataDir: PROFILE_DIR,
  headless: false,
});
let page = ctx.pages()[0] || (await ctx.newPage());
await page.goto("https://example.com");
await page.evaluate(() => {
  document.cookie = "session=abc123; path=/; max-age=3600";
  localStorage.setItem("user", "returning");
});
console.log("Session 1 cookie:", await page.evaluate(() => document.cookie));
await ctx.close();

/* Session 2 – verify persistence */
ctx = await launchPersistentContext({
  userDataDir: PROFILE_DIR,
  headless: false,
});
page = ctx.pages()[0] || (await ctx.newPage());
await page.goto("https://example.com");
console.log("Session 2 cookie:", await page.evaluate(() => document.cookie));
console.log("Session 2 localStorage:", await page.evaluate(() => localStorage.getItem("user")));
await ctx.close();

Running this script twice demonstrates that both the cookie and localStorage values survive browser restarts.

Configuring Proxies and Stealth Options

Persistent contexts support the same stealth configuration as ephemeral ones. Pass a proxy string and enable geo-IP spoofing to match your proxy location:

const ctx = await launchPersistentContext({
  userDataDir: "./profile",
  headless: false,
  proxy: "http://user:pass@proxy.example:3128",
  geoip: true,
  userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
});

The geoip: true flag triggers maybeResolveGeoip to automatically configure timezone and locale settings based on the proxy's egress IP, while the persistent directory stores any authentication cookies set during the session.

Summary

  • launchPersistentContext in js/src/playwright.ts creates durable browser profiles that persist between script runs
  • Provide a userDataDir path to store cookies, localStorage, and extension data on disk
  • The function automatically applies stealth arguments, WebRTC spoofing, and timezone normalization before launching Chromium
  • Persistent profiles avoid the "incognito penalty" that triggers bot detection on many websites
  • Use humanize: true to add realistic mouse and keyboard behavior to persistent contexts

Frequently Asked Questions

Where is the launchPersistentContext function defined in CloakBrowser?

The function is defined in js/src/playwright.ts at lines 186-214. It exports an async function that accepts LaunchPersistentContextOptions and returns a Playwright BrowserContext instance with stealth modifications applied.

What data persists between sessions when using userDataDir?

The userDataDir directory stores cookies, localStorage, IndexedDB, cache, browser history, and extension data. This includes HTTP-only cookies and HTML5 storage mechanisms that standard ephemeral contexts discard upon closing.

Can I use headless mode with persistent browser profiles?

Yes. While the examples often use headless: false for visibility, you can set headless: true (the default) and the profile will still persist to disk. However, some anti-bot services detect headless mode, so CloakBrowser's stealth arguments remain essential regardless of visibility settings.

How does CloakBrowser handle proxy authentication in persistent contexts?

Proxy credentials passed in the proxy option are resolved through js/src/proxy.ts before launch. These settings persist for the context lifetime, and any authentication cookies received through the proxy are stored in the userDataDir, allowing subsequent sessions to maintain logged-in states without re-authenticating.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →