Naming Template

  • All test files name should start with test_*
  • All methods within test file should start with test_*
  • Each method is considered as one test case.
  • The name of the class should start with Test*

Run test files

  • Run all test files in the test directory in the verbose mode and also print the message in the console

            >py.text -v -s

  • Run selected file in the package

          > py.test test_file.py

  • Run a group of tests. But those tests belongs to different files. e.g  run test cases have employee in their name e.g (test_employee_salary, test_employee_location etc.). But those tests are from different files

        > py.test -k employee

  • @pytest.mark.sanity : Run a group of tests marked as sanity set. Mark those test as sanity e.g @pytest.mark.sanity

       >py.test -m sanity

  • @pytest.mark.skip: Skip a specific test from being run. Use mark @pytest.mark.skip
  • @pytest.mark.xfail: Some test we want them to run but don’t want to report those test in the final report. e.g say a testcase is partially passing due to some known bug in the application. We don’t want to run the test till the issue is fix or don’t want to capture that test in the report. But some steps of this test are passing and other tests are dependent on those steps to be executed. Though it is always good practice to make each test independent. Mark that test as @pytest.mark.xfail

Fixtures

  • @pytest.fixture(): Usually fixture is used to share some common steps across all tests. e.g pre-test and post-test. 
  • Naming convention: Not mandatory to name the Fixture method as test_*.  Fixture name can be anything meaningful.
  • yield: In the fixture whatever mentioned after yield will be executing at the end of the test
  • conftest.py:  We can write fixture in each test file. The problem with this approach is maintenance of the fixture as well as duplicity. As in most scenarios all test require same pre and post steps. So its better to keep fixture in a separate file. We can keep all our fixture in conftest.py (create it if not present). Whenever fixture is called in a test method pytest check the same file if the fixture is not present then it check the conftest.py file.

 

Logging

				
					# pass the fixture method name to the other tests method as argument.
# To make sure the fixture run before executing the test method

#define fixtur
@pytest.fixture()
def setup():
    print("Will execute at the beginning of the testcase")
    yield
    print("Will execute at the end of the testcase")

#call fixture method in test methods
def test_demo1(setup):
    print("hello1")

def test_demo2(setup):
    print("hello2")

def test_demo3(setup):
    print("hello3")
    
# we have mentioned fixture name for each and every test method.
# using class we can pass the fixture name only once. Good for maintenance
## The name of the class should start with Test*

@pytest.mark.usefixtures("setup")
class TestDemo:

    def test_demo1(self):
        print("hello1")
        
    def test_demo2(self):
        print("hello2")
        
    def test_demo3(self):
        print("hello3")
        
# In the above case the setup fixture will run before and after every test method. If we want to run the fixture once at the beginning of all test methods (setup config) and once after ending of all test methods (teardown config). Then change the scope of the fixture from method level(default) to class level. i.e 

@pytest.fixture(scope=“class”)
def setup():
    print("Will execute at the beginning of the testcase")
    yield 
    print("Will execute at the end of the testcase")
    
# How to load data from fixture
@pytest.fixture()
def load_data():
    profiledata = {
        'name': 'ABC',
        'Age': 22,
        'location': 'India'
    }

    return profiledata

#test_demo.py
@pytest.mark.usefixtures("load_data")
class TestDemo2:
    def test_demo3(self,load_data):
        print("hello3")
        print(load_data)


# parameterisation of fixture. Run test for multiple datasets
@pytest.fixture(params=[('chrome','version2.2'),('firfox', 'version23')])
def cross_browser(request):
    return request.param

# test_demo.py
def test_different_dataset(cross_browser):
    print(cross_browser)
    print(cross_browser[1])

				
			
				
					import logging

def test_logging():
    # get the test file name
    logger = logging.getLogger(__name__)

    # file where logger will print
    filehandler = logging.FileHandler('log.log')

    # Setting log format i.e
    # <time> : Info : <name_of_test_file> : log message
    log_format = logging.Formatter("%(asctime)s : %(levelname)s : %(name)s : %(message)s")

    # pass the log format to the log file handler
    filehandler.setFormatter(log_format)

    # add log file handler to logger
    logger.addHandler(filehandler)

    logger.setLevel(logging.INFO)
    logger.debug("Debug statement")
    logger.info("Info Statement")
    logger.warning("Warning statement")
    logger.error("Error statement")
    logger.critical("Critical statement")
    
#content of log.log file
#2024-06-25 23:37:56,752 : INFO : pytestdemo.test_logging : Info Statement
#2024-06-25 23:37:56,752 : WARNING : pytestdemo.test_logging : Warning statement
#2024-06-25 23:37:56,753 : ERROR : pytestdemo.test_logging : Error statement
#2024-06-25 23:37:56,753 : CRITICAL : pytestdemo.test_logging : Critical statement
				
			

