Friday, 23 November 2012

Get type of control element by WebElement

I reckon that is useful class:
it gets WebElement and returns the type of html control corresponding to it. Implemented using W3C, some tags are absent, yet you're free to extend

public class ControlElement {
  private final Type type;
  
  public enum Type {
    BUTTON, RADIO, TEXT_AREA, SELECT, OPTION, TEXT, LINK, CHECKBOX
  }

  private static final String BUTTON = "button";
  private static final String RADIO = "radio";
  private static final String TEXT_AREA = "textarea";
  private static final String CHECKBOX = "checkbox";
  private static final String SELECT = "select";
  private static final String OPTION = "option";
  private static final String TEXT = "text";
  private static final String LINK = "a";
  
  private static final String NULL = "null";
  private static final String INPUT = "input";
  private static final String PASSWORD = "input";
  private static final String SUBMIT = "submit";
  private static final String HIDDEN = "hidden";
  private static final String RESET = "reset";
  private static final String SELECT_ONE = "select-one";

  private final Map<Entry<String, String>, Type> map =
      new HashMap<Entry<String, String>, Type>();

  public ControlElement(WebElement element) {
    initMaping();
    String tagName = element.getTagName().toLowerCase();
    String typeAttribute = element.getAttribute("type");
    Entry<String, String> key = new AbstractMap.SimpleEntry<String, String>(tagName, typeAttribute);

    if (map.containsKey(key)) {
      type = map.get(key);
    } else {
      type = null;
    }
  }

  private void initMaping() {
    // link
    map.put(new AbstractMap.SimpleEntry<String, String>(LINK, NULL), Type.LINK);
    // button
    map.put(new AbstractMap.SimpleEntry<String, String>(BUTTON, NULL), Type.BUTTON);
    map.put(new AbstractMap.SimpleEntry<String, String>(BUTTON, SUBMIT), Type.BUTTON);
    map.put(new AbstractMap.SimpleEntry<String, String>(BUTTON, RESET), Type.BUTTON);
    map.put(new AbstractMap.SimpleEntry<String, String>(INPUT, SUBMIT), Type.BUTTON);
    map.put(new AbstractMap.SimpleEntry<String, String>(INPUT, BUTTON), Type.BUTTON);
    map.put(new AbstractMap.SimpleEntry<String, String>(INPUT, RESET), Type.BUTTON);
    // text input
    map.put(new AbstractMap.SimpleEntry<String, String>(INPUT, TEXT), Type.TEXT);
    map.put(new AbstractMap.SimpleEntry<String, String>(INPUT, PASSWORD), Type.TEXT);
    map.put(new AbstractMap.SimpleEntry<String, String>(INPUT, HIDDEN), Type.TEXT);
    // text area
    map.put(new AbstractMap.SimpleEntry<String, String>(TEXT_AREA, NULL), Type.TEXT_AREA);
    // checkbox
    map.put(new AbstractMap.SimpleEntry<String, String>(INPUT, CHECKBOX), Type.CHECKBOX);
    // radio
    map.put(new AbstractMap.SimpleEntry<String, String>(INPUT, RADIO), Type.RADIO);
    // select
    map.put(new AbstractMap.SimpleEntry<String, String>(SELECT, NULL), Type.SELECT);
    map.put(new AbstractMap.SimpleEntry<String, String>(SELECT, SELECT_ONE), Type.SELECT);
    // option
    map.put(new AbstractMap.SimpleEntry<String, String>(OPTION, NULL), Type.OPTION);
  }

  /**
   * @return the type of html control element
   */
  public Type getType() {
    return type;
  }
}

Wednesday, 21 November 2012

Web Page Object How To

That's been a while since I wrote last time :) Therefore, have lately decided to summarize more or less how to use Page Object properly just to keep the good note for myself.
First of all, let's remember the git of Page Object Pattern briefly. It is an abstract description of a page (web page in our case) or of its part that has got at least one "business value point". Technically, that is a class that has a set of fields and/or their getters/setters which describe the elements available on a page and a set of methods which describe actions you may perform with those elements. And that's it, not less, not more ...

That's the typical page object implementation using webdriver API: (of course you're free to use PageFactory instead of defining the locators as fields)
public class TypicalPageObject {
  private final WebDriver driver;
  
  private static final By ELEMENT_LOCATOR = By.id("element1");
  private static final String VALUE_LOCATOR = "value";

  public TypicalPageObject(WebDriver webdriver) {
    driver = webdriver;
  }

  public void setElementValue(String value) {
    getElement().sendKeys(value);
  }

  public void getElementValue() {
    getElement().getAttribute(VALUE_LOCATOR);
  }

  private WebElement getElement() {
    return driver.findElement(ELEMENT_LOCATOR);
  }
}

Let's analyse it a bit.

  • no public webelements - page object is the margin layer of webdriver API usage; it should not provide any public accessory to others unless it is some child page object
  • WebDriver is defined as constructor parameter - it makes the page object independent from webdriver, though I not entirely agree it should be applied as a pattern - you may initialize driver wherever you want and encapsulate it in page object class. Though, Thucydides throws WrongPageException and says "the page object looks dodgy" if it's constructor does not contain parameter with WebDriver type. That was quite a surprise for me.
  • at least getter - that's the part of "not less" ... you probably may have an element which is read-only
  • no DSL within page object - that's the part of "not more" ... DSL is the higher level of abstraction and by far has nothing to do with page object. Though it is usually implemented invoking the page object. No createUserAccout(), populatePurchaserForm() etc ...
  • No assertions - another part of "not more". Page object should provide the data for them only. 
That's basically it ...
You may also extend the page object with entity based approach. Let's say you have the entity: 

public class TypicalEntity {
    public static final String FIELD = "field";
    private String field;

    public String getField() {
      return field;
    }

    public void setField(String field) {
      this.field = field;
    }
  }

Then our typical page object might look like:

public class TypicalPageObject {
  private final WebDriver driver;
  
  private static final By ELEMENT_LOCATOR = By.id("element1");
  private static final String VALUE_LOCATOR = "value";

  private final Map<String, By> mapLocatorToField = new HashMap<String, By>();

  public TypicalPageObject(WebDriver webdriver) {
    driver = webdriver;
    mapLocatorToField.put(TypicalEntity.FIELD, ELEMENT_LOCATOR);
  }

  public void setElementValue(String field, String value) {
    getElement(field).sendKeys(value);
  }

  public void getElementValue(String field) {
    getElement(field).getAttribute(VALUE_LOCATOR);
  }

  private WebElement getElement(String field) {
    return driver.findElement(mapLocatorToField.get(field));
  }
}
In that case you have to map the accessory to webelements upon the field of an entity. It makes you put additional efforts to maintain entities, though page object becomes more generic.