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 >>