Learning Objectives
By the end of this lesson you will be able to:
- Write a
.gitignorethat covers a test-automation project - Use the pattern syntax that actually matters:
/,*,**,! - Explain why ignoring an already-tracked file does nothing, and fix it
- Commit an empty directory with
.gitkeep
What .gitignore Actually Does
It is a list of patterns. Any untracked file matching one is hidden from git status and cannot be added by an ordinary git add ..
The critical word is untracked. A file Git is already tracking is completely unaffected — this is the single most common .gitignore confusion, and we will fix it below.
The file lives in the repository root, is committed like any other file, and applies to every directory beneath it. A subdirectory can have its own, which adds to the parent's rules.
The Patterns Worth Knowing
# A comment
node_modules/ # a directory, anywhere in the tree
*.log # any file with this extension, anywhere
/config.local.json # only in the repository root — the leading slash anchors it
build/**/*.map # ** matches across directory levels
!important.log # ! un-ignores something an earlier rule caught
Four rules cover nearly everything:
- Trailing
/means "directory". - Leading
/anchors the pattern to the repository root; without it, the pattern matches at any depth. *matches within one path segment;**matches across segments.!negates — but it cannot resurrect a file whose parent directory is ignored. Ignorelogs/*and negate a file inside, notlogs/itself.
A Real Automation .gitignore
Test-automation projects generate more junk than almost anything else. This is a realistic starting point:
# Dependencies
node_modules/
.venv/
# Build output
dist/
build/
target/
# Test artefacts — regenerated on every run
screenshots/
videos/
allure-results/
test-results/
playwright-report/
*.har
# Logs
*.log
logs/
# Secrets and local config — NEVER commit these
.env
.env.*
!.env.example
credentials.json
*.pem
# Editor and OS noise
.idea/
.vscode/
.DS_Store
Two things to notice.
!.env.example un-ignores the template, so the team still knows which variables exist while the real values stay out. Committing .env.example and ignoring .env is the standard pattern, and it is worth adopting on day one.
Test artefacts are ignored as a category. A single Playwright run can leave hundreds of megabytes of video, and none of it belongs in history — it is output, not source.
The Trap: "I Added It and It's Still There"
You commit .env by accident. You notice, add it to .gitignore, and it still shows up in git status.
Because .gitignore only applies to untracked files, and this one is now tracked.
git rm --cached .env
git commit -m "chore: stop tracking .env"
--cached removes it from Git's tracking without deleting it from your disk. From the next commit onward it is untracked, so .gitignore finally applies.
For a directory:
git rm -r --cached node_modules/
The Part That Is Not Fixed
The file is out of future commits. It is still in every past commit, in every clone, forever.
For build output that is merely embarrassing, fine. For a secret, it is not. A committed credential must be treated as compromised: rotate it immediately. Removing it from history entirely means rewriting every commit that touched it (git filter-repo, or the platform's secret-removal tooling) and force-pushing, which invalidates every clone in the company — a coordinated operation, not a quick fix.
Rotate first, then decide whether the history rewrite is worth it. It usually is not, if the credential is already dead.
.gitkeep: Committing an Empty Directory
Git tracks files, not directories. A directory exists only because files in it do, so an empty screenshots/ folder cannot be committed — and your test run fails because the path does not exist.
The convention is a placeholder file:
mkdir -p test-results
touch test-results/.gitkeep
git add test-results/.gitkeep
.gitkeep is not a Git feature. It is an ordinary empty file, and the name is purely a convention meaning "this exists to hold the directory open". Some teams use .gitignore for the same job.
When the directory's contents are ignored, keep the placeholder visible:
test-results/*
!test-results/.gitkeep
The directory is committed, everything generated inside it is ignored.
Pro Tip: Check
.gitignoreinto the repository on the very first commit of any automation project, before the first test run. Retrofitting one afternode_modulesand a week of screenshots are already in history is an afternoon of work that ten seconds at the start would have avoided.
Key Takeaways
.gitignoreaffects untracked files only/anchors to the root, trailing/means directory,**crosses levels,!negates- Ignore build output, test artefacts, logs, secrets and editor noise; commit
.env.example - An already-tracked file needs
git rm --cachedbefore the ignore rule takes effect - That does not remove it from past commits — treat a committed secret as compromised and rotate it
- Git cannot track an empty directory; commit a
.gitkeepplaceholder - Add
.gitignoreon the first commit, not after the first mess