Fixtures are functions that run before (and sometimes after) your test functions. They provide a fixed baseline for your tests by setting up necessary preconditions like test data, database connections, or initialized objects.
In pytest, fixtures are defined using the @pytest.fixture decorator and can be shared across multiple tests.
Let’s examine the actual fixtures defined in our project:
import jsonfrom pathlib import Pathimport pytest@pytest.fixture(scope="session")def users(): """ Loads testData/users.json and returns a dict. Accessible in any test as the 'users' fixture. """ root = Path(__file__).parent.parent # go from /tests to project root data_path = root / "testData" / "users.json" with data_path.open(encoding="utf-8") as f: return json.load(f)
Fixtures can have different scopes that control how often they’re created:
session
function
module
class
Created once per test session
@pytest.fixture(scope="session")def users(): # Created once, shared across all tests data_path = Path(__file__).parent.parent / "testData" / "users.json" with data_path.open(encoding="utf-8") as f: return json.load(f)
Use when:
Loading static data that doesn’t change
Setting up expensive resources
Data is read-only
Our users fixture uses session scope because the JSON data is loaded once and never modified.
Created for each test function (default)
@pytest.fixture # or scope="function"def login_page(page): # Created fresh for each test return LoginPage(page)
Use when:
Each test needs a fresh instance
Tests modify the fixture’s state
Most common scope for test isolation
Created once per module (.py file)
@pytest.fixture(scope="module")def database_connection(): # Created once per test file conn = create_connection() yield conn conn.close()
Use when:
Sharing expensive setup across tests in one file
Need cleanup after all tests in module
Created once per test class
@pytest.fixture(scope="class")def browser_context(): # Created once per test class context = browser.new_context() yield context context.close()
@pytest.fixture(scope="session")def users(): """ Loads testData/users.json and returns a dict. Accessible in any test as the 'users' fixture. """ root = Path(__file__).parent.parent # go from /tests to project root data_path = root / "testData" / "users.json" with data_path.open(encoding="utf-8") as f: return json.load(f)
Key points:
Uses Path(__file__).parent.parent to navigate to project root
Opens and parses JSON file
Returns dictionary structure from users.json:line_1
Session scope means it loads once for all tests
Credentials Fixture
load_dotenv(dotenv_path=Path(__file__).parent.parent / ".env")@pytest.fixture(scope="session")def creds(): """ Provides credentials from environment variables. Fails early with a clear message if missing. """ user = os.getenv("USERNAME") pwd = os.getenv("PASSWORD") if not user or not pwd: raise RuntimeError("Missing USERNAME/PASSWORD in environment. ") return {"valid_user": user, "pwd": pwd}
Key points:
load_dotenv() called at module level (runs once on import)
@pytest.fixturedef logged_in_page(page): # Setup: Login before test login_page = LoginPage(page) login_page.navigate() login_page.login("standard_user", "secret_sauce") yield page # Test runs here # Teardown: Logout after test page.locator("#logout_sidebar_link").click()
Usage:
def test_cart_actions(logged_in_page): # Page is already logged in cart = CartPage(logged_in_page) cart.add_product("sauce-labs-backpack") # After test, logout happens automatically
# Session: Static data (fast, but shared)@pytest.fixture(scope="session")def users(): return load_json("users.json")# Function: Fresh instances (slower, but isolated)@pytest.fixturedef cart_page(page): return CartPage(page)
Document Your Fixtures
@pytest.fixture(scope="session")def users(): """ Loads testData/users.json and returns a dict. Accessible in any test as the 'users' fixture. Structure: { "validUser": {"username": "...", "password": "..."}, "invalidUser": {"username": "...", "password": "..."} } """ # implementation
Fail Fast on Errors
@pytest.fixture(scope="session")def creds(): user = os.getenv("USERNAME") pwd = os.getenv("PASSWORD") if not user or not pwd: # Clear error message raise RuntimeError("Missing USERNAME/PASSWORD in environment. ") return {"valid_user": user, "pwd": pwd}