protractor
Advanced tools
Comparing version 0.1.3 to 0.2.0
@@ -10,2 +10,7 @@ { | ||
}, | ||
"devDependencies" : { | ||
"jasmine-node" : "~1.8.0", | ||
"expect.js" : "~0.2.0", | ||
"mocha" : "~1.10.0" | ||
}, | ||
"repository" : { | ||
@@ -16,3 +21,3 @@ "type" : "git", | ||
"main" : "protractor.js", | ||
"version" : "0.1.3" | ||
"version" : "0.2.0" | ||
} |
@@ -5,3 +5,272 @@ var util = require('util'); | ||
var DEFER_LABEL = 'NG_DEFER_BOOTSTRAP!'; | ||
/** | ||
* All scripts to be run on the client via executeAsyncScript or | ||
* executeScript should be put here. These scripts are transmitted over | ||
* the wire using their toString representation, and cannot reference | ||
* external variables. They can, however use the array passed in to | ||
* arguments. Instead of params, all functions on clientSideScripts | ||
* should list the arguments array they expect. | ||
*/ | ||
var clientSideScripts = {}; | ||
/** | ||
* Wait until Angular has finished rendering and has | ||
* no outstanding $http calls before continuing. | ||
* | ||
* arguments none | ||
*/ | ||
clientSideScripts.waitForAngular = function() { | ||
var callback = arguments[arguments.length - 1]; | ||
angular.element(document.body).injector().get('$browser'). | ||
notifyWhenNoOutstandingRequests(callback); | ||
}; | ||
/** | ||
* Find an element in the page by their angular binding. | ||
* | ||
* arguments[0] {string} The binding, e.g. {{cat.name}} | ||
* | ||
* @return {WebElement} The element containing the binding | ||
*/ | ||
clientSideScripts.findBinding = function() { | ||
var bindings = document.getElementsByClassName('ng-binding'); | ||
var matches = []; | ||
var binding = arguments[0]; | ||
for (var i = 0; i < bindings.length; ++i) { | ||
var bindingName = angular.element(bindings[i]).data().$binding[0].exp || | ||
angular.element(bindings[i]).data().$binding; | ||
if (bindingName.indexOf(binding) != -1) { | ||
matches.push(bindings[i]); | ||
} | ||
} | ||
return matches[0]; // We can only return one with webdriver.findElement. | ||
}; | ||
/** | ||
* Find a list of elements in the page by their angular binding. | ||
* | ||
* arguments[0] {string} The binding, e.g. {{cat.name}} | ||
* | ||
* @return {Array.<WebElement>} The elements containing the binding | ||
*/ | ||
clientSideScripts.findBindings = function() { | ||
var bindings = document.getElementsByClassName('ng-binding'); | ||
var matches = []; | ||
var binding = arguments[0]; | ||
for (var i = 0; i < bindings.length; ++i) { | ||
var bindingName = angular.element(bindings[i]).data().$binding[0].exp || | ||
angular.element(bindings[i]).data().$binding; | ||
if (bindingName.indexOf(binding) != -1) { | ||
matches.push(bindings[i]); | ||
} | ||
} | ||
return matches; // Return the whole array for webdriver.findElements. | ||
}; | ||
/** | ||
* Find a row within an ng-repeat. | ||
* | ||
* arguments[0] {string} The text of the repeater, e.g. 'cat in cats' | ||
* arguments[1] {number} The row index | ||
* | ||
* @return {Element} The row element | ||
*/ | ||
clientSideScripts.findRepeaterRow = function() { | ||
var repeater = arguments[0]; | ||
var index = arguments[1]; | ||
var rows = []; | ||
var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:']; | ||
for (var p = 0; p < prefixes.length; ++p) { | ||
var attr = prefixes[p] + 'repeat'; | ||
var repeatElems = document.querySelectorAll('[' + attr + ']'); | ||
attr = attr.replace(/\\/g, ''); | ||
for (var i = 0; i < repeatElems.length; ++i) { | ||
if (repeatElems[i].getAttribute(attr).indexOf(repeater) != -1) { | ||
rows.push(repeatElems[i]); | ||
} | ||
} | ||
} | ||
return rows[index - 1]; | ||
}; | ||
/** | ||
* Find an element within an ng-repeat by its row and column. | ||
* | ||
* arguments[0] {string} The text of the repeater, e.g. 'cat in cats' | ||
* arguments[1] {number} The row index | ||
* arguments[2] {string} The column binding, e.g. '{{cat.name}}' | ||
* | ||
* @return {Element} The element | ||
*/ | ||
clientSideScripts.findRepeaterElement = function() { | ||
var matches = []; | ||
var repeater = arguments[0]; | ||
var index = arguments[1]; | ||
var binding = arguments[2]; | ||
var rows = []; | ||
var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:']; | ||
for (var p = 0; p < prefixes.length; ++p) { | ||
var attr = prefixes[p] + 'repeat'; | ||
var repeatElems = document.querySelectorAll('[' + attr + ']'); | ||
attr = attr.replace(/\\/g, ''); | ||
for (var i = 0; i < repeatElems.length; ++i) { | ||
if (repeatElems[i].getAttribute(attr).indexOf(repeater) != -1) { | ||
rows.push(repeatElems[i]); | ||
} | ||
} | ||
} | ||
var row = rows[index - 1]; | ||
var bindings = []; | ||
if (row.className.indexOf('ng-binding') != -1) { | ||
bindings.push(row); | ||
} | ||
var childBindings = row.getElementsByClassName('ng-binding'); | ||
for (var i = 0; i < childBindings.length; ++i) { | ||
bindings.push(childBindings[i]); | ||
} | ||
for (var i = 0; i < bindings.length; ++i) { | ||
var bindingName = angular.element(bindings[i]).data().$binding[0].exp || | ||
angular.element(bindings[i]).data().$binding; | ||
if (bindingName.indexOf(binding) != -1) { | ||
matches.push(bindings[i]); | ||
} | ||
} | ||
// We can only return one with webdriver.findElement. | ||
return matches[0]; | ||
}; | ||
/** | ||
* Find the elements in a column of an ng-repeat. | ||
* | ||
* arguments[0] {string} The text of the repeater, e.g. 'cat in cats' | ||
* arguments[1] {string} The column binding, e.g. '{{cat.name}}' | ||
* | ||
* @return {Array.<Element>} The elements in the column | ||
*/ | ||
clientSideScripts.findRepeaterColumn = function() { | ||
var matches = []; | ||
var repeater = arguments[0]; | ||
var binding = arguments[1]; | ||
var rows = []; | ||
var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:']; | ||
for (var p = 0; p < prefixes.length; ++p) { | ||
var attr = prefixes[p] + 'repeat'; | ||
var repeatElems = document.querySelectorAll('[' + attr + ']'); | ||
attr = attr.replace(/\\/g, ''); | ||
for (var i = 0; i < repeatElems.length; ++i) { | ||
if (repeatElems[i].getAttribute(attr).indexOf(repeater) != -1) { | ||
rows.push(repeatElems[i]); | ||
} | ||
} | ||
} | ||
for (var i = 0; i < rows.length; ++i) { | ||
var bindings = []; | ||
if (rows[i].className.indexOf('ng-binding') != -1) { | ||
bindings.push(rows[i]); | ||
} | ||
var childBindings = rows[i].getElementsByClassName('ng-binding'); | ||
for (var k = 0; k < childBindings.length; ++k) { | ||
bindings.push(childBindings[k]); | ||
} | ||
for (var j = 0; j < bindings.length; ++j) { | ||
var bindingName = angular.element(bindings[j]).data().$binding[0].exp || | ||
angular.element(bindings[j]).data().$binding; | ||
if (bindingName.indexOf(binding) != -1) { | ||
matches.push(bindings[j]); | ||
} | ||
} | ||
} | ||
return matches; | ||
}; | ||
/** | ||
* Find an input element by model name. | ||
* | ||
* arguments[0] {string} The model name | ||
* | ||
* @return {Element} The first matching input element. | ||
*/ | ||
clientSideScripts.findInput = function() { | ||
var model = arguments[0]; | ||
var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:']; | ||
for (var p = 0; p < prefixes.length; ++p) { | ||
var selector = 'input[' + prefixes[p] + 'model=' + model + ']'; | ||
var inputs = document.querySelectorAll(selector); | ||
if (inputs.length) { | ||
return inputs[0]; | ||
} | ||
} | ||
}; | ||
/** | ||
* Find an select element by model name. | ||
* | ||
* arguments[0] {string} The model name | ||
* | ||
* @return {Element} The first matching select element. | ||
*/ | ||
clientSideScripts.findSelect = function() { | ||
var model = arguments[0]; | ||
var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:']; | ||
for (var p = 0; p < prefixes.length; ++p) { | ||
var selector = 'select[' + prefixes[p] + 'model=' + model + ']'; | ||
var inputs = document.querySelectorAll(selector); | ||
if (inputs.length) { | ||
return inputs[0]; | ||
} | ||
} | ||
}; | ||
/** | ||
* Find an selected option element by model name. | ||
* | ||
* arguments[0] {string} The model name | ||
* | ||
* @return {Element} The first matching input element. | ||
*/ | ||
clientSideScripts.findSelectedOption = function() { | ||
var model = arguments[0]; | ||
var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:']; | ||
for (var p = 0; p < prefixes.length; ++p) { | ||
var selector = | ||
'select[' + prefixes[p] + 'model=' + model + '] option:checked'; | ||
var inputs = document.querySelectorAll(selector); | ||
if (inputs.length) { | ||
return inputs[0]; | ||
} | ||
} | ||
}; | ||
/** | ||
* Tests whether the angular global variable is present on a page. Retries | ||
* twice in case the page is just loading slowly. | ||
* | ||
* arguments none | ||
* | ||
* @return {boolean} true if angular was found. | ||
*/ | ||
clientSideScripts.testForAngular = function() { | ||
var callback = arguments[arguments.length - 1]; | ||
var retry = function(n) { | ||
if (window.angular && window.angular.resumeBootstrap) { | ||
callback(true); | ||
} else if (n < 1) { | ||
callback(false) | ||
} else { | ||
window.setTimeout(function() {retry(n - 1)}, 1000); | ||
} | ||
} | ||
if (window.angular && window.angular.resumeBootstrap) { | ||
callback(true); | ||
} else { | ||
retry(3); | ||
} | ||
} | ||
/** | ||
* @param {webdriver.WebDriver} webdriver | ||
@@ -26,7 +295,3 @@ * @constructor | ||
Protractor.prototype.waitForAngular = function() { | ||
return this.driver.executeAsyncScript(function() { | ||
var callback = arguments[arguments.length - 1]; | ||
angular.element(document.body).injector().get('$browser'). | ||
notifyWhenNoOutstandingRequests(callback); | ||
}); | ||
return this.driver.executeAsyncScript(clientSideScripts.waitForAngular); | ||
}; | ||
@@ -48,2 +313,15 @@ | ||
/** | ||
* See webdriver.WebDriver.findElements | ||
* | ||
* Waits for Angular to finish rendering before searching for elements. | ||
*/ | ||
Protractor.prototype.findElements = function(locator, varArgs) { | ||
this.waitForAngular(); | ||
if (locator.findArrayOverride) { | ||
return locator.findArrayOverride(this.driver); | ||
} | ||
return this.driver.findElements(locator, varArgs); | ||
}; | ||
/** | ||
* Add a module to load before Angular whenever Protractor.get is called. | ||
@@ -80,5 +358,14 @@ * Modules will be registered after existing modules already on the page, | ||
'window.location.href = "' + destination + '"'); | ||
// Make sure the page is an Angular page. | ||
this.driver.executeAsyncScript(clientSideScripts.testForAngular). | ||
then(function(hasAngular) { | ||
if (!hasAngular) { | ||
throw new Error("Angular could not be found on the page " + | ||
destination); | ||
} | ||
}); | ||
// At this point, Angular will pause for us, until angular.resumeBootstrap | ||
// is called. | ||
for (var i = 0; i < this.moduleScripts_.length; ++i) { | ||
@@ -130,14 +417,9 @@ this.driver.executeScript(this.moduleScripts_[i]); | ||
findOverride: function(driver) { | ||
return driver.findElement(webdriver.By.js(function() { | ||
var bindings = document.getElementsByClassName('ng-binding'); | ||
var matches = []; | ||
var binding = arguments[0]; | ||
for (var i = 0; i < bindings.length; ++i) { | ||
if (angular.element(bindings[i]).data().$binding[0].exp == binding) { | ||
matches.push(bindings[i]); | ||
} | ||
} | ||
return matches[0]; // We can only return one with webdriver.findElement. | ||
}), bindingDescriptor); | ||
return driver.findElement(webdriver.By.js(clientSideScripts.findBinding), | ||
bindingDescriptor); | ||
}, | ||
findArrayOverride: function(driver) { | ||
return driver.findElements( | ||
webdriver.By.js(clientSideScripts.findBindings), | ||
bindingDescriptor); | ||
} | ||
@@ -154,4 +436,6 @@ }; | ||
return { | ||
using: 'css selector', | ||
value: 'select[ng-model=' + model + ']' | ||
findOverride: function(driver) { | ||
return driver.findElement( | ||
webdriver.By.js(clientSideScripts.findSelect), model); | ||
} | ||
}; | ||
@@ -162,4 +446,6 @@ }; | ||
return { | ||
using: 'css selector', | ||
value: 'select[ng-model=' + model + '] option:checked' | ||
findOverride: function(driver) { | ||
return driver.findElement( | ||
webdriver.By.js(clientSideScripts.findSelectedOption), model); | ||
} | ||
}; | ||
@@ -170,4 +456,6 @@ }; | ||
return { | ||
using: 'css selector', | ||
value: 'input[ng-model=' + model + ']' | ||
findOverride: function(driver) { | ||
return driver.findElement( | ||
webdriver.By.js(clientSideScripts.findInput), model); | ||
} | ||
}; | ||
@@ -189,2 +477,5 @@ }; | ||
* protractor.By.repeater("cat in pets").row(1).column("{{cat.name}}")); | ||
* // Returns an array of WebElements from a column | ||
* var ages = ptor.findElements( | ||
* protractor.By.repeater("cat in pets").column("{{cat.age}}")); | ||
*/ | ||
@@ -195,26 +486,13 @@ ProtractorBy.prototype.repeater = function(repeatDescriptor) { | ||
return { | ||
using: 'css selector', | ||
value: '[ng-repeat="' + repeatDescriptor | ||
+ '"]:nth-of-type(' + index + ')', | ||
findOverride: function(driver) { | ||
return driver.findElement( | ||
webdriver.By.js(clientSideScripts.findRepeaterRow), | ||
repeatDescriptor, index); | ||
}, | ||
column: function(binding) { | ||
return { | ||
findOverride: function(driver) { | ||
return driver.findElement(webdriver.By.js(function() { | ||
var matches = []; | ||
var repeater = arguments[0]; | ||
var index = arguments[1]; | ||
var binding = arguments[2]; | ||
var rows = document.querySelectorAll('[ng-repeat="' | ||
+ repeater + '"]'); | ||
var row = rows[index - 1]; | ||
var bindings = row.getElementsByClassName('ng-binding'); | ||
for (var i = 0; i < bindings.length; ++i) { | ||
if (angular.element(bindings[i]).data().$binding[0].exp == binding) { | ||
matches.push(bindings[i]); | ||
} | ||
} | ||
// We can only return one with webdriver.findElement. | ||
return matches[0]; | ||
}), repeatDescriptor, index, binding); | ||
return driver.findElement( | ||
webdriver.By.js(clientSideScripts.findRepeaterElement), | ||
repeatDescriptor, index, binding); | ||
} | ||
@@ -224,2 +502,11 @@ }; | ||
}; | ||
}, | ||
column: function(binding) { | ||
return { | ||
findArrayOverride: function(driver) { | ||
return driver.findElements( | ||
webdriver.By.js(clientSideScripts.findRepeaterColumn), | ||
repeatDescriptor, binding); | ||
} | ||
} | ||
} | ||
@@ -226,0 +513,0 @@ }; |
Protractor | ||
========== | ||
Protractor is an end to end test framework for Angular applications built on top of [webdriverJS](https://code.google.com/p/selenium/wiki/WebDriverJs). It is still quite in progress. | ||
Protractor is an end to end test framework for Angular applications built on top of [webdriverJS](https://code.google.com/p/selenium/wiki/WebDriverJs). | ||
@@ -25,3 +25,4 @@ To run the sample tests | ||
node httpspec.js | ||
jasmine-node spec | ||
mocha test | ||
@@ -40,7 +41,7 @@ To just use Protractor | ||
var webdriver = require('selenium-webdriver'); | ||
var protractor = require('./protractor.js'); | ||
var protractor = require('protractor'); | ||
// Configure and build your webdriver instance. | ||
var ptor = protractor.wrapDriver(driver); | ||
See httpspec.js for examples of use. | ||
See spec/testAppSpec.js for examples of use. | ||
@@ -47,0 +48,0 @@ Appendix A: Setting up a standalone selenium server |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
18258
467
58
3
4
1