Monday, June 2, 2014

table manners for selenium capsules

Here is a simple table, http://www.w3schools.com/html/html_tables.asp


Let us see how Selenium Capsules can help to read the content of it.



The Table class only has two methods now, getHeader and getRows, Stream is a new collection type appears in Java 8.

And here is the test to read the table,


You can see, the table supports type, it will return the type in a Stream. For this table, we define a simple domain class called Person,

    class Person {
        private final String firstName;
        private final String lastName;
        private final int points;

        Person(String firstName, String lastName, int points) {

            this.firstName = firstName;
            this.lastName = lastName;
            this.points = points;
        }

        @Override
        public String toString() {
            return firstName + "|" + lastName + "|" + points;
        }
    }


When we construct the Table, we need to pass a Locator to locate the table and a Mapper to map the contents in a row to the domain class,

        Locator<AbstractPage, Element> locator = Locators.<AbstractPage>element(MAIN).and(element(TABLE));

        Locator<Stream<Element>, Person> mapper = (stream) -> {
            Iterator<String> iterator = stream.map(TEXT).iterator();
            return new Person(iterator.next(), iterator.next(), PARSE_INT.locate(iterator.next()));
        };


The mapper will read all the TD tags in the TR tags and call the new Person for each TR tag.
<tr>
  <td>Jill</td>
  <td>Smith</td> 
  <td>50</td>
</tr>
            return new Person("Jill", "Smith", 50);


it prints this when you run it

Firstname|Lastname|Points|
Jill|Smith|50
Eve|Jackson|94
John|Doe|80
Adam|Johnson|67
The following code is called Method Reference in Java 8.

        table.getRows().forEach(
                System.out::println
        );


It has good table manners.

Saturday, May 31, 2014

decorating selenium

On Selenium HQ home page, selenium is defined as following,

Selenium automates browsers. That's it! What you do with that power is entirely up to you. Primarily, it is for automating web applications for testing purposes, but is certainly not limited to just that. Boring web-based administration tasks can (and should!) also be automated as well. Selenium has the support of some of the largest browser vendors who have taken (or are taking) steps to make Selenium a native part of their browser. It is also the core technology in countless other browser automation tools, APIs and frameworks.

Even since Selenium came into existence, it has been adopted by many projects as test automation library. Due to its open source nature, it became a preferred test tool in many organizations.

Selenium is not without shortcomings, it is a library, not a framework, so it doesn't provide mechanisms to reduce duplication. To write clean tests, it is necessary to add a level of abstraction on top of Selenium to form an enterprise framework. Selenium Capsules, is such an enterprise framework allowing developers to maximize theirs productivity by offering services to relieve developers from worrying about many common concerns such waiting, exception handling and logging. It also provides reference implementation to teach developers how to organize software artifacts. Behind the scene, this Selenium Capsules framework, is just an application of Decorator Pattern.



Let us have a look of the WebDriver interface,



WebDriver is an interface, it has many implementation classes such as FirefoxDriver, ChromeDriver, etc. When we use its method findElement(By), we may run into situation when the element is loaded by an Ajax request and we need to give instruction to the test to wait for a while until the element is displayed before calling it, otherwise, it would throw a NoSuchElementException. All these bullet plate codes will make the test code verbose and more difficult to find useful information. Thus it is desirable to attach additional logic to save developers from repeating this condition check and exception handling. However, since there are so many browsers implementation classes, it is not practical to extend all of them to add this waiting mechanism. Even if it is possible to extend those classes to add this override method, this method will be repeated in all of those concrete driver classes.



Decorator Pattern, a 20 years old design pattern by 2014, effectively solved our problem.

Another great tool is the default method in Java 8. In Java 8, some new methods are added to existing interface. As Java developers, we all know if a method is added to an interface, all implementing class need to be modified to add that new method, otherwise, it is a compilation error. That's why JDK interfaces have never changed. It is not the case any more. In Java 8, a new language feature is added to make it is possible to add new method into an existing interface, it is default method.

