This section contains information about key FluentLenium features. If you wonder about advantages over pure Selenium this section is for you.

Table of Contents

Page Object Pattern support

Pages

Selenium tests can easily become a mess. To avoid this, you should use the Page Object Pattern. Page Object Pattern will enclose all the plumbing relating to how pages interact with each other and how the user interacts with the page, which makes tests a lot easier to read and to maintain.

Try to construct your Page thinking that it is better if you offer services from your page rather than just the internals of the page. A Page Object can model the whole page or just a part of it.

To construct a Page, extend FluentPage.

You can define the URL of the page by overriding the getUrl method. Then, it’s possible tu use the goTo() method in your test code to set the current URL to the URL of the page.

You can also override isAt method, to run all the assertions necessary in order to ensure that you are on the right page.

For example, if I choose that the title will be sufficient to know if I’m on the right page:

public class MyPage extends FluentPage {
    @Override
    public String getUrl() {
        return "/app/my-page";
    }

    @Override
    public void isAt() {
        assertThat(window().title()).contains("Selenium");
    }
}

Instead of manually implementing isAt and getUrl method, you can use Selenium @FindBy annotation on the page class, to define an Element Locator that should identify this page, and @PageUrl to define the URL used by goTo() method.


@PageUrl("/app/my-page")
@FindBy(css = "#my-page")
public class MyPage extends FluentPage {
    ...
}

If you are creating a page object class for something that doesn’t have URL, e.g. a component of a page, the class won’t have the @PageUrl annotation applied, thus also doesn’t need to extend FluentPage, but the @Find... annotated fields in it will be initialized regardless when injecting them using @Page. The following construct is also valid:

public class HeroImage {
    @FindBy(css = ".hero .img")
    public FluentWebElement heroImage;
}

PageUrl parameters

It’s possible to define parameters in FluentPage url using {[?][/path/]parameter} syntax. If it starts with ?, it means that the parameter is optional. Path can be included in the braces so it is removed when parameter value is not defined.


@PageUrl("/document/{document}{?/page/page}{?/format}")
public class DocumentPage extends FluentPage {
    public DocumentPage customPageMethod() {
            ...
        return this;
    }
        ...
}

