Serenity Tricks with Java 8

John Ferguson Smart | Mentor | Author | Speaker - Author of 'BDD in Action'.
Helping teams deliver more valuable software sooner4th January 2017

serenity |

Java 8 came out back in 2014, but I still find many teams not making as much use of it's features as they could. Arguably the biggest new feature in Java 8 were Lambda Expressions, which finally brought a taste of functional programming to the Java world.

In this article, I want to give a simple example of how Java 8 and Lambda expressions can make your life easier, through a concrete example. Suppose we want to write a test for one of the few remaining applications that uses frames or iframes. The Java APIs are a good example. Automating pages with frames and iframes using WebDriver is tricky because, when you manipulate a web element, you first need to switch to the frame it is in, like this:

driver.switchTo().frame("MyFrame");
button.click();
driver.switchTo().defaultContent();

Let's see how we could make this nicer in Java 8. Imagine we need to write a test to check that when you select a package in the Packages list, the correct list of classes and interfaces appears in the frame to the lower left of the screen. We could write a very simple Serenity BDD test like this:

@RunWith(SerenityRunner.class)
public class WhenConsultingPackageDetails {

    @Managed WebDriver driver;

    JavaAPIDocs apiDocs;

    @Test
    public void should_be_able_to_view_the_classes_for_a_given_package() {

        // Given
        apiDocs.open();

        // When
        apiDocs.selectAPackage("java.applet");

        // Then
        assertThat(apiDocs.getClassesAndInterfaces())
                          .contains("AppletContext",
                                    "AppletStub",
                                    "AudioClip",
                                    "Applet");
    }
}

The JavaAPIDocs is a Serenity Page Object, that Serenity will initialise for us. It looks something like this.

@DefaultUrl("https://docs.oracle.com/javase/7/docs/api/")
public class JavaAPIDocs extends PageObject {

    @FindBy(tagName = "li")
    private List<WebElementFacade> packageNames;

    public JavaAPIDocs(WebDriver driver) {
        super(driver);
    }

    public void selectAPackage(final String packageName) {
        ...
    }

    public List<String> getClassesAndInterfaces() {
        ...
    }
}

Now suppose we want to implement the selectAPackage method. Using Java 7, we would need to first navigate to the frame, find the corresponding link to click on, then navigate back to the main window:

public void selectAPackage(final String packageName) {
    getDriver().switchTo().frame(("packageListFrame")
    find(By.linkText(packageName)).click();
    getDriver().switchTo().defaultContent();
}

In Java 8, on the other hand, we could create a class whose responsibility it is to switch too and from frames, and then just pass in the operation (as a Lambda expression) that we want to perform. Suppose we call this class InFrame. Our method would become something like this:

private InFrame inAFrame;

public JavaAPIDocs(WebDriver driver) {
    super(driver);
    inAFrame = new InFrame(driver);
}

public void selectAPackage(final String packageName) {
    inAFrame.called("packageListFrame")
            .attemptTo(() -> find(By.linkText(packageName)).click());
}

Notice how much more readable the selectAPackage() method just became? The magic happens in the InFrame class, which contains a method, attemptTo, whose job is to execute a lambda expression passed to it as a parameter:

public void attemptTo(UIPerformable performable) {
    driver.switchTo().frame(iframeNameOrId);
    performable.perform();
    driver.switchTo().defaultContent();
}

The UIPerformable is a simple functional interface that we can use to pass a Lambda Expression to the method:

@FunctionalInterface
public interface UIPerformable {
    void perform();
}

We can now use this method to execute a block of WebDriver code, as simple or as complex as we need. We can be sure that we will switch to the right frame before the operation starts, and switch back when we are done.

The getClassesAndInterfaces() method is similar:

public List<String> getClassesAndInterfaces() {
    return inAFrame.called("packageFrame").retrieve(() ->
            packageNames.stream()
                    .map(WebElement::getText)
                    .collect(Collectors.toList())
    );
}

Here, we use the retrieve() method, which takes a Supplier, one of the standard Java 8 functional interfaces. Like in the previous example, we switch to the frame, perform the operation, then switch back. Only this time, the operation returns a value:

public <T> T retrieve(Supplier<T> performable) {
    driver.switchTo().frame(iframeNameOrId);
    T result = performable.get();
    driver.switchTo().defaultContent();
    return result;
}

We also use Java 8 streams to convert the list of web elements returned by WebDriver to a list of Strings, another nice feature of Java 8.

The full InFrame class is shown here:

public class InFrame {
    private final WebDriver driver;

    public InFrame(WebDriver driver) {
        this.driver = driver;
    }

    public InstantiatedIFrameContainer called(String iframeNameOrId) {
        return new InstantiatedIFrameContainer(driver, iframeNameOrId);
    }

    public class InstantiatedIFrameContainer {
        private final WebDriver driver;
        private final String iframeNameOrId;

        public InstantiatedIFrameContainer(WebDriver driver, 
                                           String iframeNameOrId) {
            this.driver = driver;
            this.iframeNameOrId = iframeNameOrId;
        }

        public void attemptTo(UIPerformable performable) {
            driver.switchTo().frame(iframeNameOrId);
            performable.perform();
            driver.switchTo().defaultContent();
        }

        public <T> T retrieve(Supplier<T> performable) {
            driver.switchTo().frame(iframeNameOrId);
            T result = performable.get();
            driver.switchTo().defaultContent();
            return result;
        }
    }
}

This class took a little more effort to write initially, but in an application with a lot of frames, the effort would pay itself off very quickly through more readable and more concise Page Object and test code.

© 2019 John Ferguson Smart