Semantic locators based on accessibility
Most Selenium tests find elements with brittle CSS selectors or long XPaths, and those break whenever the DOM hierarchy changes:
// Brittle — breaks on CSS redesigns:
find(By.cssSelector("div.modal > form button.btn-primary")).click();
TestFly adds Playwright-style semantic locators. They target the accessibility tree, so a test finds elements the way a person sees them on the page:
getByRole(Role.BUTTON).withName("Submit").click();
getByLabel("Email address").type("user@testfly.io");
getByPlaceholder("Search products…").type("laptop");
getByText("Forgot password?").click();
getByTestId("checkout-cta").click();
Each one returns a chainable Locator that waits on its own, so there is no Thread.sleep() in the test.
Page objects with BasePage
BasePage has helper methods that wait before they act: type, click, getText, getAttribute and isDisplayed.
package io.testfly.examples.pages;
import io.testfly.test.BasePage;
import io.testfly.locator.Role;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
public class LoginPage extends BasePage {
private static final By USERNAME = By.id("username");
private static final By PASSWORD = By.id("password");
public LoginPage(WebDriver driver) {
super(driver);
}
public void login(String user, String pass) {
type(USERNAME, user);
type(PASSWORD, pass);
getByRole(Role.BUTTON).withName("Sign In").click();
}
}
Custom waits with WaitEngine
When you need your own wait conditions, WaitEngine keeps explicit waits in one place and takes the timeout from timeouts.explicit in testfly.yml:
import io.testfly.wait.WaitEngine;
// Centralized explicit wait conditions:
WaitEngine.waitForVisible(By.id("dashboard-banner"));
WaitEngine.waitForClickable(By.id("pay-button"));
WaitEngine.waitForInvisible(By.cssSelector(".loading-spinner"));
