Home » Angularjs » Targeting elements within an element directive with Protractor

Targeting elements within an element directive with Protractor

Posted by: admin November 30, 2017 Leave a comment

Questions:

Consider the following view model:

$scope.data = {};
$scope.data.person = {};
$scope.data.person.firstname = "";
$scope.data.person.lastname = "";
$scope.data.person.username = "";

and the following element directive:

<custom-form-directive ng-model="data.person"></custom-form-directive>

which contains three input tags to display the data. How do I use protractor to populate the input fields by targeting ng-model="data.person"?

Answers:

It depends on how you’re getting data in/out of the inputs in the directive. However, in all cases you can chain together element(<locator>) calls to search for sub-elements of the directive:

var directive = element(by.model('data.person'));
var subElement = directive.element(by.<something>);

If you’re using ng-model in the directive itself on each of the inputs, you can do something like:

var directive = element(by.model('data.person'));

// Assuming the inputs have attributes like `ng-model="firstname"` in the directive template
var firstnameInput = directive.element(by.model('firstname'));
var lastnameInput = directive.element(by.model('lastname'));
var usernameInput = directive.element(by.model('surnamname'));

and then on each call sendKeys

firstnameInput.sendKeys('Peter');
secondnameInput.sendKeys('Piper');
usernameInput.sendKeys('PickledPumpernickle');

If you’re not using ng-model in the directive template, you can use other locators to find the sub elements, together with get if needs be, and depend on the order they’re in the DOM

var inputs = directive.element(by.css('input'));
var firstnameInput = inputs.get(0);
var secondnameInput = inputs.get(1);
var usernameInput = inputs.get(2);

However, I suspect none of the above will work if you have replace: true specified in the directive, as it depends on the original element, with the ng-model attribute, being in the DOM.

Questions:
Answers:

This is exactly what the evaluate method is for: http://angular.github.io/protractor/#/api?view=ElementFinder.prototype.evaluate

element(by.model('data.person')).evaluate('data.person.firstname = "yourvaluehere"');

I assume by “targeting the model” you want to change the controller’s value and have the ng-model directive update the view, rather than the other way around.

Questions:
Answers:

I think you want to build something like a “pageObject” (see https://github.com/angular/protractor/blob/master/docs/page-objects.md) for your custom-form-directive directive. This object will understand how the directive maps to primitives that Protractor will understand (e.g., specific input fields). It should take a locator so its callers can pass in the how to find the directive on the page, but the object should handle everything after that.

var object = new customFormDirectiveObject(by.model('data.person'));

depending on how your directive works you’d do something like:

object.setName(first, last, user);

or if its more dyanmic maybe something like this:

object.setName({firstname: first, lastname: last, username: user });

Questions:
Answers:

With a few helper function you could do it rather easily. The helper object just wraps protractor and makes it a little more sane and safe to use. Especially when running on remote systems.

var helper = {
  getElement: function (selector) {
    return element(by.css(selector));
  },

  resolveSelector: function (selector) {
    var el;
    if (typeof selector === 'string') {
      el = self.getElement(selector);
    } else {
      el = selector;
    }
    return el;
  },

  waitForElementClickable: function (selector) {
    var el = self.resolveSelector(selector);
    var condition = function () {
      return self.waitForElement(el).then(function () {
        return self.isElementEnabled(el);
      });
    };
    return self.wait(condition, 15000, 'Element not clickable. (' + selector + ')');
  },

  setElementValue: function (selector, value) {
    var el = self.resolveSelector(selector);
    return self.waitForElementClickable(el).then(function () {
      el.click().clear().sendKeys(value);
    });
  },

  getElementValue: function (selector) {
    var el = self.resolveSelector(selector);
    return el ? el.getAttribute('value') : '';
  }

}

Then you can just use regular CSS selectors to set your values:

helper.setElementValue('[ng-model="data.person"] input:nth-child(1)', '1st Input Value');
helper.setElementValue('[ng-model="data.person"] input:nth-child(2)', '2nd Input Value');
helper.setElementValue('[ng-model="data.person"] input:nth-child(3)', '3rd Input Value');

And you can check for expected values with something like this:

expect(helper.getElementValue('[ng-model="data.person"] input:nth-child(1)')).toEqual('something')

Leave a Reply

Your email address will not be published. Required fields are marked *