Start a personal dev blog on your domain for free with Hashnode and grow your readership.
Get Started

End-To-End Tutorial For Pytest Fixtures With Examples

While writing your Selenium Test automation scripts, you’d often require data that you want to share across multiple tests. This is done by using objects which are used to instantiate the particular dataset. In pytest, this can be easily done with the help of fixtures.

Consider a test scenario where MySQL queries are executed on a database. The execution time here depends on the size of the database and the operations can be highly CPU intensive depending on its size. In such cases, repetitive implementation and execution are avoided by using pytest fixtures as they feed data to the tests such as DB connections. They also help to instantiate Selenium WebDriver for browsers under test, URLs to test, etc.

In this part of the Selenium Python tutorial series, I’ll focus on pytest fixtures. I’d further explore why you should use it and then explore the scope of fixtures.

What Are pytest Fixtures?

pytest fixtures are functions attached to the tests which run before the test function is executed. Fixtures are a set of resources that have to be set up before and cleaned up once the Selenium test automation execution is completed.

pytest fixture function is automatically called by the pytest framework when the name of the argument and the fixture is the same.

A function is marked as fixture using the following marker:

@pytest.fixture

Shown below is a sample pytest fixture function for this Selenium Python tutorial:

@pytest.fixture
def fixture_func():
   return "fixture test"
def test_fixture(fixture_func):
    assert fixture_func == "fixture test"

In the sample code shown above, the fixture function is fixture_func(). It is called when the test function test_fixture() is invoked for execution. The return value of the fixture function is passed as an argument to test_fixture(). Assert is raised if the value returned by fixture_func() does not match the expected output.

Why Use pytest Fixtures?

pytest fixtures are used in python instead of classic xUnit style setup and teardown methods where a particular section of code is executed for each test case.

There are various reasons for using pytest fixtures, the major ones are below:

  • pytest fixtures are implemented in a modular manner. They are easy to use and no learning curve is involved.
  • Like normal functions, fixtures also have scope and lifetime. The default scope of a pytest fixture is the function scope. Apart from the function scope, the other pytest fixture scopes are – module, class, and session.
  • Fixtures with function scope improves the readability and consistency in the test code. This makes code maintenance much easier.
  • Pytest fixtures function are reusable and can be used for simple unit-testing as well as testing complex scenarios.
  • Fixtures leverage dependency injection, the popular object-oriented programming concept. Fixture functions act as injectors and test functions that use fixtures are consumers of the fixture objects.

In order to explain the importance of pytest fixtures, I’ll take a simple example where setup() and teardown() functions of one test (test_1) are called even when another test (test_2) is executed. For a simple test, this small overhead might not make such a huge difference in the execution. However, if the Selenium test automation scenarios contain frequent access to databases or tests being performed on multiple browsers & OS combinations, repetitive execution can cause a significant hit on the execution performance.

#Import all the necessary modules for Selenium test automation for this Selenium Python tutorial
import pytest

def resource_1_setup():
    print('Setup for resource 1 called')

def resource_1_teardown():
    print('Teardown for resource 1 called')

def setup_module(module):
    print('\nSetup of module is called')
    resource_1_setup()

def teardown_module(module):
    print('\nTeardown of module is called')
    resource_1_teardown()

def test_1_using_resource_1():
    print('Test 1 that uses Resource 1')

def test_2_not_using_resource_1():
    print('\nTest 2 does not need Resource 1')

The test case test_2_not_using_resource_1() is executed by invoking the following command on the terminal

pytest --capture=no --verbose test_advantages.py::test_2_not_using_resource_1

As shown in the execution output, functions for resource 1 are unnecessarily invoked even though only test_2 is executed.

pytest-fixture

The issue mentioned above in this Selenium Python tutorial can be fixed by defining fixture function resource_1_setup() and resource_1_teardown(), akin to the xUnit style implementation. The scope of the fixture function is module [@pytest.fixture(scope=’module’)]

As seen in the execution snapshot, setup function for resource 1 is only called for Test_1 (and not for Test_2).

Pytest

pytest_fixtures

This is where pytest fixtures are effective as repetitive implementation & need for unnecessary execution is eliminated.

Scope Of pytest Fixtures

