In Lesson 6 you used pytest.ini for a few Playwright options. This lesson is the complete reference for the settings that matter in real test projects: which file pytest reads and how it picks the project root, changing discovery rules, silencing known warnings, live logging, enforcing plugin and pytest versions, and overriding settings from the environment in CI. Every setting shown here was tested with pytest 9.2.
📖 What You’ll Learn: which configuration files pytest reads and their precedence, rootdir and the configfile header, testpaths and norecursedirs, custom discovery with python_files, python_classes and python_functions, filterwarnings, live logging with log_cli, required_plugins and minversion, the PYTEST_ADDOPTS environment variable, and a complete recommended pytest.ini for Playwright.
📄 Which File Does pytest Read?
pytest looks for configuration in the folder of your test paths and then in its parent folders, and uses the first match it finds. In each folder it checks, in this order:
| File | Section | Notes |
|---|---|---|
pytest.ini | [pytest] | Always wins, even if empty. Recommended for test projects. |
pyproject.toml | [tool.pytest.ini_options] (or [tool.pytest] in pytest 9) | Common when the project already uses pyproject.toml. |
tox.ini | [pytest] | Used by projects that also use tox. |
setup.cfg | [tool:pytest] | Older projects; not recommended for new ones. |
The folder containing the chosen file becomes the rootdir, the base for test IDs and relative paths. The first lines of every run tell you what pytest decided:
🖥️ Output
rootdir: C:\pwv\pytest-configuration
configfile: pytest.ini
testpaths: tests
Check these lines first whenever settings seem to be ignored. You can also force a specific file with -c path/to/pytest.ini.
🗂️ Where to Look for Tests
testpaths = tests
norecursedirs = .venv node_modules test-results
testpathslists the folders that a barepytestcommand searches. It makes collection faster and avoids picking up stray files.norecursedirslists folder names (glob patterns) that pytest never enters. pytest already skips common ones such as.*,venvandbuildby default; setting this option replaces that default list, so include anything you still need to skip.
🔍 Custom Discovery Rules
If your team names files differently, change the patterns. This project collects check_*.py files, Verify* classes and check_* functions in addition to the defaults (pytest.ini below, used for the whole lesson):
📄 pytest.ini
[pytest]
minversion = 9.0
testpaths = tests
pythonpath = .
python_files = test_*.py check_*.py
python_classes = Test* Verify*
python_functions = test_* check_*
addopts = --strict-markers -ra
markers =
smoke: quick checks of the most important flows
filterwarnings =
ignore:Session-scoped fixtures defined as instance methods:pytest.PytestRemovedIn10Warning
log_cli = true
log_cli_level = INFO
required_plugins = pytest-playwright>=0.10.0
📄 tests/check_login.py
import logging
import pytest
from playwright.sync_api import Page, expect
log = logging.getLogger(__name__)
@pytest.mark.smoke
def check_login_page(page: Page):
log.info("opening the SQA Tools login page")
page.goto("https://sqatools.in/login-page/")
expect(page.get_by_role("button", name="Log in")).to_be_visible()
log.info("login button is visible")
class VerifyPracticePage:
def check_table_rows(self, page: Page):
page.goto("https://sqatools.in/automation-practice-page/")
expect(page.locator("table tbody tr")).to_have_count(3)
python_files = test_*.py check_*.pyaddscheck_*.pyfiles to the defaulttest_*.py.python_classesandpython_functionswork the same way for classes and functions. With these settings,check_login_pageandVerifyPracticePage.check_table_rowsare real tests.- Patterns are separated by spaces.
⚠️ Watch out: Changing discovery rules surprises new team members, who expect test_ names. Only do it when you have a strong reason, such as an existing naming convention.
🔇 filterwarnings: Silence Known Warnings
Since Lesson 2 you have seen the same harmless PytestRemovedIn10Warning from pytest-playwright’s base_url fixture. filterwarnings hides warnings you have reviewed and accepted, so new, important warnings stand out:
filterwarnings =
ignore:Session-scoped fixtures defined as instance methods:pytest.PytestRemovedIn10Warning
The format is action:message:category:
- action:
ignore,error(turn the warning into a test failure),default,alwaysand others; - message: the start of the warning text (a regular expression);
- category: the warning class, here
pytest.PytestRemovedIn10Warning.
error is useful too: filterwarnings = error::DeprecationWarning makes your suite fail as soon as your own code uses something deprecated.
💡 Tip: Keep filters narrow. Ignoring a whole category like DeprecationWarning hides warnings you would want to see.
📝 Live Logging with log_cli
Tests and page objects should use Python’s logging module rather than print. With log_cli = true, pytest shows log messages live as each test runs, at or above log_cli_level:
⌨️ Terminal
pytest -v
🖥️ Output
configfile: pytest.ini
testpaths: tests
plugins: playwright-0.10.0
collecting ... collected 2 items
tests/check_login.py::check_login_page[chromium]
-------------------------------- live log call --------------------------------
INFO check_login:check_login.py:11 opening the SQA Tools login page
INFO check_login:check_login.py:14 login button is visible
PASSED [ 50%]
tests/check_login.py::VerifyPracticePage::check_table_rows[chromium] PASSED [100%]
============================== 2 passed in 4.76s ==============================
Notice three things in this real output:
- Both custom-named tests were collected (
check_login_pageandVerifyPracticePage::check_table_rows). - The log lines appear under “live log call”, with level, logger name, file and line.
- There is no warnings summary: the filter silenced the known plugin warning.
Related settings: log_cli_format and log_cli_date_format change the line format, and log_file = logs/pytest.log also writes logs to a file.
🛡️ Guard Rails: minversion and required_plugins
minversion = 9.0stops the run if pytest is older than 9.0.required_plugins = pytest-playwright>=0.10.0stops the run if a plugin is missing or too old.
Both turn “works on my machine” problems into a clear error message. Here is what happens when a required plugin is missing (strict/pytest.ini asks for a plugin that is not installed):
📄 strict/pytest.ini
[pytest]
required_plugins = pytest-rerunfailures>=14.0
📄 strict/test_nothing.py
def test_placeholder():
assert True
⌨️ Terminal
pytest -q -c strict/pytest.ini strict
🖥️ Output
ERROR: Missing required plugins: pytest-rerunfailures>=14.0
pytest refuses to run any test until the environment is fixed, and exits with code 4 (a usage error), so CI fails clearly.
🌍 Override Settings from the Environment: PYTEST_ADDOPTS
CI systems often need extra options without editing files. pytest reads the PYTEST_ADDOPTS environment variable and adds its contents to the command line, after addopts from the config file. This script proves it by running the same tests twice (addopts_env.py):
📄 addopts_env.py
import os
import subprocess
PY = r"C:\pwv\_venv0\Scripts\python"
base = [PY, "-m", "pytest", "-p", "no:cacheprovider", "-q", "--collect-only"]
normal = subprocess.run(base, capture_output=True, text=True).stdout
print("normal run:", normal.strip().splitlines()[-1])
env = dict(os.environ, PYTEST_ADDOPTS="-m smoke")
smoke = subprocess.run(base, capture_output=True, text=True, env=env).stdout
print("with PYTEST_ADDOPTS='-m smoke':", smoke.strip().splitlines()[-1])
🖥️ Output
normal run: 2 tests collected in 0.02s
with PYTEST_ADDOPTS='-m smoke': 1/2 tests collected (1 deselected) in 0.02s
In a CI pipeline you would set PYTEST_ADDOPTS="-m smoke --browser firefox" for one job and something else for another, with the same pytest.ini.
✅ A Recommended pytest.ini for Playwright Projects
[pytest]
minversion = 9.0
testpaths = tests
pythonpath = .
addopts =
--strict-markers
-ra
--tracing retain-on-failure
--screenshot only-on-failure
markers =
smoke: quick checks of the most important flows
regression: full functional coverage
slow: tests that take longer than a few seconds
xfail_strict = true
filterwarnings =
ignore:Session-scoped fixtures defined as instance methods:pytest.PytestRemovedIn10Warning
log_cli = true
log_cli_level = INFO
required_plugins = pytest-playwright>=0.10.0
- A multi-line
addoptsis allowed: indent the continuation lines. -raprints a short summary of every test that did not pass (failures, errors, skips, xfails), which is ideal in CI logs.- Browser choice stays on the command line or in
PYTEST_ADDOPTS, for the reason explained in Lesson 6:--browservalues add up.
❓ Frequently Asked Questions
🤔 pytest.ini or pyproject.toml?
Either works. pytest.ini is explicit and always takes priority; pyproject.toml keeps all tool settings in one file. Pick one and use only one, since pytest.ini silently wins when both exist (apart from a note in the header).
🤔 Can command-line options override the config file?
Yes. addopts comes first, then PYTEST_ADDOPTS, then what you type, so later single-value options win. Ini settings that are not command-line options (such as testpaths) can be overridden with -o name=value, for example pytest -o log_cli=false.
🤔 Should log_cli be on in CI?
It is helpful in CI logs because you see progress live. Locally, some people prefer it off and use -s or the captured log sections shown for failed tests.
🤔 Where do I set the default expect timeout?
Not in pytest.ini: it is a Playwright setting. Call expect.set_options(timeout=10_000) in conftest.py, as shown in Lesson 6.
🏁 Summary
- pytest uses the first of pytest.ini, pyproject.toml, tox.ini or setup.cfg it finds; check the
rootdirandconfigfileheader. - Use
testpaths,pythonpath,addopts,markersandxfail_strictin almost every project; change discovery patterns only with good reason. filterwarningssilences reviewed warnings,log_clishows logs live,minversionandrequired_pluginsguard the environment.PYTEST_ADDOPTSand-o name=valueadjust a run without editing files.
Next, you will extend pytest itself with plugins and hook functions, including a screenshot-on-failure hook for Playwright.
🎬 Watch the Playwright tutorial series on YouTube!
Subscribe to SqaToolsByDY • Practice on the SQA Tools Automation Practice Page