Saturday, 21 September 2013

Avoiding StaleElementReferenceException

Have you ever got this annoying exception when invoking any method from WebElement interface? 

If answer is positive it means that inner DOM of element is highly dynamic and is changing between the moment of getting webelement object and it's actual usage. Some people might complain that "tests are not stable" or even reuse popular phrase "Flakey Tests" ... but let's see how we can solve it
  protected Boolean isDisplayed(int index) {
    try {
      return driver.findElements(By.cssSelector(".listElement")).get(index).isDisplayed();
    } catch (StaleElementReferenceException e) {
      return isDisplayed(index);
    }
  }
making recursive call helps simulate some kind of wait for webdriver ready DOM

Wednesday, 18 September 2013

Step Object as pattern

Step is a set of sequential page interactions wrapped into a business meaning

  1. Should never be a verification / assertion 
  2. Should be used as a preconditions provider for test data / state
  3. Do not overlap steps, make them sequential 
  4. Follow principles of public API creation 
  5. Try to design Step API beforehand depending on a business logic.
what else? 

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 

Wednesday, 20 February 2013

Selenium Camp 2013, Kyiv, Ukraine

    Having not missed a single camp I consider it as the greatest event for IT community who's either implicitly or explicitly related to quality assurance automation. I remember the time when I searched for a tool which can give me at least tiny API to manage real browser. I think I wasn't lonely in this willing. Selenium / WebDriver is something outstanding, has changed development life cycle from within influencing nearly each software development aspect apart from testing.
    It has brought up "test automation frameworking" abilities, which comparing to famous standalone automation tools attracts developers to testing problems and makes quality assurance much more team oriented.
   Selenium Camp gives a push to each participant and even speaker to evolve, structure up his work and meet people with same way of thinking

This yeah I am going to speak about Step Object



You will definitely see presentations about each layer of a cake by other speakers :) 







And you're kindly invited



Friday, 15 February 2013

IE configuration for webdriver

Recently I've found a great description of IE configuration, not for webdriver specifically though ... yet it still valid to follow
IE Configuration by Telerik