The scope of a fixture function indicates the number of times a fixture function is invoked. Here are the detailed description of the pytest fixtures scope in this Selenium Python tutorial:

  • Function – This is the default value of the fixture scope. Fixture with function scope is executed once per session.
  • Package (or Session) – A pytest fixture with scope as Session is created only once for the entire Selenium test automation session. Session scope is ideal for usage as WebDriver handles are available for the Selenium test automation session.
  • Module – As the name indicates, a fixture function with scope as Module is created (or invoked) only once per module.
  • Class – The fixture function is created once per class object.

The scope of pytest fixture function can be supplied with the @pytest.fixture marker

# Fixture function with function scope for Selenium automation in this Selenium Python tutorial
@pytest.fixture(scope="function")

# Fixture function with session scope
@pytest.fixture(scope="session")

# Fixture function with module scope
@pytest.fixture(scope="module")

# Fixture function with class scope
@pytest.fixture(scope="class")

Automated Browser Testing With Selenium & pytest Fixtures

In automated browser testing with Selenium, the web browser has to be loaded before the Selenium test automation is performed. Loading the browser before every test is not a good practice. Rather, the web browser should be loaded once before the tests have started and closed once the tests are complete. pytest fixtures are extremely useful when you are performing automated browser testing.

Common Selenium WebDriver implementation can be a part of the fixture function, particularly – initialization of the Selenium WebDriver for browser under test & cleanup of resources after the completion of tests. If you are new to Selenium WebDriver, I’ll recommend you to check out our blog on the architecture of Selenium WebDriver.

For demonstrating automated browser testing with pytest fixtures and Selenium WebDriver, I’ll consider the Selenium test automation scenarios mentioned below in this Selenium Python tutorial:

Test Case 1 (Test Browser – Chrome)

  1. Navigate to the URL https://lambdatest.github.io/sample-todo-app/
  2. Select the first two checkboxes
  3. Send ‘Happy Testing at LambdaTest’ to the textbox with id = sampletodotext
  4. Click the Add Button and verify whether the text has been added or not

Test Case 2 (Test Browser – Firefox)

  1. Navigate to the URL https://www.google.com
  2. Search for “LambdaTest”
  3. Click on the first test result
  4. Raise an Assert if the Page Title does not match the expected title

As there are two different Selenium test automation cases, we would need two pytest fixtures functions for initialization & de-initialization of resources for Chrome & Firefox browser respectively. The complete implementation is below:

# Import the 'modules' that are required for execution for Selenium test automation
import pytest
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.keys import Keys
import time
from time import sleep
import sys

#Fixture for Firefox
@pytest.fixture(scope="class")
def driver_init(request):
    ff_driver = webdriver.Firefox()
    request.cls.driver = ff_driver
    yield
    ff_driver.close()

#Fixture for Chrome
@pytest.fixture(scope="class")
def chrome_driver_init(request):
    chrome_driver = webdriver.Chrome()
    request.cls.driver = chrome_driver
    yield
    chrome_driver.close()

@pytest.mark.usefixtures("driver_init")
class BasicTest:
    pass
class Test_URL(BasicTest):
    def test_open_url(self):
        self.driver.get('https://www.google.com/')
        self.driver.maximize_window()
        title = "Google"
        assert title == self.driver.title

        search_text = "LambdaTest"
        search_box = self.driver.find_element_by_xpath("//input[@name='q']")
        search_box.send_keys(search_text)

        time.sleep(5)

        # Option 1 - To Submit the search
        # search_box.submit()

        # Option 2 - To Submit the search
        search_box.send_keys(Keys.ARROW_DOWN)
        search_box.send_keys(Keys.ARROW_UP)
        time.sleep(2)
        search_box.send_keys(Keys.RETURN)

        time.sleep(5)

        # Click on the LambdaTest HomePage Link
        title = "Cross Browser Testing Tools | Free Automated Website Testing | LambdaTest"
        lt_link = self.driver.find_element_by_xpath("//h3[.='LambdaTest: Cross Browser Testing Tools | Free Automated ...']")
        lt_link.click()

        time.sleep(10)
        assert title == self.driver.title   
        time.sleep(2)

@pytest.mark.usefixtures("chrome_driver_init")
class Basic_Chrome_Test:
    pass
