Skip to main content

TestFly

An opinionated, Spring Boot–inspired Java test automation framework.

Maven Central License


What is TestFly?​

TestFly eliminates the boilerplate that every Java Selenium project repeats — WebDriver setup and teardown, wait helpers, retry logic, screenshot capture, and report generation — so your test code contains only test intent.

It is inspired by Spring Boot's philosophy: sensible defaults and convention over configuration. A testfly.yml file is required for bootstrap; common optional settings retain their defaults when omitted.

public class LoginTest extends BaseTest {

@Test(description = "Valid user can log in")
public void loginTest() {
open();
new LoginPage(getDriver()).login("admin", "secret");
softAssert().that(new DashboardPage(getDriver()).isLoaded(), "Dashboard should be loaded");
}
}

No WebDriver setup. No @AfterMethod teardown. No wait helpers. No retry configuration. Just the test.


Design philosophy​

People often ask whether TestFly is meant to be an opinionated framework, an extensible toolkit, or a thin productivity layer over Selenium. It's the Spring Boot of Java test automation — and the answer is layered, not equal parts of all three:

  1. Opinionated core (primary). Convention over configuration with little test-code boilerplate. Add one dependency, provide the required testfly.yml, and extend BaseTest / BasePage. TestFlyDefaults lets shared test-base libraries register selected fallback values after YAML loading; it does not replace the configuration file.
  2. Never hides Selenium (the constraint). Unlike heavier abstractions, TestFly never takes the raw WebDriver away from you. When the conventions don't fit, drop straight down to WebDriver / By / WebElement. Opinionated without being a cage.
  3. Extensible toolkit (the escape hatch). An SPI/registry plugin system (DriverProviderRegistry, PluginRegistry, ReportAdapterRegistry) makes it modular for the power users who need it — serving the opinionated core, not replacing it. Most users never touch it.

Already invested in Selenium?​

You don't have to abandon Selenium to get the ergonomics people love in Playwright. TestFly brings those ideas into the Selenium ecosystem — so you keep your stack, your grid, and your team's skills:

Playwright ideaIn TestFly
Accessibility-first locatorsgetByRole, getByLabel, getByText, getByPlaceholder, getByTestId — target the accessibility tree, survive CSS/DOM refactors
Auto-waitingWaitEngine-backed actions — Thread.sleep() disappears
Web-first assertionsassertThat(...) that auto-retries until true
Convention over configurationRequired testfly.yml; defaults for optional settings

…all without hiding raw Selenium, and while keeping your existing Selenium / Java / TestNG stack, team skills, and Selenium Grid.

Why not just build your own framework?​

Almost every Java team already has one: a home-grown BaseTest, a DriverFactory, a pile of wait utilities, and a reporting hack — rewritten from scratch at each new project or company. It's unpaid infrastructure you own, debug, and maintain forever, and it's rarely tested or parallel-safe.

TestFly is that framework — already built, maintained, tested, thread-safe, and documented. You keep the part that's actually yours (the test intent) and delete the plumbing:

Roll your ownTestFly
Write & maintain driver lifecycle, waits, retriesProvided, thread-safe, zero config
Build a reporting layer from scratchHTML report + JUnit XML included
Bespoke CI wiring per projectAuto-detects GitHub Actions / Jenkins / CircleCI
Onboarding = "read our internal wiki"Onboarding = public docs + one dependency
You fix the bugsThe framework ships the fixes

TestFly is the Spring Boot of Selenium — zero setup, smarter defaults, Playwright-inspired APIs, and enterprise features, without hiding Selenium.


TestFly MCP Bridge & AI Automation

TestFly MCP Bridge (@testfly/mcp) is now live! Scaffold new Java 21 projects in seconds with npx @testfly/mcp init, or pair with Playwright MCP to let AI assistants (Cursor, Claude, Copilot) drive real browsers and generate idiomatic TestFly code. See the TestFly MCP & CLI Guide and AI & MCP Overview.


What you get out of the box​

Outcomes first — the API that delivers each one is on the right so you can jump straight to its docs.

What you getHow
Never write driver setup or teardown againOne driver per thread, created before each test, quit after
Switch environments without touching codeYAML config — browser, parallel, timeouts, retry in one file
Never write Thread.sleep() againAuto-waiting WaitEngine with 10+ built-in conditions
Flaky tests stop failing your buildGlobal, per-method @Retryable, or per-Cucumber-scenario @retryable tag
See exactly why a test failedScreenshot auto-captured on failure, base64-embedded in report
Read the test like a specStepLogger named steps with optional per-step screenshots
Hand stakeholders a report they'll readTabbed HTML dashboard — overview, test cases, failures, flakiness radar
Plug into any CI without extra toolingJUnit XML parsed natively by Jenkins, GitHub Actions, GitLab CI
CI that configures itselfHeadless forced, threads auto-tuned, no config changes needed
Extend it without forking itSPI-based plugins — custom drivers, hooks, report adapters
Bring your own test runnerFull JUnit 5 parity via @ExtendWith(TestFlyExtension.class) or BaseJUnit5Test
Write specs your product team can readBDD / Cucumber — BaseCucumberSteps, CucumberHooks, per-scenario steps in report
Test UI and API in the same suiteBaseApiTest, fluent ApiClient, JSONPath, schema validation, hybrid UI+API
Pin down the exact element, fluentlyfind(".row").filter(".active").nth(0).withText("Save") — chainable locators with explicit arguments
Tests survive CSS and DOM refactorsAccessibility-first locators — getByRole(Role.BUTTON).withName("Submit"), getByText, getByLabel, getByPlaceholder, getByTestId
Assertions that don't flake on timingWeb-first assertThat(By.id("x")).isVisible() — auto-retrying until timeout
Test admin-and-user flows in one testwithSession("admin", () -> { ... }) — two browsers in one test
Verify what actually landed in the DBdb().assertRowExists(), db().query().assertValue() — plain JDBC, no ORM
Assert on the email your app sentmailbox().waitForEmail(to("user@test.com")) — Mailhog, Mailtrap, Outlook, IMAP
Skip the browser for non-UI tests@NoBrowser — DB assertions, API checks, file operations, no WebDriver
Run on real cloud browsers unchangedexecution.mode: browserstack / saucelabs — session URL in HTML report
Understand failures without diggingAI failure analysis — Claude explains why a test failed and suggests a fix
Locators that repair themselvesSelf-healing fallback strategies when a locator fails
Know which tests will flake nextFlakiness prediction — risk scores from run history, radar chart in report
Drive tests from data you already haveExternal test data — @TestData("csv:..."), @TestData(value="excel:...", sheet="Login"), @TestData("db:SELECT...")
Test time-dependent behaviour deterministicallyClock mocking — clock().set("2030-01-01T00:00:00Z"), JS Date override, auto-reset

Philosophy​

  1. Zero boilerplate — if the user writes more than 1 line to enable something, it should be a default
  2. Convention over configuration — smart defaults, YAML opt-in for advanced behaviour
  3. No required external services — works offline, no cloud APIs in core
  4. Opt-in complexity — advanced features behind config flags, off by default
  5. Single dependency — add testfly and nothing else is required
  6. Test code stays clean — internals handle lifecycle; test methods contain only intent

Next steps​