You know Playwright. You know Python. This lesson stitches them together — from an empty folder to a working pytest-playwright project that runs the same tests against Chromium, Firefox, and WebKit. If you've worked through the Python for QA course you already have everything the install needs; if you've worked through the Playwright with TypeScript course the structure here will feel deeply familiar — same ideas, snake_case names, pytest fixtures instead of test() blocks.
Prerequisites
Before you start, you need:
- Python 3.8 or later. Run
python --version(orpython3 --versionon macOS/Linux). Anything older won't work — Playwright's Python bindings require modern asyncio and type-hint features. - pip on the path. Bundled with modern Python installers. Check with
pip --version. - VS Code. Every example assumes the Microsoft Python extension is installed for type hints, fixture autocomplete, and the integrated test explorer.
If python --version prints 3.8+ and pip --version works, you're ready. We won't re-explain virtual environments, print, or pytest fundamentals — those live in the Python for QA course.
Installing — two pip packages plus the browsers
Unlike npm init playwright@latest from the TypeScript course (which scaffolds the whole project in one go), the Python install is two steps: pip the libraries, then download the browser binaries.
mkdir playwright-python-tests
cd playwright-python-tests
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install playwright pytest-playwright
playwright installWhat each package does:
playwright— the core library. Providessync_playwright,async_playwright, and thePage/Browser/Locatorclasses.pytest-playwright— the official pytest plugin. Registers thepage,browser,context, andplaywrightfixtures so your tests just declare them as parameters and pytest wires them up.playwright install— downloads Chromium, Firefox, and WebKit binaries to~/.cache/ms-playwright/. About 200 MB total. Same browsers, same versions as the TypeScript install — Microsoft pins them per Playwright release.
On a Linux CI runner you also need the system libraries each browser links against:
playwright install --with-deps--with-deps runs apt-get install for libnss3, libatk1.0-0, libxss1, and the rest of the browser dependency tree. Skip it on macOS and Windows — the packages aren't apt-managed there.
Project structure — what to put where
A clean Playwright Python project mirrors the structure you'd see in any pytest codebase, with a few Playwright-specific folders:
playwright-python-tests/
├── tests/
│ ├── conftest.py ← shared fixtures and config
│ ├── test_login.py ← test files start with test_
│ └── test_products.py
├── pages/ ← page objects (chapter 5)
├── fixtures/ ← test data JSON files
├── requirements.txt ← dependencies
└── pytest.ini ← pytest configuration
The naming rules pytest enforces:
- Test files start with
test_(or end with_test). - Test functions start with
test_. - Test classes start with
Test.
These are not Playwright conventions — they're pytest's discovery defaults. Break them and pytest silently won't run your tests, which is the most common "why is my test not running?" beginner trap.
Pin your dependencies — requirements.txt
A reproducible install means pinning versions:
playwright==1.44.0
pytest==8.2.0
pytest-playwright==0.5.0
Install from the file with pip install -r requirements.txt. CI does the same — lock to the exact same versions as your dev machine and you eliminate "works on my laptop" mysteries.
pytest.ini — your project's settings file
pytest.ini is where pytest picks up its run-time options. The Playwright-relevant ones:
[pytest]
addopts = --headed --browser chromium
base_url = http://localhost:3000What each line does:
addopts— flags pytest applies on every run.--headedshows the browser window (helpful for development);--browser chromiumselects the browser. Drop--headedfor CI.base_url— the URLpage.goto("/login")is resolved against. The Playwright Python equivalent ofbaseURLinplaywright.config.ts.
You can also set the test directory, custom markers, and warning filters here — same syntax as any other pytest project.
Running your first test — the commands
The flags you'll use every day:
pytest tests/ -v # all tests, verbose output
pytest tests/test_login.py -v # one file
pytest tests/ --browser firefox # Firefox only
pytest tests/ --browser chromium --browser firefox # both browsers
pytest tests/ -k "login and not admin" # name filter
pytest tests/ -m smoke # marker filterThe --browser flag is the Python equivalent of playwright.config.ts's projects array — pass it multiple times to fan out across browsers without changing a line of test code.
Headed vs headless mirrors the TypeScript course exactly:
- Headless (default) — no UI, fast, the right mode for CI.
- Headed (
--headed) — see the browser do the test in real time. Useful while authoring and debugging; remove it frompytest.inionce tests are stable.
The full installation flow
Step 1 of 5
Create the project
mkdir + cd + python -m venv .venv to create an isolated virtualenv. Activate it so pip installs land in the project, not your global Python.
Coming from Playwright TypeScript?
The mapping is deeply direct — same engine, Pythonic naming:
playwright.config.ts→pytest.iniplusconftest.pytest('name', async ({ page }) => { ... })→def test_name(page: Page):await page.goto('/login')→page.goto("/login")(sync API)npx playwright test→pytestnpx playwright test --ui→pytest --headed(no full UI mode in Python yet — use--headedplus the Inspector)npx playwright codegen→playwright codegen --target python-pytestgetByRole,toBeVisible,toHaveText→get_by_role,to_be_visible,to_have_text
The biggest mental shift is no more await in the typical test. Python's sync API lets pytest-playwright give you the same auto-waiting behaviour as the TypeScript runner without async syntax bleeding into every line. We'll cover sync vs async in the next lesson.
Recommended VS Code extensions
While the install is fresh:
- Python (
ms-python.python) — Microsoft's official extension. Picks up your virtualenv, runs pytest from the Test Explorer, surfaces fixture types in autocomplete. - Pylance (
ms-python.vscode-pylance) — fast type checker. Lights uppage: Pageautocomplete on every Playwright method. - Black Formatter (
ms-python.black-formatter) — auto-formats Python on save.
The full reference is on the Playwright tools page; pytest-specific patterns live in the Python for QA reference.
⚠️ Common mistakes
- Forgetting
playwright installafterpip install. The pip package contains the bindings but no browsers. Running pytest withoutplaywright installfirst throwsplaywright._impl._errors.Error: Executable doesn't exist. On CI, runplaywright install --with-depsin the same job that installs the requirements — never assume the runner has them cached. - Naming a test file
login_test.py(Java/Go style) instead oftest_login.py. pytest discovers files matchingtest_*.pyor*_test.py. The first form is far more common in the Python world; if your test file isn't picked up, this is the first thing to check. Same rule applies to functions:def test_login(...)is collected,def login_test(...)is silently skipped. - Mixing the sync and async APIs in one test.
pytest-playwrightexposes the syncpagefixture. If youimport async_playwrightor sprinkleawaitin your tests, you get a confusingRuntimeError: This event loop is already running. Pick one API per project — sync for 99% of QA suites, as covered in the next lesson.
🎯 Practice task
Stand up the project you'll use for the rest of the course. 25-30 minutes.
-
Create a folder
playwright-python-tests/. Inside it, runpython -m venv .venvand activate the virtualenv. -
Run
pip install playwright pytest-playwrightthenplaywright install. Confirm the install withplaywright --version(you should see something likeVersion 1.44.0). -
Create
requirements.txt:playwright==1.44.0 pytest==8.2.0 pytest-playwright==0.5.0 -
Create
pytest.ini:[pytest] addopts = --headed --browser chromium base_url = https://www.saucedemo.com -
Create
tests/test_smoke.pywith a single sanity test:from playwright.sync_api import Page, expect def test_saucedemo_loads(page: Page): page.goto("/") expect(page.get_by_placeholder("Username")).to_be_visible() -
Run
pytest tests/ -v. A Chromium window opens, the Sauce Demo login page loads, and pytest reports1 passed. -
Re-run with
pytest tests/ --browser firefox -v. The same test runs in Firefox. Then run it against both:pytest tests/ --browser chromium --browser firefox -v— pytest reports2 passed(one test, two browsers). -
Stretch: drop
--headedfrompytest.iniand re-run. The test still passes but no browser window appears — that's the headless mode every CI run uses. Add apages/andfixtures/folder to your project tree so it matches the structure shown above; you'll fill them in later chapters.
Once pytest tests/ is green across two browsers headlessly, your Playwright Python install is complete. The next lesson breaks down sync vs async — when each fits, why pytest-playwright defaults to sync, and what async unlocks for the rare cases that need it.