by @amad410 and @mbcooper
This is the same setup as https://github.com/mbcooper/ProtractorExample
-
Clone this repo.
-
Install Node.js® if you haven't already. See more on official Node.js® website.
On OS X you can install Node.js® just by typing
brew update brew install node
Don't have Homebrew on OS X?
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
More on official Homebrew website.
-
Install node global dependencies
npm install -g gulp-cli npm install -g bower npm install -g jscs npm install -g protractor
-
In the directory where you placed the project, install all project's dependencies
npm install bower install
-
You need to update selenium webdrivers
webdriver-manager update
-
Finally, run project by typing
gulp
Locators are functions that will help protractor grab the specific element that you want from an applications DOM. By now, you might have already started sifting through numerous sites on protractor locators. You might have also experimented with them and discovered that the documentation out there for locators are either outdated due to new protractor versions or various implementations for what you are trying to achieve just do not work. We are going dive into what does and does not work, depending upon what you are trying to do. Last, we will show some real world examples. Before we get started on those topics, let's start off describing the different types of locators.
Every protractor locator is formatted as (by.*), where * is the locator you have chosen to locate the element. Here is a list of the most common locators:
- by.binding("{{status}}")
- by.model("{{ng-model parameter}}")
- by.css("{{css locator}}")
- by.buttonText("{{button text}}")
- by.repeater("{{repeating lists}}")
- by.id("{{element ID}}")
- by.linkText("{{link text}}")
- by.name("{{element name}}")
- by.tagName("{{element tagname}}")
- by.xPath("{{xpath}}")
- Finding Child Elements
This locator will find an element by its text binding. Any element bound to variables containing the text or having an ng-bind angular directive will be returned. Check out the div and the span tags below. It shows two ways in which a binding description is applied to an element.
<div>{{name}}</div>
<span ng-bind="person.email"></span>
Using the binding locator, it will locate the element based on description and return the div and/or span for you.
by.binding('name');
by.binding('person.email')
This locator will find an element by its ng-model angular directive.
<input type="text" ng-model="person.name">
Finding an element by its model is the most common locator. Finding an element based on a decription that binds it to the data it represents is as easy as
by.model('person.name')
This locator will find an element using CSS selectors.
<div class="primary.header">
<input placeholder="Property Address">
The div specified above can be found using the css class selector, where the . used to find an element by its css class.
by.css('.primary')
The input element can be found by first identifying the HTML tag, and then following that up with its attribute enclosed in brackets and attribute value enclosed in quotes as specified below:
by.css('input[placeholder="Property Address"]')
I would like to mention there are many other variations of CSS selectors. I often use the $ as a shorthanded version of element(by.css('')). Using this variation of the locator, I can search for the same element by performing the following:
$('input[placeholder="Property Address"]')
or search for an element by its ID or class
$('#someid');
*('.class');
Lastly, you can find elements based on css matching some text.
<li class="pet">Dog</li>
by.cssContainingText('.pet', 'Dog'))
The example shows how you can find an element based on its css class matching the text 'Dog.'
This locator will find a button element based on its text.
<button>Save</button>
<button>Go To Next Page</button>
As you can see above, the two buttons have the text 'Save' and 'Go To Next Page.' We can find these elements by performing the following:
by.buttonText('Save')
by.partialButtonText('Next')
The latter example finds a button that matches some partial text.
This locator helps us find elements inside an ng-repeat angular directive. This directive clones HTML elements once for each item in a collection (in an array).
<div ng-repeat="cat in pets">
<span>{{cat.name}}</span>
<span>{{cat.age}}</span>
</div>
Using repeater locator, we can find an array of elements inside the repeater
by.repeater('cat in pets')
Or we can narrow down our choice within the collection elements by either column or row. Here are the different permutations:
by.repeater('cat in pets').column('cat.name')
by.repeater('cat in pets').row(1)
by.repeater('cat in pets').row(0).column('cat.name')
Keep in mind that the first example will returns a promise that resolves to an array of WebElements from a column. In essence, you will get the collection. Therefore, it is necessary that you store the elements that you get back as a collection like so:
element.all(by.repeater('cat in pets').column('cat.name')
Using IDs is the one of the most common ways to find elements, when there is in fact one for an element. However, this unique identifier is not always there for an element. When you do have an ID, you can perform the following two ways:
by.id('loginButton')
$('#loginButton')
For all links, you can use this locator to find those that match targeted text.
<a href="default.asp" target="_blank">This is a link</a>
by.linkText('This is a link')
by.partialLinkText('link')
Once again, the latter example finds a link that matches some partial text.
This locator finds an element based on its name.
<form name=myform>
by.name('myForm')
This is another common locator, which finds an element based on its tag (a, h2, input, button, etc.).
<h2>Hello</h2>
by.tagName('h2')
This locator is not used all that often. The reason being is that for new development you cannot rely on the exact location of an element in the DOM. It would make more sense to use this locator when the application is stable and mature. However, it is not recommended to use this locator. What sets Protractor apart from other automation tools are the powerful locators, other than xPath, that have been provided to you to find elements within an Angular application. On other hand, if you want to find an element by xPath you can.
<div id="numDispBox" ng-mouseleave="hideNumDisplayBox()" style="display: none;">
<div class="numDispOption transition_2" ng-click="UpdateNbResultPerNode(20)">20</div>
<div class="numDispOption transition_2" ng-click="UpdateNbResultPerNode(40)">40</div>
<div class="numDispOption transition_2" ng-click="UpdateNbResultPerNode(60)">60</div>
<div class="numDispOption transition_2" ng-click="UpdateNbResultPerNode(80)">80</div>
</div>
To find the third child div under the parent, you can perform the folllowing by referencing its text in xPath:
by.xPath("//div[text()='60']")
Lastly, lets look at some other ways to find elements. You may want to find certain elements within a parent element. For example, take a look at the code below:
<div id="numDispBox" ng-mouseleave="hideNumDisplayBox()" style="display: none;">
<div class="numDispOption transition_2" ng-click="UpdateNbResultPerNode(20)">20</div>
<div class="numDispOption transition_2" ng-click="UpdateNbResultPerNode(40)">40</div>
<div class="numDispOption transition_2" ng-click="UpdateNbResultPerNode(60)">60</div>
<div class="numDispOption transition_2" ng-click="UpdateNbResultPerNode(80)">80</div>
</div>
If you wanted to find the div tags inside the parent div, you could chain the css selectors by specifying both the parent and child tage names. Since there are multiple elements that have the same tag name, it is best to perform the following:
Locator | Description | --- | --- | --- $('div div').first() | Selects first of children | $('div div').last() | Selects last of children | $('div div').get(index) | Selects index-th of children |
After developing tests using Protractor for AngularJS webapps, I have found that best protractor locators are:
Stil TBD
By now, you are probably excited that you have found a one-stop shop on protractor locators. There are many sites out there that list different implementation of using locators, but they are not as thorough or up to date. In addition, not all examples are practical. The focus of this section will be in using protractor locators to find elements in a real-world application. Go to the [Protractor Example project] (https://github.com/mbcooper/ProtractorExample), clone the repo, and launch the example application.
This is a very simple application that has a seach box, which accepts only two valid terms as inputs: Trees and Food. The user is able to search these terms and return results by entering them into the search box, and clicking the search button. Results will be displayed in a table below the search.
Validations are also performed, based on simple rules, to handle invalid searches. The terms searched for must have alpha characters only, and must be no more than 10 characters.
For the purpose of applying protractor locators, we are going to check the following:
- Verify that certain elements are presented on the page
- Validate we receive results back from searching with valid terms
- Validate we received validations when providing invalid inputs into the search box
See section titled 'Home.po.js - Our Page Object' in the Protractor Example. A page object is a design pattern for creating an object repository for UI elements. Under this design pattern, each web page has a corresponding page class which identifies elements and their properties, methods, and the available operations that can be performed by the elements. We will be creating elements and methods to interact with the elements inside its corresponding page object class Home.po.js.. Let's examine the DOM for the search box.
From the image above we can visually see that the search box element has an input tag. Since we are using protractor locators we need to find an angular directive that we can use. In this case, we can find an element by its ng-model angular directive within the input tag.
<input type="text" class="form-control inline ng-pristine ng-valid ng-valid-pattern ng-valid-minlength ng-valid-maxlength ng-touched" name="searchTerm" ng-minlength="1" ng-maxlength="10" ng-pattern="/^[a-zA-Z0-9]*$/" ng-model="home.search" id="searchTerm" placeholder="Food or trees">
Therefore, we can write the code to identify the search box in the page object class Home.po.js. as:
searchBox: {
get: function() {
return element(by.model('home.search'));
}
},
Alternatively, we can find this element by id, name, or by css:
searchBox: {
get: function() {
return element(by.id('searchTerm'));
}
},
searchBox: {
get: function() {
return element(by.name('searchTerm'));
}
},
searchBox: {
get: function() {
return element(by.css('[placeholder="Food or trees"]'));
}
},
Let's examine the DOM for the search button.
From the image above we can visually see that the search button element has a button tag that does not contain an ng-model angular directive. Therefore, we need to find other locators for which to identify this element. What we do see is that there is an id and ng-click css attribute. Therefore, we can identify the search button element using either one of the locators in our page object:
searchButton: {
get: function() {
return element(by.id('searchButton'));
}
},
searchButton: {
get: function() {
return element(by.css('[ng-click="home.makeSearch()"]'));
}
},
We can now write tests in our spec to validate that these elements are presented
it('should have a search box', function() {
var searchBox = homePage.searchBox;
expect(searchBox.isPresent()).toBe(true);
});
it('should have a search button', function() {
var searchButton = homePage.searchButton;
expect(searchButton.isPresent()).toBe(true);
});
As mentioned earlier, the application is design to accept only two valid terms as inputs: Trees and Food. When the user searches one of these terms, the application will return results in a table below the search. The main purpose of our next set of tests is to validate we get the results of searching the valid terms. As a set of prerequisites, we need to first input the valid term into the search box, and then click the search button to get the results. The framework designed for the Protractor Example has already provided you with the operations to accomplish this. Let's look at these operation in the page object class.
Since we already have identified the searchBox and searchButton objects in the page object class, we can use these objects to perform operations on them. To provide input into the searchBox, we have constructed the enterSearch() method to return the operation of sending a text value parameter to the element.
enterSearch: {
value: function(keys) {
return this.searchBox.sendKeys(keys);
}
},
Likewise, we have constructed the clickSearch() method to return the click operation for the searchButton.
clickSearch: {
value: function() {
return this.searchButton.click();
}
},
Now, lets create a test that searches a valid term to return back results by using the method operation provided to you so far.
it('should search for food and get answers', function() {
var term = 'food';
homePage.enterSearch(term);
homePage.clickSearch();
......
}
);
The test contructed so far will return back results from the term food entered. But, we need our tests needs to ensure that results are found. In order to do this, let's examine the table results.
Looking at the body of the table, where we find the actual content of the resulting search, we see that there is an ng-repeat angular directive that can be used to get all the results displayed in the table. As shown in the page object class, we can create an object that returns all results like this:
allResults: {
get: function() {
return element.all(by.repeater('bio in home.results'));
}
},
Using this object we can expand our test to assert whether results are not empty.
it('should search for food and get answers', function() {
var term = 'food';
homePage.enterSearch(term);
homePage.clickSearch();
var allElements = homePage.allResults;
allElements.then(function(results) {
expect(results).not.toBeNull();
});
}
);