I've reviewed a lot of test repositories over the years.
Some of them were clean, well-structured, and a pleasure to navigate. Most of them looked like someone had committed code at 11pm with the keyboard slightly on fire.
Vague commits. Branches that had been "open since Q2." Pull requests with descriptions that just said "added tests." Merge conflicts so deep they required archaeological excavation. And — the classic — a direct push to main with the message wip.
Here's the uncomfortable truth: the people responsible for software quality are often running some of the least disciplined codebases in the organization.
This isn't a skill problem. It's a culture and priority problem. And it's time we address it directly.
Why This Happens (And Why It's Not Entirely Your Fault)
Test code gets treated as second-class code
In a lot of orgs, there's an implicit hierarchy: production code matters, test code is just scaffolding. Nobody reviews it rigorously. No one asks about its internal quality. As long as the tests run and the pipeline is green, it's invisible.
That invisibility creates permission. And permission creates drift.
The urgency trap
QA is the last gate before a release. When a release is in two hours and a test needs to be fixed, nobody is creating a well-named branch, writing a conventional commit, and opening a PR with a template. They're pushing directly to main and hoping nobody notices.
The problem: someone always notices eventually — usually when a three-month-old direct push causes a regression at 2am.
Nobody taught us this formally
A lot of SDETs transitioned from manual QA or came up through non-engineering paths. You learned enough Git to clone, commit, and push. But trunk-based development, branch strategies, git rebase, conventional commits — those were never in the onboarding plan.
This isn't a failure of individuals. It's a failure of how the industry onboards QA talent.
What Bad Git Discipline Actually Costs You
This isn't just about aesthetics or "best practices" energy. The consequences are real:
- 🔴 Broken nightly runs — one sloppy merge to
mainbreaks the entire regression suite overnight - 🕵️ Untraceable flaky tests — when your commit history is noise, isolating when something broke becomes a multi-hour investigation
- 🧱 Merge conflict purgatory — a branch that's been open for 3 weeks diverges so hard it becomes a full-day rebase session
- 🤝 Loss of developer trust — devs stop trusting the test pipeline when they can't reason about what changed and when
- 😰 Painful onboarding — new SDETs joining your team spend days just trying to understand the repo state instead of writing tests
The Anti-Patterns (Raise Your Hand If You've Done One)
🚩 The Mega-Commit
git commit -m "stuff"
Changed files: 14. Includes: 3 new test files, 2 refactored page objects, an updated config, and a commented-out block from 2021 nobody wants to delete.
Impossible to review. Impossible to revert cleanly. Communicates nothing.
🚩 The WIP Push to Main
git push origin main
Message: wip / testing / fix / asdf
Even in a solo project, this destroys your ability to understand your own history. In a team context, it's a live grenade.
🚩 The Immortal Branch
feat/big-test-refactor (last commit: 6 weeks ago)
Nobody's sure if it's still active. It's diverged by 200 commits. The person who created it has since moved to a different squad. Nobody wants to touch it. It just... lives there.
🚩 The Copy-Paste PR
PR Title: "Tests"
Description: "Added some tests for the checkout flow."
Linked ticket: (none)
CI status: 🔴
Reviews: 0
Status: Merged
🚩 The Ignored Red Build
"Oh the pipeline's been red for a week, it's just a flaky test. I'll merge anyway."
Once you normalize merging on red, the signal disappears entirely. Your CI becomes decoration.
The Industry-Standard Lifecycle (For Test Code Specifically)
Here's what disciplined version control looks like in the SDET context — not for production code, for your test repos.
Step 1 — Branch with intention
# Branch from develop (or main, per your team's model)
git checkout -b test/JIRA-512-checkout-e2e-validation
Naming convention:
-
feat/JIRA-123-login-automation— new test coverage -
fix/JIRA-456-flaky-session-timeout— fixing an existing test -
refactor/JIRA-789-pageobject-cleanup— restructuring without behavior change -
ci/JIRA-321-update-pipeline-node-version— CI/CD changes
Short-lived. One ticket. One concern.
Step 2 — Commit atomically with Conventional Commits
The format is simple:
<type>(<scope>): <short summary>
Real examples for SDET work:
# Java
git commit -m "test(checkout): add boundary tests for quantity field validation"
git commit -m "fix(auth): correct assertion on session expiry timeout — was 5s, should be 30s"
# Python
git commit -m "refactor(conftest): extract reusable login fixture to shared helpers"
git commit -m "ci: upgrade Selenium Grid from 4.8 to 4.18 in docker-compose"
# JavaScript/Playwright
git commit -m "test(api): add contract validation for /v2/orders response schema"
git commit -m "chore(deps): update @playwright/test to 1.43.0"
Types used in test engineering:
| Type | When to use |
|------|-------------|
| test | New or modified test cases |
| fix | Fixing a broken or flaky test |
| refactor | Internal restructure, no behavior change |
| feat | New test framework feature or helper |
| ci | Pipeline / GitHub Actions / Jenkins changes |
| chore | Deps, config, maintenance |
| docs | README, test plan docs |
Step 3 — Push early and often
Don't sit on local commits for days. Push your branch regularly:
git push origin test/JIRA-512-checkout-e2e-validation
This protects your work, enables async collaboration, and means CI can catch issues before you've drifted too far.
Step 4 — Open a PR that communicates
A PR is a communication artifact, not just a code delivery mechanism. Use a template. Include at minimum:
## Summary
Adds E2E coverage for the checkout quantity validation edge cases per QA-512.
## Changes
- Added `CheckoutQuantityTests.java` with 8 test cases covering min/max/boundary inputs
- Updated `CheckoutPage` POM with `getQuantityError()` helper
- Fixed existing flaky assertion in `testCartItemRemoval`
## Test Evidence
- ✅ All 8 new tests passing locally (Java 17, Chrome 124, Selenium 4.18)
- ✅ CI run: [link to pipeline]
## Ticket
[QA-512](https://yourjira/QA-512)
Step 5 — Review test code like it's real code
Because it is.
A peer review of test code should check:
- Are assertions actually asserting the right thing? (Not just
assertNotNull(response)everywhere) - Are there hardcoded waits hiding race conditions? (
Thread.sleep(3000)is a smell) - Is the test independent, or does it depend on test order?
- Does the naming clearly describe what the test validates?
- Are page objects/fixtures being reused, or is logic being duplicated?
Step 6 — Let CI be the gatekeeper
Configure your test repo the same way a production repo would be:
- ✅ Branch protection on
mainanddevelop— no direct pushes - ✅ Require at least 1 reviewer approval before merge
- ✅ Require CI to be green before merge
- ✅ Use a
PULL_REQUEST_TEMPLATE.mdin.github/ - ✅ Add
CODEOWNERSso reviewers are auto-assigned
# .github/CODEOWNERS
*.java @your-team/sdet-java
*.py @your-team/sdet-python
*.spec.ts @your-team/sdet-playwright
Step 7 — Merge clean, tag releases, delete branches
# Squash merge keeps history clean
git merge --squash test/JIRA-512-checkout-e2e-validation
# Tag test suite releases to align with app versions
git tag -a v2.4.1-tests -m "Test coverage for release 2.4.1"
# Delete merged branches
git branch -d test/JIRA-512-checkout-e2e-validation
git push origin --delete test/JIRA-512-checkout-e2e-validation
Recommended .github Folder Setup for Test Repos
.github/
├── PULL_REQUEST_TEMPLATE.md # Enforces PR quality
├── CODEOWNERS # Auto-assign reviewers
└── workflows/
├── ci.yml # Run tests on every PR
└── lint.yml # Lint Java/Python/JS test code
The Mindset Shift
Here's the reframe that matters most:
You are a software engineer who specializes in quality. That means the quality bar applies to everything you build — including your test code.
The next time you're about to commit with a message of fix2 or push directly to main because it's faster, ask yourself: Would I accept this in a PR from a developer I'm reviewing?
If the answer is no, hold yourself to the same standard.
Quick Reference Cheat Sheet
# ✅ Start a new piece of work
git checkout develop
git pull origin develop
git checkout -b test/TICKET-123-feature-name
# ✅ Commit atomically
git add src/test/java/com/app/CheckoutTests.java
git commit -m "test(checkout): add edge case coverage for promo code validation"
# ✅ Keep your branch current
git fetch origin
git rebase origin/develop
# ✅ Push and open PR
git push origin test/TICKET-123-feature-name
# → Open PR via GitHub UI with template
# ✅ After merge, clean up
git checkout develop
git pull origin develop
git branch -d test/TICKET-123-feature-name
The discipline gap in QA version control is one of the most fixable problems in our space. It doesn't require new tools, new frameworks, or budget approval. It requires a decision — made individually and then reinforced as a team — that test code deserves the same care as the code it validates.
Start with your next commit.
Are you an SDET or QA engineer who's either guilty of these patterns or has successfully fixed them on your team? Drop your experience in the comments — I'd love to hear what worked.
#testing #github #sdet #devops #qualityengineering #automation #git
Top comments (0)