androidpythonautomated-testssleepmonkeyrunner

Avoiding MonkeyRunner.sleep


I'm completely new to monkeyrunner and Android development in general. Following a bogstandard monkeyrunner tutorial, I was advised by someone to also perform the following sleep call after startActivity:

runComponent = package + '/' + activity

# Runs the component
device.startActivity(component=runComponent)
MonkeyRunner.sleep(5)
# Presses the Menu button
device.press('KEYCODE_MENU', MonkeyDevice.DOWN_AND_UP)

# Takes a screenshot
result = device.takeSnapshot()

# Writes the screenshot to a file
result.writeToFile('C:\\Users\\username\\Desktop\\shot1.png','png')

...claiming that MonkeyRunner.sleep() is required to make sure certain actions are given enough time to execute. (But is 5 seconds even enough time on a slow device? How would I know?)

I know from experience in other languages that relying on .sleep is a terrible idea. I want to start prototyping some basic test automation strategies with monkeyrunner, and if I use .sleep all over the place, my tests will eventually accumulate arbitrary amounts of wasted time.

How can I use some kind of event-driven model for waiting for device tasks co complete? Are there common instances where I can't hook into certain events and I will have to leverage sleeping arbitrary amounts of time?


Solution

  • You should use something more sophisticated than monkeyrunner to achieve your goal.

    One of the alternatives is CulebraTester which has the ability to wait on conditions.

    This example use CulebraTester to use the launcher drawer to start Calculator.

    enter image description here

    The generated python script is this

    #! /usr/bin/env python
    # -*- coding: utf-8 -*-
    '''
    Copyright (C) 2013-2018  Diego Torres Milano
    Created on 2018-04-13 by CulebraTester 
                          __    __    __    __
                         /  \  /  \  /  \  /  \ 
    ____________________/  __\/  __\/  __\/  __\_____________________________
    ___________________/  /__/  /__/  /__/  /________________________________
                       | / \   / \   / \   / \   \___
                       |/   \_/   \_/   \_/   \    o \ 
                                               \_____/--<
    @author: Diego Torres Milano
    @author: Jennifer E. Swofford (ascii art snake)
    '''
    
    
    import re
    import sys
    import os
    
    
    import unittest
    try:
        sys.path.insert(0, os.path.join(os.environ['ANDROID_VIEW_CLIENT_HOME'], 'src'))
    except:
        pass
    
    import pkg_resources
    pkg_resources.require('androidviewclient>=12.4.0')
    from com.dtmilano.android.viewclient import ViewClient, CulebraTestCase
    from com.dtmilano.android.uiautomator.uiautomatorhelper import UiAutomatorHelper, UiScrollable, UiObject, UiObject2
    
    TAG = 'CULEBRA'
    
    
    class CulebraTests(CulebraTestCase):
    
        @classmethod
        def setUpClass(cls):
            cls.kwargs1 = {'ignoreversioncheck': False, 'verbose': False, 'ignoresecuredevice': False}
            cls.kwargs2 = {'forceviewserveruse': False, 'useuiautomatorhelper': True, 'ignoreuiautomatorkilled': True, 'autodump': False, 'startviewserver': True, 'compresseddump': True}
            cls.options = {'start-activity': None, 'concertina': False, 'device-art': None, 'use-jar': False, 'multi-device': False, 'unit-test-class': True, 'save-screenshot': None, 'use-dictionary': False, 'glare': False, 'dictionary-keys-from': 'id', 'scale': 1, 'find-views-with-content-description': True, 'window': -1, 'orientation-locked': None, 'save-view-screenshots': None, 'find-views-by-id': True, 'log-actions': False, 'use-regexps': False, 'null-back-end': False, 'auto-regexps': None, 'do-not-verify-screen-dump': True, 'verbose-comments': False, 'gui': False, 'find-views-with-text': True, 'prepend-to-sys-path': False, 'install-apk': None, 'drop-shadow': False, 'output': None, 'unit-test-method': None, 'interactive': False}
            cls.sleep = 5
    
        def setUp(self):
            super(CulebraTests, self).setUp()
    
        def tearDown(self):
            super(CulebraTests, self).tearDown()
    
        def preconditions(self):
            if not super(CulebraTests, self).preconditions():
                return False
            return True
    
        def testSomething(self):
            if not self.preconditions():
                self.fail('Preconditions failed')
    
            _s = CulebraTests.sleep
            _v = CulebraTests.verbose
    
            self.vc.uiAutomatorHelper.findObject(bySelector='res@com.google.android.apps.nexuslauncher:id/all_apps_handle,desc@Apps list,clazz@android.widget.ImageView,text@$,package@com.google.android.apps.nexuslauncher').clickAndWait(eventCondition='until:newWindow', timeout=_s*1000)
            UiScrollable(self.vc.uiAutomatorHelper, uiSelector='clazz@android.support.v7.widget.RecyclerView,res@com.google.android.apps.nexuslauncher:id/apps_list_view,index@2,parentIndex@1,package@com.google.android.apps.nexuslauncher').getChildByDescription(uiSelector='desc@Calculator', description="Calculator", allowScrollSearch=True).click()
    
    
    if __name__ == '__main__':
        CulebraTests.main()
    

    toward the end of the script, you can see (I just removed the Apps selector to reduce the size)

    self.vc.uiAutomatorHelper.findObject(bySelector='...').\
            clickAndWait(eventCondition='until:newWindow', timeout=_s*1000)
    

    which finds the Apps button, clicks it, and waits for the drawer to open, as a new window.

    Close Calculator and drawer and run the script and you will see how it opens again

    testSomething (__main__.CulebraTests) ... ok
    
    ----------------------------------------------------------------------
    Ran 1 test in 13.103s
    
    OK