Introducing Arquillian Graphene Page Fragments

Tools like Arquillian Graphene, WebDriver, or just plain Selenium combined with concepts like Page Objects can help you automate your functional(integration, whatever…) web UI tests. We have already introduced some of the advances brought to you by Arquillian Graphene earlier. In this blog entry we will look a bit deeper into a new concept introduced in Arquillian Graphene 2.0.0.Alpha2; Page Fragments.

Consider the following example taken from the RichFaces component showcase. The page under test contain three Autocomplete widgets.

public class TestAutocompleteWidgets extends AbstractGrapheneTest {

    private JQueryLocator minCharInput = jq("input[type=text]:eq(0)");
    private JQueryLocator multipleSelectionInput = jq("input[type=text]:eq(1)");
    private JQueryLocator selectFirstFalseInput = jq("input[type=text]:eq(2)");
    private JQueryLocator selection = jq("div.rf-au-itm");

    @Test
    public void testFirstAutocomplete() {

        graphene.keyPress(minCharInput, 'a');

        assertFalse(graphene.isElementPresent(selection),
            "The suggestion list should not be visible, since there is only one char!");

        String keys = "ar";
        graphene.focus(minCharInput);
        selenium.type(minCharInput, keys);
        guardXhr(graphene).fireEvent(minCharInput, Event.KEYPRESS);

        assertTrue(graphene.isVisible(selection), 
            "The suggestion list should be visible, since there are two chars!");

        String actualArizona = graphene.getText(jq(selection.getRawLocator() + ":eq(0)"));
        assertEquals(actualArizona, "Arizona", "The provided suggestion should be Arizona!");

        String actualArkansas = graphene.getText(jq(selection.getRawLocator() + ":eq(1)"));
        assertEquals(actualArkansas, "Arkansas", "The provided suggestion should be Arkansas!");

    }

    @Test
    public void testSecondAutocomplete() {

        char key = 'a';
        selenium.focus(multipleSelectionInput);
        guardXhr(selenium).keyPress(multipleSelectionInput, key);

        assertTrue(selenium.isVisible(selection),
            "The suggestion list should be visible, since there is correct starting char!");

        selenium.keyPressNative(KeyEvent.VK_ENTER);

        key = ' ';
        selenium.keyPress(multipleSelectionInput, key);

        key = 'w';

        selenium.focus(multipleSelectionInput);
        guardXhr(selenium).keyPress(multipleSelectionInput, key);

        assertTrue(selenium.isVisible(selection),
            "The suggestion list should be visible, since there is correct starting char!");

        selenium.keyPressNative(KeyEvent.VK_ENTER);

        String actualContentOfInput = selenium.getValue(multipleSelectionInput);
        assertEquals(actualContentOfInput, "Alabama Washington", "The input should contain something else!");
    }

    @Test
    public void testThirdAutocomplete() {
        //similar autocomplete interactions as in the previous tests
    }
}

Now, ask yourself the following questions:

  • Do you find these types of tests less robust than for example unit tests for the persistence layer?
  • Do you think these tests still pass when the underlying HTML change?
  • Do you see repeating code in the these tests?

In my opinion you should have a clean answer to all of these questions. You are probably aware that tests should be loosely coupled with the underlying HTML structure of the application under test as it makes tests more robust and changes in the HTML structure of the page will not directly affect all tests.

Let’s apply the Page Objects pattern to split the HTML structure and the test logic. The Page Object will encapsulate the HTML structure so when the HTML change, only your Page Objects has to change.

  • But what about when I’m testing another application that use the same Autocomplete widget?
  • Should I copy the part of the Page Object that interact with the Autocomplete widget and paste it around in my code?

But as you’re already thinking: this would be a major don’t-repeat-yourself violation! Is there something we could do to improve this?

Yes, there is! Arquillian Graphene Page Fragments to the rescue!

Page Fragments, what are they?

  • Page Fragments are any repeating part of a page, any widget, web component, etc.
  • They encapsulate parts of the page into reusable test components across your whole test suite.
  • You can differentiate each fragment by its root element and reference other elements as children of that root.
  • They leverage Selenium WebDriver under the hood combined with all of the killer features of Graphene.
  • And they come with a set of utilities which simplify using them within tests and Page Objects.

