Skip to content

A simple, self contained working application that demonstrates Protractor on an AngularJS app, with the use of Page Objects.

Notifications You must be signed in to change notification settings

mbcooper/ProtractorExample

Repository files navigation

#A Working Angular - Protractor Project

This project is a working project, an illustration of the use of Angular Protractor on a simple Angular project. It illustrates the use of Page Objects to abstract the DOM markup from tests, and to encourage re-use. You'll see that the tests are very fluent.

Setup

  1. Clone this repo.

  2. 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.

  3. Install node global dependencies

    npm install -g gulp-cli
    npm install -g jscs
    npm install -g protractor
    
  4. In the directory where you placed the project, install all project's dependencies

    npm install
    
  5. You need to update selenium webdrivers

    webdriver-manager update
    
  6. Finally, run project by typing

    gulp
    

Whew! This gets you a project that will run a simple home page that allows a search in a magical bio database. To simplify things, I serve up the results locally, and only food and trees are valid search terms. (The point of this example is show how to test).

##The App The app is a simple screen that presents a search box and a button. Results are displayed (if any) below the search

###Search Search UI

Enter one of the 2 valid search terms: trees or food and click Search. The results will be display below the search line. Ignore the contents. Its just sample "stuff".

Search Results #####Validation Errors We also have 2 validations on the search box. It has to meet our simple letters-numbers regexp, and be no more than 10 characters. IF these are violated, we display an ng-message like this:

Error

##Protractor Tests So let's dive into the protractor tests, which we find in the e2e directory.

###conf.js We configure our tests to point at our selenium (Protractor) driver and our running app.

	exports.config = {
	seleniumAddress: 'http://localhost:4444/wd/hub',
	specs: ['**/*.spec.js'],
	baseUrl: '',
	capabilities: {
		'browserName': 'chrome'
	},
	jasmineNodeOpts: {
		showColors: true
	},
	env: {
		baseUrl: 'http://localhost:3444/#'
		}
	};

The key items here: are the seleniumAddress (which is a default), and the env.baseUrl, which is a standard adopted from ngBoilerplate.

###Home.po.js - Our Page Object Page Objects let us provide a shim layer between the DOM that is rendered and the tests. This is highly flexible and has the power of finding elements by our Angular models!

You can see the whole Page Object file here, and the Home Page Template here, so let's walk through a few of the basics:

####Home Page Contructor

We're using the ControllerAs syntax in our Home Controller app, so our scope variables will be exposed as home.. We create an export object for the Home Page:

	var HomePage = function() {
		browser.get(env.baseUrl + '/home');
	};

	module.exports = HomePage;

####Reading DOM Elements For each item on the view that we want to access, we write a getter on the HomePage prototype.

HomePage.prototype = Object.create(
  {}, {
    searchBox: {
      get: function() {
        return element(by.model('home.search'))
      }
    },

This snippet shows us locating the element using the view model itself! Now, this is very cool, because we could dramatically alter the DOM template and the Protractor test will still function.

####Typing Into TextBoxes We can use the getter in the previous section and add the method .sendKeys to enter text:

 enterSearch: {
      value: function(keys) {
        return this.searchBox.sendKeys(keys);
      }
    },

####Clicking on Buttons Similarly, we can find a button and click on it.

	{
	searchButton:
      get: function() {
        return element(by.id('searchButton'));
      }
    },
    clickSearch: {
      value: function() {
        return this.searchButton.click();
      }
    },

####Get the Results of the Search I must say this is very cool. All we have to do is ask for the repeater part of the model when we get the results:

 	allResults: {
      get: function() {
        var bioElement = element.all(by.repeater('bio in home.results'));
        return bioElement;
      }
    },

So now we have created an abstraction for the page using the Home Page Object.

Let's proceed to our test spec.

###Home.spec.js - The Home Page Test

The full test spec is here. We'll focus on building up the parts. ####Require the Page Object

	var HomePage = require('./Home.po.js');

####Setup the Test and Instantiate a New Home Page Object Before Each Test

describe('home Page', function() {
  var homePage;

  beforeEach(function() {
    homePage = new HomePage();
  });

####Check the Browser Title

  it('should have a proper page tab', function() {
    var viewElem = homePage.viewTitle;
    viewElem.then(function(viewTitle) {
        expect(viewTitle).toEqual('Protractor Sample')
      }
    );
  });

This simple example stumped me at first until I realized that most of the Page Object returns are promises that need to be resolved. What an easy task! No timeouts or waits. Protractor handles that all for us and gives us our favorite object -- the promise.

####Check That Validation Errors are Displayed Remember above that we show an ng-message when we have validation errors. We defined these in the Page Object by using the css locator: by.css('[ng-message="pattern"]'). This allows us to write a test to see if the error message ("Numbers or Letters Only") actually appears.

  it('should have an error for bad characters', function() {
    var badSearch = '@@';
    homePage.enterSearch(badSearch);
    var patternMessage = homePage.badTextMessage;
    expect(patternMessage).toContain('Numbers');
  });

####Do a Search and Get Answers. Check Number of Responses and Expected Answer Now for the grand test. We'll enter a search term ("food"), click on Search and look for answers. We'll check to make sure there are 3 rows, and that the word "Cocoa" is in the answer. Remember, many of the responses are promises.

  it('should search for food and find length and keyword', function() {
      homePage.enterSearch('food');
      homePage.clickSearch().then(
        function() {
          var allElements = homePage.allResults;
          allElements.then(function(results) {

            expect(results.length).toEqual(3);

            var target = results[food.line - 1];
            target.getText().then(function(text) {

              expect(text).toContain('Cocoa');
            });

          });
        });
    }
  );

Notice how fluent the test statements are: enterSearch, clickSearch and allResults. No matter how the DOM template changes, as long as the model bindings remain the same, we have a stable test. (OK - the button is by id).

If we do change bindings, we only have to change the Page Object once ... all our specs remain stable.

##Running The Tests So, fasten your seat belts and let's take Protractor for a test run.

You will need to be running 3 terminal sessions to run the tests. I use WebStorm, which makes opening the sessions easy.

  1. Terminal 1 runs our app. (Use the default gulp task, included in the project.

    	gulp
    
  2. Terminal 2 runs the Protractor / Selenium server on port 4444.

    	webdriver-manager start
    
  3. Terminal 3 runs the protractor tests:

    	protractor e2e/conf.js
    

So when we run the tests, we should see the Chrome browser pop up and our test suite run.

Test Run

##Ya Baby I must say, this has come a long way in the past 2 years that I've been involved with the Angular world.

Are e2e / functional tests the be-all and end-all? No, but when judiciously combined with effective unit test coverage, you can produce a high quality software product, with tests that can survive a lot of change.

###Can I Test a Deployed Version of the App? Darn! I was afraid your were going to ask that! Yes!

If you deployed with debug information off ($compileProvider.debugInfoEnabled(false);), Protractor (as of version 1.6.1) will add in the debug informartion if you excluded it on your deployment (and you should).

Just point at the deployed site in e2e/config.js and run the tests!

About

A simple, self contained working application that demonstrates Protractor on an AngularJS app, with the use of Page Objects.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published