Default methods are methods with implementations in the interface. You may wonder, will this change make an interface an abstract class? No, Interface is still not an abstract class, even interface can have default methods, but it can't have instance variables, so the default method can only call method of a local variable, or methods from the same interface, thus interfaces still can't replace abstract classes since abstract classes can have instance variables.

An interface Browser, which extends WebDriver, is introduced into the framework to decorate WebDriver with additional responsibilities and it also provide default implementation of all the methods from WebDriver by just delegating the calls to WebDriver.



What's the reason to spend so much effort to decorate WebDriver? Many people may have this doubt at the beginning. The reason is simple, to attach additional responsibility to address some common concerns. This method in Browser interface takes advantage of some Java 8 syntax and transforms all WebElements it finds into Element classes, also its parameter is Supplier, not a By class like the original findElements method in the WebDriver, this feature is called method overloading in Object Oriented languages,
    default public Stream<Element> findElements(Supplier<By> by) {
        return findElements(by.get()).stream().map(Element::new);
    }


Stream, Supplier and Element::new are Java 8 features which is covered in this blog, functional selenium.

Now that we decorated WebDriver to return Element object which is a decorator of WebElement, let us compare it the raw WebElement object to see what's the advantages it gives. Here are the methods of WebElement,



We need to do the same thing as the decorator to WebDriver, to add new methods to the extended interface to have the following method,

    default public Stream<Element> findElements(Supplier<By> by) {
        return findElements(by.get()).stream().map(Element::new);
    }


Ultimately, we decorated SearchContext and added many new search methods to locate popular web widgets,by the assistance from default methods from Java 8, all those new methods are available to the implementation classes without extra effort for implementing them.

public interface Searchable<Where extends Searchable<Where>> extends SearchContext, Waitable<Where> {

    /**
     * Find the first element or throw NoSuchElementException
     *
     * @param by selector
     * @return the first element or throw NoSuchElementException
     */
    @Override
    Element findElement(By by);

    /**
     * Find the first element or return null if nothing found.
     *
     * @param by selector
     * @return the first element or return null if nothing found.
     */
    default public Element tryElement(By by) {
        try {
            return new Element(findElement(by));
        } catch (NoSuchElementException e) {
            return null;
        }
    }

    /**
     * Find the first element or throw NoSuchElementException
     *
     * @param by selector
     * @return the first element or throw NoSuchElementException
     */
    default public Element untilFound(final By by) {
        return until((Where page) -> new Element(findElement(by)));
    }

    /**
     * Find all elements within the area using the given search method.
     *
     * @param by selector
     * @return A stream of all {@link Element}s, or an empty stream if nothing matches.
     * @see org.openqa.selenium.By
     */
    default public Stream<Element> findElements(Supplier<By> by) {
        return findElements(by.get()).stream().map(Element::new);
    }

    /**
     * Find the first button meeting the By method.
     * method to find the button.
     *
     * @param by selector
     * @return
     */
    default public Clickable button(Supplier<By> by) {
        return button(by, 0);
    }

    /**
     * If there are multiple buttons with the same name on the same page, use this
     * method to find the button.
     *
     * @param by    selector
     * @param index
     * @return
     */
    @SuppressWarnings("unchecked")
    default public Clickable button(Supplier<By> by, int index) {
        return new Button<>((Where) this, Locators.<Where>elements(by)
                .and(new StreamToList<>())
                .and(new ElementAtIndex<>(index)));
    }

    /**
     * If the button can't be found using the previous two methods, use this.
     *
     * @param locator
     * @return
     */
    @SuppressWarnings("unchecked")
    default public Clickable button(Locator<Where, Element> locator) {
        return new Button<>((Where) this, locator);
    }

    /**
     * The first image using the image file.
     *
     * @param fileName
     * @return
     */
    default public Element image(String fileName) {
        return new FirstItem<Element>().locate(images(fileName));
    }

    /**
     * The image at the given index using the same image file.
     *
     * @param fileName
     * @param index
     * @return
     */
    default public Element image(String fileName, int index) {
        return new StreamToList<Element>()
                .and(new ElementAtIndex<>(index))
                .locate(images(fileName));
    }

