Self-hosted service
eliasnogueira/selenium-java-lean-test-architecture avatar
eliasnogueira/selenium-java-lean-test-architecture

selenium-java-lean-test-architecture: one test suite, four ways to run it

Ready to use Lean Test Automation Architecture using Java and Selenium WebDriver to speed up your test automation

588 stars187 forksJavaMIT

At a glance

What is it?
This project is a ready to use lean test automation architecture for Java and Selenium WebDriver, with the page object pattern, a target property that switches between local, TestContainers and Selenium Grid runs, Allure reporting and profiles in pom.xml for parallel execution. It is MIT licensed, and the interesting part is the execution switching rather than the page objects.
Who is it for?
The architecture fits a team that has decided to use Selenium with Java and wants a working skeleton rather than a blank page, and the target property is the piece worth taking because it lets the same suite run locally, in containers and on a grid without rewriting it. It does not fit a team that has already standardised on JUnit, Cucumber or Playwright, since the stack here is fixed to TestNG, and the page object guidance deliberately includes an anti-pattern to avoid.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly Java, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the project delivers

This project delivers a complete lean test architecture for web tests using Java and Selenium WebDriver, built around what the documentation calls the best frameworks and practices.

The claim is that it gives you a complete solution to run tests in different ways: local testing using the browser on your local machine, parallel or single testing using Selenium Docker, local testing using TestContainers, and distributed execution using Selenium Grid.

The stack is specified precisely. Java 25 is the programming language, TestNG is the unit test framework, Selenium WebDriver with the Java binding does the browser automation, AssertJ is the fluent assertion library, Allure Report is the reporting strategy, DataFaker is the data generation strategy, Log4J2 handles logging, Owner is used to minimize the code needed to handle the properties file, and TestContainers provides the WebDriver containers.

The architecture itself is listed as eleven items: the page objects pattern, execution types, the BaseTest class, a TestListener, logging, configuration files, parallel execution, a test data factory, profiles executors on pom.xml, pipeline as code, and test environment abstraction.

The audience is a team that has chosen Selenium and Java and wants a starting point rather than a blank repository. The project asks for pull requests or issues if you think other items belong in the architecture, which tells you the list is meant to grow.

The examples in the documentation are a local test run with a report and a parallel run against a Selenium Grid.

The target property decides how the suite runs

The mechanism that makes this architecture worth copying is the target property.

There are three execution types: local, selenium-grid and testcontainers. The TargetFactory class resolves the target execution based on the target property value located on the general.properties file, and its usage sits on the BaseWeb class before each test execution. So the same test class runs in three environments and the switch is a property value, not a code change.

Local execution is the default path. The documentation says this approach is automatically used when you run the test class in your IDE, and when the target is local the createLocalDriver method is used from the BrowserFactory class to return the browser instance. The browser used in the test is placed on the browser property in the general.properties file.

That last point is the one that catches people. You can launch a test from your IDE, see it pass against your own browser, and then run the same class on a grid and see it fail, because the local path picks up the browser property and the remote path picks up a capability class instead.

The remote path uses the getOptions method from the BrowserFactory to return the browser option class, since remote execution needs the browser capability, and the DriverFactory class has an internal method createRemoteInstance to return a RemoteWebDriver instance based on that capability.

For the container path, the TargetFactory uses the createTestContainersInstance method to initialize the container based on the browser set in the browser property, and the documentation notes that Testcontainers currently supports Chrome and Firefox only.

Running a test in TestContainers and on Selenium Grid

The container execution type uses the TestContainers WebDriver Containers to run the tests on your own machine while using the Selenium Docker images for Chrome or Firefox. The command is one line:

shell
mvn test -Pweb-execution -Dtarget=testcontainers -Dbrowser=chrome

The profile is web-execution, the target selects the execution type, and the browser property selects which Selenium image to use.

The Selenium Grid approach executes the tests in remote machines, whether those are local, remote or a cloud grid. Two values have to be right before a remote run, and the documentation is explicit about it: the grid.url and grid.port property values on the grid.properties file must be updated before the start. If you are using the docker-compose.yml file to start the Docker Selenium grid, the values on the grid.properties file should work as they are.

That last sentence is worth pausing on, because it is a good sign about the quality of the configuration. A template that ships with matching values for its own compose file removes the most common failure in a Selenium Grid setup, where the test expects localhost on one port and the grid publishes on another.

