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) .\scripts\new-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.