class Test_URL_Chrome(Basic_Chrome_Test):
    def test_open_url(self):
        self.driver.get('https://lambdatest.github.io/sample-todo-app/')
        self.driver.maximize_window()

        self.driver.find_element_by_name("li1").click()
        self.driver.find_element_by_name("li2").click()

        title = "Sample page - lambdatest.com"
        assert title ==  self.driver.title

        sample_text = "Happy Testing at LambdaTest"
        email_text_field =  self.driver.find_element_by_id("sampletodotext")
        email_text_field.send_keys(sample_text)
        time.sleep(5)

        self.driver.find_element_by_id("addbutton").click()
        time.sleep(5)

        output_str =  self.driver.find_element_by_name("li6").text
        sys.stderr.write(output_str)

        time.sleep(2)

Code WalkThrough

Step 1 – All the necessary modules for this Selenium Python tutorial example are imported at the beginning of the implementation.

import pytest
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.keys import Keys

Step 2 – Two pytest fixtures functions are created, one for each Selenium test automation case as different browsers are used for testing. The chrome_driver_init() function is decorated with the @pytest.fixture indicating that it will be used as a fixture function. The scope of the fixture is set to class.

#Fixture for Chrome
@pytest.fixture(scope="class")
def chrome_driver_init(request):
    chrome_driver = webdriver.Chrome()
    request.cls.driver = chrome_driver

request.cls will be set to None for a scope that is not of type class. Since the scope of the pytest fixtures function is set to class, request.cls is nothing but the test class that is using the function. For the test Test_URL_Chrome(), request.cls.driver will be the same as Test_URL_Chrome.driver which is the reference to the Chrome WebDriver instance.

The code after yield is run as a finalizer. Once the test is executed, the Selenium WebDriver is shut down using the close method of Selenium test automation.

yield
chrome_driver.close()

The implementation for the other fixture function i.e. driver_init() is the same, except that the browser being used is Firefox.

Step 3 – To ensure that the web driver is initialized only once, the fixtures are applied to the respective base classes i.e. BaseTest() and Basic_Chrome_Test(). The test classes would be extended from the respective base class.

@pytest.mark.usefixtures("driver_init")
class BasicTest:
    pass
class Test_URL(BasicTest):
  .................................
  .................................

@pytest.mark.usefixtures("chrome_driver_init")
class Basic_Chrome_Test:
    pass
class Test_URL_Chrome(Basic_Chrome_Test):
  .................................
  .................................

Step 4 – The Fixture functions [driver_init() and chrome_driver_init()] are passed as arguments to the corresponding test functions.

Test_URL is the test class that is extended from the base class BasicTest.

@pytest.mark.usefixtures("driver_init")
.................
.................
class Test_URL(BasicTest):
    def test_open_url(self):
        self.driver.get('https://www.google.com/')
        self.driver.maximize_window()
        title = "Google"
        assert title == self.driver.title
        .................
        .................
        search_box = self.driver.find_element_by_xpath("//input[@name='q']")
        search_box.send_keys(search_text)

        time.sleep(5)

        # Option 1 - To Submit the search
        # search_box.submit()

        # Option 2 - To Submit the search
        search_box.send_keys(Keys.ARROW_DOWN)
        search_box.send_keys(Keys.ARROW_UP)
        time.sleep(2)
        search_box.send_keys(Keys.RETURN)
        .................
        .................

Test_URL_Chrome is the test class that is extended from the base class Basic_Chrome_Test.

@pytest.mark.usefixtures("chrome_driver_init")
.................
.................
class Test_URL_Chrome(Basic_Chrome_Test):
    def test_open_url(self):
        self.driver.get('https://lambdatest.github.io/sample-todo-app/')
        self.driver.maximize_window()
        .................
        .................

        self.driver.find_element_by_name("li1").click()
        self.driver.find_element_by_name("li2").click()

        title = "Sample page - lambdatest.com"
        assert title ==  self.driver.title

        sample_text = "Happy Testing at LambdaTest"
        email_text_field =  self.driver.find_element_by_id("sampletodotext")
        email_text_field.send_keys(sample_text)
        time.sleep(5)

        self.driver.find_element_by_id("addbutton").click()
        .................
        .................

