A virtual environment is an isolated Python install that lives inside your project. Packages installed there don't pollute the system Python or any other project. It's the single most important tooling habit a Python developer can have — and the most common reason "it worked on my machine" stories happen when someone skips it. This lesson covers venv, activation, pip install, requirements.txt, version pinning, the .gitignore etiquette, and the modern alternatives (pipenv, poetry, uv) you'll meet in some teams.
Why bother — the conflict you avoid
You've worked on Project A with requests==2.28. Project B uses requests==2.32. With one global Python install, both projects share the same requests and you have to pick. Now imagine three projects, ten packages each. Versions clash. Upgrades break old code. CI runs against different versions than your laptop.
A virtual environment fixes this by giving each project its own private site-packages. Project A's requests==2.28 and Project B's requests==2.32 happily coexist on the same machine. Your CI run inside a fresh venv tests against exactly the versions you committed.
Creating a venv
The standard library ships with the venv module. From your project root:
python -m venv venvThe first venv is the module name; the second is the directory it creates. By convention the directory is called venv or .venv. After this, your project folder contains a venv/ directory holding a private Python and an empty site-packages.
Activating a venv
Activation tells your shell to use the venv's python and pip instead of the system ones.
macOS / Linux:
source venv/bin/activateWindows (PowerShell):
venv\Scripts\Activate.ps1Windows (CMD):
venv\Scripts\activate.batYou'll know it worked because your prompt gains a (venv) prefix:
(venv) ~/projects/my-tests $
Now python --version, which python, and pip all point at the venv. Anything you pip install lands in venv/lib/... — only this project sees it.
When you're done, leave the venv with:
deactivateInstalling packages into the venv
With the venv active:
pip install requests
pip install playwright pytest
pip install "requests>=2.30" # version constraintA few useful pip commands:
pip list # show installed packages
pip show requests # show one package's metadata
pip uninstall requests # remove a package
pip install --upgrade requests # upgrade to latestAlways run
pip installinside an activated venv. If you accidentally install into the system Python (no venv active), uninstall and start over inside the venv. A fast way to check:which pip(orwhere pipon Windows) should point to a path undervenv/.
requirements.txt — pin and reproduce
To capture exactly what's installed:
pip freeze > requirements.txtpip freeze outputs every installed package and its exact version, suitable for committing to git:
playwright==1.44.0
pytest==8.2.0
requests==2.32.0
allure-pytest==2.13.5
To reproduce the same environment elsewhere (a teammate's laptop, a CI runner):
python -m venv venv
source venv/bin/activate
pip install -r requirements.txtThe -r flag reads each line as a constraint and installs it. CI scripts almost always end with this incantation.
Version constraints — ==, >=, ~=, *
requests==2.32.0 # exact
requests>=2.30 # at least 2.30, anything newer
requests<2.40 # strictly less than 2.40
requests>=2.30,<3.0 # range
requests~=2.32 # compatible release: ≥2.32, <3.0
requests # any version (avoid in committed files)
For a test project, pin exact versions (==). Reproducibility matters more than living on the latest minor release — you don't want a surprise requests update breaking CI overnight. For libraries you publish, looser ranges (>=) are more polite to consumers.
~=2.32 (compatible release) is a useful middle ground: accept patch and minor updates, but never a major. Equivalent to >=2.32,<3.0.
A typical QA requirements.txt
The packages a Playwright + pytest project usually pulls in:
playwright==1.44.0
pytest==8.2.0
pytest-playwright==0.5.0
requests==2.32.0
allure-pytest==2.13.5
faker==25.3.0
python-dotenv==1.0.1
Each one earns its keep:
- playwright / pytest-playwright — the browser automation framework + its pytest adapter.
- pytest — the test runner (chapter 7).
- requests — HTTP client for API tests (chapter 4).
- allure-pytest — reporting (chapter 7).
- faker — random fake user/address/email data for fixtures.
- python-dotenv — loads environment variables from
.env, common for credentials and base URLs.
Add packages as your project genuinely needs them. Don't seed a project with twenty libraries "just in case."
Two requirements.txt files — runtime vs dev
Many projects split dependencies into two files:
requirements.txt # things needed to run tests in CI
requirements-dev.txt # tools used while developing — formatters, linters, type checkers
requirements-dev.txt typically starts with -r requirements.txt so it inherits everything from runtime, then adds tools like black, ruff, mypy. CI installs only requirements.txt; developers install requirements-dev.txt.
.gitignore — never commit venv/
The venv/ directory is huge (a full Python install plus packages), platform-specific (a venv made on macOS won't run on Windows), and reconstructable from requirements.txt. Add it to .gitignore:
venv/
.venv/
__pycache__/
*.pyc
.pytest_cache/
The __pycache__/ and *.pyc lines exclude Python's compiled-bytecode cache. .pytest_cache/ is pytest's cache directory.
If you accidentally committed venv/ once: git rm -r --cached venv/ followed by a commit removes it from the repo while leaving the local files alone.
Modern alternatives
venv + pip + requirements.txt is the lowest common denominator — every Python install supports it. Some teams use higher-level tools that combine venv management, dependency resolution, and lockfiles:
pipenv— wraps venv + pip. Stores constraints inPipfileand exact versions inPipfile.lock.poetry— full project management: dependencies, building, publishing. Usespyproject.toml. Popular in libraries and bigger apps.uv— newer, written in Rust, dramatically faster than pip. Drop-in forpip install+pip-compile. Increasingly common in 2026.
For learning Python and most QA projects, plain venv + pip is enough. The skill transfers — once you know how packages and isolation work, switching to a higher-level tool is configuration, not concept.
Activating in CI — no source needed
CI scripts don't usually source venv/bin/activate. Instead they call the venv's executables directly:
# GitHub Actions
- run: python -m venv venv
- run: venv/bin/pip install -r requirements.txt
- run: venv/bin/pytestvenv/bin/pip and venv/bin/pytest already know which Python to use — activation is just a convenience for interactive shells.
Global Python vs venv — the conflict picture
Global Python install vs virtual environment
Global Python — every project shares one
One site-packages for the whole machine
Project A's requests==2.28 conflicts with Project B's 2.32
An OS update can change Python under your projects
pip install needs sudo — easy to break the system Python
Reproducing a CI environment is guesswork
Verdict: use only for tools that are not project-specific
Virtual environment — one per project
venv/ folder holds the project's Python and packages
Each project pins its own requests version, no conflicts
OS-level Python upgrades don't move your project under you
pip install never touches the system Python
requirements.txt + pip install -r reproduces exactly
Verdict: the default for every Python project
Two columns, one obvious choice. The five-second cost of python -m venv venv saves hours of mystery debugging.
A complete setup walkthrough
A new Playwright + pytest project from scratch:
mkdir my-tests && cd my-tests
python -m venv venv
source venv/bin/activate # or venv\Scripts\activate on Windows
pip install --upgrade pip # latest pip first
pip install playwright pytest pytest-playwright requests
playwright install chromium # download the browser binary
pip freeze > requirements.txt
echo "venv/" > .gitignore
echo "__pycache__/" >> .gitignore
echo ".pytest_cache/" >> .gitignore
# write your first test...
echo "def test_smoke(): assert 1 + 1 == 2" > tests/test_smoke.py
pytest # run itEight commands, end to end. Hand the resulting folder to a teammate and they reproduce the whole environment with python -m venv venv && source venv/bin/activate && pip install -r requirements.txt. That's the magic of pinned dependencies plus a venv.
⚠️ Common mistakes
- Forgetting to activate before
pip install. Packages then go into the system Python and stay there. Symptoms: tools work in one terminal but not another, or your script can't import a library you "definitely installed." Fix: deactivate (if needed), reactivate the venv, reinstall. - Committing the
venv/folder to git. Adds gigabytes of platform-specific files that don't reproduce on another OS. Fix: addvenv/to.gitignore, thengit rm -r --cached venv/to untrack what you already committed. - No version constraints in
requirements.txt.requests(no==) installs the latest version each time, so CI pulls a different version than your laptop. Fix:pip freeze > requirements.txtto capture exact versions, or writerequests==2.32.0by hand.
🎯 Practice task
Set up a fresh QA project. 25-30 minutes.
- Create a project folder:
mkdir python-qa-playground && cd python-qa-playground. - Create the venv:
python -m venv venv. - Activate it. Confirm
which python(macOS/Linux) orwhere python(Windows) shows a path insidevenv/. - Upgrade pip:
pip install --upgrade pip. - Install three packages:
pip install requests pytest python-dotenv. - Run
pip listand confirm all three appear. - Create
requirements.txtwithpip freeze > requirements.txt. Open the file and read the pinned versions. - Add
.gitignorecontaining at leastvenv/,__pycache__/,.pytest_cache/, and*.pyc. - Deactivate (
deactivate). Confirmwhich pythonno longer points at the venv. Reactivate. - Try a "fresh-clone" reproduction: deactivate, delete
venv/(rm -rf venvor its Windows equivalent), recreate withpython -m venv venv, activate, runpip install -r requirements.txt. Confirm the same versions are installed. - Stretch: add a
requirements-dev.txtcontaining-r requirements.txtand one extra tool —pip install blackthenpip freeze > requirements-dev.txt(or write the file by hand). Usepip install -r requirements-dev.txtto install both runtime and dev dependencies in one step.
You now have the full toolkit for organising and reproducing a Python project: classes, modules, packages, and isolated environments. The next chapter focuses on the test-writing tool you'll spend the most time with — pytest — and the fixtures, parametrisation, and reporting it provides.