The repository has a grid/ directory for the distributed setup and a .sdlc/ directory, which is the software development life cycle material that covers the pipeline as code item in the architecture list. The top level also has a .github/ directory, which is where the workflow that produces the build and test badge lives.

The BrowserFactory enum and the four methods it exposes

The BrowserFactory class is a Java enum that holds all the implemented browsers to use during a test execution. Each browser is an enum constant, and each one implements four methods.

The first is createLocalDriver, which creates the browser instance for local execution, with the browser driver automatically managed by the WebDriverManager library. The second is createDriver, which creates the browser instance for the remote execution. The third is getOptions, which creates a new browser Options setting some specific configurations and is used for the remote executions using the Selenium Grid. The fourth is createTestContainerDriver, which creates a Selenium Grid lightweight test container in Standalone mode with Chrome, Firefox and Edge browser support.

There is a small inconsistency worth knowing, and it is visible in the documentation rather than hidden. The local driver method uses getOptions to get the specific browser configurations, which means the same option settings apply to local and remote runs. But the TestContainers path is limited to Chrome and Firefox, while the container driver method mentions Edge support. So a browser that works locally may not have a container counterpart.

The enum design is what makes the target switching possible in the first place. Since every browser is an enum constant rather than an if chain in a factory method, adding a browser means adding a constant and implementing the four methods, and the compiler enforces that. The cost is that every browser carries four methods whether or not it uses them.

The page object pattern, and the anti-pattern it warns about

The project deliberately does not explain the page object pattern itself, on the grounds that good explanations exist elsewhere. What it explains is what it does with page objects.

The AbstractPageObject class has a protected constructor, which removes the necessity to initialise the elements using the Page Factory. It also sets the timeout from the timeout property value located on the general.properties file, so the wait strategy is configuration rather than code. All the page object classes are supposed to extend it, and it tries to remove the driver object from the page object class as much as possible.

Then there is the exception, and it is the most interesting part of the documentation. There is a NavigationPage in the common package inside the page objects, and all the pages extend this one instead of the AbstractPageObject. The stated reasons are that the previous and next buttons are fixed on the page with no refresh on the page, and to avoid creating or passing the new reference to the NavigationPage when hitting the previous or next buttons.

The author then tells you not to copy that: as much as possible avoid this strategy, to not get an ElementNotFoundException or StaleElementReferenceException, and to use the approach if you know that the page does not refresh.

That is a good piece of engineering writing. The inheritance hack is presented as a specific compromise with specific reasons, and then it is explicitly marked as a thing not to generalise. The failure modes are named, so a reader can judge the trade for their own pages.

The rest of the architecture: config, data, reporting and parallelism

The remaining architecture items are the parts that make a test suite maintainable rather than merely runnable.

Configuration is split across property files, with general.properties holding the target, browser and timeout values and grid.properties holding the grid.url and grid.port. The Owner library is used to minimise the code to handle the properties file, which removes the boilerplate of reading keys and casting values. It is also what makes the -Dtarget and -Dbrowser overrides on the Maven command line work.

Test data comes from the test data factory, with DataFaker as the generation strategy, so the test data is synthesised rather than hard coded, and the test data factory is a separate item in the architecture list rather than something folded into the base class.

Reporting is Allure Report, and the local testing example in the documentation is a captured run ending in a report. Log4J2 is the logging management strategy, and there is a dedicated logging section in the architecture list, which matters because a browser test that fails without a log of what it saw is a test you cannot debug.

Parallel execution is handled through profiles executors on pom.xml, with the web-execution profile being the one used in the TestContainers command. The BaseTest class is the shared setup, the TestListener handles test lifecycle events, and the test environment abstraction covers the Docker Selenium distributed run.

Taken together, the eleven items are the standard set of concerns for a Selenium suite, arranged so that a new team can see where each one lives before writing their first test.

Where this architecture is the wrong choice

The first limit is the stack commitment. TestNG is fixed as the test framework, and TestNG is a minority choice in the Java ecosystem compared with JUnit 5. If your organisation has standardised on JUnit, adopting this skeleton means adopting TestNG too, or rewriting the listeners and the runner configuration.

The second is that the page object pattern itself is contested. It is a way of modelling pages as classes, and it carries a well known cost: when the UI changes, you change the page object, and when a test needs to assert something the page object does not expose, the pattern bends or the test reaches past it. The NavigationPage inheritance hack in this project is a symptom of that, and the author says as much in the warning attached to it.