Both classes contain a single test. The tests locate the required web elements on the web page. Once located, appropriate Selenium methods [find_element_by_name(), find_element_by_id()] and necessary operations [i.e. click(), submit(), send_keys(), etc.] are performed on those elements. As this part of the Selenium Python tutorial focuses on pytest fixtures, we would not get into the minute details of the Selenium test automation implementation.

The following command is used for executing Selenium test automation:

pytest --capture=no --verbose <file_name.py>

Shown below in this Selenium Python tutorial is the execution snapshot which indicates that both the tests executed successfully.

selenium-test-automation

Parameterized pytest Fixtures

What if you need to execute the same tests on different web browsers e.g. Chrome, Firefox, Opera, etc., with separate pytest fixtures functions that instantiate the Selenium WebDriver for the required web browser. It is recommended to have a single fixture function that can be executed across different input values. This can be achieved via parameterized pytest fixtures, which I’ll show next in this Selenium Python tutorial.

Parameterized driver_init fixture that takes input as Chrome and Firefox are below:

@pytest.fixture(params=["chrome", "firefox"],scope="class")

Declaration of params with @pytest.fixture contains a list of values (i.e. Chrome, Firefox) for each of which the fixture function will be executed. The value can be accessed using request.param function. Porting the code from a normal (i.e. non-parameterized) fixture to a parameterized fixture does not require any change in the feature implementation.

To demonstrate parameterized pytest features, I would execute the following Selenium test automation cases on Chrome and Firefox browsers:

Test Case 1 (Test Browsers – Chrome, Firefox)

  • Navigate to the URL https://lambdatest.github.io/sample-todo-app/
  • Select the first two checkboxes
  • Send ‘Happy Testing at LambdaTest’ to the textbox with id = sampletodotext
  • Click the Add Button and verify whether the text has been added or not

As the Selenium test automation needs to be executed on Chrome and Firefox browsers, we first create a parameterized fixture function that takes these as arguments. Depending on the browser being used for testing, an appropriate WebDriver instance for the browser is initiated i.e. if the param value is chrome, WebDriver for Chrome browser is initialized.

As shown below in this Selenium Python tutorial, request.param is used to read the value from the fixture function. The remaining implementation of the Fixture function remains the same as a non-parameterized fixture function.

@pytest.fixture(params=["chrome", "firefox"],scope="class")
def driver_init(request):
    if request.param == "chrome":
        web_driver = webdriver.Chrome()
    if request.param == "firefox":
        web_driver = webdriver.Firefox()
    request.cls.driver = web_driver
    yield
    web_driver.close()

Implementation

# Import the 'modules' that are required for execution for Selenium test automation

import pytest
import pytest_html
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.keys import Keys
import time
from time import sleep
import sys

@pytest.fixture(params=["chrome", "firefox"],scope="class")
def driver_init(request):
    if request.param == "chrome":
        web_driver = webdriver.Chrome()
    if request.param == "firefox":
        web_driver = webdriver.Firefox()
    request.cls.driver = web_driver
    yield
    web_driver.close()

@pytest.mark.usefixtures("driver_init")
class BasicTest:
    pass
class Test_URL_Chrome(BasicTest):
    def test_open_url(self):
        self.driver.get('https://lambdatest.github.io/sample-todo-app/')
        self.driver.maximize_window()

        self.driver.find_element_by_name("li1").click()
        self.driver.find_element_by_name("li2").click()

        title = "Sample page - lambdatest.com"
        assert title ==  self.driver.title

        sample_text = "Happy Testing at LambdaTest"
        email_text_field =  self.driver.find_element_by_id("sampletodotext")
        email_text_field.send_keys(sample_text)
        time.sleep(5)

        self.driver.find_element_by_id("addbutton").click()
        time.sleep(5)

        output_str =  self.driver.find_element_by_name("li6").text
        sys.stderr.write(output_str)

        time.sleep(2)

Code WalkThrough

As shown below in this Selenium Python tutorial, request.param is used to read the value from the pytest fixtures function. The remaining implementation of the Fixture function remains the same as a non-parameterized fixture function.

@pytest.fixture(params=["chrome", "firefox"],scope="class")
def driver_init(request):
    if request.param == "chrome":
        web_driver = webdriver.Chrome()
    if request.param == "firefox":
        web_driver = webdriver.Firefox()
    request.cls.driver = web_driver
    yield
    web_driver.close()