    /**
     * Find the images using the same image file.
     *
     * @param fileName
     * @return the images  using the same image file.
     */
    default public Stream<Element> images(String fileName) {
        return until(Locators.<Where>elements(IMG)
                        .and(new Filter<>(DISPLAYED.and(SRC.and(new StringContains(fileName)))))
        );
    }

    /**
     * Find the link using the selector.
     *
     * @param selector
     * @return
     */
    @SuppressWarnings("unchecked")
    default public Clickable link(Supplier<By> selector) {
        return new Link<>((Where) this, element(selector));
    }
}



The new method tryElement will return null if it can't find the element immediately, and untilFound method will wait until timeout so either returns the element it is looking for or throw an NoSuchElementException. These new methods make it easier for testing AJAX enabled web applications which requires the extensive usages of try and wait.

    /**
     * Find the first element or return null if nothing found.
     *
     * @param by selector
     * @return the first element or return null if nothing found.
     */
    default public Element tryElement(By by) {
        try {
            return new Element(findElement(by));
        } catch (NoSuchElementException e) {
            return null;
        }
    }

    /**
     * Find the first element or throw NoSuchElementException
     *
     * @param by selector
     * @return the first element or throw NoSuchElementException
     */
    default public Element untilFound(final By by) {
        return until((Where page) -> new Element(findElement(by)));
    }


The untilFound method effectively added support for Explicit Waits without cluttering your tests with millions lines of code like this,

   // ☹ this is a bad example, please don't follow the style.
   FluentWait<By> fluentWait = new FluentWait<By>(By.tagName("TEXTAREA"));  \\ define element for which you want to poll
   fluentWait.pollingEvery(300, TimeUnit.MILLISECONDS); \\ it will ping for every 3 sec
   fluentWait.withTimeout(1000, TimeUnit.MILLISECONDS);  \\ max time out
   fluentWait.until(new Predicate<By>() {
      public boolean apply(By by) {
         try {
           return browser.findElement(by).isDisplayed();
         } catch (NoSuchElementException ex) {
           return false;
         }
      }
   });
   browser.findElement(By.tagName("TEXTAREA")).sendKeys("text to enter");
You can use call this method to enter data into a text input field on form,

   page.put(() -> By.tagName("TEXTAREA"), "text to enter");
Element is an implementation class of both WebElement and Searchable interfaces, thus, it can be used as source and target elements of a drag and drop action.

    @Test
    public void testDragAndDrop() {
        Element source = CHROME.findElement(By.name("source"));
        Element target = CHROME.findElement(By.name("target"));
        Actions actions = new Actions(CHROME);
        actions.dragAndDrop(source, target);
    }
It is cleaner if you use this method, it has the same effect as the one above.

    @Test
    public void testDragAndDrop() {
        CHROME.dragAndDrop(id("drag1"), id("div2"));
    }


As a comparison, here is the code without using framework,

    //This is an ugly test not using page framework, it has the same function as the test above. :(
    @Test
    public void dragAndDropChrome() throws InterruptedException {
        System.setProperty("webdriver.chrome.driver", "src/main/resources/chrome/chromedriver");
        WebDriver webDriver = new ChromeDriver();
        webDriver.get("http://www.w3schools.com/html/html5_draganddrop.asp");
        WebElement source = webDriver.findElement(id("drag1"));
        System.out.println(source.getAttribute("src"));
        WebElement target = webDriver.findElement(id("div2"));
        System.out.println(target.getTagName() + "=" + target.toString());

        Actions builder = new Actions(webDriver);
        Action dragAndDrop = builder.clickAndHold(source)
                .moveToElement(target)
                .release(source)
                .build();
        dragAndDrop.perform();
    }



From these examples, it is clear that the tests written using Selenium Capsules framework are much cleaner than the tests written using Selenium directly. This is the reason to decorate Selenium and the sole purpose of Selenium Capsules, to encapsulate selenium powder into clean capsules and make it easy to serve.

