Skip to content

Nightly Quality Audit #61

Nightly Quality Audit

Nightly Quality Audit #61

Workflow file for this run

name: Nightly Quality Audit
on:
schedule:
- cron: '0 6 * * *' # Daily at 6 AM UTC
workflow_dispatch:
permissions:
contents: read
issues: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm install
- run: npx playwright install --with-deps
- name: Start server
run: npx http-server . -p 8080 -s &
- name: Wait for server
run: npx wait-on http://localhost:8080 --timeout 15000
- name: Run all tests
id: tests
run: npx playwright test tests/ --project=chromium --reporter=list,html 2>&1 | tee test-output.txt
continue-on-error: true
- name: Link check
id: links
run: |
npx linkinator http://localhost:8080 \
--recurse \
--skip "github.com|googletagmanager" \
--timeout 10000 \
--format json > link-report.json || true
# Parse broken links
node -e "
const r = require('./link-report.json');
const broken = (r.links || []).filter(l => l.state === 'BROKEN');
const total = (r.links || []).length;
console.log('Total links: ' + total);
console.log('Broken: ' + broken.length);
if (broken.length) {
broken.forEach(l => console.log(' BROKEN: ' + l.url + ' (from ' + l.parent + ')'));
}
// Export for issue creation
const fs = require('fs');
fs.writeFileSync('broken-links.txt', broken.map(l => l.url + ' (from ' + l.parent + ')').join('\n') || 'None');
fs.appendFileSync(process.env.GITHUB_ENV, 'BROKEN_COUNT=' + broken.length + '\n');
" | tee -a $GITHUB_STEP_SUMMARY
- name: Lighthouse audit
run: |
npm install -g @lhci/cli
lhci collect \
--url=http://localhost:8080/index.html \
--url=http://localhost:8080/getting-started.html \
--url=http://localhost:8080/docs/index.html \
--settings.preset=desktop \
--numberOfRuns=1 || true
echo "### Lighthouse Audit Complete" >> $GITHUB_STEP_SUMMARY
- name: Create issue for broken links
if: env.BROKEN_COUNT != '0' && env.BROKEN_COUNT != ''
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const brokenLinks = fs.readFileSync('broken-links.txt', 'utf8');
const today = new Date().toISOString().split('T')[0];
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
// Check if an open issue already exists for today
const existing = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
labels: 'ci-automated,links',
per_page: 5
});
const todayIssue = existing.data.find(i => i.title.includes(today));
if (todayIssue) {
// Update existing issue
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: todayIssue.number,
body: `## Updated Broken Link Report\n\n**Run:** [${context.runId}](${runUrl})\n\n\`\`\`\n${brokenLinks}\n\`\`\``
});
} else {
// Create new issue
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `[CI] Broken links detected — ${today}`,
labels: ['bug', 'ci-automated', 'links'],
body: `## Broken Links Detected\n\nThe nightly CI/CD audit found broken links on the website.\n\n**Workflow Run:** [View on GitHub](${runUrl})\n**Date:** ${today}\n\n### Broken Links\n\n\`\`\`\n${brokenLinks}\n\`\`\`\n\n### How to Fix\n\n1. Check if the target page/URL still exists\n2. Update or remove the broken link in the source HTML file\n3. Push the fix — the deploy workflow will re-test automatically`
});
}
- name: Create issue for test failures
if: steps.tests.outcome == 'failure'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const testOutput = fs.readFileSync('test-output.txt', 'utf8');
const failedLines = testOutput.split('\n').filter(l => l.includes('✘') || l.includes('FAIL') || l.includes('Error')).slice(0, 30).join('\n');
const today = new Date().toISOString().split('T')[0];
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
// Check for existing open issue
const existing = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
labels: 'ci-automated',
per_page: 10
});
const todayIssue = existing.data.find(i => i.title.includes('Test failures') && i.title.includes(today));
if (todayIssue) return; // Already reported today
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `[CI] Test failures detected — ${today}`,
labels: ['bug', 'ci-automated'],
body: `## Test Failures Detected\n\nThe nightly CI/CD audit found test failures.\n\n**Workflow Run:** [View on GitHub](${runUrl})\n**Date:** ${today}\n\n### Failed Tests\n\n\`\`\`\n${failedLines || 'See workflow run for details'}\n\`\`\`\n\n### Categories Tested\n\n- **Links** — internal link validation, placeholder detection, button functionality\n- **SEO** — title tags, meta descriptions, canonical links, OG tags, structured data\n- **Performance** — page load time, resource size, FCP, JS errors\n- **Accessibility** — ARIA landmarks, alt text, keyboard navigation, color contrast`
});
- uses: actions/upload-artifact@v4
if: always()
with:
name: nightly-reports-${{ github.run_number }}
path: |
playwright-report/
link-report.json
broken-links.txt
test-output.txt
.lighthouseci/
retention-days: 30