Rest of the implementation remains the same as Test Case (1) which is demonstrated in the section Automated Browser Testing using Selenium & pytest Fixtures. The only change is that we have used a parameterized fixture function to execute the test on Chrome and Firefox browsers.

Shown below is the Selenium test automation execution on the browsers under test:

Selenium test automation

As seen in the terminal snapshot, the test code test_open_url() is invoked separately for input values chrome and firefox.

pytest fixtures

Parameterized Test Functions

Along with parameterized test fixtures, pytest also provides decorators using which you can parameterize test functions. The @pytest.mark.parametrize decorator enables the parameterization of arguments for a test function. Using this decorator, you can use a data-driven approach to testing as Selenium test automation can be executed across different input combinations.

Here is how @pytest.mark.parametrize decorator can be used to pass input values:

@pytest.mark.parametrize("input_arg_1, input_arg_2,...,input_arg_n",
                         [("input_val_1", "input_val_2",...,"input_val_n")])

As shown in the official documentation of parameterization in pytest, the expected output can also be supplied along with the input parameters.

Demonstration Of Parameterized Test Functions

To demonstrate parameterization in test functions, we perform Selenium test automation where separate web pages are opened for Chrome and Firefox browsers. Assert is raised if the page title does not match the expected title.

Implementation

# Import the 'modules' that are required for execution

import pytest
import pytest_html
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.keys import Keys
import time
from time import sleep
import sys

@pytest.mark.parametrize(
                         "test_browser, test_url",
                         [
                             ("chrome", "https://www.lambdatest.com/"),
                             ("firefox", "https://www.lambdatest.com/blog/"),
                         ]
                        )
def test_open_url(test_browser, test_url):
    if test_browser == "chrome":
        web_driver = webdriver.Chrome()
        expected_title = "Cross Browser Testing Tools | Free Automated Website Testing | LambdaTest"
    if test_browser == "firefox":
        web_driver = webdriver.Firefox()
        expected_title = "LambdaTest | A Cross Browser Testing Blog"

    web_driver.get(test_url)
    web_driver.maximize_window()

    assert expected_title ==  web_driver.title

    time.sleep(5)

    web_driver.close()

As shown in the implementation above for this Selenium Python tutorial, two input arguments of type string (test_browser & test_url) are supplied to the @pytest.mark.parametrize decorator. The input values are separated by comma (,) and enclosed under [].

@pytest.mark.parametrize(
                         "test_browser, test_url",
                         [
                             ("chrome", "https://www.lambdatest.com/"),
                             ("firefox", "https://www.lambdatest.com/blog/"),
                         ]
                        )

The test function uses the input arguments added via the decorator for performing the Selenium test automation.

def test_open_url(test_browser, test_url):
if test_browser == "chrome":
        web_driver = webdriver.Chrome()
        expected_title = "Cross Browser Testing Tools | Free Automated Website Testing | LambdaTest"
    ...............................
    ...............................

The rest of the implementation is self-explanatory and we would not get into details of the same. Shown below in this Selenium Python tutorial is the execution snapshot which indicates that Selenium test automation was executed across both the input combinations.

automation-testing

Sharing pytest Fixtures Across Tests

There might be cases where pytest fixtures have to be shared across different tests. Sharing of pytest fixtures can be achieved by adding the pytest fixtures functions to be exposed in conftest.py. It is a good practice to keep conftest.py in the root folder from where the Selenium test automation execution is performed.

Shown below is conftest.py where parameterized fixture function driver_init() is added.

conftest.py

# Import the 'modules' that are required for execution

import pytest
from selenium import webdriver

@pytest.fixture(params=["chrome", "firefox"])
def driver_init(request):
    if request.param == "chrome":
        web_driver = webdriver.Chrome()
    if request.param == "firefox":
        web_driver = webdriver.Firefox()
    request.cls.driver = web_driver
    yield
    web_driver.close()

As driver_init() fixture function is now a part of conftest.py, the implementation of fixture function is removed from the test code and @pytest.mark.usefixtures decorator with input as fixture function is added in the test file.

@pytest.mark.usefixtures("driver_init")

Below is the snippet of the implementation in the test file:

