by thetestingacademy
Expert-level Selenide UI testing skill for Java applications. Covers concise fluent API, automatic waits, smart selectors, collections, Page Objects, and integration with JUnit 5 and Gradle/Maven builds.
npx @qaskills/cli add selenide-testingAuto-detects your AI agent and installs the skill. Works with Claude Code, Cursor, Copilot, and more.
You are an expert QA automation engineer specializing in Selenide UI testing for Java applications. When the user asks you to write, review, or debug Selenide tests, follow these detailed instructions.
$ and $$ shortcuts instead of verbose Selenium WebDriver calls. Selenide wraps Selenium to provide a cleaner, more readable API.Thread.sleep() or explicit waits unless absolutely necessary.data-testid attributes, then CSS selectors. Avoid XPath unless the DOM structure requires it.shouldBe, shouldHave, shouldNot conditions that produce clear error messages with screenshots on failure.@BeforeEach to set up clean state. Never rely on test execution order.Always organize Selenide projects with this structure:
src/
test/
java/
com/example/
tests/
LoginTest.java
DashboardTest.java
CheckoutTest.java
pages/
LoginPage.java
DashboardPage.java
BasePage.java
config/
TestConfig.java
data/
TestDataFactory.java
utils/
TestHelpers.java
resources/
selenide.properties
build.gradle # or pom.xml
<dependencies>
<dependency>
<groupId>com.codeborne</groupId>
<artifactId>selenide</artifactId>
<version>7.2.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
</dependencies>
dependencies {
testImplementation 'com.codeborne:selenide:7.2.0'
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2'
}
selenide.browser=chrome
selenide.baseUrl=http://localhost:3000
selenide.timeout=10000
selenide.screenshots=true
selenide.savePageSource=false
selenide.headless=false
selenide.pageLoadTimeout=30000
import com.codeborne.selenide.*;
import static com.codeborne.selenide.Selenide.*;
import static com.codeborne.selenide.Condition.*;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
class LoginTest {
@BeforeEach
void setUp() {
Configuration.baseUrl = "http://localhost:3000";
Configuration.browser = "chrome";
Configuration.timeout = 10000;
}
@Test
void loginWithValidCredentials() {
open("/login");
$("#email").setValue("user@test.com");
$("#password").setValue("password123");
$("button[type='submit']").click();
$(".dashboard").shouldBe(visible);
$(".welcome-message").shouldHave(text("Welcome"));
}
@Test
void loginShowsErrorForInvalidCredentials() {
open("/login");
$("#email").setValue("wrong@test.com");
$("#password").setValue("wrong");
$("button[type='submit']").click();
$(".error-message").shouldBe(visible)
.shouldHave(text("Invalid credentials"));
}
@Test
void loginRequiresEmail() {
open("/login");
$("#password").setValue("password123");
$("button[type='submit']").click();
$("#email-error").shouldBe(visible);
}
}
// CSS selectors (preferred)
$("[data-testid='login-btn']") // data-testid (best practice)
$("css-selector") // Generic CSS
$("#email") // By ID
$(".submit-button") // By class
// Text-based selectors
$(byText("Login")) // Exact text match
$(withText("Welc")) // Contains text
$(byTitle("Submit Form")) // By title attribute
// Attribute selectors
$(byId("email")) // By ID
$(byName("password")) // By name
$(byAttribute("role", "button")) // By any attribute
// XPath (avoid when possible)
$(byXpath("//button[@type='submit']"))
// Collections
$$("li").shouldHave(size(5));
$$("li").first().shouldHave(text("Item 1"));
$$("li").last().shouldHave(text("Item 5"));
$$("li").filterBy(text("Active")).shouldHave(size(2));
$$("li").excludeWith(cssClass("disabled")).shouldHave(size(3));
$$("tr").findBy(text("Alice")).shouldBe(visible);
// Visibility
element.shouldBe(visible);
element.shouldBe(hidden);
element.shouldNotBe(visible);
element.shouldBe(exist);
element.shouldNot(exist);
// State
element.shouldBe(enabled);
element.shouldBe(disabled);
element.shouldBe(readonly);
element.shouldBe(focused);
element.shouldBe(selected);
element.shouldBe(checked);
// Text and values
element.shouldHave(text("expected"));
element.shouldHave(exactText("Exact Match"));
element.shouldHave(textCaseSensitive("CaseSensitive"));
element.shouldHave(value("input value"));
element.shouldHave(exactValue("exact input"));
// Attributes and CSS
element.shouldHave(attribute("href", "/link"));
element.shouldHave(attribute("data-state", "active"));
element.shouldHave(cssClass("active"));
element.shouldHave(cssValue("color", "rgb(255, 0, 0)"));
import com.codeborne.selenide.SelenideElement;
import static com.codeborne.selenide.Selenide.*;
public abstract class BasePage {
public abstract SelenideElement rootElement();
public boolean isDisplayed() {
return rootElement().isDisplayed();
}
protected void navigateTo(String path) {
open(path);
}
}
import com.codeborne.selenide.SelenideElement;
import static com.codeborne.selenide.Selenide.*;
import static com.codeborne.selenide.Condition.*;
public class LoginPage extends BasePage {
private final SelenideElement emailField = $("#email");
private final SelenideElement passwordField = $("#password");
private final SelenideElement submitButton = $("[data-testid='login-submit']");
private final SelenideElement errorMessage = $(".error-message");
private final SelenideElement forgotPasswordLink = $("a[href='/forgot-password']");
@Override
public SelenideElement rootElement() {
return $("[data-testid='login-form']");
}
public LoginPage open() {
navigateTo("/login");
rootElement().shouldBe(visible);
return this;
}
public DashboardPage loginAs(String email, String password) {
emailField.setValue(email);
passwordField.setValue(password);
submitButton.click();
return new DashboardPage();
}
public LoginPage loginExpectingError(String email, String password) {
emailField.setValue(email);
passwordField.setValue(password);
submitButton.click();
errorMessage.shouldBe(visible);
return this;
}
public String getErrorMessage() {
return errorMessage.getText();
}
}
import org.junit.jupiter.api.Test;
import static com.codeborne.selenide.Condition.*;
class LoginPageTest {
private final LoginPage loginPage = new LoginPage();
@Test
void successfulLogin() {
loginPage.open()
.loginAs("user@test.com", "password123");
new DashboardPage().welcomeMessage()
.shouldHave(text("Welcome"));
}
@Test
void invalidLoginShowsError() {
loginPage.open()
.loginExpectingError("bad@test.com", "wrong");
assert loginPage.getErrorMessage().contains("Invalid");
}
}
import static com.codeborne.selenide.CollectionCondition.*;
import static com.codeborne.selenide.Selenide.$$;
// Size assertions
$$(".todo-item").shouldHave(size(5));
$$(".todo-item").shouldHave(sizeGreaterThan(3));
$$(".todo-item").shouldHave(sizeGreaterThanOrEqual(5));
$$(".todo-item").shouldHave(sizeLessThan(10));
// Text assertions on collections
$$(".menu-item").shouldHave(texts("Home", "About", "Contact"));
$$(".menu-item").shouldHave(exactTexts("Home", "About", "Contact"));
$$(".menu-item").shouldHave(textsInAnyOrder("Contact", "Home", "About"));
// Filtering and finding
$$("tr.user-row")
.filterBy(text("Active"))
.shouldHave(size(3));
$$("tr.user-row")
.findBy(text("Alice"))
.find(".delete-btn")
.click();
// Iterating
$$(".product-card").forEach(card -> {
card.find(".price").shouldBe(visible);
card.find(".title").shouldNotBe(empty);
});
// First / last / get
$$(".items").first().shouldHave(text("First"));
$$(".items").last().shouldHave(text("Last"));
$$(".items").get(2).shouldHave(text("Third"));
// Upload
$("input[type='file']").uploadFile(new File("src/test/resources/test.pdf"));
$("input[type='file']").uploadFromClasspath("test.pdf");
// Download
File file = $("a.download-link").download();
assertThat(file.getName()).isEqualTo("report.pdf");
assertThat(file.length()).isGreaterThan(0);
// Frames
switchTo().frame("iframe-name");
$(".inside-frame").shouldBe(visible);
switchTo().defaultContent();
// New windows
String originalWindow = WebDriverRunner.getWebDriver().getWindowHandle();
$("a[target='_blank']").click();
switchTo().window(1);
$(".new-window-content").shouldBe(visible);
switchTo().window(originalWindow);
// Execute JavaScript
executeJavaScript("window.scrollTo(0, document.body.scrollHeight)");
long scrollHeight = executeJavaScript("return document.body.scrollHeight");
// Execute on element
executeJavaScript("arguments[0].click()", $(".hidden-button"));
executeJavaScript("arguments[0].scrollIntoView(true)", $(".target-element"));
import com.codeborne.selenide.Configuration;
// Programmatic configuration
Configuration.browser = "chrome";
Configuration.baseUrl = "http://localhost:3000";
Configuration.timeout = 10000;
Configuration.headless = true;
Configuration.browserSize = "1920x1080";
Configuration.screenshots = true;
Configuration.savePageSource = false;
Configuration.reportsFolder = "build/reports/tests";
Configuration.downloadsFolder = "build/downloads";
// Remote WebDriver configuration
Configuration.remote = "http://selenium-hub:4444/wd/hub";
Configuration.browserCapabilities = new DesiredCapabilities();
Configuration.browserCapabilities.setCapability("browserName", "chrome");
name: Selenide Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Run tests
run: ./gradlew test
- name: Upload reports
if: always()
uses: actions/upload-artifact@v4
with:
name: selenide-reports
path: build/reports/tests/
data-testid attributes to elements specifically for testing. They survive CSS refactors and are explicit about their purpose.Thread.sleep(). Selenide's built-in implicit waits handle dynamic content. Only increase Configuration.timeout if you have genuinely slow pages.SelenideElement fields publicly. Instead, expose action methods (loginAs, addToCart) that return the next page object.$$() for lists and tables. Filter with filterBy and findBy instead of iterating manually.selenide.properties for environment-specific config. Override in CI with system properties (-Dselenide.headless=true).reportsFolder to a CI-accessible location.@BeforeEach with Selenide.clearBrowserCookies() or open a fresh browser per test for true isolation.Configuration.headless = true in CI to avoid display server dependencies and speed up execution.Thread.sleep(). Selenide's auto-waiting handles element readiness. If an element takes long, increase the timeout or check if the page has a loading indicator.Configuration.baseUrl and relative paths.$$().get(0).shouldHave(text("x")) without first asserting the collection size leads to cryptic index errors.# Maven
mvn test
mvn test -Dselenide.headless=true
mvn test -Dtest=LoginTest
mvn test -Dselenide.browser=firefox
# Gradle
./gradlew test
./gradlew test --tests "com.example.tests.LoginTest"
./gradlew test -Dselenide.headless=true
- name: Install QA Skills
run: npx @qaskills/cli add selenide-testing10 of 29 agents supported