Understanding Fixtures in PyTest
1. Introduction
PyTest is used for developing automation tests using python. It is a very powerful framework that can be utilized to write effective test automation scenarios in python. PyTest framework makes it easy to write small tests, yet scalable, to support complex applications and libraries.
2. PyTest fixtures
The purpose of test fixtures is to provide an inbuilt baseline which would provide repeated and reliable execution of tests. Fixtures help in reducing time and effort of implementing a function several times. Instead of implementing and defining a function, which would be used repeatedly, just call the same function as a fixture object and get it executed.
3. Passing fixture objects as function arguments
Test functions can receive fixture objects by invoking them as input arguments. To define a fixture function we have to use the decorator @pytest.fixture
.Let us consider a very small example to understand how a fixture can be implemented in real applications.
Using fixture to pass function as an object
from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.common.exceptions import WebDriverException, NoSuchElementException import traceback, time @pytest.fixture def selenium_driver(): try: driver = webdriver.Chrome(executable_path='/usr/local/bin/chromedriver') print("\n>>>Driver returned") return driver except WebDriverException: print("\n>>>WebDriver configuration error") traceback.print_exc() def test_function(selenium_driver): try: #Navigate to google driver.get("https://www.google.co.in/") #Maximize browser window browser.maximize_window() browser.implicitly_wait(5) #Enter keyword in input panel browser.find_element(By.CSS_SELECTOR,"input[name='q']").send_keys("Some input") print("\n>>>Entered data to search") #Press enter to search browser.find_element(By.CSS_SELECTOR,"input[name='q']").send_keys(Keys.ENTER) print("\n>>Key pressed to search element") time.sleep(5) browser.find_element(By.XPATH,"//a[contains(text(),'Some text')]").click() #Validate the title of the page assert browser.title == "Some title" print("\n>>>Title of the page verified") except NoSuchElementException: print("\n>>>Element not found") traceback.print_exc()
4. Fixtures: An example of dependency injection
Using fixture can be considered as a strong example for dependency injection. Fixtures allow a test function to operate by calling an already pre-initialized application object without caring much about the import/clean up/set up details. The fixture functions act as a dependency injector and the test functions which utilize the fixture object are the consumers.
5. Using fixtures across tests in a module (class/sessions)
Fixtures can be declared to be used within modules, class or a particular session. To accomplish this, the scope of a fixture has to be defined, which means, during the declaration of a fixture we have to define the scope as module/session/class. For example:
Using fixture to pass function as an object to modules
#contents of selenium_driver.py from selenium import webdriver from selenium.common.exceptions import WebDriverException import traceback @pytest.fixture(scope="module") def selenium_driver(): try: driver = webdriver.Chrome(executable_path='/usr/local/bin/chromedriver') print("\n>>>Driver returned") return driver except WebDriverException: print("\n>>>WebDriver configuration error") traceback.print_exc() #contents of test1.py from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.common.exceptions import NoSuchElementException import traceback, time def test1(selenium_driver): try: #Navigate to google driver.get("https://www.google.co.in/") #Maximize browser window browser.maximize_window() driver.implicitly_wait(5) #Enter keyword in input panel driver.find_element(By.CSS_SELECTOR,"input[name='q']").send_keys("Some input") print("\n>>>Entered data to search") #Press enter to search driver.find_element(By.CSS_SELECTOR,"input[name='q']").send_keys(Keys.ENTER) print("\n>>Key pressed to search element") time.sleep(5) driver.find_element(By.XPATH,"//a[contains(text(),'Some text')]").click() #Validate the title of the page assert browser.title == "Some title" print("\n>>>Title of the page verified") except NoSuchElementException: print("\n>>>Element not found") traceback.print_exc() #contents of test2.py from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.common.exceptions import NoSuchElementException import traceback, time def test2(selenium_driver): try: #Navigate to google driver.get("https://www.google.co.in/") #Maximize browser window driver.maximize_window() driver.implicitly_wait(5) #Enter keyword in input panel driver.find_element(By.CSS_SELECTOR,"input[name='q']").send_keys("Some input") print("\n>>>Entered data to search") #Press enter to search driver.find_element(By.CSS_SELECTOR,"input[name='q']").send_keys(Keys.ENTER) print("\n>>Key pressed to search element") time.sleep(5) driver.find_element(By.XPATH,"//a[contains(text(),'Some text')]").click() #Validate the title of the page assert browser.title == "Some title" print("\n>>>Title of the page verified") except NoSuchElementException: print("\n>>>Element not found") traceback.print_exc()
6. Yield
The object yield
in a fixture is used to execute setup or tear down code. If you have any piece of code that needs to be executed in a sequence of set up and tear down then you can use the decorator @pytest.yield_fixture
. For example, if I want to instantiate a driver as well as terminate the browser session using the same block of code then you can use yield to achieve this.
Using yield to setup/tear down
import pytest from selenium import webdriver @pytest.yield_fixture(scope="function") def SetUp(request, browser): print("\nRunning one time setUp") wdf = WebDriverFactory(browser) driver = wdf.getWebDriverInstance() if request.cls is not None: request.cls.driver = driver yield driver driver.quit() print("\nRunning one time tearDown")
When pytest runs the above function it will look for a fixture SetUp
and run it. Whatever is yielded (or returned) will be passed to the corresponding test function. The “scope” of the fixture is set to “function” so as soon as the test is complete, the block after the yield
statement will run.
7. Conclusion
I have only touched on a particular, yet powerful feature of PyTest. I would highly recommend you to go through the framework to understand how PyTest
functions work and to get started on automating test scenarios using PyTest.