CI/CDIntermediate

CI/CD for Test Automation

Set up continuous integration and deployment pipelines for your test automation with GitHub Actions, Jenkins, and more

6 min read
...
ci-cdautomationgithub-actionsjenkinsdevops

Introduction

Integrating test automation into CI/CD pipelines ensures code quality and catches bugs early. This guide covers setting up automated testing in popular CI/CD platforms.

GitHub Actions

Basic Test Workflow

# .github/workflows/test.yml
name: Tests
 
on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]
 
jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run linter
        run: npm run lint
      
      - name: Run unit tests
        run: npm test
      
      - name: Run E2E tests
        run: npm run test:e2e

Parallel Testing

name: Tests
 
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        shard: [1, 2, 3, 4]
    
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '18'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run tests (shard ${{ matrix.shard }}/4)
        run: npx playwright test --shard=${{ matrix.shard }}/4

Test with Multiple Node Versions

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [16, 18, 20]
    
    steps:
      - uses: actions/checkout@v3
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
      
      - run: npm ci
      - run: npm test

Upload Test Artifacts

- name: Run tests
  run: npm test
  
- name: Upload test results
  if: always()
  uses: actions/upload-artifact@v3
  with:
    name: test-results
    path: |
      test-results/
      screenshots/
      videos/
    retention-days: 30

Jenkins Pipeline

Declarative Pipeline

// Jenkinsfile
pipeline {
    agent any
    
    tools {
        nodejs 'NodeJS 18'
    }
    
    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }
        
        stage('Install') {
            steps {
                sh 'npm ci'
            }
        }
        
        stage('Lint') {
            steps {
                sh 'npm run lint'
            }
        }
        
        stage('Unit Tests') {
            steps {
                sh 'npm test'
            }
            post {
                always {
                    junit 'test-results/junit.xml'
                }
            }
        }
        
        stage('E2E Tests') {
            steps {
                sh 'npm run test:e2e'
            }
            post {
                always {
                    publishHTML([
                        reportDir: 'playwright-report',
                        reportFiles: 'index.html',
                        reportName: 'Playwright Report'
                    ])
                }
            }
        }
    }
    
    post {
        always {
            cleanWs()
        }
        failure {
            mail to: 'team@example.com',
                 subject: "Failed Pipeline: ${currentBuild.fullDisplayName}",
                 body: "Build failed: ${env.BUILD_URL}"
        }
    }
}

Parallel Execution in Jenkins

stage('Tests') {
    parallel {
        stage('Unit Tests') {
            steps {
                sh 'npm run test:unit'
            }
        }
        stage('Integration Tests') {
            steps {
                sh 'npm run test:integration'
            }
        }
        stage('E2E Tests') {
            steps {
                sh 'npm run test:e2e'
            }
        }
    }
}

GitLab CI

# .gitlab-ci.yml
image: node:18
 
stages:
  - install
  - lint
  - test
  - e2e
 
cache:
  paths:
    - node_modules/
 
install:
  stage: install
  script:
    - npm ci
  artifacts:
    paths:
      - node_modules/
    expire_in: 1 day
 
lint:
  stage: lint
  script:
    - npm run lint
 
unit-tests:
  stage: test
  script:
    - npm test
  coverage: '/Statements\s+:\s+(\d+\.\d+)%/'
  artifacts:
    reports:
      junit: test-results/junit.xml
      coverage_report:
        coverage_format: cobertura
        path: coverage/cobertura-coverage.xml
 
e2e-tests:
  stage: e2e
  script:
    - npm run test:e2e
  artifacts:
    when: always
    paths:
      - playwright-report/
      - screenshots/
    expire_in: 30 days

Docker for Consistent Test Environments

Dockerfile for Tests

FROM node:18
 
WORKDIR /app
 
# Install dependencies
COPY package*.json ./
RUN npm ci
 
# Install Playwright browsers
RUN npx playwright install --with-deps
 
# Copy application code
COPY . .
 
# Run tests
CMD ["npm", "test"]

Docker Compose for Full Stack Testing

# docker-compose.test.yml
version: '3.8'
 
services:
  postgres:
    image: postgres:15
    environment:
      POSTGRES_DB: testdb
      POSTGRES_PASSWORD: testpass
    ports:
      - "5432:5432"
  
  redis:
    image: redis:7
    ports:
      - "6379:6379"
  
  app:
    build: .
    depends_on:
      - postgres
      - redis
    environment:
      DATABASE_URL: postgres://postgres:testpass@postgres:5432/testdb
      REDIS_URL: redis://redis:6379
    command: npm test

