How to Take Screenshots of a Logged-In UPI Merchant Dashboard in a Test Environment
Capture an authenticated UPI merchant dashboard safely with Playwright, using test credentials, protected browser state, and a clear readiness check.
Use an authorized test merchant account, authenticate through the provider’s approved test flow, then use Playwright to capture the dashboard after a meaningful page element confirms it is ready. Keep the saved browser state private: it can contain cookies and headers that act like credentials. This guide uses generic selectors because the provider and dashboard are unspecified; confirm the exact test flow and page elements for your account.
1. Confirm the provider and test environment
Before automating a login or capture, confirm which provider and merchant account you are using, and that the account is in its approved test or sandbox environment. Use only test credentials in the script. Do not copy production credentials into local configuration, CI variables intended for test runs, or screenshots.
Razorpay is one documented example, not a requirement for this workflow: its Test and Live modes use separate API keys, test-created entities remain isolated from Live Mode, and Test Mode does not use real money. These details apply to Razorpay; check your provider’s own current documentation for its environment boundaries and UPI testing options. See Razorpay documentation and its Test Mode guidance.
2. Install Playwright
The examples below use Node.js and Playwright. They save authentication state in a local ignored directory, then reuse it in a separate capture script. Install the package and browser:
npm init -y
npm install --save-dev playwright
npx playwright install chromium
mkdir -p playwright/.auth
Add the state directory to .gitignore before creating credentials or state files:
printf '\nplaywright/.auth/\n' >> .gitignore
If your repository uses another browser or an existing Playwright installation, keep the installed browser version consistent between baseline and later captures.
3. Authenticate using the approved test flow
Prefer the provider’s supported login UI or an approved authentication API. Do not bypass access controls or use a production session to create a test artifact. The following setup script shows the shape of a UI login; replace the URL, selectors, and test-only environment variable names with those documented for your provider.
// save-auth.mjs
import { chromium } from 'playwright';
const loginUrl = process.env.TEST_DASHBOARD_LOGIN_URL;
const username = process.env.TEST_MERCHANT_USERNAME;
const password = process.env.TEST_MERCHANT_PASSWORD;
if (!loginUrl || !username || !password) {
throw new Error('Set TEST_DASHBOARD_LOGIN_URL, TEST_MERCHANT_USERNAME, and TEST_MERCHANT_PASSWORD');
}
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
try {
await page.goto(loginUrl, { waitUntil: 'domcontentloaded' });
// Replace these selectors with the provider's actual test login controls.
await page.getByLabel('Email').fill(username);
await page.getByLabel('Password').fill(password);
await page.getByRole('button', { name: /sign in|log in/i }).click();
// Wait for a post-login condition, not just a fixed delay.
await page.getByRole('heading', { name: /dashboard/i }).waitFor({ state: 'visible', timeout: 30000 });
await page.context().storageState({ path: 'playwright/.auth/merchant-test.json' });
} finally {
await browser.close();
}
Set the environment variables through your local secret manager or CI’s protected test-secret facility, then run:
TEST_DASHBOARD_LOGIN_URL='https://provider-test.example/login' \
TEST_MERCHANT_USERNAME='test-user' \
TEST_MERCHANT_PASSWORD='test-password' \
node save-auth.mjs
The example domain and labels are placeholders, not a real provider endpoint or guaranteed dashboard text. Use the provider’s actual test URL, login flow, and selectors. If login requires multi-factor authentication, an approval step, or a provider-managed identity flow, follow its supported test procedure rather than hard-coding a workaround.
4. Capture the authenticated dashboard
Create a separate script that loads the saved state, navigates to the desired dashboard view, waits for evidence that the page content is ready, and writes a screenshot. Replace the URL and selectors with stable elements from the intended test page.
// capture-dashboard.mjs
import { chromium } from 'playwright';
const dashboardUrl = process.env.TEST_DASHBOARD_URL;
if (!dashboardUrl) throw new Error('Set TEST_DASHBOARD_URL');
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
storageState: 'playwright/.auth/merchant-test.json',
viewport: { width: 1440, height: 1000 },
deviceScaleFactor: 1
});
const page = await context.newPage();
try {
await page.goto(dashboardUrl, { waitUntil: 'domcontentloaded', timeout: 60000 });
// Prefer a page-specific readiness condition, such as the transaction table
// and a known test row, if your workflow creates one.
await page.getByRole('heading', { name: /dashboard/i }).waitFor({ state: 'visible', timeout: 30000 });
await page.locator('[data-testid="transactions-table"]').waitFor({ state: 'visible', timeout: 30000 });
// Viewport capture: what is visible at the configured viewport.
await page.screenshot({ path: 'upi-dashboard-test.png', animations: 'disabled' });
// Optional: capture only a focused panel instead.
// await page.locator('[data-testid="transactions-table"]').screenshot({ path: 'upi-transactions-test.png' });
// Optional: capture the complete scrollable page.
// await page.screenshot({ path: 'upi-dashboard-full-test.png', fullPage: true, animations: 'disabled' });
} finally {
await context.close();
await browser.close();
}
Run it with the provider’s test dashboard URL:
TEST_DASHBOARD_URL='https://provider-test.example/merchant/dashboard' node capture-dashboard.mjs
The [data-testid="transactions-table"] selector is illustrative. If the dashboard has no test ID, use a stable accessible role and name or another selector that describes the page content. Avoid waiting only for a short fixed delay: network timing varies, and a delay does not prove that the relevant table or test transaction appeared.
5. Choose the screenshot scope and verify the artifact
| Capture | Use it when | Playwright option |
|---|---|---|
| Viewport | You need a consistent view of the dashboard’s current screen. | page.screenshot({ path: 'shot.png' }) |
| One element | You need a transaction table, status panel, or other focused evidence. | locator.screenshot({ path: 'panel.png' }) |
| Full page | Relevant content continues below the fold. | page.screenshot({ path: 'full.png', fullPage: true }) |
After capture, open the file and check that the intended test account and state are visible, the page is not still loading, and no unrelated personal information, credentials, tokens, or production data appear. Label artifacts clearly as test or sandbox evidence where appropriate. Do not put authentication-state files in the same place as shareable screenshots.
6. Keep repeat captures consistent
- Use the same browser engine, browser version, operating system or container image, viewport, device scale factor, and headless setting for baseline and later captures.
- Wait for a meaningful page condition such as the expected heading, table, or test transaction row. Use a transaction-specific condition when the capture is meant to prove that transaction’s state.
- Disable animations for more stable captures with
animations: 'disabled'. If the dashboard contains timestamps or changing values, account for them in your visual comparison process. - Use the same test data and page state for comparisons. A changed transaction list can alter a screenshot even when the application layout is unchanged.
Playwright notes that screenshots can vary with operating system, browser version, settings, hardware, and headless mode. When pixel-level comparison matters, keep the capture environment consistent. See Playwright visual comparisons.
7. Handle authentication state and artifacts safely
- Treat
playwright/.auth/merchant-test.jsonas a credential. Playwright warns that storage state can contain sensitive cookies and headers that could impersonate the user. - Keep the auth directory out of version control, build logs, public CI artifacts, and shared screenshots. Restrict access to any CI secret and artifact storage used for the test.
- Regenerate the saved state when it expires or when the provider revokes the session. Do not make a workflow “reliable” by weakening authentication controls.
- Use a dedicated test account with only the access needed for the capture. Avoid capturing account settings, personal data, secrets, or unrelated merchant information.
- Check the provider’s rules for test-account access and data retention before sharing screenshots outside the team.
Playwright’s authentication guide explains reusable storage state and its sensitivity.
8. cURL, Python, and Node.js alternatives
A logged-in page requires an authenticated browser context when its session depends on browser cookies, local storage, or interactive login. For maximum control over provider-specific authentication, use the Playwright workflow above. The following examples show how to automate a browser from Python or call Playwright’s browser through Node.js. A plain cURL request is included for diagnosis, but it is not a general replacement for browser authentication or rendering.
Python with Playwright
Install the Python package and browser:
python -m pip install playwright
python -m playwright install chromium
Save this as capture_dashboard.py. It reuses the state file created by an equivalent approved login setup and waits for a page-specific element:
import os
from pathlib import Path
from playwright.sync_api import sync_playwright
url = os.environ.get("TEST_DASHBOARD_URL")
if not url:
raise RuntimeError("Set TEST_DASHBOARD_URL")
state = Path("playwright/.auth/merchant-test.json")
if not state.is_file():
raise RuntimeError("Create the test account storage state through the approved login flow first")
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
context = browser.new_context(
storage_state=str(state),
viewport={"width": 1440, "height": 1000},
device_scale_factor=1,
)
page = context.new_page()
try:
page.goto(url, wait_until="domcontentloaded", timeout=60000)
page.get_by_role("heading", name="Dashboard").wait_for(state="visible", timeout=30000)
page.locator('[data-testid="transactions-table"]').wait_for(state="visible", timeout=30000)
page.screenshot(path="upi-dashboard-test.png", animations="disabled")
finally:
context.close()
browser.close()
Adjust the accessible name and test ID to match the provider’s actual UI. For an element-only capture, call page.locator('...').screenshot(path='panel.png'); for a full-page capture, pass full_page=True to page.screenshot.
Node.js with Playwright
The runnable Node.js capture is the capture-dashboard.mjs script in section 4. It reads the URL from TEST_DASHBOARD_URL, loads the saved state, sets a fixed viewport, waits for the dashboard and transaction table, and writes a PNG. For an authenticated page, that browser context is the important part: the service must be authorized to access the specific test account and URL.
cURL for a basic access check
cURL can request an HTTP page, but it does not execute JavaScript or reproduce a browser’s login flow. If your provider explicitly documents a cookie-based test session and the page can be fetched without browser rendering, a request can help diagnose a redirect or access response. Do not paste session cookies into shell history, source control, logs, or a shared terminal transcript.
curl --fail --location --silent --show-error \
--cookie "$TEST_SESSION_COOKIE" \
--output response.html \
"$TEST_DASHBOARD_URL"
This is not a screenshot command. A successful HTML response may still be only a login shell or an empty JavaScript application. For a rendered capture, use the authenticated Playwright browser context.
9. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Capture shows the login page | The state file is missing, expired, saved before login completed, or for a different provider environment. | Confirm the login setup reached a post-login condition, save state afterward, check the test URL and account, then regenerate state. |
| Dashboard loads but transaction table is absent | The selector is a placeholder, the selected page has no transactions, or the page has not finished loading its data. | Inspect the authorized test page, replace the selector, and wait for the actual table or known test row. Confirm test data exists. |
| Navigation times out | The provider is slow, redirects through an identity flow, or a network request is blocked. | Check the test URL and network access, use a reasonable navigation timeout, and wait for a page-specific condition after DOM content loads. Diagnose redirects without exposing session values. |
| Login script cannot find a field or button | The provider uses different labels, nested frames, or a changed login flow. | Use current provider documentation and inspect the test login page. Update the accessible labels or use the documented frame and authentication flow. |
| State file works locally but fails in CI | CI cannot access the file, the state expired, or the environment differs. | Create or securely provide fresh test state in CI, verify file permissions and path, and avoid publishing it as an artifact. |
| Screenshot differs between runs | Browser or host differences, animations, dynamic data, timestamps, or a different dashboard state. | Pin the capture environment, viewport, and test data; disable animations; wait for the same content; handle expected dynamic fields in comparison rules. |
| Screenshot contains sensitive details | The selected page or viewport includes unrelated account data. | Capture a narrower element or test-only page, use a minimally privileged account, and review the artifact before sharing. |
10. Performance, reliability, and cost
For one screenshot, browser startup and authentication setup are usually the main workflow costs; reusing a saved test state avoids repeating the login UI for every capture. State reuse is convenient but remains sensitive and can expire, so refresh it through the approved test flow when needed. Avoid adding arbitrary long sleeps: they slow each run and still do not prove that the dashboard is ready.
For repeatable CI captures, keep the browser version and execution environment fixed, wait for a stable page-specific condition, and make test data deterministic where possible. A screenshot records the rendered state at one moment; it does not by itself prove a payment completed or establish that the provider supports a particular UPI simulation. Verify payment status through the provider’s documented test workflow and dashboard. Razorpay’s guidance, for example, describes reviewing test transaction status in the dashboard; available methods depend on account configuration. See Razorpay Test Mode.
Playwright is open-source browser automation software; this workflow does not require a screenshot API. Compute, browser execution time, and CI usage may have costs in your environment. Keep captures scoped to what the test needs to limit runtime and avoid storing unnecessary sensitive information.
11. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. Its one-request API returns a screenshot or PDF, but an authenticated dashboard still requires access the service can use; do not send production credentials or session cookies unless your security policy explicitly allows that. For a public or otherwise appropriately accessible test page, the basic call is:
See the ScreenshotNeo API documentation for request options.
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,
)
r.raise_for_status()
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);
These example calls use the supplied Stripe URL. Replace it only with a URL you are authorized to capture and configure the API request for your test workflow. ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Every feature is on every plan. For an authenticated merchant dashboard, first confirm that the required access method is supported and permitted for your account.
Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.
12. FAQ
Can I use a live merchant account if I only take a screenshot?
Use the provider-approved test account and environment for test evidence. A screenshot does not make production data safe to expose or transform a live workflow into a sandbox test.
Will this process run a UPI payment?
No. It captures a rendered dashboard. Whether and how a UPI transaction can be simulated depends on the provider’s current test documentation and account configuration.
Should I use a viewport or full-page screenshot?
Use a viewport for a consistent screen view, an element capture for focused evidence, and full-page capture when relevant content is below the fold.
Why can two captures differ when my code is unchanged?
Browser, operating system, headless mode, dynamic dashboard data, timing, and viewport can all affect rendered pixels. Keep those inputs consistent when comparing images.


