← Blog · · 5 min read

How to Use PacketStream Residential Proxies with Playwright

Playwright is useful when a public page needs a real browser: client-rendered content, browser APIs, or interactions that a direct HTTP request cannot reproduce. It can route browser traffic through an authenticated HTTP(S) proxy at the browser or context level, making it a practical match for PacketStream’s residential proxy network.

Use browser automation only where you have permission, follow the destination’s terms, and keep request rates proportionate. If a direct HTTP client can collect the permitted data reliably, it will usually use fewer resources than a full browser.

Install Playwright

Create a small Node.js project and install Playwright with Chromium:

mkdir packetstream-playwright
cd packetstream-playwright
npm init -y
npm install playwright
npx playwright install chromium

Playwright’s current proxy documentation supports HTTP(S) proxy configuration with an optional username and password. PacketStream recommends its HTTPS endpoint on port 31111.

Place credentials in environment variables rather than source code:

export PACKETSTREAM_USER='your_username'
export PACKETSTREAM_AUTH_KEY='your_auth_key'

Keep .env files, trace archives, and debug logs out of version control when they can contain secrets.

Launch Chromium through PacketStream

Create check-proxy.mjs:

import { chromium } from 'playwright';

const username = process.env.PACKETSTREAM_USER;
const password = process.env.PACKETSTREAM_AUTH_KEY;

if (!username || !password) {
  throw new Error('Set PACKETSTREAM_USER and PACKETSTREAM_AUTH_KEY');
}

const browser = await chromium.launch({
  headless: true,
  proxy: {
    server: 'https://proxy.packetstream.io:31111',
    username,
    password,
  },
});

try {
  const page = await browser.newPage();
  await page.goto('https://ipinfo.io', {
    waitUntil: 'domcontentloaded',
    timeout: 30_000,
  });

  const exit = JSON.parse(await page.locator('body').innerText());
  console.log({
    ip: exit.ip,
    city: exit.city,
    region: exit.region,
    country: exit.country,
    org: exit.org,
    timezone: exit.timezone,
  });
} finally {
  await browser.close();
}

Run it:

node check-proxy.mjs

The response should describe the residential exit seen by the destination. IP geography is approximate, so use it as a diagnostic signal. Country is the supported targeting boundary.

Target a country

Append _country- and a two-letter ISO country code to the auth key. The proxy endpoint does not change:

const requestedCountry = 'US';

const browser = await chromium.launch({
  proxy: {
    server: 'https://proxy.packetstream.io:31111',
    username: process.env.PACKETSTREAM_USER,
    password: `${process.env.PACKETSTREAM_AUTH_KEY}_country-${requestedCountry}`,
  },
});

Verify country === requestedCountry in a preflight request before beginning a location-sensitive job. When a country has no exits available, PacketStream fails the request rather than falling back to a different country. Treat that as unavailable work, not permission to collect the wrong regional result.

Language, currency, and content can also depend on cookies, account state, browser locale, or the requested URL. An exit country alone does not guarantee that every page will show the same localized experience.

Understand rotation and browser connection reuse

Automatic rotation selects a residential exit for each new proxy connection. A browser may reuse connections across navigation and resource requests, so “new page” does not necessarily mean “new proxy connection” or “new exit.”

Design around a unit of work instead of trying to force an IP change between arbitrary browser actions. For independent jobs, close the browser or context according to your isolation needs, then verify the observed behavior under realistic load. Avoid launching a new browser for every small asset request; that defeats connection pooling and adds substantial overhead.

When several browser actions need the same exit, add a stable session ID to the auth key:

const sessionId = 'checkout-review-42';
const sessionPassword =
  `${process.env.PACKETSTREAM_AUTH_KEY}_country-US_session-${sessionId}`;

const browser = await chromium.launch({
  proxy: {
    server: 'https://proxy.packetstream.io:31111',
    username: process.env.PACKETSTREAM_USER,
    password: sessionPassword,
  },
});

PacketStream sticky sessions are available to all customers and can retain the same exit for up to 60 minutes. If the associated Packeter disconnects, the session fails. Do not silently continue a stateful workflow through a different identity. Stop it, choose a new session ID, and restart from a known-safe checkpoint.

Use random, non-sensitive session IDs. Do not put customer identifiers, email addresses, or credentials in the session string.

Apply timeouts at the workflow level

A page contains many requests. One navigation timeout does not automatically give the entire job a finite budget. Set explicit limits for navigation, individual operations, and the overall workflow.

const context = await browser.newContext();
context.setDefaultTimeout(15_000);
context.setDefaultNavigationTimeout(30_000);

const page = await context.newPage();
await page.goto('https://example.com/public-page', {
  waitUntil: 'domcontentloaded',
});

networkidle can wait indefinitely on pages with polling or analytics traffic. Prefer a page-specific readiness condition such as a visible heading, a stable data element, or a documented application response.

Reduce unnecessary transfer carefully

Browser automation can load images, fonts, video, analytics, and background requests. Blocking resources may reduce bandwidth, but it can also prevent the page from rendering or trigger different behavior.

If the workflow does not need images, test a narrow route rule:

await page.route('**/*', async route => {
  const type = route.request().resourceType();
  if (type === 'image' || type === 'media' || type === 'font') {
    await route.abort();
    return;
  }
  await route.continue();
});

Compare parsed output before and after blocking. The bandwidth estimation guide explains how to measure the full browser request set before scaling.

Keep failures observable

Record which stage failed without logging credentials:

  • browser launch or proxy authentication;
  • initial exit verification;
  • country assertion;
  • navigation;
  • page readiness;
  • extraction or validation;
  • sticky-session continuity.

Save a screenshot, trace, or HTML snapshot only when permitted, redact sensitive values, and apply retention limits. Use bounded retries with backoff for transient failures; do not retry authentication failures or deterministic parser errors.

Start with a bounded pilot

Before running many browsers, test the complete workflow on a small, representative set. Confirm the proxy endpoint, exit country, connection behavior, parsing rules, resource policy, retry limits, and dashboard usage.

That pilot turns a short configuration example into a dependable browser integration—one where proxy behavior is explicit and failures are safe to diagnose.