Testing, Security, and Deployment
Ensure quality, secure your app, containerize, automate, and deploy to cloud platforms with confidence.
Content
Continuous integration setup
Versions:
Watch & Learn
AI-discovered learning video
Sign in to watch the learning video for this topic.
Continuous Integration Setup for CS50 Web Projects
You already lint, run unit tests, and wrote a React app that talks to Flask. Now imagine a tireless robot that does all of that for every PR — and yells if something's broken. That's CI. 🎯
What this is (and why you care)
You learned linting and static analysis, and you've written end-to-end tests. Continuous Integration (CI) is the automation glue that runs those checks on every commit or pull request so you catch regressions before they land. For a CS50 web project — a Flask API + React frontend — CI ensures:
- Style consistency via linters (flake8, eslint)
- Correctness via unit and integration tests (pytest, Jest)
- Frontend builds succeed (npm build)
- E2E pipelines pass (Playwright/Cypress)
- Security checks run (dependency scanning, secret checks)
Put simply: CI scales the habit of “run tests before you ship” from you to the whole repo.
Core CI goals for this course project
- Run linters (backend and frontend).
- Run unit tests for Flask (pytest) and React (Jest).
- Build the React app and ensure the build artifact exists.
- Run end-to-end tests against a test instance (headless browser).
- Run simple security checks (pip-audit / npm audit).
- Optionally build a Docker image or deploy to staging on success.
Quick primer: GitHub Actions terms (because you'll use them)
- Workflow: YAML file that defines your pipeline (runs on push/PR).
- Job: a set of steps that runs on a runner (ubuntu-latest, windows, macos).
- Step: a command or action inside a job.
- Secrets: secure environment variables (API keys, DB creds).
Example workflow (monorepo: Flask + React)
This single workflow shows parallel jobs for linting, unit tests, build, e2e, and deployment. Paste it in .github/workflows/ci.yml and adapt commands to your repo structure.
name: CI
on:
pull_request:
branches: [ main ]
push:
branches: [ main ]
jobs:
lint:
runs-on: ubuntu-latest
strategy:
matrix:
service: [backend, frontend]
steps:
- uses: actions/checkout@v4
- name: Setup Python (backend)
if: matrix.service == 'backend'
uses: actions/setup-python@v4
with:
python-version: 3.11
- name: Install backend deps
if: matrix.service == 'backend'
run: |
python -m pip install --upgrade pip
pip install flake8
- name: Run flake8
if: matrix.service == 'backend'
run: flake8 .
- name: Setup Node (frontend)
if: matrix.service == 'frontend'
uses: actions/setup-node@v4
with:
node-version: 18
- name: Install frontend deps
if: matrix.service == 'frontend'
run: |
cd frontend
npm ci
- name: Run eslint
if: matrix.service == 'frontend'
run: |
cd frontend
npx eslint src --max-warnings=0
backend-tests:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:14
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: test_db
ports: [ 5432:5432 ]
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
with:
python-version: 3.11
- name: Install deps
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest
- name: Run pytest
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db
run: pytest -q
frontend-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
- name: Install and test
run: |
cd frontend
npm ci
npm test -- --watchAll=false --coverage
e2e:
runs-on: ubuntu-latest
needs: [backend-tests, frontend-tests]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
with:
python-version: 3.11
- uses: actions/setup-node@v4
with:
node-version: 18
- name: Start backend server
run: |
# start flask app in background on port 5000 (use env config for test DB)
flask run --port=5000 &
- name: Install Playwright and run tests
run: |
cd frontend
npm ci
npx playwright install --with-deps
npx playwright test --config=playwright.config.js --headless
deploy:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
needs: [e2e]
steps:
- uses: actions/checkout@v4
- name: Deploy to Heroku
uses: akhileshns/heroku-deploy@v3.12.12
with:
heroku_api_key: ${{ secrets.HEROKU_API_KEY }}
heroku_app_name: ${{ secrets.HEROKU_APP }}
buildpack: heroku/python
Notes on the workflow
- Use matrix jobs to run lint for backend and frontend in parallel.
- The backend job uses a Postgres service so tests run against a real DB. Alternatively use SQLite for simpler tests.
- The e2e job starts the backend and then runs Playwright from the frontend. In CI you’ll run browsers headless.
- Secrets (API keys, DB passwords) must be set in the repo settings (GitHub Secrets).
Security checks to include (quick wins)
- pip-audit (Python) and npm audit (Node) in CI to detect vulnerable dependencies.
- CodeQL scanning via actions for deeper static security analysis.
- Dependabot (auto PRs) to keep deps fresh.
Example step:
- name: Run pip-audit
run: |
python -m pip install pip-audit
pip-audit --progress-spinner=off
- name: Run npm audit
run: |
cd frontend
npm ci
npm audit --audit-level=moderate
Practical tips & gotchas
- Caching: cache pip wheels and npm ci to speed runs. Use actions/cache with pip’s wheel cache and ~/.npm.
- Matrix for Python versions: test against 3.9, 3.10, 3.11 if supporting multiple versions.
- Artifacts: upload build artifacts or coverage reports (actions/upload-artifact) for later inspection.
- Flaky E2E tests: start with unit + integration tests in CI; only add e2e once stable or run e2e on a schedule/branch.
- Local parity: mimic CI env locally using Docker Compose or act (local GitHub Actions runner) to reproduce failures.
Checklist before enabling CI on your repo
- Add .github/workflows/ci.yml
- Add lint scripts (flake8, eslint) to package files
- Add test scripts (pytest, jest, playwright) to package files
- Store secrets (DB, API keys) as GitHub Secrets
- Turn on branch protection rules to require CI passing before merge
Takeaway: Automate the boring, catch the scary
CI is the friend who runs your linters, tests, and security checks every time you push — and reports back in crisp, unforgiving badges. With a solid GitHub Actions pipeline you’ll stop shipping broken builds to main and start shipping features with confidence.
"CI doesn't make bugs disappear — it makes them show up earlier, when you still remember how the code works."
Quick summary
- CI = automatic, repeatable validation on PRs and pushes.
- For CS50 web apps: lint, backend tests, frontend tests, build, e2e, security, deploy.
- Use GitHub Actions with jobs, matrices, services (Postgres), caching, and secrets.
- Start simple; add e2e and deployments after unit tests are stable.
Happy automating — and may your green checkmarks be many. 🚦
Comments (0)
Please sign in to leave a comment.
No comments yet. Be the first to comment!