Skip to content

Instantly share code, notes, and snippets.

@vivekhaldar
Created December 18, 2025 17:34
Show Gist options
  • Select an option

  • Save vivekhaldar/004d476406e1f8e9cd83e10003dc3f05 to your computer and use it in GitHub Desktop.

Select an option

Save vivekhaldar/004d476406e1f8e9cd83e10003dc3f05 to your computer and use it in GitHub Desktop.
Continuous Test Coverage Setup Guide for Monorepos
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Continuous Test Coverage for Monorepos</title>
<style>
* { box-sizing: border-box; }
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
max-width: 900px;
margin: 0 auto;
padding: 20px;
line-height: 1.7;
color: #1a1a1a;
background: #fafafa;
}
h1 {
color: #0066cc;
border-bottom: 3px solid #0066cc;
padding-bottom: 10px;
}
h2 {
color: #333;
margin-top: 40px;
display: flex;
align-items: center;
gap: 10px;
}
h3 {
color: #555;
margin-top: 25px;
}
.subtitle {
color: #666;
font-size: 1.1em;
margin-top: -10px;
}
.architecture {
background: #1e1e1e;
color: #d4d4d4;
padding: 20px;
border-radius: 8px;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 13px;
overflow-x: auto;
white-space: pre;
line-height: 1.4;
}
pre {
background: #282c34;
color: #abb2bf;
padding: 16px;
border-radius: 8px;
overflow-x: auto;
font-size: 13px;
line-height: 1.5;
}
code {
background: #e8e8e8;
padding: 2px 6px;
border-radius: 4px;
font-size: 0.9em;
}
pre code {
background: none;
padding: 0;
}
.option-card {
background: white;
border: 1px solid #ddd;
border-radius: 12px;
padding: 25px;
margin: 20px 0;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
}
.option-card h3 {
margin-top: 0;
color: #0066cc;
}
.badge {
display: inline-block;
padding: 4px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
}
.badge-simple { background: #d4edda; color: #155724; }
.badge-detailed { background: #cce5ff; color: #004085; }
.pros-cons {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
margin: 15px 0;
}
.pros, .cons {
padding: 15px;
border-radius: 8px;
}
.pros { background: #e8f5e9; }
.cons { background: #fff3e0; }
.pros h4 { color: #2e7d32; margin-top: 0; }
.cons h4 { color: #e65100; margin-top: 0; }
.pros ul, .cons ul {
margin: 0;
padding-left: 20px;
}
.step {
display: flex;
gap: 15px;
margin: 20px 0;
}
.step-number {
background: #0066cc;
color: white;
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
flex-shrink: 0;
}
.step-content {
flex: 1;
}
.step-content h4 {
margin: 0 0 8px 0;
color: #333;
}
.step-content p {
margin: 0;
color: #666;
}
.comparison-table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
}
.comparison-table th {
background: #0066cc;
color: white;
padding: 12px 15px;
text-align: left;
}
.comparison-table td {
padding: 12px 15px;
border-bottom: 1px solid #eee;
}
.comparison-table tr:last-child td {
border-bottom: none;
}
.comparison-table tr:hover td {
background: #f8f9fa;
}
.highlight-box {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px 25px;
border-radius: 12px;
margin: 30px 0;
}
.highlight-box h3 {
color: white;
margin-top: 0;
}
.file-label {
background: #6c757d;
color: white;
padding: 4px 10px;
border-radius: 4px 4px 0 0;
font-size: 12px;
font-family: monospace;
display: inline-block;
margin-bottom: -8px;
}
.copy-btn {
float: right;
background: #495057;
color: white;
border: none;
padding: 4px 10px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
}
.copy-btn:hover {
background: #343a40;
}
footer {
margin-top: 50px;
padding-top: 20px;
border-top: 1px solid #ddd;
color: #666;
font-size: 14px;
text-align: center;
}
@media (max-width: 600px) {
.pros-cons {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<h1>📊 Continuous Test Coverage for Monorepos</h1>
<p class="subtitle">A self-hosted approach using GitHub Actions + Gist (no external services required)</p>
<h2>🏗️ The Architecture</h2>
<div class="architecture">┌─────────────────────────────────────────────────────────┐
│ GitHub Actions │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ JS Tests │ │ Python Tests │ │
│ │ (vitest/jest) │ │ (pytest-cov) │ │
│ │ → coverage.xml │ │ → coverage.xml │ │
│ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │
│ └──────────┬───────────────┘ │
│ ▼ │
│ Extract % → Update Gist/Pages │
└─────────────────────────────────────────────────────────┘
┌──────────────┴──────────────┐
▼ ▼
┌───────────────┐ ┌─────────────────┐
│ Gist Badge │ │ GitHub Pages │
│ (shields.io) │ │ (HTML reports) │
└───────────────┘ └─────────────────┘</div>
<h2>🎯 Two Options</h2>
<div class="option-card">
<span class="badge badge-simple">Simplest</span>
<h3>Option 1: Gist + Shields.io Badge</h3>
<p>Push coverage percentages to a gist, display via shields.io dynamic badge. Great when you just need a quick overview.</p>
<div class="pros-cons">
<div class="pros">
<h4>✓ Pros</h4>
<ul>
<li>Minimal setup</li>
<li>Nice badge for README</li>
<li>No extra repos/branches</li>
</ul>
</div>
<div class="cons">
<h4>✗ Cons</h4>
<ul>
<li>Just percentages, no details</li>
<li>Can't drill into uncovered lines</li>
</ul>
</div>
</div>
<h4>Setup Steps</h4>
<div class="step">
<div class="step-number">1</div>
<div class="step-content">
<h4>Create a Gist</h4>
<p>Go to <a href="https://gist.github.com">gist.github.com</a> and create a new gist (can be empty). Note the gist ID from the URL.</p>
</div>
</div>
<div class="step">
<div class="step-number">2</div>
<div class="step-content">
<h4>Create a Personal Access Token</h4>
<p>GitHub Settings → Developer Settings → Personal Access Tokens → Generate with <code>gist</code> scope.</p>
</div>
</div>
<div class="step">
<div class="step-number">3</div>
<div class="step-content">
<h4>Add Secret to Repo</h4>
<p>Repo Settings → Secrets → Actions → New secret named <code>GIST_TOKEN</code>.</p>
</div>
</div>
<div class="step">
<div class="step-number">4</div>
<div class="step-content">
<h4>Add Workflow</h4>
<p>Create <code>.github/workflows/coverage.yml</code> (see below).</p>
</div>
</div>
<div class="file-label">.github/workflows/coverage.yml</div>
<pre><code>name: Coverage
on:
push:
branches: [main]
jobs:
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Backend (Python)
- name: Backend coverage
working-directory: ./backend
run: |
pip install pytest pytest-cov
pytest --cov=src --cov-report=json
# Frontend (JavaScript)
- name: Frontend coverage
working-directory: ./frontend
run: |
npm ci
npm run test:coverage
# Update Gist
- name: Update coverage gist
env:
GH_TOKEN: ${{ secrets.GIST_TOKEN }}
run: |
# Extract coverage percentages
PY_COV=$(jq '.totals.percent_covered | floor' backend/coverage.json)
JS_COV=$(jq '.total.lines.pct | floor' frontend/coverage/coverage-summary.json)
# Create shields.io endpoint JSON
cat > coverage.json << EOF
{
"schemaVersion": 1,
"label": "coverage",
"message": "py:${PY_COV}% js:${JS_COV}%",
"color": "$([ $PY_COV -ge 80 ] && [ $JS_COV -ge 80 ] && echo 'green' || echo 'yellow')"
}
EOF
# Update gist
gh gist edit YOUR_GIST_ID --add coverage.json</code></pre>
<h4>README Badge</h4>
<pre><code>![Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/YOUR_USER/GIST_ID/raw/coverage.json)</code></pre>
</div>
<div class="option-card">
<span class="badge badge-detailed">More Detailed</span>
<h3>Option 2: GitHub Pages (Full HTML Reports)</h3>
<p>Push full HTML coverage reports to GitHub Pages. Browse line-by-line coverage directly in your browser.</p>
<div class="pros-cons">
<div class="pros">
<h4>✓ Pros</h4>
<ul>
<li>Full detailed reports</li>
<li>See exactly which lines are covered</li>
<li>All within your repo (no external deps)</li>
</ul>
</div>
<div class="cons">
<h4>✗ Cons</h4>
<ul>
<li>Uses gh-pages branch</li>
<li>Slightly more complex workflow</li>
</ul>
</div>
</div>
<div class="file-label">.github/workflows/coverage.yml</div>
<pre><code>name: Coverage
on:
push:
branches: [main]
jobs:
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Backend coverage
working-directory: ./backend
run: |
pip install pytest pytest-cov
pytest --cov=src --cov-report=html:coverage-html
- name: Frontend coverage
working-directory: ./frontend
run: |
npm ci
npm run test:coverage
- name: Prepare pages
run: |
mkdir -p public
cp -r backend/coverage-html public/backend
cp -r frontend/coverage/lcov-report public/frontend
cat > public/index.html << 'EOF'
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;&lt;title&gt;Coverage Reports&lt;/title&gt;&lt;/head&gt;
&lt;body&gt;
&lt;h1&gt;Coverage Reports&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="backend/"&gt;Backend (Python)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="frontend/"&gt;Frontend (JS)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/body&gt;
&lt;/html&gt;
EOF
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./public</code></pre>
<p>Enable GitHub Pages in repo settings (source: <code>gh-pages</code> branch). Reports live at:</p>
<pre><code>https://YOUR_ORG.github.io/YOUR_REPO/</code></pre>
</div>
<h2>📋 Quick Comparison</h2>
<table class="comparison-table">
<tr>
<th>Aspect</th>
<th>Gist + Badge</th>
<th>GitHub Pages</th>
</tr>
<tr>
<td>Setup complexity</td>
<td>⭐ Simple</td>
<td>⭐⭐ Medium</td>
</tr>
<tr>
<td>Detail level</td>
<td>Percentages only</td>
<td>Line-by-line</td>
</tr>
<tr>
<td>External dependencies</td>
<td>shields.io (for badge)</td>
<td>None</td>
</tr>
<tr>
<td>Best for</td>
<td>Quick status check</td>
<td>Investigating gaps</td>
</tr>
<tr>
<td>Storage</td>
<td>~1KB gist</td>
<td>~1-5MB per update</td>
</tr>
</table>
<div class="highlight-box">
<h3>💡 Recommendation</h3>
<p>Use <strong>GitHub Pages</strong> if you actually want to investigate coverage gaps—the HTML reports are genuinely useful for that.</p>
<p>Use <strong>Gist + Badge</strong> if you just want a quick visual indicator in your README and don't need details.</p>
<p>You can also combine both: use GitHub Pages for reports and add a badge that links to them!</p>
</div>
<h2>🔧 Coverage Tool Setup</h2>
<h3>Python (pytest-cov)</h3>
<pre><code># Install
pip install pytest-cov
# Run with JSON output (for badge)
pytest --cov=your_package --cov-report=json
# Run with HTML output (for pages)
pytest --cov=your_package --cov-report=html</code></pre>
<h3>JavaScript (Jest)</h3>
<pre><code>// package.json
{
"scripts": {
"test:coverage": "jest --coverage --coverageReporters=json-summary --coverageReporters=lcov"
}
}
// jest.config.js
module.exports = {
collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}'],
coverageDirectory: 'coverage'
};</code></pre>
<h3>JavaScript (Vitest)</h3>
<pre><code>// vite.config.js
export default {
test: {
coverage: {
reporter: ['json-summary', 'lcov', 'html'],
reportsDirectory: './coverage'
}
}
}</code></pre>
<footer>
<p>Generated by Claude • No external services required • Just GitHub!</p>
</footer>
<script>
// Add copy functionality to code blocks
document.querySelectorAll('pre').forEach(pre => {
const btn = document.createElement('button');
btn.className = 'copy-btn';
btn.textContent = 'Copy';
btn.onclick = async () => {
const code = pre.querySelector('code')?.textContent || pre.textContent;
await navigator.clipboard.writeText(code);
btn.textContent = 'Copied!';
setTimeout(() => btn.textContent = 'Copy', 2000);
};
pre.style.position = 'relative';
pre.insertBefore(btn, pre.firstChild);
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment