# Playwright

Integrate Playwright with Kameleo to automate browsing using realistic, spoofed browser fingerprints.

You can start a profile in two ways:

  1. Explicit start: Create the profile via the Local API and call startProfile. Use this when you need custom command-line switches, proxy settings, flags, or advanced options.
  2. Auto-start: Skip startProfile. When Playwright connects (Chroma via CDP; Junglefox via pw-bridge) with the WebSocket endpoint, Kameleo automatically starts the profile using defaults.

After either approach, you control the browser with normal Playwright commands, then clean up (stop / export / delete) the profile.

# Prerequisites

# Best practices

  • Do not add third-party stealth / fingerprint patches (playwright-extra-plugin-stealth, Canvas defenders, etc.). They can reduce masking quality.
  • Use one browser context per profile. Create multiple Kameleo profiles instead of multiple contexts.
  • Match your Playwright version to the Kameleo CLI compatibility matrix.

# Option 1: Explicitly start the profile (customizable)

Use this when you must set advanced startup parameters. Example below show a start with some custom settings.

# 1. Create a profile

from kameleo.local_api_client import KameleoLocalApiClient
from kameleo.local_api_client.models import CreateProfileRequest

client = KameleoLocalApiClient(endpoint='http://localhost:5050')

fingerprints = client.fingerprint.search_fingerprints(
    device_type='desktop',
    browser_product='chrome'
)

create_req = CreateProfileRequest(
    fingerprint_id=fingerprints[0].id,
    name='playwright explicit start example'
)
profile = client.profile.create_profile(create_req)
import { KameleoLocalApiClient } from "@kameleo/local-api-client";

const client = new KameleoLocalApiClient({ basePath: "http://localhost:5050" });
const fingerprints = await client.fingerprint.searchFingerprints("desktop", undefined, "chrome");
const createProfileRequest = { fingerprintId: fingerprints[0].id, name: "playwright explicit start example" };
const profile = await client.profile.createProfile(createProfileRequest);
using Kameleo.LocalApiClient;
using Kameleo.LocalApiClient.Model;

var client = new KameleoLocalApiClient(new Uri("http://localhost:5050"));
var fingerprints = await client.Fingerprint.SearchFingerprintsAsync(deviceType: "desktop", browserProduct: "chrome");
var createProfileRequest = new CreateProfileRequest(fingerprints[0].Id) { Name = "playwright explicit start example" };
var profile = await client.Profile.CreateProfileAsync(createProfileRequest);

# 2. Start the profile (customization point)

Below are three common customization patterns. Pick one (or combine arguments + preferences) before connecting Playwright. The browser must be stopped and restarted to apply a different set.

  1. Add command-line arguments (e.g. mute audio)
  2. Pass extra options / capabilities (e.g. disable background throttling)
  3. Set native browser preferences (e.g. disable images)
from kameleo.local_api_client.models import BrowserSettings, Preference

client.profile.start_profile(profile.id, BrowserSettings(
    arguments=["mute-audio"],
    additional_options=[
        Preference(key='pageLoadStrategy', value='eager'),
    ],
    preferences=[
        Preference(key='profile.managed_default_content_settings.images', value=2),
    ]
))
await client.profile.startProfile(profile.id, {
    browserSettings: {
        arguments: ["mute-audio"],
        additionalOptions: [{ key: "pageLoadStrategy", value: "eager" }],
        preferences: [{ key: "profile.managed_default_content_settings.images", value: 2 }],
    },
});
using Kameleo.LocalApiClient.Model;

await client.Profile.StartProfileAsync(profile.Id, new BrowserSettings(
    arguments: new List<string> { "mute-audio" },
    additionalOptions: new List<Preference> {
        new Preference("pageLoadStrategy", "eager"),
    },
    preferences: new List<Preference> {
        new Preference("profile.managed_default_content_settings.images", 2),
    }
));

# 3. Connect Playwright

Implementation differs by kernel.

# Chroma kernel (Chromium, auto-start)

Connect over CDP WebSocket: ws://localhost:{port}/playwright/{profileId}

from playwright.sync_api import sync_playwright

browser_ws_endpoint = f'ws://localhost:5050/playwright/{profile.id}'
with sync_playwright() as playwright:
    browser = playwright.chromium.connect_over_cdp(endpoint_url=browser_ws_endpoint)
import playwright from "playwright";

