Skip to content

Chapter 6 · pytest Integration and Fixture System ​

Chapter goals: Install the official pytest plugin, master the scope and isolation semantics of fixtures like page/context/browser, and learn to customize browser behavior with CLI options and conftest.

6.1 From "Hand-Written Scripts" to pytest Plugin ​

The scripts from previous chapters required manual launch/close management. The official plugin pytest-playwright takes over all of that:

bash
pip install pytest-playwright
playwright install   # download engines on first run

Writing tests is just declaring the page fixture — everything else is automatic:

python
# test_example.py
import re
from playwright.sync_api import Page, expect

def test_has_title(page: Page):
    page.goto("https://playwright.dev/")
    expect(page).to_have_title(re.compile("Playwright"))

def test_get_started_link(page: Page):
    page.goto("https://playwright.dev/")
    page.get_by_role("link", name="Get started").click()
    expect(page.get_by_role("heading", name="Installation")).to_be_visible()
bash
pytest                      # default: Chromium headless
pytest --browser firefox    # switch engine
pytest --headed --slowmo 500 # headed + slow-mo for debugging

6.2 Test Isolation: Why Each Test Gets a New Context ​

The plugin's isolation strategy: every test function receives a brand-new BrowserContext (equivalent to a fresh incognito profile), automatically destroyed at the end of the test. This is equivalent to "clear Cookie + localStorage + sessionStorage" in a beforeEach hook:

python
def test_a(page: Page):
    page.goto("https://example.com")
    # Login state and localStorage set here belong only to test_a

def test_b(page: Page):
    page.goto("https://example.com")
    # Completely clean environment, never polluted by test_a

The browser / playwright fixtures are session-scoped: the whole suite shares one browser process (expensive to start), while context/page are function-scoped (cheap isolation). This is the practical application of the Chapter 2 object model: share the expensive part, isolate with the cheap part.

6.3 Fixture List and Scopes ​

FixtureScopeDescription
pagefunctionPage专属 this test
contextfunctionBrowserContext专属 this test
new_contextfunctionManually create additional contexts (multi-user scenarios)
playwrightsessionPlaywright instance
browsersessionCurrent browser instance
browser_name / browser_channelsessionCurrent engine name/channel string
is_chromium / is_firefox / is_webkitsessionEngine boolean flags

CLI flags only affect default fixtures

--browser and similar options only affect the defaults provided by the plugin. Contexts you create manually with browser.new_context() do not inherit these flags.

6.4 CLI Options Quick Reference ​

bash
pytest --browser chromium --browser firefox --browser webkit  # Multi-engine matrix
pytest --browser-channel chrome        # Use system Chrome instead of bundled engine
pytest --device "iPhone 13"            # Device emulation
pytest --tracing retain-on-failure     # Keep trace on failure (Chapter 11)
pytest --screenshot only-on-failure    # Auto-screenshot on failure
pytest --video retain-on-failure       # Keep video on failure
pytest --output my-artifacts           # Artifacts output directory

Common options can be pinned in pytest.ini:

ini
[pytest]
addopts = --headed --browser firefox --screenshot only-on-failure

6.5 conftest Customization: browser_context_args and Custom Fixtures ​

Globally override context options (viewport, timezone, ignore HTTPS errors):

python
# conftest.py
import pytest

@pytest.fixture(scope="session")
def browser_context_args(browser_context_args):
    return {
        **browser_context_args,
        "viewport": {"width": 1920, "height": 1080},
        "locale": "zh-CN",
        "ignore_https_errors": True,
    }

Override per-test with a marker:

python
@pytest.mark.browser_context_args(timezone_id="Europe/Berlin")
def test_berlin_timezone(page):
    ...

The most common real-world customization is "a page with login state" — log in once at module scope, save the storage_state for reuse (full solution in Chapter 9):

python
@pytest.fixture(scope="module")
def auth_page(browser, base_url):
    context = browser.new_context()
    page = context.new_page()
    page.goto("/login")
    page.get_by_label("Username").fill("testuser")
    page.get_by_label("Password").fill("secret")
    page.get_by_role("button", name="Log in").click()
    context.storage_state(path="playwright/.auth/user.json")
    yield page
    context.close()

6.6 Chapter Summary ​

  • pytest-playwright auto-manages launch/close; just declare page and go;
  • Each test gets an isolated BrowserContext — no cross-test pollution;
  • browser/playwright are session-scoped (shared, expensive); page/context are function-scoped (isolated, cheap);
  • CLI options: --browser, --headed, --device, --tracing, --screenshot;
  • conftest.py's browser_context_args overrides apply globally; per-test markers apply locally.

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. What is the default test isolation strategy of pytest-playwright?

2. The browser fixture in pytest-playwright is what scope?

3. To run tests against Firefox with headed mode and slow_mo=500, the CLI command is?

4. How do you override browser_context_args for a single test?

🛠️ Hands-On Exercises ​

  1. Install pytest-playwright, write two tests that visit different URLs and assert titles; run with pytest --browser firefox --headed.
  2. Create a conftest.py that sets viewport to 1920×1080 and locale to en-US globally; verify with a test that checks page.evaluate("navigator.language").
  3. Write a test using @pytest.mark.browser_context_args(timezone_id="America/New_York") and verify new Date().toString() reflects the timezone.

Proceed to Chapter 7: Multi-Page, Popups, and iframes.