Using Fixtures in Pytest

You might have heard of the setup method and teardown methods in Unittest [In java, there are similar methods available in TestNG/JUnit]. In pytest, we have fixtures which simplifies the workflow of writing setup and teardown code. Fixtures have explicit names and are activated by declaring them in test functions, modules, classes or whole projects.

Let’s look at a simple example using a fixture and a test function using it and later we will see example with Selenium Webdriver below:-

import pytest

@pytest.fixture()
def my_fixture():
     print("\n I'm the fixture - setUp")
     yield
     print("I'm the fixture - tearDown")

def test_first(my_fixture):
     print("I'm the first test")

def test_second(my_fixture):
    print("I'm the second test")

def test_third():
    print("I'm the third test (without fixture)")

In pytest, we mark a function with @pytest.fixture to create fixtures. And to use fixture, we pass fixture name as a parameter to the test functions.

In the above example, for all the above tests to receive a fixture called 'my_fixture', they have an argument with the fixture name, i.e. my_fixture.

Run the above program pytest PytestFixtureExample.py -s

Below is the output :-

pytest fixture example

If you notice the output, before executing test_first and test_second methods, code in my_fixture executed before any tests are executed, and tearDown (yield) got executed after each test execution.

And the last test 'test_third' was executed without using any fixture.

If your tests need test data, you typically need to create them or read them before executing your tests. Fixtures provides an easy yet powerful way to setup and teardown resources. You can then pass these defined fixture objects into your test functions as input arguments.

Scope of a fixture

Fixture's scope is set to function by default. To run the fixture once per module, we can add scope="module" to the @pytest.fixture decorator.

When we define scope="module", fixture function will be invoked only once per test module. In the above example, if we add scope="module" , all test methods/functions in a test module 'my_fixture' will be executed only once.

In the same way, If you decide to have a session-scoped, you can simply declare it as scope="session" as below :-

@pytest.fixture(scope="session")

You can choose to parameterize fixtures and tests according to configuration and component options, or to re-use fixtures across class, module or whole test sessions scopes. These sessions are explained below:
We define scope in fixture. Scopes are of four types;
1. Function.
2. Class.
3. Module.
4. Session.

scope="function" - If we need to perform an action before and after of an function of a module we use function scope (scope=“function”)

scope="class" - If we need to perform an action before and after of an class of a module we use class scope (scope=“class”)

scope="module" - If we need to perform an action before and after of an module we use module scope (scope=“module”)

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.

Let's see how fixtures can be used with selenium webdriver : -

In the below example, we have defined a fixture 'setup' which will create driver instance and this can be used by test which includes the fixture name in the parameter list of a test function. You need to make sure the standalone ChromeDriver binary/executable is either in your path or set the executable path in your code as argument.

And ChromeDriver expects you to have Chrome installed in the default location for your platform. If not, we need to force ChromeDriver to use a custom location by setting a capability using ChromeOptions.

import pytest
from selenium import webdriver

@pytest.fixture()
def setup(request):
    print("initiating chrome driver")
    driver = webdriver.Chrome("chrome driver path")
    request.instance.driver = driver
    driver.get("http://seleniumeasy.com/test")
    driver.maximize_window()

    yield driver
    driver.close()

@pytest.mark.usefixtures("setup")
class TestExample:
    def test_title(self):
        print("Verify title...")
        assert "Selenium Easy" in self.driver.title

    def test_content_text(self):
        print("Verify content on the page...")
        centerText = self.driver.find_element_by_css_selector('.tab-content .text-center').text
        assert "WELCOME TO SELENIUM EASY DEMO" == centerText

When you run the above program, it will create driver instance for each test and close the browser as the scope is function by default. A fixture function runs before the tests that use it. However, if there is a yield statement in the function, it stops there and passes control to the tests. The statements after yield will be executed once after the tests are done.

In the later examples, we will put the fixture function into a separate conftest.py file so that tests from multiple test classes in the directory can access the fixture function. And then we will look at how we can add conftest.py to selenium webdriver tests.

Selenium Tutorials: 

Add new comment