If you are choosing between Selenium and Playwright for work that runs through residential proxies, or maintaining both, the useful thing is not two separate setup guides but the comparison: what is identical, what is genuinely different, and which of those differences should influence how you build. Here is the same job done in each, side by side.
For the deeper per-framework detail, including the edge cases each one has, see residential proxies in Selenium and residential proxies with Playwright. This is the comparison layer on top.
What is identical: the gateway
Both frameworks talk to the same endpoint with the same credentials, because the proxy does not care what is driving it. Host p.shifter.io, port 443, and all of your targeting encoded in the username:
customer-USERNAME # rotate, no geo
customer-USERNAME-country-de # German exit
customer-USERNAME-country-us-city-new_york # city level
customer-USERNAME-country-de-sid-abc123-ttl-600 # sticky, ten minutes
That means every decision about geography, rotation and session lifetime is a string, identical in both frameworks. Nothing in the sections below changes the gateway, only how each framework hands it your credentials. The format reference is in how to connect.
The one real difference: authentication
This is the difference that matters, and it explains most of the friction people hit.
Playwright supports authenticated proxies natively. Username and password are first-class options, so it works out of the box.
Selenium does not. It can set a proxy host and port, but the WebDriver specification has no mechanism for passing credentials, so Chrome shows a native auth dialog that your script cannot dismiss. Every Selenium solution is a workaround for that gap, and there are three: Selenium Wire, which handles credentials for you; a small generated Chrome extension that supplies them; or CDP, driving the browser’s debugging protocol directly.
If you are starting fresh and your work needs authenticated proxies, that difference alone is a legitimate reason to prefer Playwright.
The same setup in both
Playwright, Python:
from playwright.sync_api import sync_playwright
USER = "customer-USERNAME-country-de-sid-abc123-ttl-600"
with sync_playwright() as p:
browser = p.chromium.launch()
context = browser.new_context(
proxy={"server": "http://p.shifter.io:443",
"username": USER, "password": "PASSWORD"},
locale="de-DE", timezone_id="Europe/Berlin", # match the exit
)
page = context.new_page()
page.goto("https://ipinfo.io/json")
print(page.inner_text("body"))
Playwright, Node:
const ctx = await browser.newContext({
proxy: { server: 'http://p.shifter.io:443',
username: 'customer-USERNAME-country-de-sid-abc123-ttl-600',
password: 'PASSWORD' },
locale: 'de-DE', timezoneId: 'Europe/Berlin',
});
Selenium, Python, using Selenium Wire:
from seleniumwire import webdriver
USER = "customer-USERNAME-country-de-sid-abc123-ttl-600"
proxy_url = f"http://{USER}:PASSWORD@p.shifter.io:443"
opts = {"proxy": {"http": proxy_url, "https": proxy_url,
"no_proxy": "localhost,127.0.0.1"}}
driver = webdriver.Chrome(seleniumwire_options=opts)
driver.get("https://ipinfo.io/json")
print(driver.find_element("tag name", "body").text)
Same gateway, same username, same result. The only difference is how the credentials get in.
The structural difference: how you isolate identities
This is the one that should actually shape your design, and it follows from how expensive an isolated identity is in each framework.
In Playwright, contexts are cheap. A browser context is an isolated profile with its own cookies, storage and, importantly, its own proxy. You can run one browser process and create a context per identity, which makes rotating geography or sessions a matter of creating a new context rather than a new browser.
browser = p.chromium.launch() # one process
for country in ["de", "fr", "us"]:
ctx = browser.new_context(proxy={"server": "http://p.shifter.io:443",
"username": f"customer-USERNAME-country-{country}",
"password": "PASSWORD"})
page = ctx.new_page()
page.goto("https://example.com")
ctx.close() # identity discarded, process stays
In Selenium, the proxy is bound to the driver. Changing it generally means a new driver, and a driver is a whole browser process: slow to start and heavy in memory. So the Selenium pattern is the opposite, reuse a driver for many requests on one identity, and treat a change of identity as an expensive operation you batch around rather than do per request.
The practical consequence: a job that needs many short-lived identities is markedly cheaper in Playwright, while a job that holds one identity for a long sequence suits either. If you are running Selenium and find yourself launching a driver per request, that is the thing to fix first, not the proxy configuration.
Geo, rotation and sessions
Because targeting is in the username, this part is framework-agnostic. Omit sid and each new connection gets a fresh exit; include one and the same address is held until the TTL expires. In Playwright you scope that per context; in Selenium you scope it per driver.
One thing to get right in both: when you set a country, set the browser’s locale and timezone to match it. A German exit reporting a New York timezone is a contradiction that is easy to detect and easy to avoid, and both frameworks expose those as context options, per matching geo, timezone and locale.
Bandwidth: the cost both frameworks share
Browsers are expensive on a per-GB product because they fetch everything a real browser would: images, fonts, media, analytics. Blocking what you do not need is the single largest saving available, and both frameworks support it.
Playwright:
context.route("**/*", lambda route: route.abort()
if route.request.resource_type in {"image", "media", "font", "stylesheet"}
else route.continue_())
Selenium has no equivalent one-liner in vanilla form; with Selenium Wire you can filter requests, or you can block resource types over CDP. Either way it is worth doing, since it commonly cuts page weight by a large multiple. The wider argument, including whether you need a browser at all, is in when you need a headless browser and cutting proxy bandwidth costs.
Verifying it works, in both
Do not assume the proxy is applied. Navigate to an endpoint that reports the address and check it is not yours:
# Playwright
page.goto("https://ipinfo.io/json"); print(page.inner_text("body"))
# Selenium
driver.get("https://ipinfo.io/json"); print(driver.find_element("tag name", "body").text)
If the address is your own, the proxy is not being applied at all. If it is right but the content is wrong for the region, suspect DNS or a locale mismatch before blaming the pool, per preventing DNS leaks.
Which to choose
If you have no existing commitment and your work involves authenticated proxies and many identities, Playwright is the easier path: native credential support and cheap per-context isolation remove two problems you would otherwise engineer around.
Selenium remains a reasonable choice where you already have a Selenium estate, where you need its grid and cross-browser ecosystem, or where the work is one long-lived session rather than many short ones. The proxy support is entirely workable, it just costs you a library or a small extension to get credentials in.
And in both cases, remember the browser is only half of not getting blocked: the address gets you to the door, and headers, fingerprint and pacing decide what happens next, per avoiding blocks.
The bottom line
The gateway is identical for both frameworks, so geography, rotation and session lifetime are the same string in each. The real difference is authentication, which Playwright supports natively and Selenium does not, requiring Selenium Wire, an extension, or CDP. The difference that should shape your architecture is isolation cost: Playwright contexts are cheap so you rotate identity per context, while a Selenium proxy is bound to a driver, so you reuse drivers and batch identity changes. Match locale and timezone to the exit in both, block unnecessary resources in both because you pay per gigabyte, and verify the exit address before trusting any of it.
Both run on the same residential proxies, one gateway with country and city targeting and sticky sessions when a flow needs one, billed per GB so the resource blocking above translates directly into a smaller bill.