Test Reporting

JUnit XML Reports

// jest.config.js
module.exports = {
  reporters: [
    'default',
    [
      'jest-junit',
      {
        outputDirectory: 'test-results',
        outputName: 'junit.xml',
      },
    ],
  ],
};

HTML Reports

// playwright.config.js
export default {
  reporter: [
    ['html', { outputFolder: 'playwright-report' }],
    ['junit', { outputFile: 'test-results/junit.xml' }],
  ],
};

Code Coverage

# GitHub Actions
- name: Run tests with coverage
  run: npm test -- --coverage
 
- name: Upload coverage to Codecov
  uses: codecov/codecov-action@v3
  with:
    files: ./coverage/coverage-final.json

Best Practices

1. Fast Feedback

# Run quick checks first
jobs:
  quick-checks:
    runs-on: ubuntu-latest
    steps:
      - name: Lint
        run: npm run lint
      - name: Type check
        run: npm run type-check
  
  full-tests:
    needs: quick-checks  # Only run if quick checks pass
    runs-on: ubuntu-latest
    steps:
      - name: Run all tests
        run: npm test

2. Fail Fast

jobs:
  test:
    strategy:
      fail-fast: true  # Stop all jobs if one fails
      matrix:
        shard: [1, 2, 3, 4]

3. Conditional Tests

# Only run E2E tests on main branch
- name: E2E Tests
  if: github.ref == 'refs/heads/main'
  run: npm run test:e2e

4. Secrets Management

- name: Run tests
  env:
    DATABASE_URL: ${{ secrets.DATABASE_URL }}
    API_KEY: ${{ secrets.API_KEY }}
  run: npm test

Performance Optimization

1. Caching

# Cache dependencies
- name: Cache node modules
  uses: actions/cache@v3
  with:
    path: node_modules
    key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
 
# Cache Playwright browsers
- name: Cache Playwright browsers
  uses: actions/cache@v3
  with:
    path: ~/.cache/ms-playwright
    key: ${{ runner.os }}-playwright-${{ hashFiles('**/package-lock.json') }}

2. Selective Testing

# Only run affected tests
- name: Get changed files
  id: changed-files
  uses: tj-actions/changed-files@v35
 
- name: Run tests for changed files
  run: |
    npx jest --findRelatedTests ${{ steps.changed-files.outputs.all_changed_files }}

3. Test Retries

// playwright.config.js
export default {
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 4 : undefined,
};

Monitoring and Notifications

Slack Notifications

- name: Notify on failure
  if: failure()
  uses: slackapi/slack-github-action@v1
  with:
    payload: |
      {
        "text": "Tests failed on ${{ github.ref }}",
        "blocks": [
          {
            "type": "section",
            "text": {
              "type": "mrkdwn",
              "text": "Build failed: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
            }
          }
        ]
      }
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

Test Analytics

# Send test results to analytics service
- name: Upload test results
  if: always()
  uses: buildpulse/buildpulse-action@main
  with:
    account: ${{ secrets.BUILDPULSE_ACCOUNT }}
    repository: ${{ secrets.BUILDPULSE_REPOSITORY }}
    path: test-results/junit.xml
    key: ${{ secrets.BUILDPULSE_ACCESS_KEY }}

Complete Example

name: CI/CD Pipeline
 
on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]
 
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'
      - run: npm ci
      - run: npm run lint
 
  unit-tests:
    needs: lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'
      - run: npm ci
      - run: npm test -- --coverage
      - uses: codecov/codecov-action@v3
 
  e2e-tests:
    needs: unit-tests
    runs-on: ubuntu-latest
    strategy:
      matrix:
        shard: [1, 2, 3]
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npx playwright test --shard=${{ matrix.shard }}/3
      - uses: actions/upload-artifact@v3
        if: always()
        with:
          name: playwright-report-${{ matrix.shard }}
          path: playwright-report/
 
  deploy:
    needs: [unit-tests, e2e-tests]
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Deploy to production
        run: echo "Deploying..."

Conclusion

Effective CI/CD for test automation requires:

  • Fast, reliable tests
  • Parallel execution for speed
  • Proper caching and optimization
  • Clear reporting and notifications
  • Security best practices

Start simple, then optimize based on your needs.

Further Reading

Comments (0)

Loading comments...