← All projects

Project milestone / Test automation and CI/CD

From a Java API framework
to a local multi-node CI lab.

I connected my API automation framework to GitHub and built a Jenkins environment with a containerized controller and three isolated agents. The result is a repeatable path from an exact commit to service health checks, TestNG execution, and published test evidence.

Framework
Java · TestNG
CI controller
Jenkins in Docker
Build capacity
3 isolated agents
Source of truth
GitHub

01 / A framework with deliberate boundaries

The project started as an exercise in understanding API automation below the convenience layer. Instead of putting REST Assured calls directly in every test, I designed a path that separates application operations, shared execution behavior, HTTP transport, and test assertions.

TEST→API OBJECT→API CLIENT→INTERCEPTORS→HTTP ADAPTER

ApiRequest describes an outgoing operation without pretending to know its response type. ApiClient coordinates serialization, the interceptor chain, transport, and response mapping. The REST Assured implementation stays behind an HTTP boundary, while tests receive both typed data and the raw response evidence needed for diagnostics and negative scenarios.

The framework also has consumer-owned YAML configuration for environments and services. Test authors select an environment, while runtime overrides allow the same framework code to run locally or inside a CI agent without hard-coded hosts.

02 / One controller, three Docker agents

The first CI stage runs entirely on my workstation. Docker Compose defines a Jenkins controller and three inbound agents. The controller has zero executors, so it coordinates work without running builds itself. Each agent exposes one executor and has its own workspace and Gradle cache volume.

GITHUBrepository + Jenkinsfile
→
JENKINScontroller · 0 executors
→
AGENT 01AGENT 02AGENT 03

The three containers provide clean, isolated execution environments. They do not create three times the physical CPU or memory; all three still share the workstation. That distinction matters when reading CI capacity: containers isolate processes and files, while the host remains the resource boundary.

A build selects one matching agent for the entire suite. Parallel fan-out is a later stage. This first milestone proves scheduling, environment selection, connectivity, execution, and reporting before introducing sharding.

03 / GitHub supplies the exact build input

The repository contains the Jenkinsfile, Docker image definitions, Docker Compose deployment, and service health-check script. Jenkins discovers the branch from GitHub, checks out the selected revision, and prints the full commit SHA and commit metadata in the build log.

The GitHub credential has read access to the repository. I deliberately disabled build-status publishing because I did not want Jenkins writing results back to commits. GitHub is the source of the code and pipeline definition; Jenkins owns the build history and test results.

Pipeline as code

The build behavior travels with the project.

The Jenkinsfile selects an agent, exposes the environment parameter, checks service health, runs Gradle, verifies that test-result XML exists, publishes results, archives reports, and cleans the workspace.

04 / The first green build was an infrastructure lesson

The initial “success” was only branch indexing. The first real build exposed the assumptions that Windows development had hidden.

  1. The Gradle script was not executable. Git stored gradlew as 100644, so Linux returned exit code 126. The repository now preserves the executable bit and LF line endings.
  2. The wrapper JAR was missing from GitHub. A broad *.jar ignore rule overrode an earlier exception. Jenkins had the script but not the class that launches Gradle.
  3. The Gradle cache belonged to root. Docker created the mounted cache directory with ownership that prevented the Jenkins user from creating wrapper lock files. The agent image and existing volumes were corrected.
  4. The wrapper itself was outdated. Its fixed network timeout ignored the new property. I regenerated the complete wrapper with Gradle 8.14.5 so the scripts, JAR, properties, and target distribution agree.

These failures were useful because each came from a different boundary: Git metadata, ignore rules, container filesystem ownership, and tool bootstrapping. Solving them made the build reproducible instead of merely making one machine pass.

05 / What the pipeline proves today

For the QA environment, Jenkins injects container-safe service URLs that use host.docker.internal. The agent reaches seven locally running services through their host-published ports and checks every /health endpoint before starting the test suite.

Checkout exact commit
Configure selected environment
Check seven service health endpoints
Run clean TestNG suite through Gradle
Verify and publish XML test results
Archive reports
Clean the agent workspace

Gradle generates JUnit-compatible XML even though TestNG is the test runner. Jenkins consumes that standard report format to show test counts, failures, and duration trends. The reporting format does not change the framework’s test engine.

The completed build demonstrates the full local path: GitHub commit, Jenkins scheduling, Docker agent, service connectivity, framework execution, and retained test evidence.

06 / A foundation for the larger CI/CD lab

This is Stage 1 of a broader lab, not the finished distributed system. The next infrastructure stage will preserve Jenkins on an ASUS home server and add physical worker capacity from an HP laptop and a constrained Raspberry Pi 3B+. Those machines will be measured before deciding how much work each should receive.

Later stages can add duration-aware sharding, disposable agents, and optional AWS capacity. The cloud environment is intended to be created on demand and destroyed when it is not needed, while its infrastructure code remains available for another demonstration.

The important outcome from today is smaller and concrete: the framework no longer depends on my IDE or one local shell. A committed revision can enter a controlled pipeline, run against a known environment, and leave behind evidence that another person can inspect.

← Explore the other projects