ScreenshotNeo

BlogHow-to

How to Test a Web App’s Rupee Price Display with Visual Regression Screenshots

Test rupee prices with an explicit locale and currency, a precise text assertion, and a stable Playwright screenshot baseline. Includes setup, edge cases, and troubleshooting.

By the ScreenshotNeo team4 October 20268 min read

Test a rupee price display with two complementary checks: assert the exact price text promised by your app for a chosen locale and currency, then compare a screenshot of that UI against a reviewed baseline. The text assertion catches a wrong value directly; the screenshot catches visual changes such as symbol placement, spacing, alignment, clipping, and wrapping.

Use an explicit locale and currency. Do not rely on the machine’s default locale, and do not assume one exact INR string is universal across runtimes. Intl.NumberFormat is locale-sensitive, and its output can include non-breaking spaces or bidirectional controls that vary by implementation. Define the display your product supports and verify it in the browser/runtime used for the test. See MDN’s Intl.NumberFormat reference.

1. Define the price display contract

Before writing a screenshot test, decide what the application promises to show. “Rupee price” alone is underspecified: the currency, locale, rounding, symbol or code, and fraction digits all affect the result.

Decision Example question
Currency Does this price represent INR, or another rupee-denominated currency? Pass the relevant ISO 4217 currency code explicitly.
Locale Is the display en-IN, another Indian language locale, or a locale selected independently of the visitor’s region?
Fraction digits Should the UI show paise, always show two decimals, or show no decimals for whole-unit prices?
Rounding Where does rounding happen: in the calculation, in the formatter, or both?
Presentation Does the product contract require a currency symbol, a currency code, or a specific surrounding label?
Supported environments Which browsers and runtime versions must produce the promised result?

Use Intl.NumberFormat with explicit options in the app. For example, the formatter shape is:

const formatter = new Intl.NumberFormat('en-IN', {
  style: 'currency',
  currency: 'INR',
});
const display = formatter.format(amount);

This selects locale-sensitive currency formatting; it does not define your entire product contract. Set options such as minimumFractionDigits and maximumFractionDigits deliberately when the UI requires fixed precision. Check the supported output in the app’s target browsers rather than copying an unverified literal from another environment.

2. Cover grouping, fractions, and state changes

Choose fixture values that expose formatting boundaries. Indian grouping can differ from three-digit Western grouping: MDN’s en-IN decimal example formats 123456.789 as 1,23,456.789. That is a decimal example, not a canonical INR currency string. Let the app’s formatter and contract determine the expected currency display.

  • A small amount, to check symbol or code placement and whether fractions appear.
  • A fractional amount, to verify the app’s paise and rounding policy.
  • A value around a grouping transition, to catch separator and alignment changes.
  • A larger value that exercises lakh or crore grouping.
  • Zero, discounts, or negative/refund amounts when those states are supported by the UI.

Keep the input value and surrounding product state fixed in a fixture. If the displayed amount depends on a locale selector, select it in the test; a browser context locale only helps when the app actually reads the browser locale.

3. Set up Playwright Test

Install Playwright Test in the project and install the browser your CI job will use. Add a stable route, fixture, or test data setup that renders a known price without depending on live pricing or a changing account.

npm install --save-dev @playwright/test
npx playwright install

A minimal configuration can set the test directory and a stable viewport. Use the same browser project and rendering environment when creating and checking baselines.

// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  use: {
    baseURL: 'http://127.0.0.1:3000',
    viewport: { width: 1280, height: 800 },
    // Pin the project/browser in CI and baseline generation.
  },
});

4. Assert the text and capture a focused baseline

The following is a runnable test pattern once the app provides /product?fixture=price-boundary and a price element with data-testid="price". Replace the route, fixture, currency, locale, and expected text with the application’s supported contract. The expected text is intentionally supplied by the application team; this example does not claim a universal rupee literal.

// tests/rupee-price.spec.ts
import { test, expect } from '@playwright/test';

const expectedPriceForEnIN = process.env.EXPECTED_PRICE_EN_IN;
if (!expectedPriceForEnIN) {
  throw new Error('Set EXPECTED_PRICE_EN_IN to the app-approved display string');
}

test('renders the agreed rupee price for en-IN', async ({ browser }) => {
  const context = await browser.newContext({ locale: 'en-IN' });
  const page = await context.newPage();

  await page.goto('/product?fixture=price-boundary');
  const price = page.getByTestId('price');
  await expect(price).toBeVisible();
  await expect(price).toHaveText(expectedPriceForEnIN);
  await expect(price).toHaveScreenshot('price-en-IN.png');

  await context.close();
});

Run it with the agreed expected string, for example by setting EXPECTED_PRICE_EN_IN in the test environment and invoking npx playwright test tests/rupee-price.spec.ts. A missing baseline is created on the first run; inspect and commit that image only after confirming it represents the intended UI. Subsequent runs compare the captured image with the stored reference.

