API Docs for: 1.0
Show:

File: index.js

(function () {

"use strict";


// polyfill
if ( !Array.prototype.forEach ) {
  Array.prototype.forEach = function(fn, scope) {
    for(var i = 0, len = this.length; i < len; ++i) {
      fn.call(scope, this[i], i, this);
    }
  }
}

/**
 * Add properties from source object to target object
 */
function _extend (target, source) {
  for (var prop in source) {
    target[prop] = source[prop];
  }
}


/**
 * Provides several functions that make using WebDriver more 
 * convenient.
 * 
 * @example
 *     var webdriver = require('selenium-webdriver'),
 *         WebDriverHelpers = require('webdriver-helpers'),
 *         driver,
 *         helpers;
 *     
 *     driver = require('./drivers/selenium-server.js')(webdriver, {
 *       browserName: 'chrome',
 *       seleniumProtocol: 'WebDriver',
 *       'chrome.switches': ['--window-size=1366,768'] // this is being ignored
 *     });
 *
 *     driver.manage().timeouts().setScriptTimeout(5000);
 *     driver.manage().timeouts().implicitlyWait(5000);
 *
 *     helpers = new WebDriverHelpers(webdriver, driver);
 *
 *     function login (email, password) {
 *       helpers.populateElements({
 *         '#login-email': email,
 *         '#login-password': password
 *       });
 *
 *       helpers.id('login-submit-btn').click();
 *     }
 *
 * @class WebDriverHelpers
 * @param {Object} webdriver selenium-webdriver instance
 * @param {Object} driver browser driver instance
 * @return {Object} object with several convenience functions
 * @constructor
 */
var WebDriverHelpers = function (webdriver, driver) {
  this.flow = webdriver.promise.controlFlow();
  this.webdriver = webdriver;
  this.driver = driver;
};

_extend(WebDriverHelpers.prototype, {

  /**
   * Sugar for: this.driver.findElement(this.webdriver.By.name(args))
   *
   * @method name
   * @param {String} args
   * @return {Object} webdriver element
   */
  name: function (args) {
    return this.driver.findElement(this.webdriver.By.name(args));
  },

  /**
   * Sugar for: this.driver.findElement(this.webdriver.By.id(args))
   *
   * @example
   *     helpers.id('btnSubmit').click()
   * @method id
   * @param {String} args
   * @return {Object} webdriver element
   */
  id: function (args) {
    return this.driver.findElement(this.webdriver.By.id(args));
  },

  /**
   * Sugar for: this.driver.findElement(this.webdriver.By.tagName(args)
   *
   * @example
   *     var verifyOnCheckinPage = function () {
   *       helpers.tagName('h1').then(function (h1) {
   *           h1.getText().then(function (text) {
   *             expect(text).toBe("Check-In");
   *           });
   *       });
   *     };
   * @method tagName
   * @param {String} args
   * @return {Object} webdriver element
   */
  tagName: function (args) {
    return this.driver.findElement(this.webdriver.By.tagName(args));
  },

  /**
   * Sugar for: this.driver.findElement(this.webdriver.By.className(args))
   *
   * @method className
   * @param {String} args
   * @return {Object} webdriver element
   */
  className: function (args) {
    return this.driver.findElement(this.webdriver.By.className(args));
  },

  /**
   * Selects a drop-down option by value
   *
   * @method selectByValue
   * @param {webdriver.Locator|Object.<string>} locator The locator
   *     strategy to use when searching for the select element.
   * @param {string} value The value of the option to select
   */
  selectByValue: function (locator, value) {
    var selElement = this.driver.findElement(locator);
    selElement.findElement(this.webdriver.By.css("option[value='" + value + "']"))
      .click();
  },

  /**
   * Selects a radio button element by value
   *
   * @method radioByValue
   * @param {string} name The name of the radio button group
   * @param {string} value The value of the option to select
   */
  radioByValue: function (name, value) {
    var locator = "input[name='" + name + "'][value='" + value + "']";
    this.driver.findElement(this.webdriver.By.css(locator))
      .click();
  },

  /**
   * Pauses for a short amount of time, giving webdriver time
   * to re-render the page, if needed.
   *
   * @example
   *     var authenticate = function () {
   *       var name = 'Teacher User',
   *           email = 'teacher@example.com',
   *           password = 'password',
   *           deferred = webdriver.promise.defer(),
   *           i = 0;
   *
   *       helpers.populateElements({
   *         '#login-email': email,
   *         '#login-password': password
   *       });
   *       helpers.id('login-buttons-password').click();
   *       helpers.avoidStaleElement();
   *       helpers.id('display-name-link').getText()
   *         .then(function (text) {
   *           var expected = name;
   *           if (text.indexOf(expected) !== 0) {
   *             deferred.reject(new Error(text + ' did not contain ' + expected));
   *           } else {
   *             deferred.resolve();
   *           }
   *         });
   *
   *       return deferred.promise;
   *     };
   *
   * @method avoidStaleElement 
   */
  avoidStaleElement: function () {
    var i = 0;
    this.driver.wait(function () {
      i++;
      //console.log(i);
      return (i > 3);
    }, 3000);
  },

  /**
   * Easily set values of form elements by passing a map of 
   * css selector / element value pairs.
   *
   * @example
   *     // populate a radio button group, a select box, a text
   *     // input and a textarea
   *     var helpers = new WebDriverHelpers(webdriver, driver);
   *     helpers.populateElements({
   *       'input[name="gender"][value="M"]': 'M',
   *       '#country': "United States",
   *       '#age': '34',
   *       '#comment': "example msg"
   *     });
   *
   * @method populateElements
   * @param {Object} locatorValuePairs key/value pairs consisting of a css 
   *                    string locator for a form element and a string or 
   *                    array of strings to populate the element with.
   */
  populateElements: function (locatorValuePairs) {
    var key,
        value,
        locator,
        singleWord;

    for (key in locatorValuePairs) {
      value = locatorValuePairs[key];
      locator = { css: key };

      // optimize 'id' case
      singleWord = -1 === key.indexOf(' ');
      if (singleWord && 
          '#' === key.charAt(0)) {
        locator = { id: key.substring(1) };
      }

      //console.log("locator ", locator, ", value ", value);
      
      this.populateElement(locator, value);
    }
  },  // end populateElements 

  /**
   * Find first element in a group who's text matches parameter.
   *
   * @example
   *     var findPersonByName = function (nameOfPerson) {
   *       var collectionLocator = {
   *             'css':'div.people > .person'
   *           },
   *           criteriaLocator = {
   *             'className':'name'
   *           };
   *           
   *       return findElementInCollectionByText(
   *           collectionLocator,
   *           criteriaLocator,
   *           nameOfPerson
   *         );
   *     };
   *
   *     //...can be called like:
   *   
   *     it("can click person",
   *       function (done) {
   *         authenticate().
   *           then(findPersonByName("Jeff Winger")).
   *           then(clickPerson).
   *           then(finish(done), error);
   *       });
   *
   *     //...and will match:
   *
   *     <div class="people">
   *       <div class="person">
   *         <p class="name">Jeff Winger</p>
   *         <p class="pic">...</p>
   *       </div>
   *     </div>
   *
   * @method findElementInCollectionByText
   * @param {webdriver.Locator|Object.<string>} collectionLocator The locator 
   *     for the group of elements to search
   * @param {webdriver.Locator|Object.<string>} criteriaLocator The locator 
   *     for the element who's text should be compared, relative to collection
   * @param {string} text The text to search for.
   * @return {function} A function that, when executed, returns a promise that 
   *     will be resolved with the matching element.
   */
  findElementInCollectionByText: function (collectionLocator, criteriaLocator, text) {
    var self = this;

    return function () {
      var mainDefer = self.webdriver.promise.defer();

      self.driver.findElements(collectionLocator).
        then (function (collection) {

          var matchDefer = self.webdriver.promise.defer(),
              matchPromise = matchDefer.promise,
              resolved;
             
          //console.log('found elements');
          collection.map(function (item) {
            item.findElement(criteriaLocator).
              then (function (elem) {
                self.flow.execute(function () {
                  elem.getText().
                    then (function (value) {
                      //console.log('comparing ' + value);
                      if (value === text) {
                        resolved = true;
                        matchDefer.resolve(item);
                      }
                    });
                });
              });
          });

          self.flow.execute(function () {
            matchPromise.then(function (item) {
              //console.log('mainDefer.resolve');
              mainDefer.resolve(item);
            });
            //console.log('resolved? ' + resolved);
            if (!resolved) {
              throw new Error('failed to find target element ' + 
                JSON.stringify(collectionLocator) +
                JSON.stringify(criteriaLocator) +
                text);
            }
          });
          
        });  // end findElements.then
        return mainDefer.promise;
    };
  },  //  end findElementInCollectionByText

  /**
   * Populate textbox(es) with string value(s).  If 'strings' is an 
   * array, locator is assumed to match a group of textboxes which 
   * all have the same name.  
   *
   * This function performs a new find for each subsequent textbox 
   * in order to support dynamically added inputs.  For example, when
   * a second input field is added once a value is entered into the 
   * first input.
   *
   * @example
   *     var populateFriends = function (names) {
   *       var locator = {className: "friends-mlText"};
   *       return helpers.populateTextBox (locator, names);
   *     };
   *
   * @method populateTextBox  
   * @param {webdriver.Locator|Object.<string>} locator The locator
   *     strategy to use when locating the target input box(es)
   * @param {string | array<string>} strings The values to populate
   *     into the text box(es)
   * @return {function} A function which, when executed, populates
   *     the target text boxes
   */
  populateTextBox: function (locator, strings) {
    var self = this;
    
    if ('string' === typeof strings) {
      return function () {
        self.driver.findElement(locator).sendKeys(strings);
      }
    }
    if ('number' !== typeof strings.length) {
      throw new Error("invalid type for parameter 'strings'");
    }

    // else, assume strings is an Array
    return function () {
      var stringCount = strings.length,
          i,
          populateTextBoxByIndex;

      // populate first string 
      self.driver.findElement(locator).sendKeys(strings[0]);

      populateTextBoxByIndex = function (i) {
        // use closure to save appropriate index
        self.driver.findElements(locator).
          then (function (collection) {
            
            // sanity check
            if (i >= collection.length) {
              throw new Error("failed to locate text box for target string '" +
                strings[i] + "'.");
            }

            collection[i].sendKeys(strings[i]);
          });
      };

      // populate the rest of the names
      for (i = 1; i < stringCount; i++) {
        populateTextBoxByIndex(i);
      }
    }
  },  // end populateTextBox

  /**
   * Sets the value of a form element.  
   *
   * Supports the following form elements:
   *   input - text, radio, email, password
   *   textarea
   *   select
   *
   * @method populateElement
   * @param {webdriver.Locator|Object.<string>} locator The locator
   *     strategy to use when searching for the form element.
   * @param {string | array<string>} strings The target value(s) of
   *     into the form element(s)
   */
  populateElement: function (locator, value) {
    var self = this;

    if ('string' !== typeof value) {
      // assume a group of dynamically-added text boxes
      // aka. Multi-List
      this.populateTextBox(locator, value)();
      return;
    } 

    // assume regular, non-dynamic, form element
    this.driver.findElements(locator).then(function (collection) {
      collection.forEach(function (elemPromise) {
        elemPromise.then(function (element) {
          element.getTagName().
            then(function (tagName) {
              switch (tagName) {
                case "select":
                  element.findElement(self.webdriver.By.css("option[value='" + value + "']"))
                    .click();
                  break;
                case "textarea":
                  element.sendKeys(value);
                  break;
                case "input":
                  element.getAttribute("type").
                    then(function (type) { 
                      switch (type) {
                        case "radio":
                          // click if value matches
                          element.getAttribute("value").
                            then(function (val) {
                              if (val === value) {
                                element.click();
                              }
                            });
                          break;
                        case "text":
                        case "email":
                        case "password":
                          element.sendKeys(value);
                          break;
                        default:
                          throw new Error ("Unsupported input type '" + type + "'");
                      }
                    });
                    break;
                default:
                  throw new Error ("Unsupported form element '" + tagName + "'");
              }
          })
        })
      })
    });

  }  // end populateElement

});  // end _extend WebDriverHelpers.prototype

module.exports = WebDriverHelpers;

})();