import pytest
import pytest_html
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.keys import Keys
import time
from time import sleep
import sys

@pytest.mark.usefixtures("driver_init")
class BasicTest:
    pass
class Test_URL_Chrome(BasicTest):
    def test_open_url(self):
        self.driver.get('https://lambdatest.github.io/sample-todo-app/')
        self.driver.maximize_window()
        ...............................
        ...............................

Rest of the implementation remains the same as the one demonstrated in the section Parameterized Fixtures. As seen from the code snippet, the fixture implementation is no longer a part of the test code as it is now shifted to conftest.py. We do not import conftest.py in the test code as the pytest framework automatically checks its presence in the root directory when compilation is performed.

Parameterized pytest Fixtures

Skip & Xfail Tests In pytest

There are cases where a test might not be relevant for a particular platform or browser. Rather than executing the Selenium test automation case for that platform and expecting it to fail, it would be better if the test is skipped with a valid reason.

A skip in pytest means that the test is expected to pass only on if certain conditions are met. Common cases are executing certain cross browser tests on the latest browsers such as Chrome, Firefox, etc. & skipping on Internet Explorer with a reason.

A xfail means that the test is expected to fail due to some reason. A common example is a test for a feature that is yet to be implemented. If the test marked as xfail still happens to pass, it is marked as xpass (and not pass).

xfail tests are indicated using the following marker:

@pytest.mark.xfail

Tests can be skipped for execution using the following marker:

@pytest.mark.skip

Tests that skip, xpass, or xfail are reported separately in the test summary. Detailed information about skipped/xfailed tests is not available by default in the summary and can be enabled using the ‘–r’ option

pytest -rxXs

Skipping Test Functions

A test function that has to be skipped for execution can be marked using the skip decorator along with an optional reason for skipping the test.

@pytest.mark.skip(reason="reason to be skipped")
def test_a_feature():
    .................

For conditional skip, the @pytest.mark.skipif marker can be used to skip the function if a condition is True. In the example shown below for this Selenium Python tutorial, test_function() will not be executed (i.e. skipped) if the Python version is less than 3.8.

@pytest.mark.skipif(sys.version_info < (3, 8), reason="requires python3.8 or higher")
def test_function():
    ...............

Detailed information about skip & skipif markers is available on the official documentation of pytest here & here.

Xfail – Marking test functions expected to fail

The xfail marker is used to mark a test function that is expected to fail.

@pytest.mark.xfail
def test_func():
    .................

If a test fails only under a certain condition, the test can be marked as xfail with a condition and an optional reason that is printed alongside the xfailed test.

@pytest.mark.xfail(sys.platform == "win64", reason="Test cannot be executed on Win 64 platform")
def test_func():
    .................

Xfail and Skip markers can also be used along with fixtures in pytest. The respective markers can be supplied along with the parameters in a parameterized fixture. Sample code snippet is below:

@pytest.mark.parametrize(
    "test_browser, test_url",
    [
        pytest.param("firefox", "https://lambdatest.github.io/sample-todo-app/", marks=pytest.mark.xfail),
        pytest.param("chrome", "https://www.lambdatest.com/blog/", marks=pytest.mark.basic),
        pytest.param("safari", "https://www.lambdatest.com/blog/", marks=pytest.mark.skip),
    ]
)

To demonstrate the usage of xfail and skip markers with parameterized fixtures, we take sample test cases which are executed on Chrome, Firefox, and Safari browsers. As seen in the snippet above:

  1. Test on Firefox is marked as xfail
  2. Test on Chrome is a regular test and marked with a marker pytest.mark.basic
  3. Test on Safari is marked as skip hence, it will not be executed

The complete implementation is below:

# Import the 'modules' that are required for execution

import pytest
import pytest_html
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.keys import Keys
import time
from time import sleep
import sys

@pytest.mark.parametrize(
    "test_browser, test_url",
    [
        pytest.param("firefox", "https://lambdatest.github.io/sample-todo-app/", marks=pytest.mark.xfail),
        pytest.param("chrome", "https://www.lambdatest.com/blog/", marks=pytest.mark.basic),
        pytest.param("safari", "https://www.lambdatest.com/blog/", marks=pytest.mark.skip),
    ]
)

