ScreenshotNeo

BlogHow-to

How to Handle an Indian Mobile Number OTP Prompt in Selenium Screenshot Tests

Make OTP prompt screenshots repeatable without depending on live SMS. Choose a fixture, test token, or provider-supported test number, then capture the state reliably.

By the ScreenshotNeo team4 October 20269 min read

For a stable Selenium screenshot of an Indian mobile number OTP prompt, avoid waiting for a real SMS. If you only need to verify the screen, render it with controlled fixture data. If you need to exercise authentication, use a test-only token or a fictional phone number and fixed code supported by your provider, in an isolated test environment. The exact setup depends on your application and OTP provider; an Indian country code alone does not identify a universal test flow.

Keep visual assertions separate from live SMS delivery checks. Control the displayed number, OTP state, browser, viewport, fonts, and execution environment so screenshot differences reflect changes in your UI rather than changes in the environment.

1. Pick the test that matches the screenshot

Approach Use it for What it verifies Trade-off
Mocked or fixture prompt Visual screenshot baselines Prompt layout, displayed number, empty or filled code, validation and error states Does not verify the live authentication backend.
Test-only token or controlled 2FA bypass Testing the login journey in a test environment The application flow beyond the OTP step Must be isolated from production and access-controlled.
Provider-supported fictional number and fixed code Provider integration tests The provider’s test authentication flow without actual SMS Availability and configuration vary by provider and project.
Real SMS end-to-end test Checking message delivery and live verification Actual SMS delivery and verification behavior Depends on external delivery, throttling, and timing, so it is a poor default for screenshot baselines.

Selenium’s guidance says automating two-factor authentication is difficult to keep seamless and consistent. It suggests a special test token or disabling 2FA in a controlled test environment when the journey still needs coverage. Selenium: Two Factor Authentication.

2. Configure a safe, repeatable OTP state

  1. Decide what the image must prove. For a visual assertion, identify the exact UI state: number-entry screen, code-entry screen, invalid-code message, or success state. Feed that state from a test fixture or mock response rather than a live SMS round trip.
  2. Use a dedicated test lane for authentication tests. Add a test-only token or provider test configuration that is unavailable to production. Restrict test users to test data and the minimum required permissions.
  3. Use an Indian-format value only when the UI or validation needs it. Choose a fictional number accepted by your provider’s documented test setup. Don’t assume a number from a US documentation example works for an Indian-number case.
  4. Fix the displayed inputs and expected state. Keep the number, code-entry state, error text, and test account stable between runs. Avoid depending on timestamps, random identifiers, or asynchronously changing content in the captured region.
  5. Pin the rendering conditions. Use the same browser version, operating system or CI image, viewport, device scale factor, and fonts for baseline creation and comparison. Screenshot comparisons can change across platforms and environments; Android’s guidance recommends consistent conditions, such as CI or cloud execution, for pixel-perfect comparisons. Android: Screenshot testing.
  6. Keep live delivery checks separate. If SMS delivery matters, run a separate integration check with its own delivery assertions and retry policy. Don’t make a pixel baseline depend on carrier timing.

3. Selenium example: capture a controlled OTP screen

This Python example assumes the application has a test-only route that renders the OTP form using fixture state and does not send a message. Replace the example host and selectors with the route and stable selectors from your app. The example captures the screen after the code-entry form is visible.

from pathlib import Path
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait

TEST_URL = "https://your-test-app.example.test/test/otp?case=code-entry"

options = webdriver.ChromeOptions()
options.add_argument("--headless=new")
options.add_argument("--window-size=1440,1000")

# In CI, pin the Chrome and driver versions in the environment.
driver = webdriver.Chrome(options=options)
try:
    driver.get(TEST_URL)

    wait = WebDriverWait(driver, 15)
    number = wait.until(EC.visibility_of_element_located(
        (By.CSS_SELECTOR, "[data-testid='otp-phone-number']")
    ))
    code_input = wait.until(EC.visibility_of_element_located(
        (By.CSS_SELECTOR, "[data-testid='otp-code']")
    ))

    assert number.text.strip() == "+91 90000 00000"
    assert code_input.is_displayed()

    # Capture a stable UI state. Prefer an element screenshot if the test
    # concerns only the OTP card; use save_screenshot for the whole viewport.
    otp_card = driver.find_element(By.CSS_SELECTOR, "[data-testid='otp-card']")
    Path("artifacts").mkdir(exist_ok=True)
    otp_card.screenshot("artifacts/otp-card.png")
finally:
    driver.quit()

For an error-state baseline, configure the fixture route to render the invalid-code state directly, or submit an intentionally invalid code against a test-only backend and wait for the stable error element. Avoid repeatedly guessing codes against a live provider: that can trigger throttling and makes the test depend on backend behavior.

Use an element or page screenshot?

  • Element screenshot: captures only the OTP component. It reduces unrelated changes from navigation, headers, and page content.
  • Viewport screenshot: useful when layout, page context, or responsive placement is part of the assertion.
  • Full-page screenshot: useful for long pages, but usually unnecessary for a single OTP prompt. For lazy-loaded content, ensure the page has reached the intended state before capture.

4. Indian number formatting and OTP verification are separate

Formatting a number as an international number, such as one beginning with +91, does not establish that it is assigned, reachable, controlled by the test user, or able to receive a code. Google’s libphonenumber supports international parsing, formatting, and validation; it does not receive an OTP or prove number ownership. Google libphonenumber.

