mozilla
Your Search Results

    Part 5: Introducing a test runner

    Everything is going well so far, but we are still only dealing with one test. When testing a large real-world web app there may be tens or hundreds of test cases, and we certainly don't want to run each one manually. In such as scenario we need to use a test runner to find and execute the tests for us, and in this article we'll explore just that.

    Test runners

    A test runner provides the a good basis for a real testing framework. A test runner is designed to run tests, tag tests with attributes (annotations), and provide reporting and other features. There are many Python test runners available but in this case we’ll use Python’s own unittest as it’s simple, effective and comes packaged with Python, so we don’t need to install anything.

    In general you break your tests up into 3 standard sections; setUp(), tests, and tearDown(), typical for a test runner setup.
     
    The setUp() and tearDown() methods are run automatically for every test, and contain respectively:

    • The setup steps you need to take before running the test, such as unlocking the screen and killing open apps.
    • The cooldown steps you need to run after the test, such as closing the Marionette session.

    The test part of the setup is whatever code you want to run for the actual test. Let's look at how we can apply this to the test we built up over Parts 2-4.

    Running test_add_contact.py with unittest

    To use unittest we need to first import unittest: add the following below your other import lines:

    import unittest

    Next we need to create a test runner. To do this, we will make the TestContacts class inherit from the unittest.Testcase class; update your class line to this:

    class TestContacts(unittest.TestCase):

    We will also need to remove the following:

        def __init__(self):
            self.test_add_contacts()

    Initialising the test will instead be handled by unittest, so we don't need to handle this ourselves. At the bottom of your code, replace the following:

    if __name__ == '__main__':
        TestContacts()

    with this:

    if __name__ == '__main__':
        unittest.main()

    Next we need to create a setUp(self): method inside our TestContacts class, and put the following steps into it:

    1. Instantiate Marionette and start Marionette session
    2. Unlock the screen
    3. Kill all open apps
    4. Load the Contacts app

    The method should look like below. You will need to remove the identical lines which where already in test_add_contacts.

        def setUp(self):
             # Create the client for this session. Assuming you're using the default port on a Marionette instance running locally
            self.marionette = Marionette()
            self.marionette.start_session()
    
            # Unlock the screen
            self.unlock_screen()
    
            # kill all open apps
            self.kill_all()
    
            # Switch context to the homescreen iframe
            time.sleep(2)
            home_frame = self.marionette.find_element('css selector', 'div.homescreen iframe')
            self.marionette.switch_to_frame(home_frame)

    Now on to creating the tearDown(self): method. In this we need to add the code for closing our Marionette session. The method should look like this:

        def tearDown(self):
            # Close the Marionette session now that the test is finished
            self.marionette.delete_session()
    

    Again, do not forget to remove the same line from test_add_contacts.

    Now try run the test exactly as you did before. You’ll see that now you get a report of passes and failures. This is one of the advantages of using a test runner like unittest or py.test.

    Note: If you get stuck then there are lots of guides to using unittest around the internet. We'd recommend http://selenium-python.readthedocs.org/en/latest/getting-started.html and http://assertselenium.com/2013/10/07/getting-started-with-python-webdriver/. They are for Python and WebDriver but they are still relevant.

    Reference code

    For reference, our final code at this stage looks like this:

    import time
    from marionette import Marionette
    from marionette_driver import Wait
    import unittest
    
    
    class TestContacts(unittest.TestCase):
    
        def unlock_screen(self):
            self.marionette.execute_script('window.wrappedJSObject.lockScreen.unlock();')
    
        def kill_all(self):
            self.marionette.switch_to_frame()
            self.marionette.execute_async_script("""
                 // Kills all running apps, except the homescreen.
                 function killAll() {
                   let manager = window.wrappedJSObject.AppWindowManager;
    
                   let apps = manager.getApps();
                   for (let id in apps) {
                     let origin = apps[id].origin;
                     if (origin.indexOf('verticalhome') == -1) {
                       manager.kill(origin);
                     }
                   }
                 };
                 killAll();
                 // return true so execute_async_script knows the script is complete
                 marionetteScriptFinished(true);
                """)
    
        def setUp(self):
             # Create the client for this session. Assuming you're using the default port on a Marionette instance running locally
            self.marionette = Marionette()
            self.marionette.start_session()
    
            # Unlock the screen
            self.unlock_screen()
    
            # kill all open apps
            self.kill_all()
    
            # Switch context to the homescreen iframe and tap on the contacts icon
            time.sleep(2)
            home_frame = self.marionette.find_element('css selector', 'div.homescreen iframe')
            self.marionette.switch_to_frame(home_frame)
    
    
        def test_add_contacts(self):
            contacts_icon = self.marionette.find_element('xpath', "//div[@class='icon']//span[contains(text(),'Contacts')]")
            contacts_icon.tap()
    
            # Switch context back to the base frame
            self.marionette.switch_to_frame()
            Wait(self.marionette).until(lambda m: m.find_element('css selector', "iframe[data-url*='contacts']").is_displayed())
    
            # Switch context to the contacts app
            contacts_frame = self.marionette.find_element('css selector', "iframe[data-url*='contacts']")
            self.marionette.switch_to_frame(contacts_frame)
    
            # Tap [+] to add a new Contact
            self.marionette.find_element('id', 'add-contact-button').tap()
            Wait(self.marionette).until(lambda m: m.find_element('id', 'save-button').location['y']== 0)
    
            # Type name into the fields
            self.marionette.find_element('id', 'givenName').send_keys('John')
            self.marionette.find_element('id', 'familyName').send_keys('Doe')
    
            # Tap done
            self.marionette.find_element('id', 'save-button').tap()
            Wait(self.marionette).until(lambda m: not m.find_element('id', 'save-button').is_displayed())
    
        def tearDown(self):
            # Close the Marionette session now that the test is finished
            self.marionette.delete_session()
    
    if __name__ == '__main__':
        unittest.main()
    
    

    Document Tags and Contributors

    Last updated by: chrisdavidmills,
    Hide Sidebar