Advanced Selenide Selectors and Page Objects

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.

Read more

Start now free