⎇ Expert Masterclass

Git & GitHub

Version Control, Collaboration & CI/CD for SDETs & Developers

7
Modules
40+
Commands
20+
Code Snippets
20
Quiz Questions

🎯 What You Will Learn

Git Internals: Understand objects, DAGs, the staging area, and how commits actually work under the hood.
Branching Strategies: Master Git Flow, GitHub Flow, trunk-based development, and when to use each strategy in a QA pipeline.
GitHub Collaboration: Pull Requests, code reviews, branch protection, and team workflows that prevent broken builds.
CI/CD with GitHub Actions: Run automated test suites, Playwright pipelines, and quality gates on every PR — automatically.
SDET-Specific Patterns: Tagging releases, managing test data branches, .gitignore for test artifacts, and git bisect for flaky failures.
Interview Readiness: 20 real-world Q&A drawn from top SDET interview rounds at product companies.
💡

Interview Insight: Interviewers consistently report that most candidates understand basic git commit but fail on questions about rebasing strategy, merge conflict resolution in CI, and how to structure test branches for a release pipeline. This course closes that gap.

Git object model overview Blob Raw file content Tree Directory snapshot Commit Author, msg, tree ptr Tag Named reference Working Directory Your local files Staging Area (Index) git add → staged Local Repository .git/ object store add commit Remote Repository (GitHub) Shared origin push pull/fetch
Git's four stages: Working Directory → Staging Area → Local Repository → Remote
Module 1

Git Fundamentals

Git is a distributed version control system — every developer has the full project history on their machine. Unlike SVN, there is no single server; every clone is a full repository. Understanding the internals (objects, refs, the DAG) is what separates candidates who "use git" from engineers who understand it.

Git's Four Core Object Types

📄

Blob

Stores raw file content. Git traditionally identifies content using a SHA-1 hash (which remains the most common format), though support for modern SHA-256 hashes exists in modern Git releases. Two files with identical content share the same blob — Git is content-addressable, not path-addressable.

📁

Tree

Maps filenames and directory structure to blobs. A tree is a snapshot of a directory, pointing to other trees (subdirectories) and blobs (files).

🔗

Commit

Points to a root tree + parent commits + metadata (author, timestamp, message). The commit SHA is the hash of all this data — one byte change = completely different hash.

🏷️

Tag

An annotated tag is itself a Git object containing a tagger, date, message, and a pointer to a commit. Lightweight tags are just references.

Essential Commands

CommandWhat it doesSDET Context
git initCreate a new repo in current dirBootstrap a new test framework repo
git clone <url>Clone a remote repo locallyPull automation code to CI runner
git statusShow working tree statusVerify no test artifacts are uncommitted
git add -pStage specific hunks interactivelyStage only test changes, not config files
git commit -mSave staged changes with a messageCommit test spec + POM together
git log --onelineCompact commit historyTrace which commit introduced a flaky test
git diff HEAD~1Diff against one commit backReview test changes before pushing
git stashTemporarily shelve changesSwitch branches without committing WIP

Setting Up Git Correctly

bash — global config
# Identity — used in every commit you make
git config --global user.name "Rishabh Ranjan"
git config --global user.email "rishabh@example.com"

# Sensible defaults
git config --global init.defaultBranch main
git config --global pull.rebase true     # rebase on pull instead of merge
git config --global core.autocrlf input  # Mac/Linux: normalize line endings

# Useful aliases for SDETs
git config --global alias.lg "log --oneline --graph --decorate --all"
git config --global alias.st "status -sb"
⚠️

Warning for pull.rebase: While pull.rebase true is recommended to keep a clean, linear history, it is best suited for intermediate and advanced users. Beginners should first understand rebase conflict resolution, as resolving conflicts during a rebase can be more complex than resolving them during a standard merge.

The .gitignore File for Test Projects

.gitignore — Playwright / Java test project
# Node / Playwright
node_modules/
playwright-report/
test-results/
.env
*.mp4
*.zip

# Java / Maven
target/
*.class
*.jar
surefire-reports/

# IDE files
.idea/
.vscode/
*.iml

# OS
.DS_Store
Thumbs.db
⚠️

Common Mistake: Committing .env files with credentials to Git history is a critical security issue. Even after deletion, the data remains in history. Use git filter-repo to scrub it and rotate your secrets immediately.

