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/


    No comments:

    Post a Comment