How to Test a Web App’s Locale-Specific Date and Currency Layouts with Screenshots
Test localized dates and currencies for both correct formatting and visual fit with Playwright, deterministic fixtures, and screenshot comparisons.
Use Playwright Test to run each supported locale and timezone, assert the rendered date and currency as text, then compare a screenshot to catch wrapping, clipping, alignment, and overflow. Keep screenshot baselines and comparisons in the same browser and environment so rendering differences do not obscure real layout changes.
1. Choose locale cases that can expose differences
There is no universal locale matrix. Start with locales and currencies your application supports, then select representative cases that exercise different formatting and layout behavior:
- A month-first date and a day-first date.
- A locale with different decimal or thousands separators.
- A currency formatted with its symbol after the amount.
- A currency with different fraction-digit behavior.
Also decide which timezones matter to your product. Locale and timezone are separate inputs: locale influences language and formatting settings, while timezone affects how dates and times are interpreted or displayed. Test meaningful combinations rather than every possible pair.
2. Configure Playwright for a locale and timezone
Install Playwright Test and its browser if the project does not already use them:
npm init playwright@latest
For repeated coverage, define a project for each locale/timezone combination. This makes each run and its screenshot baseline explicit. Example playwright.config.ts:
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
projects: [
{
name: 'en-US',
use: {
browserName: 'chromium',
locale: 'en-US',
timezoneId: 'America/New_York',
viewport: { width: 1280, height: 800 },
},
},
{
name: 'en-GB',
use: {
browserName: 'chromium',
locale: 'en-GB',
timezoneId: 'Europe/London',
viewport: { width: 1280, height: 800 },
},
},
{
name: 'de-DE',
use: {
browserName: 'chromium',
locale: 'de-DE',
timezoneId: 'Europe/Berlin',
viewport: { width: 1280, height: 800 },
},
},
],
});
Playwright also allows locale and timezone settings per test or browser context. Use project configuration when the combinations are stable and regularly run; create contexts inside a test when it is more convenient to iterate over locale data in one test. In the context approach, close each context after use to avoid leaking browser resources.
Browser timezone emulation does not change the test runner’s timezone. If application or test code running outside the browser depends on the runner’s local timezone, set the TZ environment variable for the test process as well, for example TZ=UTC npx playwright test on environments that support that syntax.
3. Use deterministic dates and prices, then assert the text
Render fixed test data so that a failure reflects a code or rendering change rather than a changing price or the current time. If a component formats values from “now,” fix its clock or pass a stable timestamp through your test setup. The following example assumes the application displays a date in [data-testid="invoice-date"] and a price in [data-testid="invoice-total"]:
import { test, expect } from '@playwright/test';
test('invoice date and total fit for this locale', async ({ page }) => {
await page.goto('/test-fixtures/invoice?date=2024-03-05&amount=1234.5¤cy=EUR');
const date = page.getByTestId('invoice-date');
const total = page.getByTestId('invoice-total');
await expect(date).toHaveText('03/05/2024');
await expect(total).toHaveText('€1,234.50');
});
The expected strings above are illustrative for an en-US project. Use the exact strings your product contract requires for each locale and currency. Prefer asserting the complete displayed value when symbol position, separators, and digits matter. If the product intentionally permits multiple equivalent forms, assert the stable parts or use a carefully scoped regular expression.
4. Compare the locale-specific screenshot
Add a screenshot assertion after the semantic checks. The screenshot can reveal visual defects that text assertions cannot, including a currency symbol wrapping onto another line, a date clipped by a narrow column, misaligned totals, or a layout that overflows its container.
test('invoice date and total fit for this locale', async ({ page }) => {
await page.goto('/test-fixtures/invoice?date=2024-03-05&amount=1234.5¤cy=EUR');
await expect(page.getByTestId('invoice-date')).toHaveText('03/05/2024');
await expect(page.getByTestId('invoice-total')).toHaveText('€1,234.50');
await expect(page).toHaveScreenshot('invoice-locale.png', {
fullPage: true,
});
});
In normal HTML test source, write & in a string only when it is HTML-escaped by the surrounding representation; in the actual TypeScript URL string, use a literal & character. A complete test file can combine both assertions as follows:
import { test, expect } from '@playwright/test';
test('invoice date and total fit for this locale', async ({ page }) => {
await page.goto('/test-fixtures/invoice?date=2024-03-05&amount=1234.5¤cy=EUR');
await expect(page.getByTestId('invoice-date')).toHaveText('03/05/2024');
await expect(page.getByTestId('invoice-total')).toHaveText('€1,234.50');
await expect(page).toHaveScreenshot('invoice-locale.png', { fullPage: true });
});
Playwright Test creates a reference screenshot when one is missing and compares later runs with it. Review a newly created or updated baseline before committing it; do not accept a visual change just to clear a failing run. For long pages, a full-page capture checks broad layout, while a targeted locator screenshot can make a focused component easier to review:
await expect(page.getByTestId('invoice-summary')).toHaveScreenshot('invoice-summary.png');
5. Cover breakpoints and keep baselines reproducible
Date and currency strings can fit at a desktop width but wrap or push neighboring content at a smaller breakpoint. Add a small number of viewport sizes that represent your supported layouts, and capture separate baselines for each meaningful locale and viewport combination. This is a practical extension of the screenshot workflow; choose coverage based on your product’s layout risks.
- Use fixed fixture values and stable application state.
- Pin the Playwright browser version used to create and compare baselines.
- Use the same operating system or container, viewport, and headless settings where practical.
- Keep baseline images under version control and review intentional diffs.
- Disable or normalize dynamic content that is unrelated to the behavior under test.
- Check screenshots at representative breakpoints, especially where dates or prices sit in constrained columns.
Screenshot output can vary with host operating system, browser version and settings, hardware, power source, and headless mode. A baseline generated in one environment may therefore differ from a run in another even when application code is unchanged. Keep the comparison environment consistent instead of loosening tolerances until meaningful layout changes disappear.
6. Choose projects or contexts
| Approach | Useful when | Trade-off |
|---|---|---|
| One project per locale and timezone | The combinations are stable and should run throughout the suite. | More project runs and corresponding baselines to maintain. |
| Create contexts inside a test | You want to iterate over locale data in one test or run a focused matrix. | You must manage context creation, page setup, and cleanup carefully. |
Both approaches let you set locale and timezone explicitly. Pick the one that makes the intended coverage clearest to the team; the choice is an implementation trade-off, not a performance claim.
7. Troubleshoot failures
| Symptom | Likely cause | What to check or change |
|---|---|---|
| Date or currency text assertion fails | The actual formatter output differs from the expected string, or the fixture did not load. | Inspect the rendered text and fixture response. Confirm the selected locale, currency, date input, separators, symbol position, and fraction digits. |
| Date changes around midnight or across runs | The value depends on current time or a timezone boundary. | Use a fixed timestamp or frozen clock, set timezoneId, and set runner TZ if test-side code needs a deterministic timezone. |
| Screenshot fails with broad visual differences | The browser, OS, viewport, headless mode, or other rendering conditions differ from those used for the baseline. | Compare in a consistent pinned environment and inspect the diff before updating the reference. |
| Screenshot changes on every run | Dynamic data or unrelated page content is changing. | Use stable fixtures and normalize or disable only the dynamic content that is irrelevant to the assertion. |
| Text passes but screenshot shows clipping or wrapping | The content is correct but does not fit the tested layout. | Inspect container width, wrapping rules, overflow, and adjacent elements. Add a screenshot at the affected breakpoint. |
| Screenshot passes but the displayed value is wrong | The visual layout is stable while the formatting itself is incorrect. | Keep the text assertion; a screenshot comparison alone does not explain every semantic formatting error. |
8. Performance, reliability, and cost
Each added project, locale, timezone, viewport, and screenshot baseline increases the work and artifacts in the suite. Keep the matrix tied to supported markets and meaningful formatting differences. Run the core cases on changes where fast feedback matters, and place broader combinations in the suite that fits your release workflow. This is a planning recommendation, not a measured benchmark.
Visual comparisons are most reliable when the rendering environment and fixture state are stable. They complement unit or text assertions: text checks identify the value that is wrong, and image comparisons reveal where the layout changed. Playwright’s screenshot assertions run as part of your test suite; a hosted screenshot API is an optional way to capture pages without maintaining browser setup for that capture workflow.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. For screenshot captures, one GET request takes a URL and returns an image or PDF. It can remove cookie banners, newsletter popups, and chat widgets before the shot; bot checks, blank pages, and failed loads are never billed; its MCP server lets AI agents take screenshots; and 1,000 screenshots a month are free with no card, with paid plans starting at $5 for 3,000. For locale-specific layout work, use your app’s locale and timezone setup to render the target state, then capture that URL.
See the ScreenshotNeo API documentation for options. Example cURL call:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);
Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.
FAQ
Does setting the browser timezone also set Node’s timezone?
No. Playwright’s browser timezone emulation does not change the test runner’s timezone. Set the runner’s TZ environment variable when test-side code requires a fixed timezone.
Should every locale have its own expected string?
For exact formatting requirements, yes: assert the expected rendered value for each selected locale and currency. Keep the cases representative of supported markets.
Can I use screenshots without text assertions?
You can, but the image tells you that something changed more readily than why. Pair visual comparison with assertions on the formatted date and currency.
When should I update a screenshot baseline?
After reviewing the visual difference and confirming it reflects an intended UI change. Commit the updated reference with the change that caused it.


