Testing Utilities Overview¶
Contents
WebDriverTestCase Assertion Methods¶
In addition to the unittest.TestCase
assertion methods, WebDriverTestCase
has additional assertions:
Method |
Checks That |
---|---|
Element exists on the page |
|
Element does not exists on the page |
|
Element is scrolled into view |
|
Element is not scrolled into view |
|
Element is visible |
|
Element is not visible |
|
Element is enabled |
|
Element is disabled |
|
URL matches |
|
Base URL (ignoring query strings)
matches |
Each of these assertion methods accepts the following optional keyword arguments:
msg
: If specified, used as the error message on failurewait_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.
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:
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:
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
:
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
:
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:
# 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.
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.
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
.
# 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'