├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── Readme.md ├── examples ├── onProtractorRunner.js ├── pages │ └── angularJsPage.js └── protractor.conf.js ├── lib ├── astrolabe.js └── astrolabe │ ├── base.js │ ├── module.js │ ├── page.js │ └── utils │ ├── serializer.js │ └── url.js ├── package.json └── test ├── base.js ├── common.js ├── mocha.opts ├── module.js ├── page.js ├── serializer.js └── url.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | 4 | node_modules/ 5 | 6 | npm-debug.log 7 | 8 | selenium -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | examples/ 3 | selenium/ 4 | 5 | .idea 6 | .npmignore 7 | 8 | Readme.md 9 | chromedriver.log 10 | 11 | *.iml 12 | *.yml -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.8" 4 | - "0.9" 5 | - "0.10" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Astrolabe 2 | Copyright (c) 2013, Stuart Plumbley 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * Neither the name of the author nor the names of contributors may be used 13 | to endorse or promote products derived from this software without 14 | specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | Astrolabe [![Build Status](https://travis-ci.org/stuplum/astrolabe.png?branch=master)](https://travis-ci.org/stuplum/astrolabe) 2 | ========= 3 | 4 | `Astrolabe` is an extension for [protractor](https://github.com/juliemr/protractor) that adds page objects to your functional/e2e tests. 5 | 6 | Installation 7 | ------------ 8 | 9 | via [npm (node package manager)](http://github.com/isaacs/npm) 10 | 11 | $ npm install astrolabe 12 | 13 | 14 | Usage 15 | ----- 16 | 17 | Example signInPage.js 18 | 19 | ``` js 20 | var Page = require('astrolabe').Page; 21 | 22 | module.exports = Page.create({ 23 | 24 | url: { value: 'http://mysite.com/signin' }, 25 | 26 | username: { get: function() { return this.findElement(this.by.input('username')); } }, // finds an input element with the name 'username' 27 | submit: { get: function() { return this.findElement(this.by.id('submit')); } } // finds an element with the id 'submit' 28 | }); 29 | ``` 30 | 31 | adding to tests: 32 | 33 | ``` js 34 | var signInPage = require('./path/to/signInPage'); 35 | 36 | ... 37 | ``` 38 | 39 | navigating: 40 | 41 | ``` js 42 | signInPage.go(); // will send browser to 'http://mysite.com/signin' 43 | 44 | signInPage.go('some', 'path'); // will send browser to 'http://mysite.com/signin/some/path' 45 | signInPage.go('some/path'); // will send browser to 'http://mysite.com/signin/some/path' 46 | 47 | signInPage.go({ some: 'query' }); // will send browser to 'http://mysite.com/signin?some=query' 48 | ``` 49 | 50 | interacting: (See [Protractor API Docs](https://github.com/angular/protractor/blob/master/docs/api.md) for more info on available api methods) 51 | 52 | ``` js 53 | signInPage.username.sendKeys('a username'); // will fill the username input with the text 'a username' 54 | 55 | signInPage.submit.click(); // will click on the submit element 56 | ``` 57 | 58 | ``` js 59 | signInPage.username.getAttribute('value'); // will return a promise that is resolved with the value of the text field, in this case 'a username' 60 | 61 | // this can be used within an expectation 62 | expect(signInPage.username.getAttribute('value')).toBe('a username'); 63 | ``` 64 | 65 | It is possible to create convenience methods to wrap up common logic. 66 | 67 | Example signInPage.js 68 | 69 | ``` js 70 | var Page = require('astrolabe').Page; 71 | 72 | module.exports = Page.create({ 73 | 74 | url: { value: 'http://mysite.com/signin' }, 75 | 76 | username: { get: function() { return this.findElement(this.by.input('username')); } }, 77 | password: { get: function() { return this.findElement(this.by.input('password')); } }, 78 | submit: { get: function() { return this.findElement(this.by.id('submit')); } }, 79 | invalid: { get: function() { return this.findElement(this.by.id('incorrectLogin')); } }, 80 | 81 | InvalidLoginException: { get: function() { return this.exception('Invalid Login'); } }, 82 | 83 | // Adds a signIn method to the page object. 84 | signIn: { value: function(username, password) { 85 | 86 | var page = this; 87 | 88 | page.go(); 89 | 90 | page.username.sendKeys(username); 91 | page.password.sendKeys(password); 92 | 93 | page.submit.click(); 94 | 95 | return this.invalid.isDisplayed().then(function (wrongLogin) { 96 | if (wrongLogin) { 97 | page.InvalidLoginException.thro(username + ', ' + password + ' is not valid'); 98 | } 99 | }); 100 | } } 101 | }); 102 | ``` 103 | 104 | can be used in your tests: 105 | 106 | ``` js 107 | var signInPage = require('./path/to/signInPage'); 108 | 109 | ... 110 | 111 | signInPage.signIn('test user', 'testpassword'); // will navigate to sign in page, enter username and password then click submit. 112 | 113 | ... 114 | ``` 115 | 116 | Cloning and running Astrolabe's tests 117 | ------------------------------------- 118 | Clone the github repository. 119 | 120 | git clone https://github.com/stuplum/astrolabe.git 121 | cd astrolabe 122 | npm install 123 | 124 | npm test 125 | 126 | 127 | Running Astrolabe's example protractor test 128 | ------------------------------------------- 129 | 130 | Install protractor with. 131 | 132 | npm install protractor 133 | 134 | Start up a selenium server (See the appendix below for help with this). By default, the tests expect the selenium server to be running at `http://localhost:4444/wd/hub`. 135 | 136 | The example folder contains a simple test suite which runs against angularjs.org. It is a port of the simple test suite included with protractor. 137 | 138 | Currently only the protractor runner is supported. The runner accepts a configuration file, which runs the tests at `example/onProtractorRunner.js`. 139 | 140 | node_modules/.bin/protractor examples/protractor.conf.js 141 | 142 | 143 | Setting up a standalone selenium server 144 | --------------------------------------- 145 | 146 | See Appendix A of [protractor's](https://github.com/juliemr/protractor) installation instructions 147 | -------------------------------------------------------------------------------- /examples/onProtractorRunner.js: -------------------------------------------------------------------------------- 1 | describe('angularjs homepage', function() { 2 | 3 | var angularJsPage = require('./pages/angularJsPage'); 4 | 5 | it('should greet using binding', function() { 6 | 7 | angularJsPage.go(); 8 | 9 | angularJsPage.yourName.sendKeys("John Doe"); 10 | 11 | expect(angularJsPage.greeting.getText()).toEqual('Hello John Doe!'); 12 | }); 13 | 14 | it('should list todos', function() { 15 | 16 | angularJsPage.go(); 17 | 18 | expect(angularJsPage.todo.getText()).toEqual('build an angular app'); 19 | }); 20 | 21 | //Uncomment to see failures. 22 | 23 | // it('should greet using binding - this one fails', function() { 24 | // 25 | // angularJsPage.go(); 26 | // 27 | // angularJsPage.yourName.sendKeys("John Doe"); 28 | // 29 | // angularJsPage.greeting.getText().then(function(text) { 30 | // expect(text).toEqual('Hello Jack'); 31 | // }); 32 | // }); 33 | 34 | }); 35 | -------------------------------------------------------------------------------- /examples/pages/angularJsPage.js: -------------------------------------------------------------------------------- 1 | var Page = require('../../lib/astrolabe').Page; 2 | 3 | module.exports = Page.create({ 4 | 5 | url: { value: 'http://www.angularjs.org' }, 6 | 7 | yourName: { get: function() { return this.findElement(this.by.input('yourName')) } }, 8 | greeting: { get: function() { return this.findElement(this.by.binding('{{yourName}}!')); } }, 9 | todo: { get: function() { return this.findElement(this.by.repeater('todo in todos').row(2)); } } 10 | }); -------------------------------------------------------------------------------- /examples/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // An example configuration file. 2 | exports.config = { 3 | // The address of a running selenium server. If this is specified, 4 | // seleniumServerJar and seleniumPort will be ignored. 5 | seleniumAddress: 'http://localhost:4444/wd/hub', 6 | 7 | // A base URL for your application under test. Calls to protractor.get() 8 | // with relative paths will be prepended with this. 9 | baseUrl: '', 10 | 11 | // Capabilities to be passed to the webdriver instance. 12 | capabilities: { 13 | 'browserName': 'chrome' 14 | }, 15 | 16 | // Spec patterns are relative to the current working directly when 17 | // protractor is called. 18 | specs: ['./onProtractorRunner.js'], 19 | 20 | // Options to be passed to Jasmine-node. 21 | jasmineNodeOpts: { 22 | isVerbose: false, 23 | showColors: true, 24 | includeStackTrace: true 25 | } 26 | }; -------------------------------------------------------------------------------- /lib/astrolabe.js: -------------------------------------------------------------------------------- 1 | var Page = require('./astrolabe/page'), 2 | Module = require('./astrolabe/module'); 3 | 4 | exports = module.exports = { 5 | 6 | version: '0.3.6', 7 | 8 | Page: Page, 9 | Module: Module 10 | }; -------------------------------------------------------------------------------- /lib/astrolabe/base.js: -------------------------------------------------------------------------------- 1 | var exceptions = require('exceptions'); 2 | 3 | if ( typeof protractor === 'undefined' ) { 4 | protractor = require('protractor'); 5 | } 6 | 7 | function typeOf(name, obj) { 8 | return Object.prototype.toString.call(obj) === '[object ' + name + ']'; 9 | } 10 | 11 | var create = function() { 12 | var Klass = this.extend.apply(this, arguments); 13 | return new Klass(); 14 | }; 15 | 16 | var extend = function() { 17 | 18 | var _super = this, 19 | _noop = function() {}, 20 | _const = typeOf('Function', arguments[0]) ? arguments[0] : _noop, 21 | _content = typeOf('Object', arguments[0]) ? arguments[0] : arguments[1] || {}; 22 | 23 | function Fn() { 24 | _super.apply(this, arguments); 25 | _const.apply(this, arguments); 26 | } 27 | 28 | Fn.extend = extend; 29 | Fn.create = create; 30 | 31 | Fn.prototype = Object.create(_super.prototype, _content); 32 | Fn.prototype.constructor = Fn; 33 | 34 | return Fn; 35 | }; 36 | 37 | var Base = function() {}; 38 | 39 | Base.create = create; 40 | Base.extend = extend; 41 | 42 | Base.prototype = Object.create({}, { 43 | by: { value: protractor.By }, 44 | driver: { get: function() { return browser; } }, 45 | exception: { value: function(name) { return new exceptions.Exception(name); } }, 46 | findElement: { value: function(by) { return this.context.findElement(by); } }, 47 | findElements: { value: function(by) { return this.context.findElements(by); } }, 48 | find: { 49 | get: function() { 50 | var base = this; 51 | var selectorStrategies = [ 'id', 52 | 'css', 53 | 'xpath', 54 | 'name', 55 | 'tagName', 56 | 'className', 57 | 'linkText', 58 | 'partialLinkText', 59 | 'js', 60 | 'binding', 61 | 'select', 62 | 'selectedOption', 63 | 'input', 64 | 'model', 65 | 'textarea', 66 | 'repeater', 67 | 'buttonText', 68 | 'partialButtonText', 69 | 'cssContainingText']; 70 | 71 | var bys = {}; 72 | var allBys = {}; 73 | selectorStrategies.forEach(function (strategy) { 74 | var by = base.by[strategy]; 75 | bys[strategy] = function (selector) { return base.findElement(by(selector)); }; 76 | allBys[strategy] = function (selector) { 77 | return base.findElements(by(selector)); 78 | }; 79 | }); 80 | 81 | var all = { by: allBys }; 82 | return { all: all, by: bys }; 83 | } 84 | } 85 | 86 | }); 87 | 88 | exports = module.exports = Base; 89 | -------------------------------------------------------------------------------- /lib/astrolabe/module.js: -------------------------------------------------------------------------------- 1 | var Base = require('./base'); 2 | 3 | exports = module.exports = Base.extend(function (context) { 4 | this.context = context; 5 | }); -------------------------------------------------------------------------------- /lib/astrolabe/page.js: -------------------------------------------------------------------------------- 1 | var Base = require('./base'), 2 | Serializer = require('./utils/serializer'), 3 | URL = require('./utils/url'), 4 | underscore = require('underscore'); 5 | 6 | var go = function() { 7 | 8 | var url = new URL(this.url); 9 | 10 | underscore.each(arguments, function(arg) { 11 | if(underscore.isString(arg)) { url.addPath(arg); } 12 | if(underscore.isObject(arg)) { url.addParams(arg); } 13 | }); 14 | 15 | this.context.get(url.url); 16 | }; 17 | 18 | var mockBackend = function(script) { 19 | 20 | var serializer = new Serializer(), 21 | scriptString = serializer.serialize(script), 22 | moduleString = "angular.module('httpBackendMock', ['ngMockE2E']).run(" + scriptString + ");"; 23 | 24 | this.context.addMockModule('httpBackendMock', moduleString); 25 | }; 26 | 27 | var mockModule = function(name, script) { 28 | 29 | var serializer = new Serializer(), 30 | mockName = name + 'Mock', 31 | scriptString = serializer.serialize(script), 32 | moduleString = "angular.module('" + mockName + "', []).value('" + name + "', " + scriptString + ");"; 33 | 34 | this.context.addMockModule(mockName, moduleString); 35 | }; 36 | 37 | var clearMocks = function() { 38 | this.context.clearMockModules(); 39 | }; 40 | 41 | exports = module.exports = Base.extend(function () { this.context = this.driver; }, { 42 | 43 | debug: { value: function() { this.context.debugger(); } }, 44 | 45 | title: { get: function() { return this.context.getTitle(); } }, 46 | currentUrl: { get: function() { return this.context.getCurrentUrl(); } }, 47 | baseUrl: { get: function() { return this.context.baseUrl; } }, 48 | 49 | go: { value: go }, 50 | 51 | mockBackend: { value: mockBackend }, 52 | mockModule: { value: mockModule }, 53 | clearMocks: { value: clearMocks } 54 | }); -------------------------------------------------------------------------------- /lib/astrolabe/utils/serializer.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore'); 2 | 3 | var Serializer = function() { 4 | 5 | function serialize(serializable) { 6 | return _.isFunction(serializable) ? '' + serializable : serializeObject(serializable); 7 | } 8 | 9 | function serializeObject(serializable) { 10 | var propertyStrings = []; 11 | 12 | _.each(serializable, function(prop, key) { 13 | propertyStrings.push(getPropertyString(key, prop)); 14 | }); 15 | 16 | return '{' + propertyStrings.join(', ') + '}'; 17 | } 18 | 19 | function getPropertyString(key, value) { 20 | 21 | var s = key + ': '; 22 | 23 | switch(getObjectType(value)) { 24 | case 'String': 25 | s += '"' + value + '"'; 26 | break; 27 | case 'Number': 28 | case 'Boolean': 29 | s += value; 30 | break; 31 | case 'Date': 32 | s += value.toLocaleString(); 33 | break; 34 | case 'Function': 35 | s += value; 36 | break; 37 | case 'Array': 38 | _.each(value, function(arrayValue) { 39 | s += serialize(value[arrayValue]); 40 | }); 41 | break; 42 | default: 43 | _.each(value, function(arrayValue) { 44 | s += serialize(value[arrayValue]); 45 | }); 46 | break; 47 | } 48 | 49 | return s; 50 | } 51 | 52 | function getObjectType(obj) { 53 | 54 | var type; 55 | 56 | if (_.isArguments(obj)) { type = "Arguments"; } 57 | if (_.isObject(obj)) { type = "Object"; } 58 | if (_.isFunction(obj)) { type = "Function"; } 59 | if (_.isArray(obj)) { type = "array"; } 60 | if (_.isString(obj)) { type = "String"; } 61 | if (_.isNumber(obj)) { type = "Number"; } 62 | if (_.isBoolean(obj)) { type = "Boolean"; } 63 | if (_.isNaN(obj)) { type = "NaN"; } 64 | if (_.isNull(obj)) { type = "Null"; } 65 | if (_.isUndefined(obj)) { type = "undefined"; } 66 | if (_.isDate(obj)) { type = "date"; } 67 | 68 | return type; 69 | } 70 | 71 | this.serialize = serialize; 72 | }; 73 | 74 | exports = module.exports = Serializer; -------------------------------------------------------------------------------- /lib/astrolabe/utils/url.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore'); 2 | 3 | var URL = function(base) { 4 | this.base = base; 5 | this.paths = []; 6 | this.params = {}; 7 | }; 8 | URL.prototype = Object.create({}, { 9 | url: { get: function() { return this.base + this.path + this.query; } }, 10 | path: { get: function() { return this.paths.length ? '/' + this.paths.join('/') : ''; }}, 11 | query: { 12 | get: function() { 13 | var params = _.map(this.params, function(param, key){ return key + '=' + param; }); 14 | return params.length ? '?' + params.join('&') : ''; 15 | } 16 | } 17 | }); 18 | URL.prototype.addPath = function() { 19 | 20 | var self = this; 21 | 22 | _.each(arguments, function(arg) { 23 | var leadingSlashRegEx = /^\//; 24 | self.paths.push(arg.replace(leadingSlashRegEx,'')); 25 | }); 26 | 27 | return self; 28 | }; 29 | URL.prototype.addParams = function(params) { 30 | _.extend(this.params, params); 31 | return this; 32 | }; 33 | 34 | exports = module.exports = URL; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "astrolabe", 3 | "version": "0.3.6", 4 | "description": "Page objects for protractor", 5 | "keywords": [ 6 | "angular", 7 | "angularjs", 8 | "protractor", 9 | "karma", 10 | "e2e", 11 | "test", 12 | "testing", 13 | "webdriver", 14 | "webdriverjs", 15 | "selenium" 16 | ], 17 | "author": "stuplum ", 18 | "licenses": [ 19 | { 20 | "type": "BSD 3-Clause", 21 | "url": "http://opensource.org/licenses/BSD-3-Clause" 22 | } 23 | ], 24 | "main": "lib/astrolabe.js", 25 | "directories": { 26 | "lib": "./lib", 27 | "test": "./test" 28 | }, 29 | "scripts": { 30 | "test": "mocha test/*.js" 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "git://github.com/stuplum/astrolabe.git" 35 | }, 36 | "bugs": { 37 | "url": "http://github.com/stuplum/astrolabe/issues" 38 | }, 39 | "dependencies": { 40 | "underscore": "*", 41 | "protractor": "*", 42 | "exceptions": "~0.1.1" 43 | }, 44 | "devDependencies": { 45 | "mocha": "*", 46 | "chai": "*", 47 | "sinon": "~1.7.3", 48 | "sinon-chai": "~2.4.0", 49 | "sandboxed-module": "0.2.1" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/base.js: -------------------------------------------------------------------------------- 1 | describe('Base', function() { 2 | 3 | var Base; 4 | 5 | beforeEach(function() { 6 | 7 | this.mockProtractor = { 8 | By: 'protractor "By" property' 9 | }; 10 | 11 | Base = Sandbox.require('../lib/astrolabe/base', { 12 | globals: { protractor: this.mockProtractor, 13 | browser: 'driver instance' } 14 | }); 15 | 16 | this.base = new Base(); 17 | }); 18 | 19 | it('should have a by property', function() { 20 | this.base.by.should.be.string('protractor "By" property'); 21 | }); 22 | 23 | it('should have a driver instance', function() { 24 | this.base.driver.should.be.string('driver instance'); 25 | }); 26 | 27 | it('should extend the base class', function() { 28 | 29 | var Extended = Base.extend({ 30 | newProp: { value: 'new prop value' }, 31 | newFunc: { value: function() { return 'new func value'} } 32 | }); 33 | 34 | var extended = new Extended(); 35 | 36 | expect(extended).to.be.an.instanceof(Base); 37 | 38 | expect(extended.newProp).to.be.a.string('new prop value'); 39 | expect(extended.newFunc()).to.be.a.string('new func value'); 40 | }); 41 | 42 | it('should extend an already extended class', function() { 43 | 44 | var SubClass = Base.extend(), 45 | SubSubClass = SubClass.extend(); 46 | 47 | expect(new SubClass()).to.be.an.instanceof(Base); 48 | expect(new SubSubClass()).to.be.an.instanceof(SubClass); 49 | }); 50 | 51 | it('should create an instance of an extended class', function() { 52 | 53 | var subClass = Base.create({ 54 | newProp: { value: 'new prop value' }, 55 | newFunc: { value: function() { return 'new func value'; } } 56 | }); 57 | 58 | expect(subClass).to.be.an.instanceof(Base); 59 | 60 | expect(subClass.newProp).to.be.a.string('new prop value'); 61 | expect(subClass.newFunc()).to.be.a.string('new func value'); 62 | }); 63 | 64 | it('should create an instance of an previously extended class', function() { 65 | 66 | var SubClass, subSubClass; 67 | 68 | SubClass = Base.extend({ 69 | prop1: { value: 'prop1' }, 70 | prop2: { value: 'prop2' } 71 | }); 72 | 73 | subSubClass = SubClass.create(function() { 74 | this.prop4 = 'prop4'; 75 | },{ 76 | prop2: { value: 'newProp2' }, 77 | prop3: { value: 'prop3' } 78 | }); 79 | 80 | expect(subSubClass).to.be.an.instanceof(SubClass); 81 | 82 | expect(subSubClass.prop1).to.be.a.string('prop1'); 83 | expect(subSubClass.prop2).to.be.a.string('newProp2'); 84 | expect(subSubClass.prop3).to.be.a.string('prop3'); 85 | expect(subSubClass.prop4).to.be.a.string('prop4'); 86 | }); 87 | 88 | }); 89 | -------------------------------------------------------------------------------- /test/common.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | global.sinon = require("sinon"); 4 | global.chai = require("chai"); 5 | global.expect = require("chai").expect; 6 | global.should = require("chai").should(); 7 | global.Sandbox = require('sandboxed-module'); 8 | 9 | var sinonChai = require("sinon-chai"); 10 | 11 | chai.use(sinonChai); 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --reporter spec 2 | --require test/common -------------------------------------------------------------------------------- /test/module.js: -------------------------------------------------------------------------------- 1 | describe('Module', function() { 2 | 3 | var ctxFindElementStub, ctxFindElementsStub; 4 | 5 | beforeEach(function() { 6 | 7 | var mockDriver = { 8 | findElement: sinon.stub(), 9 | findElements: sinon.stub() 10 | }; 11 | 12 | global.protractor = { 13 | By: 'protractor "By" property', 14 | }; 15 | 16 | global.browser = mockDriver; 17 | 18 | var Module = require('../lib/astrolabe/module'); 19 | 20 | ctxFindElementStub = sinon.stub(); 21 | ctxFindElementsStub = sinon.stub(); 22 | 23 | this.module = new Module({ 24 | findElement: ctxFindElementStub, 25 | findElements: ctxFindElementsStub 26 | }); 27 | }); 28 | 29 | afterEach(function() { 30 | global.protractor = undefined; 31 | }); 32 | 33 | it('should find an element', function() { 34 | 35 | this.module.findElement('moduleFindElementBy'); 36 | 37 | ctxFindElementStub.should.have.been.calledWithExactly('moduleFindElementBy'); 38 | 39 | this.module.driver.findElement.should.not.have.been.called; 40 | }); 41 | 42 | it('should find elements', function() { 43 | 44 | this.module.findElements('moduleFindElementsBy'); 45 | 46 | ctxFindElementsStub.should.have.been.calledWithExactly('moduleFindElementsBy'); 47 | 48 | this.module.driver.findElement.should.not.have.been.called; 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /test/page.js: -------------------------------------------------------------------------------- 1 | describe('Page', function() { 2 | 3 | beforeEach(function() { 4 | 5 | var mockDriver = { 6 | get: sinon.stub(), 7 | getTitle: sinon.stub(), 8 | debugger: sinon.stub(), 9 | findElement: sinon.stub(), 10 | findElements: sinon.stub(), 11 | getCurrentUrl: sinon.stub(), 12 | addMockModule: sinon.stub(), 13 | clearMockModules: sinon.stub(), 14 | browser: sinon.stub() 15 | }; 16 | 17 | global.protractor = { 18 | By: { 19 | id: sinon.stub(), 20 | css: sinon.stub(), 21 | xpath: sinon.stub(), 22 | name: sinon.stub(), 23 | tagName: sinon.stub(), 24 | className: sinon.stub(), 25 | linkText: sinon.stub(), 26 | partialLinkText: sinon.stub(), 27 | js: sinon.stub(), 28 | binding: sinon.stub(), 29 | select: sinon.stub(), 30 | selectedOption: sinon.stub(), 31 | input: sinon.stub(), 32 | model: sinon.stub(), 33 | textarea: sinon.stub(), 34 | repeater: sinon.stub(), 35 | buttonText: sinon.stub(), 36 | partialButtonText: sinon.stub(), 37 | cssContainingText: sinon.stub() 38 | } 39 | }; 40 | 41 | global.browser = mockDriver; 42 | 43 | var mockSerializer = { 44 | serialize: sinon.stub().returns("serialized script") 45 | }; 46 | 47 | this.mockURL = { 48 | url: 'fake/url', 49 | addPath: sinon.stub(), 50 | addParams: sinon.stub() 51 | }; 52 | 53 | var Page = Sandbox.require('../lib/astrolabe/page', { 54 | requires: { 55 | "./utils/url": sinon.stub().withArgs('/').returns(this.mockURL), 56 | "./utils/serializer": sinon.stub().returns(mockSerializer) 57 | } 58 | }); 59 | 60 | this.page = new Page(); 61 | }); 62 | 63 | it('should have a title', function() { 64 | 65 | this.page.driver.getTitle.returns('page title'); 66 | 67 | this.page.title.should.be.string('page title'); 68 | }); 69 | 70 | it('should have a currentUrl', function() { 71 | 72 | this.page.driver.getCurrentUrl.returns('http://currentUrl.com'); 73 | 74 | this.page.currentUrl.should.be.string('http://currentUrl.com'); 75 | }); 76 | 77 | it('should have a baseUrl', function() { 78 | 79 | this.page.driver.baseUrl = 'http://baseUrl.com'; 80 | 81 | this.page.baseUrl.should.be.string('http://baseUrl.com'); 82 | }); 83 | 84 | it('should find an element', function() { 85 | 86 | this.page.findElement('pageFindElementBy'); 87 | 88 | this.page.driver.findElement.should.have.been.calledWithExactly('pageFindElementBy'); 89 | }); 90 | 91 | it('should find elements', function() { 92 | 93 | this.page.findElements('pageFindElementsBy'); 94 | 95 | this.page.driver.findElements.should.have.been.calledWithExactly('pageFindElementsBy'); 96 | }); 97 | 98 | describe('find', function () { 99 | var selectorStrategies = [ 'id', 100 | 'css', 101 | 'xpath', 102 | 'name', 103 | 'tagName', 104 | 'className', 105 | 'linkText', 106 | 'partialLinkText', 107 | 'js', 108 | 'binding', 109 | 'select', 110 | 'selectedOption', 111 | 'input', 112 | 'model', 113 | 'textarea', 114 | 'repeater', 115 | 'buttonText', 116 | 'partialButtonText', 117 | 'cssContainingText']; 118 | 119 | describe('api', function () { 120 | selectorStrategies.forEach(function (scenario) { 121 | it('should find by ' + scenario, function () { 122 | this.page.find.by[scenario](scenario); 123 | this.page.by[scenario].should.have.been.calledOnce; 124 | this.page.driver.findElement.should.have.been.calledOnce; 125 | this.page.by[scenario].should.have.been.calledWithExactly(scenario); 126 | }); 127 | }); 128 | }); 129 | 130 | describe('all api', function () { 131 | selectorStrategies.forEach(function (scenario) { 132 | it('should find all by ' + scenario, function () { 133 | this.page.find.all.by[scenario](scenario); 134 | this.page.by[scenario].should.have.been.calledOnce; 135 | this.page.driver.findElements.should.have.been.calledOnce; 136 | this.page.by[scenario].should.have.been.calledWithExactly(scenario); 137 | }); 138 | }); 139 | }); 140 | }); 141 | 142 | describe('exceptions', function() { 143 | it('should throw an exception', function() { 144 | 145 | var exception = this.page.exception('CustomException'); 146 | expect(exception.thro).to.throw(exception); 147 | 148 | }); 149 | }); 150 | 151 | describe('debug', function() { 152 | 153 | it('should debug a page', function() { 154 | 155 | this.page.debug(); 156 | 157 | this.page.driver.debugger.should.have.been.calledOnce; 158 | }); 159 | }); 160 | 161 | describe('go', function() { 162 | 163 | it('should go to / by default', function() { 164 | 165 | this.page.go(); 166 | 167 | this.page.driver.get.should.have.been.calledWithExactly('fake/url'); 168 | }); 169 | 170 | it('should add paths to base url', function() { 171 | 172 | this.page.go('testPath'); 173 | 174 | this.mockURL.addPath.should.have.been.calledOnce; 175 | this.mockURL.addPath.should.have.been.calledWithExactly('testPath'); 176 | 177 | this.mockURL.addParams.should.not.have.been.called; 178 | }); 179 | 180 | it('should add params to base url', function() { 181 | 182 | this.page.go({ test: 'param' }); 183 | 184 | this.mockURL.addParams.should.have.been.calledOnce; 185 | this.mockURL.addParams.should.have.been.calledWithExactly({ test: 'param' }); 186 | 187 | this.mockURL.addPath.should.not.have.been.called; 188 | }); 189 | 190 | it('should add paths and params to base url', function() { 191 | 192 | this.page.go('testPath', { test: 'param' }, 'anotherPath'); 193 | 194 | this.mockURL.addPath.should.have.been.calledTwice; 195 | this.mockURL.addPath.firstCall.should.have.been.calledWithExactly('testPath'); 196 | this.mockURL.addPath.secondCall.should.have.been.calledWithExactly('anotherPath'); 197 | 198 | this.mockURL.addParams.should.have.been.calledOnce; 199 | this.mockURL.addParams.should.have.been.calledWithExactly({ test: 'param' }); 200 | }); 201 | }); 202 | 203 | describe('mockBackend', function() { 204 | 205 | it('should add a mock angular module with run config', function() { 206 | 207 | this.page.mockBackend(function() { return "test"; }); 208 | 209 | this.page.driver.addMockModule.should.have.been.calledOnce; 210 | this.page.driver.addMockModule.should.have.been.calledWithExactly('httpBackendMock', 'angular.module(\'httpBackendMock\', [\'ngMockE2E\']).run(serialized script);'); 211 | }); 212 | }); 213 | 214 | describe('mockModule', function() { 215 | 216 | it('should add a mock angular module', function() { 217 | 218 | this.page.mockModule('myModule', { test: "script" }); 219 | 220 | this.page.driver.addMockModule.should.have.been.calledOnce; 221 | this.page.driver.addMockModule.should.have.been.calledWithExactly('myModuleMock', 'angular.module(\'myModuleMock\', []).value(\'myModule\', serialized script);'); 222 | }); 223 | }); 224 | 225 | describe('clearMocks', function() { 226 | 227 | it('should clear all mock angular modules', function() { 228 | 229 | this.page.clearMocks(); 230 | 231 | this.page.driver.clearMockModules.should.have.been.calledOnce; 232 | }); 233 | }); 234 | }); 235 | -------------------------------------------------------------------------------- /test/serializer.js: -------------------------------------------------------------------------------- 1 | var Serializer = require('../lib/astrolabe/utils/serializer'); 2 | 3 | describe('Serializer', function() { 4 | 5 | describe('serialize', function() { 6 | 7 | var scenarios = [ 8 | { it: 'should serialize object to string', object: {}, expectedString: '{}' }, 9 | { it: 'should serialize object to string', object: { prop: 'prop' }, expectedString: '{prop: "prop"}' }, 10 | { it: 'should serialize object to string', object: { prop: 'prop', func: function() {} }, expectedString: '{prop: "prop", func: function () {}}' }, 11 | 12 | { it: 'should serialize function to string', object: function() {}, expectedString: 'function () {}' } 13 | ]; 14 | 15 | scenarios.forEach(function(scenario) { 16 | 17 | it(scenario.it, function() { 18 | 19 | var serializer = new Serializer(); 20 | 21 | expect(serializer.serialize(scenario.object)).to.equal(scenario.expectedString); 22 | }); 23 | }); 24 | 25 | }); 26 | }); -------------------------------------------------------------------------------- /test/url.js: -------------------------------------------------------------------------------- 1 | var URL = require('../lib/astrolabe/utils/url'); 2 | 3 | describe('URL', function() { 4 | 5 | beforeEach(function() { 6 | this.url = new URL('base'); 7 | }); 8 | 9 | describe('addPath', function() { 10 | 11 | it('should add a path to a base url', function() { 12 | 13 | this.url.addPath('test'); 14 | 15 | expect(this.url.url).to.have.string('base/test'); 16 | }); 17 | 18 | it('should add a multiple paths to a base url', function() { 19 | 20 | this.url.addPath('test', 'another'); 21 | 22 | expect(this.url.url).to.have.string('base/test/another'); 23 | }); 24 | 25 | it('should add a multiple paths to a base url', function() { 26 | 27 | this.url.addPath('test', 'me/too'); 28 | 29 | expect(this.url.url).to.have.string('base/test/me/too'); 30 | }); 31 | 32 | it('should support leading slashes', function() { 33 | 34 | this.url.addPath('/test', '/another'); 35 | 36 | expect(this.url.url).to.have.string('base/test/another'); 37 | }); 38 | 39 | it('should support leading slashes', function() { 40 | 41 | this.url.addPath('/test', '/me/three'); 42 | 43 | expect(this.url.url).to.have.string('base/test/me/three'); 44 | }); 45 | 46 | }); 47 | 48 | describe('addParam', function() { 49 | 50 | it('should add a query param to a base url', function() { 51 | 52 | this.url.addParams({test:'test'}); 53 | 54 | expect(this.url.url).to.have.string('base?test=test'); 55 | }); 56 | 57 | it('should add a query param to a base url', function() { 58 | 59 | this.url.addParams({ test: 'test', another: 'another' }); 60 | 61 | expect(this.url.url).to.have.string('base?test=test&another=another'); 62 | }); 63 | }); 64 | 65 | describe('method chaining', function() { 66 | 67 | it('should allow chaining of paths and params', function() { 68 | 69 | this.url.addParams({ test: 'test' }).addPath('testme').addParams({ another: 'another' }); 70 | 71 | expect(this.url.url).to.have.string('base/testme?test=test&another=another'); 72 | }); 73 | }); 74 | 75 | }); --------------------------------------------------------------------------------