const browserWSEndpoint = `ws://localhost:5050/playwright/${profile.id}`;
const browser = await playwright.chromium.connectOverCDP(browserWSEndpoint);
using Microsoft.Playwright;

var browserWsEndpoint = $"ws://localhost:5050/playwright/{profile.Id}";
var playwright = await Playwright.CreateAsync();
var browser = await playwright.Chromium.ConnectOverCDPAsync(browserWsEndpoint);

# Junglefox kernel (Firefox, auto-start)

Playwright cannot attach to an already running Firefox instance, so use Kameleo's pw-bridge helper that translates commands.

from playwright.sync_api import sync_playwright
from os import path

browser_ws_endpoint = f'ws://localhost:5050/playwright/{profile.id}'
with sync_playwright() as playwright:
    # On macOS use '/Applications/Kameleo.app/Contents/Resources/CLI/pw-bridge'
    pw_bridge_path = path.expandvars(r'%LOCALAPPDATA%\Programs\Kameleo\pw-bridge.exe')
    browser = playwright.firefox.launch_persistent_context(
        '',
        executable_path=pw_bridge_path,
        args=[f'-target {browser_ws_endpoint}'],
        viewport=None)
import playwright from "playwright";

const browserWSEndpoint = `ws://localhost:5050/playwright/${profile.id}`;
// On macOS use "/Applications/Kameleo.app/Contents/Resources/CLI/pw-bridge"
const pwBridgePath = `${process.env["LOCALAPPDATA"]}\\Programs\\Kameleo\\pw-bridge.exe`;
const browser = await playwright.firefox.launchPersistentContext("", {
    executablePath: pwBridgePath,
    args: [`-target ${browserWSEndpoint}`],
    viewport: null,
});
using Microsoft.Playwright;

var browserWsEndpoint = $"ws://localhost:5050/playwright/{profile.Id}";
// On macOS use "/Applications/Kameleo.app/Contents/Resources/CLI/pw-bridge"
var pwBridgePath = Path.Combine(
    Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
    "Programs", "Kameleo", "pw-bridge.exe");
var playwright = await Playwright.CreateAsync();
var browser = await playwright.Firefox.LaunchPersistentContextAsync("", new BrowserTypeLaunchPersistentContextOptions
{
    ExecutablePath = pwBridgePath,
    Args = new List<string> { $"-target {browserWsEndpoint}" },
    ViewportSize = null,
});

# 4. Run Playwright commands

Use standard Playwright APIs.

# Chroma kernel

context = browser.contexts[0]
page = context.new_page()
page.goto('https://google.com')
const context = browser.contexts()[0];
const page = await context.newPage();
await page.goto("https://google.com");
var context = browser.Contexts[0];
var page = await context.NewPageAsync();
await page.GotoAsync("https://google.com");

# Junglefox kernel

page = browser.new_page()
page.goto('https://google.com')
const page = await browser.newPage();
await page.goto("https://google.com");
var page = await browser.NewPageAsync();
await page.GotoAsync("https://google.com");

# Option 2: Auto-start the profile (simpler)

Skip the explicit start call. Kameleo starts the profile automatically on the first Playwright connection. Use this for quick scripts where default startup behavior is enough.

# 1. Create the profile (same as before, no start call later)

from kameleo.local_api_client import KameleoLocalApiClient
from kameleo.local_api_client.models import CreateProfileRequest
from playwright.sync_api import sync_playwright

client = KameleoLocalApiClient(endpoint='http://localhost:5050')
fps = client.fingerprint.search_fingerprints(device_type='desktop', browser_product='chrome')
profile = client.profile.create_profile(CreateProfileRequest(
    fingerprint_id=fps[0].id,
    name='playwright auto-start example'
))
import { KameleoLocalApiClient } from "@kameleo/local-api-client";
import playwright from "playwright";

const client = new KameleoLocalApiClient({ basePath: "http://localhost:5050" });
const fps = await client.fingerprint.searchFingerprints("desktop", undefined, "chrome");
const profile = await client.profile.createProfile({ fingerprintId: fps[0].id, name: "playwright auto-start example" });
using Kameleo.LocalApiClient;
using Kameleo.LocalApiClient.Model;
using Microsoft.Playwright;

var client = new KameleoLocalApiClient(new Uri("http://localhost:5050"));
var fps = await client.Fingerprint.SearchFingerprintsAsync(deviceType: "desktop", browserProduct: "chrome");
var profile = await client.Profile.CreateProfileAsync(new CreateProfileRequest(fps[0].Id) { Name = "playwright auto-start example" });

