Keep assertions only in tests. Give every parallel test its own browser. Mock APIs for logic checks. Tag tests as smoke or regression. I turned all of this into a free GitHub template you can clone today.
Every time I started a new test automation project, the first weeks went to the same chores: choosing library versions, deciding on folders, wiring reports, writing a pipeline.
After the fifth time, I turned my setup into a template. This post explains the structure and the rules behind it, so you can use them even if you never clone the repo.
🧭 The one rule that keeps projects clean
Tests assert. Everything else acts.
| Layer | What it does | Assertions? |
|---|---|---|
| Page objects | Click and type | Never |
| API clients | Send requests and return the response | Never |
| Repositories | Run SQL | Never |
| Tests | Check the result | Always |
Because only tests contain assertions, you can reuse every page, client and query in any test without surprises. Here is a page object from the template:
public class LoginPage extends BasePage {
@Step("Login as {username}")
public LoginPage loginAs(String username, String password) {
locator("#username").fill(username);
locator("#password").fill(password);
locator("button[type='submit']").click();
return this;
}
public String flashMessage() {
return locator("#flash").innerText();
}
}
And the test that uses it:
@Test
@Tag("smoke")
void validLogin() {
LoginPage page = new LoginPage().open().loginAs("tomsmith", "SuperSecretPassword!");
assertThat(page.flashMessage()).contains("You logged into a secure area!");
assertThat(page.currentUrl()).endsWith("/secure");
}
There is no sleep and no wait code. Playwright waits for each element to be ready before it acts.
⚡ Parallel runs without flaky chaos
Fast suites run tests in parallel, and that breaks anything that shares a browser.
The template gives every test its own isolated browser session. A small manager stores one session per thread, and a JUnit extension starts and closes it around each test. When a test fails, the same extension attaches a screenshot and a Playwright trace to the report.
The trace is the feature I would not give up. You replay the failed run step by step, with page snapshots and network calls, instead of guessing from a stack trace.
🧪 Mock-first API tests
Tests against a public test API fail when that API is slow or down. For logic checks I stub the service with WireMock and keep only a few tests against the real one:
wm.stubFor(get(urlEqualTo("/api/users/2")).willReturn(okJson(body)));
client.getUser("2").then()
.statusCode(200)
.body(matchesJsonSchemaInClasspath("schemas/user-response.json"));
The JSON schema check turns the response into a contract, so a renamed field fails the test even if the status code is still 200.
🔎 Queues and log search without hand-written JSON
If your system uses RabbitMQ or Elasticsearch, tests need small helpers or they turn into copy-pasted boilerplate. A fluent query builder keeps Elasticsearch requests readable:
ElasticQueryBuilder query = ElasticQueryBuilder.create()
.filterTerm("service.keyword", "payments")
.filterRange("@timestamp", "now-1h", null)
.sortBy("@timestamp", false)
.size(20);
SearchResult result = elastic.waitForHits(index, query, 1, Duration.ofSeconds(15));
The waiting method polls, because Elasticsearch needs about a second before a new document becomes searchable. That is a place where a fixed sleep always ends up wrong.
🏷️ Smoke and regression with tags
Every test carries a layer tag (ui, api, db, integration) and a level tag (smoke or regression). Pull requests run only smoke, and the nightly run does everything:
mvn clean test -Dgroups=smoke
mvn clean test -Dgroups="api & regression"
Failed tests retry once with -Drerun.count=1, and the report marks them as flaky, so retries never hide a problem.
🚀 Try the template
The whole structure is available as a free GitHub template with:
- Playwright UI tests with screenshots and traces on failure
- REST Assured + WireMock for API tests
- Database, RabbitMQ and Elasticsearch helpers
- Allure reports and a GitHub Actions pipeline
- Secrets read from environment variables
- A script that renames everything to your own package
git clone https://github.com/edwardmrdoian/qa-enterprise-template
./scripts/new-project.sh com.acme.qa acme-qa-tests
mvn clean test -Dgroups=smoke

edwardmrdoian
/
qa-enterprise-template
Production-ready QA automation template in Java: Playwright UI, REST Assured + WireMock API, JDBI database, RabbitMQ and Elasticsearch/ELK helpers. JUnit 5 parallel runs, Allure reports with failure traces, GitHub Actions CI. Clone, rename with one script, and start writing tests.
QA Enterprise Template
A Java 25 starter for maintainable UI, API, database, RabbitMQ and Elasticsearch tests
The default suite checks the framework using real local infrastructure or isolated mocks
Chromium against a local HTTP fixture, REST Assured against WireMock, and JDBI against H2.
Public demo services and Docker-backed examples are explicitly opt-in.
Stack: Maven Wrapper 3.9.9, JUnit 5, Playwright, REST Assured, WireMock, AssertJ,
Awaitility, Jackson, JDBI/HikariCP, Owner, Datafaker/Instancio, Allure and Spotless.
1. Create your project
Install JDK 25 and set JAVA_HOME; no global Maven installation is needed.
Use GitHub’s Use this template button when enabled, or clone into a new repository.
Keep the MIT license notice when redistributing template code.
From the project root, initialize the Java package, Maven coordinates and display name:
# Windows (also works in PowerShell on Linux/macOS)
.scriptsnew-project.ps1 com.acme.qa acme-qa-tests "Acme QA Tests"
# Linux/macOS or Git
…
If it saves you time, a ⭐ on GitHub helps other testers find it.
What would you add to a template like this? Tell me in the comments.
