skip to content

Language Surface Reach

The browser API is available in JavaScript, Python, Java and .NET, but the batteries-included runner is not. Interviewers use this to test whether you know what a non-JS team actually gets.

on this pageshow

explore

questions

5

In a Python Playwright suite, what does the pytest-playwright page fixture give each test?

level: juniorimportance: must knowfreq 58%

answer

  1. One fixture, three objects behind it
  2. The browser outlives the test
  3. The context does not
  4. Isolation comes from a fresh context
  5. Override browser_context_args to configure it

basics

~20 s

The 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 s

Declaring `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 lines
python
from 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

for a junior

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.

for a middle

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.

for a senior

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.

for a principal

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
open as a page

Which parts of Playwright do the Python, Java and .NET bindings get, and which are JavaScript-only?

level: middleimportance: must knowfreq 72%

basics

~20 s

Playwright's browser automation library, covering browser, context, page, locators, assertions, routing and tracing, ships in Python, Java and .NET. The @playwright/test runner does not: projects, retries, sharding, custom fixtures, component mounting and its reporters stay JavaScript-only.

open as a page

How do Playwright's Java and .NET bindings hand a test a ready-made Page without the JavaScript runner?

level: middleimportance: should knowfreq 44%

basics

~20 s

Through small per-language integration layers. In Java, @UsePlaywright on a JUnit 5 class injects Page, BrowserContext and Browser as test-method parameters. In .NET, inheriting the PageTest base class from Microsoft.Playwright.NUnit or MSTest exposes Page, Context and Expect().

open as a page

In Playwright for .NET, a NUnit insurance-quote suite needs cross-browser runs, retries and traces -- what must the team build itself?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Everything the JavaScript config would declare. Playwright for .NET has no projects, retries or trace mode, so the browser comes from run settings or a CI job per engine, tracing from explicit Context.Tracing calls, and retries from the host runner.

open as a page

How do you choose between Playwright's Java binding and a TypeScript suite when replacing a legacy insurance-quote regression pack?

level: principalimportance: nice to knowfreq 31%

basics

~20 s

Trade harness work against organisational fit. Both bindings drive the browser identically, so the question is whether writing tests in the team's own language is worth rebuilding the JavaScript runner's matrix, retries, parallelism and reporting on JUnit and CI.

open as a page