Saturday, May 24, 2014

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

Saturday, November 30, 2013

locator pattern (100 mcg)

Selenium By API provides a good way to search for element on web page. However, it doesn't support complex search unless you use By.ByXPath. I don't like to use ByXPath, I think it cluttered the code with too many string literals. I found Locator pattern quite useful in improving the readability of the code so I designed a Locator interface in Selenium Capsules.
public interface Locator<Where, What> extends Function<Where, What> {

    /**
     * Returns a composed function that first applies this function to
     * its input, and then applies the {@code after} function to the result.
     * If evaluation of either function throws an exception, it is relayed to
     * the caller of the composed function.
     *
     * @param <V> the type of output of the {@code after} function, and of the
     *           composed function
     * @param after the function to apply after this function is applied
     * @return a composed function that first applies this function and then
     * applies the {@code after} function
     * @throws NullPointerException if after is null
     *
     * @see #compose(Function)
     */
    default <V> Locator<Where, V> and(Locator<? super What, ? extends V> after) {
        Objects.requireNonNull(after);
        return (Where t) -> after.apply(apply(t));
    }
}

The locator is an extension of function object that became available in Java 8. An example of the Selector and a Locator Factory to return a locator of your choice.
public enum ClassName implements Supplier<By> {

    SF_JS_ENABLED("sf-js-enabled"),

    UI_DATEPICKER_CALENDAR("ui-datepicker-calendar"),
    UI_DATEPICKER_NEXT("ui-datepicker-next"),
    UI_DATEPICKER_PREV("ui-datepicker-prev"),
    UI_DATEPICKER_MONTH("ui-datepicker-month"),
    UI_DATEPICKER_YEAR("ui-datepicker-year"),
    UI_DATEPICKER_HEADER("ui-datepicker-header"),

    PAGE_TITLE("page-title"),
    UI_DATEPICKER_CLOSE("ui-datepicker-close");

    private final By by;

    private ByClassName(String id) {
        this.by = className(id);
    }

    @Override
    public By get() {
        return by;
    }

    @Override
    public String toString() {
        return by.toString();
    }
}

public class Locators<Where extends Searchable<Where>, What>
        implements Locator<Where, What> {

    public static <Where extends Searchable<Where>> Locators<Where, Element> element(Supplier<By> selector) {
        return new ElementLocator<>(selector);
    }

    public static <Where extends Searchable<Where>> Locators<Where, Stream<Element>> elements(Supplier<By> selector) {
        return new ElementsLocator<>(selector);
    }

    public static <Where extends Searchable<Where>> Locators<Where, Element> tryElement(Supplier<By> selector) {
        return new ElementTryLocator<>(selector);
    }

    public static SelectLocator select(Supplier<By> selector) {
        return new SelectLocator(selector);
    }

    private final Locator<Where, What> locator;

    public Locators(Locator<Where, What> locator) {
        this.locator = locator;
    }

    @Override
    public What locate(Where where) {
        return locator.locate(where);
    }
}

public class ElementLocator<Where extends Searchable<Where>>
        extends Locators<Where, Element> {

    public ElementLocator(Supplier<By> selector) {
        super((Where where) -> where.untilFound(selector));
    }
}
With this Locator pattern, we can have an Input class for the <input/> tag,
package com.algocrafts.formelements;


import com.algocrafts.algorithm.Retry;
import com.algocrafts.capsules.Element;
import com.algocrafts.capsules.Searchable;
import com.algocrafts.converters.GetText;
import com.algocrafts.locators.Locator;
import org.apache.log4j.Logger;

import java.util.function.Predicate;

import static java.util.concurrent.TimeUnit.SECONDS;
import static org.apache.log4j.LogManager.getLogger;

public class Input<T extends Searchable<T>> {

    private static final Logger log = getLogger(Input.class);

    private final T form;

    public Input(T form) {
        this.form = form;
    }

    public void put(final Locator<T, Element> locator, final Object value) {
        String string = value.toString();
        log.info("setting input[" + locator + "]=[" + string + "]");
        final Retry retry = new Retry(5, 1, SECONDS);
        try {
            retry.attempt(() -> {
                locator.apply(form).clear();
                locator.apply(form).sendKeys(string);
                if (locator.and(GetText.VALUE).apply(form).equals(string)) {
                    retry.off();
                }
                return null;

            });
        } catch (Exception e) {
            log.info(String.format("Failed to set text %s to %s", string, locator), e);
        }
    }

}
In test class, for example, http://seleniumcapsules.blogspot.com/2013/01/refactor-selenium-example-using-page.html, if there are elements with the same name on a form, it is not possible to use findElementByName method, we need to use alternative API to locate the element, instead of provide other method such as typeByName, typeById and typeByXpath, we can use this type(Locator locator, Object value) to simplify the API, and the test became something like this, using an overloaded type method,
public class LoginPage extends AbstractPage{
 
    public HomePage loginAs(String username, String password) {
        put(USER_NAME, username);
        put(PASSWORD, password);
        button("login").submit();
        return new HomePage();
    }
}
Where USER_NAME and PASSWORD are enum instances in the above IdLocator

