Python and Appium: Scroll through search result table

In this post we show you how to scroll through a table and identify elements. We also tackle the case where the table spans multiple pages. This is a natural and common workflow on most mobile devices. A typical use case involves the user performing a search within a mobile application. The app returns a table of results. The user then scrolls through the results and clicks on the result she likes.


Why this post?

Qxf2’s top goal is to help testers. We identified mobile automation as one area where we could help testers. Over the past few months, we have written several basic tutorials that help testers with mobile automation. We feel like the next stage is to address some common intermediate level problems you may face as you implement mobile automation at your workplace. This post outlines a technical challenge testers are likely to face and our approach to solve it.


New to mobile automation?

If you are new to mobile automation, we suggest the following posts to get started:
a) Run Appium on an emulator
b) Run Appium on mobile devices
c) Identify UI elements on mobile platforms
d) Execute different mobile gestures


Overview

We are going to be using BigBasket as the example mobile application. We will login, search for a product, scroll through all the search results and print out the products we encounter in the search results. In previous posts, we have shown you how to login to BigBasket and perform a search. Here is a short video (with no audio) showing you what actions our automated script intends to perform. We used the excellent Recordable app to create this video.

We are searching for ‘Real juice‘ using the BigBasket application. Once we click on the search icon we note that 20 items out of the total 24 items are displayed on page 1. We need a way to iterate through these first 20 search results, print out each one of them and then click on the ‘Next’ button to view the remaining 4 items. We are choosing to print out the title of every product we notice while scrolling. This is to show you how to pick a specific element/column from a table!


Algorithm for scrolling through tables

The logic for collecting each search result deserves some explanation. Search results are usually multi-page, which means it is not enough to just collect the results on the first page. We need to be able to paginate intelligently too. Further, given that the scroll size is arbitrary, we have to ensure we collect each search result exactly once and miss no search result. If your table does not paginate, simply skip the pagination portion of the logic.

Flowchart for how to scroll through tables.
Flowchart for how to scroll through tables using Appium

Code to implement scrolling through tables

Here is the code snippet for implementing the flow chart above. We use a counter to keep track of the number of elements traversed. The counter is helpful while debugging.

def get_search_result(self,results_count=0):
        "Get and scroll through the search results of the query"
        elm = self.get_element(self.first_element_tablerow)
        elm_prod_desc = elm.find_element_by_id(self.search_result_text)
        new_prod_desc = self.get_dom_text(elm_prod_desc)
        #Case when we cant scroll further,we get all elements present on the screen
        if (new_prod_desc == self.current_prod_desc):
            results = self.get_elements(self.search_result_text)
            for result in results:
                search_result = self.get_dom_text(result)
                #To avoid counting the top element twice
                if (search_result != new_prod_desc):
                    results_count = results_count + 1
                    self.write("Item number %d is %s"%(results_count,search_result))
            #Click 'Next' button if present
            if (self.check_element_present(self.search_next_button)):
                self.click_element(self.search_next_button)
                self.wait(3)
                self.get_search_result(results_count)
                self.wait(3)
            else:
                self.write ("Reached end of search results")
        else:            
            self.current_prod_desc = new_prod_desc
 
            #######################
            #TO BE FILLED IN THE SWIPE, MOVE_TO SECTIONS OF THIS TUTORIAL
            #option 1 : swipe, option 2:move_to
            #######################
 
            results_count = results_count + 1
            self.write("Item number %d is %s"%(results_count,self.current_prod_desc))
            self.get_search_result(results_count)

Approaches

While all this seems handy and neat, a big problem happens to be how much to scroll. If you scroll too much, you run the risk of missing some search results because they were never displayed. If you scroll too little, this GUI test could take forever! So we experimented with two approaches:
1. Swipe
2. move_to
It turned out that we have more control with move_to. Use our previous tutorial to move out the amount you want move_to to move into a configuration file. For the completeness of this post, we will show you both methods.

1.Using swipe:
Initially we went ahead implementing the scroll functionality using the swipe method (bottom-up), but we realized the results were not consistent and few elements were getting missed while iterating over the search results.

#swipe from the bottom of the screen to the top of the screen
self.driver.swipe(470, 800, 470, 50, 400) 
#swipe the top element 
self.driver.swipe(470, 420, 470,180, 400) 
#swipe the last element swipe  
self.driver.swipe(470, 800, 470, 600, 400) 
# swipe from middle of top element to the top of screen
self.driver.swipe(470, 280, 470, 80, 400)

