Monday, January 21, 2013

horizontally refactor selenium example using page (100 mcg)

When people talk about Refactoring, most of time, it is about Vertical Refactoring as laid out in the Refactoring book by Martin Fowler. Actually, there is another kind of refactoring, Horizontal Refactoring, which is quite useful in writing more comprehensible code.

Please take a look of the example in this page,

http://code.google.com/p/selenium/wiki/PageObjects Do you see duplicated code?
// ☹ this is a bad example, please don't follow the style.
public class LoginPage {
    private final WebDriver driver;

    public LoginPage(WebDriver driver) {
        this.driver = driver;

        // Check that we're on the right page.
        if (!"Login".equals(driver.getTitle())) {
            // Alternatively, we could navigate to the login page, perhaps logging out first
            throw new IllegalStateException("This is not the login page");
        }
    }

    // Conceptually, the login page offers the user the service of being able to "log into"
    // the application using a user name and password. 
    public HomePage loginAs(String username, String password) {
        // This is the only place in the test code that "knows" how to enter these details
        driver.findElement(By.id("username")).sendKeys(username);
        driver.findElement(By.id("passwd")).sendKeys(password);
        driver.findElement(By.id("login")).submit();
         
        // Return a new page object representing the destination. Should the login page ever
        // go somewhere else (for example, a legal disclaimer) then changing the method signature
        // for this method will mean that all tests that rely on this behaviour won't compile.
        return new HomePage(driver);
    }
}
Most people would tolerate this kind of duplicates and so far no tool can detect this kind of duplicates either. However, this coding style cluttered source code with too much information and slows down people's speed since there are too much code to read,

Here is refactored version of the same code, you would see much cleaner code base. I recommend using this style, achieved through Horizontal Refactoring.
//   ☺ This is an good example.
public class LoginPage extends AbstractPage{

    public HomePage loginAs(String username, String password) {
        put(USERNAME, username);
        put(PASSWORD, password);
        button(LOGIN).click();
        return new HomePage(this);
    }
}

These are the methods handling text input field,
 //   ☺ This is an good example.

    /**
     * Enter text into an input filed.
     *
     * @param method
     * @param value
     */
    default public void put(Supplier<By> method, Object value) {
        new Input<>((Where) this).put(method, value);
    }

    /**
     * Autocomplete for text field and return the first found suggestion match the whole word.
     *
     * @param method
     * @param value
     * @param locator
     */
    default public void autocomplete(Supplier<By> method, Object value, Locator<Where, Element> locator) {
        new Input<>((Where) this).autocomplete(method, value, locator);
    }

New code is much cleaner.