WebdriverIO and Twilio API – OTP Test

    Over the past six years, WebdriverIO has been my go-to framework for test automation, thanks to its incredible flexibility and extensibility. Whether it’s handling complex UI interactions, API testing, or custom integrations, WebdriverIO never failed me to serve my purpose.

    One particular challenge I’ve tackled is automating OTP (One-Time Password) verification—a critical security layer in modern web applications. With rising cybersecurity threats, OTPs have become a standard method for securing user accounts, making it essential to test these flows reliably in automation.

    In this article, I’ll walk you through how I have implemented OTP verification with WebdriverIO in my automation test suite, ensuring mirror real-world user behavior while maintaining security and efficiency.

    Scenario

    Here is in Foxtel, while was designing automated tests for user signup, we came acoross to this OTP feature which had to be automated. After discussing with the team, we decided to go with Twilio. There are several reasons for going with Twilio, such as:

    • You can buy phone numbers as many as you need, not so expensive.
    • Stable and easy to use REST API
    • Offers SDKs for different languages including NodeJS.
    • Provides a sandbox environment for testing
    • And I only have used this service :D.

    Prerequisites

    Before jumping in, I presume that:

    • You already have a Twilio account (free trial available)
    • You have installed and configured WebdriverIO in your project to test with.
    • You haver typescript installed, use this command to install Typescript:
      • npm install --save-dev typescript @types/node
      • A tsconfig.json file in the root of the project folder.
    • Your Node.js environment functional

    Let’s dive in!

    Step 1: Setting up Twilio (including SDK)

    • Create a Twilio account, signup at Twilio (trial available).
    • Get your credentials:
      • Account SID
      • Auth Token
      • Twilio phone number
    • Install the Twilio Node.js SDK:
    npm install twilio

    Step 2: Create a Twilio Helper

    TwilioHelper.ts
    import {default as twilio} from 'twilio';
    import _ from 'lodash';
    import { execSync } from 'child_process';
    
    class TwilioHelper {
      getSmsVerificationMessage = async (toNumber: string) => {
    
        interface OTPMessage {
          body?: string
        }
    
        const accountSid = process.env.accountSid;
        const authToken = process.env.authToken;
        const client = twilio(accountSid, authToken);
        const nowDate = new Date().toLocaleString('au-AU', { timeZone: 'UTC' }); // Change it to your own locale
        const todayDate = new Date(nowDate);
        // Setting up a time range to capture one message from multiple messages
        // This is important as we are using the same number
        // and that number might be flooded with multiple OTP messages
        const dateAfter = new Date(Date.UTC(todayDate.getFullYear(), todayDate.getMonth(), todayDate.getDate(), todayDate.getHours(), todayDate.getMinutes()-1, todayDate.getSeconds()));
        const dateBefore = new Date(Date.UTC(todayDate.getFullYear(), todayDate.getMonth(), todayDate.getDate(), todayDate.getHours(), todayDate.getMinutes()+2, todayDate.getSeconds()));    
        let otpMessage: OTPMessage = {};
        let counter = 0;
    
        // This while loop is a bloody brute force attempt 
        // to tackle all sorts of timeouts and noises 
        // please use your skill to improve it
        while (!otpMessage.hasOwnProperty('body') && counter<3) {
          execSync('sleep 20');
          let messages: any[] = [];
          let messageList: any[] = [];
          
          messages = await client.messages.list({ limit:2, dateSentAfter: dateAfter, dateSentBefore: dateBefore });
          messages.forEach(async (message) => {
            messageList.push(message);
          });
          
          if (messageList.length > 0) {
            otpMessage = _.head(_.filter(messageList, function(o) {
              return o.to === toNumber;
            }));
    
            if (!otpMessage) {
              otpMessage = {};
            } 
          }
          counter++;
        };
        return otpMessage.body;
      };
    
      getVerificationCode = async (toNumber: string) => {
        const message: any = await this.getSmsVerificationMessage(toNumber);
        let code: string = '';
        if(message) {
          code = message.replace(/(\r\n|\n|\r)/gm, '');
          let arr = code.match(/\d+/);
    
          if(arr) {
            code = arr[0];
          }
        }
        return code;
      };
    }
    
    export default new TwilioHelper();Code language: JavaScript (javascript)

    Step 3: Write test to verify OTP

    A sample test written with Wdio and MochaJS.

    wdio-twilio.ts
    // tests/otpVerification.spec.ts
    import TwilioHelper from '../utils/TwilioHelper'; // assume you have put the helper class inside `utils` folder
    import { locators } from '../locators/twilio.locators';
    
    const phoneNumber = '<your-phone-number-with-country-code>'; // Test recipient number
    
    describe('OTP Verification Test', () => {
      it('should verify OTP via SMS', async () => {
        // Navigate to login page
        // And put other script to navigate to the OTP screen
        
        // Enter phone number and request OTP
        const phoneInput = await $(locators.phoneNumberInput); //or whatever element details
        await phoneInput.setValue(phoneNumber.replace('+', ''));
        
        const requestOTPButton = await $(locators.requestOTPButton);
        await requestOTPButton.click();
        
        // Wait for OTP to be sent (adjust as needed)
        await browser.pause(5000);
        
        // Retrieve OTP from Twilio
        let otp: string;
        try {
          otp = await TwilioHelper.getVerificationCode(phoneNumber);
        } catch (error) {
          throw new Error(`Failed to retrieve OTP: ${error.message}`);
        }
        
        // Enter OTP in application
        const otpInput = await $(locators.otpInput);
        await otpInput.setValue(otp);
        
        const verifyButton = await $(locators.verifiyButton);
        await verifyButton.click();
        
        // Verify successful authentication
        await expect(browser).toHaveUrlContaining('/dashboard'); // or whatever landing URL is
      });
    });Code language: JavaScript (javascript)

    Step 4: Run test

    If you provide all necessary information correctly, such as navigating URL, phone number, Twilio details (account sid and auth token), then upon running the command npm run wdio should run your test showing OTP verification.

    This tiny example project is available in my Github, feel free to clone or fork and play with it.

    Best Practices

    1. Store Twilio credentials securely in environment variables
    2. Implement error handling for message retrieval failures (e.g., timeout, api failure etc.)
    3. Tailor the OTP extraction logic based on your message format
    4. Be aware of Twilio’s rate limits in the production

    Conclusion

    The goal of this article was to demonstrate how to automate OTP verification in end-to-end testing by mimicking real user behavior, eliminating manual intervention, and enabling seamless CI/CD integration. While Twilio served as an example for retrieving OTPs programmatically, the same approach can be adapted to other SMS/email services like AWS SNS, MessageBird, or even custom backend APIs.

    I hope this implementation helps streamline your signup testing, regardless of Twilio or whatever service you are using. The principles remain the same: automate realistically, test thoroughly, and integrate effortlessly.

    Happy testing! 🚀

    Leave a Reply

    Your email address will not be published. Required fields are marked *