CI/CD for an Angular + Jest App on Firebase with GitHub Actions
Lint, run Jest tests, build, and deploy your Angular app to Firebase Hosting automatically.
This recipe runs an Angular app with Jest instead of Karma for headless unit tests, then deploys the production build to Firebase Hosting via the Firebase CLI and a service-account token.
What the pipeline does
- install deps with npm ci
- lint with ng lint
- run Jest unit tests
- build with ng build --configuration production
- deploy to Firebase Hosting
The workflow
Jest runs headless without a browser. firebase deploy uses a service account passed through GOOGLE_APPLICATION_CREDENTIALS or the FIREBASE_TOKEN secret.
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm run lint
- run: npm test -- --ci
- run: npm run build -- --configuration production
- if: github.ref == 'refs/heads/main'
run: npx firebase-tools deploy --only hosting --token ${{ secrets.FIREBASE_TOKEN }}Caching and speed
cache: npm restores node_modules, and Jest caches its transform output under /tmp/jest keyed by config. The Angular AOT build is CPU heavy; cheaper managed runners such as Latchkey (around 69% cheaper than GitHub-hosted) keep production builds fast, and auto-retry handles a flaky Firebase CLI download.
Deploying
Generate a CI token with firebase login:ci, store it as FIREBASE_TOKEN, and reference your project in firebase.json. For preview channels per PR, run firebase hosting:channel:deploy pr-${{ github.event.number }} instead of a production deploy.
Key takeaways
- Use Jest with --ci for deterministic headless Angular unit runs.
- Deploy only hosting with a scoped Firebase CI token.
- Use Firebase preview channels for per-PR deployments.