Playwright — per-context proxy
Set the proxy at browser.newContext(). Each context can have its own proxy, which is the cleanest way to fan out across many sessions without juggling multiple browsers.
import { chromium } from "playwright";
const browser = await chromium.launch({ headless: true });
const ctx = await browser.newContext({
proxy: {
server: "http://gw.justproxies.online:8080",
username: "USER",
password: "PASS",
},
});
const page = await ctx.newPage();
await page.goto("https://api.ipify.org");
console.log(await page.textContent("body"));
await browser.close();
Playwright — fixture per worker
For test runners or batch jobs, parameterise the context fixture with a sticky session token derived from the worker index — every worker gets its own IP, every test gets repeatable affinity.
import { test as base, chromium } from "@playwright/test";
export const test = base.extend({
context: async ({}, use, testInfo) => {
const session = `worker-${testInfo.workerIndex}-${testInfo.testId.slice(0,6)}`;
const browser = await chromium.launch();
const ctx = await browser.newContext({
proxy: {
server: "http://gw.justproxies.online:8080",
username: `USER-session-${session}`,
password: "PASS",
},
});
await use(ctx);
await browser.close();
},
});
Puppeteer — launch arg
Puppeteer takes the proxy at the --proxy-server launch flag. The flag is browser-wide; for parallelism, launch multiple browsers.
import puppeteer from "puppeteer";
const browser = await puppeteer.launch({
args: ["--proxy-server=http://gw.justproxies.online:8080"],
});
const page = await browser.newPage();
await page.authenticate({ username: "USER", password: "PASS" });
await page.goto("https://api.ipify.org");
console.log(await page.evaluate(() => document.body.innerText));
await browser.close();
Puppeteer — proxy auth
Puppeteer doesn't accept inline credentials in the launch arg. Call page.authenticate() per page, before the first navigation.
page.authenticate() must run before any page.goto() — the credentials apply to the next request the page makes.Mobile profile + mobile pool
For full carrier-NAT realism: set the browser to a mobile device emulation and route through the mobile pool. The two operate independently — emulation sets headers/touch/viewport, the proxy sets the egress IP.
import { chromium, devices } from "playwright";
const browser = await chromium.launch();
const ctx = await browser.newContext({
...devices["Pixel 7"],
proxy: {
server: "http://gw.justproxies.online:8080",
username: "MOBILE_USER-session-S1",
password: "MOBILE_PASS",
},
});
const page = await ctx.newPage();
await page.goto("https://m.target.com/");
// Mobile UA, mobile viewport, mobile carrier NAT egress.
Stealth and fingerprinting
We don't bundle a stealth plugin or rewrite browser fingerprints — that is target-specific work and changes faster than we can ship. The two pieces we recommend:
- Pool selection — pick the right pool for the target. See /products for the trust-score breakdown.
- Per-context isolation — never reuse a context across sessions you want isolated. Storage state, cookies and cache leak between.
For fingerprint patching specifically, the open-source puppeteer-extra-plugin-stealth and the Playwright equivalents are the usual starting points.