pythontestingpytestpytest-django

Pytest paramaterized fixture not returning yielded object, only returning tuple?


I'm working to setup a fixture that will open a headless (or headed) instance of a web browser for some end to end testing. I am using Django with pytest and pytest-django.

I'd like to be able to paramaterize the test class with the browsers I'd like to use and whether or not they are headless in a fashion like this:

@pytest.mark.parametrize("browser_fixture", [("chrome", False)])
@pytest.mark.slow()
class TestEndToEnd:

In my conftest.py I have set it up like this:

def create_browser(browser_name, headless=True):
    if browser_name == "chrome":
        options = ChromeOptions()
        if headless:
            options.add_argument("--no-sandbox")
            options.add_argument("--headless")
            options.add_argument("--disable-dev-shm-usage")
            options.add_argument("--disable-gui")
        return webdriver.Chrome(options=options)
    elif browser_name == "firefox":
        options = FirefoxOptions()
        if headless:
            options.add_argument("--headless")
            options.add_argument("--disable-gui")
        return webdriver.Firefox(options=options)
    else:
        raise ValueError(f"Unsupported browser: {browser_name}")


@pytest.fixture(scope="class")
def browser_fixture(request):
    browser_name, headless = request.param
    browser = create_browser(browser_name, headless=headless)
    yield browser
    browser.quit()

My test looks like this:

@pytest.mark.parametrize("browser_fixture", [("chrome", False)])
@pytest.mark.slow()
class TestEndToEnd:

    @pytest.fixture(autouse=True)
    def setup(self, browser_fixture, live_server):
        management.call_command("create_project_data", verbosity=0)
        self.browser = browser_fixture
        # At this point I would expect the browser to open
        # but instead self.browser is just the tuple: `('chrome', False)`
        self.live_server_url = live_server.url

    def login_user(
        self, username=None, password="test", user=None, browser=None
    ):
        if browser is None:
            raise Exception("No browser provided")
        # The logic to login here 


    def test_as_admin(self):
        standard_user = User.objects.first()
        self.login_user(standard_user.username)
        self.browser.get(self.live_server_url + "/mills/")
        assert "Mills" in self.browser.title

The issue appears to be in the setup method. It should be opening a browser and then the test should be using that to call the login (to login the user) and do the testing it needs to.

Instead, the browser_fixture fixture is just returning the tuple.

Any ideas on where to go here?


Solution

  • You forgot to set indirect=True:

    @pytest.mark.parametrize("browser_fixture", [("chrome", False)], indirect=True)
    @pytest.mark.slow()
    class TestEndToEnd:
        ...
    

    Without indirection, the first argument ("browser_fixture") defines a variable that can be used in the test, which will be assigned each of the parameter values of the second argument as usual. If there is a fixture of the same name, that fixture is just hidden and cannot be used in the test.

    The indirect argument tells pytest to redirect the parameter values to the fixture of that name instead. Setting indirect=True is a shortcut to saying "redirect the parameter for all names in the first argument", which is what is mostly needed. It is equivalent to indirect=["browser_fixture"], which is a form that can be handy if you want to mix direct and indirect parametrization.

    As an aside: usually a fixture is not called "xxx_fixture". In your case, it is usually called "driver" or "browser". This make the usage a bit more intuitive.