# 2. Connect (auto-start happens here)

Implementation differs by kernel. When you connect, the profile starts automatically.

# Chroma kernel (Chromium)

browser_ws_endpoint = f'ws://localhost:5050/playwright/{profile.id}'
with sync_playwright() as playwright:
    browser = playwright.chromium.connect_over_cdp(endpoint_url=browser_ws_endpoint)
    context = browser.contexts[0]
    page = context.new_page()
    page.goto('https://wikipedia.org')
const browserWSEndpoint = `ws://localhost:5050/playwright/${profile.id}`;
const browser = await playwright.chromium.connectOverCDP(browserWSEndpoint);
const context = browser.contexts()[0];
const page = await context.newPage();
await page.goto("https://wikipedia.org");
var browserWsEndpoint = $"ws://localhost:5050/playwright/{profile.Id}";
var playwright = await Playwright.CreateAsync();
var browser = await playwright.Chromium.ConnectOverCDPAsync(browserWsEndpoint);
var context = browser.Contexts[0];
var page = await context.NewPageAsync();
await page.GotoAsync("https://wikipedia.org");

# Junglefox kernel (Firefox)

Playwright cannot attach to an already running Firefox instance, so use the pw-bridge helper (auto-start triggers on first command relay).

from os import path

browser_ws_endpoint = f'ws://localhost:5050/playwright/{profile.id}'
with sync_playwright() as playwright:
    # On macOS use '/Applications/Kameleo.app/Contents/Resources/CLI/pw-bridge'
    pw_bridge_path = path.expandvars(r'%LOCALAPPDATA%\Programs\Kameleo\pw-bridge.exe')
    browser = playwright.firefox.launch_persistent_context(
        '',
        executable_path=pw_bridge_path,
        args=[f'-target {browser_ws_endpoint}'],
        viewport=None)
    page = browser.new_page()
    page.goto('https://wikipedia.org')
// On macOS use "/Applications/Kameleo.app/Contents/Resources/CLI/pw-bridge"
const pwBridgePath = `${process.env["LOCALAPPDATA"]}\\Programs\\Kameleo\\pw-bridge.exe`;
const browserWSEndpoint = `ws://localhost:5050/playwright/${profile.id}`;
const browser = await playwright.firefox.launchPersistentContext("", {
    executablePath: pwBridgePath,
    args: [`-target ${browserWSEndpoint}`],
    viewport: null,
});
const page = await browser.newPage();
await page.goto("https://wikipedia.org");
// On macOS use "/Applications/Kameleo.app/Contents/Resources/CLI/pw-bridge"
var pwBridgePath = Path.Combine(
    Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
    "Programs", "Kameleo", "pw-bridge.exe");
var browserWsEndpoint = $"ws://localhost:5050/playwright/{profile.Id}";
var playwright = await Playwright.CreateAsync();
var browser = await playwright.Firefox.LaunchPersistentContextAsync("", new BrowserTypeLaunchPersistentContextOptions
{
    ExecutablePath = pwBridgePath,
    Args = new List<string> { $"-target {browserWsEndpoint}" },
    ViewportSize = null,
});
var page = await browser.NewPageAsync();
await page.GotoAsync("https://wikipedia.org");

# Cleanup (stop, export, delete)

Always stop the profile to persist its state. Optionally export it for backup or delete it to reclaim space.

# Stop, export, or delete

import os
from kameleo.local_api_client.models import ExportProfileRequest

client.profile.stop_profile(profile.id)

export_path = f'{os.path.dirname(os.path.realpath(__file__))}/test.kameleo'
client.profile.export_profile(profile.id, body=ExportProfileRequest(path=export_path))

client.profile.delete_profile(profile.id)
await client.profile.stopProfile(profile.id);

await client.profile.exportProfile(profile.id, { body: { path: `${import.meta.dirname}/test.kameleo` } });

await client.profile.deleteProfile(profile.id);
using Kameleo.LocalApiClient.Model;

await client.Profile.StopProfileAsync(profile.Id);

await client.Profile.ExportProfileAsync(profile.Id, new ExportProfileRequest(Path.Combine(Environment.CurrentDirectory, "test.kameleo")));

await client.Profile.DeleteProfileAsync(profile.Id);

# Full examples on GitHub