Migrate from Serenity BDD
Serenity BDD and TestFly both sit on top of Selenium and aim to produce readable, reportable tests. Serenity leans heavily into BDD, Screenplay, and its own detailed living documentation. TestFly is lighter: it gives you the same readable tests and solid reporting, but with less annotation ceremony, a single YAML config file, and a smaller dependency footprint.
What's familiar
Page Objects / Screenplay
If you use Serenity Page Objects, the move is straightforward:
| Serenity | TestFly |
|---|---|
PageObject | BasePage |
@FindBy(id = "email") | By.id("email") field or inline find("#email") |
element(email).type("...") | type(EMAIL, "...") or find("#email").type("...") |
WebElementFacade | Locator / WebElement via find(...) |
Assert.assertThat(...) with Serenity matchers | assertThat(find(...)).hasText(...) |
Step reporting
Serenity records every @Step method in its report. TestFly uses StepLogger for the same purpose:
@Step("Enter credentials")
public void entersCredentials(String user, String pass) { ... }
StepLogger.step("Enter credentials");
find("#email").type(user);
Both produce a human-readable timeline in the HTML report.
BDD / Cucumber
Serenity's Cucumber integration is a major draw. TestFly has a Cucumber bridge too:
public class MySteps extends BaseCucumberSteps { ... }
See Cucumber for the full setup.
What's different
Configuration model
Serenity uses serenity.conf / serenity.properties plus many JVM properties. TestFly uses a single testfly.yml:
browser:
name: chrome
headless: false
execution:
mode: local
baseUrl: https://your-app.com
parallel: methods
threadCount: 4
timeouts:
explicit: 10
pageLoad: 30
retry:
enabled: true
maxAttempts: 2
Driver lifecycle
Serenity manages drivers through its own WebDriverManager / WebDriverFacade. TestFly uses DriverManager with thread-local isolation:
protected WebDriver getDriver() { ... } // from BaseTest / BasePage
No @Managed annotations, no PageFactory, no driver field injection.
Assertions
Serenity wraps Hamcrest/Fest assertions and adds auto-waiting. TestFly provides LocatorAssert:
loginButton.shouldBeVisible();
loginButton.shouldContainText("Sign in");
assertThat(find("#login")).isVisible();
assertThat(find("#login")).hasText("Sign in");
Reporting philosophy
Serenity generates very detailed living documentation. TestFly generates a focused HTML dashboard:
- Pass-rate gauge and suite summary
- Per-test timeline with screenshots
- Flakiness radar
- Retry badges
- JUnit XML for CI ingestion
- Optional Allure / Slack / Teams / ReportPortal adapters
If your organisation depends on Serenity's narrative living-documentation reports, TestFly's report is intentionally simpler. Evaluate whether the simplified format meets stakeholder needs before migrating.
Migration checklist
-
Replace dependencies
- Remove
net.serenity-bdd:*artifacts - Add
io.github.hakanngul:testfly
- Remove
-
Move configuration
- Convert
serenity.conf/serenity.propertiestotestfly.yml webdriver.driver→browser.nameserenity.take.screenshots→ screenshot config is automatic on failureserenity.timeout→timeouts.explicitserenity.restart.browser.for.each→browser.lifecycle
- Convert
-
Update page objects
- Extend
BasePageinstead ofPageObject - Replace
@FindByfields withByconstants or inlinefind(...)calls - Use
BasePagehelpers:click(By),type(By, String),select(By, String)
- Extend
-
Replace Serenity steps
@Stepannotated methods →StepLogger.step("...")calls- Or keep methods and add
StepLogger.step(...)at their entry points
-
Update assertions
- Replace Serenity
shouldBeVisible,shouldContainText, etc. withassertThat(find(...)).*
- Replace Serenity
-
Update test base
- Replace
SerenityRunner/SerenityJUnit5Extensionwith TestFly'sBaseTestorBaseJUnit5Test
- Replace
-
Cucumber steps (if used)
- Replace
Serenity Cucumberstep definitions withBaseCucumberSteps
- Replace
Side-by-side example
Serenity Page Object
public class LoginPage extends PageObject {
@FindBy(id = "email")
private WebElementFacade email;
@FindBy(id = "password")
private WebElementFacade password;
@FindBy(id = "login")
private WebElementFacade loginButton;
@Step("Login as {0}")
public void login(String user, String pass) {
email.type(user);
password.type(pass);
loginButton.click();
}
}
TestFly Page Object
public class LoginPage extends BasePage {
private static final By EMAIL = By.id("email");
private static final By PASSWORD = By.id("password");
private static final By LOGIN = By.id("login");
public void login(String user, String pass) {
StepLogger.step("Login as " + user);
type(EMAIL, user);
type(PASSWORD, pass);
click(LOGIN);
}
}
When to stay with Serenity
Serenity is a strong fit when:
- You rely on its rich living-documentation reports for stakeholder sign-off.
- Your team is fully committed to Screenplay pattern and
@Step-driven BDD. - You have a large existing investment in Serenity-specific annotations and plugins.
TestFly is a better fit when you want a lighter, YAML-configured, Selenium-native framework with modern locators and simpler CI integration.
Next steps
- Getting Started — first TestFly test in 5 minutes
- BasePage — page-object helpers
- Step Logging — named steps and screenshots
- Cucumber — BDD bridge in TestFly
- Configuration Reference — full
testfly.yml