<p><strong>Meta Description:</strong> Aprenda a configurar CI/CD para QA: GitHub Actions, GitLab CI, Jenkins. Automação de testes, quality gates e deploy contínuo.</p>

<hr>
<h2>O Que é CI/CD?</h2>

<h3>Continuous Integration (CI)</h3>

<p>Desenvolvedores commitam código frequentemente (diariamente ou mais). Cada commit triggers build automático e suite de testes.</p>

<h3>Continuous Delivery (CD)</h3>

<p>Código que passa em todos os testes é automaticamente deployado para staging/produção.</p>

<hr>
<h2>GitHub Actions</h2>

<h3>Estrutura Básica</h3>

<pre><code># .github/workflows/ci.yml
name: CI Pipeline

<p>on:<br>  push:<br>    branches: [main, develop]<br>  pull_request:<br>    branches: [main]</p>

<p>env:<br>  NODE_VERSION: '18'</p>

<p>jobs:<br>  lint:<br>    runs-on: ubuntu-latest<br>    steps:<br>      - uses: actions/checkout@v4</p>

<p>- name: Setup Node<br>        uses: actions/setup-node@v4<br>        with:<br>          node-version: ${{ env.NODE_VERSION }}<br>          cache: 'npm'</p>

<p>- name: Install dependencies<br>        run: npm ci</p>

<p>- name: Run linter<br>        run: npm run lint</p>

<p>- name: Run type check<br>        run: npm run type-check</p>

<p>test:<br>    needs: lint<br>    runs-on: ubuntu-latest<br>    services:<br>      postgres:<br>        image: postgres:15<br>        env:<br>          POSTGRES_DB: test<br>          POSTGRES_USER: test<br>          POSTGRES_PASSWORD: test<br>        ports:<br>          - 5432:5432<br>        options: &gt;-<br>          --health-cmd pg_isready<br>          --health-interval 10s<br>          --health-timeout 5s<br>          --health-retries 5</p>

<p>redis:<br>        image: redis:7<br>        ports:<br>          - 6379:6379</p>

<p>steps:<br>      - uses: actions/checkout@v4</p>

<p>- name: Setup Node<br>        uses: actions/setup-node@v4<br>        with:<br>          node-version: ${{ env.NODE_VERSION }}<br>          cache: 'npm'</p>

<p>- name: Install dependencies<br>        run: npm ci</p>

<p>- name: Run unit tests<br>        run: npm run test:unit -- --coverage</p>

<p>- name: Run integration tests<br>        env:<br>          DATABASE_URL: postgresql://test:test@localhost:5432/test<br>          REDIS_URL: redis://localhost:6379<br>        run: npm run test:integration</p>

<p>- name: Upload coverage<br>        uses: codecov/codecov-action@v3<br>        with:<br>          files: ./coverage/lcov.info<br>          fail_ci_if_error: true</p>

<p>e2e:<br>    needs: test<br>    runs-on: ubuntu-latest<br>    if: github.event_name == 'push'</p>

<p>steps:<br>      - uses: actions/checkout@v4</p>

<p>- name: Setup Node<br>        uses: actions/setup-node@v4<br>        with:<br>          node-version: ${{ env.NODE_VERSION }}</p>

<p>- name: Install dependencies<br>        run: npm ci</p>

<p>- name: Install Playwright<br>        run: npx playwright install --with-deps</p>

<p>- name: Run E2E tests<br>        run: npm run test:e2e</p>

<p>- name: Upload test results<br>        uses: actions/upload-artifact@v4<br>        if: always()<br>        with:<br>          name: playwright-report<br>          path: playwright-report/</p>

<p>security:<br>    needs: test<br>    runs-on: ubuntu-latest<br>    steps:<br>      - uses: actions/checkout@v4</p>

<p>- name: Run Snyk<br>        uses: snyk/actions/node@master<br>        env:<br>          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}</p>

<p>- name: Run SonarCloud<br>        uses: SonarSource/sonarcloud-github-action@master<br>        env:<br>          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}</p>

<p>deploy-staging:<br>    needs: [test, security, e2e]<br>    runs-on: ubuntu-latest<br>    if: github.ref == 'refs/heads/develop'<br>    environment: staging</p>

<p>steps:<br>      - uses: actions/checkout@v4</p>

<p>- name: Deploy to Staging<br>        run: |<br>          echo "Deploying to staging..."<br>          kubectl apply -f k8s/staging/</p>

<p>- name: Smoke tests<br>        run: |<br>          sleep 30<br>          curl -f https://staging.exemplo.com/health</p>

<p>deploy-production:<br>    needs: [deploy-staging]<br>    runs-on: ubuntu-latest<br>    if: github.ref == 'refs/heads/main'<br>    environment: production</p>

<p>steps:<br>      - uses: actions/checkout@v4</p>

<p>- name: Deploy to Production<br>        run: |<br>          echo "Deploying to production..."<br>          kubectl apply -f k8s/production/<br></code></pre></p>