def test_open_url(test_browser, test_url):
        if test_browser == "firefox":
            web_driver = webdriver.Firefox()
            web_driver.get(test_url)

            web_driver.maximize_window()

            web_driver.find_element_by_name("li1").click()
            web_driver.find_element_by_name("li2").click()

            title = "Sample page - lambdatest.com"
            assert title ==  web_driver.title

            sample_text = "Happy Testing at LambdaTest"
            email_text_field =  web_driver.find_element_by_id("sampletodotext")
            email_text_field.send_keys(sample_text)
            time.sleep(5)

            web_driver.find_element_by_id("addbutton").click()
            time.sleep(5)

            output_str =  web_driver.find_element_by_name("li6").text
            sys.stderr.write(output_str)
        if test_browser == "chrome":
            web_driver = webdriver.Chrome()
            web_driver.get(test_url)
            title = "LambdaTest | A Cross Browser Testing Blog"

            assert title ==  web_driver.title

        time.sleep(2)
        web_driver.close()

The test case to be executed on Firefox is marked as xfail but the test case passes. Hence, the final status of the test on Firefox is xpass. Test on Chrome browser is marked with a marker pytest.mark.basic. It executes successfully and hence the status is pass. The final test is on Safari browser and is marked with the skip marker. Hence, it is skipped for execution. Shown below in this Selenium Python tutorial is the execution snapshot:

We use the earlier example to demonstrate usage of xfail and skip markers, with the markers applied on the individual test cases.

# Import the 'modules' that are required for execution

import pytest
import pytest_html
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.keys import Keys
import time
from time import sleep
import sys

@pytest.mark.xfail
def test_chrome_url():
    web_driver = webdriver.Chrome()
    web_driver.get("https://lambdatest.github.io/sample-todo-app/")

    web_driver.maximize_window()

    web_driver.find_element_by_name("li1").click()
    web_driver.find_element_by_name("li2").click()

    title = "Sample page - lambdatest.com"
    assert title ==  web_driver.title

    sample_text = "Happy Testing at LambdaTest"
    email_text_field =  web_driver.find_element_by_id("sampletodotext")
    email_text_field.send_keys(sample_text)
    time.sleep(5)

    web_driver.find_element_by_id("addbutton").click()
    time.sleep(5)

    output_str =  web_driver.find_element_by_name("li6").text
    sys.stderr.write(output_str)
    time.sleep(2)
    web_driver.close()

@pytest.mark.xfail    
def test_firefox_url():
    web_driver = webdriver.Firefox()
    web_driver.get("https://www.lambdatest.com/blog/")

    web_driver.maximize_window()    

    title = "LambdaTest | A Cross Browser Testing Blog"
    assert title ==  web_driver.title
    time.sleep(2)
    web_driver.close()

@pytest.mark.skip 
def test_safari_url():
    web_driver = webdriver.Safari()
    web_driver.get("https://www.lambdatest.com/")

    web_driver.maximize_window()    

    title = "Cross Browser Testing Tools | Free Automated Website Testing | LambdaTest"
    assert title ==  web_driver.title
    time.sleep(2)
    web_driver.close()

The test cases test_chrome_url() and test_firefox_url() are marked as xfail but they execute successfully. Hence, the result for these test cases is xpass. On the other hand, the final test test_safari_url() is marked with pytest.mark.skip marker and hence, will be skipped from execution. Shown below is the execution snapshot:

pytest-tutorials

Wrapping It Up!

Pytest fixtures are functions that are run before each function to which it is applied is executed. Fixtures can be used for simple unit testing as well as testing for complex scenarios. Pytest fixtures are ideal for usage in cross browser testing as browser resources need not be instantiated every time when a test is executed.

Function, module, class, and session are the different scopes available with fixture functions. Pytest Fixtures, as well as test functions, can be parameterized. conftest.py is used to share fixtures across tests.

Feel free to retweet and share this article with your peers! Do let us know of any queries or doubts you might have in the comment section down below. That’s it for now! Happy Testing!!! 😊

Start a personal dev blog on your domain for free and grow your readership.

3.4K+ developers have started their personal blogs on Hashnode in the last one month.

Write in Markdown · Publish articles on custom domain · Gain readership on day zero · Automatic GitHub backup and more

No Comments Yet