Advanced Selenide Selectors and Page Objects
Once you're past the basics of Selenide, you'll hit scenarios where simple CSS selectors aren't enough: dynamic content, nested components, shadow DOM, iframes, and complex page structures. Here's how to handle them, plus how to organize tests with page objects.
Selector Reference
CSS and XPath
// Standard CSS
$(".submit-btn")
$("#user-id")
$("input[type=email]")
$("nav > ul > li:first-child")
$("[data-testid=login-form]")
// XPath
$x("//button[contains(@class, 'primary')]")
$x("//td[text()='Active']/following-sibling::td")
$x("//label[text()='Email']/following-sibling::input")By Text Content
import static com.codeborne.selenide.Selectors.*;
// Exact visible text
$(byText("Sign In"))
$(byText("Delete"))
// Partial text
$(withText("Sign"))
// Text in specific element type
$("button", byText("Submit"))
// XPath text functions
$x("//*[normalize-space(text())='Submit']")By Attribute
$(byAttribute("data-role", "admin"))
$(byAttribute("aria-label", "Close dialog"))
$(byAttribute("name", "csrf_token"))
// Attribute contains value (use XPath)
$x("//*[contains(@class, 'btn-')]")
$x("//input[starts-with(@name, 'address')]")Element Collections and Filtering
ElementsCollection rows = $$("table tbody tr");
// By index
rows.get(0) // first row (zero-indexed)
rows.first()
rows.last()
// Filter by condition
rows.filterBy(text("Active"))
rows.filterBy(cssClass("selected"))
rows.excludeWith(cssClass("disabled"))
// Find within collection
rows.findBy(text("John Doe"))
// Chain: find specific cell in matching row
$$("table tbody tr")
.findBy(text("John Doe"))
.$("td.status");Working with Dynamic Elements
Elements That Appear After Interaction
Selenide waits automatically, but you need to be precise about what you're waiting for:
// Waiting for a loader to disappear before proceeding
$(".loading-overlay").shouldNotBe(visible);
$(".data-table").shouldBe(visible);
// Waiting for item count to change
int initialCount = $$(".product-item").size();
$(".load-more-btn").click();
$$(".product-item").shouldHave(sizeGreaterThan(initialCount));Elements with Dynamic IDs or Classes
Avoid brittle selectors tied to generated IDs:
// Fragile - generated ID
$("#ember-123") // breaks every deploy
// Stable - data-testid (requires dev coordination)
$("[data-testid=checkout-btn]")
// Stable - semantic HTML
$("nav [aria-label='User menu']")
$("main article h1")
// Stable - visible text
$(byText("Proceed to Checkout"))Stale Element Handling
Selenide automatically re-fetches elements that become stale (detached from DOM after re-render):
SelenideElement row = $$("tr").findBy(text("Order #123"));
// If the table re-renders, Selenide retries the selector
row.$("td.status").shouldHave(text("Shipped"));iframes
import static com.codeborne.selenide.Selenide.switchTo;
// Switch to iframe by index
switchTo().frame(0);
$(".content-inside-iframe").shouldBe(visible);
// Switch by name or ID
switchTo().frame("payment-iframe");
$("input[name=card]").setValue("4111111111111111");
// Switch back to main content
switchTo().defaultContent();
// Or use SelenideElement
$("iframe#payment").as("payment frame");
switchTo().frame($("iframe#payment").toWebElement());Shadow DOM
// Shadow DOM requires explicit piercing
SelenideElement shadowHost = $("my-custom-element");
SelenideElement shadowRoot = shadowHost.shadowRoot();
shadowRoot.$(".inner-button").click();
// Nested shadow roots
$("outer-component")
.shadowRoot()
.$("inner-component")
.shadowRoot()
.$("button.submit")
.click();Page Object Pattern
Page objects encapsulate selectors and actions for a page or component. This keeps tests readable and centralizes the impact of UI changes.
Basic Page Object
import com.codeborne.selenide.SelenideElement;
import static com.codeborne.selenide.Selenide.$;
import static com.codeborne.selenide.Selenide.open;
import static com.codeborne.selenide.Condition.*;
public class LoginPage {
// Selectors as fields
private final SelenideElement emailInput = $("input[name=email]");
private final SelenideElement passwordInput = $("input[name=password]");
private final SelenideElement submitButton = $("button[type=submit]");
private final SelenideElement errorMessage = $(".error-banner");
public LoginPage open() {
Selenide.open("/login");
return this;
}
public DashboardPage loginAs(String email, String password) {
emailInput.setValue(email);
passwordInput.setValue(password);
submitButton.click();
return new DashboardPage();
}
public LoginPage submitWithInvalidCredentials(String email, String password) {
emailInput.setValue(email);
passwordInput.setValue(password);
submitButton.click();
return this;
}
public LoginPage shouldShowError(String message) {
errorMessage.shouldBe(visible).shouldHave(text(message));
return this;
}
}Page Object in a Test
class LoginTest {
@Test
void successfulLogin() {
new LoginPage()
.open()
.loginAs("user@example.com", "secret")
.shouldShowWelcome("John Doe");
}
@Test
void wrongPasswordShowsError() {
new LoginPage()
.open()
.submitWithInvalidCredentials("user@example.com", "wrong")
.shouldShowError("Invalid email or password");
}
}Component Objects
For reusable UI components (nav bar, modals, tables):
public class DataTable {
private final SelenideElement container;
public DataTable(String selector) {
this.container = $(selector);
}
public DataTable shouldHaveRows(int count) {
container.$$("tbody tr").shouldHave(size(count));
return this;
}
public DataTable shouldContainRow(String... cellTexts) {
container.$$("tbody tr")
.findBy(text(cellTexts[0]))
.shouldHave(text(String.join("", cellTexts)));
return this;
}
public DataTable sortBy(String columnName) {
container.$$("th").findBy(text(columnName)).click();
return this;
}
}
// Usage
DataTable ordersTable = new DataTable("#orders-table");
ordersTable
.shouldHaveRows(10)
.shouldContainRow("Order #123", "Shipped")
.sortBy("Date");Page Factory (Annotation-Based)
Selenide supports Selenium's @FindBy annotation via PageFactory:
import org.openqa.selenium.support.FindBy;
import com.codeborne.selenide.SelenideElement;
public class ProductPage {
@FindBy(css = ".product-title")
private SelenideElement title;
@FindBy(css = "button.add-to-cart")
private SelenideElement addToCartButton;
@FindBy(css = ".cart-count")
private SelenideElement cartCount;
public ProductPage addToCart() {
addToCartButton.click();
return this;
}
public ProductPage shouldShowCartCount(int count) {
cartCount.shouldHave(text(String.valueOf(count)));
return this;
}
}Initialize with:
ProductPage page = page(ProductPage.class); // Selenide.page()Useful Utility Methods
// Execute JavaScript
import static com.codeborne.selenide.Selenide.executeJavaScript;
executeJavaScript("window.scrollTo(0, document.body.scrollHeight)");
executeJavaScript("arguments[0].click()", element.toWebElement());
// Hover
$(".dropdown-toggle").hover();
// Right-click
$(".file-item").contextClick();
// Double-click
$(".editable-cell").doubleClick();
// Drag and drop
$(".draggable").dragAndDropTo(".droptarget");
// Clear and type
$("input").clear();
$("input").sendKeys("new value");
// Select from dropdown
import static com.codeborne.selenide.Selenide.select;
$("select#country").selectOption("United States");
$("select#country").selectOptionByValue("US");These patterns handle the scenarios that trip up most UI test suites. Combined with Selenide's automatic waiting, they give you tests that are resilient to typical SPA rendering patterns.