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);
    }

}

Friday, May 9, 2014

a browser factory using enum (200 mcg)

When testing web applications, we often need to switch the browsers. There are many ways to manage this, usually people use if else statement.

There is nothing wrong with if else statement, but if you have many branches of else if statement, the code is extremely fragile. Many patterns have been introduced to avoid using if statement, among them, Factory, Factory Method, Strategy are most popular ones. By the assistance from Spring, we can actually use enum to achieve this, here is the enum of browsers we are going to support in the tests, we can always add more if we want to support more browsers.


Firefox is one of the Browsers
Then declare this Browser as an instance variable of AbstractPage and passed in from Constructor,
In a Spring managed property file, i.e. pages.properties, add the name of the enum,
browser=FIREFOX


The way to let Spring know about the property file is to add this bean into Spring context xml file.
    
        
            
                classpath:properties/pages.properties
            
        
    

In your code, whenever you want to access webDriver instance, just simply call,
This also solves the incompatibility issue caused by Firefox upgrade, mentioned here. And you can run test for different browsers,



And since this Browser interface extends WebDriver, it can be used as the constructor parameter to Actions class and perform advanced actions such mouseOver, drapAndDrop, etc.

     Actions action =  new Actions(CHROME);



Let us compare the tests with or without this browser factory,

    //This is an ugly test not using page framework, it has the same function as the test below. :(
    @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();
    }

    @Test
    public void testDragAndDrop() {
        Browser browser = Browsers.FIREFOX;
        browser.get("http://www.w3schools.com/html/html5_draganddrop.asp");
        browser.dragAndDrop(id("drag1"), id("div2"));
    }