ScreenshotNeo

BlogHow-to

How to Test a Website’s UPI Payment Pages with Visual Regression Screenshots

Use Playwright screenshots to catch UPI checkout UI changes, then verify application behavior and payment outcomes separately in the provider sandbox.

By the ScreenshotNeo team4 October 202610 min read

Use Playwright Test to capture repeatable screenshots of each UPI checkout state and compare them with reviewed baselines. A screenshot comparison catches presentation changes such as clipped labels, shifted buttons, or a broken responsive layout. It does not prove that a payment succeeded: assert application state and navigation separately, then confirm transaction outcomes through the provider’s documented sandbox or reporting tools.

What visual regression can and cannot verify

A UPI checkout can cross several boundaries: the merchant page, a payment address, a collect request in a UPI app, and PIN authorization. The exact path depends on the provider and integration. A browser screenshot can record the merchant page and any browser-rendered intermediate or return states that your test can reach. It cannot establish that an external app authorized a payment, that a callback was accepted, or that an order was updated correctly. NPCI describes the collect flow as selecting UPI, entering a payment address, receiving a request in a UPI app, and authorizing there with a UPI PIN. Never put a real PIN or production payment details in a screenshot test. See the NPCI UPI FAQ.

Keep three kinds of evidence distinct:

  • Visual: screenshot comparison for layout, styling, visible text, and responsive presentation.
  • Application behavior: assertions for page state, enabled controls, navigation, and order status transitions.
  • Payment outcome: provider sandbox confirmation, callback or test reporting as documented for your integration.

A passing pixel comparison only supports the first category. It is not payment authorization evidence.

Choose the states to test

List the states that matter to your merchant flow. Candidate states include:

  • Payment method selection before UPI is chosen.
  • UPI selected, with payment address entry or app-selection UI.
  • Pending while the application waits for a provider response.
  • Return from an external app or simulator.
  • Success, failure, timeout, and retry states.

Do not assume every state is available in a browser-only sandbox. Some handoffs require a provider simulator on an Android device. Record which provider, integration version, sandbox route, and fixture produce each state. Make state setup deterministic so the screenshot is checking the intended UI rather than a race or a live transaction.

Set up a provider sandbox that matches your integration

  1. Use the provider’s test endpoint and test credentials. Keep production credentials and live payment routes out of visual regression jobs.
  2. Read the current guide for the exact gateway product and SDK version in your application. Sandbox capabilities differ by provider and may change.
  3. Identify how the test environment produces pending, success, failure, and return states. Use documented test callbacks, simulator controls, or reporting. Do not fake a provider success by changing only the page DOM if the goal is to validate the integration.
  4. Document whether the tested route is a collect flow, an intent handoff, or another provider-specific flow.

For example, PayU’s documented Android Checkout Pro test instructions use the test endpoint https://test.payu.in/_payment and list test VPAs anything@upi and 9999999999@upi for certain UPI collect scenarios. That test mode documentation says UPI in-app and UPI intent are unavailable in the described mode. PayU’s Integration Lab separately documents a UPI Simulator app on an Android device for UPI Intent or complete UPI transaction tests. These are specific PayU routes; do not apply them to other gateways. Check the PayU Android Checkout Pro test integration guide and PayU Integration Lab guide for current instructions.

Create controlled Playwright screenshot baselines

Playwright’s screenshot assertions create a reference screenshot on the first run and compare subsequent runs against it. Rendering can vary across operating systems, browser versions, settings, hardware, power source, and headless mode. Generate and compare baselines in the same environment, with a fixed browser project, OS or container image, fonts, viewport, and device scale. Playwright’s Visual comparisons guide explains these constraints and its screenshot assertion behavior.

Install Playwright Test in your project using the official installation guide, then add a test such as the following. It assumes your application has a documented sandbox fixture route that renders the payment page in a deterministic UPI-selected state. Replace that route and the selectors with your own application contract; do not point it at a production checkout.

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

test('UPI checkout selected state stays visually stable', async ({ page }) => {
  await page.goto('/test-fixtures/checkout?state=upi-selected');

  await expect(page.getByRole('heading', { name: 'Pay with UPI' })).toBeVisible();
  await expect(page.getByLabel('UPI ID')).toBeVisible();
  await expect(page.getByRole('button', { name: 'Continue' })).toBeEnabled();

  await expect(page).toHaveScreenshot('upi-selected.png', {
    fullPage: true,
    animations: 'disabled',
  });
});

