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
    

    Sunday, May 18, 2014

    jquery calendar in java 8

    package com.algocrafts.calendar;
    
    import com.algocrafts.conditions.IsEquals;
    import com.algocrafts.converters.Filter;
    import com.algocrafts.converters.FirstItem;
    import com.algocrafts.decorators.AbstractPage;
    import com.algocrafts.decorators.Element;
    import com.algocrafts.locators.ElementLocator;
    import com.algocrafts.locators.ElementTryLocator;
    import com.algocrafts.locators.ElementsLocator;
    import com.algocrafts.locators.Locator;
    
    import static com.algocrafts.conditions.ElementFunction.CLICK_IF_NOT_NULL;
    import static com.algocrafts.conditions.PageCondition.CALENDAR_NOT_DISPLAYED;
    import static com.algocrafts.converters.GetText.TEXT;
    import static com.algocrafts.converters.Ordinal.ORDINAL;
    import static com.algocrafts.converters.StringToInt.PARSE_INT;
    import static com.algocrafts.converters.ToMonth.TO_MONTH;
    import static com.algocrafts.searchmethods.ByClassName.*;
    import static com.algocrafts.searchmethods.ById.UI_DATEPICKER_DIV;
    import static com.algocrafts.searchmethods.ByTagName.TD;
    
    /**
     * This is the reference implementation of the Calendar interface which can be
     * operated by a DatePicker.
     * The location of the date picker is here,
     * http://jqueryui.com/datepicker/
     *
     * @author Yujun Liang
     * @since 0.1
     */
    public class JQueryCalendar implements Calendar {
    
        private final AbstractPage page;
        private final Locator<AbstractPage, Element> trigger;
    
        /**
         * Constructor of the JQueryCalendar, an active page and a search
         * criteria of the trigger element.
         *
         * @param page
         * @param trigger
         */
        public JQueryCalendar(AbstractPage page, Locator<AbstractPage, Element> trigger) {
            this.page = page;
            this.trigger = trigger;
        }
    
        @Override
        public void show() {
            trigger.and(CLICK_IF_NOT_NULL).apply(page);
        }
    
        @Override
        public int currentYear() {
            return new ElementLocator<AbstractPage>(UI_DATEPICKER_DIV)
                .and(new ElementLocator<>(UI_DATEPICKER_HEADER))
                .and(new ElementLocator<>(UI_DATEPICKER_YEAR))
                .and(TEXT)
                .and(PARSE_INT)
                .apply(page);
        }
    
        @Override
        public int currentMonth() {
            return new ElementLocator<AbstractPage>(UI_DATEPICKER_DIV)
                .and(new ElementLocator<>(UI_DATEPICKER_MONTH))
                .and(TEXT)
                .and(TO_MONTH)
                .and(ORDINAL)
                .apply(page);
        }
    
        @Override
        public void previousMonth() {
            new ElementLocator<AbstractPage>(UI_DATEPICKER_DIV)
                .and(new ElementLocator<>(UI_DATEPICKER_PREV))
                .and(CLICK_IF_NOT_NULL)
                .apply(page);
        }
    
        @Override
        public void nextMonth() {
            new ElementLocator<AbstractPage>(UI_DATEPICKER_DIV)
                .and(new ElementLocator<>(UI_DATEPICKER_NEXT))
                .and(CLICK_IF_NOT_NULL)
                .apply(page);
        }
    
        @Override
        public void pickDay(int day) {
            new ElementLocator<AbstractPage>(UI_DATEPICKER_DIV)
                .and(new ElementLocator<>(UI_DATEPICKER_CALENDAR))
                .and(new ElementsLocator<>(TD))
                .and(new Filter<>(new IsEquals(TEXT, day)))
                .and(new FirstItem<>())
                .and(CLICK_IF_NOT_NULL)
                .apply(page);
            new ElementLocator<AbstractPage>(UI_DATEPICKER_DIV)
                .and(new ElementTryLocator<>(UI_DATEPICKER_CLOSE))
                .and(CLICK_IF_NOT_NULL)
                .apply(page);
            page.until(CALENDAR_NOT_DISPLAYED);
        }
    
    }