Fixtures and Conftest


Fixtures are used when we want to run some code before every test method. Instead of repeating the same code in every test we define fixtures. Usually, fixtures are used to initialize database connections, pass the base, URLs  etc.

A function is marked as a fixture by
@pytest.fixture

Create a test file called test_multiplication.py 
import pytest
@pytest.fixture
def input_data():
    inputdata=
5
   
return inputdata

def test_multi(input_data):
   
assert (3*input_data==15)

def test_multiply_evennos(input_data):
   
assert (4*input_data==21)
def test_multiply_oddno(input_data):
   
assert (5*input_data==25)

output will be
We have fixture method called input_data, which supplies the input to the tests. To access the fixture function, the tests have to mention the fixture name as input parameter.
While executing the  test, you will see the fixture name as input parameter. It then executes the fixture function and the returned value is stored to the input parameter, which can be used by the test.

The fixture method has a scope only within that test file it is defined. If we try to access the fixture in some other test file , we will get an error saying fixture ‘input_data’ not found for the test methods in other files.
To use the same fixture for multiple test files, we will create fixture methods in a file called conftest.py.
Conftest.py
We can define the fixture functions in this file to make them accessible across multiple test files.
Create a new file conftest.py and add the below code.

import pytest
@pytest.fixture
def input_data():
    inputdata=
5
   
return inputdata
 Create a test called test_addition.py

import pytest
def test_add_even(input_data):
   
assert(10+input_data==16)

def test_add_odd():
   
assert(5+7==13)
Create another new test called test_subtraction.py 
import  pytest
def test_subtract_even(input_data):
   
assert(10-input_data==5)

def test_subtract_odd():
   
assert (7-3==4)
Create another test called test_multiplication.py

import pytest
def test_multiply_even(input_data):
   
assert (4*input_data==20)
   
def test_multiply_oddno(input_data):
   
assert (5*input_data==25)

Run the tests by executing the following command 
pytest -k even -v
output will be 
pytest will look for the fixture in the test file first and if fixture is not found then it will look into the conftest.py

Scope of a fixture
Fixture's scope is set to function by default(above example test_multiplication.py is the default fixture). To run the fixture once per module, we can add scope="module" to the @pytest.fixture decorator.

We define scope in fixture. Scopes are of four types;
1)   scope="function" - If we need to perform an action before and after of an function of a module we use function scope (scope=“function”)
2)   scope="class" - If we need to perform an action before and after of an class of a module we use class scope (scope=“class”)
3)   scope="module" - If we need to perform an action before and after of an module we use module scope (scope=“module”)
4)   scope="session" If we need to perform an action before and after for a set of methods in a folder or project we session scope (scope=“session”). It creates single fixture for set of methods in a project or modules in some path.

Scope at Class level
Below is the example for class level scope.
We will create conftest.py file and add below code.

import pytest
from selenium import webdriver

@pytest.fixture(scope="class")
def WebDriver(request):
    base_url =
"http://automationpractice.com/"
   
chromePath = "C:\\executables\\chromedriver.exe"
   
driver = webdriver.Chrome(executable_path=chromePath)
    driver.get(base_url)
    request.cls.driver=driver
   
yield  driver
   
#return driver

Let us create two classes in "test_1.py" and decorate with @pytest.mark.usefixtures("WebDriver")

import pytest
import time

@pytest.mark.usefixtures("WebDriver")
class TestExampleone:
   
def test_title(self):
       
assert "My Store" in self.driver.title
   
def tests_clickOnLoginLink(self):
       
self.driver.find_element_by_xpath("//a[@title='Log in to your customer account']").click()
   
def test_Login(self):
        time.sleep(
2)
       
self. driver.find_element_by_xpath("//input[@id='email']").send_keys("abc111@gmail.com")
       
self.driver.find_element_by_xpath("//input[@name='passwd']").send_keys("abcd1234")
       
self.driver.find_element_by_xpath("//button[@type='submit' and @id='SubmitLogin']").click()


@pytest.mark.usefixtures("WebDriver")
class TestExampleTwo:
    
def testLogin_title(self):
       
assert "My Store" in self.driver.title
   
def testclickOnOrders(self):
        time.sleep(
3)
       
self.driver.find_element_by_xpath("(//a[@title='Dresses'])[2]").click()
       
assert "Dresses - My Store" in self.driver.title

Applying "@pytest.mark.usefixtures" to the class is same as applying a fixture to every test methods in the class. Since the fixture "WebDriver" has a scope defined as "class", webdriver will be initialized only once per each class.

Request.cls.driver=driver in conftest.py
This is done using the request parameter provided by pytest to give the request of the current test function. 
Run the test using below command 
Pytest test_1 -v

Scope at session level
A session-scoped fixture effectively has access to all collected test items. Here is an example of a fixture function which walks all collected tests

First create conftest.py 
import pytest
from selenium import webdriver

@pytest.fixture(scope="session")
def WebDriver(request):
    base_url =
"http://automationpractice.com/"
   
chromePath = "C:\\executables\\chromedriver.exe"
   
driver = webdriver.Chrome(executable_path=chromePath)
    session=request.node
   
for item in session.items:
        cls=item.getparent(pytest.Class)
        
setattr(cls.obj,"driver",driver)
    driver.get(base_url)
   
yield  driver

 Let us create two classes in "test_sessionlevel.py" and decorate with @pytest.mark.usefixtures("WebDriver") 

import pytest
import time
import logging
logging.basicConfig(
level=logging.DEBUG)

@pytest.mark.usefixtures("WebDriver")
class TestExampleone:
   
def test_title(self):
        log=logging.getLogger(
"test_title")
       
assert "My Store" in self.driver.title
        log.debug(
self.driver.title)

   
def tests_clickOnLoginLink(self):
       
self.driver.find_element_by_xpath("//a[@title='Log in to your customer account']").click()
   
def test_Login(self):
        time.sleep(
2)
       
self. driver.find_element_by_xpath("//input[@id='email']").send_keys("abc111@gmail.com")
       
self.driver.find_element_by_xpath("//input[@name='passwd']").send_keys("abcd1234")
       
self.driver.find_element_by_xpath("//button[@type='submit' and @id='SubmitLogin']").click()


@pytest.mark.usefixtures("WebDriver")
class TestExampleTwo:
   
def testLogin_title(self):
       
assert "My account - My Store" in self.driver.title
   
def testclickOnOrders(self):
        time.sleep(
3)
       
self.driver.find_element_by_xpath("//a[@title='Orders']").click()
       
assert "Order history - My Store" in self.driver.title

we have used scope=session, using this, the fixture will be created only once per test session and shared across several tests.

Run the test using below command

Pytest test_sessionlevel -v

Note: we are sharing webdriver session in multiple classes.

Comments