QA Engineer Skills 2026QA-2026Bash Scripting for QA

Bash Scripting for QA

Why QA Engineers Write Scripts

Bash scripts automate repetitive QA tasks: health-checking environments, setting up test data, running smoke tests, analyzing logs, and managing test infrastructure. A well-written script saves hours of manual work and reduces human error.


Bash Fundamentals

Script Structure

#!/bin/bash
# The shebang line (first line) tells the system to use bash

# Exit immediately if a command fails
set -e

# Treat unset variables as an error
set -u

# Fail on pipe errors (not just the last command in a pipe)
set -o pipefail

# Your script logic goes here
echo "Script started at $(date)"

Make scripts executable: chmod +x my-script.sh

Run scripts: ./my-script.sh or bash my-script.sh

Variables

# Assignment (no spaces around =)
NAME="QA Engineer"
PORT=3000
BASE_URL="https://staging.example.com"

# Using variables
echo "Hello, $NAME"
echo "Server is at ${BASE_URL}:${PORT}"

# Default values (use default if variable is not set)
TIMEOUT=${TEST_TIMEOUT:-30000}
BROWSER=${TEST_BROWSER:-chromium}

# Command substitution (capture command output in a variable)
CURRENT_DATE=$(date +%Y-%m-%d)
GIT_HASH=$(git rev-parse --short HEAD)

Arrays

# Declare an array
ENVIRONMENTS=("dev" "staging" "preprod")
BROWSERS=("chromium" "firefox" "webkit")

# Access elements
echo "${ENVIRONMENTS[0]}"          # First element: "dev"
echo "${ENVIRONMENTS[@]}"          # All elements
echo "${#ENVIRONMENTS[@]}"         # Count: 3

# Loop through an array
for env in "${ENVIRONMENTS[@]}"; do
  echo "Testing $env..."
done

Control Flow

Conditionals

# If/else
if [ "$STATUS" -eq 200 ]; then
  echo "PASS: Service is healthy"
elif [ "$STATUS" -eq 503 ]; then
  echo "WARNING: Service unavailable"
else
  echo "FAIL: Unexpected status $STATUS"
fi

# String comparison
if [ "$ENVIRONMENT" = "production" ]; then
  echo "Running production smoke tests"
fi

# File checks
if [ -f "test-config.json" ]; then
  echo "Config file exists"
fi

if [ -d "test-results" ]; then
  echo "Results directory exists"
fi

# Logical operators
if [ "$STATUS" -eq 200 ] && [ "$RESPONSE_TIME" -lt 5 ]; then
  echo "Fast and healthy"
fi

Common Test Operators

Operator Meaning Example
-eq Equal (numeric) [ "$a" -eq 200 ]
-ne Not equal (numeric) [ "$a" -ne 0 ]
-lt Less than [ "$a" -lt 100 ]
-gt Greater than [ "$a" -gt 0 ]
= Equal (string) [ "$a" = "hello" ]
!= Not equal (string) [ "$a" != "" ]
-f File exists [ -f "config.json" ]
-d Directory exists [ -d "results/" ]
-z String is empty [ -z "$VAR" ]
-n String is not empty [ -n "$VAR" ]

Loops

# For loop with a list
for browser in chromium firefox webkit; do
  echo "Running tests in $browser"
  npx playwright test --project="$browser"
done

# For loop with a range
for i in $(seq 1 5); do
  echo "Test run $i of 5"
done

# While loop
ATTEMPTS=0
MAX_ATTEMPTS=10
while [ "$ATTEMPTS" -lt "$MAX_ATTEMPTS" ]; do
  STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/health")
  if [ "$STATUS" -eq 200 ]; then
    echo "Service is up after $ATTEMPTS attempts"
    break
  fi
  ATTEMPTS=$((ATTEMPTS + 1))
  sleep 5
done

Functions

# Define a function
check_health() {
  local env=$1
  local url="https://${env}.example.com/health"
  local status
  status=$(curl -s -o /dev/null -w "%{http_code}" --max-time 30 "$url")
  if [ "$status" -eq 200 ]; then
    echo "PASS: $env ($status)"
    return 0
  else
    echo "FAIL: $env ($status)"
    return 1
  fi
}

# Call the function
check_health "staging"
check_health "production"

Practical Script: Smoke Test Multiple Environments

#!/bin/bash
# run-smoke-tests.sh -- Health-check multiple environments
set -euo pipefail

ENVIRONMENTS=("dev" "staging" "preprod")
FAIL_COUNT=0
RESULTS=()