<hr>
<h2>GitLab CI</h2>

<pre><code># .gitlab-ci.yml
stages:
  - lint
  - test
  - security
  - deploy

<p>variables:<br>  NODE_VERSION: '18'<br>  POSTGRES_DB: test<br>  POSTGRES_USER: test<br>  POSTGRES_PASSWORD: test</p>

<p>lint:<br>  stage: lint<br>  image: node:$NODE_VERSION<br>  before_script:<br>    - npm ci<br>  script:<br>    - npm run lint<br>    - npm run type-check<br>  allow_failure: false</p>

<p>test:unit:<br>  stage: test<br>  image: node:$NODE_VERSION<br>  services:<br>    - postgres:15<br>  before_script:<br>    - npm ci<br>  script:<br>    - npm run test:unit -- --coverage<br>  coverage: '/Liness<em>:s</em>(d+.d+){6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}/'<br>  artifacts:<br>    reports:<br>      junit: junit.xml<br>      coverage_report:<br>        coverage_format: cobertura<br>        path: coverage/cobertura-coverage.xml</p>

<p>test:integration:<br>  stage: test<br>  image: node:$NODE_VERSION<br>  services:<br>    - postgres:15<br>    - redis:7<br>  before_script:<br>    - npm ci<br>  script:<br>    - npm run test:integration<br>  dependencies:<br>    - test:unit</p>

<p>test:e2e:<br>  stage: test<br>  image: node:$NODE_VERSION<br>  before_script:<br>    - npm ci<br>    - npx playwright install --with-deps<br>  script:<br>    - npm run test:e2e<br>  dependencies:<br>    - test:integration<br>  allow_failure: true</p>

<p>security:dependency-scanning:<br>  stage: security<br>  image: alpine:latest<br>  before_script:<br>    - apk add --no-cache curl<br>  script:<br>    - curl -sL https://getsentry.com/snippets/snyk-gitlab.sh | bash</p>

<p>deploy:staging:<br>  stage: deploy<br>  script:<br>    - echo "Deploying to staging"<br>    - kubectl config use-context staging<br>    - kubectl apply -f k8s/staging/<br>  environment:<br>    name: staging<br>  only:<br>    - develop</p>

<p>deploy:production:<br>  stage: deploy<br>  script:<br>    - echo "Deploying to production"<br>    - kubectl config use-context production<br>    - kubectl apply -f k8s/production/<br>  environment:<br>    name: production<br>  when: manual<br>  only:<br>    - main<br></code></pre></p>

<hr>
<h2>Quality Gates</h2>

<h3>Implementação</h3>

<pre><code>quality-gates:
  stage: quality
  image: node:$NODE_VERSION
  before_script:
    - npm ci
  script:
    - |
      # Check coverage
      COVERAGE=$(cat coverage/coverage.json | jq '.total.lines.pct')
      echo "Coverage: $COVERAGE{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}"

<p>if (( $(echo "$COVERAGE &lt; 80&quot; | bc -l) )); then<br>        echo "Coverage below 80{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442} threshold"<br>        exit 1<br>      fi</p>

<p># Check test pass rate<br>      PASS_RATE=$(cat test-results.json | jq '.passed / .total * 100')<br>      echo "Pass rate: $PASS_RATE{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}"</p>

<p>if (( $(echo "$PASS_RATE &lt; 95&quot; | bc -l) )); then<br>        echo "Pass rate below 95{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442} threshold"<br>        exit 1<br>      fi</p>

<p># Check security<br>      if grep -q "high|critical" security-report.json; then<br>        echo "Security vulnerabilities found"<br>        exit 1<br>      fi</p>

<p>echo "All quality gates passed"<br>  needs:<br>    - test<br>  allow_failure: false<br></code></pre></p>

<hr>
<h2>Test Reports</h2>

<h3>JUnit XML Format</h3>

<pre><code>

  
  
    assert 'token' not in response
  

</code></pre>

<h3>GitHub Actions Upload</h3>

<pre><code>- name: Upload JUnit test results
  uses: actions/upload-artifact@v4
  with:
    name: test-results
    path: test-results/junit.xml

<ul><li>name: Publish Test Results</li></ul>
  uses: dorny/test-reporter@v1
  with:
    name: Test Results
    path: test-results/*.xml
    reporter: java-junit
    fail-on: test failures
</code></pre>

<hr>
<h2>Conclusão</h2>

<p>CI/CD robusto é essencial para QA moderno. As chaves são:<br><ol><li><strong>Automação completa</strong> - Build, test, deploy</li><br><li><strong>Quality gates</strong> - Critérios objetivos para avançar</li><br><li><strong>Feedback rápido</strong> - Resultados em minutos</li><br><li><strong>Parallel execution</strong> - Speed up pipelines</li><br><li><strong>Reliable reporting</strong> - JUnit XML, cobertura, etc.</li></ol></p>