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:
- Instantiate Marionette and start Marionette session
- Unlock the screen
- Kill all open apps
- 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()