visit
Continuous integration (CI) allows developers to automate running test suites and other jobs on each pull request created in their projects. These jobs must pass before merging the code changes into the master branch. This creates confidence in the master version of the code and ensures that one developer doesn’t break things for every other developer working out of the same codebase.
Continuous deployment (CD) facilitates deploying changes into production immediately when new code is merged into the master branch. Gone are the days of only releasing code once per quarter, per month, or per week. By releasing code early and often, developers can deliver value to their customers at a faster pace. This strategy also makes it easier to identify issues in production and to pinpoint which commit introduced them.
There are many great tools for creating CI/CD pipelines. is a popular open-source tool, and even comes with its own CI/CD features. offers a service called , which makes it a viable choice for developers already hosting and deploying their code through Heroku.In this article, we’ll go through the basic setup for getting up and running with Heroku CI, and then explore some advanced features like parallel test runs and automated browser tests.Pun generator app
You can find all of the .I added an NPM script so that I can run my tests by entering the command
npm test
in my terminal. Running my tests locally generates output that looks like the following:Test output
This is where the magic happens. At this point, I could see a section in my pull request in GitHub showing “checks” that need to pass. These “checks” are jobs running in the CI pipeline. In the screenshot below, you should notice the job for
continuous-integration/heroku
.GitHub pull request waiting on Heroku CI pipeline to pass
When I then hopped over to the Heroku pipeline dashboard, I could view the progress of the job as it ran my tests:Heroku CI job in progress
Once the job finished, I could then see a green checkmark back in GitHub as shown in the screenshot below:GitHub pull request after Heroku CI pipeline passes
Now, I could merge my branch into the master branch with confidence. All the tests were passing, as verified by my Heroku CI pipeline.As a side note, you should notice in my GitHub screenshot above that the
continuous-integration/heroku
check is required to pass. By default, checks are not required to pass. Therefore, if you’d like to enforce passing checks, you can set that up in the settings for your specific repo.Require status checks to pass before merging
To set up parallel test runs, all you need to do is specify in your
app.json
file the quantity
of dynos you want to run. I chose to use just two dynos, but you can use as many as you want! You can also specify the size
of the dynos you use. By default, your tests run on a single "performance-m" dyno, but you can increase or decrease the size of the dyno if you're trying to control costs. In my case, I chose the smallest dyno that Heroku CI supports, which is the "standard-1x" size.{
"environments": {
"test": {
"formation": {
"test": {
"quantity": 2,
"size": "standard-1x"
}
}
}
}
}
Two dynos being used during a Heroku CI job
npm install --save-dev cypress cross-env start-server-and-test
Second, I added some more NPM scripts in my
package.json
file so that it looked like this:"scripts": {
"cypress:open": "cypress open",
"cypress:run": "cypress run --browser chrome",
"cypress:test": "start-server-and-test start //localhost:5000 cypress:run",
"start": "cross-env PORT=5000 node index.js",
"test": "jest"
},
describe('Pun Generator App', () => {
beforeEach(() => {
cy.visit('//localhost:5000')
})
it('adds a dad joke to the page when the button is clicked', () => {
cy.contains('Click me for a terrible pun').click()
cy.contains('My ceiling isn’t the best, but it’s up there.')
})
it('tells the user when it is all out of puns', () => {
const button = cy.contains('Click me for a terrible pun')
for (let i = 0; i < 20; i++) {
button.click()
}
cy.contains("I'm all out of puns!")
})
})
npm run cypress:test
Finally, I modified my
app.json
file to include a new test script and appropriate buildpacks for Heroku CI to use. It's important to note that for JavaScript apps, Heroku CI uses the npm test
command. If you don't specify a test script in the app.json
file, then Heroku CI will just use the test script specified in your package.json
file. However, I wanted Heroku CI to use a custom script that ran both Jest and Cypress as part of the test, so I wrote an override test script in
app.json
.{
"environments": {
"test": {
"formation": {
"test": {
"quantity": 1,
"size": "standard-1x"
}
},
"buildpacks": [
{ "url": "//github.com/heroku/heroku-buildpack-google-chrome" },
{ "url": "heroku/nodejs" }
],
"scripts": {
"test": "npm test && npx cypress install && npm run cypress:test"
}
}
}
}
Unfortunately, I hit a snag on this last step. After several hours of reading, researching, and troubleshooting, I discovered that . The recommend using the
--headless
option rather than the deprecated default Xvfb
option. However, while running Cypress inside of the Heroku CI pipeline, it still tries to use
Xvfb
. Using previous versions of Cypress and older (and deprecated) Heroku stacks like "cedar-14" yielded no better results.It would appear that either Heroku or Cypress (or both) have some issues to address! Hopefully users running end-to-end tests with Selenium fare better than I did when trying to use Cypress.If you need to specify any non-confidential , you can add them to your
app.json
file like so:{
"environments": {
"test": {
"env": {
"NON_SECRET_VARIABLE": "abcd1234"
}
}
}
}
You would typically place private secrets in an
.env
file which you tell Git to ignore so that isn't checked into your source control. That way you aren't storing those values in your repo. Heroku CI adheres to this same principle by allowing you to store private environment variables directly in the Heroku CI Pipeline Dashboard rather than exposing them in the
app.json
file.If you are running into issues while setting up your Heroku CI pipeline, you can use the
heroku ci:debug
command directly in your terminal to create a test run based on your project's last local commit. This allows you to inspect the CI environment and gives you greater insight into possible test setup problems. This command is especially helpful if you know that your tests are passing outside of the Heroku CI environment but failing when run in the Heroku CI pipeline. In this case, the issue likely lies in the CI setup itself.Puns from the demo app