# Ludax proxies in Playwright: guide for AI assistants

> Give this file to your AI assistant (ChatGPT, Claude, Cursor, Copilot) and it can write working Playwright code for Ludax Proxy in Node.js, Python, Java or .NET. Human-readable version: https://ludaxproxy.com/docs/playwright

You are helping a developer route Playwright browsers through Ludax residential proxies. Playwright logs in to the proxy itself: no extension, no local forwarder, no SDK. Follow the rules below exactly; they come from the gateways' behaviour and from tests with Playwright 1.64.

## Rules

- Pass the proxy as `{ server, username, password }`: server `http://<gateway>` (for example `gw.ludaxproxy.com:7777`), username `resi-lx_k3m9q2p7xa`, password `YOUR_PASSWORD` plus options.
- The proxy goes in `chromium.launch()` for the whole browser, or in `browser.newContext()` for one context. A context's proxy needs no proxy at launch.
- **Give each context its own session token** (`-session-<TOKEN>-time-<MINUTES>`). A browser opens many connections per page; without a token each new connection can leave from a new IP. One token per context keeps one IP for everything that context loads; a new context with a new token is how to rotate.
- Use the standard HTTP gateways (Residential `gw.ludaxproxy.com:7777`, Premium `gw.ludaxproxy.com:8080`). Do **not** use the TLS gateways with a browser: Chromium already sends a real browser handshake, and the TLS gateways would need their CA installed.
- Playwright's username and password apply to HTTP proxies: use the HTTP gateway (it carries HTTPS too), not SOCKS5, when a login is needed.
- Match the browser to the exit: `locale` and `timezoneId` on the context (for example `de-DE` and `Europe/Berlin` with `-country-de`).
- Save bandwidth (plans are priced per GB): abort `image`, `font` and `media` requests with `context.route`.
- Errors in Chromium: `net::ERR_TUNNEL_CONNECTION_FAILED` is a gateway 502 (retry with backoff); `net::ERR_HTTP_RESPONSE_CODE_FAILURE` is a refused login (fix the credentials, never loop: 30 failed logins in 5 minutes block the client IP for 10 minutes); `net::ERR_PROXY_CONNECTION_FAILED` means the gateway was not reached.

## Credentials

- **Username:** the product prefix (`resi-` or `premi-`), then the developer's account name from the dashboard (Generate), for example `resi-lx_k3m9q2p7xa`. The account name is `lx_` and 10 characters, the same for every plan. Never invent one: ask the developer, or use a placeholder such as `YOUR_USERNAME`.
- **Password:** the plan's own password (16 hexadecimal characters, one per plan), then the options, in this order: `-session-<TOKEN>-time-<MINUTES>`, then `-country-<cc>`, `-state-<code>`, `-city-<name>` (and `-asn-<number>`, `-os-<device>` on Premium). Use a placeholder such as `YOUR_PASSWORD`, or read it from an environment variable. Never put options in the username.
- **Sessions:** without `-session-` every new connection leaves from a new IP. `-session-<TOKEN>-time-<MINUTES>` keeps one IP for every connection that sends the same token (TOKEN: 3 to 24 letters or digits; MINUTES: 1 to 1000; Premium holds a session at least 3 minutes). Always send `-time-`: without it a session lasts 1 minute. On the TLS gateways add `-sameip` after the time, or only the browser profile is held.
- **Location:** `-country-` takes a lower-case ISO code (`us`, `de`, `jp`); `-state-` and `-city-` follow it (`-country-de-state-by-city-munich`); on Premium the state is its name without spaces (`-country-us-state-illinois-city-chicago`), in any of 219 countries. `-os-` (Premium) takes `windows`, `android`, `ios` or `macos`. With no free exit in that city or region the request falls back to the country. Codes for every country: https://ludaxproxy.com/locations
- **The port picks the product:** a plan's password works only on its own product's gateway, and the prefix must match it.

