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 

GitHub logo 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.