Module 2

Branching & Merging

Branches are cheap pointers to commits — creating one takes microseconds. Mastering branching strategy is the single most impactful Git skill for SDETs because it determines how test code flows from development to production.

Branching Strategies Compared

Git Flow vs GitHub Flow vs Trunk-based development Git Flow main develop feature branch hotfix GitHub Flow main short-lived feature → PR → merge LONG-LIVED FAST CI/CD
Git Flow (complex release cycles) vs GitHub Flow (continuous delivery)

Note on Branching Strategies: While Git Flow is often considered too heavy for fast-paced continuous delivery teams (who lean towards GitHub Flow or trunk-based development), it is still very common and widely used in industries with rigid release windows, such as banking, healthcare, telecom, government, and regulated enterprise environments.

Common Branch Operations

bash — branch management
# Create and switch to a new feature branch
git switch -c feature/add-login-tests

# List all branches (local + remote)
git branch -a

# Merge feature into main (creates a merge commit)
git switch main
git merge --no-ff feature/add-login-tests

# Rebase feature on top of main (clean linear history)
git switch feature/add-login-tests
git rebase main

# Delete a merged branch
git branch -d feature/add-login-tests

# Push branch to remote and track it
git push -u origin feature/add-login-tests

Merge vs Rebase — When to Use Which

ScenarioMergeRebase
Integrating a long-running feature✓ Better — preserves full contextCan rewrite public history
Keeping your branch up to date with mainCreates noise merge commits✓ Better — clean linear history
Shared team branch✓ Safer — never rewrites historyNever rebase shared branches
Personal feature branch cleanupOK but messy✓ Better — interactive rebase to squash
Test pipeline PRsOK for traceability✓ Preferred — CI history stays readable

Resolving Merge Conflicts

conflict resolution workflow
# Git marks conflicts in files like this:
<<<<<<< HEAD
  const timeout = 30000;  // original
=======
  const timeout = 60000;  // incoming change
>>>>>>> feature/slow-networks

# 1. Edit the file to keep the right version
# 2. Stage the resolved file
git add playwright.config.ts

# 3. Complete the merge
git merge --continue   # Modern, recommended approach
# OR
git commit             # Traditional workflow (both approaches are valid)

# Use a visual merge tool
git mergetool    # opens configured diff tool (VSCode, IntelliJ, etc.)
Module 3

GitHub & Collaboration

GitHub is not just a remote Git host — it is the collaboration layer. Pull Requests, code review workflows, branch protection rules, and Actions are the features that transform a personal Git repo into an enterprise engineering platform.

Pull Request Lifecycle

Pull Request lifecycle flowchart Branch Push origin/feature Open PR Draft or ready CI Runs Tests + lint + checks Review Approve / Request Merge to main Squash / Merge / Rebase
The PR lifecycle from branch push to merge

Branch Protection Rules (Critical for QA)

🔒

Require PR reviews

At least 1–2 approved reviews before merge. Prevents unreviewed test code reaching production environments.

Require status checks

All CI pipelines (lint, unit tests, E2E smoke) must pass. Broken test suites cannot be merged.

📏

Require linear history

Forces squash or rebase merges — keeps the main branch history clean and bisectable.

🚫

Restrict force-push

Prevents history rewriting on main — critical when CI artifacts reference specific commit SHAs.

Key GitHub Concepts for SDETs

bash — working with remotes
# Fetch all remote changes without merging
git fetch --prune   # --prune removes stale remote-tracking refs

# Pull with rebase (cleaner than merge pull)
git pull --rebase origin main

# Check out a PR locally for review/testing
# Note: pull/42/head is GitHub-specific and may not work on GitLab, Bitbucket, or Azure DevOps
git fetch origin pull/42/head:pr-42
git switch pr-42

# Tag a release
git tag -a v1.3.0 -m "Release 1.3.0 — adds API test suite"
git push origin v1.3.0

# Cherry-pick a specific commit to hotfix branch
git cherry-pick a3f8c2d

# If cherry-pick encounters conflicts:
# 1. Resolve conflicts in files manually
git add <resolved-file>
git cherry-pick --continue

# Or abort the cherry-pick completely:
git cherry-pick --abort

