Saturday, June 14, 2014

handle ajax

When testing web sites built with AJAX technology, special cares must be taken to make sure when you call webDriver.findElement. Here is a web site selling event tickets, Ticketfly, to test the change location function, you may be attempted to write a test like this,

    @Test
    public void changeLocationUsingSelenium() {
        System.setProperty("webdriver.chrome.driver", "src/main/resources/chrome/chromedriver");
        WebDriver webDriver = new ChromeDriver();
        webDriver.get("http://www.ticketfly.com");
        webDriver.findElement(linkText("change location")).click();
        webDriver.findElement(linkText("CANADA")).click();
        webDriver.findElement(linkText("All Canada")).click();
        assertEquals("Canada", 
                     webDriver.findElement(By.className("tools-location"))
                              .findElement(By.tagName("a"))
                              .findElement(By.tagName("strong"))
                              .getText());
    }


Unfortunately, this test doesn't work, you will get this exception when you try to run the test,

Starting ChromeDriver (v2.10.267517) on port 39666
Only local connections are allowed.
log4j:WARN No appenders could be found for logger (org.apache.http.client.protocol.RequestAddCookies).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.

org.openqa.selenium.NoSuchElementException: no such element
  (Session info: chrome=35.0.1916.153)
  (Driver info: chromedriver=2.10.267517,platform=Mac OS X 10.9.3 x86_64) (WARNING: The server did not provide any stacktrace information)
Command duration or timeout: 115 milliseconds
For documentation on this error, please visit: http://seleniumhq.org/exceptions/no_such_element.html
Build info: version: '2.42.2', revision: '6a6995d31c7c56c340d6f45a76976d43506cd6cc', time: '2014-06-03 10:52:47'
System info: host: 'yujun.home', ip: '192.168.1.2', os.name: 'Mac OS X', os.arch: 'x86_64', os.version: '10.9.3', java.version: '1.8.0_05'
Driver info: org.openqa.selenium.chrome.ChromeDriver
Capabilities [{applicationCacheEnabled=false, rotatable=false, chrome={userDataDir=/var/folders/ks/4h1b7nps1vx5712vz12qd3880000gn/T/.org.chromium.Chromium.WmhgSi}, takesHeapSnapshot=true, databaseEnabled=false, handlesAlerts=true, version=35.0.1916.153, platform=MAC, browserConnectionEnabled=false, nativeEvents=true, acceptSslCerts=true, locationContextEnabled=true, webStorageEnabled=true, browserName=chrome, takesScreenshot=true, javascriptEnabled=true, cssSelectorsEnabled=true}]
Session ID: e2f0a757e78c351f2808a6b957c534c5
 at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
 at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
 at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
 at java.lang.reflect.Constructor.newInstance(Constructor.java:408)
 at org.openqa.selenium.remote.ErrorHandler.createThrowable(ErrorHandler.java:204)
 at org.openqa.selenium.remote.ErrorHandler.throwIfResponseFailed(ErrorHandler.java:156)
 at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:599)
 at org.openqa.selenium.remote.RemoteWebDriver.findElement(RemoteWebDriver.java:352)
 at org.openqa.selenium.remote.RemoteWebDriver.findElementByLinkText(RemoteWebDriver.java:401)
 at org.openqa.selenium.By$ByLinkText.findElement(By.java:242)
 at org.openqa.selenium.remote.RemoteWebDriver.findElement(RemoteWebDriver.java:344)
 at com.algocrafts.TicketflyTest.changeLocationUsingSelenium(TicketflyTest.java:26)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
 at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
 at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
 at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
 at org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79)
 at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71)
 at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
 at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
 at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
 at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
 at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
 at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
 at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
 at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
 at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:74)
 at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:211)
 at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:67)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
 at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)


Process finished with exit code 255



The reason is this function is built upon AJAX, it doesn't refresh the entire page, it just repaint the specific area with the new DOM elements. In order to test this feature, we need to add Explicit Wait mechanism to make sure the elements we are looking for appear after certain waiting period, the suitable classes from Selenium are WebDriverWait and FluentWait, using either one, we can rewrite the about test as following,

    @Test
    public void changeLocationUsingSeleniumWithExplicitWait() {
        System.setProperty("webdriver.chrome.driver", "src/main/resources/chrome/chromedriver");
        WebDriver webDriver = new ChromeDriver();
        webDriver.get("http://www.ticketfly.com");
        webDriver.findElement(linkText("change location")).click();
        WebDriverWait wait = new WebDriverWait(webDriver, 5);
        WebElement canada = wait.until(new Function<WebDriver, WebElement>() {
            @Override
            public WebElement apply(WebDriver webDriver) {
                return webDriver.findElement(linkText("CANADA"));
            }
        });
        canada.click();
        WebElement allCanada = wait.until(new Function<WebDriver, WebElement>() {
            @Override
            public WebElement apply(WebDriver webDriver) {
                return webDriver.findElement(linkText("All Canada"));
            }
        });
        allCanada.click();
        assertEquals("Canada", 
                     webDriver.findElement(By.className("tools-location"))
                              .findElement(By.tagName("a"))
                              .findElement(By.tagName("strong"))
                              .getText());
    }



When you run it, it passes.

