Guides / Robot Framework

How to test SMS OTP verification in Robot Framework

Updated October 8, 2026
Short answer

Route your app's SMS to a mock inbox in the test environment. In Robot Framework, generate a unique phone number, store the time, trigger the SMS through the UI, then call a keyword that reads the inbox with RequestsLibrary inside Wait Until Keyword Succeeds. Type the returned code with SeleniumLibrary or Browser.

Step 1: route SMS to the mock

The application under 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. Robot only reads the inbox.

Step 2: the inbox API

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}
404 {"error":"no_code_yet"}

Step 3: libraries

requirements.txt
robotframework>=7.0
robotframework-requests>=0.9.7
robotframework-seleniumlibrary   # or robotframework-browser

Step 4: an otpmock resource file

Put the inbox keywords in one resource so every suite can import them. GET On Session with expected_status=200 fails on the 404 that means "not yet", which is exactly what Wait Until Keyword Succeeds needs to retry.

resources/otpmock.resource
*** Settings ***
Library    RequestsLibrary
Library    String

*** Keywords ***
Open Otpmock Session
    &{headers}=    Create Dictionary    Authorization=Bearer %{OTPMOCK_API_KEY}
    Create Session    otpmock    %{OTPMOCK_URL=https://api.otpmock.com}    headers=${headers}    verify=${True}

Random Phone
    ${digits}=    Generate Random String    7    [NUMBERS]
    RETURN    +1555${digits}

Now Minus Skew
    ${since}=    Evaluate    int(time.time() * 1000) - 5000    modules=time
    RETURN    ${since}

Latest OTP Should Exist
    [Arguments]    ${phone}    ${since}
    ${encoded}=    Evaluate    urllib.parse.quote($phone, safe='')    modules=urllib.parse
    &{params}=    Create Dictionary    since=${since}
    ${resp}=    GET On Session    otpmock    /v1/inbox/${encoded}/code    params=${params}    expected_status=200
    RETURN    ${resp.json()}[code]

Wait For OTP
    [Arguments]    ${phone}    ${since}    ${timeout}=15s
    ${code}=    Wait Until Keyword Succeeds    ${timeout}    300ms    Latest OTP Should Exist    ${phone}    ${since}
    RETURN    ${code}

Wait Until Keyword Succeeds returns the inner keyword's return value, so Wait For OTP hands the code straight back to the test.

Step 5: the test with SeleniumLibrary

tests/signup.robot
*** Settings ***
Library        SeleniumLibrary
Resource       ../resources/otpmock.resource
Suite Setup    Open Otpmock Session
Test Teardown  Close Browser

*** Test Cases ***
Phone Signup With SMS Code
    ${phone}=    Random Phone
    Open Browser    https://staging.example.com/signup    headlesschrome
    Input Text    name:phone    ${phone}
    ${since}=    Now Minus Skew
    Click Button    css:button[type=submit]
    ${code}=    Wait For OTP    ${phone}    ${since}
    Input Text    name:code    ${code}
    Click Button    css:button[type=submit]
    Wait Until Page Contains    Welcome

With the Browser library (Playwright)

Only the UI keywords change; the otpmock resource stays the same.

tests/signup_browser.robot
*** Settings ***
Library        Browser
Resource       ../resources/otpmock.resource
Suite Setup    Open Otpmock Session

*** Test Cases ***
Phone Signup With SMS Code
    ${phone}=    Random Phone
    New Page    https://staging.example.com/signup
    Fill Text    input[name=phone]    ${phone}
    ${since}=    Now Minus Skew
    Click    button[type=submit]
    ${code}=    Wait For OTP    ${phone}    ${since}
    Fill Text    input[name=code]    ${code}
    Click    button[type=submit]
    Get Text    body    contains    Welcome

Running in CI

Set OTPMOCK_API_KEY as a secret and run robot tests/. If the endpoint uses a private CA, point requests at it: either export REQUESTS_CA_BUNDLE, or pass the path as verify=%{REQUESTS_CA_BUNDLE} in Create Session. Do not use verify=${False} outside local experiments. With pabot each test still uses its own number, so parallel runs never read each other's codes.

Common pitfalls

FAQ

Do I need a custom Python library?

No. RequestsLibrary and the built-in Wait Until Keyword Succeeds cover the whole flow.

Does it work for mobile tests with AppiumLibrary?

Yes. Reading the code is the same HTTP call; only the keywords that type it change.

Try it on your own suite

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

Get a free API key

Related guides