Monday, October 15, 2012

design of page (500 mcg)

Selenium is used for functional tests of a web application. The purpose of tests are to verify the expected behavior of the pages under certain usage which are human interactions such as typing, clicking, etc.

A page is a collective concepts describe what's displayed on that page,
  • a Button,
  • a list of Buttons,
  • a Header,
  • a list of Links,
  • a list of input,
  • a list of images,etc.
  • Or some actions it can do for you, such as accepting the alert box, move mouse to here and there, find this and find that.

    In the post of "Design of DatePicker", there is some snippet of test code to test the behavior of the jQuery DatePicker on this page, http://jqueryui.com/datepicker/, the complete class is here,

    /**
     *
     * Copyright (c) 2012, Algocrafts, Inc. All rights reserved.
     * Apache License version 2.
     */
    package com.algocrafts.calendar;
    
    import com.algocrafts.calendar.core.DatePicker;
    import com.algocrafts.calendar.jqury.JQueryCalendar;
    import com.google.common.base.Function;
    import org.junit.Before;
    import org.junit.Test;
    import org.openqa.selenium.By;
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.WebElement;
    import org.openqa.selenium.firefox.FirefoxDriver;
    
    import static com.algocrafts.calendar.month.CapitalizedMonths.September;
    import static org.junit.Assert.assertEquals;
    import static org.openqa.selenium.By.id;
    
    public class JQueryCalendarTest {
    
        WebDriver webDriver;
        DatePicker jQueryCalendarDatePicker;
    
        @Before
        public void setup() {
            webDriver = new FirefoxDriver();
            webDriver.get("http://jqueryui.com/datepicker/");
            jQueryCalendarDatePicker = new DatePicker(
               new JQueryCalendar(webDriver, new JqueryCalendarTriggerFinder())
            );
        }
    
        @Test
        public void testPickADate() {
            jQueryCalendarDatePicker.pick(September, 30, 1999);
            assertEquals("09/30/1999",   webDriver.findElement(id("datepicker")).getAttribute("value"));
        }
    
        private class JqueryCalendarTriggerFinder
          implements Function<WebDriver, WebElement> {
            @Override
            public WebElement apply(WebDriver webDriver) {
                WebElement iframe = webDriver.findElement(By.tagName("iframe"));
                return webDriver.switchTo().frame(iframe).findElement(id("datepicker"));
            }
        }
    } 

    The test is well written and it seems very clean. It follows normal jUnit practice. Most people would like this test. And most tests are written this way.

    There are some inadequateness of the test. What's lacking is a little bit of design. While mixing the test specification with the details of how the testing is conducted. It makes the test more a technical artifact, thus not well suited for Behavior Driven Development.

    There is another way of organizing Selenium tests, by introducing a Page model. The Page model is also an application of Domain Driven Design principle. This approach separates the concern of What to do and How to do it. It makes the tests more domain specific thus more readable.

    Let us have a look of the modified test,

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = {"/appContext.xml"})
    public class JQueryDatePickerTest extends AbstractPageTest {
    
        @Autowired
        JQueryDatePickerPage jQueryDatePickerPage;
    
        @Before
        public void setup() {
            jQueryDatePickerPage.open();
        }
    
    
        @Test
        public void start() {
            jQueryDatePickerPage.pick(APRIL, 1, 2012);
            assertEquals("04/01/2012",jQueryDatePickerPage.getDate());
        }
    
        @After
        public void close() {
            jQueryDatePickerPage.close();
        }
    }
    
    

    In Java language, one way of measure the complexity of the class is to counting the import statements at the top. The new test only has 4 import statements and 3 lines of real code and there is nothing about Selenium WebDriver,  it is much simpler than the previous version of the test.

    The JQueryCalendarPage class used by the test is listed below, it describes the properties and behavior of the page. Even it is incomplete yet but it illustrates its purpose. By encapsulating page related behavior into a page object, we increased the cohesion of the design.
    To fully understand it, you need to have knowledge of lambda expression in Java 8.
    package com.jquery;
    
    
    import com.algocrafts.calendar.DatePicker;
    import com.algocrafts.calendar.JQueryCalendar;
    import com.algocrafts.clickable.Clickable;
    import com.algocrafts.decorators.AbstractPage;
    import com.algocrafts.decorators.Browser;
    import com.algocrafts.locators.ElementLocator;
    
    import static com.algocrafts.converters.GetText.TEXT;
    import static com.algocrafts.converters.GetText.VALUE;
    import static com.algocrafts.converters.TextEquals.DATEPICKER;
    import static com.algocrafts.searchmethods.ById.CONTENT;
    import static com.algocrafts.searchmethods.ById.DATE_PICKER;
    import static com.algocrafts.searchmethods.ByTagName.H1;
    
    public class JQueryDatePickerPage extends AbstractPage {
    
        public JQueryDatePickerPage(Browser browser, Clickable clickable) {
            super(browser, clickable,
                page -> new ElementLocator<AbstractPage>(CONTENT)
                    .and(new ElementLocator<>(H1))
                    .and(TEXT)
                    .and(DATEPICKER)
                    .apply(page)
            );
        }
    
        private final DatePicker datepicker = new DatePicker(
            new JQueryCalendar(this,
                page -> new ElementLocator<AbstractPage>(DATE_PICKER)
                    .apply(page.frame(0))
            )
        );
    
        public void pick(Enum month, int day, int year) {
            datepicker.pick(month, day, year);
        }
    
        public String getDate() {
            return new ElementLocator<AbstractPage>(DATE_PICKER)
                .and(VALUE)
                .apply(this);
        }
    
    }
    


    One more advantage of this design is the same page object can also used in a jBehave test step,

    /**
     *
     * Copyright (c) 2012, Algocrafts, Inc. All rights reserved.
     * Apache License version 2.
     */
    package com.algocrafts.calendar;
    
    import org.jbehave.core.annotations.Given;
    import org.jbehave.core.annotations.Then;
    import org.jbehave.core.annotations.When;
    import org.junit.Before;
    import org.junit.Test;
    
    import static com.algocrafts.calendar.month.CapitalizedMonths.September;
    import static org.junit.Assert.assertEquals;
    
    public class JQueryCalendarSteps {
    
        JQueryCalendarPage page;
    
        @Given("User is on jQuery Calendar page.")
        public void setup() {
            page = new JQueryDatePickerPage();
        }
    
        @When("user pick $month, $day, $year from calendar")
        public void userPick(String month, int day, int year) {
            page.pick Month.valueOf(month), day, year
        }
    
        @Then("The input displays $date")
        public void verifyDateIs(String date) {
            assertEquals date, page.getDate()
        }
    } 
    

    which can be used to serve the test for a story written in jBehave story format,

     Given user is on jQuery Calendar page.
     When user pick $month, $day, $year from calendar
     |month    |day|year|
     |September|  1|2002|
     |July     | 31|1999|
     |December | 25|2018|
     Then the input displays $date
     |date      |
     |09/01/2002|
     |07/31/1999|
     |12/25/2018|
     
    Not only that, the same page can be used by a Grails Spock test as well,
    /**
     *
     * Copyright (c) 2012, Algocrafts, Inc. All rights reserved.
     * Apache License version 2.
     */
    package com.algocrafts.calendar
    
    import grails.plugin.spock.IntegrationSpec
    
    import static org.junit.Assert.assertEquals
    import static com.algocrafts.calendar.month.CapitalizedMonths.*;
    
    class JQueryCalendarPageSpec extends IntegrationSpec {
    
       JQueryCalendarPage page
    
       def "jQuery Calendar works as expected"(){
    
          given: page = new JQueryCalendarPage()
          when: page.setDate month, day, year
          then: assertEquals date, page.datDate()
    
          where:
    //     |---------|---|----|----------|
           |month    |day|year|date      |
           |September|  1|2002|09/01/2002|
           |July     | 31|1999|07/31/1999|
           |December | 25|2018|12/25/2018|
    //     |---------|---|----|----------|
       }
    }
    

    It is noticeable that the Page object model makes the design more versatile, the same test can be used to serve multiple testing flavors, those tests are more business friendly thus can be used to share the domain knowledge with business users, once business users get familiar with those test syntax, you may be able to convince them to provide pseudo test code. This improves the collaboration between developers and business users and in turn improve the agility of the project.

    Refrences:
    1. PageObject Pattern:  http://code.google.com/p/selenium/wiki/PageObjects
    2. jBehave: http://jbehave.org/
    3. jQuery: http://jquery.com/
    4. Grails Spock: http://code.google.com/p/spock/


    Saturday, October 13, 2012

    design of datepicker (1000 mcg)

    When testing web application, sometime it is necessary to simulate the clicking of a calendar icon and pick a date using JavaScript DatePicker.

    DatePicker is no difference from the other element on web page, usually after clicking the calendar icon, a calendar will be popup and most likely it is a division object on the same page, then user looks at the title of the Calendar and decides whether to click previous year or next year, previous month or next month and then click the day.

    They are many styles of the calendar right now, most popular JavaScript frameworks such as jQuery, dojo and ext-js provide their own implementations. However, even they have different look and feel, they all have these common properties and behaviors,

    1. an icon used as a trigger;
    2. display a calendar when the icon is clicked;
    3. Previous Year button(<<), optional;
    4. Next Year Button(>>), optional;
    5. Previous Month button(<);
    6. Next Month button(>);
    7. day buttons;
    8. Weekday indicators;
    9. Calendar title i.e. "September, 2011" which representing current month and current year.

    Based on these properties and associated behaviors, we can design a generic DatePicker class to be used by all selenium based tests.

    /**
     *
     * Copyright (c) 2012, Algocrafts, Inc. All rights reserved.
     * Apache License version 2.
     */
    package com.algocrafts.calendar;
    
    /**
     * A general purpose DatePicker can be used to pick a given date from
     * the calendar flyout provided by JavaScript framework.
     *
     * @author Yujun Liang
     * @since 0.1
     */
    public class DatePicker {
    
        private final Calendar calendar;
    
        /**
         * Constuctor of the DatePicker which taking a Calendar interface.
         *
         * @param calendar
         */
        public DatePicker(Calendar calendar) {
            this.calendar = calendar;
        }
    
        /**
         * Pick a date by the given parameter.
         * for example,
         * datePicker.pick(AMonthEnum.July, 31, 1999) or for another style,
         * datePicker.pick(AnotherMonthEnum.JULY, 31, 1999), or
         * datePicker.pick(AbbreviatedMonthEnum.Sep, 31, 1999)
         * datePicker.pick(AnotherAbbreviatedMonthEnum.SEPT, 31, 1999)
         * the month just need to be same as the text on the calendar, if it looks like
         * Sep. on the calendar, you can either strip out the dot before the enum valueOf
         * call, or define an enum like this Sep("Sep.") and then provide a lookup map to
         * resolve the enum Sep from "Sep.".
         *
         * @param month, it need to be defined as an enum to make the code cleaner.
         * @param day,   an integer representing the day appearing on the calendar
         * @param year,  an ineger representing the year appearing on the calendar
         */
        public void pick(Enum month, int day, int year) {
            calendar.show();
            calendar.enterYear(year);
            calendar.enterMonth(month);
            calendar.pickDay(day);
        }
    
    
    The calendar is an interface, it can be implemented based on the DatePicker used by each project.

    /**
     *
     * Copyright (c) 2012, Algocrafts, Inc. All rights reserved.
     * Apache License version 2.
     */
    package com.algocrafts.calendar;
    
    /**
     * This is the interface describing the behaviour of a calendar flyout
     * which can be operated by a DatePicker.
     *
     * @author Yujun Liang
     * @since 0.1
     */
    public interface Calendar {
    
        /**
         * The action making the calendar visible.
         */
        void show();
    
        /**
         * 
         * @return current year displayed on Calendar.
         */
        int currentYear();
    
        /**
         * @return the ordinal of the enum instance for current month on Calendar.
         */
        int currentMonth();
    
        /**
         * Clicking next year button once, or clicking the next month button
         * 12 times if the next year button is not present on the calendar.
         */
        void nextYear();
    
        /**
         * Clicking next month button once.
         */
        void nextMonth();
    
        /**
         * Clicking previous month button once.
         */
        void previousMonth();
    
        /**
         * Clicking previous year button once, or clicking the previous month
         * button 12 times if the next year button is not present on the calendar.
         */
        void previousYear();
    
        /**
         * Some calendar allows user to select a year from a dropdown(select) or
         * enter a value from an input field. This method is to cater that function.
         * @param year
         */
        void enterYear(int year);
    
        /**
         * Some calendar allows user to select a month from a dropdown(select) or
         * enter a value from an input field. This method is to cater that function.
         * @param month
         */
        void enterMonth(Enum month);
    
        /**
         * After flipping the calendar to the month user is picking, clicking
         * the day button.
         * @param day
         */
        void pickDay(int day);
    }
    

    Some calendar does provide direct method to enter year and month, so we need a general purpose Flipping Method to click the buttons to select year and month.
    /**
     *
     * Copyright (c) 2012, Algocrafts, Inc. All rights reserved.
     * Apache License version 2.
     */
    package com.algocrafts.calendar;
    
    /**
     * For some calender, if no direct handle allow you to change year and month,
     * user need to click the year and month button to flip the calendar to the
     * desired year and month. This class provide such functionality.
     */
    public class FlippingMethod {
    
        private final Calendar calendar;
    
        /**
         * Associate this with the calendar.
         *
         * @param calendar
         */
        public FlippingMethod(Calendar calendar) {
            this.calendar = calendar;
        }
    
        /**
         * flip to the year like counting the click by heart with eye closed to save the time
         * of reading the calender again since reading the calendar is more expensive then
         * maitaining a counter.
         *
         * @param year
         */
        public void flipToYear(int year) {
            int yearDiffrence = calendar.currentYear() - year;
            if (yearDiffrence < 0) {
                for (int i = yearDiffrence; i < 0; i++) {
                    calendar.nextYear();
                }
            } else if (yearDiffrence > 0) {
                for (int i = 0; i < yearDiffrence; i++) {
                    calendar.previousYear();
                }
            }
        }
    
        /**
         * flip to the month like counting the click by heart with eye closed to save the time
         * of reading the calender again since reading the calendar is more expensive then
         * maitaining a counter.
         *
         * @param month
         */
        public void flipToMonth(Enum month) {
            int monthDifference = calendar.currentMonth() - month.ordinal();
            if (monthDifference < 0) {
                for (int i = monthDifference; i < 0; i++) {
                    calendar.nextMonth();
                }
            } else if (monthDifference > 0) {
                for (int i = 0; i < monthDifference; i++) {
                    calendar.previousMonth();
                }
            }
        }
    }
    
    
    There is no Selenium used in DatePicker, Calendar and FlippingMethod at all. That's right, this is called Dependency Injection. You may have already heard of this pattern if you have worked on any project using Spring framework or read the blog from Martim Fowler. The following implementation can be injected into DatePicker during runtime and you can inject different Calendar depending on what JavaScript library you are using. You don't even need to use WebDriver, you can still implement your calendar using Selenium-RC which has been deprecated in favor of new WebDriver API in Selenium 2, which is another example of the same principle.

    Here is the actual implementation using Selenium WebDriver api against this Datepicker on jQuery's website,  http://jqueryui.com/datepicker/

    /**
     *
     * Copyright (c) 2012, Algocrafts, Inc. All rights reserved.
     * Apache License version 2.
     */
    package com.algocrafts.calendar;
    
    import com.google.common.base.Function;
    import com.google.common.base.Predicate;
    import org.openqa.selenium.By;
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.WebElement;
    import org.openqa.selenium.support.ui.WebDriverWait;
    
    import java.util.List;
    
    import static com.algocrafts.calendar.JQueryCalendar.Months.valueOf;
    import static com.google.common.base.Predicates.not;
    import static java.lang.Integer.parseInt;
    import static org.openqa.selenium.By.className;
    import static org.openqa.selenium.By.tagName;
    
    /**
     * This is the reference implmetnation of the Calendar interface which can be
     * operated by a DatePicker.
     * The location of the date picker is here,
     * http://jqueryui.com/datepicker/
     *
     * @author Yujun Liang
     * @since 0.1
     */
    public class JQueryCalendar implements Calendar {
    
        /**
         * An enum of all months used by that calendar
         */
        public enum Months {
            January,
            February,
            March,
            April,
            May,
            June,
            July,
            August,
            September,
            October,
            November,
            December;
        }
    
        /**
         * Constructor of the JQueryCalendar, an active WebDriver and a search
         * criteria of the trigger element.
         *
         * @param webDriver
         * @param trigger
         */
        public JQueryCalendar(WebDriver webDriver, Function<WebDriver, WebElement> trigger) {
            this.webDriver = webDriver;
            this.trigger = trigger;
        }
    
        private final WebDriver webDriver;
        private final Function<WebDriver, WebElement> trigger;
    
            @Override
        public void show() {
            trigger.apply(webDriver).click();
            new WebDriverWait(webDriver, 60).until(new CalendarIsDisplayed());
        }
    
        @Override
        public int currentYear() {
            return parseInt(calendar().findElement(className("ui-datepicker-year")).getText());
        }
    
        @Override
        public int currentMonth() {
            return valueOf(
                       calendar().findElement(className("ui-datepicker-month")).getText()
                   ).ordinal();
        }
    
        @Override
        public void previousYear() {
            for (int i = 0; i < 12; i++) {
                previousMonth();
            }
        }
    
        @Override
        public void previousMonth() {
            calendar().findElement(className("ui-datepicker-prev")).click();
        }
    
        @Override
        public void nextYear() {
            for (int i = 0; i < 12; i++) {
                nextMonth();
            }
        }
    
        @Override
        public void nextMonth() {
            calendar().findElement(className("ui-datepicker-next")).click();
        }
    
        @Override
        public void enterYear(int year) {
            flippingMethod.flipToYear(year);
        }
    
        @Override
        public void enterMonth(Enum month) {
            flippingMethod.flipToMonth(month);
        }
    
        @Override
        public void pickDay(int day) {
            List<WebElement> dayButtons =
                    calendar().findElement(
                            className("ui-datepicker-calendar")
                    ).findElements(tagName("td"));
            for (WebElement dayButton : dayButtons) {
                if (dayButton.getText().equals(String.valueOf(day))) {
                    dayButton.click();
                    new WebDriverWait(webDriver, 60).until(not(new CalendarIsDisplayed()));
                }
            }
        }
    
        private WebElement calendar() {
            return webDriver.findElement(By.id("ui-datepicker-div"));
        }
    
        /**
         * Predicate needed by WebDriverWait for Calendar to become visible
         * it can be used for Calendar to become invisible as well, simply
         *   Predicates.not(new CalendarIsDisplayed()) as used in method 
         *   pickDay.
         */
        private class CalendarIsDisplayed implements Predicate<WebDriver> {
            @Override
            public boolean apply(WebDriver webDriver) {
                return calendar() != null && calendar().isDisplayed();
            }
        }    
    }
    
    In your test, where you have access to Selenium WebDriver webDriver, you can simply instantiate the DatePicker,
    jQueryCalendarDatePicker = new DatePicker(
                                   new JQueryCalendar(
                                       webDriver, 
                                       new JQueryCalendarTriggerFinder()
                                   )
                               );
        
    jQueryDatePicker.pick(July, 23, 1999);
    jQueryDatePicker.pick(September, 2, 2018);
    
    These classes have this relationship as illustrated by the following class diagram,
    The test ran and passed, it took a while to go back to 1999 since there is no previous year button available so the behavior of the previous year is actually implemented by clicking previous month 12 times.
    If you noticed, the parameter for a date is in 3 parts, an enum of the month, an integer day and integer year, the reason behind it is to make the code more readable, it is the application of Domain Driven Design principle, Ubiquitous Language. I prefer it over a java.util.Date as the parameter. Also, if I had chosen to use java.util.Date as the parameter, the implementation of the DatePicker would have been more complex. As an exercise, you can try that at home.

    If you use another JavaScript framework, the calendar may have different elements, you can implement another Calendar and use the same DatePicker to pick any Date in the range provided by the calendar.

    Dojo,
    DatePicker dojoDatePicker = 
       new DatePicker(new DojoCalendar(webDriver, new DojoCalendarTriggerFinder()));
    dojoDatePicker.pick(July, 23, 1999);
    dojoDatePicker.pick(September, 2, 2018);
    
    public class DojoCalendar implements Calendar {
       ... //Jonathan, sorry for using ...   
    }
    
    YUI,
    DatePicker yuiDatePicker = 
       new DatePicker(new YuiCalendar(webDriver,new YuiCalendarTriggerFinder()));
    
    yuiDatePicker.pick(July, 23, 1999);
    yuiDatePicker.pick(September, 2, 2018);
    
    public class YuiCalendar implements Calendar {
       ... //sorry again.   
    }
    
    Here is another style of the calendar,
    since it provides the direct method to pick year and month, the implementation is slightly different from JQueryCalendar, it uses a Select to change the year. So in this class, it doesn't need to instantiate an instance of FlippingMethod.
        @Override
        public void enterYear(int year) {
            yearSelect().selectByVisibleText(String.valueOf(year));
        }
    
        /**
         * //TODO: use firebug to find out THE NAME OF THE SELECT OF YEAR  
         * @return
         */
        private Select yearSelect() {
            return new Select(webDriver.findElement(By.name("THE NAME OF THE SELECT OF YEAR")));
        }
    
        @Override
        public void enterMonth(Enum month) {
            monthSelect().selectByVisibleText(month.name());
        }
    
        /**
         * //TODO: use firebug to find out THE NAME OF THE SELECT OF MONTH  
         * @return
         */
        private Select monthSelect() {
            return new Select(webDriver.findElement(By.name("THE NAME OF THE SELECT OF MONTH")));
        }
    
    Why bother to design this DatePicker class with an interface, not just use inheritance with an abstract PickPicker for shared logic such as flipping calendar to desired year and month?

    References:
    1. jQuery : http://jquery.com/
    2. Selenium : http://seleniumhq.org/
    3. Martin Fowler : http://martinfowler.com/
    4. Domain Driven Design : http://www.domaindrivendesign.org/