Friday, June 6, 2014

form element - select

Select is a dropdown user interface for people to select the desired the value from the list. for example,



Selenium UI Support provided a Select class which is great for users to manipulate select element on html page, however, it lacks a feature which will cause problem on AJAX enabled web pages. If the option list is built dynamically, selecting before it is populated will result in NoSuchElementException. Thus it requires adding waiting mechanism to the select before selecting an option from it. This waiting logic is encapsulated into a Select class in Selenium Capsules. The Select class takes a SelectLocator as parameter.

public class SelectLocator<Where extends Searchable<Where>>
        extends Locators<Where, Select> {

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

    public SelectLocator(Supplier<By> selector) {
        super((Where where) -> {
            final Element element = where.untilFound(selector);
            try {
                element.until(Locators.<Element>elements(OPTION).and(new HasElements<>()));
                return new Select(element);
            } catch (NoSuchElementException e) {
                element.click();
                where.save();
                log.error("Timeout waiting for the option list to populate.", e);
                throw e;
            }
        });
    }
}


What it does is to find the Select element first, then wait for option list becomes populated and use it as a parameter for the constructor of the Select class in Selenium Capsules.

public class Select<Where extends Searchable<Where>> {

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

    private final Where where;
    private final SelectLocator<Where> locator;

    /**
     * Constructor of the Select, It is a wrapper for the Select from Selenium UI.
     * @param where
     * @param locator
     */
    public Select(Where where, SelectLocator<Where> locator) {
        this.where = where;
        this.locator = locator;
    }

    public void selectByVisibleText(Object text) {
        log.info("selecting select[" + locator + "] using [" + text + "]");
        locator.locate(where).selectByVisibleText(text.toString());
    }
}


Unlike the selectByVisibleText in the Selenium UI Select class which takes String as parameter, this method in Selenium Capsules takes Object as parameter, so anything can be used as parameter, which make it possible to use enum, as long as the toString method of the enum to a string that can be found in the option list, then it can be used

public enum CreditCardType {

    American_Express,
    JCB,
    MasterCard,
    Visa,
    Discover;

    @Override
    public String toString() {
        return REPLACE_UNDERSCORE.locate(this.name());
    }

    public static CreditCardType fromString(String string) {
        return valueOf(RESTORE_UNDERSCORE.locate(string));
    }
}


And we add a method to FormControl as following,
    /**
     * Select the dropdown by given value.
     *
     * @param selector selector
     * @param value    value
     */
    @SuppressWarnings("unchecked")
    default public void select(Supplier<By> selector, Object value) {
        new Select<>((Where) this, Locators.<Where>select(selector)).selectByVisibleText(value);
    }


Then we can use this method to select the dropdown.

        select(CARD_TYPE, CreditCardType.MasterCard);


If you don't want to use selectByVisibleText method you can simply create a Select object and use the other methods, all methods in Select class from selenium ui support are still available through the Select in Selenium Capsules, with some modification and additions, 1. parameter type from String to Object to enable enum as parameter, 2. additional method to accept enum as parameter and use its ordinal,
    public void selectByEnumOrdinal(Enum index) {
        locate().selectByIndex(index.ordinal());
    }
    
    public void selectByIndex(int index) {
        locate().selectByIndex(index);
    }



And CARD_TYPE is an enum instance to provide a Selenium By.id("card-type"), where Supplier is an interface since Jave 8.
public enum BookStoreId implements Supplier<By> {

    CARD_TYPE("card-type");

    private final By by;

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

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

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


No comments:

Post a Comment