Friday, 22 March 2013

Element Object - ExtJs Combobox

The topic is becoming more and more hot nowadays. Element object pretends to be evolved into the separate layer of any automation framework. And it highly depends on a UI framework chosen.
Frameworks like ExtJS have their internal way of generating ids, classes, etc ... and naturally to let webdriver operate with it you need to implement user conductible API
We've implemented it for some of extjs standard components like table, alert and combobox, which I'd like to share here

public class ExtJsComboBox {

  private static final String COMBO_EXT_JS_OBJECT = "var id = arguments[0].id.replace('-inputEl', '')" +
          ".replace('-triggerWrap','');var el = Ext.getCmp(id);";
  private static final By BOUND_LIST_LOCATOR = By.cssSelector("li.x-boundlist-item");
  private WebElement element;
  private WebElement input;
  private String listDynId = null;
  private static final By TEXT_INPUT_LOCATOR = By.cssSelector("input.x-form-field.x-form-text");
  private final WebDriver driver;
  private WebDriverWait wait;

  protected String getListDynId() {
    return listDynId;
  }

  /**
   * sets id of generated list with combobox options
   */
  protected void setListDynId() {
    listDynId = (String) ((JavascriptExecutor) driver).executeScript(
            COMBO_EXT_JS_OBJECT + "el.expand(); return el.listKeyNav.boundList.id;"
            , getTextInput());
  }

  /**
   * @param elementContainer - locator of either parent element which wraps text input and drop down button or text input
   */
  public ExtJsComboBox(WebDriver driver, WebElement elementContainer) {
    this.driver = driver;
    wait = new WebDriverWait(driver, 5);
    setElement(elementContainer);
  }

  private void setElement(WebElement el) {
    element = el;
  }

  /**
   * sends arrow key to text box
   */
  public void retrieveOptions() {
    sendKeys(Keys.ARROW_DOWN);
  }

  /**
   * @param optionToChoose - option to choose and additional keys to send
   */
  public void chooseOption(CharSequence... optionToChoose) {
    chooseOption(optionToChoose[0].toString());
    for (int i = 1; i < optionToChoose.length; i++) {
      getTextInput().sendKeys(optionToChoose[i]);
    }
  }
Main magic is probably here, at chooseOption() method
it iterates among option list looking for a partial match avoiding staleness of element which is often the case
  /**
   * chooses option if one is present
   * @param optionToChoose - partial text to find among options
   */
  public void chooseOption(final String optionToChoose) {
    clear();
    if (getListDynId() == null) {
      setListDynId();
    }
    List<WebElement> optionList = getOptionElements();
    if (optionList.size() == 0) {
      sendKeys(optionToChoose);
      new WebDriverWait(driver, 2, 200).until(
              new ExpectedCondition<Boolean>() {
                @Override
                public Boolean apply(final WebDriver webDriver) {
                  return isDirty();
                }
              }
      );
    }

    wait.until(ExpectedConditions.elementToBeClickable(By.cssSelector("#" + getListDynId() + " li.x-boundlist-item")));

    for (int i = 0; i < optionList.size(); i++) {
      String actualOption;
      try {
        actualOption = optionList.get(i).getText().toLowerCase();
      } catch (StaleElementReferenceException e) {
        optionList = getOptionElements();
        i--;
        continue;
      }
      if (actualOption.contains(optionToChoose.toLowerCase())) {
        optionList.get(i).click();
        collapseDropDown();
        wait.until(ExpectedConditions.invisibilityOfElementLocated(By.id(getListDynId())));
        break;
      }
    }
  }

  private Boolean isDirty() {
    return (Boolean) ((JavascriptExecutor) driver).executeScript(
            COMBO_EXT_JS_OBJECT + " return el.isDirty();"
            , getTextInput());
  }

  private void collapseDropDown() {
    ((JavascriptExecutor) driver).executeScript(
            COMBO_EXT_JS_OBJECT + " el.collapse();"
            , getTextInput());
  }

  /**
   * @return list of available options
   */
  public List<String> getOptions() {
    retrieveOptions();
    List<String> optionStrList = new ArrayList<String>();
    List<WebElement> optionList = getOptionElements();
    for (WebElement option : optionList) {
      optionStrList.add(option.getText().trim().toLowerCase());
    }
    retrieveOptions();
    return optionStrList;
  }

  /**
   * @return list of available option elements
   */
  private List<WebElement> getOptionElements() {
    return getListContainer().findElements(BOUND_LIST_LOCATOR);
  }

  /**
   * @return web element by dynamic id of reloaded list
   */
  private WebElement getListContainer() {
    if (getListDynId() == null) {
      setListDynId();
    }
    return driver.findElement(By.id(getListDynId()));
  }

  private WebElement getTextInput() {
    if (input == null) {
      if (!element.getTagName().equals(HTML.Tag.INPUT.toString())) {
        input = element.findElement(TEXT_INPUT_LOCATOR);
      } else {
        input = element;
      }
    }
    return input;
  }

  public void clear() {
    getTextInput().clear();
  }

  public String getAttribute(String arg0) {
    return getTextInput().getAttribute(arg0);
  }

  public void sendKeys(CharSequence... arg0) {
    getTextInput().sendKeys(arg0);
  }
}

There is also multi selection combobox which behaves in a bit different way. You can easily extend this one or just put a comment here and I'll post it
it is important to highlight that main actions are done by webdriver, not js, which makes it more user alike. Though it was impossible to avoid js completely - it would impact performance and stability a lot.

Saturday, 2 March 2013

Step Objects @ Selenium Camp 2013

Selenium Camp 2013 is much more mature comparing to previous ones, regardless of hearing an explanation what the page object is 5+ times. Speakers were great and regarded their topics in a simple and affordable manner, having told you by far not primitive things though.
Appreciating a lot XPInjections for all the efforts they have made to make this event happening, I'd like to share slides (and video as soon as I have it) of my speech.


and the github demo project example you'd probably intent to try out is here
please, feel free to comment it on and ask questions, I'll try to answer in short notice