You didn't set out to build a framework. You set out to test a login form.
But somewhere between the first WebDriver driver = new ChromeDriver() and the fiftieth flaky CI run, you built one anyway. There's a BaseTest. There's a DriverFactory. There's a WaitUtils class that everyone copies, and no one fully trusts. There's a reporting hack bolted onto TestNG listeners, and a block of CI YAML that only one person understands.
That's a framework. You just never called it one — and that's exactly why it's so expensive.
The framework you didn't mean to build
Here's the pattern, repeated at nearly every Java shop:
// The BaseTest that grows a little every sprint
public class BaseTest {
protected WebDriver driver;
@BeforeMethod
public void setUp() {
driver = new ChromeDriver(/* options someone tuned in 2022 */);
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
// …plus retries, screenshots, and env switching bolted on over time
}
@AfterMethod
public void tearDown(ITestResult result) {
if (result.getStatus() == ITestResult.FAILURE) {
// take a screenshot… somehow… attach it… somewhere
}
driver.quit();
}
}
It looks harmless. It's ten lines. But it never stays ten lines, because production testing keeps asking for more: parallel execution, a second browser, cloud grids, retry-on-flake, a report your manager will actually open. Each request adds a little more plumbing — and every line of that plumbing is code you now own.
The five costs nobody budgets for
1. Maintenance you can't schedule. Selenium 4 lands. ChromeDriver changes its options API. A dependency bump breaks your screenshot logic. None of this is on the roadmap, all of it is on you, and it always arrives the week before a release.
2. Onboarding that lives in someone's head. A new engineer can't just read the docs — there are no docs. Onboarding is "sit with Priya and she'll explain the wait helpers." The framework's real specification is tribal knowledge, and it walks out the door when people leave.
3. Parallelism bugs that only appear under load. Home-grown BaseTest classes are notorious for sharing a WebDriver across threads by accident. It passes locally and on a quiet CI box, then corrupts state the moment you scale to real parallelism. Thread-safety is hard, and you're debugging it on top of your actual product.
4. A reporting and CI layer rebuilt from scratch. Every team re-solves the same problems: parse results into something readable, embed failure screenshots, force headless in CI, tune the thread count, wire up JUnit XML so Jenkins understands it. It's weeks of work that produces nothing your users ever see.
5. The opportunity cost. Every hour spent maintaining the harness is an hour not spent writing tests that catch real bugs. You hired test engineers to improve quality — and a chunk of their time goes to being unpaid framework maintainers instead.
The cost isn't that a home-grown framework is bad. It's that it's infrastructure you're maintaining for free, and the bill compounds with every project that rebuilds it.
"But it's just a BaseTest"
That's the trap. It's just a BaseTest — until it's a BaseTest, a BasePage, a DriverFactory, a ConfigLoader, a RetryAnalyzer, a ScreenshotListener, a ReportBuilder, and a folder of utilities that three other repos have already forked and diverged from.
You didn't decide to maintain a framework. You accreted one. And because it grew a line at a time, nobody ever made the call to ask: should we be building this at all?
What if that framework were just… maintained for you?
This is the entire idea behind Selenium Boot. Your real alternative to a home-grown framework isn't "no framework" — it's someone else's, maintained, tested, and documented. Selenium Boot is that framework: Spring Boot's conventions, applied to Selenium. You add one dependency and the plumbing is handled.
public class LoginTest extends BaseTest {
@Test
public void login() {
open("/login"); // baseUrl from config
getByLabel("Username").fill("admin");
getByRole(Role.BUTTON)
.withName("Sign in").click(); // auto-waits, no sleep()
assertThat(getByText("Welcome"))
.isVisible(); // web-first, retries until true
}
}
No driver setup. No teardown. No Thread.sleep(). No brittle CSS selectors. The driver lifecycle is thread-safe by default, retries and reporting are built in, and CI configures itself. The five costs above? They're now someone else's job — and that someone ships the fixes, writes the docs, and tests the parallel path so you don't have to.
Crucially, it never hides Selenium. When the conventions don't fit, drop straight down to raw WebDriver / By / WebElement. It's opinionated, not a cage — and it brings Playwright's best ideas (accessibility-first locators, auto-waiting, web-first assertions) into the Selenium ecosystem you already run, keeping your Grid, your Java, and your team's skills.
It's not a Playwright replacement — the architecture is genuinely different, and if you're greenfield with no Selenium investment, Playwright is a reasonable choice. This is for the many teams with years of Selenium/Java/Grid they can't rip out.
Keep your test code. Delete the plumbing.
Look at your repo. The test intent — the part that says "a valid user can log in" — is genuinely yours, and it's valuable. The BaseTest, the DriverFactory, the wait-utils? That's the part you never wanted to own.
Selenium Boot lets teams focus on testing, not framework engineering. Keep the first part. Delete the second.
If you've built the framework in this post — and most Java teams have — that's not a criticism. It's just a bill you no longer have to pay.
Selenium Boot is open source (Apache-2.0), Java 17+, TestNG and JUnit 5. Full disclosure: it's my project.
Top comments (0)