========================== Testing Utilities Overview ========================== .. contents:: .. _assertion-methods: WebDriverTestCase Assertion Methods =================================== .. currentmodule:: webdriver_test_tools.testcase.webdriver In addition to the ``unittest.TestCase`` `assertion methods`_, ``WebDriverTestCase`` has additional assertions: +------------------------------------------+-------------------------------------+ | Method | Checks That | +==========================================+=====================================+ | :meth:`assertExists(element_locator) | Element exists on the page | | ` | | +------------------------------------------+-------------------------------------+ | :meth:`assertNotExists(element_locator) | Element does not exists on the page | | ` | | +------------------------------------------+-------------------------------------+ | :meth:`assertInView(element_locator) | Element is scrolled into view | | ` | | +------------------------------------------+-------------------------------------+ | :meth:`assertNotInView(element_locator) | Element is not scrolled into view | | ` | | +------------------------------------------+-------------------------------------+ | :meth:`assertVisible(element_locator) | Element is visible | | ` | | +------------------------------------------+-------------------------------------+ | :meth:`assertInvisible(element_locator) | Element is not visible | | ` | | +------------------------------------------+-------------------------------------+ | :meth:`assertEnabled(element_locator) | Element is enabled | | ` | | +------------------------------------------+-------------------------------------+ | :meth:`assertDisabled(element_locator) | Element is disabled | | ` | | +------------------------------------------+-------------------------------------+ | :meth:`assertUrlChange(expected_url) | URL matches ``expected_url`` | | ` | | +------------------------------------------+-------------------------------------+ | :meth:`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 :class:`WebDriverTestCase`. .. _assertion methods: https://docs.python.org/library/unittest.html#assert-methods Page Object Prototypes ====================== .. currentmodule:: webdriver_test_tools.pageobject The ``webdriver_test_tools`` package includes pre-defined subclasses of :class:`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: * :doc:`prototypes` * :doc:`yaml` * :doc:`Prototypes API docs ` 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 :class:`prototypes.FormObject ` and :class:`prototypes.ModalObject ` classes, which have pre-defined methods for most of the tasks we want them to do. .. literalinclude:: ../example/util-example/util_example/pages/contact.py :caption: pages/contact.py .. todo Better phrasing and more detail The ``FormObject`` parses the file specified with the ``YAML_FILE`` attribute on initialization: .. literalinclude:: ../example/util-example/util_example/pages/contact.yml :caption: pages/contact.yml 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: .. literalinclude:: ../example/util-example/util_example/pages/success_modal.yml :caption: pages/success_modal.yml 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 :ref:`yaml-page-objects`. After creating those page object classes, we have everything necessary to write our test in ``ContactTestCase``: .. literalinclude:: ../example/util-example/util_example/tests/contact.py :caption: tests/contact.py :pyobject: ContactTestCase.test_contact_form The :meth:`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 ------------------------------------ .. todo rephrase title? By default, page objects generated using the ``new page`` command use a basic template and subclass :class:`BasePage `. You can specify a page object prototype to use as a basis for a new page using the ``--prototype`` (or ``-p``) argument: :: new page -p Where ```` is one of the options listed when calling ``python -m new page --help``: :: ... -p , --prototype 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: - `randomuser `__ - `py-loremipsum `__ The :class:`RandomUser ` class generates fake user information (names, emails, contact info, addresses, etc). The :mod:`loremipsum` module generates `lorem ipsum `__ placeholder text. These utilities are imported in the :mod:`webdriver_test_tools.data` module for convenience. To use them, add the following import statement: .. code-block:: python from webdriver_test_tools import data Example ------- Continuing from the above example, we'll add the following method to ``ContactTestCase``: .. literalinclude:: ../example/util-example/util_example/tests/contact.py :caption: tests/contact.py :pyobject: ContactTestCase.generate_contact_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 ``/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: .. literalinclude:: ../example/util-example/util_example/data.py :caption: data.py 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: Screenshots =========== Take Screenshot on Test Failure ------------------------------- .. currentmodule:: webdriver_test_tools.testcase.webdriver The :meth:`WebDriverTestCase.screenshotOnFail` decorator method can be used to save screenshots when a test assertion fails. This can be particularly useful when running tests using :ref:`headless browsers `. .. code-block:: python :caption: Usage example: class ExampleTestCase(WebDriverTestCase): ... @WebDriverTestCase.screenshotOnFail() def test_method(self): ... See :ref:`screenshot-configs` 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 :meth:`method's documentation ` for more information. Take Screenshots During Test Execution -------------------------------------- The :meth:`WebDriverTestCase.takeScreenshot() ` method can be used to take screenshots at arbitrary points during test execution. .. code-block:: python :caption: 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 :ref:`screenshot-configs` for details on screenshot filename and path configurations. .. _screenshot-configs: Configure Screenshot Directory and Filename Format -------------------------------------------------- Screenshots are saved to the directory configured in ``WebDriverConfig.SCREENSHOT_PATH``, which is set to ``/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``. .. code-block:: python :caption: 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'