Use this when your code already speaks Playwright and you want BrowserCity to host the browser. The main change is creating a BrowserCity session, connecting to its returned endpoint, and reusing the prepared context/page.
Minimal executable examples
browsercity-playwright.ts
import { chromium } from 'playwright';const apiKey = process.env.BROWSERCITY_API_KEY;if (!apiKey) { throw new Error('Set BROWSERCITY_API_KEY before running this script.');}const response = await fetch('https://api.browser.city/v1/sessions', { method: 'POST', headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ browser: 'chromium' }),});if (!response.ok) { throw new Error(`Create session failed: ${response.status} ${await response.text()}`);}const { endpoint, token, id } = await response.json() as { endpoint: string; token: string; id: string };try { const browser = await chromium.connect(endpoint, { headers: { Authorization: `Bearer ${token}` }, }); const context = browser.contexts()[0]; const page = context?.pages()[0]; if (!page) { throw new Error('BrowserCity session did not return a ready page.'); } await page.goto('https://example.com'); console.log(await page.title());} finally { await fetch(`https://api.browser.city/v1/sessions/${id}`, { method: 'DELETE', headers: { Authorization: `Bearer ${apiKey}` }, });}import jsonimport osimport urllib.errorimport urllib.requestfrom playwright.sync_api import sync_playwrightAPI_BASE = "https://api.browser.city/v1"API_KEY = os.environ["BROWSERCITY_API_KEY"]def api_request(method, path, body=None): data = None if body is None else json.dumps(body).encode("utf-8") request = urllib.request.Request( f"{API_BASE}{path}", data=data, headers={ "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json", }, method=method, ) try: with urllib.request.urlopen(request, timeout=60) as response: payload = response.read().decode("utf-8") return json.loads(payload) if payload else {} except urllib.error.HTTPError as error: detail = error.read().decode("utf-8", errors="replace") raise RuntimeError(f"{method} {path} failed: {error.code} {detail}") from errorcreated = api_request("POST", "/sessions", {"browser": "chromium"})session_id = created["id"]endpoint = created["endpoint"]token = created["token"]try: with sync_playwright() as playwright: browser = playwright.chromium.connect( endpoint, headers={"Authorization": f"Bearer {token}"}, ) page = browser.contexts[0].pages[0] page.goto("https://example.com") print(page.title())finally: api_request("DELETE", f"/sessions/{session_id}")using Microsoft.Playwright;using System.Net.Http.Headers;using System.Net.Http.Json;var apiKey = Environment.GetEnvironmentVariable("BROWSERCITY_API_KEY") ?? throw new InvalidOperationException("Set BROWSERCITY_API_KEY before running this script.");using var http = new HttpClient { BaseAddress = new Uri("https://api.browser.city/v1/") };http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);var session = await (await http.PostAsJsonAsync("sessions", new { browser = "chromium" })) .Content.ReadFromJsonAsync<Session>();var (id, endpoint, token) = session!;IPlaywright? playwright = null;try { playwright = await Playwright.CreateAsync(); var browser = await playwright.Chromium.ConnectAsync(endpoint, new() { Headers = new() { ["Authorization"] = $"Bearer {token}" } }); var page = browser.Contexts[0].Pages[0]; await page.GotoAsync("https://example.com"); Console.WriteLine(await page.TitleAsync());} finally { await http.DeleteAsync($"sessions/{id}"); playwright?.Dispose();}record Session(string Id, string Endpoint, string Token);import com.fasterxml.jackson.databind.ObjectMapper;import com.microsoft.playwright.BrowserType;import com.microsoft.playwright.Playwright;import java.net.URI;import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpResponse;import java.util.Map;public class BrowserCityPlaywright { static final URI API_BASE = URI.create("https://api.browser.city/v1/"); static final String API_KEY = System.getenv("BROWSERCITY_API_KEY"); static final HttpClient HTTP = HttpClient.newHttpClient(); static final ObjectMapper JSON = new ObjectMapper(); public static void main(String[] args) throws Exception { var created = postSession(Map.of("browser", "chromium")); var id = created.id(); var endpoint = created.endpoint(); var token = created.token(); Playwright playwright = null; try { playwright = Playwright.create(); var browser = playwright.chromium().connect(endpoint, new BrowserType.ConnectOptions().setHeaders(Map.of( "Authorization", "Bearer %s".formatted(token)))); var page = browser.contexts().get(0).pages().get(0); page.navigate("https://example.com"); System.out.println(page.title()); } finally { deleteSession(id); if (playwright != null) { playwright.close(); } } } static Session postSession(Map<String, ?> body) throws Exception { var request = HttpRequest.newBuilder(API_BASE.resolve("sessions")) .header("Authorization", "Bearer %s".formatted(API_KEY)) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(JSON.writeValueAsString(body))) .build(); var response = HTTP.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() / 100 != 2) { throw new RuntimeException("Create session failed: %d %s".formatted(response.statusCode(), response.body())); } return JSON.readValue(response.body(), Session.class); } static void deleteSession(String id) throws Exception { HTTP.send(HttpRequest.newBuilder(API_BASE.resolve("sessions/%s".formatted(id))) .header("Authorization", "Bearer %s".formatted(API_KEY)) .DELETE() .build(), HttpResponse.BodyHandlers.discarding()); } record Session(String id, String endpoint, String token) {}}
Migration steps
- Create a BrowserCity session before your Playwright script starts.
- Connect to
endpointwith the returnedtokenbearer header. - Reuse the prepared context/page that BrowserCity returns on connect.
- Delete the BrowserCity session during cleanup.
Cost and plan notes
For test suites, estimate the number of workers times average runtime. If you add managed residential/mobile egress or BYOP traffic, include that traffic in /pricing-calculator before increasing parallelism.