Guides / Selenium

How to test SMS OTP verification in Selenium

Updated October 8, 2026
Short answer

Route your app's SMS to a mock inbox in the test environment. In the Selenium test, use a unique phone number, note the time, trigger the SMS, then poll the inbox's HTTP API for a code sent to that number after that time and type it into the page. It works the same in Python, Java or any other binding.

Step 1: route SMS to the mock

Your application, not the test, sends the SMS. In the test or staging environment, point your provider's SDK at otpmock with one option. Each provider's option is on its provider page; otpmock emulates fifteen, including Twilio, Vonage, AWS SNS, Infobip, Sinch and Plivo.

Step 2: the inbox API your test calls

One request returns the newest code sent to a number after a timestamp, or 404 if none has arrived yet:

HTTP
GET https://api.otpmock.com/v1/inbox/{phone}/code?since={unix_ms}
Authorization: Bearer $OTPMOCK_API_KEY

200 {"code":"482913","messageSid":"SM…","body":"Your code is 482913","receivedAt":1791417652342}

Python

tests/otp.py
import os, random, time, urllib.parse
import requests

API = os.environ.get("OTPMOCK_URL", "https://api.otpmock.com")
KEY = os.environ["OTPMOCK_API_KEY"]

def random_phone():
    return "+1555" + "".join(random.choices("0123456789", k=7))

def wait_for_code(phone, since_ms, timeout=15):
    url = f"{API}/v1/inbox/{urllib.parse.quote(phone)}/code"
    deadline = time.time() + timeout
    while time.time() < deadline:
        r = requests.get(url, params={"since": since_ms}, headers={"Authorization": f"Bearer {KEY}"})
        if r.status_code == 200:
            return r.json()["code"]
        if r.status_code != 404:
            r.raise_for_status()
        time.sleep(0.3)
    raise TimeoutError(f"no code for {phone}")
tests/test_signup.py
from selenium.webdriver.common.by import By
from otp import random_phone, wait_for_code
import time

def test_phone_signup(driver):
    phone = random_phone()
    driver.get("https://staging.example.com/signup")
    driver.find_element(By.NAME, "phone").send_keys(phone)
    since = int(time.time() * 1000) - 5000  # small margin for clock skew
    driver.find_element(By.CSS_SELECTOR, "button[type=submit]").click()

    code = wait_for_code(phone, since)
    driver.find_element(By.NAME, "code").send_keys(code)
    driver.find_element(By.CSS_SELECTOR, "button[type=submit]").click()

Java

OtpMock.java
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.*;
import java.nio.charset.StandardCharsets;
import java.util.regex.*;

public final class OtpMock {
  private static final HttpClient HTTP = HttpClient.newHttpClient();
  private static final String API = System.getenv().getOrDefault("OTPMOCK_URL", "https://api.otpmock.com");
  private static final String KEY = System.getenv("OTPMOCK_API_KEY");

  public static String waitForCode(String phone, long sinceMs) throws Exception {
    String url = API + "/v1/inbox/" + URLEncoder.encode(phone, StandardCharsets.UTF_8) + "/code?since=" + sinceMs;
    long deadline = System.currentTimeMillis() + 15_000;
    while (System.currentTimeMillis() < deadline) {
      HttpResponse<String> r = HTTP.send(
          HttpRequest.newBuilder(URI.create(url)).header("Authorization", "Bearer " + KEY).build(),
          HttpResponse.BodyHandlers.ofString());
      if (r.statusCode() == 200) {
        Matcher m = Pattern.compile("\"code\":\"([^\"]+)\"").matcher(r.body());
        if (m.find()) return m.group(1);
      } else if (r.statusCode() != 404) {
        throw new IllegalStateException("otpmock " + r.statusCode() + ": " + r.body());
      }
      Thread.sleep(300);
    }
    throw new IllegalStateException("no code for " + phone);
  }
}

Use your JSON library of choice instead of the regex if one is already on the classpath.

Selenium Grid and parallel runs

Each test generates its own number, so parallel sessions never read each other's codes. The test runner only needs outbound HTTPS to api.otpmock.com; the browser nodes don't talk to otpmock at all.

Common pitfalls

FAQ

Does this work with Selenium IDE or other languages?

Any test that can make an HTTP request can read codes. For C#, Ruby or JavaScript bindings, port the polling loop above; it is a single GET request.

Do I need to change application code?

Only where the SMS client is created, behind an environment variable that is set in test environments.

Try it on your own suite

The free plan includes 100 messages a month. No card required.

Get a free API key

Related guides