Skip to content

02 · Installation & Project Setup

Creating the project

mkdir playwright-practice && cd playwright-practice
python3 -m venv .venv
source .venv/bin/activate      # Windows: .venv\Scripts\activate

Installing Playwright

There are two separate installs, and skipping the second one is the single most common "why doesn't this work" moment for newcomers:

pip install pytest-playwright
playwright install
Collecting pytest-playwright
  ...
Successfully installed playwright-1.4x.0 pytest-playwright-0.5.x

Downloading Chromium 1xx.x (playwright build vXXXX) ...
Downloading Firefox 1xx.x (playwright build vXXXX) ...
Downloading Webkit 1x.x (playwright build vXXXX) ...
Playwright Host validation warnings:
...

pip install gets you the Python library — the API you import and call. playwright install downloads the actual browser binaries Playwright drives, into a local cache (~/.cache/ms-playwright on Linux/macOS, %USERPROFILE%\AppData\Local\ms-playwright on Windows). Those binaries are versioned and tested together with the Playwright release you installed — this is why Playwright doesn't just reuse whatever Chrome you already have on your machine: it guarantees the automation protocol and the browser build are compatible.

If you only need one engine (common in CI to save time):

playwright install chromium

System dependencies (Linux)

On a fresh Linux machine (including most CI images) the browsers also need OS-level shared libraries:

playwright install --with-deps chromium

This runs the platform's package manager under the hood to install what's missing. macOS and Windows generally don't need this step.

Verifying the install

playwright --version
Version 1.4x.0
python3 -c "from playwright.sync_api import sync_playwright; \
p = sync_playwright().start(); b = p.chromium.launch(); \
print('OK:', b.version); b.close(); p.stop()"
OK: 1xx.0.xxxx.xx

If that prints OK and a Chromium version, the library and the binary are both correctly installed and can talk to each other.

Project layout

A conventional pytest-playwright project looks like this:

playwright-practice/
├── .venv/
├── pytest.ini
├── conftest.py
├── requirements.txt
└── tests/
    ├── test_homepage.py
    └── test_login.py

requirements.txt:

pytest-playwright==0.5.*

Pin it — a Playwright minor version bump can ship browser binary updates that change rendering just enough to break a visual regression suite (Level 3), so reproducible installs matter more here than in most Python projects.

pytest.ini:

[pytest]
testpaths = tests
addopts = --browser chromium

--browser is a flag pytest-playwright adds on top of plain pytest. It controls which engine the page fixture launches (Module 3 introduces that fixture). Without it, Chromium is the default anyway, but being explicit in pytest.ini means the whole team runs the same engine by default and only overrides it deliberately.

Editor tooling (optional but worth it)

playwright codegen https://example.com

This opens a real browser window alongside an "Inspector" window. Every click, type, and navigation you perform in the browser is recorded live as Playwright Python code in the Inspector. You won't ship generated code as-is — Module 4 explains why hand-picked locators beat generated ones — but codegen is genuinely useful for two things: discovering what locator Playwright would pick for an element you're pointing at, and quickly prototyping a flow before you clean it up by hand.

playwright codegen --target python-pytest https://example.com
# pass --target python-pytest to get pytest-style output directly,
# instead of the default sync_api script format

Updating

pip install --upgrade pytest-playwright
playwright install

Always re-run playwright install after upgrading the library — an updated Python package expects updated browser binaries, and running mismatched versions is a common source of confusing failures that have nothing to do with your test code.

How It Actually Works

playwright install isn't just a convenience download — it's what makes the protocol handshake in Module 1 possible at all. Each cached binary in ~/.cache/ms-playwright is a browser build that Playwright's maintainers compiled (or patched, for Firefox and WebKit) against a specific revision of that browser's remote-control interface, and pinned to a matching revision of the Node.js driver bundled inside the playwright package. When sync_playwright().start() runs, Python spawns that driver as a child process over stdio and asks it to launch a browser binary from the cache with a --remote-debugging-port (Chromium) or equivalent flag; the browser opens a WebSocket server on that port, the driver connects to it, and every Python call after that is proxied: Python → stdio → Node driver → WebSocket → browser, with events flowing back the same path in reverse.

playwright codegen runs that same connection in the other direction: it launches a browser with CDP's Input.dispatchMouseEvent / DOM.getDocument domains active, listens for the raw DOM events your clicks and keystrokes generate, walks the DOM tree CDP hands back to compute a locator for whatever you interacted with, and prints the resulting Python statement in the Inspector window in real time — it's watching the same protocol traffic your own scripts will later drive programmatically.

Exercise

  1. Create the playwright-practice project exactly as shown above, with a virtual environment.
  2. Run pip install pytest-playwright and then playwright install chromium firefox (skip webkit for now to save disk space/time).
  3. Run the verification one-liner for both Chromium and Firefox (change p.chromium to p.firefox) and confirm both print a version.
  4. Create pytest.ini with testpaths = tests and addopts = --browser chromium.
  5. Run playwright codegen https://example.com, click around the page for a few seconds, then close both windows and look at the code it generated in your terminal. Note anything it picked that looks like a CSS class or auto-generated ID — you'll revisit why that's fragile in Module 4.