Fixtures are pytest's dependency-injection mechanism for setup/teardown. A function decorated with @pytest.fixture provides a resource (driver, DB connection, test data) that tests request by naming it as a parameter.
@pytest.fixture def driver(): d = webdriver.Chrome(); yield d; d.quit() sets up a browser, yields it to the test, and tears it down after. A test just adds driver as a parameter: def test_x(driver): .... Fixtures have SCOPES — function (default), class, module, session — controlling how often they run/reuse (a session-scoped DB connection is created once). Fixtures can depend on other fixtures, and conftest.py shares them across files without imports. yield-style fixtures put teardown after the yield. This replaces setUp/tearDown with composable, reusable, scoped resources.
A session-scoped fixture opens one browser for the whole run: @pytest.fixture(scope='session') — reused across tests to save the cost of launching Chrome repeatedly.
Each test creates and quits its own browser, making the suite slow. How do fixtures and scope help?