check_health() {
    local env=$1
    local url="https://${env}.example.com/health"
    local status
    local time

    status=$(curl -s -o /dev/null -w "%{http_code}" --max-time 30 "$url" 2>/dev/null || echo "000")
    time=$(curl -s -o /dev/null -w "%{time_total}" --max-time 30 "$url" 2>/dev/null || echo "0")

    if [ "$status" -eq 200 ]; then
        RESULTS+=("PASS: $env (HTTP $status, ${time}s)")
        return 0
    else
        RESULTS+=("FAIL: $env (HTTP $status, ${time}s)")
        return 1
    fi
}

echo "=== Environment Health Check ==="
echo "Date: $(date)"
echo "================================"

for env in "${ENVIRONMENTS[@]}"; do
    check_health "$env" || FAIL_COUNT=$((FAIL_COUNT + 1))
done

echo ""
for result in "${RESULTS[@]}"; do
    echo "  $result"
done

echo ""
echo "Results: $((${#ENVIRONMENTS[@]} - FAIL_COUNT))/${#ENVIRONMENTS[@]} passed"

if [ "$FAIL_COUNT" -gt 0 ]; then
    echo "STATUS: SOME ENVIRONMENTS DOWN"
    exit 1
else
    echo "STATUS: ALL ENVIRONMENTS HEALTHY"
    exit 0
fi

Practical Script: Wait for Service Readiness

#!/bin/bash
# wait-for-service.sh -- Wait until a service is ready before running tests
set -euo pipefail

URL="${1:?Usage: $0 <url> [timeout_seconds]}"
TIMEOUT="${2:-120}"
INTERVAL=5
ELAPSED=0

echo "Waiting for $URL to be ready (timeout: ${TIMEOUT}s)..."

while [ "$ELAPSED" -lt "$TIMEOUT" ]; do
    STATUS=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "$URL" 2>/dev/null || echo "000")

    if [ "$STATUS" -eq 200 ]; then
        echo "Service is ready (HTTP $STATUS) after ${ELAPSED}s"
        exit 0
    fi

    echo "  Not ready (HTTP $STATUS), retrying in ${INTERVAL}s... (${ELAPSED}/${TIMEOUT}s)"
    sleep "$INTERVAL"
    ELAPSED=$((ELAPSED + INTERVAL))
done

echo "ERROR: Service not ready after ${TIMEOUT}s"
exit 1

Usage: ./wait-for-service.sh https://staging.example.com/health 180


Practical Script: Test Data Cleanup

#!/bin/bash
# cleanup-test-data.sh -- Remove test data older than N days
set -euo pipefail

DAYS="${1:-7}"
DIRS=("test-results" "screenshots" "videos" "traces" "coverage")

echo "Cleaning up test artifacts older than $DAYS days..."

for dir in "${DIRS[@]}"; do
    if [ -d "$dir" ]; then
        COUNT=$(find "$dir" -type f -mtime +"$DAYS" | wc -l)
        if [ "$COUNT" -gt 0 ]; then
            echo "  Removing $COUNT files from $dir/"
            find "$dir" -type f -mtime +"$DAYS" -delete
        else
            echo "  $dir/ -- nothing to clean"
        fi
    fi
done

echo "Cleanup complete"

Script Error Handling

# Trap errors and clean up
cleanup() {
    echo "Cleaning up..."
    docker compose down -v 2>/dev/null || true
    rm -f /tmp/test-data-*.json
}

# Call cleanup on exit (success or failure)
trap cleanup EXIT

# Call cleanup on specific signals
trap cleanup SIGINT SIGTERM

Script Best Practices

Practice Why
Use set -euo pipefail Catch errors early instead of silently continuing
Use local in functions Prevent variable leaks between functions
Quote all variables Prevent word splitting: "$VAR" not $VAR
Use shellcheck Static analysis tool that catches common bash mistakes
Add usage messages echo "Usage: $0 <url> [timeout]"
Use meaningful exit codes 0 = success, 1 = failure, 2 = usage error
Add comments for non-obvious logic Future you will thank present you

Hands-On Exercise

  1. Write a smoke test script that checks 3 URLs and reports pass/fail
  2. Write a wait-for-service script and use it before running your test suite
  3. Write a cleanup script that removes test artifacts older than 7 days
  4. Install shellcheck and run it on your scripts. Fix any warnings.
  5. Add error handling (trap) to one of your scripts
  6. Write a script that takes environment name as an argument and runs the appropriate test suite