Version Control, Collaboration & CI/CD for SDETs & Developers
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 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.
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.
Maps filenames and directory structure to blobs. A tree is a snapshot of a directory, pointing to other trees (subdirectories) and blobs (files).
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.
An annotated tag is itself a Git object containing a tagger, date, message, and a pointer to a commit. Lightweight tags are just references.
| Command | What it does | SDET Context |
|---|---|---|
git init | Create a new repo in current dir | Bootstrap a new test framework repo |
git clone <url> | Clone a remote repo locally | Pull automation code to CI runner |
git status | Show working tree status | Verify no test artifacts are uncommitted |
git add -p | Stage specific hunks interactively | Stage only test changes, not config files |
git commit -m | Save staged changes with a message | Commit test spec + POM together |
git log --oneline | Compact commit history | Trace which commit introduced a flaky test |
git diff HEAD~1 | Diff against one commit back | Review test changes before pushing |
git stash | Temporarily shelve changes | Switch branches without committing WIP |
# 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.
# 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.
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.
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.
# 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
| Scenario | Merge | Rebase |
|---|---|---|
| Integrating a long-running feature | ✓ Better — preserves full context | Can rewrite public history |
| Keeping your branch up to date with main | Creates noise merge commits | ✓ Better — clean linear history |
| Shared team branch | ✓ Safer — never rewrites history | Never rebase shared branches |
| Personal feature branch cleanup | OK but messy | ✓ Better — interactive rebase to squash |
| Test pipeline PRs | OK for traceability | ✓ Preferred — CI history stays readable |
# 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.)
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.
At least 1–2 approved reviews before merge. Prevents unreviewed test code reaching production environments.
All CI pipelines (lint, unit tests, E2E smoke) must pass. Broken test suites cannot be merged.
Forces squash or rebase merges — keeps the main branch history clean and bisectable.
Prevents history rewriting on main — critical when CI artifacts reference specific commit SHAs.
# 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
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.
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
# 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 }}-
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.
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.
# 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"
| Type | When to Use | Example |
|---|---|---|
test: | Adding/modifying automated tests | test: add login flow E2E spec |
fix: | Fixing a broken test or test infra | fix: flaky selector on checkout page |
chore: | CI config, dependency updates | chore: upgrade playwright to 1.44 |
refactor: | POM restructuring, code cleanup | refactor: extract BaseAuthPage class |
feat: | New test capability or helper | feat: add API seeding helper module |
ci: | GitHub Actions changes | ci: add test sharding to PR workflow |
# 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
# 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
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.
# 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!
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.
# --- 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.
Real questions from SDET and developer interview rounds at product companies, compiled from 10+ years of hiring experience.
git merge and git rebase? When do you use each? ⌄git reset --hard HEAD~1? ⌄git revert HEAD instead — it creates a new commit that undoes the changes while preserving history.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.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.git stash and when should an SDET use it? ⌄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.git cherry-pick work and when is it useful for SDETs? ⌄git cherry-pick abc1234. git add <filename>.git cherry-pick --continue to write the commit.git cherry-pick --abort.git bisect? Walk me through using it to find a regression. ⌄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.git reset and git revert? ⌄${{ 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.git switch -c <new-branch-name> to create and switch to a new branch, attaching HEAD to the new branch pointer.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.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.Test your Git & GitHub knowledge across all modules. 20 questions covering fundamentals, branching, CI/CD, and SDET-specific patterns.