The first run creates the expected screenshot. Review that image and commit it as the baseline only after accepting the rendered state. Later runs compare against it. When an intended UI change is approved, regenerate the expected image using Playwright’s --update-snapshots option for the relevant project and inspect the resulting change before merging.

Run with a pinned project and viewport

Keep the browser and environment consistent in CI and local baseline work. A minimal Playwright configuration can make the viewport explicit:

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

export default defineConfig({
  testDir: './tests',
  projects: [
    {
      name: 'chromium- desktop',
      use: {
        ...devices['Desktop Chrome'],
        viewport: { width: 1440, height: 1000 },
        deviceScaleFactor: 1,
      },
    },
  ],
});

Use the actual browser project name and version you pin in your repository. A viewport alone does not make rendering identical across operating systems or browser builds. Avoid creating baselines on one host and comparing them on another unless the differences are understood and accepted.

Cover multiple states without conflating them

Use one named expectation per meaningful state. The following example demonstrates the pattern; the fixture route should set up a provider-documented or application-controlled test state.

import { test, expect } from '@playwright/test';

for (const state of ['upi-selected', 'pending', 'success', 'failure', 'retry']) {
  test(`checkout ${state} state`, async ({ page }) => {
    await page.goto(`/test-fixtures/checkout?state=${state}`);
    await expect(page.getByTestId('checkout-state')).toHaveAttribute('data-state', state);
    await expect(page).toHaveScreenshot(`checkout-${state}.png`, {
      fullPage: true,
      animations: 'disabled',
    });
  });
}

This example checks the merchant application’s rendered states; it does not itself exercise or verify a real provider transaction. Add separate integration tests for the sandbox behavior your provider supports.

Handle volatile content carefully

Dynamic timestamps, generated references, rotating banners, or changing test identifiers can create irrelevant diffs. Prefer fixed test fixtures or deterministic application data. If an element must vary, Playwright supports a screenshot style option to hide or normalize elements for that capture. Restrict this to data irrelevant to the visual behavior under test. Do not mask the selected payment method, pending/success/failure indicator, primary action, or any UI state the test is intended to protect. See the screenshot assertion options in the Playwright API reference.

Pair screenshots with behavior and transaction checks

Write separate assertions for each layer. For application behavior, check that the expected state is visible, the correct controls are enabled, and the expected route or order status appears after the action. For payment outcomes, trigger the provider’s sandbox scenario and verify the documented result through its callback, test report, or other provider-side evidence where available. A success-looking page can still conceal a failed callback or incorrect order update.

For an external-app handoff, browser automation may only be able to assert the merchant’s launch or return behavior. Use the provider’s supported simulator or device route for the parts that leave the browser. Keep screenshots of the merchant page separate from device or simulator evidence and label the test route precisely.

Review visual diffs and tune tolerances

Start with strict comparisons. When a diff occurs, inspect expected, actual, and diff images and determine whether the cause is an actual UI regression, an environment change, or volatile content. Playwright supports thresholds such as maxDiffPixels; raise tolerance only after identifying recurring harmless rendering variation. A loose threshold can hide a real small but important change, such as a clipped label or shifted payment button.

Keep the artifacts with enough context to reproduce the result: browser project and version, OS or container image, viewport, device scale, test state, and fixture or sandbox route. Playwright’s Trace Viewer can help investigate test failures, and visual comparison failures report screenshot artifacts for review.

cURL, Python, and Node.js: capture a page outside Playwright

For a one-off rendering check or a service-level screenshot, use the ScreenshotNeo API. These calls capture the supplied URL as an image; they do not run Playwright assertions, drive your provider sandbox, or confirm a transaction. Point the URL at a non-sensitive test page that is reachable by the API. See the ScreenshotNeo API documentation for options such as full-page capture, viewport, device scale, selector capture, wait conditions, custom headers, and output format.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://example.com/test-checkout \
  -o shot.webp

Python

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com/test-checkout"},
    timeout=90,
)
r.raise_for_status()
with open("shot.webp", "wb") as f:
    f.write(r.content)

