If you have ever tried to run automated browser sessions across multiple accounts — for QA testing, price monitoring, ad verification, or managing several business profiles — you have probably hit the same wall everyone hits. Everything works fine for the first account, then the second account starts seeing CAPTCHAs, the third gets a verification prompt, and by the fourth the platform has quietly linked all of them together.
The problem is almost never your code. It is the fact that every session is leaving the same fingerprint and the same IP address. This guide walks through how to build a multi-account browser automation system with Playwright and residential proxies that keeps each session genuinely isolated.
Why Multi-Account Automation Fails
Modern platforms identify users on two independent layers, and you have to defeat both.
The first layer is network identity. If ten "different" users all connect from the same datacenter IP, no amount of clever scripting will save you. The IP itself is the giveaway.
The second layer is browser fingerprinting. Even on separate IPs, websites read dozens of signals from the browser — canvas hashes, WebGL renderer strings, installed fonts, screen resolution, timezone, hardware concurrency, the AudioContext signature, and more. Combined, these create a fingerprint that is unique enough to track you across sessions. Two Playwright contexts launched from the same machine with default settings will produce nearly identical fingerprints.
A robust setup therefore needs three things working together: isolated browser contexts, a unique and consistent fingerprint per account, and a dedicated residential IP per account.
Step 1: Isolate Sessions with Browser Contexts
Playwright's browserContext is the foundation. Each context has its own cookie jar, local storage, cache, and session storage. Nothing leaks between contexts, which means account A's login session is invisible to account B.
const { chromium } = require('playwright');
async function createAccountSession(accountConfig) {
const browser = await chromium.launch({ headless: false });
const context = await browser.newContext({
viewport: accountConfig.viewport,
userAgent: accountConfig.userAgent,
locale: accountConfig.locale,
timezoneId: accountConfig.timezone,
proxy: accountConfig.proxy,
});
const page = await context.newPage();
return { browser, context, page };
}
A common mistake is to open multiple pages inside one context and treat them as separate accounts. They are not separate — they share cookies and storage. Always use one context per account, and ideally one browser process per account so that crashes stay isolated.
Step 2: Add Residential Proxies

