In Playwright, which browser context options set the page's language and time zone?
answer
- Two options, one context
- Language tag and IANA zone name
- navigator.language and Intl resolution
- Set at context creation, no setter
- Browser only, not the Node process
basics
~10 sPlaywright's locale and timezoneId context options. locale sets navigator.language, the Accept-Language header and Intl formatting; timezoneId sets the IANA zone that Date and Intl.DateTimeFormat resolve to.
solid answer
~40 s`locale` and `timezoneId` are **browser-context** options: pass them to `browser.newContext()`, or in the runner to `test.use({ locale: 'de-DE', timezoneId: 'Europe/Berlin' })`. `locale` takes a BCP 47 tag and changes `navigator.language`, the `Accept-Language` request header, and the locale `Intl.NumberFormat` / `Intl.DateTimeFormat` resolve to, so `(1000000.5).toLocaleString()` renders `1 000 000,5` under `fr-FR` instead of `1,000,000.5`. `timezoneId` takes an IANA zone name and changes `Intl.DateTimeFormat().resolvedOptions().timeZone` plus the offset in `new Date().toString()`; an unknown name rejects with `Invalid timezone ID`. Both are read when the context is created — there is no `context.setLocale()` — and both are isolated per context and inherited by popups and web workers. Neither touches the Node process running the tests.
code
typescript · 13 linesimport { test, expect } from '@playwright/test';
test.use({ locale: 'de-DE', timezoneId: 'Europe/Berlin' });
test('room rate and check-in render in the German environment', async ({ page }) => {
await page.goto('/rooms/deluxe-double');
expect(await page.evaluate(() => navigator.language)).toBe('de-DE');
expect(await page.evaluate(() => Intl.DateTimeFormat().resolvedOptions().timeZone))
.toBe('Europe/Berlin');
await expect(page.getByTestId('nightly-rate')).toHaveText('1.250,00 €');
});go deeper
Remember the two option names and where they go: locale and timezoneId, passed to newContext or test.use. Know that locale is a language tag and timezoneId is an IANA zone name like Europe/Berlin.
Explain what each option actually reaches: navigator.language, Accept-Language and Intl resolution for one, the resolved time zone and Date offset for the other, and that both are fixed when the context is created.
Show you know the boundary. The emulation covers the browser context and everything it spawns, never the Node process, and a second locale means a second context rather than a runtime setter.
Own the question of which layer sets these — a global use block, a project, or a per-file override — and be able to say what a green suite under an emulated locale does and does not prove about the shipped product.
## What the two options are `locale` and `timezoneId` are **browser-context options** in Playwright. You can pass them to `browser.newContext()` or `browser.newPage()` in library code, and in the test runner you set them in the `use` block of `playwright.config.ts`, in a project, or per file with `test.use()`. They belong to the *context*, so every page, popup and web worker created inside that context reports the same values; a context created without them falls back to whatever the host machine reports. `locale` takes a BCP 47 language tag — `en-GB`, `de-DE`, `fr-CH`. `timezoneId` takes an IANA time-zone name — `Europe/Berlin`, `America/Jamaica`, `Pacific/Honolulu`. ## What each one actually changes | Option | What the page observes | |---|---| | `locale` | `navigator.language`; the `Accept-Language` header on requests the browser makes; the locale that `Intl.NumberFormat` and `Intl.DateTimeFormat` resolve to, and therefore `toLocaleString()` output | | `timezoneId` | `Intl.DateTimeFormat().resolvedOptions().timeZone`; the UTC offset and zone name printed by `new Date().toString()` | Concretely, on a multi-tenant hotel-booking site: - A nightly rate rendered with `(1000000.5).toLocaleString()` prints `1,000,000.5` under `locale: 'en-US'` and `1 000 000,5` under `locale: 'fr-FR'`. - A check-in timestamp `new Date(1479579154987).toString()` prints `Sat Nov 19 2016 13:12:34 GMT-0500` under `timezoneId: 'America/Jamaica'` and `Sat Nov 19 2016 19:12:34 GMT+0100` under `timezoneId: 'Europe/Berlin'`. - A tenant that serves German copy off the `Accept-Language` header will serve it because `locale: 'de-DE'` put `de-DE` on the request, not because anything in the test asked for German content. ## Context-scoped, not page-scoped and not process-scoped This is the part candidates most often get wrong, and it has three separate edges: 1. **Creation-time only.** Both options are read when the context is built. Playwright exposes no `context.setLocale()` and no `context.setTimezone()`, so a test that needs a second language needs a second context — in the runner, a second file or `describe` block with its own `test.use()`, or a second project. 2. **Isolated between contexts.** Two contexts in the same browser can hold different locales at the same time and neither leaks into the other, and a context created afterwards with no options is back on the machine default. 3. **Not the test process.** The emulation reaches the browser only. Inside the test file, your own `new Date()` and `toLocaleString()` still use the machine's zone and locale. If you need the runner itself pinned, that is the `TZ` environment variable on the Node process, not `timezoneId`. Values also propagate downwards: a popup opened with `window.open()` and a `Worker` spawned by the page both inherit the context's locale and time zone, so `Intl.NumberFormat().resolvedOptions().locale` inside a worker reports the emulated value too. ## Failure modes worth knowing - **An unknown zone rejects.** `timezoneId: 'Foo/Bar'` fails with `Invalid timezone ID: Foo/Bar` when the context or its first page is created — a typo is a hard failure, not a silent fallback. - **`locale` does not translate anything.** It only changes what the browser reports. If the booking app picks its language from a cookie or a URL prefix rather than from `Accept-Language`, setting `locale` changes number formatting and nothing else. - **A device descriptor does not set them.** Locale and time zone are separate options; spreading a device preset into `use` leaves both at the machine default. - **Machine-dependent assertions.** Asserting a formatted string without pinning both options makes the test pass on one laptop and fail on another; that is exactly the class of flake these options remove. ## What the two options do not reach They are narrow on purpose, and the gaps are where suites quietly lose signal: - **Currency, and text direction.** `locale` changes what `Intl` resolves to and what the browser advertises. Whether the booking site prices in euros or flips to `dir="rtl"` is the application's own logic reading that tag, and it will only happen if the app implements it. - **Other emulated axes.** Colour scheme, forced colours, geolocation and offline are separate options. A German context is not automatically a dark, high-contrast or mobile one. - **Server-side negotiation you do not control.** The option puts the tag on `Accept-Language`; a tenant that stores a language preference against the account ignores the header entirely. - **The clock itself.** `timezoneId` changes the zone dates are interpreted in, not what "now" is. Freezing or advancing time is a different mechanism. ## Verifying the emulation landed The cheapest checks read the browser back directly: - `await page.evaluate(() => navigator.language)` should equal the tag you set. - `await page.evaluate(() => Intl.DateTimeFormat().resolvedOptions().timeZone)` should equal the IANA name you set. The more valuable check asserts the rendered result — the price on the room card, the arrival time on the reservation summary — because that proves the app's own formatting path consumed the emulated environment rather than merely that Playwright applied it.
- Does timezoneId also change the time zone the test file's own new Date() uses?No. `timezoneId` is a browser-context option, so it only moves the browser. Code running in the Node test process keeps the machine's zone. To move the runner as well you set the `TZ` environment variable on the process; the two are configured independently and can legitimately disagree.
- How would you run the same booking spec under two different locales?Give each locale its own context. In the runner that means two projects, each with its own `use: { locale }`, or two `describe` blocks each calling `test.use()`. There is no runtime setter, so you cannot flip locale inside a single test — a second value always means a second context.
- If locale is set but the site still serves English, what is the first thing you check?How the app chooses its language. `locale` only sets `navigator.language` and the `Accept-Language` header. If the site negotiates language from a cookie, a path prefix or a stored tenant setting, the header is ignored and you have to drive that mechanism instead.
saying these in an interview costs you the question
- Thinks locale changes the operating system language of the runner
- Believes a context.setLocale method exists for mid-test changes
- Expects the app to translate itself just because locale was set
- Assumes an invalid timezone name silently falls back to UTC
- Sets locale on the page instead of the browser context
- Thinks timezoneId also moves the Node process clock