Playwright with TypeScript has a single playwright.config.ts file. In Python, configuration is split between two places that every pytest user knows: a settings file (pytest.ini or pyproject.toml) for default command-line options, and conftest.py for Python code that customises the browser, context and timeouts. In this lesson you will build a complete, realistic configuration step by step and verify each setting with a test.
π What You’ll Learn: where pytest looks for configuration, addopts, testpaths and base_url in pytest.ini and pyproject.toml, how addopts combines with command-line options, overriding browser_context_args and browser_type_launch_args in conftest.py, per-test context settings with a marker, and setting default timeouts for actions and assertions.
πΊοΈ Where Configuration Lives
| What you want to configure | Where it goes | Example |
|---|---|---|
| Default command-line options | pytest.ini or pyproject.toml (addopts) | --browser chromium --tracing retain-on-failure |
| Where tests live, base URL | pytest.ini or pyproject.toml | testpaths = tests, base_url = https://... |
| Browser launch settings | conftest.py fixture browser_type_launch_args | extra Chromium flags |
| Context settings (viewport, locale, timezone, HTTPS errors) | conftest.py fixture browser_context_args | {"viewport": {"width": 1440, "height": 900}} |
| Timeouts | conftest.py | expect.set_options(timeout=10_000) |
pytest finds its settings file by looking in the folder you run it from and then in parent folders. Put the settings file and conftest.py in your project’s root folder, next to the tests folder.
π Option 1: pytest.ini
Create pytest.ini in the project root:
π pytest.ini
[pytest]
# Look for tests only in the tests folder
testpaths = tests
# Options added to every pytest run
addopts = -v --tracing retain-on-failure --screenshot only-on-failure
# Relative URLs in page.goto() are resolved against this
base_url = https://sqatools.in
[pytest]is the required section header.testpaths = testsmakes a barepytestcommand search only thetestsfolder, which is faster and avoids collecting stray files.addoptsis added in front of whatever you type, so every run now uses verbose output and keeps traces and screenshots of failures.base_urllets tests callpage.goto("/automation-practice-page/"). A--base-urlon the command line takes priority over this value.- Lines starting with
#are comments.
π Option 2: pyproject.toml
Many Python projects keep all tool settings in pyproject.toml. The same configuration looks like this:
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-v --tracing retain-on-failure --screenshot only-on-failure"
base_url = "https://sqatools.in"
- The section is
[tool.pytest.ini_options], and values follow TOML rules: strings in quotes, lists in square brackets. - pytest 9 also accepts a native
[tool.pytest]table, whereaddoptsis a list:addopts = ["-v", "--tracing", "retain-on-failure"]. Theini_optionsform works on every pytest version, so it is the safer choice for shared projects.
β οΈ Watch out: Use only one settings file. If both pytest.ini and a [tool.pytest.ini_options] section exist, pytest.ini wins and the pyproject settings are ignored. The only sign is a short note in the run header: configfile: pytest.ini (WARNING: ignoring pytest config in pyproject.toml!). Check the configfile: line whenever settings seem to have no effect.
βοΈ How addopts Combines with the Command Line
addopts does not replace what you type; it is added before it. For options that take one value, the last one wins, so the command line overrides the file. But --browser can be repeated, so the values add up. To see it, imagine addopts = --browser firefox in pytest.ini and this small test:
def test_which_browser(page, browser_name):
print("RUN", browser_name)
β¨οΈ Terminal
pytest -q -s --browser webkit
π₯οΈ Output
RUN firefox
RUN webkit
2 passed in 2.04s
The test ran in both Firefox (from the file) and WebKit (from the command line). If you want to switch browsers freely from the command line, do not put --browser in addopts; Chromium is already the default.
π conftest.py: Python-Powered Configuration
conftest.py is a special pytest file. Fixtures defined in it are available to every test in its folder and sub-folders without importing anything. pytest-playwright builds its browser, context and page fixtures from a few settings fixtures, and you customise them by redefining those settings fixtures in conftest.py.
Create conftest.py in the project root:
π conftest.py
import pytest
from playwright.sync_api import Page, expect
@pytest.fixture(scope="session")
def browser_context_args(browser_context_args):
"""Settings applied to every new browser context (and so every page)."""
return {
**browser_context_args, # keep the plugin's own settings
"viewport": {"width": 1440, "height": 900}, # larger desktop window
"locale": "en-GB", # browser language
"timezone_id": "Europe/London", # browser time zone
"ignore_https_errors": True, # accept self-signed test certificates
}
@pytest.fixture(scope="session")
def browser_type_launch_args(browser_type_launch_args):
"""Settings used when the browser process is launched."""
return {
**browser_type_launch_args,
"slow_mo": 0, # keep 0 in CI; raise it to watch tests
}
@pytest.fixture(autouse=True)
def default_timeouts(page: Page):
"""Runs before every test: make actions and navigations time out after 15 s."""
page.set_default_timeout(15_000)
page.set_default_navigation_timeout(30_000)
# Web-first assertions (expect) wait 5 s by default; give slow pages 10 s
expect.set_options(timeout=10_000)
Line by line:
- **
browser_context_args** is a session-scoped fixture provided by pytest-playwright. Your version receives the original as a parameter (also namedbrowser_context_args), and returns a new dictionary that starts with**browser_context_argsso the plugin’s own settings (such as the--base-urland--devicevalues) are kept, then adds yours. - The keys are the same as the parameters of
browser.new_context():viewport,locale,timezone_id,ignore_https_errors,user_agent,color_scheme,geolocation,permissions,storage_state, and more. - **
scope="session"** must match the plugin’s fixture, because the settings are calculated once for the whole run. - **
browser_type_launch_args** works the same way forlaunch()options such asslow_mo,args(extra browser command-line flags) ordownloads_path. - **
autouse=True** runs thedefault_timeoutsfixture for every test automatically. It asks forpage, so it changes the timeouts of the very page your test receives. - **
expect.set_options(timeout=10_000)** changes the default wait for everyexpect()assertion. Becauseconftest.pyis imported before the tests run, it applies everywhere.
π‘ Tip: 15_000 is the same number as 15000. Python ignores the underscores; they just make large numbers easier to read.
β Prove the Configuration Works
Create tests/test_config.py:
π tests/test_config.py
from playwright.sync_api import Page, expect
def test_context_settings_are_applied(page: Page):
page.goto("/automation-practice-page/") # relative URL thanks to base_url
assert page.viewport_size == {"width": 1440, "height": 900}
assert page.evaluate("navigator.language") == "en-GB"
assert page.evaluate("Intl.DateTimeFormat().resolvedOptions().timeZone") == "Europe/London"
def test_base_url_is_used(page: Page):
page.goto("/automation-practice-page/")
expect(page).to_have_url("https://sqatools.in/automation-practice-page/")
Run pytest with no arguments:
β¨οΈ Terminal
pytest
π₯οΈ Output
configfile: pytest.ini
testpaths: tests
plugins: playwright-0.10.0
collecting ... collected 3 items
tests/test_config.py::test_context_settings_are_applied[chromium] PASSED [ 33%]
tests/test_config.py::test_base_url_is_used[chromium] PASSED [ 66%]
tests/test_markers.py::test_dark_mode_for_one_test[chromium] PASSED [100%]
configfile: pytest.iniandtestpaths: testsconfirm which settings file pytest used.- The
-vstyle output appears without typing-v, because it is inaddopts. - The plugins line on your machine may list different plugins; that is fine.
The same tests also pass in Firefox, which shows the settings are browser-independent:
β¨οΈ Terminal
pytest --browser firefox
π― Different Settings for One Test
Sometimes one test needs something special, such as dark mode or a different language. pytest-playwright provides a marker for that. Create tests/test_markers.py:
π tests/test_markers.py
import pytest
from playwright.sync_api import Page
@pytest.mark.browser_context_args(color_scheme="dark", locale="fr-FR")
def test_dark_mode_for_one_test(page: Page):
page.goto("/automation-practice-page/")
assert page.evaluate("matchMedia('(prefers-color-scheme: dark)').matches") is True
assert page.evaluate("navigator.language") == "fr-FR"
@pytest.mark.browser_context_args(...)adds or overrides context settings for this test only.- Settings from
conftest.pythat the marker does not mention (here the 1440×900 viewport) still apply. matchMedia(...)is a browser API; the test asks the page whether it is in dark mode.
ποΈ The Project Structure
π₯οΈ Output
playwright-python-demo/
βββ .venv/
βββ conftest.py # fixtures: context args, launch args, timeouts
βββ pytest.ini # defaults: testpaths, addopts, base_url
βββ requirements.txt
βββ tests/
βββ test_config.py
βββ test_markers.py
π TypeScript Config vs Python Config
If you know Playwright with TypeScript, this table maps the familiar playwright.config.ts settings to Python:
| playwright.config.ts | Python equivalent |
|---|---|
testDir: './tests' | testpaths = tests |
use: { baseURL } | base_url in pytest.ini, or --base-url |
use: { viewport, locale, timezoneId } | browser_context_args fixture in conftest.py |
use: { trace: 'retain-on-failure' } | --tracing retain-on-failure in addopts |
expect: { timeout: 10000 } | expect.set_options(timeout=10_000) |
use: { actionTimeout } | page.set_default_timeout() in an autouse fixture |
projects: [...] | --browser (repeatable) or --device |
retries: 2 | pytest-rerunfailures plugin: --reruns 2 |
workers: 4 | pytest-xdist plugin: -n 4 |
β Frequently Asked Questions
π€ Can I have more than one conftest.py?
Yes. A conftest.py in a sub-folder adds or overrides fixtures for the tests in that folder only. The root conftest.py applies to everything. You will use this in Sharing Fixtures with conftest.py.
π€ Why does my browser_context_args override seem to do nothing?
The most common causes are a missing scope="session", forgetting to return the dictionary, or a key misspelled (for example timezone instead of timezone_id). Run a test that prints page.viewport_size or navigator.language to confirm.
π€ Where do I set the browser headless mode in config?
Headless is the default. Add --headed to addopts if you want windows on your own machine, but do not commit that, because CI servers have no screen.
π€ Should I put secrets like passwords in pytest.ini?
No. Keep secrets in environment variables or a .env file that is not committed. You will learn this in Environment Variables and .env Files.
π Summary
pytest.iniorpyproject.tomlholdstestpaths,addoptsandbase_url; use only one settings file.addoptsis added before command-line options, and repeatable options like--browseradd up.- Override
browser_context_argsandbrowser_type_launch_argsinconftest.py(session scope, keep the original with**). - Use an autouse fixture for action timeouts,
expect.set_options()for assertion timeouts, and thebrowser_context_argsmarker for one-off settings.
Next, you will let Playwright write tests for you with Codegen, and learn how to turn recorded code into clean pytest tests.
π¬ Watch the Playwright tutorial series on YouTube!
Subscribe to SqaToolsByDY • Practice on the SQA Tools Automation Practice Page
2 thoughts on “Configure Playwright Python Tests: pytest.ini, pyproject.toml and conftest.py”