# Force-update a fork with upstream changes
git remote add upstream https://github.com/org/repo.git
git fetch upstream
git rebase upstream/main
Module 4

CI/CD with GitHub Actions

GitHub Actions is the built-in CI/CD engine. For SDETs, Actions is where your Playwright / Selenium / REST Assured test suites get executed automatically on every PR. Understanding how to write and optimize workflows is a core SDET skill in 2025.

Actions Architecture

GitHub Actions architecture: trigger, workflow, jobs, steps Event Trigger push / PR / schedule Workflow YAML .github/workflows/ Job (ubuntu) Runs on GitHub runner Job (macos) Parallel execution Steps checkout, npm, tests Steps setup-java, mvn test Report artifacts
GitHub Actions: event triggers → workflow YAML → parallel jobs → steps → artifacts

Complete Playwright CI Workflow

.github/workflows/playwright-tests.yml
name: Playwright E2E Tests

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        shard: [1, 2, 3]  # parallel sharding

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"

      - name: Install dependencies
        run: npm ci

      - name: Install Playwright browsers
        run: npx playwright install --with-deps chromium

      - name: Run Playwright tests
        run: npx playwright test --shard=${{ matrix.shard }}/3
        env:
          BASE_URL: ${{ secrets.STAGING_URL }}
          API_KEY: ${{ secrets.API_KEY }}

      - name: Upload test report
        uses: actions/upload-artifact@v4
        if: always()   # upload even on failure
        with:
          name: playwright-report-shard-${{ matrix.shard }}
          path: playwright-report/
          retention-days: 14

Caching Dependencies for Speed

yaml — caching npm + Playwright browsers
# Cache node_modules — saves ~40s on each run
- name: Cache Playwright binaries
  uses: actions/cache@v4
  with:
    path: ~/.cache/ms-playwright
    key: playwright-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
    restore-keys: playwright-${{ runner.os }}-
Module 5

Git for SDETs

SDETs have unique Git challenges: managing test data branches, debugging flaky tests with git bisect, tagging stable automation builds, and keeping test artifacts out of history. This module covers SDET-specific patterns.

git bisect — Find the Flaky Commit

When a test starts failing and you don't know which code commit caused it, git bisect runs a binary search across commits, letting you mark each one as good or bad. It finds the culprit in O(log n) steps.

bash — git bisect workflow
# Start bisect session
git bisect start

# Tell git the current commit is broken
git bisect bad

# Tell git a known-good commit (a week ago, perhaps)
git bisect good v1.2.0

# Git checks out a middle commit — run your test:
npm test -- --grep "login flow"

# Mark result and git moves to next midpoint
git bisect good    # or: git bisect bad

# After a few rounds, git prints the culprit commit:
# a3f8c2d is the first bad commit
# commit a3f8c2d  — "refactor: update timeout values"

# End the bisect session
git bisect reset

# Automate bisect with a test script (fully hands-free)
git bisect run npm test -- --grep "login flow"

Conventional Commits for Test Code

TypeWhen to UseExample
test:Adding/modifying automated teststest: add login flow E2E spec
fix:Fixing a broken test or test infrafix: flaky selector on checkout page
chore:CI config, dependency updateschore: upgrade playwright to 1.44
refactor:POM restructuring, code cleanuprefactor: extract BaseAuthPage class
feat:New test capability or helperfeat: add API seeding helper module
ci:GitHub Actions changesci: add test sharding to PR workflow

Tagging Stable Test Suite Versions

bash — release tagging strategy
# Tag a stable test suite release (use semantic versioning)
git tag -a test-suite-v2.4.0 -m "Stable: 240 specs, 0 flaky, Playwright 1.44"
git push origin test-suite-v2.4.0

# List all test-suite tags
git tag -l "test-suite-*"

# Roll back to a stable release if new changes break CI
git checkout test-suite-v2.3.0

# Compare what changed between two stable tags
git diff test-suite-v2.3.0..test-suite-v2.4.0 --stat

Interactive Rebase — Clean Up Before PR

bash — interactive rebase to squash commits
# Squash last 4 commits into one clean commit before PR
git rebase -i HEAD~4

# In the editor that opens:
pick a1b2c3d test: add login spec
squash d4e5f6a test: fix typo in spec
squash 7g8h9i0 test: remove debug log
squash j1k2l3m test: update assertion