The third is the browser matrix. The container path supports Chrome and Firefox only, while the local path goes through WebDriverManager, which can fetch drivers for more browsers. A team whose users are on Safari or Edge will find a gap between what runs locally and what runs in the shared environment, and that gap is where flaky, environment-dependent failures come from.

The fourth is the scope of the framework. This is a UI test architecture, and it is entirely silent on API testing, on test data management at scale, on non functional testing and on the reporting and triage workflow that a large suite needs. It is a starting skeleton, and the documentation says as much by calling it lean and by asking for contributions.

Compare that with the alternative of writing a Playwright suite in TypeScript. The difference in approach is that Playwright ships its own runner, its own assertions, its own fixtures and its own trace viewer, so the framework layer largely disappears and the language is the one most teams use for the application. What this project gives you instead is a layout you can recognise and extend, in the stack you have already chosen, with a proven answer to the question of how to run the same suite in three places.

Licence, repository state and the upgrade cost

The project is released under the MIT licence, with the text in the LICENSE file at the repository root. The MIT terms permit use, modification and redistribution including inside a proprietary codebase, provided the copyright notice and licence text are kept with copies. Read the LICENSE file yourself for the exact wording, and keep the notice in your distribution.

The repository is not archived, and the last push was on 2026-10-20, so the code is current relative to today. The repository publishes no GitHub releases, so there is no tagged version to pin and no changelog to diff, which means adopting the project means adopting the current state of the main branch and taking on the maintenance of the parts you keep.

The upgrade cost is therefore in the toolchain rather than in a version number. The build is a Maven project with a pom.xml at the top level, and it targets Java 25, which is a recent long term release. Moving to a newer Selenium or TestNG version means resolving the dependency tree in a pom.xml that is a starting point rather than a carefully curated set of pins, so expect to do that work yourself.

The repository also carries a.sdlc/ directory, a CODE_OF_CONDUCT.md and a CONTRIBUTING.md, which is a reasonable level of project hygiene for something intended as a shared starting point. There is also an assets/ directory holding the animated examples used in the documentation, and a src/ directory with the test code and the page objects.

Before you take it as a starting point, run the same test class under all three target values on your own machine. If the local and container runs disagree, you have found the problem before it is in your pipeline.

Editorial conclusion

The architecture fits a team that has decided to use Selenium with Java and wants a working skeleton rather than a blank page, and the target property is the piece worth taking because it lets the same suite run locally, in containers and on a grid without rewriting it. It does not fit a team that has already standardised on JUnit, Cucumber or Playwright, since the stack here is fixed to TestNG, and the page object guidance deliberately includes an anti-pattern to avoid. Before adopting it, run the same test class under all three target values and confirm the grid.url and grid.port settings on grid.properties, since the documentation says you must update them before a remote run.

Frequently asked questions

How do I switch the selenium-java-lean-test-architecture suite between local, containers and grid?

Set the target property on general.properties, or override it on the command line. The documented container run is mvn test -Pweb-execution -Dtarget=testcontainers -Dbrowser=chrome, and the TargetFactory class resolves the execution type from that value before each test execution.

What must I configure before running tests on Selenium Grid?

You must update the grid.url and grid.port property values on the grid.properties file before the start. If you are using the docker-compose.yml file to start the Docker Selenium grid, the values on the grid.properties file should work as they are.

Which frameworks does the lean test architecture use?

Java 25, TestNG as the unit test framework, Selenium WebDriver with the Java binding, AssertJ for fluent assertions, Allure Report for reporting, DataFaker for data generation, Log4J2 for logging, Owner for properties handling and TestContainers for WebDriver containers.

Why does the project advise against the NavigationPage inheritance approach?

The project states that as much as possible you should avoid that strategy, because it risks an ElementNotFoundException or a StaleElementReferenceException, and that the approach is only safe if you know the page does not refresh.

What licence does the selenium-java-lean-test-architecture project use?

It is released under the MIT licence, with the terms in the LICENSE file at the repository root. The repository publishes no GitHub releases, and the last push was on 2026-10-20.

Official sources

  1. eliasnogueira/selenium-java-lean-test-architecture on GitHub
  2. Issues
  3. License: MIT
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/eliasnogueira-selenium-java-lean-test-architecture.svg)](https://hysenlabs.com/projects/eliasnogueira-selenium-java-lean-test-architecture)

Was this useful?