Enhanced test automation with Selenium WebDriver and pytest
The biggest challenge for a developer today is to ensure consistent and smooth working of their website or web application across different devices, browsers, and platforms. The easiest way to ensure all-round consistency is using a cross-browser testing method for testing websites or web applications across contrasting combinations. If you wish to test a web application, there are several frameworks available that can automate the tests performed across different web browsers to ensure consistency, repeatability, and speed. Here, I am going to discuss one such popular framework.
A brief about Selenium WebDriver
Selenium is an open-source tool that automates web application testing across numerous platforms, such as Windows, Mac, and Linux. With Selenium, you can perform web testing against some of the popular browsers like Chrome, Firefox, Opera, Microsoft Edge, etc. It also supports various programming languages like Python, C#, Ruby, PERL, Java, etc.
Selenium WebDriver is one of the core components of the Selenium framework. It is a collection of open-source APIs that are used to automate the testing of a web application. It allows test automation to open a browser, then sends clicks, type keys, scrape text, and finally exit the browser cleanly. There are particular drivers for the browsers that include Chrome, Firefox, Opera, and Microsoft Edge. Download these drivers first and then place them on the PATH. For a better understanding, check out the image below that depicts the architecture of a Selenium WebDriver:
Let’s see the workflow in the above-mentioned architecture:
- When you run the automation, the complete automation code gets converted to JSON.
- Generated JSON is sent to the browser driver through JSON Wire Protocol Over HTTP.
- Browser Driver executes commands on the respective browser over HTTP.
- Browser Driver gets the response from the browser, which is sent back to you.
Talking about the Selenium Drivers, you can download the drivers for your respective browser here. Place the driver on the PATH such as /usr/bin/, /usr/local/bin/ or at the current working directory.
When you think of choosing a framework for test automation, you would probably like to have a comprehensive solution that fits any type of test like unit, functional, end-to-end, acceptance, and others. You would also want this framework to share common data, track how much time a test execution takes, execute prerequisites and tear down steps, and show reporting. pytest meets all these requirements. The pytest framework is one of the best open-source test frameworks that allows you to write test cases using Python.
pytest eases web application testing and allows you to create simple yet scalable test cases in Selenium WebDriver. It is popular amongst the testers because of its simplicity, scalability, and pythonic nature. You can parallelly run specific tests or subsets of test cases. Test cases are written as functions, and test assertion failures are reported with actual values. You can use plugins to add code coverage, pretty reports, and parallel execution. If you create test cases in WebDriver then the pytest supports valuable functionalities like Log Events and Test Report Generation. Features like fixture, parameterize, Xfail, and skip make pytest more powerful. Since the pytest is widely popular amongst the QA community, check out the qualities of pytest and standard practices that should be followed while writing any test case in my previous blog.
Steps to install the pytest and Selenium
There are multiple ways to install pytest with Selenium like using pytest-webdriver, which is a pytest plugin for webdriver. However the easiest one is -
- To install pytest, execute the following command on the prompt/terminal:
pip install –U pytest
- To install Selenium, execute the following command on the prompt/terminal:
pip install -U selenium
Test automation using pytest with Selenium WebDriver
I hope you have gone through my previous blog on the pytest as mentioned above. Now, let’s go through the first test case as given below.
The first test:
pytest will search for test functions named test_* in modules named test_*.py.
Write the first test case -
Now, run the following to get the output:
Once the test is executed, you can get the test results in a report format (like HTML). For that, you will need to install the pytest-Html module. Then execute the following command, and it will generate an HTML report with the name pytest_selenium_report.html at the same location where your test suite file is stored.
The report will look like this:
Why do we need pytest Fixture?
Any test that requires a WebDriver instance can simply use the pytest fixture. You can put fixtures in the individual test files only if you want to use the fixture in a single test file. But to share fixtures among multiple test files, you should use conftest.py file so that tests from multiple test classes/modules in the directory can access the fixture function. Usually, a method is marked as a fixture by marking it as @pytest.fixture. A test method can use a fixture by mentioning the fixture name as an input parameter.
Let's write a test case by using a pytest fixture. This test case aims to open the first email from the email box and get the subject name.
In the above example, a fixture is written with a browser name. When you run the above program, it will create a driver instance for each test and close the browser as the scope is functioned by default.
The fixture mentioned above contains:
- driver = Chrome() - Chrome() initializes the ChromeDriver instance using the default options on your local machine.
- driver.implicitly_wait(10) - The challenging part of web UI test automation is to wait for the page to load/change just after firing an interaction, as the page takes time to render new elements. If this automation tries to access the new elements even before they occur, WebDriver will raise a NoSuchElementException in this scenario. It takes up to 10 seconds for elements to exist as the implicity_wait method, as mentioned above, tells you to wait while trying to find those elements. It is just once that these implicit waits are declared before being used automatically for all elements.
- yield driver - A value should be returned by the pytest fixture that represents the setup. To the initialized WebDriver the fixture returns a reference. The WebDriver does not use a return statement but uses yield, which means the fixture is a generator. In this case, the first iteration of the fixture, the WebDriver initialization, is the “setup” phase that can be defined before a test begins. The second iteration is to quit calling, which denotes a “cleanup” phase and can be used after a test finishes. When you write the fixtures as generators, it helps you to keep together the related setup and cleanup operations as a single concern.
- driver.quit() - I would like to suggest always quitting your WebDriver instance at the end of a test. When the test automation ends, it is not necessary the driver processes on the test machine would always die. If you happen to fail in quitting a driver instance, it may keep on running as a zombie process that could consume as well as lock the system resources.
Now, run the above test case and make sure it works. When the web test runs, it will open the Gmail and the username and password are automatically entered as they have been provided in the script. Just wait for the results page to appear. Open the first email, and now you can quit the browser.
Fixtures and their Scope:
When you develop tests with Selenium, the first issue that you should solve is to know when to load a browser. Loading your browser just before each test is not a good practice. Rather, it is much better to load a browser before all tests and then close it after all tests are executed, as this will save resources and test execution time. It is possible to do this by using Fixtures in the pytest. Fixtures make extensive use of a concept called ‘Dependency Injection’ where dependencies can be loaded before the actual tests start.
The scope is an optional parameter in Fixtures, and it controls the total duration a fixture takes to set up and torn down. The scope parameter to @pytest.fixture() can have the values of Function, Class, Module, or Session, and the default scope is a function. Let’s see what each one of them stands for:
Module: If your Module scope is defined, then the fixture is created/invoked only once per module.
Class: One fixture is created per Class object with Class scope.
Session: With the Session scope, the fixture is created only once for the entire test session. It is mainly used to manage WebDriver sessions.
Function: It stands for the default value for the fixture scope. The fixture gets executed once per test session.
Let’s take an example of a fixture with scope class. In the example given below, the browser will load only once for all the test cases as its scope is class level. Once the execution is done, the browser will be closed:
And this will be the result -
In the above example, the tests are run only in the Chrome browser but what if you want to run them in some other browser like Firefox? For that, you can use another useful feature of the pytest - the parametrization of a fixture. With the help of @pytest.mark.parametrize, you can follow a data-driven approach and execute your tests on different data sets.
All tests that use the fixture “driver_init” will be executed in Chrome and Firefox browsers.
The parameterized driver_init fixture looks like this:
The result will look like this:
For test automation, a pytest with Selenium WebDriver is quite a good option. You can use this to write test cases for simple scenarios as well as highly complex scenarios. A developer who is well-versed in Python can easily use pytest. If you are looking to test web-applications or websites, you can execute test automation using a pytest with the Selenium framework. You can test your application on different browsers as well as on different platforms too. Well, that was all about using pytest framework’s most powerful instruments - fixtures and parameterized fixtures. You can try replicating the test case and get the best results. Happy Testing!