PART 4: writing tests

BUILDING TEST AUTOMATION FRAMEWORK WITH TEST JUNKIE & SELENIUM WEBDRIVER

THE GOAL

Write efficient tests using Test Junkie functionality.

THE HOW

We are going to start off by creating tests to validate navigation functionality for the the footer and the header. In my case, links and navigation in general is the area that is the most prone to bugs. I'm going to provide the code snippet bellow. Feel free to copy-paste the code into the NavigationSuite.py file.

from test_junkie.decorators import test, Suite, afterTest

from src.pages.Browser import Browser
from src.pages.about.AboutPage import AboutPage
from src.pages.documentation.DocumentationPage import DocumentationPage
from src.pages.home.HomePage import HomePage


@Suite(parameters=[HomePage, DocumentationPage, AboutPage])
class NavigationSuite:

    @afterTest()
    def after_test(self):
        Browser.shutdown()

    @staticmethod
    def __validate_page_properties(page):
        """
        There are common validation steps in this suite,
        thus it was unified under this method.
        This method validates:
        - expected and actual page URL
        - expected and actual page Title
        Based on the Page Object definitions
        :param page: Object, any page object
        :return: None
        """
        expected_url, actual_url = page.expected_url, page.get_actual_url()
        assert expected_url in actual_url, \
            "Expected URL: {} Actual URL: {}".format(expected_url, actual_url)

        expected_title, actual_title = page.expected_title, page.get_actual_title()
        assert expected_title in actual_title, \
            "Expected Title: {} Actual Title: {}".format(expected_title, actual_title)

    @test(parameters=["logo", "documentation", "about", "get_started", "tutorials"],
          parallelized_parameters=True)
    def verify_header_navigation(self, suite_parameter, parameter):

        page = suite_parameter().open()
        page = getattr(page.header, "click_{}".format(parameter))()
        NavigationSuite.__validate_page_properties(page)

    @test(parameters=["home", "documentation", "about", "get_started", "tutorials"],
          parallelized_parameters=True)
    def verify_footer_navigation(self, suite_parameter, parameter):

        page = suite_parameter().open()
        page = getattr(page.footer, "click_{}".format(parameter))()
        NavigationSuite.__validate_page_properties(page)
The gist is, we have a suite with two tests. When we run those two tests, they will actually test all possible navigation combinations both for footer and for the header across multiple pages that we have defined in the suite level parameters. This is the power of parametrization with Test Junkie, you can use it to create permutations.

You may notice that tests have parallelized_parameters set to True. This allows me to test multiple parameters in parallel which saves a lot of time.

Another problematic area that I have, are the anchor links on the Documentation page. I need to make sure that they work. The way JavaScript is set up on that page is that if link works, it will change the URL to show #some_heading. However, if the link does not work the URL wont change, this is not typical for anchor links, this is just my implementation of how clicks are handled on anchor links on that page. However, that implementation makes it very easy for me to verify that those anchors work. So lets validate that. I'm going to provide the code snippet bellow. Feel free to copy-paste the code into the DocumentationSuite.py file.

import time

from test_junkie.decorators import Suite, test, afterTest
from src.pages.Browser import Browser
from src.pages.documentation.DocumentationPage import DocumentationPage


@Suite()
class DocuamentationSuite:

    @afterTest()
    def after_test(self):
        Browser.shutdown()

    @test()
    def verify_content_anchor_links(self):

        failed_sections = []

        page = DocumentationPage().open()
        Browser.maximize()
        script = "$('#navbar').hide()"
        Browser.get_driver().execute_script(script, DocumentationPage().header.NAV_BAR.get_element())

        section_links = page.get_content_links_per_section()
        for section, links in section_links.items():
            for link in links:  # links is an array of UiObjects
                href = link.get_attribute("href")

                if page.expected_url in href:
                    link.click()
                    current_url = page.get_actual_url()
                    if href not in current_url:
                        if href.split("#")[-1] not in ["cli_run", "cli_audit", "cli_config"]:
                            # Changing CLI tabs does not change the URL thus we ignore those failures
                            failed_sections.append({section: href})
                    time.sleep(0.5)  # because page scrolls on anchor click, we pause for half a sec to avid errors

        assert not failed_sections, "Some anchor links don't work: {}".format(failed_sections)

    @test()
    def verify_navigation_links(self):

        failed_sections = []

        page = DocumentationPage().open()
        Browser.maximize()

        links = page.get_left_nav_links()
        for link in links:  # links is an array of UiObjects
            link.click()
            href = link.get_attribute("href")
            current_url = page.get_actual_url()
            if href not in current_url:
                failed_sections.append(href)

        assert not failed_sections, "Some menu links don't work: {}".format(failed_sections)

I decided to break it down into two tests. One test will collect and validate all anchor links from the content of the page. The other one will collect and validate all anchor links from the left navigation menu. Since I want to get results for all links that fail, I don't do assertion in the loop and instead use an array where I log all the failures and then assert that the array is empty. If the array is not empty, it will raise AssertionError with all the information that I need to track down the broken anchors.

You may have notices in the verify_content_anchor_links test, I, also, run some JavaScript to hide the navigation header. This is because fixed navigation headers are a pain and can intercept clicks that were destined for elements that were below the header. Which will produce errors similar to WebDriverException: Message: element click intercepted. Since this test is not validating anything in the header, I decided to hide it to avoid those errors.

TEST IT

from test_junkie.runner import Runner

from tests.suites.DocumentationSuite import DocuamentationSuite
from tests.suites.NavigationSuite import NavigationSuite

runner = Runner(suites=[DocuamentationSuite, NavigationSuite])
runner.run(test_multithreading_limit=6, suite_multithreading_limit=2)

With this configuration we will run tests from two suites at a time, with no more than 6 tests in parallel. Next >>

footer-background-top