For a full-page check, use await expect(page).toHaveScreenshot('product-price-page.png'). Prefer the price component screenshot when the question is specifically about price rendering: it keeps unrelated page content from making the test noisy. Playwright documents both screenshot baselines and the update flow in its visual comparisons guide.

5. Keep screenshot comparisons stable

Playwright screenshot assertions wait for two consecutive captures to match before comparison. That helps with transient rendering, but deterministic inputs and rendering conditions still matter. See the PageAssertions API for the assertion controls.

  • Pin the rendering setup. Browser version, host OS, fonts, and device scale can affect pixels. Generate and review baselines in the same environment used by CI.
  • Fix the state. Use a fixture price, fixed locale, known viewport, and stable product data. Avoid current timestamps, rotating banners, randomized content, and live API values.
  • Wait for the UI. Wait for the price to become visible and for any app loading state to finish before capturing.
  • Control animation and motion. Use Playwright’s screenshot assertion options to disable animations when motion is not part of the behavior being tested.
  • Mask or hide genuinely volatile regions. Use masks or screenshot styling controls for dynamic content outside the price contract. Do not mask the price or its surrounding layout if those are what the test must verify.
  • Set tolerance intentionally. Difference thresholds can help with harmless rendering variation, but an overly generous threshold can hide a meaningful currency or layout regression.

When a UI change is intentional, inspect the diff and update snapshots deliberately with npx playwright test --update-snapshots. Review the resulting baseline as a code change; do not make unexplained screenshot changes pass automatically.

6. Diagnose common failures

Symptom Likely cause Fix
Text assertion differs by whitespace The formatter emitted a non-breaking space, or the runtime uses a different representation. Inspect the actual string and Unicode code points in the supported browser. Define the product’s intended output for that runtime; avoid assuming ordinary spaces and non-breaking spaces are interchangeable if exact output matters.
Symbol placement or separators differ The test relied on a default locale, or the app and test are using different locale state. Set locale and currency explicitly in the app and test. Drive the app’s locale selector if that is the real user control.
Screenshot fails only in CI Browser, OS, font, device scale, or rendering environment differs from baseline creation. Use the same pinned browser and host environment for baseline generation and CI comparison; recreate baselines only after confirming the intended rendering.
Snapshot changes on every run Dynamic data, animation, asynchronous content, or unstable layout is present. Use a deterministic fixture, wait for the relevant state, disable irrelevant animation, and mask only unrelated volatile regions.
Price text passes but screenshot fails The value is correct, but spacing, typography, wrapping, alignment, or another visible detail changed. Inspect the visual diff around the price. Decide whether the change is a regression or an intentional design update.
Screenshot passes but price is wrong The baseline may have been updated with an incorrect value, or the visible value is too small for pixel comparison to reliably express the failure. Keep the locator-based text assertion. It gives a direct failure for the displayed string.
First run reports a missing snapshot No reference image exists yet for this test and environment. Generate the baseline, review it against the product contract, then commit it with the test.
Full-page snapshot is flaky Unrelated dynamic content elsewhere on the page changes between captures. Use a focused component screenshot for the price and reserve full-page coverage for a separate stable page-level test.

7. Performance, reliability, and maintenance

A focused component screenshot generally captures less page content to stabilize and review than a full-page comparison. The main maintenance cost is baseline review: browser or design changes can produce many image diffs. Keep fixtures and rendering environments consistent, and update only the snapshots whose intended appearance has changed.

Use both checks because they fail for different reasons. The text assertion precisely identifies a wrong formatted amount. The screenshot checks the visible presentation, including whether the right value fits and appears in the right place. Neither should be treated as a substitute for the other.

Price-format correctness also depends on runtime internationalization data. If multiple browsers or runtime versions are supported, run the contract in each supported target or define a narrower rendering environment for the screenshot baseline. MDN notes that formatted output can vary across implementations and may contain non-breaking spaces or bidirectional control characters; see the format method reference.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. It can capture a page without you managing a local browser setup. One GET request returns a screenshot; see the API documentation for request options and response details.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
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);

Adapt the target URL to your app’s public test page. ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture; each cleanup step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and the response identifies the page verdict and billing status in headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. These captures can help inspect a deployed page, while Playwright remains useful for running assertions against your application and maintaining test baselines.

Sign up free for 1,000 screenshots a month, with no card required.

FAQ

Should I compare the entire page or only the price?

Use a focused price screenshot for price-specific regressions. Add a full-page assertion when page-level layout is also part of the requirement and the rest of the page can be made stable.

Should I assert an exact rupee string?

Yes, assert the exact string your product promises in its supported locale and runtime. Do not treat a copied string as universal across browsers or internationalization implementations.

Does setting Playwright’s locale change the app’s price?

Only if the app derives its formatting from browser locale settings. If the app has its own locale selection or fixed pricing policy, set that state directly in the test.

When should I update a screenshot baseline?

After reviewing a visual diff and deciding the changed appearance is intentional. Keep the matching text assertion so a baseline update cannot silently legitimize a wrong amount.

References