Task Manager — Spring Boot Full‑Stack App + Advanced Test Automation (API + UI) + CI (GitHub Actions)
A production-style demo project that combines a Spring Boot 3 backend, a classic Thymeleaf UI, and a 3‑level testing strategy:
- Unit tests (fast, isolated)
- API E2E tests (Rest Assured)
- UI E2E tests (Selenide + Selenoid)
Everything is runnable locally and in CI via one deterministic Docker Compose environment.
CI for this repository is implemented with GitHub Actions:
.github/workflows/ci.yml.
- Project Highlights
- Tech Stack
- Project Structure
- Prerequisites
- Quick Start
- How to Run
- Configuration
- Allure Reporting
- CI (GitHub Actions)
- Troubleshooting
- REST API with JWT auth for stateless clients
- Web UI with Form Login (Thymeleaf + Bootstrap)
- Separate Spring Security filter chains for JWT vs Form Login (clean and realistic)
- DB migrations via Flyway
- Actuator health endpoint used by CI to wait for service readiness
- Unit tests: tagged
@Tag("Unit") - E2E tests: tagged
@Tag("Api")and@Tag("UI") - E2E runs in an isolated environment (PostgreSQL + app + Selenoid) using
docker-compose.e2e.yml - JUnit 5 + Gradle retry configured for flaky E2E scenarios
- The pipeline follows the idea: build once → test everywhere
- CI reuses the same Docker Compose definition you can run locally:
- PostgreSQL (
db) - Application (
app) - Selenoid (
selenoid) for remote browser execution + video - Test runner container (
e2e-tests) that executes./gradlew e2eTest
- PostgreSQL (
- Java 17, Gradle
- Spring Boot 3.x: Web, Security, Data JPA, Validation, Actuator, Cache
- Thymeleaf (+
thymeleaf-layout-dialect), Bootstrap 5 - JWT:
jjwt - DB: H2 (local fast run), PostgreSQL (docker profile), Flyway
- API tests: Rest Assured
- UI tests: Selenide + remote execution in Selenoid
- Reporting: Allure (JUnit 5, Rest Assured, Selenide)
.
├── src/main
│ ├── java/com/example/taskmanager
│ │ ├── controller/ # web + api controllers
│ │ ├── security/ # JWT + security configuration
│ │ ├── service/ # business logic
│ │ └── ... # dto/model/repository/etc
│ └── resources
│ ├── application.yml # default (H2) + docker profile (PostgreSQL)
│ ├── db/migration # Flyway migrations
│ └── templates # Thymeleaf views
├── src/test
│ ├── java/com/example/taskmanager
│ │ ├── unit/ # @Tag("Unit")
│ │ ├── api/ # @Tag("Api") (Rest Assured)
│ │ └── ui/ # @Tag("UI") (Selenide)
│ └── resources
│ └── junit-platform.properties
├── config/browsers.json # Selenoid browser config (video + version pinning)
├── docker-compose.e2e.yml # full E2E environment (db + app + selenoid + tests)
├── Dockerfile # application image
├── Dockerfile.tests # test-runner image
└── .github/workflows/ci.yml # GitHub Actions pipeline
For local runs you have two options:
- JDK 17
- (Optional) Docker, if you also want E2E environment
- Docker Desktop / Docker Engine
- Docker Compose v2 (
docker compose ...)
./gradlew bootRunOpen:
- App:
http://localhost:8080 - H2 console:
http://localhost:8080/h2-console- JDBC URL:
jdbc:h2:mem:testdb - user:
sa
- JDBC URL:
./gradlew unitTestdocker compose -f docker-compose.e2e.yml up --buildReports will appear in build/ after completion.
This repository uses dedicated tasks:
unitTest→ only@Tag("Unit")e2eTest→ runs@Tag("Api")+@Tag("UI")
So use these tasks explicitly:
./gradlew unitTestIf you already have the app running at http://localhost:8080:
./gradlew e2eTestThis reproduces the CI environment:
docker compose -f docker-compose.e2e.yml up --build --abort-on-container-exit --exit-code-from e2e-testsTo clean up afterwards:
docker compose -f docker-compose.e2e.yml down --volumessrc/main/resources/application.yml contains:
- default profile: H2 in-memory DB (fast local run)
- docker profile: PostgreSQL (
dbservice)
To run app with PostgreSQL profile locally:
SPRING_PROFILES_ACTIVE=docker ./gradlew bootRunThe E2E test runner supports overriding endpoints via system properties:
| Property | Default | Used for |
|---|---|---|
api.base.uri |
http://localhost:8080 |
Rest Assured base URI |
selenide.base.url |
http://localhost:8080 |
UI base URL |
selenide.remote |
(empty) | Remote WebDriver URL (Selenoid) |
selenide.headless |
true |
Headless UI mode |
In Docker Compose E2E these are passed automatically:
-Dapi.base.uri=http://app:8080-Dselenide.base.url=http://app:8080-Dselenide.remote=http://selenoid:4444/wd/hub
Raw results:
build/allure-results
Generate HTML report:
./gradlew allureReportResulting HTML:
build/reports/allure-report
Tip: In CI, Allure results and the generated report are uploaded as workflow artifacts.
Workflow:
.github/workflows/ci.yml
What it does:
- Sets up Java 17 + Gradle cache
- Builds the app once (without tests)
- Runs
./gradlew unitTest - Spins up Docker Compose E2E environment and executes E2E via
e2e-testscontainer - Generates Allure report and uploads artifacts:
build/test-resultsbuild/allure-resultsbuild/reports/allure-report- Selenoid videos (if produced)
In this project the default test task is disabled. Use:
./gradlew unitTest
./gradlew e2eTest- Run the full E2E via Docker Compose (most deterministic):
docker compose -f docker-compose.e2e.yml up --build
- If running without Docker, you may need a local browser and/or set:
./gradlew e2eTest -Dselenide.headless=true
The test runner waits for:
GET /actuator/healthto return"status":"UP"
If you changed Actuator exposure settings, ensure health is still available.