Google Identity Platform’s registered test numbers use E.164 format and six-digit codes; its documentation says test sign-in sends no SMS. Firebase also documents fictional phone numbers for development tests without actual SMS. These are provider-specific mechanisms, not a general guarantee that any Indian-format fixture will work. Configure a number and code supported by the actual project and provider. Identity Platform: Test phone numbers · Firebase: Test with fictional phone numbers.

5. Protect test numbers, codes, and bypasses

  • Keep test numbers, fixed codes, and test tokens in restricted test configuration; don’t commit secrets to source control.
  • Use values that are hard to guess, limit who can use the test lane, and rotate test credentials where appropriate.
  • Give test users restricted roles or claims. A successful fictional-number sign-in may create a user with a valid ID token and real-user-like access characteristics.
  • Make test bypasses impossible to enable in production through environment separation and deployment checks.
  • Do not leave app verification disabled or fictional-number settings active in production. Firebase explicitly warns against production use of these testing settings.

Identity Platform recommends securing and rotating test numbers. Its documentation allows up to 10 test phone numbers. Firebase likewise documents up to 10 fictional numbers for development. Treat the resulting identities as security-sensitive. Identity Platform guidance · Firebase guidance.

6. Make screenshot comparisons reliable

  • Wait for a condition, not an arbitrary pause. Wait until the OTP component and its expected state are visible. Add a short fixed delay only for a known rendering transition that cannot be observed directly.
  • Stabilize dynamic regions. Freeze clocks and fixture data where possible; mask or exclude changing regions if your comparison tool supports it.
  • Use stable selectors. Prefer test IDs or semantic selectors maintained by the app over brittle positional selectors.
  • Control layout inputs. Set viewport and device scale factor consistently, load the same fonts, and avoid variable network-dependent assets in the captured component.
  • Keep the capture narrow. Compare the OTP card when that is the behavior under test. Use page captures only when surrounding layout is relevant.
  • Save failure artifacts. On a failed assertion, preserve the screenshot and relevant browser logs so you can distinguish a layout regression from a fixture or navigation failure.

7. Troubleshooting

Symptom Likely cause Fix
The test waits forever for the code field The route did not reach the OTP state, a selector changed, or the app redirected. Check the current URL and page content on failure. Confirm the fixture state and selector, then wait for the actual state transition.
The provider sends a real SMS during a test The number is not registered as a provider test number, or the test configuration is not active for this project. Verify the provider’s test configuration and project/environment. Do not assume a documentation example is configured in your app.
A fixed code is rejected The configured code and number do not match, the wrong environment is in use, or the provider does not support that test setup. Check the test-number registration and configured code for the exact project. Keep visual tests on fixtures if provider behavior is not what the screenshot needs to verify.
The screenshot differs across machines Browser, OS, fonts, viewport, device scale factor, or rendering timing differs. Run baseline and comparison in the same pinned CI environment with the same viewport and fonts.
The Indian number is rejected before OTP delivery Input formatting, country selection, validation rules, and provider test-number configuration are being conflated. Check the app’s expected international format and provider setup independently. A number library can help with formatting and validation, but cannot supply or verify an OTP.
Authentication passes but the test user can access too much The test identity has production-like permissions or the test configuration creates a valid authenticated user. Restrict test roles/claims and test data; isolate the test project and rotate credentials.
Tests intermittently hit throttling or fail after retries The flow depends on real SMS or repeated provider requests. Keep baseline screenshots on fixtures. Run a limited live delivery check separately and follow provider-specific limits.

8. Cost and execution notes

A fixture-based screenshot test avoids per-run SMS delivery dependencies, but still uses browser execution time and CI resources. A real SMS flow adds provider and delivery dependencies; Firebase documentation notes SMS throttling. Keep expensive or externally variable checks out of every visual baseline run, and run them separately at a frequency appropriate to the integration risk.

For pixel-sensitive comparisons, consistent CI or cloud execution can reduce environment variation. It does not remove the need to control fixture data, rendering inputs, and state transitions.

Or skip the browser setup

If you need a screenshot of a public page or a rendered OTP test page, ScreenshotNeo is a website screenshot API and MCP server. A request returns an image or PDF; it does not replace your app’s authentication test or configure provider test numbers. See the ScreenshotNeo API documentation.

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

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://your-test-page.example.test/otp"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://your-test-page.example.test/otp'
});
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', new Uint8Array(await res.arrayBuffer()));

Use a page that is safe to access through the API; do not put OTP secrets or private authenticated pages in a public URL. ScreenshotNeo removes cookie banners, popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed, and response headers report the page verdict and billing status. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000.

Start with 1,000 free screenshots a month, no card required.

FAQ

Can Selenium read an OTP from an Indian phone number?

Selenium controls the browser; it does not receive SMS. Use a provider test setup or a separate SMS integration if message delivery itself is what you need to verify.

Does +91 make a number valid for an OTP test?

No. Formatting and number validation are separate from provider test-number registration, SMS delivery, and proof of ownership.

Should the screenshot test submit a real OTP?

Usually not for a visual baseline. Render the needed state deterministically, and keep any live delivery check as a separate test.

Can a fictional-number account be treated as harmless?

No. Provider documentation describes test users with real-like authentication properties, including valid ID tokens. Restrict permissions and keep test identities isolated.