🧭 You can find the source code for this blog post in the repository bahmutov/cypress-gh-action-changed-files.
Imagine we have a large monorepo (ughh, don't get me started!) with all its dependencies declared in the root
package.json file, and every application in its own subfolder:
apps/app-c. Every application has its own end-to-end tests. We run those tests using GitHub Actions CI by using cypress-io/github-action.
The repo has the following structure:
How do we run only the tests for
app-a when its files have changed and skip the tests for
app-c? We do not want to blindly run all tests - that would waste our CI minutes testing the apps that have not changed.
This blog post shows how to run different workflows depending on the changed files.
We can create separate workflow files, each for its own application.
You can find these workflows in the repo. Let's look at the tricks we play to make it work.
Our first application is the simplest one. It is a basic static site that can work right from the
index.html file. Thus our spec file is simple:
/// <reference types="cypress" />
The workflow file uses the GitHub event syntax to trigger the workflow on commit but only if it has changed files inside the
apps/app-a folder or the workflow file itself.
We use the Cypress GH action with
project: apps/app-a setting to install all repo's dependencies, including Cypress using the package lock file in the root of the repo. Then we run Cypress using
--project apps/app-a parameter, testing just the
Let's test it out. Let's open a pull request from another branch after changing the
The pull request #2 shows only the first workflow running.
Our second application is slightly more complex static site that requires a web server to work. We declared the serve dependency in the root package file, but in the
apps/app-b/package.json we simply add the start command for convenience.
Our second workflow should run when the files in the folder
apps/app-b change, or when the workflow file itself changes. It should also run when the root
package.json changes - because that's where we upgrade the dependencies.
The installation and testing steps are a little bit tricker than before. We want to install all dependencies using the root
package.json file, but then start the server in the
apps/app-b working directory. Thus we cannot simply use the
project: apps/app-b parameter - it would try to execute
npm start in the root folder before calling Cypress with
--project apps/app-b argument.
Instead we need to split the install and test steps into two. First we will install the dependencies in the root folder, but won't run any tests. Second, we will execute the tests using the
working-directory: apps/app-b parameter. Thus our workflow uses the following steps:
Let's change a file and observe. You can find the result in the pull request #3. Only the single workflow runs and it starts the server before running Cypress tests.
Tip: Cypress GH Action automatically caches dependencies using the hash of the root package lock file. Thus all these workflows share the same cached dependencies, which allows them to start and run really quickly. The entire CI run takes 35 seconds here, for example.
The last example uses the Next.js framework to create the full single-page application. It mimics the
app-b but might need a little bit of time to start. Thus we use
wait-on parameter to the test run.
Let's test it, let's change the root package file. Both
app-c workflows should run. You can find the result in pull/4.
The output from the Cypress GH Action shows the Next dev server starting and responding before the tests start.
I hope this example and explanation help you target your CI workflows better.
Bonus: handling inter-app dependencies
If the apps in the monorepo share code, if they depend on each other, then the clean split between the changed files and the tests breaks down. In that case, you want to run all tests on every commit and every pull request. Just remove every "paths" filter from the workflows and they all will run in parallel to quickly finish the tests. You can even parallelize each Cypress job to finish faster.