While our first instinct was to use swipe, Appium can only ‘see’ elements visible on the screen. So if you swipe hard, you run the risk of overshooting and having some elements not display at all.

2.Using move_to:
Next we tried the move_to method where we identify the element using the tablerow xpath and move it to the top of the screen so that the next item is the first item on the screen. We called it recursively till all the elements are traversed successfully. This approach gave us accurate results.

from appium.webdriver.common.touch_action import TouchAction
 
action = TouchAction(self.driver) 
action.press(elm).perform()
action.move_to(x=0, y=50).perform() #We highly recommend moving the '50' into a conf file
self.wait(2)

Here is the final test script for test run

"""
Qxf2 test case to Search for a item in BigBasket app. This class imports BigBasket_Base_appium 
"""
 
from BigBasket_Base_Appium import BigBasket_Base_Appium
from optparse import OptionParser
from ConfigParser import SafeConfigParser
 
def run_search_test(config_file,test_run):
    " Perform search for the value supplied and list the search results"
    #Create a test obj with parameters
    test_obj = BigBasket_Base_Appium(config_file=config_file,test_run=test_run)
    test_obj.wait(10)
    test_obj.click_search_home()
    test_obj.perform_search("real juice")
    test_obj.get_search_result()
    test_obj.wait(5)
 
    " TearDown Test"
    test_obj.tearDown()
 
 
#---START OF SCRIPT
if __name__=='__main__':
    #Accept command line options from the user
    #Python module optparse 
    usage = "usage: %prog -c  -t  \nE.g.1: %prog -c D:\\Git_Qxf2\\qxf2\\samples\\clients\\BigBasket\\configuration.ini -t \"Android_4.2\"\n---"
    parser = OptionParser(usage=usage)
    parser.add_option("-c","--config",dest="config_file",help="The full path of the configuration file")
    parser.add_option("-t","--test_run",dest="test_run",help="The name of the test run")
    (options,args) = parser.parse_args()
 
    run_search_test(config_file=options.config_file, test_run=options.test_run)

Now you can run the test with the command line parameters mentioned here.

run appium scroll table

And finally, here is a recording of the entire automated test.


Phew! We needed video, diagrams, code snippets and a whole lot of text to write up this tutorial. We also had to leave out a lot of code and share only the snippets. We plan on sharing our code on GitHub sometime this year. Until then, if you need help with some of the missing code, feel free to ask us below.

P.S.: Yes. We are taking baby steps to publishing these tutorials on Youtube.


A weekly newsletter for testers


View a sample



Vrushali Toshniwal
My journey as a tester started at Sun Microsystems (now Oracle). I was part of the testing and sustaining team for the Portal Server and Identity Management products. My first assignment was to test the Rewriter module. I enjoyed understanding the big picture, writing test cases, finding bugs and sometimes suggesting the fix too! I was hooked onto testing. Testing felt natural and intuitive to me. I am technically inclined and can write automation in Java, C++, Perl and Python. I am well versed with SilkTest, Selenium, Appium and Selendroid. I am a Computer Science graduate from BITS-Pilani. I love travelling and listening to music.

© 2013-2017, Vrushali Toshniwal. All rights reserved.

3 Comments

  1. John B said:

    Your approach to explain the scroll was indeed good..

    April 1, 2015
    Reply
  2. kumar said:

    Hello,
    I Have an app which shows all the available products (similar to bigbasket). I would like to scroll through all the products one by one. I tried the following approach however i can scroll through only 4 products since the rows contains only 4 elements. How do i write code to check till end of all products. Your logic look perfect how do i implement it here.

    table = self.driver.find_element_by_android_uiautomator(‘new UiSelector().className(“android.support.v7.widget.RecyclerView”)’)
    rows = table.find_elements_by_id(‘com.example.test:id/card_view’)
    for i in range(0, len(rows)):
    cols = rows[i].find_elements_by_id(‘com.example.testt:id/prdText’)
    print(“”)
    for j in range(0, len(cols)):
    # print(cols[j].get_attribute(‘text’)+” — “),
    print(“”)

    self.driver.scroll(rows[i], rows[i-1])
    sleep(3)

    July 24, 2016
    Reply

Leave a Reply to John B Cancel reply

Your email address will not be published.