How to define Page Fragments

public class AutocompleteFragment<T> {

    @Root
    WebElement root;

    @FindBy(css = "input[type='text']")
    WebElement inputToWrite;

    public static final String CLASS_NAME_SUGGESTION = "rf-au-itm";

    public List<Suggestion<T>> getAllSuggestions(SuggestionParser<T> parser) {
        List<Suggestion<T>> allSugg = new ArrayList<Suggestion<T>>();

        if (areSuggestionsAvailable()) {
            WebElement rightSuggList = getRightSuggestionList();
            List<WebElement> suggestions = rightSuggList.findElements(
                        By.className(CLASS_NAME_SUGGESTION));

            for (WebElement suggestion : suggestions) {
                allSugg.add(parser.parse(suggestion));
            }
        }

        return allSugg;
    }

    public List<Suggestion<T>> type(String value, SuggestionParser<T> parser) {
        List<Suggestion<T>> suggestions = new ArrayList<Suggestion<T>>();

        inputToWrite.sendKeys(value);
        try {
            waitForSuggestions();
        } catch (TimeoutException ex) {
            // no suggestions available
            return suggestions;
        }

        suggestions = getAllSuggestions(parser);
        return suggestions;
    }

    //other handy encapsulation of Autocomplete services
}

The example is just a snippet from the full implementation of the RichFaces Autocomplete component.

Notice the @Root annotation? The value of the root field is automatically injected by Graphene. The root field contain the root element as defined by the @FindBy annotation on the injection point of the Page Fragment in the Page Object or test. All @FindBy fields in the Page Fragment will use this root as a starting point.

The fragment implementation is pretty generic and therefore reusable in all tests in all applications that use the Autocomplete widget. A full implementation would encapsulate all the services this fragment provide, but it could for example also encapsulate browser specific interactions like submit or click actions. There are no boundaries!

Using Page Fragments and Page Objects together

Let’s rewrite the previous test to use our new Page Fragment together with our Page Object.

First we define a Page Object which contain the structure of the page under test.

public class TestPage {
    
    @FindBy(css = "div.rf-au:nth-of-type(1)")
    private AutocompleteFragment<String> autocomplete1;

    @FindBy(css = "div.rf-au:nth-of-type(2)")
    private AutocompleteFragment<String> autocomplete2;

    @FindBy(css = "div.rf-au:nth-of-type(3)")
    private AutocompleteFragment<String> autocomplete3;

    @FindBy(xpath = "//*[contains(@id,'sh1')]")
    private WebElement viewSourceLink;

    //all getters for the fields

    //other handy methods which you find useful when testing those three Autocomplete widgets
}

Declaring the Page Fragment in the Page Object is the preferred option, but you can also declare the Page Fragment directly in the test if desired. The only thing you need to do is to annotate the Page Fragment object with WebDriver’s @FindBy annotation to refer to the fragment’s root DOM element. That simple!

Graphene differentiates between Page Fragments and plain WebElements in the same Page Object so they can be declared side by side. All of the initialization is done automatically, so there is no need to initialize the Page Fragments or Page Objects in the @Before method of your test case.

In this last example we see how the autocomplete widgets’s Page Fragment is used in the test case.

public class TestWhichUsesPageFragments extend AbstractTest {

    @Page
    private TestPage testPage;

    @Test
    public void testFirstAutocomplete {
        AutocompleteFragment<String> autocomplete = testPage.getAutocomplete1();
        
        autocomplete.type("a");

        assertFalse(autocomplete.areSuggestionsAvailable(), 
            "The suggestion list should not be visible, since there is only one char!");

        String keys = "ar";
        autocomplete.type(keys);

        assertTrue(autocomplete.areSuggestionsAvailable(), 
            "The suggestion list should be visible, since there are two chars!");

        List<Suggestion<String>> expectedSuggestions = new ArrayList<Suggestion<String>>();
        expectedSuggestions.add(new Suggestion<String>("Arizona"));
        expectedSuggestions.add(new Suggestion<String>("Arkansas"));

        assertEquals(autocomplete.getAllSuggestions(new StringSuggestionParser()), expectedSuggestions, 
            "Suggestions are wrong!");
    }

