CI Basics with Jenkins
A regression suite that only runs when someone remembers to run it on their laptop is, in practice, a regression suite that doesn't run. The whole point of the RecipeBox suite we've built across this track is to catch a broken login or a broken "Add Recipe" form before it merges — which means it has to run automatically, on a server, every time someone opens a pull request. That's what Continuous Integration (CI) gives us, and Jenkins is one of the most widely used CI tools for exactly this job.
Why "It Works On My Laptop" Isn't Enough
| Running locally only | Running in CI on every PR |
|---|---|
| Depends on someone remembering to run it | Runs automatically, every time, no one has to remember |
| Only tests the author's machine/browser version | Runs in a clean, consistent environment every time |
| A broken test is discovered whenever, if ever | A broken test blocks the pull request immediately |
| Doesn't scale past one or two developers | Scales to an entire team submitting PRs in parallel |
A Basic Jenkins Job Running mvn test
Jenkins jobs come in two common flavors for a suite like ours:
- Freestyle job — configured through Jenkins' UI. You point it at the RecipeBox test repo, add a build step, and tell it what shell command to run.
- Pipeline job — configured through a Jenkinsfile checked into the repo itself, as code. More flexible, and the direction most teams eventually move toward.
Either way, the core of the job is the same Maven command you already run locally:
mvn clean test
A minimal declarative Jenkinsfile for the RecipeBox automation repo:
pipeline {
agent any
triggers {
githubPullRequest() // conceptually: trigger on every PR
}
stages {
stage('Checkout') {
steps {
git branch: 'main', url: 'https://github.com/example/recipebox-tests.git'
}
}
stage('Run Regression Suite') {
steps {
sh 'mvn clean test'
}
}
}
post {
always {
junit 'target/surefire-reports/*.xml'
}
}
}
Headless Chrome: There's No Display on a CI Server
Your laptop has a screen, so a normal Chrome window popping up during a test run is fine. A Jenkins build agent usually doesn't have a display attached at all. Running Chrome headless — with no visible UI — solves this, and is also faster since there's no rendering to a screen:
ChromeOptions options = new ChromeOptions();
options.addArguments("--headless=new"); // modern headless mode, Chrome 109+
options.addArguments("--window-size=1920,1080");
options.addArguments("--no-sandbox"); // commonly needed inside CI containers
options.addArguments("--disable-dev-shm-usage"); // avoids /dev/shm size issues in containers
WebDriver driver = new ChromeDriver(options);
driver.get("https://recipebox.example.com/login");
boolean isCi = Boolean.parseBoolean(System.getenv("CI"));
ChromeOptions options = new ChromeOptions();
if (isCi) {
options.addArguments("--headless=new");
}
Publishing the TestNG HTML Report as a Build Artifact
TestNG already generates an HTML report under test-output/index.html after every run. Rather than let it disappear when the build finishes, tell Jenkins to keep it around and show it directly on the build page:
post {
always {
publishHTML(target: [
reportDir: 'test-output',
reportFiles: 'index.html',
reportName: 'TestNG Report',
keepAll: true
])
archiveArtifacts artifacts: 'test-output/**', allowEmptyArchive: true
}
}
| Step | What it gives the team |
|---|---|
| publishHTML | A clickable "TestNG Report" link right on the Jenkins build page — no digging through console logs. |
| archiveArtifacts | The raw report files saved with the build, so you can go back and check what a run from three weeks ago actually reported. |
| junit step (previous section) | Jenkins' own pass/fail trend graph across builds, parsed from Surefire's XML output. |