Also when a page is open, one might want to extract certain parameter values from the url template defined in the @PageUrl annotation. One can do that by calling getParam(String) on a FluentPage object. It works only when the FluentPage object is annotated as `@PageUrl’.

So for a template like /document/{document}{?/page/page}{?/format} with an actual URL value /document/345/json one can query parameters like:

String document=documentPage.getParam("document");

The queried value will be 345 in this case.

If the provided parameter name is not defined in the template or it has no value in the actual URL, the returned value is null.

Local files

You can also refer to files in your test resources directory by specifying the file attribute with a file name:


@PageUrl(file = "page2url.html", value = "?param1={param1}&param2={param2}")
class Page2DynamicP2P1 extends FluentPage {
    @Override
    protected void isAtUsingUrl(String urlTemplate) {
        //overridden to skip URL check because PageUrl is not able to get local file path relatively
    }
}

In case you don’t specify the file attribute but you override either FluentPage#getUrl() or FluentPage#getUrl(Object...) in a way that it retrieves a local test resource, you need to also override FluentPage#isAtUsingUrl(String) and leave its body empty to skip URL check because PageUrl is not able to get local file path relatively.


@PageUrl(value = "?param1={param1}&param2={param2}")
class Page2DynamicP2P1 extends FluentPage {
    @Override
    protected String getUrl() {
        return someLocalResource;
    }

    @Override
    public void isAtUsingUrl(String urlTemplate) {
    }
}

Parameter values are given in order to isAt and go methods.

@Page
private DocumentPage page;

@Test
public void test(){
        page.go(267); // go to "/document/267"
        page.go(174,3); // go to "/document/174/page/3"
        page.go(124,1,"pdf"); // go to "/document/124/page/1/pdf"
        page.go(124,null,"html"); // go to "/document/124/html"
        page.isAt(174,3); // Assert that current url is "/document/174/page/3"
        }

In order to be able to chain or return Page class which extends FluentPage as a result of go() method you need to provide your FluentPage extending class name as follows.

The example above can be implemented as follows:

@Page
private DocumentPage page;

@Test
public void test(){
        page.<DocumentPage>go(267) // go to "/document/267"
        .<DocumentPage>go(174,3) // go to "/document/174/page/3"
        .<DocumentPage>go(124,1,"pdf") // go to "/document/124/page/1/pdf"
        .<DocumentPage>go(124,null,"html") // go to "/document/124/html"
        .customPageMethod() //do the custom actions
        .<DocumentPage>go(267); // go to "document/267"
        ...
        }

As a result the go method will return DocumentPage type instead of generic FluentPage

You can always override default go method in your DocumentPage class instead of typing <DocumentPage> for every single method call


@PageUrl("/document/{document}{?/page/page}{?/format}")
public class DocumentPage extends FluentPage {
    @Override
    DocumentPage go() {
        return super.go();
    }


    public DocumentPage customPageMethod() {
        ...
        return this;
    }
    ...
}

Create your own methods to easily fill out forms, go to another or whatever else may be needed in your test.

public class LoginPage extends FluentPage {
    public String getUrl() {
        return "myCustomUrl";
    }

    public void isAt() {
        assertThat(window().title()).isEqualTo("MyTitle");
    }

    public void fillAndSubmitForm(String... paramsOrdered) {
        $("input").fill().with(paramsOrdered);
        $("#create-button").click();
    }
}

And the corresponding test:

public void checkLoginFailed(){
        goTo(loginPage);
        loginPage.fillAndSubmitLoginForm("login","wrongPass");
        loginPage.isAt();
        }

Or using AssertJ.

public void checkLoginFailed(){
        goTo(loginPage);
        loginPage.fillAndSubmitLoginForm("login","wrongPass");
        assertThat($(".error")).hasSize(1);
        assertThat(loginPage).isAt();
        }

Page Injection

You can use the annotation @Page to construct your Page Objects easily.

public class AnnotationInitialization extends FluentTest {
    @Page
    private MyPage page;

    @Test
    public void test_no_exception() {
        goTo(page);
        //put your assertions here
    }
}

You can also use the factory method newInstance:

public class BeforeInitialization extends FluentTest {
    private MyPage page;

    @Before
    public void beforeTest() {
        page = newInstance(MyPage.class);
    }

    @Test
    public void test_no_exception() {
        page.go();
    }
}

Anyway, Page Objects constructors should never be called directly.

All FluentWebElement fields are automatically searched for by name or id. For example, if you declare a FluentWebElement named createButton, it will search the page for an element where id is createButton or name is createButton.

Keep in mind that all elements are Lazy Proxy Locators, they behave exactly like if they where found with $ method.

public class LoginPage extends FluentPage {
    private FluentWebElement createButton;

    public String getUrl() {
        return "myCustomUrl";
    }

    public void isAt() {
        assertThat(window().title()).isEqualTo("MyTitle");
    }

    public void fillAndSubmitForm(String... paramsOrdered) {
        $("input").fill().with(paramsOrdered);
        createButton.click();
    }
}

Not only FluentWebElement fields are populated. Every type with a constructor taking a WebElement is a candidate. This makes it possible for the page to expose fields with functional methods and not (only) the ‘technical’ methods that FluentWebElement exposes.

public class LoginPage extends FluentPage {
    private MyButton createButton;

    public void fillAndSubmitForm(String... paramsOrdered) {
        $("input").fill().with(paramsOrdered);
        createButton.clickTwice();
    }

    public static class MyButton {
        private WebElement webElement;

        public MyButton(WebElement webElement) {
            this.webElement = webElement;
        }

        public void clickTwice() {
            webElement.click();
            webElement.click();
        }
    }
}

If the naming conventions of your HTML ids and names don’t match with the naming conventions of your Java fields, or if you want to select an element with something other than the id or name, you can annotate the field with the Selenium @FindBy (or @FindBys) annotation. The following example shows how to find the create button if its CSS class is create-button.

public class LoginPage extends FluentPage {
    @FindBy(css = "button.create-button")
    private FluentWebElement createButton;

    public String getUrl() {
        return "myCustomUrl";
    }

    public void isAt() {
        assertThat(window().title()).isEqualTo("MyTitle");
    }

    public void fillAndSubmitForm(String... paramsOrdered) {
        $("input").fill().with(paramsOrdered);
        createButton.click();
    }
}

You can also refer to the list of FluentWebElements using FluentList dedicated interface, or java.util.List.

public class LoginPage extends FluentPage {
    @FindBy(css = "button.create-button")
    private FluentList<FluentWebElement> createButtons;

    public String getUrl() {
        return "myCustomUrl";
    }

    public void isAt() {
        assertThat(window().title()).isEqualTo("MyTitle");
        assertThat(buttons).hasSize(2);
    }

    public void fillAndSubmitForm(String... paramsOrdered) {
        $("input").fill().with(paramsOrdered);
        createButtons.get(1).click();
    }
}

Injection is recursive, and it’s possible to retrieve the parent container using @Parent annotation.

Labeling

Using the @Label and @LabelHint annotations you can define a custom toString() representation for FluentWebElements and FluentLists.

@Label can be added with or without a custom value. If a value is not defined, it defaults to empty value.

The purpose of @LabelHint is to provide additional information (kind of tags) to the toString() representation of the object that is annotated with it. It may be used with or without the @Label annotation, they don’t depend on each other.

Below you can find some examples of what labeling combinations will result in what toString() values.

Label

If no label value is provided, it will use the class name and field name as the label.

FluentWebElement + Label without value

public class Homepage extends FluentPage {
    @FindBy(css = ".hero img")
    @Label
    private FluentWebElement heroImage;
}

will give the following toString: Homepage.heroImage (Lazy Element)

FluentList + Label without value

@FindBy(css = "img")
@Label("pictures")
private FluentList<FluentWebElement> images;

will give the following toString: pictures ([])

LabelHint

If the label value is defined, the toString value will being with the value, and by specifying label hints, they will be list after the label value enclosed with [ and ].

Label + single label hint

@FindBy(css = ".teaser img")
@Label("teaser")
@LabelHint("img")
private FluentWebElement teaserImage;

will give the following toString: teaser [img] (Lazy Element)

Label + multiple label hints

@FindBy(css = ".banner img")
@Label("banner")
@LabelHint({"ad", "img"})
private FluentList<FluentWebElement> bannerImages;

will give the following toString: banner [ad, img] ([])

Components

A Component is an object wrapping a WebElement instance.

A Component supports injection like a Page Object, but all searches are performed in the local context of the wrapped element.

Using components improves readability of both Page Objects and Tests.

FluentWebElement is the default component class in FluentLenium, so you can implement your own custom component by extending FluentWebElement to add custom logic.

public class SelectComponent extends FluentWebElement {
    public SelectComponent(WebElement webElement, WebDriver driver, ComponentInstantiator instantiator) {
        super(webElement, driver, instantiator);
    }

    public void doSelect(String selection) {
        // Implement selection provided by this component.
    }

    public String getSelection() {
        // Return the selected value as text.
    }
}

Components are created automatically by injection, or programmatically by calling as(Class<?> componentClass) method of FluentWebElement or FluentList.

SelectComponent comp=el("#some-select").as(SelectComponent.class);

        comp.doSelect("Value to select");
        assertThat(comp.getSelection()).isEquals("Value to select");

It’s not mandatory to extend FluentWebElement, but a constructor with at least WebElement parameter is required.

public class SelectComponent {
    private WebElement element;

    private SelectComponent(WebElement element) { // This constructor MUST exist ! But can be private.
        this.element = element;
    }
}

Simple & working Component example can be found in our dedicated GitHub section .

Lazy Fluent Locators

$ always returns Lazy Fluent Locators. Building a Locator doesn’t perform the search immediately. It will be searched later, just before it’s really needed to interact with the located elements. (ie: clicking, retrieving text value, send user input).

// Creates the Fluent Locator, but doesn't perform the search
FluentList<FluentWebElement> fluentElements=$(".fluent");

// Perform the search and call click() on found elements
        fluentElements.click();

// NoSuchElementException will be throw if no element is found.

You can control the state of those Lazy Fluent Locators.

// Check if the element is present in DOM (boolean)
$(".fluent").present();

// Force the underlying search if it's not already loaded
// Throws NoSuchElementException if not found
        $(".fluent").now();

// Forget the underlying search results.
// You can then reuse the same locator to perform the search again
        $(".fluent").reset();

// Check if the underlying element has been loaded (boolean)
        $(".fluent").loaded();

Extended awaits

Wait for an Ajax Element to be available

There are multiple ways to make your driver wait for the result of an asynchronous call. FluentLenium provides a rich and fluent API in order to help you to handle AJAX calls. If you want to wait for at most 2 seconds until the number of elements corresponding to the until criteria (here the class fluent) has the requested size:

await().atMost(2,TimeUnit.SECONDS).until($(".fluent")).size(3);

The default wait is 5 seconds.

You can also use after size() : greaterThan(int), lessThan(int), lessThanOrEqualTo(int) , greaterThanOrEqualTo(int) , equalTo(int), notEqualTo(int)

Many others conditions like size(3) are available, like present(), displayed(), enabled(), text(), id() , name().

You can use not() function to negate any condition.

If you need to be more precise, you can also use filters and matchers in the search:

await().atMost(2,TimeUnit.SECONDS).until($(".fluent",withText("myText"))).size(3);
        await().atMost(2,TimeUnit.SECONDS).until($(".fluent",withText().startsWith("start"))).present();

Just use startsWith, notStartsWith, endsWith, notEndsWith, contains, notContains, equalTo, containsWord.

If you need to filter on a custom attribute name, this syntax will help:

await().atMost(2,TimeUnit.SECONDS).until($(".fluent",with("myAttribute").startsWith("myValue"))).present();

You can also give instance of elements or list of elements if required.

@FindBy(css = ".button")
private FluentWebElement button;

        await().atMost(2,TimeUnit.SECONDS).until(element).enabled();

When running Java 8, you can use lambdas with until, untilPredicate, untilElement or untilElements.

await().atMost(2,TimeUnit.SECONDS).untilElement(()->el(".button").enabled());
        await().atMost(2,TimeUnit.SECONDS).untilElements(()->$(".button").each().enabled());
        await().atMost(2,TimeUnit.SECONDS).untilElements(()->$(".button").one().enabled());

        await().atMost(2,TimeUnit.SECONDS).untilPredicate((f)->el(".button").enabled());

Using Supplier as until argument will help you to write awaits which can work with dynamic pages.

await().atMost(2,TimeUnit.SECONDS).until(()->el(".button").enabled());
        await().until(()->$(".listItem").first().displayed());

You can also check if the page is loaded.

await().atMost(1,NANOSECONDS).untilPage().isLoaded();

If you want to wait until the page you want is the page that you are at, you can use:

await().atMost(2,TimeUnit.SECONDS).untilPage(myPage).isAt();

You can override await() method in your own test class to define default settings for wait objects:

@Override
public FluentWait await(){
        return super.await().atMost(2,TimeUnit.SECONDS).ignoring(NoSuchElementException.class,ElementNotVisibleException.class);
        }

Polling Every

You can also define the polling frequency, for example, if you want to poll every 5 seconds:

await().pollingEvery(5,TimeUnit.SECONDS).until($(".fluent",with("myAttribute").startsWith("myValue"))).present();

The default value is 500ms.

You can also chain filter in the asynchronous API:

await().atMost(2,TimeUnit.SECONDS).until($(".fluent",with("myAttribute").startsWith("myValue"),with("a second attribute").equalTo("my@ndValue"))).present();

@Wait Hook

Wait hook automatically waits for conditions to be applied before interacting with Elements, avoiding the need of writing technical waiting and condition code in tests and page objects.

Shadow root

Shadow DOM/Shadow root definition

 public class HomepageTest extends FluentPage {
    @Unshadow(css = {"outer-shadow-root", "inner-shadow-root", ".element"})
    FluentWebElement element;
    //Element is instantiated and can be used by test methods.
}

It can also handle List and Set collections if many objects are found

public class HomepageTest extends FluentPage {
    @Unshadow(css = {"outer-shadow-root", "inner-shadow-root", "div"})
    List<FluentWebElement> elementsList;
    @Unshadow(css = {"outer-shadow-root", "inner-shadow-root", "span"})
    Set<FluentWebElement> elementsSet;
    //elementsList and elementsSet are instantiated and can be used by test methods.
}

It is an experimental feature and works only on annotation level for fields