Integration of Logging and test cases

				
					#BaseClass.py
class BaseClass:
    import logging
    
    def getLogger():
        # __name__ print the BaseClass class where logger defined
        # inspect.stack() capture from test case the logger has been called
        #logger = logging.getLogger(__name__)
        logger = inspect.stack()[1][3]
        
    
        # file where logger will print
        filehandler = logging.FileHandler('log.log')
    
        # Setting log format
        log_format = logging.Formatter("%(asctime)s : %(levelname)s : %(name)s : %(message)s")
    
        # pass the log format to the log file handler
        filehandler.setFormatter(log_format)
    
        # add log file handler to logger
        logger.addHandler(filehandler)
    
        logger.setLevel(logging.INFO)
        return logger
        
#test_demo.py
@pytest.mark.usefixtures("getLogger")
import BaseClass

class TestDemo(BaseClass):
    def test_demo3(self,getLogger):
        log = self.getLogger()
        # pytest html report does not contain print statemet
        print("hello3")
        
        # pytest html report contain log statemet
        log("hello4")
        log(load_data)

				
			

Getting driver from fixture

				
					#conftest.py
@pytest.fixture(scope="class")
def setup():
    driver = webdriver.Chrome()
    driver.get("url")
    driver.maximize_window()
    return driver
    
#test_url.py
@pytest.use_fixtures("setup")
class TestDemo:
    # Here setup is contain the driver object returned from fixture
    def test_first(setup):
        setup.find_element(By.NAME, "name of locator")
        ...

# But there is a problem in the above approach
# In setup we also write teardown method so lets rewrite the above fixture
# we know every fixture has an request instance. As the fixture scope is class so attach the request with class instance
#conftest.py
@pytest.fixture(scope="class")
def setup(request):
    driver = webdriver.Chrome()
    driver.get("url")
    driver.maximize_window()
    # Now driver is assigned as the class variable i.e which ever class call this fixture that class will have a class variable call driver. 
    request.cls.driver = driver
    yield
    driver.close()
    
#test_url.py
@pytest.use_fixtures("setup")
class TestDemo:
    # no need to have setup pass to the test method now rather pass self which caontain the driver variable
    def test_first(self):
        # as driver is now a class variable we can access it as self.driver
        self.driver.find_element(By.NAME, "name of locator")
        ...
   
				
			

Move the fixture to the BaseClass

  • The setup fixture is used in all testcases. 
  • So instead of calling it individually move it under BaseClass
				
					#conftest.py
@pytest.fixture(scope="class")
def setup(request):
    driver = webdriver.Chrome()
    driver.get("url")
    driver.maximize_window()
    # Now driver is assigned as the class variable i.e which ever class call this fixture that class will have a class variable call driver. 
    request.cls.driver = driver
    yield
    driver.close()
   
   
# BaseClass.py
@pytest.use_fixtures("setup")
class BaseClass:
    pass
   
    
#test_url.py
import BaseClass

class TestDemo(BaseClass):
    # Here setup is contain the driver object returned from fixture
    def test_first(setup):
        # as driver is now a class variable we can access it as self.driver
        self.driver.find_element(By.NAME, "name of locator")
        ...
   
				
			

Pytest Hook for Command Line Option 

  • Currently when we type py.test from the command line it run all test in Chrome browser. 
  • But using the pytest hook we can specify the browser to pick for running the test
  • After using this hook we can run py.test from the command line as
    • > py.test –browser_name firefox …
				
					#conftest.py
# pytest hook to pass command line option
def pytest_addoption(parser):
	parser.addoption(
		“browser_name”, action=“store”, default=“chrome”
	)
	
@pytest.fixture(scope="class")
def setup(request):
    # Get the browser_name from the command line
    browser_name = request.config.getOption(“browser_name”)
    if browser_name==“chrome”:
        driver = webdriver.Chrome()
        driver.get("url")
        driver.maximize_window()
        # Now driver is assigned as the class variable i.e which ever class call this fixture that class will have a class variable call driver. 
        request.cls.driver = driver
        yield
        driver.close()
   
   
# BaseClass.py
@pytest.use_fixtures("setup")
class BaseClass:
    pass
   
    
#test_url.py
import BaseClass

class TestDemo(BaseClass):
    # Here setup is contain the driver object returned from fixture
    def test_first(setup):
        # as driver is now a class variable we can access it as self.driver
        self.driver.find_element(By.NAME, "name of locator")
        ...
   
				
			
Search

Table of Contents

You may also like to read