This test become very verbose when we try to address some common concerns such as wait, it repeats many similar but not exactly same codes. It takes longer time for other developer to read the code and understand what it is doing. We can use framework approach to clean up the code, let us compare the following test written using Selenium Capsules,

    @Test
    public void changeLocationUsingBrowser() {
        Browser browser = CHROME;
        browser.get("http://www.ticketfly.com");
        browser.link(CHANGE_LOCATION).click();
        browser.link(CANADA).click();
        browser.link(ALL_CANADA).click();

        assertEquals("Canada",  Locators.<AbstractPage>element(TOOLS_LOCATION)
                .and(element(A))
                .and(element(STRONG))
                .and(TEXT).locate(new Page(browser)));
    }


It is much cleaner if you write code using framework approach since framework handles many tedious common concerns such as wait and see.

The test can be further cleaned by introducing a Page Object,

    @Test
    public void changeLocation() {
        TicketflyHomePage page = new TicketflyHomePage(CHROME);
        page.open();
        page.changeLocation(CANADA, ALL_CANADA);

        assertEquals("Canada", page.currentLocation());
    }


Even I am impressed by my own work. In case you wonder which class handles the Explicit Wait, it is the ElementLocator class in Selenium Capsules, it calls the untilFound method of Searchable interface as described here.

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

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


