Lesson 1 of 8 · DevOps for QA
Git for Testers
The Git you need as a tester or SDET: commits, branches, switch and restore, merging, rebasing, resolving conflicts and keeping test code history clean.
beginner 6 min read Updated
You will learn to
- Explain the working tree, staging area and commit history
- Create, switch and merge branches using modern git switch and git restore
- Resolve a merge conflict in a test file confidently
- Use git log, git diff and git bisect to investigate regressions
Prerequisites
- Comfortable with a terminal
- Git installed locally
Test automation is code, and code lives in Git. As a tester you will clone repositories, create branches for new tests, review pull requests, investigate which commit broke a feature and occasionally untangle a conflict in a shared page object. None of that needs deep Git internals, but it does need a solid mental model and a small set of commands you can use without hesitation.
The three places your changes live
Git tracks your work in three areas:
- Working tree: the files on disk you edit.
- Staging area (index): a snapshot of what will go into the next commit.
- Repository history: the chain of commits, each pointing to its parent.
Most confusion disappears once you know which area a command touches. git add copies changes from the working tree into the staging area. git commit turns the staging area into a new commit. git restore moves content back the other way.
git status # what changed, what is staged
git diff # working tree vs staging area
git diff --staged # staging area vs last commit
git add tests/login.spec.ts
git commit -m "Add login lockout test"
Restoring files
Git 2.23 introduced git switch and git restore to split the overloaded git checkout into two clearer commands. Use them.
git restore tests/login.spec.ts # discard unstaged edits to a file
git restore --staged tests/login.spec.ts # unstage, keep the edits
git restore --source=HEAD~2 playwright.config.ts # bring back an older version
git restore on unstaged changes is destructive: the edits are gone. If you are unsure, git stash first.
Branches
A branch is just a movable pointer to a commit. Creating one is cheap, so create one for every piece of work.
git switch main
git pull # update main from the remote
git switch -c test/checkout-coupons # create and switch to a new branch
# ...edit, add, commit...
git push -u origin test/checkout-coupons
-u sets the upstream so future git push and git pull calls on this branch need no arguments. Many teams use prefixes such as test/, fix/ or feat/ to make branch lists readable; follow whatever convention your repository already uses.
Keeping your branch up to date
While you work, main moves on. You have two ways to bring those changes into your branch:
# Option 1: merge main into your branch (creates a merge commit)
git fetch origin
git merge origin/main
# Option 2: rebase your commits on top of main (rewrites your branch history)
git fetch origin
git rebase origin/main
Merging is safe and preserves history exactly. Rebasing gives a linear history but rewrites your commits, so after a rebase you need git push --force-with-lease to update the remote branch. --force-with-lease refuses to overwrite the remote if someone else pushed in the meantime, which makes it a much safer default than --force.
Resolving merge conflicts
A conflict happens when two branches change the same lines. Git stops and marks the file:
<<<<<<< HEAD
await page.getByRole('button', { name: 'Sign in' }).click();
=======
await page.getByTestId('login-submit').click();
>>>>>>> origin/main
The part above ======= is your branch; the part below is the incoming change. Resolving is a decision, not a mechanical edit. Ask: which locator strategy did the team agree on? Did the other change exist because the button text changed? Then:
# edit the file to the correct final content, removing the markers
git add tests/login.spec.ts
git merge --continue # or: git rebase --continue
If a conflict gets out of hand, git merge --abort or git rebase --abort returns you to where you started. After resolving, run the affected tests before pushing. A conflict that compiles can still be wrong.
Reading history like an investigator
Testers use history to answer “when did this break and why?”
git log --oneline --graph -20 # compact recent history
git log --oneline -- src/checkout/ # commits touching a folder
git log -S "applyCoupon" --oneline # commits that added/removed a string
git show 3f9c2ab # what one commit changed
git blame tests/cart.spec.ts # who last changed each line
Finding the breaking commit with git bisect
git bisect performs a binary search over commits. You tell it one good and one bad commit, and it checks out commits in between for you to test.
git bisect start
git bisect bad # current commit is broken
git bisect good v2.3.0 # this tag was fine
# Git checks out a midpoint. Run your test, then:
git bisect good # or: git bisect bad
# ...repeat until Git names the first bad commit
git bisect reset
You can automate it with a script that exits 0 for good and non-zero for bad:
git bisect start HEAD v2.3.0
git bisect run npx playwright test tests/cart.spec.ts
git bisect reset
With a few hundred commits between good and bad, bisect needs only around eight or nine test runs, because each step halves the range. This is one of the most valuable debugging skills a tester can bring to a team.
Stashing and small fixes
git stash push -m "wip: flaky retry experiment"
git switch main # do something urgent
git switch -
git stash pop
git switch - returns to the previous branch, like cd -.
To fix the last commit message or add a forgotten file before pushing:
git add tests/forgotten.spec.ts
git commit --amend --no-edit
Amending rewrites the commit, so only do it before you push (or on a branch only you use).
What not to commit
A .gitignore keeps generated and secret files out of the repository. For a typical Playwright project:
node_modules/
test-results/
playwright-report/
blob-report/
playwright/.cache/
.auth/
.env
Never commit credentials, tokens or .env files. If you do, removing the file in a later commit is not enough: the secret is still in history. Treat it as leaked, rotate it, and then clean the history if your team requires it.
Practice exercise
You have a branch test/search-filters with two commits. Meanwhile main has gained a commit that renames the page object SearchPage.ts method applyFilter to selectFilter. When you merge origin/main, you get a conflict in tests/search.spec.ts. Write the sequence of commands you would run, and describe how you decide on the resolution.
Show a model answer
git switch test/search-filters
git fetch origin
git merge origin/main
git status # shows tests/search.spec.ts as "both modified"
# open the file, inspect both sidesDecision: the rename on main is the new contract, so the final file keeps my new test cases but calls selectFilter everywhere. I also search my branch for any other applyFilter calls that did not conflict but will now fail to compile:
git grep -n "applyFilter"
# fix remaining calls
npx playwright test tests/search.spec.ts
git add tests/search.spec.ts
git merge --continue
git pushKey points: resolve based on intent, not by picking a side blindly; search for non-conflicting usages of the changed API; run the tests before pushing.
Key takeaways
- Know which of the three areas (working tree, staging, history) each command touches.
- Prefer
git switchandgit restoreover the overloadedgit checkout. - Merge is safe for shared branches; rebase only your own, and push with
--force-with-lease. - Resolve conflicts by understanding both changes, then run the tests.
git log -S,git blameandgit bisectturn “something broke” into “this commit broke it”.- Keep reports, results, auth state and secrets out of the repository with
.gitignore.
Next, see how this fits into team collaboration in GitHub Pull Request Workflow.
Key terms
Finished this lesson?
Progress is saved in this browser. No account needed.
Practise it
Related interview questions
- How do you resolve a merge conflict? Walk through the steps.
- A test started failing sometime in the last 200 commits. How do you find the commit that broke it?
- Rebase vs merge: what is the difference, and which would you use on a shared branch?
- How do you undo a commit? Compare git reset, git revert and git restore.