Pytest class-instance level fixture inside parametrized class

Viewed 174

I have a following simplified code example:

from urllib.parse import urlparse, parse_qs
from selenium import webdriver
import pytest

@pytest.fixture(scope="module")
def driver():
    options = webdriver.ChromeOptions()
    _driver = webdriver.Chrome(options=options)
    yield _driver
    _driver.close()

@pytest.mark.parametrize("instance_name", ["instance1", "instance2"])
class TestInstance:
    @pytest.fixture
    def authorization_code(self, instance_name, driver):
        driver.get(f"https://{instance_name}.com")
        ...do some UI actions here
        authorization_code = parse_qs(urlparse(redirected_url).query)["code"][0]

    @pytest.fixture
    def access_token(self, authorization_code):
        ...obtain access_token here using authorization code
        return "access_token"

    def test_case_1(self, access_token):
        ...do some API calls using access_token

    def test_case_2(self, access_token):
        ...do some API calls using access_token

What I would like to do is to execute UI actions in the authorization_code function once and obtain one access_token per instance.

Currently my UI actions are executed for every test case, leading to the fact that UI actions actually execute 2 * 2 = 4 times.

Is it possible to do with pytest?
Or maybe I am missing something in my setup?

1 Answers

In general I would just change the fixture scope: currently it gets recreated every time it is called, hence the reuse of ui actions. This is by design to ensure fixtures are clean. If your fixture didn't depend on the function-level fixture instance you could just put scope="class". (See the docs on scopes).

In this case I'd be tempted to handle the caching myself:

import pytest
from datetime import datetime


@pytest.mark.parametrize("instance", ("instance1", "instance2"))
class TestClass:
    @pytest.fixture()
    def authcode(self, instance, authcodes={}, which=[0]):
        if not instance in authcodes:
            authcodes[
                instance
            ] = f"authcode {which[0]} for {instance} at {datetime.now()}"
            which[0] += 1
        return authcodes[instance]

    def test1(self, authcode):
        print(authcode)

    def test2(self, authcode):
        print(authcode)

(which is just used to prove that we don't regenerate the fixture).

This feels inelegant and I'm open to better ways of doing it.

Related