public class AbstractPage  {

    ...

    public void put(Locator<AbstractPage, Element> locator, Object value) {
        new Input<AbstractPage>(this).put(locator, value);
    }

    ...

}

public class AbstractForm  {

    ...

    public void put(Locator<AbstractForm, Element> locator, Object value) {
        new Input<AbstractForm>(this).put(locator,value);
    }

    ...

}
The developers of the Selenium may also realize that the By class is not enough to conduct a search on a page, if more complex scenario is required so they came up with another search method, ByChained, however, this class still doesn't address the issue, if we want to find an element and find all elements on it which meeting certain criteria, Locator solved this problem gracefully.

Locator is a generic interface which is a function of convert A to B, so we can define it operates on any A which can be WebDriver, WebElement, etc and define B as WebElement, List<WebElement> or Stream<WebElement>, where to search from and what to search for are entirely up to you, so the parameter type for the interface is <Where, What> rather than the standard < as appeared in the Function interface.

Here are some examples of the locators, these two enum instance of CURRENT_YEAR and CURRENT_MONTH return the year and month on the calendar,



public enum CalendarIntegerLocator implements Locator<AbstractPage, Integer> {

    /**
     * Locate the integer value representing current year on a calendar
     */
    CURRENT_YEAR(
            Locators.<AbstractPage>element(UI_DATEPICKER_DIV)
                    .and(element(UI_DATEPICKER_HEADER))
                    .and(element(UI_DATEPICKER_YEAR))
                    .and(TEXT)
                    .and(PARSE_INT)
    ),


    /**
     * Locate the integer value representing current month on a calendar
     */
    CURRENT_MONTH(
            Locators.<AbstractPage>element(UI_DATEPICKER_DIV)
                    .and(element(UI_DATEPICKER_MONTH))
                    .and(TEXT)
                    .and(TO_MONTH)
                    .and(ORDINAL)
    );

    private Locator<AbstractPage, Integer> locator;

    private CalendarIntegerLocator(Locator<AbstractPage, Integer> locator) {
        this.locator = locator;
    }

    @Override
    public Integer locate(AbstractPage page) {
        return locator.locate(page);
    }
}


Here is a more complex locator which combines mouseover event to locate hidden menu which only displays when you mouse is over its group header,

public class MouseOverLocator implements Locator<AbstractPage, Element> {

    private final String menuGroup;
    private final String menuItem;

    public MouseOverLocator(String menuGroup, String menuItem) {
        this.menuGroup = menuGroup;
        this.menuItem = menuItem;
    }

    public Element locate(AbstractPage page) {
        return Locators.<AbstractPage>element(MAIN_NAV)
                .and(element(SF_JS_ENABLED))
                .and(elements(LI))
                .and(new FirstMatch<>(DISPLAYED.and(TEXT.and(new IsStringEqual(menuGroup)))))
                .and(page.mouseOver())
                .and(element(UL))
                .and(element(() -> linkText(menuItem)))
                .locate(page);
    }

    @Override
    public String toString() {
        return "[" + menuGroup + "->" + menuItem + "]";
    }
}



First, it locates an element E_A using MAIN_NAV, then locates another element on element E_A using SF_JS_ENABLED, then locates all element iwht <li> tag and find the first element whose text is same as menuGroup, then overOver it and find another element E_B whose tag is <ul> and on element E_B, find the element whose linkText is menuItem. That's how you can find the it, it is quite complex but it is not impossible. This technical is called function composition, it compose a series of simple functions into a complex function and operates the final function on the given subject and returns the final result of the function computation.





        Clickable menu = new Menu(homePage, new MouseOverLocator("Features", "Events Management"));
        menu.click();


The code above will find the "Events Management" menu and click it. Also, you can have a page return all the menu items and click them one by one,

Sunday, March 31, 2013

test report generation (50 mcg)

Report generation is a common function of the applications especially financial applications. For example, all the banks provide statement download function through their websites. I am not sure how they test that functionality, this is what I did for one of my client.

The report downloading process contains following steps, thus a template method or composite approach will be suitable to design a generic ReportDownload, let me design an interface first,

public interface Report{
}

public interface ReportDownload {
   void navigate();
   void enterCriteria(ReportCriteria criteria);
   void triggering();
   Clickable trigger();
   Report findReport();
   Report resolveExpection(Report actual)
}

Report is a type, based on its file type, it can be a CSV, Excel or PDF document.

This is an article about how to save the downloaded file a certain folder, selenium forefox profile, with design, we can add a variable to hold the directory of the download files as per test base, so the findReport() method can look into that directory to find newly arrived file, in that way, you don't need to specify the download file name in the test. To test the result, a simple way is to use pre generation file as an expectation to compare the downloaded report. The expectation can be stored in a classpath directory which can be resolved by the file name of the actual report.

Once expectation and actual report are founded, we can compare two files to find out whether they are same, we can override the equals method to compare the content of the two files.


Report actual = page.findReport();
Report expected = page.resolveExpectation();
assertEquals(expected, actual)