## Products and gateways in force

| Product | Username prefix | HTTP and HTTPS | SOCKS5 | Browser TLS | Sticky session |
| --- | --- | --- | --- | --- | --- |
| Residential | `resi-` | `gw.ludaxproxy.com:7777` | `gw.ludaxproxy.com:7575` | No | up to 1000 min |
| Residential TLS | `resi-` | `gw.ludaxproxy.com:9999` | none | Yes | up to 1000 min |
| Residential Premium | `premi-` | `gw.ludaxproxy.com:8080` | `gw.ludaxproxy.com:1080` | No | up to 1000 min |
| Residential Premium TLS | `premi-` | `gw.ludaxproxy.com:8081` | none | Yes | up to 1000 min |

## Check the connection

`GET https://api.ludaxproxy.com/whoami` through the proxy returns JSON: `ip`, `country` (full name), `type`, `tls_profile` (the browser the TLS handshake matches) and `latency_ms`. Add `?full=1` for a `debug` block with the JA4 fingerprint, the HTTP/2 fingerprint, the ASN and the User-Agent that arrived.

## Tested examples

The same code as on https://ludaxproxy.com/docs/playwright, in four languages. Replace `lx_k3m9q2p7xa` and `YOUR_PASSWORD` with the developer's credentials.

### First request

**Node.js** (`first_request.mjs`)

```javascript
import { chromium } from "playwright";

const browser = await chromium.launch({
  proxy: {
    server: "http://gw.ludaxproxy.com:7777",
    username: "resi-lx_k3m9q2p7xa",
    password: "YOUR_PASSWORD-country-us",
  },
});
const page = await browser.newPage();
await page.goto("https://api.ludaxproxy.com/whoami");
console.log(await page.textContent("body"));
await browser.close();
```

**Python** (`first_request.py`)

```python
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(proxy={
        "server": "http://gw.ludaxproxy.com:7777",
        "username": "resi-lx_k3m9q2p7xa",
        "password": "YOUR_PASSWORD-country-us",
    })
    page = browser.new_page()
    page.goto("https://api.ludaxproxy.com/whoami")
    print(page.text_content("body"))
    browser.close()
```

**Java** (`FirstRequest.java`)

```java
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.Proxy;

public class FirstRequest {
  public static void main(String[] args) {
    try (Playwright playwright = Playwright.create()) {
      Browser browser = playwright.chromium().launch(new BrowserType.LaunchOptions()
          .setProxy(new Proxy("http://gw.ludaxproxy.com:7777")
              .setUsername("resi-lx_k3m9q2p7xa")
              .setPassword("YOUR_PASSWORD-country-us")));
      Page page = browser.newPage();
      page.navigate("https://api.ludaxproxy.com/whoami");
      System.out.println(page.textContent("body"));
    }
  }
}
```

