Saltar al contenido
mypipelines
Pipelines Actions Gradle Buscar
React· Reusable workflow ·on: workflow_call

React Test

React - Test & Coverage

.github/workflows/react-test.yml

.github/workflows/react-test.yml
name: React - Test & Coverage
on:
workflow_call:
inputs:
runner:
description: 'Runner type'
required: false
type: string
default: 'ubuntu-latest'
node_version:
description: 'Node.js version'
required: false
type: string
default: '24'
package_manager:
description: 'Package manager (npm or yarn)'
required: false
type: string
default: 'yarn'
run_coverage:
description: 'Run tests with coverage'
required: false
type: boolean
default: true
test_command:
description: 'Test script name (yarn <command>)'
required: false
type: string
default: 'test'
upload_reports:
description: 'Upload coverage reports as artifact'
required: false
type: boolean
default: false
run_code_analysis:
description: 'Run code analysis'
required: false
type: boolean
default: false
code_analysis_tool:
description: 'Code analysis tool: sonar or qodana'
required: false
type: string
default: 'sonar'
skip_quality_gate:
description: 'Skip Quality Gate check'
required: false
type: boolean
default: false
outputs:
result:
description: 'Test result (success/failure)'
value: ${{ jobs.test.outputs.result }}
coverage_percentage:
description: 'Code coverage percentage'
value: ${{ jobs.test.outputs.coverage }}
tests_passed:
description: 'Number of tests passed'
value: ${{ jobs.test.outputs.passed }}
tests_failed:
description: 'Number of tests failed'
value: ${{ jobs.test.outputs.failed }}
quality_gate_status:
description: 'Quality Gate status (Sonar/Qodana)'
value: ${{ steps.quality-gate.outputs.quality-gate-status }}
jobs:
test:
name: Test & Coverage
runs-on: ${{ inputs.runner }}
timeout-minutes: 30
outputs:
result: ${{ steps.test-run.outcome }}
coverage: ${{ steps.coverage.outputs.percentage }}
passed: ${{ steps.test-summary.outputs.passed }}
failed: ${{ steps.test-summary.outputs.failed }}
quality_gate: ${{ steps.quality-gate.outputs.quality-gate-status }}
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup Node.js ${{ inputs.node_version }}
uses: actions/setup-node@v5
with:
node-version: ${{ inputs.node_version }}
cache: ${{ inputs.package_manager }}
- name: Install dependencies
run: |
if [ "${{ inputs.package_manager }}" = "yarn" ]; then
yarn install --frozen-lockfile
else
npm ci
fi
# ============================================
# SONARQUBE ANALYSIS
# ============================================
- name: SonarQube Scan
if: inputs.run_code_analysis && inputs.code_analysis_tool == 'sonar' && secrets.SONAR_TOKEN != ''
uses: SonarSource/sonarqube-scan-action@v4
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_URL }}
- name: SonarQube Quality Gate
id: quality-gate
if: inputs.run_code_analysis && inputs.code_analysis_tool == 'sonar' && !inputs.skip_quality_gate && secrets.SONAR_TOKEN != ''
uses: SonarSource/sonarqube-quality-gate-action@v1
timeout-minutes: 5
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_URL }}
- name: SonarQube Quality Gate Summary
if: inputs.run_code_analysis && inputs.code_analysis_tool == 'sonar' && !inputs.skip_quality_gate && secrets.SONAR_TOKEN != ''
run: |
echo "" >> $GITHUB_STEP_SUMMARY
echo "### SonarQube Quality Gate" >> $GITHUB_STEP_SUMMARY
echo "Status: ${{ steps.quality-gate.outputs.quality-gate-status }}" >> $GITHUB_STEP_SUMMARY
# ============================================
# RUN TESTS
# ============================================
- name: Run tests
id: test-run
run: |
if [ "${{ inputs.package_manager }}" = "yarn" ]; then
if [ "${{ inputs.run_coverage }}" = "true" ]; then
yarn ${{ inputs.test_command }} --coverage --ci 2>&1 | tee test-output.log || true
else
yarn ${{ inputs.test_command }} --ci 2>&1 | tee test-output.log || true
fi
else
if [ "${{ inputs.run_coverage }}" = "true" ]; then
npm run ${{ inputs.test_command }} -- --coverage --ci 2>&1 | tee test-output.log || true
else
npm run ${{ inputs.test_command }} -- --ci 2>&1 | tee test-output.log || true
fi
fi
- name: Parse test results
id: test-summary
if: always()
run: |
PASSED=0
FAILED=0
if [ -f "test-output.log" ]; then
# Parse Jest output: "Tests: X passed, Y failed, Z total"
PASSED=$(grep -oP 'Tests:.*?(\d+) passed' test-output.log | grep -oP '\d+' | tail -1 || echo "0")
FAILED=$(grep -oP 'Tests:.*?(\d+) failed' test-output.log | grep -oP '\d+' | tail -1 || echo "0")
# Fallback if parsing fails
if [ -z "$PASSED" ]; then PASSED=0; fi
if [ -z "$FAILED" ]; then FAILED=0; fi
fi
echo "passed=$PASSED" >> $GITHUB_OUTPUT
echo "failed=$FAILED" >> $GITHUB_OUTPUT
TOTAL=$((PASSED + FAILED))
echo "### Test Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Status | Count |" >> $GITHUB_STEP_SUMMARY
echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Passed | $PASSED |" >> $GITHUB_STEP_SUMMARY
echo "| Failed | $FAILED |" >> $GITHUB_STEP_SUMMARY
echo "| **Total** | **$TOTAL** |" >> $GITHUB_STEP_SUMMARY
# ============================================
# COVERAGE
# ============================================
- name: Parse coverage report
id: coverage
if: inputs.run_coverage && always()
run: |
COVERAGE=0
if [ -f "coverage/coverage-summary.json" ]; then
COVERAGE=$(node -e "
const report = require('./coverage/coverage-summary.json');
const pct = report.total.lines.pct;
console.log(typeof pct === 'number' ? pct.toFixed(1) : '0');
" 2>/dev/null || echo "0")
fi
echo "percentage=$COVERAGE" >> $GITHUB_OUTPUT
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Code Coverage" >> $GITHUB_STEP_SUMMARY
echo "**Line Coverage: ${COVERAGE}%**" >> $GITHUB_STEP_SUMMARY
- name: Check coverage threshold
if: inputs.run_coverage && always()
run: |
COVERAGE="${{ steps.coverage.outputs.percentage }}"
COVERAGE_THRESHOLD=80
if [ "$(echo "$COVERAGE < $COVERAGE_THRESHOLD" | bc -l)" -eq 1 ]; then
echo "::error::Coverage ${COVERAGE}% is below threshold of ${COVERAGE_THRESHOLD}%"
exit 1
fi
- name: Check test results
if: always()
run: |
FAILED="${{ steps.test-summary.outputs.failed }}"
if [ -n "$FAILED" ] && [ "$FAILED" -gt 0 ]; then
echo "::error::$FAILED test(s) failed"
exit 1
fi
# ============================================
# UPLOAD REPORTS
# ============================================
- name: Upload coverage reports
if: inputs.upload_reports && inputs.run_coverage && always()
uses: actions/upload-artifact@v5
with:
name: coverage-reports-${{ github.run_id }}
path: coverage/
retention-days: 7