    @Test
    public void testSecondAutocomplete() {
        AutocompleteFragment<String> autocomplete = testPage.getAutocomplete2();

        String key = "a";
        autocomplete.type(key);
        
        String errorMsg = "The suggestion list should be visible, since there was typed correct char ";
        assertTrue(autocomplete.areSuggestionsAvailable(), errorMsg + key);

        autocomplete.autocomplete(autocomplete.getFirstSuggestion());

        autocomplete.type(" ");

        key = "w"
        autocomplete.type(key);

        assertTrue(autocomplete.areSuggestionsAvailable(), errorMsg + key);

        autocomplete.autocomplete(autocomplete.getFirstSuggestion());        

        String actualContentOfInput = autocomplete.getInputValue();
        assertEquals(actualContentOfInput, "Alabama Washington", "The input should contain something else!");
    }

    @Test
    public void testThirdAutocomplete() {
        //similar autocomplete interactions as in the previous tests
    }
}

As your application grow, the only thing that needs to change is the root references of your Page Fragments. Last but not least you will be able to make your Page Fragment availabe to be used by other tests in other applications.

To try Page Fragments check out the Graphene 2.0.0.Alpha2 release. Getting start information can be found in the Reference Documentation.

The Danger of Embedded Containers

In the Arquillian project, we’re all about real tests. There’s a good reason for this philosophy. If you use mocks or a substitute container when testing code that uses a programming model (i.e., CDI), all you can be sure of is that you’ve faked out enough functionality to get the test to work. You can’t be certain that your code works—really, truly works.

Obviously, we provide adapters for embedded containers (i.e., substitute containers). The reason is, they often do a decent enough job of simultating the real environment that we are willing to make the trade-off for its variations when we are working in development. I would expect, though, that you are also running the same tests in CI using a real container…just to get that peace of mind.

How do you know when not to use an embedded container?

If you write a test that you are positive should work, but instead throws some wacky exception, immediately stop and try the same test in a real container. For example, if you are using Embedded GlassFish, I would advise running the test in managed or remote GlassFish. The latter two adapters both use an authentic GlassFish instance. If the test passes, then you have answered the question of this section.

When the embedded container begins to act inconsistently, drop it.

As with all things, there are exceptions to the rule. If the code really should work on the embedded container, then it may be worth exploring why it isn’t. That may lead you to the discovery of a bug in the library you’re using. If debugging the library is your main concern, chase it down. If you are just trying to get a test written for your application, stop using the embedded container (or save debugging it for a rainy day).

Examples

See this thread in the Seam 3 forums for an example where the Embedded GlassFish container was acting more of a hindrence than a help. The thread includes the steps I took to get a green bar using a different GlassFish container adapter.

Markus Eisele identifies another hazard. He points out in a recent blog post that testing JPA using Embedded GlassFish is problematic because JPA weaving (in EclipseLink) does not occur:

[Weaving] only works when the entity classes to be weaved only exist in the application classloader. The combination of Embedded GlassFish, Arquillian and the Maven Surefire Plugin mix this up a bit and the end of the story is, that exactly none of your entities are enhanced at all.

Again, testing on Embedded GlassFish is not serving us well in this scenario. Graduate.

Closing thoughts

Code that uses Solder works fine on GlassFish 3.1.2, but it does not play very nicely with Embedded GlassFish—it’s just a hostile environment for CDI because of the visibility issues that come with starting an embedded container inside an existing Java process. The same goes for JPA. You end up in a minefield of class visibility and access problems, and far from making any progress writing tests (your ultimate goal).

If you want the container to start automatically like with embedded, then switch to a managed container. It works almost exactly the same as the remote container, except that Arquillian will start the standalone process at the beginning of the test execution and stop it at the end. In other words:

managed >>>> embedded

It’s clean. It’s true.

For speed of development:

remote > managed

With remote, you don’t have to wait for the container to start…and you can start it in debug mode very easily and keep it there. That’s why I say that the best container adapters for development are the remote adapters and the best adapters for CI are the managed adapters. I personally don’t like the embedded container adapters much (with the exception of embedded CDI since that’s mostly a pure standalone CDI environment).

Keep that advice in mind.