**.NET (C#)** (`FirstRequest.cs`)

```csharp
using Microsoft.Playwright;

using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.Chromium.LaunchAsync(new()
{
    Proxy = new Proxy
    {
        Server = "http://gw.ludaxproxy.com:7777",
        Username = "resi-lx_k3m9q2p7xa",
        Password = "YOUR_PASSWORD-country-us",
    },
});
var page = await browser.NewPageAsync();
await page.GotoAsync("https://api.ludaxproxy.com/whoami");
Console.WriteLine(await page.TextContentAsync("body"));
```

### One IP per browser context (rotation by context)

**Node.js** (`contexts.mjs`)

```javascript
import { chromium } from "playwright";
import { randomBytes } from "node:crypto";

const GATEWAY = "http://gw.ludaxproxy.com:7777";
const USER = "resi-lx_k3m9q2p7xa";
const PASS = "YOUR_PASSWORD";

const browser = await chromium.launch();
for (const job of ["account-1", "account-2", "account-3"]) {
  // a new session token = a new IP, held by this context for 30 minutes
  const token = randomBytes(4).toString("hex");
  const context = await browser.newContext({
    proxy: { server: GATEWAY, username: USER, password: `${PASS}-session-${token}-time-30-country-us` },
  });
  const page = await context.newPage();
  await page.goto("https://api.ludaxproxy.com/whoami");
  console.log(job, JSON.parse(await page.textContent("body")).ip);
  await context.close();
}
await browser.close();
```

**Python** (`contexts.py`)

```python
import json
import secrets
from playwright.sync_api import sync_playwright

GATEWAY = "http://gw.ludaxproxy.com:7777"
USER = "resi-lx_k3m9q2p7xa"
PASS = "YOUR_PASSWORD"

with sync_playwright() as p:
    browser = p.chromium.launch()
    for job in ["account-1", "account-2", "account-3"]:
        # a new session token = a new IP, held by this context for 30 minutes
        token = secrets.token_hex(4)
        context = browser.new_context(proxy={
            "server": GATEWAY,
            "username": USER,
            "password": f"{PASS}-session-{token}-time-30-country-us",
        })
        page = context.new_page()
        page.goto("https://api.ludaxproxy.com/whoami")
        print(job, json.loads(page.text_content("body"))["ip"])
        context.close()
    browser.close()
```

**Java** (`Contexts.java`)

```java
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.Proxy;
import java.security.SecureRandom;
import java.util.HexFormat;

public class Contexts {
  public static void main(String[] args) {
    SecureRandom random = new SecureRandom();
    try (Playwright playwright = Playwright.create()) {
      Browser browser = playwright.chromium().launch();
      for (String job : new String[] {"account-1", "account-2", "account-3"}) {
        // a new session token = a new IP, held by this context for 30 minutes
        byte[] bytes = new byte[4];
        random.nextBytes(bytes);
        String token = HexFormat.of().formatHex(bytes);
        BrowserContext context = browser.newContext(new Browser.NewContextOptions()
            .setProxy(new Proxy("http://gw.ludaxproxy.com:7777")
                .setUsername("resi-lx_k3m9q2p7xa")
                .setPassword("YOUR_PASSWORD-session-" + token + "-time-30-country-us")));
        Page page = context.newPage();
        page.navigate("https://api.ludaxproxy.com/whoami");
        System.out.println(job + " " + page.textContent("body"));
        context.close();
      }
    }
  }
}
```

**.NET (C#)** (`Contexts.cs`)

```csharp
using System.Security.Cryptography;
using Microsoft.Playwright;

using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.Chromium.LaunchAsync();
foreach (var job in new[] { "account-1", "account-2", "account-3" })
{
    // a new session token = a new IP, held by this context for 30 minutes
    var token = Convert.ToHexString(RandomNumberGenerator.GetBytes(4)).ToLowerInvariant();
    await using var context = await browser.NewContextAsync(new()
    {
        Proxy = new Proxy
        {
            Server = "http://gw.ludaxproxy.com:7777",
            Username = "resi-lx_k3m9q2p7xa",
            Password = $"YOUR_PASSWORD-session-{token}-time-30-country-us",
        },
    });
    var page = await context.NewPageAsync();
    await page.GotoAsync("https://api.ludaxproxy.com/whoami");
    Console.WriteLine($"{job} {await page.TextContentAsync("body")}");
}
```

### Geo-targeting with a matching locale and time zone

**Node.js** (`geo.mjs`)

```javascript
import { chromium } from "playwright";

const browser = await chromium.launch();
// an IP in Munich, and a browser that looks local: German language, Berlin time
const context = await browser.newContext({
  proxy: {
    server: "http://gw.ludaxproxy.com:7777",
    username: "resi-lx_k3m9q2p7xa",
    password: "YOUR_PASSWORD-session-muc01-time-30-country-de-state-by-city-munich",
  },
  locale: "de-DE",
  timezoneId: "Europe/Berlin",
});
const page = await context.newPage();
await page.goto("https://api.ludaxproxy.com/whoami");
console.log(await page.textContent("body"));
console.log(await page.evaluate(() => [navigator.language, Intl.DateTimeFormat().resolvedOptions().timeZone]));
await browser.close();
```

**Python** (`geo.py`)

```python
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    # an IP in Munich, and a browser that looks local: German language, Berlin time
    context = browser.new_context(
        proxy={
            "server": "http://gw.ludaxproxy.com:7777",
            "username": "resi-lx_k3m9q2p7xa",
            "password": "YOUR_PASSWORD-session-muc01-time-30-country-de-state-by-city-munich",
        },
        locale="de-DE",
        timezone_id="Europe/Berlin",
    )
    page = context.new_page()
    page.goto("https://api.ludaxproxy.com/whoami")
    print(page.text_content("body"))
    print(page.evaluate("() => [navigator.language, Intl.DateTimeFormat().resolvedOptions().timeZone]"))
    browser.close()
```

**Java** (`Geo.java`)

```java
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.Proxy;

public class Geo {
  public static void main(String[] args) {
    try (Playwright playwright = Playwright.create()) {
      Browser browser = playwright.chromium().launch();
      // an IP in Munich, and a browser that looks local: German language, Berlin time
      BrowserContext context = browser.newContext(new Browser.NewContextOptions()
          .setProxy(new Proxy("http://gw.ludaxproxy.com:7777")
              .setUsername("resi-lx_k3m9q2p7xa")
              .setPassword("YOUR_PASSWORD-session-muc01-time-30-country-de-state-by-city-munich"))
          .setLocale("de-DE")
          .setTimezoneId("Europe/Berlin"));
      Page page = context.newPage();
      page.navigate("https://api.ludaxproxy.com/whoami");
      System.out.println(page.textContent("body"));
      System.out.println(page.evaluate("() => [navigator.language, Intl.DateTimeFormat().resolvedOptions().timeZone]"));
    }
  }
}
```

**.NET (C#)** (`Geo.cs`)

```csharp
using Microsoft.Playwright;

using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.Chromium.LaunchAsync();
// an IP in Munich, and a browser that looks local: German language, Berlin time
var context = await browser.NewContextAsync(new()
{
    Proxy = new Proxy
    {
        Server = "http://gw.ludaxproxy.com:7777",
        Username = "resi-lx_k3m9q2p7xa",
        Password = "YOUR_PASSWORD-session-muc01-time-30-country-de-state-by-city-munich",
    },
    Locale = "de-DE",
    TimezoneId = "Europe/Berlin",
});
var page = await context.NewPageAsync();
await page.GotoAsync("https://api.ludaxproxy.com/whoami");
Console.WriteLine(await page.TextContentAsync("body"));
Console.WriteLine(await page.EvaluateAsync<string>("() => navigator.language + ' ' + Intl.DateTimeFormat().resolvedOptions().timeZone"));
```

### Block images, fonts and media to save bandwidth

**Node.js** (`save_data.mjs`)

```javascript
import { chromium } from "playwright";

const SKIP = new Set(["image", "font", "media"]);

const browser = await chromium.launch({
  proxy: {
    server: "http://gw.ludaxproxy.com:7777",
    username: "resi-lx_k3m9q2p7xa",
    password: "YOUR_PASSWORD-session-wiki01-time-10-country-us",
  },
});
const context = await browser.newContext();
// images, fonts and video never leave the browser, so they never count against your GB
await context.route("**/*", (route) =>
  SKIP.has(route.request().resourceType()) ? route.abort() : route.continue(),
);
const page = await context.newPage();
await page.goto("https://en.wikipedia.org/wiki/Proxy_server");
console.log(await page.title());
await browser.close();
```

**Python** (`save_data.py`)

```python
from playwright.sync_api import sync_playwright

SKIP = {"image", "font", "media"}

with sync_playwright() as p:
    browser = p.chromium.launch(proxy={
        "server": "http://gw.ludaxproxy.com:7777",
        "username": "resi-lx_k3m9q2p7xa",
        "password": "YOUR_PASSWORD-session-wiki01-time-10-country-us",
    })
    context = browser.new_context()
    # images, fonts and video never leave the browser, so they never count against your GB
    context.route("**/*", lambda route: route.abort()
                  if route.request.resource_type in SKIP else route.continue_())
    page = context.new_page()
    page.goto("https://en.wikipedia.org/wiki/Proxy_server")
    print(page.title())
    browser.close()
```

**Java** (`SaveData.java`)

```java
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.Proxy;
import java.util.Set;

public class SaveData {
  public static void main(String[] args) {
    Set<String> skip = Set.of("image", "font", "media");
    try (Playwright playwright = Playwright.create()) {
      Browser browser = playwright.chromium().launch(new BrowserType.LaunchOptions()
          .setProxy(new Proxy("http://gw.ludaxproxy.com:7777")
              .setUsername("resi-lx_k3m9q2p7xa")
              .setPassword("YOUR_PASSWORD-session-wiki01-time-10-country-us")));
      BrowserContext context = browser.newContext();
      // images, fonts and video never leave the browser, so they never count against your GB
      context.route("**/*", route -> {
        if (skip.contains(route.request().resourceType())) route.abort();
        else route.resume();
      });
      Page page = context.newPage();
      page.navigate("https://en.wikipedia.org/wiki/Proxy_server");
      System.out.println(page.title());
    }
  }
}
```

**.NET (C#)** (`SaveData.cs`)

```csharp
using Microsoft.Playwright;

var skip = new HashSet<string> { "image", "font", "media" };

using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.Chromium.LaunchAsync(new()
{
    Proxy = new Proxy
    {
        Server = "http://gw.ludaxproxy.com:7777",
        Username = "resi-lx_k3m9q2p7xa",
        Password = "YOUR_PASSWORD-session-wiki01-time-10-country-us",
    },
});
var context = await browser.NewContextAsync();
// images, fonts and video never leave the browser, so they never count against your GB
await context.RouteAsync("**/*", async route =>
{
    if (skip.Contains(route.Request.ResourceType)) await route.AbortAsync();
    else await route.ContinueAsync();
});
var page = await context.NewPageAsync();
await page.GotoAsync("https://en.wikipedia.org/wiki/Proxy_server");
Console.WriteLine(await page.TitleAsync());
```

### Retry a 502, stop on a refused login

**Node.js** (`retry.mjs`)

```javascript
import { chromium } from "playwright";

// Open a URL through Ludax: retry a dropped exit (502), stop at once on anything else.
async function open(page, url, tries = 3) {
  for (let attempt = 1; ; attempt++) {
    try {
      return await page.goto(url, { timeout: 30000 });
    } catch (error) {
      const dropped = error.message.includes("ERR_TUNNEL_CONNECTION_FAILED");
      if (!dropped || attempt === tries) throw error; // a refused login will not fix itself
      await new Promise((resolve) => setTimeout(resolve, 1000 * 2 ** (attempt - 1)));
    }
  }
}

const browser = await chromium.launch({
  proxy: {
    server: "http://gw.ludaxproxy.com:7777",
    username: "resi-lx_k3m9q2p7xa",
    password: "YOUR_PASSWORD-country-us",
  },
});
const page = await browser.newPage();
await open(page, "https://api.ludaxproxy.com/whoami");
console.log(await page.textContent("body"));
await browser.close();
```

**Python** (`retry.py`)

```python
import time
from playwright.sync_api import sync_playwright, Error


def open_page(page, url, tries=3):
    """Open a URL through Ludax: retry a dropped exit (502), stop at once on anything else."""
    for attempt in range(1, tries + 1):
        try:
            return page.goto(url, timeout=30000)
        except Error as error:
            dropped = "ERR_TUNNEL_CONNECTION_FAILED" in error.message
            if not dropped or attempt == tries:
                raise  # a refused login will not fix itself
            time.sleep(2 ** (attempt - 1))


with sync_playwright() as p:
    browser = p.chromium.launch(proxy={
        "server": "http://gw.ludaxproxy.com:7777",
        "username": "resi-lx_k3m9q2p7xa",
        "password": "YOUR_PASSWORD-country-us",
    })
    page = browser.new_page()
    open_page(page, "https://api.ludaxproxy.com/whoami")
    print(page.text_content("body"))
    browser.close()
```

**Java** (`Retry.java`)

```java
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.Proxy;

public class Retry {
  // Open a URL through Ludax: retry a dropped exit (502), stop at once on anything else.
  static Response open(Page page, String url, int tries) throws InterruptedException {
    for (int attempt = 1; ; attempt++) {
      try {
        return page.navigate(url);
      } catch (PlaywrightException error) {
        boolean dropped = error.getMessage().contains("ERR_TUNNEL_CONNECTION_FAILED");
        if (!dropped || attempt == tries) throw error; // a refused login will not fix itself
        Thread.sleep(1000L << (attempt - 1));
      }
    }
  }

  public static void main(String[] args) throws InterruptedException {
    try (Playwright playwright = Playwright.create()) {
      Browser browser = playwright.chromium().launch(new BrowserType.LaunchOptions()
          .setProxy(new Proxy("http://gw.ludaxproxy.com:7777")
              .setUsername("resi-lx_k3m9q2p7xa")
              .setPassword("YOUR_PASSWORD-country-us")));
      Page page = browser.newPage();
      open(page, "https://api.ludaxproxy.com/whoami", 3);
      System.out.println(page.textContent("body"));
    }
  }
}
```

**.NET (C#)** (`Retry.cs`)

```csharp
using Microsoft.Playwright;

using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.Chromium.LaunchAsync(new()
{
    Proxy = new Proxy
    {
        Server = "http://gw.ludaxproxy.com:7777",
        Username = "resi-lx_k3m9q2p7xa",
        Password = "YOUR_PASSWORD-country-us",
    },
});
var page = await browser.NewPageAsync();
await Open(page, "https://api.ludaxproxy.com/whoami");
Console.WriteLine(await page.TextContentAsync("body"));

// Open a URL through Ludax: retry a dropped exit (502), stop at once on anything else.
static async Task<IResponse?> Open(IPage page, string url, int tries = 3)
{
    for (var attempt = 1; ; attempt++)
    {
        try
        {
            return await page.GotoAsync(url);
        }
        catch (PlaywrightException error) when (
            error.Message.Contains("ERR_TUNNEL_CONNECTION_FAILED") && attempt < tries)
        {
            await Task.Delay(1000 * (1 << (attempt - 1))); // a refused login is not caught: it will not fix itself
        }
    }
}
```

### Playwright Test: a proxy for the whole run

```typescript
import { defineConfig } from "@playwright/test";

export default defineConfig({
  use: {
    // every test in the run uses one US IP for 60 minutes
    proxy: {
      server: "http://gw.ludaxproxy.com:7777",
      username: "resi-lx_k3m9q2p7xa",
      password: `${process.env.LUDAX_PASSWORD}-session-ci01-time-60-country-us`,
    },
  },
});
```

## Plans and help

- Prepaid, no subscription: 1 GB of Residential Proxies costs $0.99. The smallest plan is the test; there is no free trial. Every plan: https://ludaxproxy.com/pricing
- Credentials: dashboard, Generate (https://ludaxproxy.com/dashboard). A plan's password can be reset there.
- Full reference: https://ludaxproxy.com/docs. Whole site for AI: https://ludaxproxy.com/llms.txt
- Support: Telegram https://t.me/LudaxProxy, email support@ludaxproxy.com.
