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
- Store Twilio credentials securely in environment variables
- Implement error handling for message retrieval failures (e.g., timeout, api failure etc.)
- Tailor the OTP extraction logic based on your message format
- 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! 🚀