Module 5: Rebasing: Creating Clean Test History
Master rebasing to maintain a linear, clean history in your test automation projects. Learn when rebasing is preferable to merging, how to rebase feature branches onto updated main branches, and practice interactive rebasing to clean up test commits before sharing with the team.
Advanced Rebase: Handling Conflicts in Test Branch Updates
Why This Matters
As a test automation engineer working in a collaborative environment, you’ve likely experienced this scenario: You’ve been working on a feature branch with your automated test suite for several days. Meanwhile, your teammates have merged multiple updates to the main branch—new test utilities, framework improvements, configuration changes. When you finally try to integrate your work, you face a messy merge commit that obscures what changes you actually made to the test suite.
The Real-World Problem: Merge commits create a non-linear history that makes it difficult to understand the evolution of your test codebase. When debugging a failing test or reviewing what changed in your automation framework, you need a clear, chronological story. Traditional merging creates a “spaghetti history” with interleaved commits from multiple branches, making it challenging to track down when a specific test behavior was introduced or why a particular test configuration changed.
When You’ll Use This Skill:
- Before Pull Requests: Clean up your experimental test commits, combining multiple “trying different selector strategies” commits into one coherent change
- Keeping Feature Branches Current: Update your long-running test development branch with the latest framework changes from main without creating merge commits
- Team Collaboration: Present your test automation work in a logical, reviewable sequence that tells a clear story
- Release Preparation: Ensure your test suite changes integrate smoothly with the latest application code before deployment
Common Pain Points Addressed:
- Cryptic Commit History: “Which commit actually fixed the flaky login test?” becomes impossible to answer when history is cluttered with merge commits and “WIP” messages
- Difficult Code Reviews: Reviewers struggle to understand your test changes when commits are out of order or mixed with temporary debugging code
- Integration Headaches: Resolving conflicts during a final merge is more stressful than handling them incrementally during rebase
- Lost Context: Important test automation decisions get buried in noisy commit history, making future maintenance difficult
Mastering rebase puts you in control of your project’s history, transforming it from a chaotic record of every keystroke into a curated narrative that serves your team’s needs.
Learning Objectives Overview
In this lesson, you’ll develop advanced rebasing skills specifically tailored for test automation workflows. Here’s what you’ll accomplish:
1. Execute Safe Rebases of Feature Branches
You’ll learn the complete workflow for rebasing your test automation feature branch onto an updated main branch. We’ll cover the git rebase
command syntax, understanding what’s happening during a rebase, and safety measures like creating backup branches. You’ll practice rebasing a branch containing Selenium tests onto a main branch that has new test utilities.
2. Identify and Resolve Rebase Conflicts
When rebasing test code, conflicts are inevitable—especially in shared configuration files, page object classes, or test data. You’ll learn to recognize conflict markers, understand why conflicts occur during rebase (versus merge), and systematically resolve them. We’ll work through realistic scenarios like conflicting changes to test framework initialization code and overlapping modifications to API test assertions.
3. Use Interactive Rebase for Commit Cleanup
The interactive rebase (git rebase -i
) is your power tool for crafting perfect commit history. You’ll learn to squash multiple “fix typo in test” commits into one, reorder commits to group related test changes logically, edit commit messages for clarity, and even split commits that changed too many things at once. This section includes hands-on practice cleaning up a messy branch of API test development.
4. Apply Rebase Best Practices in Team Environments
Rebasing has critical “don’t do this” rules, especially around shared branches. You’ll learn the golden rule of never rebasing public history, when to use --force-with-lease
versus --force
for pushing rebased branches, how to communicate with your team about rebased branches, and strategies for handling situations where others have based work on your branch.
5. Recognize Rebase vs Merge Scenarios
Not every situation calls for rebase. You’ll develop judgment for choosing the right integration strategy: when rebase maintains clarity for feature work, when merge commits are actually beneficial (like recording integration points), how your team’s workflow influences the decision, and special considerations for test automation branches versus application code branches.
By the end of this lesson, you’ll confidently maintain clean, linear history in your test automation projects, making your work more reviewable, debuggable, and professional.
Core Content
Core Content: Advanced Rebase: Handling Conflicts in Test Branch Updates
Core Concepts Explained
Understanding Git Rebase in Test Automation Context
Rebase is a powerful Git operation that moves or combines a sequence of commits to a new base commit. In test automation, rebasing is crucial when you need to update your test branch with the latest changes from the main branch while maintaining a clean, linear commit history.
Key Difference: Merge vs. Rebase
graph LR
A[main: A-B-C] --> B[feature: A-B-D-E]
B --> C[After Merge: A-B-C-D-E-M]
B --> D[After Rebase: A-B-C-D'-E']
style C fill:#f9f,stroke:#333
style D fill:#9f9,stroke:#333
- Merge creates a new “merge commit” combining histories
- Rebase rewrites history by replaying your commits on top of the updated base
When to Use Rebase for Test Branches
Ideal Scenarios:
- Updating feature test branch with latest main branch changes
- Cleaning up test commits before code review
- Synchronizing parallel test development work
- Preparing test branches for CI/CD integration
Avoid Rebasing When:
- The branch is already pushed and shared with team members
- You’re working on a public/protected branch
- Others have based work on your commits
Step-by-Step Rebase Workflow
1. Prepare Your Environment
Before starting a rebase, ensure your repository is clean:
# Check current status
$ git status
On branch test/login-automation
nothing to commit, working tree clean
# Fetch latest changes from remote
$ git fetch origin
# View current branch structure
$ git log --oneline --graph --all
* 7a3d9e2 (HEAD -> test/login-automation) Add password validation tests
* 5b8c1f4 Add username validation tests
* 2e9a7d3 (origin/main, main) Update test framework config
* 1c5f8a2 Initial test structure
2. Start the Interactive Rebase
Interactive rebase gives you control over each commit:
# Rebase current branch onto latest main
$ git rebase -i origin/main
# Alternative: Rebase with conflict resolution strategy
$ git rebase -i origin/main -X theirs # Prefer incoming changes
This opens your editor with a rebase todo list:
pick 5b8c1f4 Add username validation tests
pick 7a3d9e2 Add password validation tests
# Rebase 2e9a7d3..7a3d9e2 onto 2e9a7d3 (2 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# d, drop = remove commit
3. Handling Merge Conflicts During Rebase
When conflicts occur, Git pauses the rebase:
$ git rebase -i origin/main
Auto-merging tests/login.test.js
CONFLICT (content): Merge conflict in tests/login.test.js
error: could not apply 5b8c1f4... Add username validation tests
Conflict Resolution Process:
# Step 1: Check which files have conflicts
$ git status
interactive rebase in progress; onto 2e9a7d3
Last command done (1 command done):
pick 5b8c1f4 Add username validation tests
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: tests/login.test.js
# Step 2: View the conflict markers in your file
$ cat tests/login.test.js
Example Conflict in Test File:
// tests/login.test.js
const { test, expect } = require('@playwright/test');
test.describe('Login Tests', () => {
<<<<<<< HEAD (Current Change - main branch)
test('should validate email format', async ({ page }) => {
await page.goto('https://practiceautomatedtesting.com/login');
await page.fill('#email', 'invalid-email');
await page.click('#submit');
await expect(page.locator('.error')).toContainText('Invalid email format');
});
=======
test('should validate username format', async ({ page }) => {
await page.goto('https://practiceautomatedtesting.com/login');
await page.fill('#username', 'test@user');
await page.click('#login-btn');
await expect(page.locator('.error-msg')).toBeVisible();
});
>>>>>>> 5b8c1f4 (Incoming Change - your commit)
});
Resolving the Conflict:
// tests/login.test.js - RESOLVED
const { test, expect } = require('@playwright/test');
test.describe('Login Tests', () => {
// Keep updated selector from main branch
test('should validate email format', async ({ page }) => {
await page.goto('https://practiceautomatedtesting.com/login');
await page.fill('#email', 'invalid-email');
await page.click('#submit');
await expect(page.locator('.error')).toContainText('Invalid email format');
});
// Add your new test with updated selectors
test('should validate username format', async ({ page }) => {
await page.goto('https://practiceautomatedtesting.com/login');
await page.fill('#username', 'test@user');
await page.click('#submit'); // Updated to match new selector
await expect(page.locator('.error')).toBeVisible();
});
});
Continue the Rebase:
# Stage the resolved file
$ git add tests/login.test.js
# Continue rebase process
$ git rebase --continue
# If you made a mistake, abort and start over
$ git rebase --abort
4. Squashing Test Commits
Clean up multiple small test commits into logical units:
# In the interactive rebase editor
pick 5b8c1f4 Add username validation tests
squash 7a3d9e2 Add password validation tests
squash 9c2f1a8 Fix typo in username test
squash 3e7b4d9 Update test assertions
# This combines all into one commit with a new message
Practical Real-World Example
Scenario: Updating Test Suite After Framework Migration
Initial State:
# Your test branch
$ git log --oneline
4d9e2f1 (HEAD -> test/checkout-flow) Add payment method tests
8c3a1b7 Add cart validation tests
2e9a7d3 (origin/main) Initial tests
# Meanwhile, main branch was updated
$ git log --oneline origin/main
9f7e4c2 (origin/main) Migrate from Selenium to Playwright
6a8d3e1 Update test configuration
2e9a7d3 Initial tests
Rebase Process:
# Start rebase
$ git rebase origin/main
# Conflict in cart validation tests
CONFLICT (content): Merge conflict in tests/cart.test.js
Before Rebase (Selenium syntax):
// tests/cart.test.js - Your branch
const { Builder, By, until } = require('selenium-webdriver');
describe('Cart Tests', () => {
it('should add items to cart', async () => {
const driver = await new Builder().forBrowser('chrome').build();
await driver.get('https://practiceautomatedtesting.com/cart');
await driver.findElement(By.id('add-to-cart')).click();
const count = await driver.findElement(By.className('cart-count')).getText();
expect(count).toBe('1');
});
});
After Rebase (Playwright syntax from main):
// tests/cart.test.js - Resolved
const { test, expect } = require('@playwright/test');
test.describe('Cart Tests', () => {
test('should add items to cart', async ({ page }) => {
await page.goto('https://practiceautomatedtesting.com/cart');
await page.click('#add-to-cart');
const count = await page.locator('.cart-count').textContent();
expect(count).toBe('1');
});
});
Complete Resolution:
# Update all tests to new framework
$ git add tests/cart.test.js tests/payment.test.js
# Continue rebase
$ git rebase --continue
# Force push updated branch (only if not shared)
$ git push --force-with-lease origin test/checkout-flow
Advanced Conflict Resolution Strategies
Using Merge Tools
# Configure merge tool (VS Code)
$ git config --global merge.tool vscode
$ git config --global mergetool.vscode.cmd 'code --wait --merge $REMOTE $LOCAL $BASE $MERGED'
# Launch merge tool during conflict
$ git mergetool
Choosing Conflict Resolution Strategy
# Keep all changes from main branch
$ git checkout --theirs tests/config.js
$ git add tests/config.js
# Keep all your changes
$ git checkout --ours tests/login.test.js
$ git add tests/login.test.js
Common Mistakes and Debugging
Mistake 1: Forgetting to Fetch Before Rebase
Problem:
$ git rebase origin/main
Current branch test/api-tests is up to date.
# But you know there are new commits!
Solution:
# Always fetch first
$ git fetch origin
$ git rebase origin/main
Mistake 2: Losing Work During Conflict Resolution
Problem: Accidentally accepting wrong changes during conflict resolution.
Solution:
# Use reflog to find your commits
$ git reflog
7a3d9e2 HEAD@{0}: rebase: abort
5b8c1f4 HEAD@{1}: rebase: Add username tests
# Restore to before rebase
$ git reset --hard HEAD@{1}
Mistake 3: Rebasing Published Branches
Problem: Force pushing rebased commits that others depend on.
Solution:
# Use merge instead for shared branches
$ git merge origin/main
# If you must rebase, use --force-with-lease
$ git push --force-with-lease origin test/feature-branch
Debugging Rebase Issues
# Check rebase status
$ cat .git/rebase-merge/head-name
refs/heads/test/login-automation
# View remaining commits to rebase
$ cat .git/rebase-merge/git-rebase-todo
# If stuck, abort and try again
$ git rebase --abort
# Skip problematic commit
$ git rebase --skip
Best Practices Checklist
✅ Always fetch latest changes before rebasing
✅ Work on a local branch, not shared/protected branches
✅ Commit your work before starting rebase
✅ Test your code after resolving conflicts
✅ Use --force-with-lease
instead of --force
when pushing
✅ Keep commits atomic and logically organized
✅ Write clear commit messages when squashing
✅ Document complex conflict resolutions in commit messages
Pro Tip: Create a backup branch before complex rebases:
$ git branch backup-before-rebase
$ git rebase -i origin/main
# If things go wrong: git reset --hard backup-before-rebase
Hands-On Practice
EXERCISE AND CONCLUSION
🏋️ Hands-On Exercise
Exercise: Resolve Rebase Conflicts in a Test Automation Suite
Scenario: You’re working on a feature branch that adds new API tests. Meanwhile, the main branch has been updated with refactored test utilities and new authentication methods. You need to rebase your feature branch onto the updated main branch and resolve conflicts.
Task
Rebase your feature branch with conflicting test files and successfully integrate changes from both branches while maintaining test functionality.
Step-by-Step Instructions
Setup Phase
- Clone the practice repository:
git clone https://github.com/your-org/test-automation-rebase-exercise.git
cd test-automation-rebase-exercise
- Examine the branches:
git checkout main
# Review the updated test utilities in utils/auth_helper.py
git checkout feature/api-tests
# Review your new API tests in tests/api/test_user_endpoints.py
Exercise Phase
- Initiate the rebase:
git checkout feature/api-tests
git rebase main
-
Identify and analyze conflicts:
- Check which files have conflicts using
git status
- Expected conflicts will be in:
tests/api/test_user_endpoints.py
conftest.py
utils/auth_helper.py
- Check which files have conflicts using
-
Resolve each conflict:
- Open conflicting files in your editor
- Identify conflict markers (
<<<<<<<
,=======
,>>>>>>>
) - For
utils/auth_helper.py
: Merge the new token-based auth method with your API key additions - For
conftest.py
: Combine fixture updates from both branches - For
test_user_endpoints.py
: Update your tests to use the new auth helper methods
-
Stage resolved files:
git add tests/api/test_user_endpoints.py
git add conftest.py
git add utils/auth_helper.py
- Continue the rebase:
git rebase --continue
-
Handle any additional conflicts:
- If more conflicts appear, repeat steps 5-7
- If you make a mistake, use
git rebase --abort
and start over
-
Verify the resolution:
# Run the test suite
pytest tests/api/test_user_endpoints.py -v
# Check test coverage
pytest --cov=utils --cov=tests/api
# Verify linting passes
flake8 tests/ utils/
Expected Outcome
After successfully completing the rebase:
- ✅ All commits from your feature branch are replayed on top of main
- ✅ Conflicts are resolved, preserving functionality from both branches
- ✅ Tests pass with 100% success rate
- ✅ No merge commits in history (clean linear history)
- ✅ Your new API tests use the refactored authentication utilities
- ✅ Code style and linting checks pass
Solution Approach
For utils/auth_helper.py
conflict:
# WRONG - Keeping only one side
def authenticate():
return get_api_key() # Old method only
# CORRECT - Integrating both approaches
def authenticate(method='token'):
"""Support both token and API key authentication"""
if method == 'token':
return get_auth_token() # From main branch
elif method == 'api_key':
return get_api_key() # From feature branch
else:
raise ValueError(f"Unknown auth method: {method}")
For test_user_endpoints.py
conflict:
# Update imports to use refactored utilities
from utils.auth_helper import authenticate
# Modify test setup to use new auth method
@pytest.fixture
def auth_headers():
token = authenticate(method='token') # Updated call
return {"Authorization": f"Bearer {token}"}
def test_get_user(auth_headers):
response = requests.get(API_URL + "/user", headers=auth_headers)
assert response.status_code == 200
Recovery commands if needed:
# If you need to abort and restart
git rebase --abort
# If you need to skip a problematic commit
git rebase --skip
# To view the rebase in progress
git status
git log --oneline --graph
🎓 Key Takeaways
-
Rebase creates linear history by replaying your commits on top of the target branch, avoiding unnecessary merge commits and keeping the project history clean and readable
-
Conflict resolution during rebase requires careful analysis of both branches’ changes to preserve intended functionality; use
git status
, diff tools, and test execution to validate your resolutions -
Interactive rebase (
git rebase -i
) is powerful for cleaning up commit history before sharing your work, allowing you to squash, reorder, edit, or drop commits strategically -
Always run your test suite after resolving conflicts to ensure that integrated changes work correctly together; automated tests are your safety net during complex rebases
-
The rebase workflow suits feature branch development particularly well for test automation projects, where clean history and up-to-date integration with main branch changes are crucial for team collaboration
🚀 Next Steps
Practice These Skills
- Daily rebase practice: Before starting work each day, rebase your feature branches onto the latest main branch
- Interactive rebase cleanup: Practice using
git rebase -i HEAD~5
to clean up your last 5 commits before creating pull requests - Complex conflict scenarios: Intentionally create conflicts between test utilities, fixtures, and test cases to build confidence in resolution strategies
- Rebase with CI/CD: Set up pre-push hooks that run tests to catch integration issues early
Related Topics to Explore
- Git Cherry-Pick: Selectively apply specific commits from one branch to another without full rebasing
- Git Reflog: Recover from rebase mistakes by understanding the reference log and commit history
- Merge Strategies: Learn when to use
git merge --no-ff
vs rebase for different workflow scenarios - Git Bisect: Use binary search through commit history to identify which commit introduced a test failure
- Test Quarantine Strategies: Managing flaky tests during branch integration and rebase operations
- CI/CD Pipeline Integration: Configuring automated test runs on rebased branches before merging
- Git Hooks for Test Automation: Setting up pre-rebase and post-rebase hooks to enforce testing standards
Recommended Resources
- Pro Git Book - Chapter 7: Advanced Rebasing Techniques
- Practice repository: Create your own “conflict playground” with intentionally conflicting test scenarios
- Git workflow documentation for your team’s specific branching strategy (Git Flow, GitHub Flow, Trunk-Based Development)