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:
pip install pytest-playwright
playwright install # download engines on first runWriting tests is just declaring the page fixture — everything else is automatic:
# 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()pytest # default: Chromium headless
pytest --browser firefox # switch engine
pytest --headed --slowmo 500 # headed + slow-mo for debugging6.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:
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_aThe 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
| Fixture | Scope | Description |
|---|---|---|
page | function | Page专属 this test |
context | function | BrowserContext专属 this test |
new_context | function | Manually create additional contexts (multi-user scenarios) |
playwright | session | Playwright instance |
browser | session | Current browser instance |
browser_name / browser_channel | session | Current engine name/channel string |
is_chromium / is_firefox / is_webkit | session | Engine 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
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 directoryCommon options can be pinned in pytest.ini:
[pytest]
addopts = --headed --browser firefox --screenshot only-on-failure6.5 conftest Customization: browser_context_args and Custom Fixtures
Globally override context options (viewport, timezone, ignore HTTPS errors):
# 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:
@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):
@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-playwrightauto-manages launch/close; just declarepageand go;- Each test gets an isolated BrowserContext — no cross-test pollution;
browser/playwrightare session-scoped (shared, expensive);page/contextare function-scoped (isolated, cheap);- CLI options:
--browser,--headed,--device,--tracing,--screenshot; conftest.py'sbrowser_context_argsoverrides 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
- Install
pytest-playwright, write two tests that visit different URLs and assert titles; run withpytest --browser firefox --headed. - Create a
conftest.pythat sets viewport to 1920×1080 and locale toen-USglobally; verify with a test that checkspage.evaluate("navigator.language"). - Write a test using
@pytest.mark.browser_context_args(timezone_id="America/New_York")and verifynew Date().toString()reflects the timezone.
Proceed to Chapter 7: Multi-Page, Popups, and iframes.