pytest-dev / pytest-dev/pytest
class-scoped fixtures VS test subclasses
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 14.5k
- Forks
- 3.4k
- Avg merge
- 2d 9h
- Merged PRs (30d)
- 35
Description
Hi,
I'm trying to build a base class to hold a common test setup and assumptions, that I want to enable in subclasses (inspired by this article) having the fixture-powered setup be dependent on class variables. However this approach clashes with class-scoped fixtures which are not run in subclasses of test classes, so any effect or class variables initialized there are not available. I don't know if it's a feature or a bug.
I wonder if this is a bug, if the fixtures should be called in __init_subclass__() for example. Or maybe if an additional "subclass" scope would be of interest or just legit to have?
Here a concrete example, modifying the code from the docs https://docs.pytest.org/en/stable/how-to/fixtures.html#running-multiple-assert-statements-safely
I'm also open to any other suggestion to get a similar result. Passing fixtures as subclasses variable was also not successful.
# contents of tests/end_to_end/test_login.py
from unittest.mock import MagicMock
from urllib.parse import urljoin
from uuid import uuid4
import pytest
Chrome = MagicMock()
AdminApiClient = MagicMock()
User = MagicMock()
LandingPage = MagicMock()
LoginPage = MagicMock()
@pytest.fixture(scope="class")
def admin_client(base_url, admin_credentials):
return AdminApiClient(base_url, **admin_credentials)
@pytest.fixture(scope="class")
def user(admin_client):
_user = User(name="Susan", username=f"testuser-{uuid4()}", password="P4$$word")
admin_client.create_user(_user)
yield _user
admin_client.delete_user(_user)
@pytest.fixture(scope="class")
def driver():
_driver = Chrome()
yield _driver
_driver.quit()
@pytest.fixture(scope="class")
def landing_page(driver, login):
return LandingPage(driver)
class TestLandingPageSuccessBase:
__test__ = False # Hide from test discovery
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
cls.__test__ = True # Enable test discovery for subclasses
api_version: str # To be defined in subclasses
# Unfortunately, login is not called in subclasses!!!
@pytest.fixture(scope="class", autouse=True)
def login(self, driver, base_url, user):
driver.get(urljoin(base_url, f"/{self.api_version}/login"))
page = LoginPage(driver)
page.login(user)
def test_name_in_header(self, landing_page, user):
assert landing_page.header == f"Welcome, {user.name}!"
def test_sign_out_button(self, landing_page):
assert landing_page.sign_out_button.is_displayed()
def test_profile_link(self, base_url, landing_page, user):
profile_href = urljoin(base_url, f"/profile?id={user.profile_id}")
assert landing_page.profile_link.get_attribute("href") == profile_href
class TestLandingPageSuccessV1(TestLandingPageSuccessBase):
api_version: str = 'v1'
class TestLandingPageSuccessV2(TestLandingPageSuccessBase):
api_version: str = 'v2'
Running locally on Python 3.14.0 and pytest 8.4.2.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reproducing the example in tests/end_to_end/test_login.py and trace how the class-scoped, autouse login fixture is collected for TestLandingPageSuccessV1 and TestLandingPageSuccessV2. Read the interaction between init_subclass(), test discovery, and inherited fixtures. Done means the behavior is explained and either corrected with regression coverage or clearly documented as intended.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- testing-qa
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100