Thursday, May 29, 2014

all roads lead to rome - options to locate element and elements

Selenium provides an interface to locate an element or elements on web page, SearchContext, both WebDriver and WebElement implement this inerface.

It has two methods,
  • findElement(By by)
  • findElements(By by)
  • indElement(By by) will return the first element meeting the search criteria or throw a NoSuchElementException. The findElements(By by) will return a list of elements meeting the criteria which can be empty. The parameter By has many subclasses giving users many options to use, they are,

  • By.ByClassName
  • By.ByCssSelector
  • By.ById
  • By.ByLinkText
  • By.ByName
  • By.ByPartialLinkText
  • By.ByTagName
  • By.ByXPath


  • These classes are not meant to be instantiated directly by users, users are required to use the correspondent static factory method to create them, for example, By.id(String id) will return a By.ById

    In my opinion, The By class in these methods is the best design strategy in Selenium WebDriver framework, it allows users to only one method to achieve many variations of the searching method. Without this By parameter, it would need The following methods,

  • findElementById(String id)
  • findElementsById(String id)
  • findElementByCssSelector(String css)
  • findElementsByCssSelector(String css)
  • findElementByClassName(String css)
  • findElementsByClassName(String css)
  • findElementByLinkText(String css)
  • findElementsByLinkText(String css)
  • findElementByName(String css)
  • findElementsByName(String css)
  • findElementByPartialLinkText(String css)
  • findElementsByPartialLinkText(String css)
  • findElementByTagName(String css)
  • findElementsByTagName(String css)
  • findElementByXPath(String css)
  • findElementsByXPath(String css)


  • Luckily, they didn't take this route.

    Joshua Block advised the same principle in his book, Effective Java, Avoid Strings when other types are more appropriate, please refer to Page 224 of Effective Java, Second Edition for more information.

    • Method Summary

      Methods 
      Modifier and Type Method and Description
      static By className(java.lang.String className)
      Finds elements based on the value of the "class" attribute.
      static By cssSelector(java.lang.String selector)
      Finds elements via the driver's underlying W3 Selector engine.
      static By id(java.lang.String id) 
      static By linkText(java.lang.String linkText) 
      static By name(java.lang.String name) 
      static By partialLinkText(java.lang.String linkText) 
      static static By tagName(java.lang.String name) 
      static By xpath(java.lang.String xpathExpression) 
      WebElement findElement(SearchContext context)
      Find a single element.
      abstract java.util.List<WebElement> findElements(SearchContext context)
      Find many elements.


    If there are too many choices, people will be overwhelmed with the options. A lot of people often wonder which By class they should use.

    There are some rule of thumb,
    ByName
  • If there is unique name for the input, use it, so it won't matter whether there is an id defined for that element, this is because inputs are meant to be sent back to server and the interface between browser is the name, so most input fields will have names. However,web servers don't require input fields have the unique names, so you may encounter some situation where multiple inputs sharing the same name. In that case, findElement will only return the first element. To locate the elements with the same name, findElements method can collect them into a List object, then other algorithms can be used to locate the element from the list.
  • ById
  • For non input elements, if there is an id associated with it, use the id, it makes the task easier,
  • For links, both linkText and partialLinkText can be used, but partialLinkText may return other links containing the partial link text
  • ByClassName
  • ClassName and TagName are not suitable for locating single element, they are used for locating a group of elements and since the search results are broader than other selectors, it may result in lower performance, however, I don't have benchmark data for this suspicion.
  • ByCssSelector and ByXpath
  • cssSelector and xpath are designed for advanced users, they require more knowledge on CSS and xpath of the XML DOM. Here are some links you can use for reference,




  • Let us practise on this field.

    <label for="shipping-zip" id="labelshipping-zip" class="text"><strong>Zip Code:&tl;/strong>
    <input type="text" class="text" name="shippingAddressDS.shipping_ROW0_zip" id="shipping-zip" size="10" maxlength="99" value=""  />
    </label>
    
    ByName
    // ☹ this is a bad example, please don't follow the style.
    webDriver.findElement(By.name("shippingAddressDS.shipping_ROW0_zip"));
    
    ById
    // ☹ this is a bad example, please don't follow the style.
    webDriver.findElement(By.id("shipping-zip"));
    
    ByCssSelector
    // ☹ this is a bad example, please don't follow the style.
    webDriver.findElement(By.cssSelector("input[id='shipping-zip'"));
    
    ByXpath
    // ☹ this is a bad example, please don't follow the style.
    webDriver.findElement(By.xpath("//input[@id='shipping-zip'"));
    
    You can see there are many different ways to locate the same element.

    These are the ways to locate the element, but they are not the ways that they should be used in the test automation projects, for example, when filling out a form and click a button, it usually need to locate multiple elements and call the sendKeys method on each of them, such as,

    // ☹ this is a bad example, please don't follow the style.
    try {
       WelElement webElement = webDriver.findElement(By.name("shippingAddressDS.shipping_ROW0_zip"));
       webElement.clear();
       webElement.sendKeys(zipCode);
    } catch(NoSuchElementException e) {
       handle(e);
    }
    
    This is just the code for one input field, if you are doing this way for all input fields on the form, your tests will be full of code.

    There is a better way than using Selenium directly, here is an example,



    So just call,
    //   ☺ This is a good example.
            put(BILLING_ZIP_______, address.zipcode);
    

    All Roads Lead to Rome, but there exists a shortest one.

    Sunday, May 25, 2014

    autocomplete

    Let us implement the autocomplete functional test for Google search page, when you type, o,r,a,c,l,e, it popup some matching words or phrases in the flyout area and you can select the one you are looking for.

    There are 5 actions in autocomplete,
  • Find the input box
  • Send keys one by one
  • Try to find the suggestion you are looking for in the suggestion area
  • Click the suggested item
  • Explicitly wait for it

  • The input field for us to enter criteria is named "q", so you can use By.name("q") to find it and type the characters of Oracle one by one into it and wait for "Oracle" appears in the selection area and click it.



    It is difficult to locate the suggestion items since there is no obvious selection criteria to find them.



    You can use Firebug to find its absolute xpath, to get some idea but it can be used in the tests since the structure of the page may change and it will not work,

    /html/body/table/tbody/tr/td[2]/table/tbody/tr/td/div/table/tbody/tr/td/span
    


    If you are not familiar with xpath, you can use complex search to find a container first and from the container, find the element. This autocomplete suggestion is inside a table with class "gssb_c", so we can findElement of the table first, and the findElements of all the span elements inside that table and find the one with the text "oracle" you try to type in,

    // ☹ this is a bad example, please don't follow the style.
    try {
       WebElement table = webDriver.findElement(By.className("gssb_c"));
       List<WebElement> spans = table.findElements(By.tagName("span"));
       WebElement oracle = find(spans, 
               new Predicate<WebElement>() {
                  @Override
                  public boolean apply(WebElement span) {
                     return span.getText().equals("oracle");
                  }
               });
       oracle.click();
    } catch (NoSuchElementException e) {
       e.printStackTrace();
    }
    


    This is not the complete logic, it is just the part to find the "oracle" from the suggestion list, it is already very verbose.

    Selenium capsules, a new Selenium framework, uses a locator pattern to abstract the element locating logic to make the test code cleaner, in the autoCompleteUsingLocator test from following code block.

    Note, () -> className("gssb_c") is a lambda expression introduced in Java 8.

     //   ☺ This is a good example.
     new ElementTryLocator<AbstractPage>(() -> className("gssb_c"))
         .and(new ElementsLocator<>(SPAN))
         .and(new FirstMatch<>(TEXT.and(new IsStringEqual("oracle")))));
    


    It is a little bit cleaner, but still very complex.





    After rewriting the search using By xpath, it only has one findElement method call, which is much better than the navigational locating strategy, so you can greatly simplify the test if you know how to use xpath.

    // ☹ this is a bad example, please don't follow the style.
    try {
       WebElement oracle = webDriver.findElement(
           By.xpath("//table[contains(concat(' ', @class, ' '), 'gssb_c')]/descendant::span[text()='oracle']");
       oracle.click();
    } catch (NoSuchElementException e) {
       e.printStackTrace();
    }
    


    But it is still not clean if the By.xpath call appears multiple times in different tests,

       By.xpath("//table[contains(concat(' ', @class, ' '), 'gssb_c')]/descendant::span[text()='oracle']");
    


    By using Selenium Capsules framework, you can actually put this xpath string literal inside an enum and use the enum in the tests,

    //   ☺ This is a good example.
    import org.openqa.selenium.By;
    
    import java.util.function.Supplier;
    
    import static org.openqa.selenium.By.xpath;
    
    public enum Xpath implements Supplier<By> {
    
        DIV_CONTAINER_ID("//div[@id='container']"),
        ORACLE_AUTOCOMPLETE("//table[contains(concat(' ', @class, ' '), 'gssb_c')]/descendant::span[text()='oracle']"),
        QUANTITY("//div[@id='ys_cartInfo']/descendant::input[@name='cartDS.shoppingcart_ROW0_m_orderItemVector_ROW0_m_quantity']");
    
        private final By by;
    
        private Xpath(String id) {
            this.by = xpath(id);
        }
    
        @Override
        public By get() {
            return by;
        }
    
        @Override
        public String toString() {
            return by.toString();
        }
    }
    
    


    Now the test can be rewritten as simple as one line of code,

        //   ☺ This is a good example.
        @Test
        public void autoCompleteUsingXpath() {
            googlePage.autocomplete(Q, "oracle", new ElementTryLocator<>(ORACLE_AUTOCOMPLETE));
        }
    


    Even you don't want to use a framework, putting locating strategy in enum can still simplify code, unlike the above code, you can just use ORACLE_AUTOCOMPLETE as a parameter, you need to call get() method to get a By object, but it is still cleaner than using xpath string literals directly in the code,

    
    //   ☺ This is an OK example.
    
    try {
       WebElement oracle = webDriver.findElement(ORACLE_AUTOCOMPLETE.get());
       oracle.click();
    } catch (NoSuchElementException e) {
       e.printStackTrace();
    }
    

    versus original,
    // ☹ this is a bad example, please don't follow the style.
    try {
       WebElement oracle = webDriver.findElement(
           By.xpath("//table[contains(concat(' ', @class, ' '), 'gssb_c')]/descendant::span[text()='oracle']");
       oracle.click();
    } catch (NoSuchElementException e) {
       e.printStackTrace();
    }
    

    Now let us have a comparison between the tests written with and without using framework,

    1. autoCompeleteUsingSelenium doesn't use framework, it uses Selenium directly.
    2. autoCompleteUsingXpath uses framework,



    Cohesively, all By selectors can be organized into one package, selectors to encourage people reuse existing definitions, the name of that package should be bysuppliers, I found out selectors is a more meaningful name since that's the purpose of those classes.

    By Selector Suppliers and all its member classes are enum types implementing Supplier interface to provide a by selector for Selenium WebDriver or WebElement, this is explained in this blog entry, functional selenium

    Saturday, May 24, 2014

    functional selenium

    Java 8 incorporated part of Google Guava library and provides nice support for functional programming, I found it extremely helpful when working with Selenium.

    These is a new class in Java 8 called Supplier, it only has one method get() which will return the type you specified.

    Then I used enum or organize the same By type into enum which implements Supplier,

    0. By xpath

    1. By cssSelector



    2. By tagName



    I should complete this TagName class with all HTML tags.

    3. By name



    4. By Others



    and a Locator to locate one or a list of elements,



    and a helper method to use ElementsLocator to find a Stream of Element meeting the criteria. ElementsLocator extends AbstractLocator which implements Function interface from Java 8.

    Function is too generic so I extend it into another interface called Locator which is more suitable concept for Selenium tests, locating something from somewhere,



    and use it in another page object to locate the Button,



    This code snippet is to combine 3 functions into one,



    Use the Supplier enum constants to locate elements on the form or page and either enter text or click it,

    With static imports,



    or with full class qualifier,





    put(QUANTITY, 2) is to type the text "2" into an input field QUANTITY which is an enum constant in Name class to specify

    By.name("cartDS.shoppingcart_ROW0_m_orderItemVector_ROW0_m_quantity") 
    


    or another enum constant in Xpath class,
    By.xpath("//div[@id='ys_cartInfo']/descendant::input[@name='cartDS.shoppingcart_ROW0_m_orderItemVector_ROW0_m_quantity']");
    


    It doesn't care which selector you are using, as long as it implements Supplier interface so the framework can get a By selector from the parameter, if it is from Name, it is a ByName selector, if it is from Xpath, it is a ByXpath selector.

    and button(UPDATE) is to locate the UPDATE button, UPDATE is an enum constant in CssSelector to specify

    By.cssSelector("input[value='Update']");
    


    The advantage of having this Supplier enums is to reduce the number of methods of the helper API, before I came out this Supplier solution, I have used the following approach,
    findElementById,
    findElementByName,
    ...
    findElementByXpath,
    findElementsById,
    findElementsByName,
    ...
    findElementsByXpath

    While now I only have three methods,



    Element findElement(By by);

    This is same as the original Selenium SearchContext.findElement

    Element untilFindElement(By by);

    This is a new method after calling the original Selenium SearchContext.findElement it waits until an element is found or timeout

    Stream findElements(By by); This is similar to original SearchContext.findElements, just it uses Stream rather than list, Stream is a new interface from Java 8. One implementation is in Browser interface as a default method, Element::new is called method refernece in Java 8, the following two code snippets have the same functionality while the latter one is using Lambda Expression

        default public Stream<Element> findElements(Supplier<By> by) {
            return findElements(by.get()).stream().map(Element::new);
        }
    
        default public Stream<Element> findElements(Supplier<By> by) {
            return findElements(by.get()).stream().map(element -> new Element(element));
        }
    
    


    By using Supplier, you can hide Selenium By inside an enum and make the test codes very clean,

    separate test data from page

    1. Input Data

    Test data can be organized into Java Value Object or POJO as people call it, then used as the parameter for the methods of Page Object. For example, in a shopping page, it can have methods such setBillingAddress, setCreditCard and setOtherInformation which take a parameter object such as Address, CreditCard and OtherInfomation, etc.



    Here is the Address Java Value Object, it is a POJO, since all its properties are final, all its fields can be public without getter to save some extra code.


    The address can be a bean in a spring config file, or a row in jBehave table, depends on your preference,



    Then the test can be as simple as this,



    This example just showed you how to separate test data from page and use the data to populate the form with input, radio button, select and checkbox widgets. However, It is not the ultimate way to manage test data. There are other frameworks, i.e. jBehave which can be used to organize the tests in tabular format. Please check out their websites for more information.


    2. Output Data

    Expectation data can also be organized into Java objects, for example, ErrorMessages,





    So in the test, you can just call,
            assertEquals(expectedErrorMessages, cartPage.getErrorMessages());
    

    And in the case of test failure, it will display meaningful error messages,

    On page,



    On IDE,



    Please implement toString method of this expectation class, without it, the reported error would be like following, which really doesn't tell us much.

    add support for new browsers

    There is an enum based Browser support in Selenium Capsules, the code is here, Browser


    The Firefox is,

    It doesn't contain all of the browsers on the market, how to extend it to support new browsers?
    It is easy, just add another class to implement Browser interface then you can use this new Browser type to support page framework,

    The difference between Firefox and FirefoxOnWindows is FirefoxOnWindows also implements Browser interface while Firefox doesn't.

    So Firefox itself can't be used as a parameter the constructor of the AbstractPage, what's the reason for this? this reason is simple, since it is used to be part of the enum constants Browsers, so Browsers is preferred class to use in the codebase, then it can used as value in Spring context file and use Java property to choose the browser for the tests.

    browser=FIREFOX
    
    or
    browser=CHROME