Author: ChatGPT o4-mini-high 2025-06 https://chatgpt.com/share/6859173a-08ac-8007-9d1b-e076bd1bd7e3
Here are several strategies you can combine to avoid running your full suite on every single commit and to scope down Cypress (or any other heavy) tests only when they’re really needed:
If your workflows live in .github/workflows/
, you can tell GitHub “don’t even kick off this workflow unless files in frontend/…
changed” (and vice-versa).
# .github/workflows/frontend.yml
on:
pull_request:
paths:
- 'frontend/**'
- 'cypress/**'
- 'package.json' # or other files whose changes really require front-end tests
# .github/workflows/backend.yml
on:
pull_request:
paths:
- 'backend/**'
- 'api/**'
- 'requirements.txt'
Pros: super simple; GitHub does the filtering Cons: hard to share common jobs & you end up with many tiny workflow files
B. Job-level Conditional Runs via dorny/paths-filter
-
Install
uses: dorny/paths-filter@v2 with: filters: | frontend: - 'frontend/**' - 'cypress/**' backend: - 'backend/**'
-
Branch jobs on its outputs:
name: CI on: [pull_request] jobs: filter: runs-on: ubuntu-latest outputs: fw: ${{ steps.filter.outputs.frontend }} bw: ${{ steps.filter.outputs.backend }} steps: - uses: actions/checkout@v3 - id: filter uses: dorny/paths-filter@v2 with: filters: | frontend: - 'frontend/**' - 'cypress/**' backend: - 'backend/**' test-frontend: if: needs.filter.outputs.fw == 'true' needs: filter runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install & Run Cypress run: | cd frontend npm ci npm run test:cypress test-backend: if: needs.filter.outputs.bw == 'true' needs: filter runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install & Run Backend Tests run: | cd backend pip install -r requirements.txt pytest
Pros: single workflow, clear “only run what changed” logic Cons: adds one small job to calculate diff
If you’re iterating quickly on a PR, you usually only care about the latest commit’s results:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
This will automatically cancel earlier runs on the same branch/PR as soon as a new commit arrives, so you won’t have duplicate builds piling up.
-
Manual trigger for your heaviest jobs:
on: workflow_dispatch: # “Run workflow” button in UI pull_request: types: [opened, reopened, synchronize]
-
Scheduled nightly or on-merge full-suite:
on: push: branches: - main # run full suite once merged schedule: - cron: '0 2 * * *' # every day at 02:00 UTC
Developers can prefix small commits with [skip ci]
(or [ci skip]
) in the commit message. GitHub will skip all workflows for that commit automatically.
Here’s a concise example combining path-filters, concurrency, manual triggers, and skip-ci:
name: PR CI
on:
pull_request:
types: [opened, reopened, synchronize]
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
filter:
runs-on: ubuntu-latest
outputs:
frontend: ${{ steps.filter.outputs.frontend }}
backend: ${{ steps.filter.outputs.backend }}
steps:
- uses: actions/checkout@v3
- id: filter
uses: dorny/paths-filter@v2
with:
filters: |
frontend:
- 'frontend/**'
- 'cypress/**'
backend:
- 'backend/**'
lint:
needs: filter
if: "!contains(github.event.head_commit.message, '[skip ci]')"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: npm ci && npm run lint
test-backend:
needs: [filter, lint]
if: needs.filter.outputs.backend == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: |
cd backend
pip install -r requirements.txt
pytest
test-frontend:
needs: [filter, lint]
if: needs.filter.outputs.frontend == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: |
cd frontend
npm ci
npm run test:unit
- name: Run Cypress (only on manual dispatch)
if: github.event_name == 'workflow_dispatch'
run: |
npm run test:cypress
- Lint & unit tests run whenever relevant files change
- Cypress only on manual “Run workflow”
- Skip CI honored if commit message contains
[skip ci]
- Concurrency cancels stale runs
- Cache dependencies (
actions/cache
) so repeated runs don’t reinstall everything. - Split workflows by category (e.g.
ci-lint.yml
,ci-e2e.yml
) if you want different cadence or permissions. - Consider putting really slow suites behind a “canary” label or an explicit comment trigger (e.g.
/run-e2e
).
With these patterns you’ll only spend runner-minutes where you really need them—and still keep full coverage available when you ask for it.