In a Python Playwright suite, what does the pytest-playwright page fixture give each test?
answer
- One fixture, three objects behind it
- The browser outlives the test
- The context does not
- Isolation comes from a fresh context
- Override browser_context_args to configure it
basics
~20 sThe pytest-playwright plugin's page fixture gives every test a fresh Playwright Page in its own new BrowserContext, opened on a session-scoped browser and closed automatically when the test ends, so cookies and storage never leak between tests.
solid answer
~30 sDeclaring `page` as a test argument makes the plugin launch a session-scoped `Browser` once, create a function-scoped `BrowserContext` for that test, open one `Page` in it, and close the context afterwards. That is where per-test isolation comes from: contexts are cheap, browsers are not. The plugin also exposes `context`, `browser`, `new_context`, `browser_name` and the `browser_context_args` and `browser_type_launch_args` override points, and adds flags such as `--browser`, `--headed`, `--base-url`, `--tracing`, `--video` and `--screenshot`. You configure the context by overriding `browser_context_args` in `conftest.py`, not by editing a config file -- the plugin never reads `playwright.config.ts`. Everything else, including parallelism and retries, stays with pytest.
code
python · 8 linesfrom playwright.sync_api import Page, expect
def test_annual_premium_is_shown(page: Page) -> None:
page.goto("https://quotes.example.com/motor")
page.get_by_label("Vehicle value").fill("18500")
page.get_by_role("button", name="Get quote").click()
expect(page.get_by_test_id("annual-premium")).to_have_text("412.60")go deeper
Recall the shape: ask for page in the test signature and you get a ready page in a clean, private browser context that is torn down for you. You never launch or close anything yourself.
Explain the scoping. The browser is session-scoped and the context is function-scoped, which is why isolation is cheap, and configuration happens by overriding browser_context_args rather than by writing a config file.
Talk about what you turn on in CI: retain-on-failure tracing and video through the plugin flags, an artefact output directory, and a storage_state context argument so tests do not each pay for a login.
Judge the plugin for what it is: a thin adapter that deliberately leaves discovery, parallelism, retries and reporting to pytest. Decide how much runner machinery your team should own on top of it before that becomes a maintenance burden.
The `pytest-playwright` plugin is the whole of Playwright's Python test integration. It is small on purpose: it creates browser objects around each test and adds a handful of command-line flags, and everything else about running tests stays with pytest. ## What the fixture builds for each test Requesting `page` in a test signature triggers a short chain: 1. A session-scoped `browser` is launched once for the whole run -- launching per test would be far too slow. 2. A function-scoped `context` is created from that browser for the test that asked, so cookies, `localStorage` and permissions start empty. 3. A single `page` is opened inside that context and handed to the test. 4. When the test finishes, the context is closed, which disposes the page and any artefacts configured for the run. The important consequence is isolation without cost: a browser process is expensive, a `BrowserContext` is cheap, so per-test freshness comes from the context rather than from restarting the browser. A legacy insurance-quote suite that used to log in once and share a session across tests usually gets faster, not slower, when each test gets its own context. ## The fixtures the plugin ships - `page` -- a `Page` in a fresh context, the one you use in nearly every test - `context` -- the `BrowserContext` behind that page, for opening a second tab or reading cookies - `browser` -- the session-scoped `Browser`, when you need to build contexts yourself - `new_context` -- a factory that makes extra contexts which are still closed for you - `browser_name` and `browser_channel` -- the engine and channel in effect for the current run - `is_chromium`, `is_firefox`, `is_webkit` -- booleans for the occasional engine-specific branch - `playwright` -- the root object, for device descriptors and `expect` configuration - `browser_type_launch_args` and `browser_context_args` -- override these in `conftest.py` to change how the browser and every context are created ## Flags the plugin adds to pytest | Flag | Effect | | --- | --- | | `--browser` | which bundled engine to use; repeat it to run every test once per engine | | `--browser-channel` | use a branded build such as `chrome` or `msedge` | | `--headed` | show the browser window instead of running headless | | `--slowmo` | delay each operation by the given number of milliseconds | | `--device` | apply a device descriptor by name | | `--base-url` | base for relative URLs passed to `page.goto()` | | `--tracing` | `on`, `off` or `retain-on-failure` | | `--video` | `on`, `off` or `retain-on-failure` | | `--screenshot` | `on`, `off` or `only-on-failure` | | `--output` | directory for artefacts, `test-results` by default | | `--ignore-https-errors` | accept self-signed certificates | `--tracing retain-on-failure` is the flag worth knowing by name: it is how a Python suite gets the same failure-diagnosis artefact a JavaScript suite gets from its config, without writing any tracing code. ## Changing how contexts are created Because the plugin builds the context, you configure it by overriding a fixture rather than by editing a config file: ```python import pytest @pytest.fixture(scope="session") def browser_context_args(browser_context_args): return { **browser_context_args, "viewport": {"width": 1440, "height": 900}, "locale": "en-GB", "ignore_https_errors": True, } ``` Anything a `browser.new_context()` call accepts can go in that dictionary, including `storage_state` for a pre-authenticated session. ## What the plugin is not It is not a runner, and confusing the two is the classic mistake: - it does not read `playwright.config.ts`, and nothing in that file affects a Python run - it has no `projects`; repeating `--browser` parametrises tests, which covers the engine but not per-project options, dependencies or setup ordering - it has no retries, no sharding and no worker model of its own -- parallelism comes from running pytest under `pytest-xdist`, and retries from a pytest plugin - its default API is synchronous; `async def` tests need the separate `pytest-playwright-asyncio` distribution Everything on that list is pytest's territory, and that is the trade: you keep the runner your team already knows and rebuild the small amount of Playwright-specific plumbing yourself.
- How would you give every test in the suite a 1440px viewport and a saved login session?Override the `browser_context_args` fixture in `conftest.py`, spreading the incoming dictionary and adding `viewport` and `storage_state`. The plugin passes that dictionary straight to `browser.new_context()`, so anything the context accepts works there, and it applies to every test that requests `page` or `context`.
- What actually happens when you pass --browser twice on the pytest command line?Each test is parametrised across the named engines, so it runs once per engine and the test id gains a suffix identifying the browser. It is engine-level parametrisation only: unlike a JavaScript project entry it carries no per-engine options, dependencies or separate setup.
saying these in an interview costs you the question
- Thinks a new browser process is launched for every test
- Says isolation comes from clearing cookies between tests
- Configures the run by adding playwright.config.ts to a Python repo
- Assumes the plugin brings its own parallelism and retries
- Believes the page fixture must be closed manually