Tutorials / Selenium WebDriver series
Part 4: Writing the Selenium tests
With the page objects in place, the tests are short. Test Junkie's parameters turn two test methods into 33 navigation checks, and its threads run them side by side.
Navigation tests
Every page has the same header and footer, so we check every link from every page. The suite parameters are the page objects to start from, the test parameters the links to click:
from test_junkie.decorators import Suite, afterTest, test from src.pages.AboutPage import AboutPage from src.pages.Browser import Browser from src.pages.DocumentationPage import DocumentationPage from src.pages.HomePage import HomePage def validate_page(page): # expected vs. actual URL and title, from the page object's own definition expected_url, actual_url = page.expected_url, page.get_actual_url() assert actual_url.startswith(expected_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) @Suite(parameters=[HomePage, DocumentationPage, AboutPage], feature="Navigation") class NavigationSuite: @afterTest() def close_browser(self): Browser.shutdown() # a fresh browser for every test @test(parameters=["logo", "home", "get_started", "tutorials", "documentation", "about"], parallelized_parameters=True, component="Header") def header_navigation(self, suite_parameter, parameter): page = suite_parameter().open() destination = getattr(page.header, "click_{}".format(parameter))() validate_page(destination) @test(parameters=["home", "get_started", "tutorials", "documentation", "about"], parallelized_parameters=True, component="Footer") def footer_navigation(self, suite_parameter, parameter): page = suite_parameter().open() destination = getattr(page.footer, "click_{}".format(parameter))() validate_page(destination)
- 3 pages × 6 header links + 3 pages × 5 footer links = 33 checks from two methods.
parallelized_parameters=Trueruns a test's link checks at the same time, each in its own thread, and so with its own browser (part 1).@afterTestcloses the browser after every check, so each one starts clean.featureandcomponentlabel the tests, sotj run -c Headerruns just the header checks.
Documentation link tests
The documentation page is long and full of links to its own sections. One test clicks every in-page link in the content, another every link in the menu:
from test_junkie.decorators import Suite, afterTest, test from src.pages.Browser import Browser from src.pages.DocumentationPage import DocumentationPage from src.pages.components.PageHeader import PageHeader @Suite(feature="Documentation") class DocumentationSuite: @afterTest() def close_browser(self): Browser.shutdown() # a fresh browser for every test @staticmethod def __hide_header(): # a sticky header can sit on top of the link you click ("element click intercepted") Browser.get_driver().execute_script("arguments[0].style.display = 'none'", PageHeader.NAV_BAR.get_element()) @test(component="Anchor links") def content_anchor_links(self): broken = [] page = DocumentationPage().open() DocumentationSuite.__hide_header() for section, links in page.get_content_links_per_section().items(): for link in links: # UiObjects if not link.get_element().is_displayed(): continue # e.g. inside a collapsed block href = link.get_attribute("href") link.click() if page.get_actual_url() != href: broken.append({section: href}) # collect every broken link, then assert once - the failure lists all of them, not just the first assert not broken, "Anchor links that don't work: {}".format(broken) @test(component="Left menu") def left_menu_links(self): broken = [] page = DocumentationPage().open() DocumentationSuite.__hide_header() for link in page.get_left_nav_links(): href = link.get_attribute("href") # read it before the click link.click() if page.get_actual_url() != href: broken.append(href) assert not broken, "Menu links that don't work: {}".format(broken)
Both collect the broken links and assert once at the end, so a failure lists every broken link instead of stopping at the first. The sticky navigation bar is hidden first: it can sit on top of the link being clicked and make Selenium raise element click intercepted.
Run them
Save where the tests are and how many threads to use in a tj.cfg at the project root, then run tj run from the project folder. Test Junkie puts the project folder on the import path itself, so from src.pages... imports work without setting PYTHONPATH:
tj config update -s tests/suites -T 6 -S 2 --config ./tj.cfg tj run
Test Junkie 0.9a6 · Python 3.12.5 · Linux
tests tests/suites · 2 suites, 35 tests · found in 0.35s
mode parallel · 2 suite threads · 6 test threads
config /home/you/selenium_demo/tj.cfg
sources tests/suites · suite_multithreading_limit 2 · test_multithreading_limit 6
DocumentationSuite tests/suites/DocumentationSuite.py [||||||||||||||||||||||||] 2/2 84.61s
NavigationSuite tests/suites/NavigationSuite.py [||||||||||||||||||||||||] 33/33 87.05s
Total [||||||||||||||||||||||||] 35/35 100% 87.0s
Summary ────────────────────────────────────────────────────────────────────────
Suite Pass Fail Error Ignore Skip Cancel Time
DocumentationSuite 2 · · · · · 84.61s
NavigationSuite 33 · · · · · 87.05s
──────────────────────────────────────────────────────────────────────
Total 35 · · · · · 87.05s
35 tests 100%
────────────────────────────────────────────────────────────────────────────────
[PASSED] exit code 0
Up to 2 suites and 6 tests run at once. Every browser is a real Chrome, so set the thread limits to what the machine can take; tj run -m charts CPU and memory to help you choose. The same run from Python is Runner([DocumentationSuite, NavigationSuite]).run(suite_multithreading_limit=2, test_multithreading_limit=6).