Appium: Fetching OTP from the Notification Panel | With working code

Anurut Malhan
noobQA
Published in
5 min readJul 4, 2019

I’ve recently dived into the world of “Automation Testing”. While I was still learning the basics of automation, I was giving interviews for Automation Testers to get a feel of the industry requirements. During one such interview, I was asked to write a program to fetch OTP and obviously, I had no idea how to do that.

Since then I got obsessed to find a solution to that question. I did what every noob does, I googled. There were a few solutions which used the SMS application in order to look for OTP. I didn’t like that approach as it included switching between apps during the test execution and in some cases if the AUT(Application Under Test) loses its state this could be troublesome. Here’s how I did it.

For an example, I’m using the Make My Trip application. As soon as you open the app you are greeted with four different screens chosen at random(to know how did I tackle that, you can check out my GitHub.

The first thing to do here is to find a unique locator. When trying to locate a unique identifier on this screen for the text box. While using Uiautomatorviewer I found that there is no resource ID for the text box, I used the next best thing then; the class:

android.widget.EditText

Since there is only one text box this identifier will do the job just okay.

Fortunately, there’s a resource ID for the OTP text box. There's one more screen we have to contend with and that is the “Notification Screen”.

Note: There are a few steps involved to get to the OTP screen. Complete project framework is in my GitHub.

We’re only concerned with the TextView class

On the Notification Screen all we need is the android.widget.TextView class and the Close All Notifications button at the bottom. Locator for which is

com.android.systemui:id/Clear_all_button

Now we have all the required locators, let’s move to the code. I have split the code into three screens namely EnterMobileScreen, EnterOTPScreen, and NotificationScreen.

The EnterMobileNumberScreen

As I’ve mentioned earlier, this application has four welcome screens which appear randomly at launch. I’ve written another blog to talk about this here.

Once we’ve reached at a screen where a text box to Enter the phone number can be seen. All we have to do is enter the phone number using the sendKeys() method.

public EnterPhoneNumberScreen enterPhoneNumber(String phNumber) {AndroidElement phNo = driver.findElement(By.className("android.widget.EditText"));if (phNo.isDisplayed()) {System.out.println("");System.out.println("--------- Entering phone number ---------");phNo.sendKeys(phNumber);}return this;}

But before that we should clear any previous notifications which might contain OTPs from previous attempts. For that, I created a method in the NotificationScreen class to clear-all notifications.

public EnterPhoneNumberScreen clearNotification() throws InterruptedException {driver.openNotifications();System.out.println("---------- Opened Notifications ----------");try {AndroidElement notif = driver.findElementById("com.android.systemui:id/dismiss_text");if (notif.isDisplayed()) {notif.click();System.out.println("---- Clicked on clear all button ----");return new EnterPhoneNumberScreen(driver);} else if(!notif.isEnabled()) {System.out.println("No notifications to clear, going back");driver.pressKey(new KeyEvent(AndroidKey.BACK));return new EnterPhoneNumberScreen(driver);}} catch (Exception e) {System.out.println(e);System.out.println("No notifications to clear, going back");driver.pressKey(new KeyEvent(AndroidKey.BACK));}return new EnterPhoneNumberScreen(driver);}

Once the phone number is entered and the user is on the OTP screen, now is the time to fetch the OTP from the notification panel.

The NotificationScreen

This is the money screen, this is where the “fetching happens”. I’ve tried to make the code as generic as possible that’s why I used the ‘TextView’ class as a locator instead of a specific locator.

Since there can be more than one textview fields, I needed a List of all the texts.

List<AndroidElement> messageText = driver.findElements(By.className("android.widget.TextView"));

Now I’ll iterate through this list to look for any text that contains text “is the OTP”.

private String OTPloop(int size, List<AndroidElement> element) {System.out.println("Inside OTP Loop method");for (int i = 0; i < size; i++) {System.out.println("Current position = " + i);if (element.get(i).getText().contains("is the OTP")) {return element.get(i).getText();}}return "";}

And this loop will be run thrice just in case if the OTP is delivered after the loop has started.

int Size = messageText.size();for(int i=0; i<=3; i++){Thread.sleep(2000);if(OTP.length()==0) {OTP = OTPloop(Size, messageText);}else {System.out.println("Found the OTP, Yay!!!");break;}}

At this point the variable OTP contains the whole string which contains the desired OTP. To extract OTP from the string we will first extract any number combinations using Regex and then check if any of the numbers extracted is the correct length.

private String extractOTP(String OTP) {
Pattern p = Pattern.compile("\\d+"); //looking for digitsMatcher m = p.matcher(OTP);while(m.find()) {System.out.println(m.group().length());System.out.println(m.group());if(m.group().length()==4) {System.out.println("The OTP is: " + m.group());return m.group();}}return "";}if(OTP.length()<4) {System.out.println("---- Failed to retrieve OTP ----");clickBackButton();return "";}else {OTP = extractOTP(OTP);}if(OTP.length()==0) {Assert.fail("OTP not received");}else {System.out.println("OTP is: " + OTP);}

In the end the method getOTP() returns the value of the extracted OTP. Which is then inserted into the text box on the EnterOTPScreen.

The EnterOTPScreen

On this screen there are two operations that need to be done:

  • Enter the OTP in the text box
  • Click on the next/submit button

The enterOTP() method takes one parameter ‘otp’ of type String to be entered in the text box

public EnterOTPScreen enterOTP(String otp) {try {AndroidElement otpBox = driver.findElement(By.id("com.makemytrip:id/otp_edit_text"));if (otpBox.isDisplayed()) {System.out.println("");System.out.println("--------- Entering OTP ---------");if(otp != null) {otpBox.sendKeys(otp);}}} catch (NoSuchElementException e) {System.out.println("OTP textbox not displayed, are you on the right page?");}return this;}

and at last clicking the submit button.

public void clickSubmitButton() {System.out.println("");System.out.println("--------- clicking submit button ---------");List<AndroidElement> submitButton = driver.findElements(By.className("android.widget.ImageView"));submitButton.get(1).click();}

Note: Please note that there are two buttons in the EnterOTPScreen with the same class ‘android.widget.ImageView’, hence, I’ve used List as a datatype for the Android element submitButton and clicked on the element with index ‘1’.

The TestCases

Here is the testng class having two test cases enterPhoneNumber() and enterOTPRecieved().

public class NewOTPTest extends TestBase{@Test(priority=1)public void enterPhoneNumber() throws InterruptedException {pcNotification = new NotificationScreen(driver);pcNotification.clearNotification();pcEnterPhoneNo = new EnterPhoneNumberScreen(driver);//Enter phone number at which you want to get the OTPpcEnterPhoneNo.enterPhoneNumber("Enter phone number here");pcEnterPhoneNo.clickSubmitButton();}@Test(priority=2)public void enterOTPReceived() throws InterruptedException {pcEnterOTP = new EnterOTPScreen(driver);String otp = pcNotification.getOTP();if(otp.contentEquals("")) {Assert.fail();}else {pcEnterOTP.enterOTP(otp);pcEnterOTP.clickSubmitButton();}}@BeforeTestpublic void init() {softAssertion = new SoftAssert();}}

The complete working code is at my GitHub, feel free to use that as it is or make any changes to it.

Happy bug bashing!!

--

--