This is the single most important part. Datacenter proxies are cheap, but platforms maintain lists of known datacenter IP ranges and flag them instantly. Residential proxies route traffic through real ISP-assigned home connections, so each session looks like an ordinary household visitor.
Playwright accepts proxy configuration directly at the context level:
const accountConfig = {
proxy: {
server: 'http://proxy-host:port',
username: 'your-proxy-user',
password: 'your-proxy-pass',
},
// ... fingerprint fields
};
Three rules matter when assigning proxies:
One IP per account, kept consistent. If account A logs in from a New York IP one day and a Berlin IP the next, that is a red flag. Use sticky sessions so each account keeps the same IP across runs.
Match the IP geography to the account. A profile that claims to be in Spain but connects from a Vietnamese IP is an obvious mismatch. The proxy location, the browser locale, and the timezone all need to agree.
Do not over-rotate. Rotating IPs mid-session is for scraping public pages, not for account management. Logged-in sessions should look stable.
For testing your setup, providers like NodeMaven, IPRoyal, or SOAX offer residential pools with sticky-session support. Whichever you choose, verify it lets you hold an IP for the length of a working session.
Step 3: Build a Consistent Fingerprint Per Account
Here is where most DIY setups quietly fail. Playwright lets you set the user agent, viewport, locale, and timezone — but websites read far more than that. Canvas fingerprinting, WebGL vendor and renderer strings, AudioContext, the navigator object, font enumeration, and WebRTC all need to be consistent and believable.
You can patch some of these manually with addInitScript:
await context.addInitScript(() => {
Object.defineProperty(navigator, 'hardwareConcurrency', {
get: () => 8,
});
Object.defineProperty(navigator, 'deviceMemory', {
get: () => 8,
});
});
But doing this thoroughly is a serious project. You have to spoof canvas without producing an impossible canvas, keep the WebGL strings matched to a real GPU, block WebRTC from leaking the true IP, and — critically — make sure the same fingerprint reappears every single time that account runs. An account whose fingerprint changes on every login looks more suspicious than one that never hid at all.
This is the point where many developers stop hand-rolling and move to a purpose-built tool.
A More Reliable Approach: Antidetect Browsers with Automation Support
Rather than patching dozens of APIs by hand, an antidetect browser generates a complete, internally consistent fingerprint for each profile and stores it so the profile reopens identically every time. The profiles are real Chromium environments, so Playwright and Puppeteer can still drive them.
BitBrowser is one option that exposes a local automation API. You create a profile in the app, assign its proxy, and then connect Playwright over the Chrome DevTools Protocol endpoint the API returns:
const axios = require('axios');
const { chromium } = require('playwright');
async function launchProfile(profileId) {
// Ask the local API to open the profile
const res = await axios.post('http://127.0.0.1:54345/browser/open', {
id: profileId,
});
const wsEndpoint = res.data.data.ws;
// Connect Playwright to the already-running browser
const browser = await chromium.connectOverCDP(wsEndpoint);
const context = browser.contexts()[0];
const page = context.pages()[0] || await context.newPage();
return { browser, context, page };
}
The advantage is that fingerprint consistency, proxy binding, and profile storage are handled for you, while you keep full Playwright scripting on top. You write automation logic; the browser handles the identity layer.
Step 4: Handle Mobile-First Platforms
Some platforms — short-video apps, certain marketplaces, messaging services — barely tolerate desktop browser access and expect a real mobile device. A desktop fingerprint, however perfect, cannot fully imitate a phone's sensor data and app environment.
For those cases a cloud phone runs a genuine Android instance in the cloud, each with its own hardware identifiers. You manage it remotely, but to the platform it is an ordinary mobile device. It is the natural complement to a desktop antidetect setup when your accounts live on mobile-first platforms.
Step 5: Make the Automation Behave Like a Human
Even with perfect isolation, robotic behavior gives you away. Build these habits into every script:
Randomize timing. Never click instantly. Insert variable delays between actions.
const wait = (min, max) =>
new Promise(r => setTimeout(r, min + Math.random() * (max - min)));
await page.click('#login');
await wait(800, 2500);
Type, do not paste. Use page.type() with a per-character delay rather than filling the value in one shot.
Stagger your accounts. Running 30 accounts in a synchronized burst at 09:00 produces an obvious machine pattern. Spread activity across the day.
Limit concurrency. Each isolated browser eats real RAM. Run accounts in small batches and queue the rest rather than launching everything at once.
Putting It Together
A production multi-account automation system has a clear shape:
- A profile store holding each account's fingerprint, proxy, and credentials.
- A launcher that opens an isolated browser per account and connects Playwright.
- Task scripts containing the per-account automation logic.
- A scheduler that staggers runs and caps concurrency.
- Health checks that detect CAPTCHAs, logouts, and verification prompts early.
The architectural principle is separation of concerns. Your Playwright scripts should never care about how identity isolation works — they just receive a clean, connected page. Whether that isolation comes from hand-patched contexts or from an antidetect browser with an API, the automation layer stays the same. That separation is what lets the system scale from five accounts to fifty without a rewrite.
Final Notes
Multi-account browser automation is a solvable engineering problem, but only when you respect both detection layers. Isolated contexts alone are not enough. Residential proxies alone are not enough. Fingerprint spoofing alone is not enough. The three have to work together, consistently, for every account, on every run.
Start small. Get two accounts running cleanly with full isolation before you scale. Verify each profile against a fingerprinting test page, confirm the IP geography matches the locale and timezone, and only then add more accounts to the rotation.
Finally, stay inside the terms of service of whatever platforms you work with. The techniques here are about legitimate session isolation — QA across environments, ad verification, price monitoring, and managing business accounts you are entitled to operate. Used that way, a Playwright-plus-residential-proxy stack is a reliable, maintainable foundation.





Top comments (0)