# Result: one clean commit "test: add login spec with assertions"
# This makes code review much easier for reviewers

Git Hooks & Husky — Shift-Left Quality Gates

As an SDET, you can set up automation that runs before commits or pushes occur. This prevents developers from pushing syntax errors, lint failures, or broken tests to the remote repository. The industry-standard tool for managing git hooks in Node.js projects is Husky.

bash & package.json — pre-commit hook setup
# 1. Initialize Husky in your test project
npx husky init

# 2. Add a lint or formatting gate to pre-commit hook (.husky/pre-commit)
echo "npm run lint" > .husky/pre-commit

# 3. Add a fast smoke-test gate before pushing (.husky/pre-push)
echo "npx playwright test --project=chromium --grep @smoke" > .husky/pre-push

# Now, if linting fails or smoke tests break, Git aborts the commit/push automatically!

git reflog & Detached HEAD — Disaster Recovery

SDETs run into HEAD issues frequently: CI servers often checkout a specific commit SHA or tag rather than a branch, causing a Detached HEAD state. Additionally, running destructive commands (like git reset --hard) might seem to delete unpushed test commits. Here is how to handle both scenarios.

bash — reflog recovery and detached HEAD resolution
# --- SCENARIO A: Recovering lost commits after a bad hard reset ---
# View the log of all local HEAD pointer movements (even deleted commits)
git reflog

# Find the SHA before your accidental reset (e.g., HEAD@{1})
# HEAD@{0}: reset: moving to HEAD~1
# HEAD@{1}: commit: test: add API contract validation suite

# Hard-reset back to the lost commit to recover it fully
git reset --hard HEAD@{1}

# --- SCENARIO B: Handling a Detached HEAD state ---
# You checked out a tag or commit directly: HEAD points to SHA, not a branch
git checkout v2.4.0

# To save work/changes made in this state, spawn a branch from your current position
git switch -c feature/save-detached-work
🎯

SDET Interview Insight: Interviewers love to ask: "How do you handle it when the same test passes locally but fails in CI?" The answer involves: checking git stash for uncommitted env differences, verifying the CI is running the same commit with git log --oneline -1, and using git bisect to trace the regression commit if the failure is new.

Interview Prep

Top Git & GitHub Interview Q&A

Real questions from SDET and developer interview rounds at product companies, compiled from 10+ years of hiring experience.

What is the difference between git merge and git rebase? When do you use each?
Merge creates a new "merge commit" that preserves the full history of both branches — all original commits remain intact. Rebase replays your commits on top of the target branch, creating new commits with new SHAs and producing a linear history. Use merge for shared branches (develop, main) to preserve context. Use rebase to keep a personal feature branch up to date with main — it avoids noisy merge commits and keeps git log readable. Never rebase a branch that others are also working on, as it rewrites history and causes conflicts for everyone else.
What happens when you do git reset --hard HEAD~1?
It moves the current branch pointer back one commit AND resets the index (staging area) AND the working tree to match that commit. All changes from the discarded commit are gone from the working directory — they are NOT in the staging area or stash. This is destructive and should not be done on commits already pushed to a shared remote. If you need to undo a pushed commit, use git revert HEAD instead — it creates a new commit that undoes the changes while preserving history.
Explain git fetch vs git pull.
git fetch downloads all remote changes into your remote-tracking branches (origin/main etc.) but does NOT merge them into your local branches. Your working tree is unchanged. git pull is fetch + merge (or fetch + rebase if configured). Best practice: always fetch first, inspect the diff with git log origin/main..HEAD, then pull. This prevents surprise merges and gives you the chance to rebase cleanly.
How do you undo a commit that was already pushed to the remote?
Use git revert <commit-sha> — it creates a new commit that reverses the changes. This is safe because it does not rewrite history; it appends a new commit that others can pull. For a force-push undo (only if no one else has pulled), you could do git reset --hard HEAD~1 && git push --force-with-lease — the --force-with-lease flag prevents overwriting if someone else has pushed in the meantime. In a team environment, always prefer revert over force-push.
What is git stash and when should an SDET use it?
git stash saves your uncommitted changes (staged and unstaged) to a temporary storage stack, cleaning your working tree to the last commit state. For SDETs: use it when you need to quickly switch to another branch to investigate a CI failure without committing incomplete test code. git stash pop restores the saved changes. You can name stashes with git stash push -m "WIP: flaky test fix" and list them with git stash list.
How does git cherry-pick work and when is it useful for SDETs?
Cherry-pick applies the diff introduced by a specific commit onto your current branch, creating a new commit with a new SHA. For SDETs: useful for backporting a critical test fix to a release branch without merging all other changes. Example: a flaky test fix was committed to main but you need it on the v1.3-release-testing branch. Run git cherry-pick abc1234.

