Thursday, June 5, 2014

form element - radio button

Radio buttons are one type of graphical user interface element that allows the user to choose only one of the available options.



And this is the HTML code behind it,



You can see they have the same name,

name="customFieldDS.customfield_ROW0_value"

this is an important character of radio buttons, they all share the same name and that's how browser knows to switch to the answer you just clicked. We are going to use this feature to locate the radio button group and select the one clicked by user. For this mailing option radio group, we can define a Name selector enum instance for it,

import org.openqa.selenium.By;

import java.util.function.Supplier;

/**
 * This enum has elements with ByName from Selenium By API.
 */
public enum Name implements Supplier<By> {

    Q("q"),
    MAILING_OPTION("customFieldDS.customfield_ROW0_value"),
    QUANTITY("cartDS.shoppingcart_ROW0_m_orderItemVector_ROW0_m_quantity");

    private final By by;

    private Name(String id) {
        this.by = By.name(id);
    }

    /**
     * @return the by instance variable which is a ByName.
     */
    @Override
    public By get() {
        return by;
    }

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


To find all elements with the same name, we need to use the findElements method of WebDriver, since Selenium Capsules encapsulate Selenium API, the class to use is,

import com.algocrafts.pages.Element;
import com.algocrafts.pages.Locators;
import com.algocrafts.selenium.Searchable;
import org.openqa.selenium.By;

import java.util.function.Supplier;
import java.util.stream.Stream;

public class ElementsLocator<Where extends Searchable<Where>>
        extends Locators<Where, Stream<Element>> {

    public ElementsLocator(Supplier<By> selector) {
        super((Where where)
                        -> where.findElements(selector)
        );
    }
}


ElementsLocator extends a base locator called Locators and what it does is to pass a lambda expression to its super class. The lambda expression represents a Locator which calls the findElements of a Where generic parameter type which can be an AbstractPage, an Element or an AbstractForm.





This radioButtonGroup Locator is used to locate all the radio buttons into a Stream of Elements, if you don't understand what Stream is, you can think of it is a List.

And we also defined an enum for the values of the radio buttons



So on the ShoppingCartPage, we can also this method to select the radio, what it means is select the MailingOptions.No_Promotional_Mailers value from the radio group which can be located by Selector Name.MAILING_OPTION,

        cartPage.radio(Name.MAILING_OPTION, MailingOptions.No_Promotional_Mailers);


By the way, the following code works the same way as the code above, but the code above is much cleaner, that's the power of enum.

   cartPage.radio(() -> By.name("customFieldDS.customfield_ROW0_value"), 
                  "No promotional mailers. I will still receive updates on my MEAPs and other books.");


And here is the code for the radio method above, what it does is to create an instance of RadioButton class with parameter from where it is called, since it is called from cartPage, (Where) this refers to the page, and selector which is Name.MAILING_OPTION, and then call the setValue method of the RadioButton instance.

    /**
     * Choose the radio by given option.
     *
     * @param selector selector
     * @param option   option
     */
    @SuppressWarnings("unchecked")
    default public void setRadio(Supplier<By> selector, Object option) {
        new RadioButton<>((Where) this, selector).setValue(option);
    }


Here is a test setting the value of a radio and read it back,

public class RadioTest {
    @Test
    public void testRadio() {
        Browser browser = Browsers.CHROME;
        browser.get("http://localhost:63342/seleniumcapsules/html/radio.html");
        Page page = new Page(browser);
        page.setRadio(MAILING_OPTION, No_Promotional_Mailers);

        assertEquals(No_Promotional_Mailers, fromString(page.getRadio(MAILING_OPTION)));
        assertEquals(No_Promotional_Mailers, from(page.getRadio(MAILING_OPTION)));
        assertEquals(No_Promotional_Mailers, page.getRadio(MAILING_OPTION, (s) -> from(s)));
        assertEquals(No_Promotional_Mailers, page.getRadio(MAILING_OPTION, MailingOptions::from));
    }
}


public enum MailingOptions {

    Weekly_Newsletter("Weekly newsletter--New books, updates, news, and special offers"),
    Deal_Of_the_Day("Deal of the Day--These amazing special offers last just 24 hours!"),
    Both("Both"),
    No_Promotional_Mailers("No promotional mailers. I will still receive updates on my MEAPs and other books."),
    Keep_Me("Keep me on the lists I'm already on.");

    private final String string;

    private MailingOptions(String string) {
        this.string = string;
        MapHolder.map.put(string, this);
    }

    @Override
    public String toString() {
        return string;
    }

    /**
     * This method filtering the enum constants using the string and return the first one.
     *
     * @param string the string value
     * @return enum with the string value
     */
    public static MailingOptions fromString(String string) {
        return of(values()).filter((o) -> string.equals(o.string)).findFirst().get();
    }

    /**
     * This method look up the enum constant in the map.
     * @param string
     * @return
     */
    public static MailingOptions from(String string) {
        return MapHolder.map.get(string);
    }

    private static class MapHolder {
        private static final Map<String, MailingOptions> map = newHashMap();
    }
}


There are some subtle difference between the 4 assert statements,
        //This line converts the String to MailingOptions using fromString method.
        assertEquals(No_Promotional_Mailers, fromString(page.getRadio(MAILING_OPTION)));

        //This line converts the string to MailingOptions using the from method.
        assertEquals(No_Promotional_Mailers, from(page.getRadio(MAILING_OPTION)));

        //This line calls the getRadio method and passes in a lambda expression which converts the string to enum.
        assertEquals(No_Promotional_Mailers, page.getRadio(MAILING_OPTION, (s) -> from(s)));

        //This line calls the getRadio method and passes in a method reference of MailOptions.from(String string).
        assertEquals(No_Promotional_Mailers, page.getRadio(MAILING_OPTION, MailingOptions::from));


You can see Selenium Capsules framework uses enum extensively to increase the readability of the code, for comparison purpose, 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 testRadio() {
    System.setProperty("webdriver.chrome.driver", "src/main/resources/chrome/chromedriver");
    WebDriver webDriver = new ChromeDriver();
    webDriver.get("http://localhost:63342/seleniumcapsules/html/radio.html");
    List<WebElement> radios = webDriver.findElements(By.name("customFieldDS.customfield_ROW0_value"));
    for (WebElement radio : radios) {
       if (radio.getAttribute("value").equals("No promotional mailers. I will still receive updates on my MEAPs and other books.")) {
          radio.click();
       }
    }

    radios = webDriver.findElements(By.name("customFieldDS.customfield_ROW0_value"));
    for (WebElement radio : radios) {
       if (radio.getAttribute("checked").equals("true")) {
          assertEquals("No promotional mailers. I will still receive updates on my MEAPs and other books.", radio.getAttribute("value"));
       }
    }
}

and code using the framework,
@Test
public void testRadio() {
    Browser browser = Browsers.CHROME;
    browser.get("http://localhost:63342/seleniumcapsules/html/radio.html");
    Page page = new Page(browser);
    page.setRadio(MAILING_OPTION, No_Promotional_Mailers);
    assertEquals(No_Promotional_Mailers, page.getRadio(MAILING_OPTION, MailingOptions::from));
}

No comments:

Post a Comment