Localization and QA testing by country
See your site the way visitors in each country see it: language, currency, consent banners, redirects and region-specific features, checked on every release.
Proxy URL, exit IP in United Kingdom
http://USERNAME-cc-GB-session-a1b2c3d4-ttl-30:PASSWORD@scale-eu.netdash.io:2000Residential exit IPs in United Kingdom
Illustrative routes, not live traffic. Exits are available in most countries; the countries shown are examples.
- Country-level exits by ISO code
- Sticky sessions per test
- Mobile exits on the same syntax
Localization testing proxies in your test suite
Language negotiation, currency, consent banners, geo-redirects and region-specific features often depend on where a visitor connects from. From the office or a CI runner you see one country’s version of the site, so this behavior ships untested and breaks for users abroad.
Localization testing proxies route each test through an exit IP in the country it covers. netdash takes the exit country as -cc- in the proxy username, so your test matrix is a list of ISO codes, and a sticky session keeps each test on one exit IP from the first page to the last assertion.
What depends on the visitor’s country
| Attribute | Signals your app may use | How to test it |
|---|---|---|
| Language | IP country, Accept-Language, account setting | Assert the lang attribute and key strings per exit country and browser locale |
| Currency and tax display | IP country, site selector | Assert the currency on a price element and the tax wording where it differs |
| Consent banner variant | IP country | Assert which banner variant appears on a first visit |
| Geo-redirect to a country site | IP country | Assert the final URL, and that a visitor who picks another country stays there |
| Region-specific features or content rules | IP country, account region | Assert that the feature is present or absent per country |
Build a country matrix
Step 1: Write the matrix
Country → expected language, currency, consent variant, redirect target and features.
Step 2: Create a context per row
Exit country via
-cc-, a sticky session for the test, and the locale and timezone the test case calls for.Step 3: Assert expectations
The
langattribute, price currency, banner presence and final URL.Step 4: Capture screenshots
Key pages per country, for visual review.
Step 5: Run on every release
In CI, with each exit country paired with its gateway as in the configuration below.
Country targetingStep 6: Triage differences
Separate content bugs from signal bugs with the one-signal-at-a-time cases.
Test one signal at a time
Apps mix three signals: the IP country, the browser locale and the timezone. If every test changes all three together, a failure doesn’t tell you which signal your app used. Vary them independently instead.
For example, run a German exit with an en-US browser locale, then a US exit with de-DE. If the German exit still sends the visitor to the German site after they chose English, you’ve found a redirect that ignores the user’s language choice.
- IP country: the
-cc-part of the proxy username - Browser locale:
localeon the browser context, which also setsAccept-Language - Timezone:
timezoneIdon the browser context - Site or account setting: your app’s own selector, set in the test
Matrix configuration
The examples use the Scale plan: every residential plan uses the same syntax, and you switch plans by changing the host and port. The proxy string format explains each part of the username.
- United States
scale-us.netdash.io:2000·USERNAME-cc-US-session-{SID}-ttl-30Sticky,
30minutes, through the Americas gateway.- Germany
scale-eu.netdash.io:2000·USERNAME-cc-DE-session-{SID}-ttl-30Sticky,
30minutes, through the Europe gateway.- United Kingdom
scale-eu.netdash.io:2000·USERNAME-cc-GB-session-{SID}-ttl-30Sticky,
30minutes. UseGB, notUK.- United Kingdom, mobile web
mobile-eu.netdash.io:4000·USERNAME-cc-GB-session-{SID}-ttl-30Sticky,
30minutes, with a mobile viewport in the test.- Session ID (`SID`)
Country code in lowercase plus a short run hash, e.g.
gb3fa91cLetters and digits only. A new run gets new IDs, so each run starts on fresh sessions and each test keeps one exit IP while it stays available.
- More countries
Add a row with the ISO code
To confirm availability for a specific country, contact support.
Generate one row of the test matrix
Choose a country and session lifetime, then copy the server, username and password into your test configuration.
Your route
Through the gateway to an exit IP in , , on the plan.
Proxy URL
http://USERNAME-cc-GB-session-a1b2c3d4-ttl-30:PASSWORD@scale-eu.netdash.io:2000Replace USERNAME and PASSWORD with the credentials from your dashboard.
Scale, Europe gateway, exit IP in United Kingdom, sticky for 30 min.
curl --max-time 30 -x "http://USERNAME-cc-GB-session-a1b2c3d4-ttl-30:PASSWORD@scale-eu.netdash.io:2000" \
"https://api.ipify.org?format=json"import requests
proxy = "http://USERNAME-cc-GB-session-a1b2c3d4-ttl-30:PASSWORD@scale-eu.netdash.io:2000"
proxies = {"http": proxy, "https": proxy} # set both, or HTTPS requests bypass the proxy
response = requests.get("https://api.ipify.org?format=json", proxies=proxies, timeout=30)
response.raise_for_status()
print(response.text)import { ProxyAgent, fetch } from "undici";
const dispatcher = new ProxyAgent({
uri: "http://scale-eu.netdash.io:2000",
token: "Basic " + Buffer.from("USERNAME-cc-GB-session-a1b2c3d4-ttl-30:PASSWORD").toString("base64"),
});
const res = await fetch("https://api.ipify.org?format=json", { dispatcher, signal: AbortSignal.timeout(30_000) });
console.log(await res.text());import { chromium } from "playwright";
const browser = await chromium.launch({
proxy: {
server: "http://scale-eu.netdash.io:2000",
username: "USERNAME-cc-GB-session-a1b2c3d4-ttl-30",
password: "PASSWORD",
},
});
const page = await browser.newPage();
await page.goto("https://api.ipify.org?format=json");
console.log(await page.textContent("body"));
await browser.close();Example output
{"ip":"203.0.113.10"}Your output shows a different address in the country you chose.
One Playwright test per country
Each matrix row gets its own browser context, with its own proxy, locale and timezone, so rows don’t share cookies or exit IPs. Playwright takes the proxy server, username and password as separate fields.
The expectations are examples: replace them with your app’s rules. Each session ID combines the country with a hash of the run ID, so every run starts on fresh sessions.
- Playwright Test applies its default test and assertion timeouts
- Add the mobile-web row with
mobile-eu.netdash.io:4000and a mobile viewport - Syntax-checked. Setup details for Playwright, Selenium and Puppeteer
import { createHash } from "node:crypto";
import { test, expect } from "@playwright/test";
const USER = process.env.NETDASH_USERNAME!;
const PASS = process.env.NETDASH_PASSWORD!;
const RUN_ID = process.env.CI_RUN_ID ?? String(Date.now()); // any value unique per run
const runHash = createHash("sha256").update(RUN_ID).digest("hex").slice(0, 6);
const sid = (cc: string) => `${cc.toLowerCase()}${runHash}`; // e.g. gb3fa91c
// Example expectations: replace them with your app's rules.
const matrix = [
{ cc: "US", host: "scale-us.netdash.io:2000", locale: "en-US", timezoneId: "America/New_York",
lang: "en", currency: "$", consent: "consent-us", path: "/" },
{ cc: "DE", host: "scale-eu.netdash.io:2000", locale: "de-DE", timezoneId: "Europe/Berlin",
lang: "de", currency: "€", consent: "consent-eu", path: "/de/" },
{ cc: "GB", host: "scale-eu.netdash.io:2000", locale: "en-GB", timezoneId: "Europe/London",
lang: "en", currency: "£", consent: "consent-gb", path: "/en-gb/" },
];
for (const row of matrix) {
test(`storefront in ${row.cc}`, async ({ browser }) => {
const context = await browser.newContext({
proxy: {
server: `http://${row.host}`,
username: `${USER}-cc-${row.cc}-session-${sid(row.cc)}-ttl-30`,
password: PASS,
},
locale: row.locale,
timezoneId: row.timezoneId,
});
try {
const page = await context.newPage();
await page.goto("https://staging.example.com/");
await expect(page).toHaveURL(new RegExp(`${row.path}$`));
await expect(page.locator("html")).toHaveAttribute("lang", new RegExp(`^${row.lang}`));
await expect(page.getByTestId("price").first()).toContainText(row.currency);
await expect(page.getByTestId(row.consent)).toBeVisible();
await page.screenshot({ path: `screenshots/home-${row.cc}.png`, fullPage: true });
} finally {
await context.close();
}
});
}Testing staging behind a firewall
Residential exit IPs change, so a firewall can’t admit them by address. You have two options.
Test a publicly reachable staging host that is protected by your own authentication, such as a login or a signed preview link. Or use static ISP or datacenter IPs and add them to your firewall’s allow rules: they stay assigned to you for as long as the subscription renews and arrive as a host:port:user:pass list. ISP IPs are in Ashburn, Frankfurt, London and Sydney; datacenter IPs are in the US, Germany, the UK and the Netherlands.
Static IPs through sales
Self-serve checkout for ISP and datacenter proxies is paused. Contact sales for ISP or datacenter IPs with the locations and the number of IPs your staging setup needs.
Which product for which test
Residential proxies
Self-serveOne exit country per test, sticky for the length of the test. The examples use the Scale plan.
From $1.20/GB, graduated per order
Mobile proxies
Self-serveMobile-web checks from mobile exit IPs, with a mobile viewport in the test.
From $3.50/GB, graduated per order
ISP proxies
Through salesFixed IPs in Ashburn, Frankfurt, London or Sydney that your staging firewall can admit.
$1.60 per IP per 30 days
Test what you’re responsible for
Test sites and apps you own, or that you’re authorized to test. Keep test traffic to what the suite needs. Targeting is country-level, so state- or city-specific rules can’t be tested this way. See how we expect netdash to be used.
Localization testing questions
No. Targeting is country-level, so rules that depend on a state, region or city can’t be tested this way.
Set them per test case. The exit country changes the IP address your app sees; the browser’s locale and timezone are separate signals. Varying them independently shows which one your app uses.
Residential exit IPs change, so use a publicly reachable staging host protected by your own authentication, or static ISP or datacenter IPs that your firewall admits. Static IPs are available through sales; self-serve checkout is paused.
Use any ISO 3166-1 alpha-2 code in the username. To confirm availability for a specific country, contact support. Gateways and static-IP locations are on the locations page.
Yes. Use the Mobile plan (mobile-eu.netdash.io:4000 or mobile-us.netdash.io:4000) and a mobile viewport in your test. Credentials and username syntax are the same as on the residential plans.
Add a country to your next release check
Pick one country your release checks don’t cover yet, add it as a matrix row, and run the suite against staging.