Handling Cherry-Pick Conflicts: If a conflict occurs during cherry-picking:
  1. Resolve the conflict markers in the file manually.
  2. Stage the resolved files: git add <filename>.
  3. Run git cherry-pick --continue to write the commit.
  4. If you want to cancel the operation and reset the branch, run git cherry-pick --abort.
What is git bisect? Walk me through using it to find a regression.
git bisect performs a binary search across commit history to find the commit that introduced a bug. Process: 1) git bisect start, 2) mark current as bad: git bisect bad, 3) mark a known-good commit: git bisect good v1.2.0. Git checks out the midpoint commit — you run your test and mark it good or bad. This repeats in O(log n) steps until Git identifies the exact culprit commit. Automate it: git bisect run npm test — Git runs the command automatically and completes the search without human input.
Explain a GitHub Actions workflow you've written for running automated tests.
A well-structured answer covers: the trigger events (push, pull_request), runner selection (ubuntu-latest), steps sequence: checkout → setup-node → npm ci → install Playwright browsers → run tests with sharding → upload artifacts with "if: always()" so reports are available even on failure. Bonus points for: caching ~/.cache/ms-playwright keyed by package-lock.json hash, using GitHub Secrets for staging credentials, setting matrix strategy for parallel shards, and adding a merge-reports job that collects shard artifacts for a unified report.
What is the difference between git reset and git revert?
reset moves the branch pointer backward (optionally changing index and working tree), rewriting history. It should only be used on local commits. revert creates a new commit that undoes the target commit's changes — history is preserved and it is safe for shared branches. Three reset modes: --soft (move pointer only, changes stay staged), --mixed (move pointer + unstage, default), --hard (move pointer + unstage + discard working tree changes).
How do you handle secrets in GitHub Actions without hardcoding credentials?
Store credentials in GitHub repository Settings → Secrets → Actions. Reference them in YAML as ${{ secrets.MY_SECRET }}. Secrets are masked in logs — even if you accidentally echo them, GitHub replaces the value with ***. For environment-specific secrets (staging vs production), use GitHub Environments with environment-specific secrets and required reviewers. Never store secrets in the YAML file itself, never commit .env files, and rotate any accidentally committed credentials immediately using git filter-repo to clean history.
What is a "Detached HEAD" state, how does it occur, and how do you resolve it if you need to keep changes?
A "Detached HEAD" state occurs when HEAD points directly to a specific commit SHA or tag rather than to a branch pointer. This commonly happens in CI runners checking out tagged builds or specific commits for execution. To resolve this and preserve your changes, run git switch -c <new-branch-name> to create and switch to a new branch, attaching HEAD to the new branch pointer.
How do you implement "shift-left" quality gates using Git in an automation framework?
By implementing Git Hooks (specifically pre-commit and pre-push hooks) using tools like Husky. A pre-commit hook can run code formatting and linting (e.g., npm run lint) to prevent malformed code from being committed. A pre-push hook can run a lightweight, critical smoke test suite (e.g., npx playwright test --project=chromium --grep @smoke) to ensure no developer pushes broken code that breaks the shared main CI build. If these hooks exit with a non-zero code, Git automatically aborts the commit or push.
I ran `git reset --hard` and accidentally lost local commits that weren't pushed. Can I recover them, and how?
Yes! When you delete or reset commits, they remain in Git's database temporarily. You can find them using git reflog, which lists the exact chronological history of all local HEAD pointer movements. Find the SHA of the commit right before the accidental reset (often denoted as HEAD@{1} or similar), and run git reset --hard <SHA> to point your branch and working directory back to that state.
Assessment

Final Knowledge Quiz

Test your Git & GitHub knowledge across all modules. 20 questions covering fundamentals, branching, CI/CD, and SDET-specific patterns.