Node.js

const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://example.com/test-checkout',
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(({ writeFile }) =>
  writeFile('shot.webp', Buffer.from(await res.arrayBuffer()))
);

Or skip the browser setup

Use a ScreenshotNeo call when you need a rendered screenshot without maintaining a browser capture service. It does not replace sandbox assertions or transaction confirmation: retain those checks for payment correctness.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com/test-checkout -o shot.webp

ScreenshotNeo accepts cookie and consent banners before capture and removes 60+ known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots. See the API documentation and ScreenshotNeo.

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

Troubleshooting

Symptom Likely cause What to do
Large diff on every run Baseline and comparison use different OS images, browser versions, fonts, scale, or headless settings. Pin the environment and browser project; create and compare baselines in that same environment.
Intermittent screenshot mismatch Animation, delayed content, network timing, or a nondeterministic fixture changes the capture. Control fixture data, wait for the relevant state, disable animations for the snapshot, and avoid arbitrary sleeps when a state assertion can provide a clear condition.
Baseline unexpectedly changes A snapshot was updated without reviewing the UI change, or generated in a different environment. Inspect expected, actual, and diff images; restore or regenerate only after a reviewer accepts the UI change.
Screenshot passes while payment test fails Visual output does not establish callback, authorization, or order correctness. Keep application assertions and provider sandbox or reporting checks separate from the screenshot assertion.
UPI intent or in-app route cannot be exercised The selected sandbox mode may not support that route. Check the exact provider integration guide; use its documented simulator or device route if required.
Screenshot contains sensitive or changing data Production-like data or uncontrolled fixture values are rendered. Use sandbox fixtures and non-sensitive test values; normalize only irrelevant volatile elements and protect stored artifacts according to your team’s policies.

Performance, reliability, and cost

  • Control the capture surface: full-page screenshots include more content and can expose lower-page layout changes; use them when the whole page matters. A focused element capture can reduce unrelated diffs, but only if the selector represents the state you need to protect.
  • Keep the matrix purposeful: each browser, viewport, and state adds capture work and artifacts. Cover the supported responsive breakpoints and important states rather than duplicating equivalent cases.
  • Make failures diagnosable: preserve screenshots and environment metadata, and avoid automatically accepting changed baselines.
  • Separate API capture from regression comparison: a screenshot API can render a URL, but your regression workflow still needs a known expected image and a comparison process. ScreenshotNeo offers caching with a chosen TTL; cache hits are not billed. Avoid relying on a cached result when the purpose is to observe a newly changed page; choose cache behavior and TTL to fit the check.
  • Budget the whole test: browser CI cost depends on your own runners and matrix. ScreenshotNeo’s plans are Free: 1,000 shots/month with no card; Starter: $5 for 3,000; Growth: $15 for 15,000; Pro: $39 for 60,000; Scale: $99 for 250,000; Business: $249 for 1,000,000. Yearly billing gives two months free, and every feature is on every plan. Only clean shots are billed; bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing.

Checklist before merging

  • Baselines and comparisons use the same pinned browser and environment.
  • The test uses a provider sandbox route and test credentials, never live payment credentials.
  • Each important merchant UI state has a named screenshot and functional assertions.
  • Volatile data is controlled or narrowly normalized; meaningful state is never hidden.
  • Provider-side evidence validates transaction outcomes separately where the sandbox supports it.
  • Every baseline update has been reviewed as a UI change.

FAQ

Does a matching screenshot mean the UPI payment succeeded?

No. It means the rendered page matched the expected image within the configured comparison rules. Verify payment outcome through the integration’s functional checks and provider sandbox evidence.

Can I test UPI intent entirely in a desktop browser?

Not necessarily. The route depends on provider support and may require a documented simulator or Android device. Confirm the capabilities of the exact sandbox mode you use.

Should I approve every changed snapshot in CI?

No. Treat a changed baseline as a review item. Accept it only after confirming the UI change is intended and the comparison environment is controlled.

Can ScreenshotNeo replace Playwright visual assertions?

It can capture a rendered URL, but it does not by itself supply your test fixture, expected baseline, application assertions, or provider transaction confirmation. Use the capture method that fits each part of your workflow.