If you've ever had to QA a web app across different "identities" different locales, timezones, screen sizes, or user-agents. You already know how painful it is to do this by hand. Spinning up a fresh Chrome window, manually changing settings, clearing cookies, repeating for every combination... it doesn't scale.
This post walks through a lightweight way to automate isolated browser profiles with Puppeteer, each with its own fingerprint-relevant configuration (timezone, locale, viewport, user-agent), so you can run repeatable, parallel test sessions without profiles bleeding into each other.
Why this matters beyond QA
Multi-profile isolation isn't just a testing nicety. Teams running multiple legitimate accounts for the same platform, agencies managing separate client ad accounts, e-commerce sellers with several storefronts, researchers doing cross-region UX audits — run into the same core problem: browser state (cookies, cache, local storage, fingerprint signals) leaks across sessions unless you deliberately isolate it.
The pattern below is the same whether you're writing automated tests or just want a clean, repeatable way to keep sessions separate.
Setting up isolated launch contexts
Each profile gets its own userDataDir, so cookies, local storage, and cache never mix:
const puppeteer = require('puppeteer');
async function launchProfile(profileName, config) {
const browser = await puppeteer.launch({
headless: false,
userDataDir: `./profiles/${profileName}`,
args: [
`--window-size=${config.viewport.width},${config.viewport.height}`,
],
});
const page = await browser.newPage();
await page.setUserAgent(config.userAgent);
await page.setViewport(config.viewport);
await page.emulateTimezone(config.timezone);
return { browser, page };
}
Defining profile configs
Keep configs declarative so you can add new profiles without touching the launch logic:
const profiles = {
agencyClientA: {
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
viewport: { width: 1366, height: 768 },
timezone: 'America/New_York',
locale: 'en-US',
},
agencyClientB: {
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
viewport: { width: 1440, height: 900 },
timezone: 'Europe/London',
locale: 'en-GB',
},
};
Running sessions in parallel
Because each profile has its own userDataDir and browser instance, you can run them concurrently without cross-contamination:
async function runAll() {
const sessions = await Promise.all(
Object.entries(profiles).map(([name, config]) =>
launchProfile(name, config)
)
);
for (const { page } of sessions) {
await page.goto('https://example.com');
// your test/automation logic here
}
// Clean up
for (const { browser } of sessions) {
await browser.close();
}
}
runAll();
A few things worth knowing
-
userDataDirisolation is the foundation. Without it, Chrome will happily share cookies and local storage across "different" sessions launched from the same default profile. - Puppeteer alone won't fully normalize every fingerprint signal things like canvas rendering, WebGL parameters, and audio context fingerprints require deeper patching than what's shown here. If that level of consistency matters for your use case (large-scale QA across many simulated environments, for example), dedicated profile-management tooling handles this more robustly than hand-rolled scripts.
- Don't over-engineer this for simple test suites. If you just need 2-3 consistent test personas, the setup above is enough. Reach for something heavier only when you're managing dozens of isolated sessions.
Wrapping up
Isolated, config-driven browser profiles make cross-environment testing (and any workflow requiring genuinely separate browser identities) far more reliable than manually juggling incognito windows. The userDataDir + per-page config pattern above is a solid starting point from there it's mostly a matter of scaling out your config list.
Have you built something similar for your own testing pipeline? Curious what edge cases you've run into with fingerprint consistency across headless vs headful runs.
Top comments (0)