Skip to content

UI-licious vs Selenium ​

Selenium WebDriver has been the foundational open-source standard for browser automation for nearly two decades. Introduced in 2004, it established the W3C WebDriver protocol and enabled automated cross-browser testing across multiple programming languages—most predominantly in Java.

This guide compares UI-licious and Selenium WebDriver (Java), highlighting differences in script authoring, locator resilience, synchronization, and test maintenance.

1. User-Centric Selectors vs Fragile XPath & CSS ​

The Selenium Approach ​

Selenium interacts directly with the browser's Document Object Model (DOM). To target an element, engineers must inspect internal implementation details:

java
// Selenium WebDriver (Java)
driver.findElement(
    By.xpath("//div[@class='auth-form']//button[contains(@class, 'submit-btn-v2')]")
).click();

Because these locators depend on specific HTML tags, wrapper <div> elements, and CSS class names, tests break whenever:

  • A developer refactors styling (e.g. migrating to Tailwind CSS or styled-components).
  • Component markup is restructured or nested inside a new container.
  • Autogenerated or dynamic IDs change across production builds.

When this happens, Selenium throws an immediate NoSuchElementException.

Additionally, Selenium requires locators to match a single element unambiguously. If a selector matches multiple elements on the page, Selenium blindly selects the first DOM element (which may be hidden, disabled, or an unintended match) or throws an error when attempting to interact. Testers are forced to write overly complex, hyper-specific XPath or CSS queries (such as //div[@id='cart-modal']//div[contains(@class,'footer')]//button[1]) just to isolate a single node—making test scripts bloated, hard to read, and brittle to any layout changes.

The UI-licious Approach ​

UI-licious tests your application through the eyes of the user:

js
// UI-licious
I.click("Sign In")

UI-licious inspects what is visually rendered on the screen:

  • Visible button text, link labels, and headings.
  • Form <label> tags and aria-label / aria-labelledby attributes.
  • Input placeholders and hover tooltips.

When multiple elements share similar labels or text (such as several "Add to Cart" or "Edit" buttons), UI-licious doesn't throw an ambiguous selector error or force you to write convoluted DOM paths. Instead, it automatically resolves the ambiguity to the best candidate element based on factors such as:

  • Match precision: Prioritizing an exact text match (e.g. "Sign In") over broader text matches ("Sign in to view your dashboard").
  • Element semantics: Giving higher relevance to interactive elements (<button>, <a>, <input>) over generic containers (<div>, <span>).
  • Context & proximity: Prioritizing elements closer to recently interacted elements (see the Targeting Elements Guide).

Because UI-licious tests what the user sees and does, tests continue to pass even when internal CSS classes, DOM wrappers, or frontend frameworks change completely.

2. Built-In Visual Auto-Waiting vs Explicit WebDriverWait ​

One of the most persistent challenges in Selenium test automation is timing synchronization.

Selenium: Explicit Waits & Sleeps ​

Selenium commands execute immediately against the DOM. If a button takes 150ms to render due to an asynchronous API call or animation, Selenium throws NoSuchElementException or StaleElementReferenceException.

To prevent crashes, engineers must wrap nearly every interaction in explicit wait conditions:

java
// Selenium (Java): Cumbersome explicit wait boilerplates
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement button = wait.until(
    ExpectedConditions.elementToBeClickable(By.id("checkout-button"))
);
button.click();

When explicit waits become tedious, teams often resort to arbitrary sleeps (Thread.sleep(3000)), drastically slowing down test suites while still failing unpredictably under network jitter.

UI-licious: Smart Visual Auto-Waiting ​

In UI-licious, every command automatically waits:

js
// Automatically waits for the button to appear and become visible
I.click("Checkout")

UI-licious automatically polls the DOM and waits for candidate elements matching the target to appear and become visible on the page before executing the action.

No boilerplate WebDriverWait or arbitrary sleep statements are needed.

3. Transparent Shadow DOM Piercing vs Query Chaining ​

Modern web applications (such as Salesforce Lightning, Shopify, and custom Web Components) encapsulate their internal markup inside #shadow-root trees.

Selenium: Manual .getShadowRoot() Chaining ​

Standard Selenium locators (findElement, By.xpath, By.cssSelector) cannot penetrate shadow boundaries. To interact with an element inside a Web Component, you must manually locate the shadow host and chain shadow root queries:

java
// Selenium (Java): Chained shadow root traversal
WebElement host = driver.findElement(By.cssSelector("custom-card"));
SearchContext shadowRoot = host.getShadowRoot();
WebElement nestedHost = shadowRoot.findElement(By.cssSelector("custom-input"));
SearchContext nestedShadowRoot = nestedHost.getShadowRoot();
WebElement input = nestedShadowRoot.findElement(By.cssSelector("input"));
input.sendKeys("Acme Corp");

If the design system introduces an intermediate wrapper component, every chained locator in your test suite breaks. Furthermore, XPath expressions cannot be evaluated inside shadow roots at all.

UI-licious: Transparent Shadow Root Piercing ​

UI-licious automatically pierces open shadow roots recursively throughout the entire document tree:

js
// Works seamlessly across all shadow boundaries
I.fill("Company Name", "Acme Corp")

Whether the input field is standard HTML or nested deep inside multiple layers of Web Components, UI-licious finds it directly by its visible label.

4. Code Comparison: Java WebDriver vs UI-licious ​

Here is the same real-world login flow written in Selenium WebDriver (Java) vs UI-licious:

Selenium WebDriver (Java) ​

java
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;

public class LoginTest {
    public static void main(String[] args) {
        WebDriver driver = new ChromeDriver();
        try {
            driver.get("https://example.com/login");

            WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));

            WebElement userField = wait.until(
                ExpectedConditions.visibilityOfElementLocated(By.name("username"))
            );
            userField.sendKeys("[email protected]");

            WebElement passField = driver.findElement(By.name("password"));
            passField.sendKeys("SecretPass123!");

            WebElement loginBtn = wait.until(
                ExpectedConditions.elementToBeClickable(By.xpath("//button[@type='submit']"))
            );
            loginBtn.click();

            wait.until(
                ExpectedConditions.visibilityOfElementLocated(By.xpath("//h1[contains(text(),'Dashboard')]"))
            );
        } finally {
            driver.quit();
        }
    }
}

UI-licious ​

js
I.goTo("https://example.com/login")
I.fill("Username", "[email protected]")
I.fill("Password", "SecretPass123!")
I.click("Sign In")
I.see("Dashboard")

See Also ​