Testing Utilities Overview

WebDriverTestCase Assertion Methods

In addition to the unittest.TestCase assertion methods, WebDriverTestCase has additional assertions:

Method

Checks That

assertExists(element_locator)

Element exists on the page

assertNotExists(element_locator)

Element does not exists on the page

assertInView(element_locator)

Element is scrolled into view

assertNotInView(element_locator)

Element is not scrolled into view

assertVisible(element_locator)

Element is visible

assertInvisible(element_locator)

Element is not visible

assertEnabled(element_locator)

Element is enabled

assertDisabled(element_locator)

Element is disabled

assertUrlChange(expected_url)

URL matches expected_url

assertBaseUrlChange(expected_url)

Base URL (ignoring query strings) matches expected_url

Each of these assertion methods accepts the following optional keyword arguments:

  • msg: If specified, used as the error message on failure

  • wait_timeout: (Default = self.DEFAULT_ASSERTION_TIMEOUT) Number of seconds to wait for expected conditions to occur before test fails

Some assertions have other optional keyword arguments specific to what they are testing. For details, check the documentation for WebDriverTestCase.

Page Object Prototypes

The webdriver_test_tools package includes pre-defined subclasses of BasePage for common components like forms and navbars. These classes define common methods and attributes to reduce the amount of code needed to create page objects.

For more information on usage and the different prototypes available, see the following documentation:

Example

Suppose we have a “Contact Us” form with form validation that disables the submit button until required inputs are filled. When submit is clicked, a modal appears informing the user that their submission was successful. The modal has a close button that hides the modal when clicked.

To test the form submission process, we’ll need to have page objects for the contact form and the success modal, as well as functions for filling out the form inputs, submitting the form and getting the success modal page object, and clicking the close button on the modal. We’ll need to create pages/contact.py with ContactPage and SuccessModal page object classes for the “Contact Us” page and submission success modal respectively. We’ll then need to create tests/contact.py with test class ContactTestCase.

To save time writing tests, we can use the prototypes.FormObject and prototypes.ModalObject classes, which have pre-defined methods for most of the tasks we want them to do.

pages/contact.py
import os

from selenium.webdriver.common.by import By
from webdriver_test_tools.pageobject import *
from webdriver_test_tools.webdriver import actions, locate


class ContactPage(prototypes.FormObject):
    # Path to YAML file representing the form object
    YAML_FILE = os.path.join(os.path.dirname(__file__), 'contact.yml')

    # Relative to SiteConfig.BASE_URL
    PAGE_FILENAME = 'contact.html'

    # Page object class to return on click_submit()
    SUBMIT_SUCCESS_CLASS = SuccessModal


class SuccessModal(prototypes.ModalObject):
    # Path to YAML file representing the modal object
    YAML_FILE = os.path.join(os.path.dirname(__file__), 'success_modal.yml')

The FormObject parses the file specified with the YAML_FILE attribute on initialization:

pages/contact.yml
form:
  # REQUIRED: Locator for the form
  form_locator:
    by: ID
    locator: contact-form
  # REQUIRED: Locator for the submit button
  submit_locator:
    by: CSS_SELECTOR
    locator: 'button[type="submit"]'
  # List of inputs in the form
  inputs:
    - name: firstname
    - name: lastname
    - name: email
      type: email
    - name: message
      type: textarea

This is used to set the attributes FORM_LOCATOR and SUBMIT_LOCATOR, which are required to use the methods fill_inputs() and click_submit(). If the SUBMIT_SUCCESS_CLASS attribute is set, click_submit() will return an initialized page object of that type.

The ModalObject parses the file specified with the YAML_FILE attribute on initialization:

pages/success_modal.yml
modal:
  # REQUIRED: Locator for the modal
  modal_locator:
    by: ID
    locator: success-modal
  # REQUIRED: Locator for the close button
  close_locator:
    by: ID
    locator: close

This is used to set the attributes MODAL_LOCATOR and CLOSE_LOCATOR, which are required to use the click_close_button() method and other ModalObject methods.

Note

For more information on YAML syntax, see Page Object Prototype Syntax.

After creating those page object classes, we have everything necessary to write our test in ContactTestCase:

tests/contact.py
    def test_contact_form(self):
        """Send message through contact form"""
        # Initialize ContactPage form object
        contact_page = ContactPage(self.driver)
        # Generate form data
        # NOTE: We'll write generate_contact_form_data() in the following section
        contact_form_data = self.generate_contact_form_data()

        # Fill all required fields
        contact_page.fill_inputs(contact_form_data)
        # Assert submit is enabled after filling required fields
        # NOTE: SUBMIT_LOCATOR is set based on the 'submit_locator' value in contact.yml
        self.assertEnabled(contact_page.SUBMIT_LOCATOR,
                           msg='Submit was disabled after filling form inputs')

        # Submit contact form
        # NOTE: click_submit() returns an instance of SUBMIT_SUCCESS_CLASS
        # (which we set to SuccessModal)
        success_modal = contact_page.click_submit()
        # Assert success modal is visible on submit
        # NOTE: MODAL_LOCATOR is set based on the 'modal_locator' value in success_modal.yml
        self.assertVisible(success_modal.MODAL_LOCATOR,
                           msg='Success modal was not visible after clicking submit')

        # Close success modal
        success_modal.click_close_button()
        # Assert success modal is no longer visible
        self.assertInvisible(success_modal.MODAL_LOCATOR,
                             msg='Success modal still visible after clicking close button')