Unlike the original findElement method in SearchContext of the Selenium API, two new methods are introduced to handle different situations,
  • 1. If an element may appear or may not, use tryElement, it returns null if not found.
  • 2. If an element will appear after sometime since it is AJAX managed DOM element, use untilFound method which is explicitly waiting for the element to appear.


  •     /**
         * Find the first element or return null if nothing found.
         *
         * @param by selector
         * @return the first element or return null if nothing found.
         */
        default public Element tryElement(Supplier<By> by) {
            try {
                return findElement(by.get());
            } catch (NoSuchElementException e) {
                return null;
            }
        }
    
        /**
         * Find the first element until timeout then throw NoSuchElementException
         *
         * @param by selector
         * @return the first element or throw NoSuchElementException
         */
        default public Element untilFound(Supplier<By> by) {
            return until((Where page) -> findElement(by.get()));
        }
    

    Thursday, June 12, 2014

    handle alert and confirm

    The following two tests demonstrate how to click "OK" on a confirm message box, the first method uses Selenium directly, the second one uses Selenium Capsules framework,

    
    public class ConfirmTest {
    
        @Test
        public void clickConfirmUsingSelenium() {
            System.setProperty("webdriver.chrome.driver", "src/main/resources/chrome/chromedriver");
            WebDriver webDriver = new ChromeDriver();
            webDriver.get("http://localhost:63342/seleniumcapsules/html/upload.html");
            webDriver.findElement(cssSelector("input[value='Need Confirm']")).click();
            webDriver.switchTo().alert().accept();
        }
    
        @Test
        public void clickConfirm() {
            Page page = CHROME.load("http://localhost:63342/seleniumcapsules/html/upload.html");
            page.button(NEED_CONIRM).click();
            page.accept();
        }
    }
    

    Sunday, June 8, 2014

    upload file

    Some applications may require user to upload the file and since it is part of the workflow, without it, the test can be executed end to end, so it is necessary to add the support for file upload.

    Actually file upload is simpler than you think, even it appears like you have to browser the folder to find the file, but it is not a case when you do test automation. The browsing part of the function of the browser, it is out of your control so you don't need to test that part. As tester, we only test the part we are responsible for, for example, after you choose the file, click the button and the file should be sent to server and process by the application. So we can use put the file under test source folder and use Java File to locate it and convert the file into a file and use it for the testing.





    We can put the file to be uploaded inside /test/resources/upload/ folder and refer it in the test.



    The button doesn't submit the form, it just alert a message instead, so the last part of the test is to accept the alert.

    The test can be managed by Spring as well,

    locating

    I found out one commonality between all form elements,

    1. They all have these two instance fields, Where and Locator;
    2. They all call locator.locate(where) before taking other actions.

    To use one word to describe this activity of the locator, it is "locating"

    Thus this class Locating is introduced into Selenium Capsules framework,
    public class Locating<Where extends Searchable<Where>, What> {
    
        protected final Where where;
        protected final Locator<Where, What> locator;
    
        /**
         * Constructor of the Locating.
         *
         * @param where   where
         * @param locator locator
         */
        public Locating(Where where, Locator<Where, What> locator) {
            this.where = where;
            this.locator = locator;
        }
    
        public What locate() {
            return locator.locate(where);
        }
    }
    


    And now Input is
    public class Input<Where extends Searchable<Where>> extends Locating<Where, Element> {
    
        public static final Logger log = getLogger(Input.class);
    
        /**
         * Constructor of the input field.
         *
         * @param where    where
         * @param selector selector
         */
        public Input(Where where, Supplier<By> selector) {
            super(where, Locators.<Where>tryElement(selector));
        }
    
        /**
         * the value of input field, for example, "good" will be return
         * <p>
         * String value = page.get(() -> By.name("status"))
         * <p>
         * <input name="status" value="good"/>
         *
         * @return the value of the input
         */
        public String getValue() {
            final Retry retry = new Retry(5, 1, SECONDS);
            try {
                retry.attempt(() -> {
                    log.info("{}", retry);
                    Element element = locate();
                    return VALUE.locate(element);
                });
            } catch (Exception e) {
                log.info("Failed to read text", e);
            }
            return null;
        }
    
        /**
         * set the value of input field, for example,
         * <p>
         * after,
         * page.set(() -> By.name("status"), "good");
         * <p>
         * it will be,
         * <input name="status" value="good"/>
         *
         * @param value the value to set
         */
    
        public void put(final Object value) {
            String string = value.toString();
            final Retry retry = new Retry(5, 1, SECONDS);
            try {
                retry.attempt(() -> {
                    log.info("{}", retry);
                    Element element = locate();
                    element.clear();
                    element.sendKeys(string);
                    if (VALUE.and(new IsStringEqual(string)).test(element)) {
                        retry.off();
                    }
                    return null;
    
                });
            } catch (Exception e) {
                log.info("Failed to set text {}", string);
            }
        }
    
        /**
         * Test the autocomplete function for the input by given value, click the element
         * on the suggestion list which matches value parameter.
         * <p>
         * Please refer "http://seleniumcapsules.blogspot.com/2014/05/by-xpath.html"
         *
         * @param value   value
         * @param locator locator
         */
        public void autocomplete(Object value, Locator<Where, Element> locator) {
            Element element = locate();
            element.clear();
            Element suggestion;
            for (char c : value.toString().toCharArray()) {
                element.sendKeys(String.valueOf(c));
                suggestion = locator.locate(where);
                if (suggestion != null) {
                    suggestion.click();
                    return;
                }
            }
            suggestion = where.until(locator);
            if (suggestion != null) {
                suggestion.click();
            }
        }
    }
    
    


    Checkbox became,

    public class Checkbox<Where extends Searchable<Where>> extends Locating<Where, Element> {
    
        /**
         * Constructor of the checkbox.
         *
         * @param where    the place the checkbox can be found
         * @param selector the selector that leads to the checkbox
         */
        public Checkbox(final Where where, Supplier<By> selector) {
            super(where, element(selector));
        }
    
        /**
         * Change the checkbox according to the value parameter
         *
         * @param value true or false
         */
        public void setValue(boolean value) {
            Element checkbox = locate();
            if (checkbox != null && checkbox.isSelected() != value) {
                checkbox.click();
            }
        }
    
        /**
         * @return whether the checkbox is checked or not
         */
        public boolean isChecked() {
            return CHECKED.and(TRUE).test(locate());
        }
    }
    
    


    Radio became,

    public class RadioButton<Where extends Searchable<Where>> extends Locating<Where, Stream&tl;Element>> {
    
        /**
         * Constructor this radio button.
         *
         * @param where    where
         * @param selector selector
         */
        public RadioButton(Where where, Supplier<By> selector) {
            super(where, elements(selector));
        }
    
        /**
         * @param value value to set
         */
        public void setValue(Object value) {
            new FirstMatch<>(DISPLAYED.and(VALUE.and(new IsStringEqual(value))))
                    .and(CLICK_IF_NOT_NULL)
                    .locate(locate());
        }
    
        /**
         * @return the value of the select radio
         */
        public String getValue() {
            return new FirstMatch<>(DISPLAYED.and(CHECKED.and(TRUE)))
                    .and(VALUE)
                    .locate(locate());
        }
    }
    
    


    There no longer have these two instance fields,

    protected final Where where;
    protected final Locator locator;


    And locator.locate(where) became locate(). There is change in the function call chain as well, radioButtonGroup used to be the first function in the chain and now it is FirstMatch, after radioButtonGroup became the locator variable in the super class and locator.locate(where) became locate(), to illustrate the functional transformation, the following three function calls have the same effect.
    
            radioButtonGroup
                    .and(new FirstMatch<>(DISPLAYED.and(CHECKED.and(TRUE))))
                    .and(VALUE)
                    .locate(where);
    
            new FirstMatch<>(DISPLAYED.and(CHECKED.and(TRUE)))
                    .and(VALUE)
                    .locate(radioButtonGroup.locate(where));
    
           
            VALUE.locate(
                    new FirstMatch<>(DISPLAYED.and(CHECKED.and(TRUE))).locate(
                          radioButtonGroup.locate(where)));
    
    


    They all are equivalent to this sequential form,

         Stream<Element> radios = radioButtonGroup.locate(where);
         Element radio = new FirstMatch<>(DISPLAYED.and(CHECKED.and(TRUE))).locate(radios);
         String value = VALUE.locate(radio);
    


    which is exactly same to this raw form, without Selenium Capsules, you can see a lot of Selenium powder.
    
        String value = null;
        List<WebElement> radios = webDriver.findElements(By.name("customFieldDS.customfield_ROW0_value"));
        for (WebElement radio : radios) {
           if (radio.getAttribute("checked").equals("true")) {
               value = radio.getAttribute("value"));
           }
        }
    

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


    form element - checkbox

    Checkboxes are one kind of form elements allowing user to answer true or false questions. It provides toggle behaviour when user clicks tone checkbox. Here are two checkboxes on a form,

    Based on the common requirement of using a checkbox when doing test automation, we can define a Checkbox class to encapsulate the behaviours as following, and it only has two public methods, setValue and isChecked.

    public class Checkbox<Where extends Searchable<Where>> {
    
        private final Where where;
        private final Locator<Where, Element> locator;
    
        /**
         * Constructor of the checkbox.
         *
         * @param where    the place the checkbox can be found
         * @param selector the selector that leads to the checkbox
         */
        Checkbox(final Where where, Supplier<By> selector) {
            this.where = where;
            this.locator = element(selector);
        }
    
        /**
         * Change the checkbox according to the value parameter
         *
         * @param value true or false
         */
        public void setValue(boolean value) {
            Element apply = locator.locate(where);
            if (apply != null && apply.isSelected() != value) {
                apply.click();
            }
        }
    
        /**
         * @return whether the checkbox is checked or not
         */
        public boolean isChecked() {
            return locator.and(CHECKED).and(TRUE).test(where);
        }
    }
    


    If you don't want to create a new instance of Checkbox, you can use the helper methods in FormControl to read and write the checkbox that can be found using the locator parameter,

        /**
         * Check if the checkbox is checked by the given selector.
         *
         * @param selector selector
         * @return true if it is checked.
         */
        @SuppressWarnings("unchecked")
        default public boolean isChecked(Supplier<By> selector) {
            return new Checkbox<>((Where) this, selector).isChecked();
        }
    
        /**
         * Set checkbox to the given value.
         *
         * @param selector selector
         * @param value    value
         */
        @SuppressWarnings("unchecked")
        default public void check(Supplier<By> selector, boolean value) {
            new Checkbox<>((Where) this, selector).setValue(value);
        }
    


    So in a page,

            AbstractPage page = new AbtractPage(Browsers.CHROME);
            System.out.println(page.isChecked(CONFIRM_EMAIL_)),
            System.out.println(page.isChecked(RATINGS_______)),
    
            page.check(CONFIRM_EMAIL_, false);
            page.check(RATINGS_______, true);
    


    And it will print true and false on the console and change the checkboxes to the following,



    Here is the code how it is used in a form and a page object and a test,

    public class OtherInformationForm extends AbstractPage {
    
        public OtherInformationForm(AbstractPage page) {
            super(page);
        }
    
        public void setOtherInformation(OtherInformation info) {
            put(BILLING_EMAIL___, info.emailAddress);
            put(COMMENTS________, info.comments);
            check(CONFIRM_EMAIL_, info.confirmEmail);
            check(RATINGS_______, info.askRating);
            radio(MAILING_OPTION, info.mailingOptions);
        }
    
        public OtherInformation getOtherInformation() {
            return new OtherInformation(
                    get(BILLING_EMAIL___),
                    isChecked(CONFIRM_EMAIL_),
                    isChecked(RATINGS_______),
                    MailingOptions.valueOf(get(MAILING_OPTION)),
                    get(COMMENTS________));
        }
    
    }
    

    public class ShoppingCartPage extends AbstractPage {
    
        private final BillingAddressForm billingAddressForm = new BillingAddressForm(this);
        private final CreditCardForm creditCardForm = new CreditCardForm(this);
        private final OtherInformationForm otherInformationForm = new OtherInformationForm(this);
    
        public ShoppingCartPage(AbstractPage page) {
            super(page);
        }
    
        public void setBillingAddress(Address address) {
            billingAddressForm.setBillingAddress(address);
        }
    
        public void getBillingAddress() {
            billingAddressForm.getBillingAddress();
        }
    
        public void setCreditCard(CreditCard card) {
            creditCardForm.setCreditCard(card);
        }
    
        public CreditCard getCreditCard() {
            return creditCardForm.getCreditCard();
        }
    
        public void setQuantity(int quantity) {
            put(Xpath.QUANTITY, quantity);
            button(UPDATE).click();
        }
    
        public void setOtherInformation(OtherInformation info) {
            otherInformationForm.setOtherInformation(info);
        }
    
        public OtherInformation getOtherInformation() {
            return otherInformationForm.getOtherInformation();
        }
    
        public void continues() {
            button(CONTINUE).click();
        }
    
        public ErrorMessages getErrorMessages() {
             return new ErrorMessages(this);
        }
    }
    

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = {"classpath:bookstore/beans/context.xml"})
    public class BookStoreShoppingTest {
    
        @Autowired
        private Address billingAddress;
    
        @Autowired
        private CreditCard creditCard;
    
        @Autowired
        private OtherInformation otherInformation;
    
        @Autowired
        private ErrorMessages expectedErrorMessages;
    
        @Autowired
        private BookStoreHomePage homePage;
    
        @Test
        public void invalidCardInfo() {
    
            BookListPage listPage = new BookListPage(homePage, homePage.link(JAVA), IS_COPYRIGHTED) {{
                open();
                link(ACTIVE_MQ_IN_ACTION).click();
            }};
            BookDetailsPage bookPage = new BookDetailsPage(listPage) {{
                until(IS_COPYRIGHTED);
                secondAddToCart().click();
            }};
    
            ShoppingCartPage cartPage = new ShoppingCartPage(bookPage) {{
                setQuantity(2);
                setBillingAddress(billingAddress);
                setCreditCard(creditCard);
                setOtherInformation(otherInformation);
                continues();
            }};
    
            assertEquals(expectedErrorMessages, cartPage.getErrorMessages());
        }
    
        @Test
        public void invalidCardInfoNormalWay() {
    
            BookListPage listPage = new BookListPage(homePage, homePage.link(JAVA), IS_COPYRIGHTED);
            listPage.open();
            listPage.link(ACTIVE_MQ_IN_ACTION).click();
    
            BookDetailsPage bookPage = new BookDetailsPage(listPage);
            bookPage.until(IS_COPYRIGHTED);
            bookPage.secondAddToCart().click();
    
            ShoppingCartPage cartPage = new ShoppingCartPage(bookPage);
            cartPage.setQuantity(2);
            cartPage.setBillingAddress(billingAddress);
            cartPage.setCreditCard(creditCard);
            cartPage.setOtherInformation(otherInformation);
            cartPage.continues();
    
            assertEquals(expectedErrorMessages, cartPage.getErrorMessages());
        }
    
    
        @Before
        public void setup() {
            homePage.open();
        }
    
        @After
        public void close() {
            homePage.close();
        }
    
    }
    


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

    form element - input

    Quite often, test automation requires to fill out forms and click button or link to send data back to server, thus it is necessary to introduce some Classes to be responsible to do this repeated tasks such findElement, sendKeys etc.

    Inputs are most common form elements for user to enter information, for a online shopping site, shipping information and billing information are required for any transaction,



    On this form, most fields are inputs, and here is how to enter text on a single field,

            // Find the text input element by its name
            WebElement element = driver.findElement(By.name("q"));
    
            // Enter something to search for
            element.sendKeys("Cheese!");
    
            // Now submit the form. WebDriver will find the form for us from the element
            element.submit();
    


    And it doesn't consider the possibility that it may require some waiting before you can find the element and send keys to it, all things considered, it could be very complex.
    So it is necessary to introduce this method to combine all those small steps and provide and single transation like,
            put(()->By.name("q"), "Cheese!");
    


    So we can design an Input class to encapsulate the behaviour of read, write and autocomplete on html text input fields,
    
    public class Input<Where extends Searchable<Where>> implements Supplier<String> {
    
    
    
        /**
         * the value of input field, for example, "good" will be return
         * <p>
         * String value = page.get(() -> By.name("status"))
         * <p>
         * <input name="status" value="good"/>
         *
         * @return the value of the input
         */
        public String get() {
             ...
        }
    
        /**
         * set the value of input field, for example,
         * <p>
         * after,
         * page.set(() -> By.name("status"), "good");
         * <p>
         * it will be,
         * <input name="status" value="good"/>
         *
         * @param value the value to set
         */
    
        public void put(final Object value) {
            ...
        }
    
        /**
         * Test the autocomplete function for the input by given selector, click the element
         * on the suggestion list which has the same value of value parameter.
         * <p>
         * Please refer "http://seleniumcapsules.blogspot.com/2014/05/by-xpath.html"
         *
         * @param value   value
         * @param locator locator
         */
        public void autocomplete(Object value, Locator<Where, Element> locator) {
            ...
        }
    
    }
    


    
    public interface FormControl<Where extends Searchable<Where>> {
    
        /**
         * Check if the checkbox is checked by the given selector.
         *
         * @param selector selector
         * @return true if it is checked.
         */
        @SuppressWarnings("unchecked")
        default public boolean isChecked(Supplier<By> selector) {
            return new Checkbox<>((Where) this, selector).isChecked();
        }
    
        /**
         * Set checkbox to the given value.
         *
         * @param selector selector
         * @param value    value
         */
        @SuppressWarnings("unchecked")
        default public void check(Supplier<By> selector, boolean value) {
            new Checkbox<>((Where) this, selector).setValue(value);
        }
    
        /**
         * Read the value of the radio by given option.
         *
         * @param selector selector
         * @return the value of selected radio.
         */
        @SuppressWarnings("unchecked")
        default public String radio(Supplier<By> selector) {
            return new RadioButton<>((Where) this, selector).get();
        }
    
        /**
         * Choose the radio by given option.
         *
         * @param selector selector
         * @param option   option
         */
        @SuppressWarnings("unchecked")
        default public void radio(Supplier<By> selector, Object option) {
            new RadioButton<>((Where) this, selector).setValue(option);
        }
    
        /**
         * Select the dropdown by given value.
         *
         * @param selector selector
         * @param value    value
         */
        @SuppressWarnings("unchecked")
        default public void select(Supplier<By> selector, Object value) {
            new Selection<>((Where) this, Locators.<Where>select(selector)).selectByVisibleText(value);
        }
    
        /**
         * Read value from an input field.
         *
         * @param selector selector
         * @return its value.
         */
        @SuppressWarnings("unchecked")
        default public String get(Supplier<By> selector) {
            return new Input<>((Where) this, selector).get();
        }
    
        /**
         * Enter text into an input field.
         *
         * @param selector selector
         * @param value    value
         */
        @SuppressWarnings("unchecked")
        default public void put(Supplier<By> selector, Object value) {
            new Input<>((Where) this, selector).put(value);
        }
    
        /**
         * Autocomplete for text field and return the first found suggestion match the whole word.
         *
         * @param selector selector
         * @param value    value
         * @param locator  locator
         */
        @SuppressWarnings("unchecked")
        default public void autocomplete(Supplier<By> selector, Object value, Locator<Where, Element> locator) {
            new Input<>((Where) this, selector).autocomplete(value, locator);
        }
    }
    
    So for that form, it can be just like this,

    package com.bookstore;
    
    import com.algocrafts.domain.Countries;
    import com.algocrafts.domain.UnitedStates;
    import com.algocrafts.pages.AbstractPage;
    import com.bookstore.domain.Address;
    
    import static com.bookstore.BookStoreId.*;
    
    public class BillingAddressForm extends AbstractPage {
        public BillingAddressForm(AbstractPage page) {
            super(page);
        }
    
        public void setBillingAddress(Address address) {
            put(BILLING_FIRST_NAME, address.firstName);
            put(BILLING_LAST_NAME_, address.lastName);
            put(BILLING_ADDRESS1__, address.street1);
            put(BILLING_ADDRESS2__, address.street2);
            put(BILLING_CITY______, address.city);
            put(BILLING_STATE_____, address.state);
            put(BILLING_ZIP_______, address.zipcode);
            select(BILLING_COUNTRY___, address.country);
        }
    
        public Address getBillingAddress() {
            return new Address(
                    get(BILLING_ADDRESS1__),
                    get(BILLING_ADDRESS2__),
                    get(BILLING_CITY______),
                    get(BILLING_ZIP_______),
                    UnitedStates.fromString(get(BILLING_STATE_____)),
                    Countries.fromString(get(BILLING_COUNTRY___)),
                    get(BILLING_FIRST_NAME),
                    get(BILLING_LAST_NAME_));
        }
    
    }
    


    Input class and helpers in FormControl only give you most frequently used functions of the text input, reading its value and setting a value to it, if other functions of the input are needed for tests, please use Element instead, Element implement WebElement so it provides the same functionality as WebElement. It also have additional functions such as untilFound method which explicitly wait for the element to be found, this untilFound method is available to both Element and AbstractPage since it is a default method in Searchable interface, it handles explicit wait in a way similar to implicit wait which saves a lot of code to handle explicit wait.

    
        /**
         *  Find the first element until timeout then throw NoSuchElementException 
         *
         * @param by selector
         * @return the first element or throw NoSuchElementException
         */
        default public Element untilFound(Supplier<By> by) {
            return until((Where page) -> new Element(findElement(by.get())));
        }
    

    Wednesday, June 4, 2014

    what inside selenium capsules?

    Selenium Capsules Selenium WebDriver
    Searchable SearchContext
    ExplicitWait FluentWait
    Browser WebDriver
    Element WebElement
    Supplier By

    Tuesday, June 3, 2014

    specification

    In this post, table manners, I delibrately gave an incomplete test, just to show the way to read a table, now, I am going to talk another important topic, where to assert?

    To organize assertion into a cohesive group and output meaningful difference message for debugging purpose, I defined a TableContents class to measure the actual table and put the differences inside a SetDiff class, it means the difference of two sets.




    We need to add hashCode method in the Person class since it is used in a HashSet now,



    SetDiff class uses Set Operation to find out what elements are only in expectation and what are only in actual results,


    S1 - expectation, S2 - actual
    only in expectation = S1 - S2
    only in actual = S2 - S1

    The test is complete.



    By splitting responsibilities among different classes, the test becomes very clean, and the error messages can show what's the difference in expectation and actual results. For this test, let us pretend last time, there was only one row in the table,



    So the expectation used to be,


    Now you are making changes and added 3 rows of data, and changed first row from 49 to 50, when you run the test, here is the result,



    It tells you what's the actual result, what is in expectation but doesn't show in actual result and the unexpected result in actual, best thing yet, you can just copy the actual result into the expectation after you verify it is the right result.

    And test passes,


    However, there is no assertion method in junit which takes Object as parameter, I wrote one by myself, but I am asking Kent Beck to add this into future release of junit.

    Monday, June 2, 2014

    table manners for selenium capsules

    Here is a simple table, http://www.w3schools.com/html/html_tables.asp


    Let us see how Selenium Capsules can help to read the content of it.



    The Table class only has two methods now, getHeader and getRows, Stream is a new collection type appears in Java 8.

    And here is the test to read the table,


    You can see, the table supports type, it will return the type in a Stream. For this table, we define a simple domain class called Person,

        class Person {
            private final String firstName;
            private final String lastName;
            private final int points;
    
            Person(String firstName, String lastName, int points) {
    
                this.firstName = firstName;
                this.lastName = lastName;
                this.points = points;
            }
    
            @Override
            public String toString() {
                return firstName + "|" + lastName + "|" + points;
            }
        }
    


    When we construct the Table, we need to pass a Locator to locate the table and a Mapper to map the contents in a row to the domain class,

            Locator<AbstractPage, Element> locator = Locators.<AbstractPage>element(MAIN).and(element(TABLE));
    

            Locator<Stream<Element>, Person> mapper = (stream) -> {
                Iterator<String> iterator = stream.map(TEXT).iterator();
                return new Person(iterator.next(), iterator.next(), PARSE_INT.locate(iterator.next()));
            };
    


    The mapper will read all the TD tags in the TR tags and call the new Person for each TR tag.
    <tr>
      <td>Jill</td>
      <td>Smith</td> 
      <td>50</td>
    </tr>
    
                return new Person("Jill", "Smith", 50);
    


    it prints this when you run it

    Firstname|Lastname|Points|
    Jill|Smith|50
    Eve|Jackson|94
    John|Doe|80
    Adam|Johnson|67
    
    The following code is called Method Reference in Java 8.

            table.getRows().forEach(
                    System.out::println
            );
    


    It has good table manners.

    Saturday, May 31, 2014

    decorating selenium

    On Selenium HQ home page, selenium is defined as following,

    Selenium automates browsers. That's it! What you do with that power is entirely up to you. Primarily, it is for automating web applications for testing purposes, but is certainly not limited to just that. Boring web-based administration tasks can (and should!) also be automated as well. Selenium has the support of some of the largest browser vendors who have taken (or are taking) steps to make Selenium a native part of their browser. It is also the core technology in countless other browser automation tools, APIs and frameworks.

    Even since Selenium came into existence, it has been adopted by many projects as test automation library. Due to its open source nature, it became a preferred test tool in many organizations.

    Selenium is not without shortcomings, it is a library, not a framework, so it doesn't provide mechanisms to reduce duplication. To write clean tests, it is necessary to add a level of abstraction on top of Selenium to form an enterprise framework. Selenium Capsules, is such an enterprise framework allowing developers to maximize theirs productivity by offering services to relieve developers from worrying about many common concerns such waiting, exception handling and logging. It also provides reference implementation to teach developers how to organize software artifacts. Behind the scene, this Selenium Capsules framework, is just an application of Decorator Pattern.



    Let us have a look of the WebDriver interface,



    WebDriver is an interface, it has many implementation classes such as FirefoxDriver, ChromeDriver, etc. When we use its method findElement(By), we may run into situation when the element is loaded by an Ajax request and we need to give instruction to the test to wait for a while until the element is displayed before calling it, otherwise, it would throw a NoSuchElementException. All these bullet plate codes will make the test code verbose and more difficult to find useful information. Thus it is desirable to attach additional logic to save developers from repeating this condition check and exception handling. However, since there are so many browsers implementation classes, it is not practical to extend all of them to add this waiting mechanism. Even if it is possible to extend those classes to add this override method, this method will be repeated in all of those concrete driver classes.



    Decorator Pattern, a 20 years old design pattern by 2014, effectively solved our problem.

    Another great tool is the default method in Java 8. In Java 8, some new methods are added to existing interface. As Java developers, we all know if a method is added to an interface, all implementing class need to be modified to add that new method, otherwise, it is a compilation error. That's why JDK interfaces have never changed. It is not the case any more. In Java 8, a new language feature is added to make it is possible to add new method into an existing interface, it is default method.

    Default methods are methods with implementations in the interface. You may wonder, will this change make an interface an abstract class? No, Interface is still not an abstract class, even interface can have default methods, but it can't have instance variables, so the default method can only call method of a local variable, or methods from the same interface, thus interfaces still can't replace abstract classes since abstract classes can have instance variables.

    An interface Browser, which extends WebDriver, is introduced into the framework to decorate WebDriver with additional responsibilities and it also provide default implementation of all the methods from WebDriver by just delegating the calls to WebDriver.



    What's the reason to spend so much effort to decorate WebDriver? Many people may have this doubt at the beginning. The reason is simple, to attach additional responsibility to address some common concerns. This method in Browser interface takes advantage of some Java 8 syntax and transforms all WebElements it finds into Element classes, also its parameter is Supplier, not a By class like the original findElements method in the WebDriver, this feature is called method overloading in Object Oriented languages,
        default public Stream<Element> findElements(Supplier<By> by) {
            return findElements(by.get()).stream().map(Element::new);
        }
    


    Stream, Supplier and Element::new are Java 8 features which is covered in this blog, functional selenium.

    Now that we decorated WebDriver to return Element object which is a decorator of WebElement, let us compare it the raw WebElement object to see what's the advantages it gives. Here are the methods of WebElement,



    We need to do the same thing as the decorator to WebDriver, to add new methods to the extended interface to have the following method,

        default public Stream<Element> findElements(Supplier<By> by) {
            return findElements(by.get()).stream().map(Element::new);
        }
    


    Ultimately, we decorated SearchContext and added many new search methods to locate popular web widgets,by the assistance from default methods from Java 8, all those new methods are available to the implementation classes without extra effort for implementing them.

    public interface Searchable<Where extends Searchable<Where>> extends SearchContext, Waitable<Where> {
    
        /**
         * Find the first element or throw NoSuchElementException
         *
         * @param by selector
         * @return the first element or throw NoSuchElementException
         */
        @Override
        Element findElement(By by);
    
        /**
         * Find the first element or return null if nothing found.
         *
         * @param by selector
         * @return the first element or return null if nothing found.
         */
        default public Element tryElement(By by) {
            try {
                return new Element(findElement(by));
            } catch (NoSuchElementException e) {
                return null;
            }
        }
    
        /**
         * Find the first element or throw NoSuchElementException
         *
         * @param by selector
         * @return the first element or throw NoSuchElementException
         */
        default public Element untilFound(final By by) {
            return until((Where page) -> new Element(findElement(by)));
        }
    
        /**
         * Find all elements within the area using the given search method.
         *
         * @param by selector
         * @return A stream of all {@link Element}s, or an empty stream if nothing matches.
         * @see org.openqa.selenium.By
         */
        default public Stream<Element> findElements(Supplier<By> by) {
            return findElements(by.get()).stream().map(Element::new);
        }
    
        /**
         * Find the first button meeting the By method.
         * method to find the button.
         *
         * @param by selector
         * @return
         */
        default public Clickable button(Supplier<By> by) {
            return button(by, 0);
        }
    
        /**
         * If there are multiple buttons with the same name on the same page, use this
         * method to find the button.
         *
         * @param by    selector
         * @param index
         * @return
         */
        @SuppressWarnings("unchecked")
        default public Clickable button(Supplier<By> by, int index) {
            return new Button<>((Where) this, Locators.<Where>elements(by)
                    .and(new StreamToList<>())
                    .and(new ElementAtIndex<>(index)));
        }
    
        /**
         * If the button can't be found using the previous two methods, use this.
         *
         * @param locator
         * @return
         */
        @SuppressWarnings("unchecked")
        default public Clickable button(Locator<Where, Element> locator) {
            return new Button<>((Where) this, locator);
        }
    
        /**
         * The first image using the image file.
         *
         * @param fileName
         * @return
         */
        default public Element image(String fileName) {
            return new FirstItem<Element>().locate(images(fileName));
        }
    
        /**
         * The image at the given index using the same image file.
         *
         * @param fileName
         * @param index
         * @return
         */
        default public Element image(String fileName, int index) {
            return new StreamToList<Element>()
                    .and(new ElementAtIndex<>(index))
                    .locate(images(fileName));
        }
    
        /**
         * Find the images using the same image file.
         *
         * @param fileName
         * @return the images  using the same image file.
         */
        default public Stream<Element> images(String fileName) {
            return until(Locators.<Where>elements(IMG)
                            .and(new Filter<>(DISPLAYED.and(SRC.and(new StringContains(fileName)))))
            );
        }
    
        /**
         * Find the link using the selector.
         *
         * @param selector
         * @return
         */
        @SuppressWarnings("unchecked")
        default public Clickable link(Supplier<By> selector) {
            return new Link<>((Where) this, element(selector));
        }
    }
    
    


    The new method tryElement will return null if it can't find the element immediately, and untilFound method will wait until timeout so either returns the element it is looking for or throw an NoSuchElementException. These new methods make it easier for testing AJAX enabled web applications which requires the extensive usages of try and wait.

        /**
         * Find the first element or return null if nothing found.
         *
         * @param by selector
         * @return the first element or return null if nothing found.
         */
        default public Element tryElement(By by) {
            try {
                return new Element(findElement(by));
            } catch (NoSuchElementException e) {
                return null;
            }
        }
    
        /**
         * Find the first element or throw NoSuchElementException
         *
         * @param by selector
         * @return the first element or throw NoSuchElementException
         */
        default public Element untilFound(final By by) {
            return until((Where page) -> new Element(findElement(by)));
        }
    


    The untilFound method effectively added support for Explicit Waits without cluttering your tests with millions lines of code like this,

       // ☹ this is a bad example, please don't follow the style.
       FluentWait<By> fluentWait = new FluentWait<By>(By.tagName("TEXTAREA"));  \\ define element for which you want to poll
       fluentWait.pollingEvery(300, TimeUnit.MILLISECONDS); \\ it will ping for every 3 sec
       fluentWait.withTimeout(1000, TimeUnit.MILLISECONDS);  \\ max time out
       fluentWait.until(new Predicate<By>() {
          public boolean apply(By by) {
             try {
               return browser.findElement(by).isDisplayed();
             } catch (NoSuchElementException ex) {
               return false;
             }
          }
       });
       browser.findElement(By.tagName("TEXTAREA")).sendKeys("text to enter");
    
    You can use call this method to enter data into a text input field on form,

       page.put(() -> By.tagName("TEXTAREA"), "text to enter");
    
    Element is an implementation class of both WebElement and Searchable interfaces, thus, it can be used as source and target elements of a drag and drop action.

        @Test
        public void testDragAndDrop() {
            Element source = CHROME.findElement(By.name("source"));
            Element target = CHROME.findElement(By.name("target"));
            Actions actions = new Actions(CHROME);
            actions.dragAndDrop(source, target);
        }
    
    It is cleaner if you use this method, it has the same effect as the one above.

        @Test
        public void testDragAndDrop() {
            CHROME.dragAndDrop(id("drag1"), id("div2"));
        }
    


    As a comparison, 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 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();
        }
    
    


    From these examples, it is clear that the tests written using Selenium Capsules framework are much cleaner than the tests written using Selenium directly. This is the reason to decorate Selenium and the sole purpose of Selenium Capsules, to encapsulate selenium powder into clean capsules and make it easy to serve.

    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