diff --git a/.github/workflows/lighthouse.yml b/.github/workflows/lighthouse.yml new file mode 100644 index 0000000000..f9ac724287 --- /dev/null +++ b/.github/workflows/lighthouse.yml @@ -0,0 +1,140 @@ +name: Lighthouse audit + +on: + pull_request_target: + types: [opened, synchronize] + +permissions: + contents: read + pull-requests: write + +jobs: + lighthouse: + if: github.actor != 'dependabot[bot]' + runs-on: ubuntu-latest + + steps: + - name: Checkout PR code + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + + - name: Setup Node.js with npm cache + uses: actions/setup-node@v4 + with: + node-version: '22.x' + cache: 'npm' + + - name: Install dependencies + run: npm install lighthouse + + - name: Wait for Netlify preview to be live + run: | + PREVIEW_URL="https://deploy-preview-${{ github.event.pull_request.number }}--expressjscom-preview.netlify.app" + echo "PREVIEW_URL=$PREVIEW_URL" >> "$GITHUB_ENV" + for i in {1..2}; do + if curl -s --head "$PREVIEW_URL" | grep "200 OK" > /dev/null; then + echo "Preview is live!" + break + fi + echo "Waiting for Netlify to deploy... ($i/2)" + sleep 10 + done + + - name: Run Lighthouse audits + run: | + URLS=( + "$PREVIEW_URL" + "$PREVIEW_URL/en/blog/posts.html" + "$PREVIEW_URL/en/5x/api.html" + ) + + echo "## 🚦 Lighthouse Results (Mobile & Desktop)" > lighthouse-report.md + echo "| URL | Device | Perf | A11y | Best Practices | ⚠️ SEO score unreliable |" >> lighthouse-report.md + echo "| --- | ------ | ---- | ---- | -------------- | --- |" >> lighthouse-report.md + + for device in mobile desktop; do + for url in "${URLS[@]}"; do + if [ "$device" = "desktop" ]; then + lighthouse_args="--preset=desktop" + else + lighthouse_args="--form-factor=mobile" + fi + + npx lighthouse "$url" \ + $lighthouse_args \ + --output json \ + --output-path="lighthouse-report-${device}.json" \ + --chrome-flags="--headless" + + report="lighthouse-report-${device}.json" + perf=$(jq '.categories | .performance.score * 100' $report) + a11y=$(jq '.categories | .accessibility.score * 100' $report) + bp=$(jq '.categories | .["best-practices"].score * 100' $report) + seo=$(jq '.categories | .seo.score * 100' $report) + + stoplight() { + if (( $(echo "$1 >= 90" | bc -l) )); then echo "🟢"; + elif (( $(echo "$1 >= 75" | bc -l) )); then echo "🟠"; + else echo "🔴"; fi + } + + perf_stoplight=$(stoplight $perf) + a11y_stoplight=$(stoplight $a11y) + bp_stoplight=$(stoplight $bp) + seo_stoplight=$(stoplight $seo) + + path=$(echo "$url" | sed "s|$PREVIEW_URL||") + if [ -z "$path" ]; then path="/"; fi + + echo "| $path | $device | $perf_stoplight $(printf "%.0f" $perf) | $a11y_stoplight $(printf "%.0f" $a11y) | $bp_stoplight $(printf "%.0f" $bp) | $seo_stoplight $(printf "%.0f" $seo) |" >> lighthouse-report.md + done + done + + - name: Log Lighthouse report + run: | + cat lighthouse-report.md + + - name: Upload Lighthouse reports as artifacts + uses: actions/upload-artifact@v4 + with: + name: lighthouse-reports + path: | + lighthouse-report.md + + - name: Comment on PR with Lighthouse results + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const fs = require('fs'); + const report = fs.readFileSync('lighthouse-report.md', 'utf8'); + + const { data: comments } = await github.rest.issues.listComments({ + issue_number: context.payload.pull_request.number, + owner: context.repo.owner, + repo: context.repo.repo, + }); + + const botComment = comments.find(comment => + comment.user.type === 'Bot' && + comment.body.includes('🚦Lighthouse Results (Mobile & Desktop)') + ); + + if (botComment) { + await github.rest.issues.updateComment({ + comment_id: botComment.id, + owner: context.repo.owner, + repo: context.repo.repo, + body: report + }); + } else { + await github.rest.issues.createComment({ + issue_number: context.payload.pull_request.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: report + }); + } +