Please note, this is a STATIC archive of website developer.mozilla.org from 03 Nov 2016, cach3.com does not collect or store any user information, there is no "phishing" involved.

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 https://selenium-python.readthedocs.org/en/latest/getting-started.html and https://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,