The fill_inputs() method takes a dictionary mapping input names to the values to set them to. In the Test Data Generation section, we will create the generate_contact_form_data() method used to assign contact_form_data in the above example.

Using Prototypes in New Page Objects

By default, page objects generated using the new page command use a basic template and subclass BasePage. You can specify a page object prototype to use as a basis for a new page using the --prototype (or -p) argument:

<test_package> new page <module_name> <PageObjectClass> -p <prototype>

Where <prototype> is one of the options listed when calling python -m <test_package> new page --help:

...
  -p <prototype_choice>, --prototype <prototype_choice>
                        Page object prototype to subclass.
                        Options: {form,modal,nav,"collapsible nav","web page"}
...

The generated page object will subclass the corresponding prototype class and include any variable declarations used by that prototype’s methods.

Test Data Generation

The webdriver_test_tools package includes some utility packages for generating random user information and placeholder text to use for sample data:

The RandomUser class generates fake user information (names, emails, contact info, addresses, etc). The loremipsum module generates lorem ipsum placeholder text.

These utilities are imported in the webdriver_test_tools.data module for convenience. To use them, add the following import statement:

from webdriver_test_tools import data

Example

Continuing from the above example, we’ll add the following method to ContactTestCase:

tests/contact.py
    def generate_contact_form_data(self):
        """Returns a dictionary mapping input names to generated user data"""
        user = data.RandomUser({'nat': 'us'})
        msg = data.loremipsum.generate(1, 'short')
        form_data = {
            'firstname': user.get_first_name(),
            'lastname': user.get_last_name(),
            'email': user.get_email(),
            'message': msg,
        }
        return form_data

This method generates random user info and message text and returns a dictionary mapping form input names to the corresponding values.

Static Test Data

Some tests may require static data for testing (e.g. login info for a registered user). These values can be stored in inside the <test_package>/data.py module to keep them independent of test modules or page objects.

Example

Suppose we have a user registration form that displays an error when registering with an email of an existing user. To test this functionality, we’ll need to use an email that we know is already registered. We can add the following value to the data module and import it in our test modules:

data.py
# email for a registered user
REGISTERED_USER_EMAIL = 'registered.user@example.com'

We can then import it in our test cases. Since this data is independent of our test cases, it can be easily used in other tests (e.g. testing a sign in page). Additionally, if the registered user email changes at any point, it can be updated in a single place without altering the tests.

Screenshots

Take Screenshot on Test Failure

The WebDriverTestCase.screenshotOnFail() decorator method can be used to save screenshots when a test assertion fails. This can be particularly useful when running tests using headless browsers.

Usage example:
class ExampleTestCase(WebDriverTestCase):
    ...
    @WebDriverTestCase.screenshotOnFail()
    def test_method(self):
        ...

See Configure Screenshot Directory and Filename Format for details on screenshot filename and path configurations.

Note

Currently, this method does not take a screenshot for assertions that fail within a subTest. See the method's documentation for more information.

Take Screenshots During Test Execution

The WebDriverTestCase.takeScreenshot() method can be used to take screenshots at arbitrary points during test execution.

Usage example:
class ExampleTestCase(WebDriverTestCase):
    ...
    def test_method(self):
        ...
        self.takeScreenshot()

This method accepts optional parameter print_filename, which will print the path to the new file to standard output.

See Configure Screenshot Directory and Filename Format for details on screenshot filename and path configurations.

Configure Screenshot Directory and Filename Format

Screenshots are saved to the directory configured in WebDriverConfig.SCREENSHOT_PATH, which is set to <test_package>/screenshot/ by default.

Screenshot filenames are determined by the format string configured in WebDriverConfig.SCREENSHOT_FILENAME_FORMAT. The format string can include the following parameters:

  • {date}: Replaced with the date the screenshot was taken (YYYY-MM-DD)

  • {time}: Replaced with the time the screenshot was taken (HHMMSS)

  • {test}: Replaced with the test method running when screenshot was taken

  • {browser}: Replaced with the browser used when screenshot was taken

The format string can include ‘/’ directory separators to save screenshots in subdirectories of WebDriverConfig.SCREENSHOT_PATH.

Example filename formats:
# The default output format
SCREENSHOT_FILENAME_FORMAT = '{date}/{time}-{test}-{browser}.png'

# Parameters can be used more than once
SCREENSHOT_FILENAME_FORMAT = '{date}-{time}/{time}-{test}-{browser}.png'

# Can include multiple subdirectories
SCREENSHOT_FILENAME_FORMAT = '{date}/{time}/{test}/{browser}.png'