Enhancing Browser Automation with Python and Selenium
Written on
Chapter 1: Introduction to Selenium and unittest
In our previous discussion, we delved into the page object model, which aids in creating more maintainable and reusable code. This time, we will focus on how to integrate Selenium with Python’s unittest framework.
Unit testing is essential for validating the smallest components of a program. Python's unittest framework offers various features for managing tests, including setting up conditions before and after tests, comparing outcomes with expected results, and generating reports based on test executions.
The unittest module includes five key functionalities:
- Test Loader: Loads test cases and suites.
- Test Case: Implements the necessary interface for the test runner.
- Test Suite: Groups multiple test cases.
- Test Runner: Provides an interface for executing tests.
- Test Report: Displays the pass/fail status of executed tests along with the total elapsed time.
To create a test case, you must subclass the TestCase class from the unittest module. Adding a test method to your derived class and utilizing an assertion method provided by the TestCase class is crucial for checking and reporting test outcomes.
The most commonly used assertion methods include:
- assertEqual(a, b): Verifies that a is equal to b.
- assertNotEqual(a, b): Checks that a is not equal to b.
- assertTrue(a): Validates that bool(a) is True.
- assertFalse(a): Confirms that bool(a) is False.
- assertIs(a, b): Tests if a is identical to b.
- assertIsNone(a): Checks if a is None.
- assertIn(a, b): Ensures that a is included in b.
- assertNotIn(a, b): Confirms that a is not found in b.
- assertIsInstance(a, b): Tests if a is an instance of b.
- assertRaises(exception, func): Checks if an exception is raised when func is executed.
During a test execution, the process continues to the next test only if the assertion passes. If an assertion fails, the execution is halted with a failure message.
A test suite can consist of one or more test cases, and each test case can contain multiple tests. Below is a simple example of a test case:
class SampleTestCase(unittest.TestCase):
def setUp(self):
# Initialization code
pass
def test_method(self):
# Test logic
pass
def tearDown(self):
# Cleanup code
pass
The setUp method is executed prior to each test in the test case, allowing for a consistent starting state. Conversely, tearDown runs after each test, enabling the reversal of actions performed in setUp.
Additionally, setUpClass and tearDownClass methods, decorated with @classmethod, are executed only once per test case, before and after all tests, respectively.
To run the test script, use the following command:
if __name__ == "__main__":
unittest.main()
Using Selenium with the unittest Framework
In this segment, we will utilize the page object classes discussed previously in our tests.
Home Page Tests
The following example demonstrates the establishment of a WebDriver instance before executing tests. If you prefer not to instantiate and destroy the WebDriver for each test, consider placing it in the setUpClass method.
Within the setUp method, we create an instance of the page object for the home page, which initializes necessary elements and loads the home page for interaction.
This test method evaluates the functionality of submitting a question, employing methods defined in the home page's page object class, such as enter_code, enter_question, and submit_question.
Additionally, this test showcases the use of component objects, as the navigate_to_bopi method from the PythonDoctorHomePage class delegates the request to the Navigation class.
Tests are executed in an unpredictable order; thus, it is vital to ensure that they do not produce side effects when run in a different sequence. For example, here we validate the opening of a link in a new tab and subsequently switch back to the original window to verify the expected outcome.
Once all tests within the test case are completed, the tearDownClass method is invoked to quit the browser initialized in setUpClass, thereby releasing resources.
BOPI Page Tests
Tests for the second page follow the same structure outlined previously.
To execute the tests, use the command:
python -m unittest -v
The output of the test report will be displayed in the standard output, for instance:
test_best_practices_filter (test_bopi.TestBOPIPage) ... ok
test_navigate_to_homepage (test_bopi.TestBOPIPage) ... ok
...
Ran 7 tests in 48.165s
OK
Executing the Tests
For those seeking a more user-friendly and adaptable framework, the pytest package is worth exploring. Note that it is not included in the official Python distribution, so it must be installed as a third-party package.
Key Takeaways
- Python's unittest framework provides essential test management features, including pre/postconditions, expected result comparisons, and execution reporting.
- Test cases must inherit from the TestCase class in the unittest module and include at least one test method.
- Test execution continues only if assertions pass; failures result in immediate termination of the test.
- The setUp method is executed before each test, while the tearDown method runs afterward, allowing for cleanup actions.
- The setUpClass and tearDownClass methods execute once per test case, not for each individual test.
- Writing tests that do not depend on execution order is crucial for reliability.
In the upcoming post, we will explore executing our automation script on a remote machine. Thank you for your attention!
References
This introductory video showcases a small sample project using Selenium with Python. It covers unit tests and generating HTML reports.
This tutorial provides a comprehensive guide to using the UnitTest framework with Selenium in Python, focusing on best practices and implementation.