diff --git a/lib/cli.js b/lib/cli.js
index 4715fbf96..8839c6b30 100644
--- a/lib/cli.js
+++ b/lib/cli.js
@@ -28,7 +28,7 @@ var argv = require('optimist').
alias('build', 'capabilities.build').
boolean('verbose').
boolean('includeStackTrace').
- alias('verbose', 'jasmineNodeOpts.verbose').
+ alias('verbose', 'jasmineNodeOpts.isVerbose').
alias('includeStackTrace', 'jasmineNodeOpts.includeStackTrace').
check(function(arg) {
if (arg._.length > 1) {
diff --git a/lib/locators.js b/lib/locators.js
index 0f60feeeb..e69679bf5 100644
--- a/lib/locators.js
+++ b/lib/locators.js
@@ -76,7 +76,9 @@ ProtractorBy.prototype.selectedOption = function(model) {
}
};
};
+
/**
+ * @DEPRECATED - use 'model' instead.
* Usage:
*
* ptor.findElement(protractor.By.input("user"));
@@ -94,6 +96,24 @@ ProtractorBy.prototype.input = function(model) {
};
};
+/**
+ * Usage:
+ *
+ * ptor.findElement(protractor.By.model("user"));
+ */
+ProtractorBy.prototype.model = function(model) {
+ return {
+ findOverride: function(driver, using) {
+ return driver.findElement(
+ webdriver.By.js(clientSideScripts.findInput), using, model);
+ },
+ findArrayOverride: function(driver, using) {
+ return driver.findElements(
+ webdriver.By.js(clientSideScripts.findInputs), using, model);
+ }
+ };
+};
+
/**
* Usage:
*
diff --git a/lib/protractor.js b/lib/protractor.js
index 419f8a36a..0bc0d1bdd 100644
--- a/lib/protractor.js
+++ b/lib/protractor.js
@@ -7,6 +7,11 @@ var ProtractorBy = require('./locators.js').ProtractorBy;
var DEFER_LABEL = 'NG_DEFER_BOOTSTRAP!';
+var WEB_ELEMENT_FUNCTIONS = [
+ 'click', 'sendKeys', 'getTagName', 'getCssValue', 'getAttribute', 'getText',
+ 'getSize', 'getLocation', 'isEnabled', 'isSelected', 'submit', 'clear',
+ 'isDisplayed', 'getOuterHtml', 'getInnerHtml'];
+
/**
* Mix in other webdriver functionality to be accessible via protractor.
*/
@@ -17,6 +22,8 @@ for (foo in webdriver) {
/**
* Mix a function from one object onto another. The function will still be
* called in the context of the original object.
+ *
+ * @private
* @param {Object} to
* @param {Object} from
* @param {string} fnName
@@ -28,7 +35,93 @@ var mixin = function(to, from, fnName, setupFn) {
setupFn();
}
return from[fnName].apply(from, arguments);
+ };
+};
+
+/**
+ * Build the helper 'element' function for a given instance of Protractor.
+ *
+ * @private
+ * @param {Protractor} ptor
+ * @return {function(webdriver.Locator): ElementFinder}
+ */
+var buildElementHelper = function(ptor) {
+ var element = function(locator) {
+ var elementFinder = {};
+ var webElementFns = WEB_ELEMENT_FUNCTIONS.concat(
+ ['findElement', 'findElements', 'isElementPresent',
+ 'evaluate', '$', '$$']);
+ webElementFns.forEach(function(fnName) {
+ elementFinder[fnName] = function() {
+ var elem = ptor.findElement(locator);
+ return elem[fnName].apply(elem, arguments);
+ };
+ });
+
+ elementFinder.find = function() {
+ return ptor.findElement(locator);
+ };
+
+ elementFinder.isPresent = function() {
+ return ptor.isElementPresent(locator);
+ };
+
+ return elementFinder;
+ };
+
+ /**
+ * @type {function(webdriver.Locator): ElementArrayFinder}
+ */
+ element.all = function(locator) {
+ var elementArrayFinder = {};
+
+ elementArrayFinder.count = function() {
+ return ptor.findElements(locator).then(function(arr) {
+ return arr.length;
+ });
+ };
+
+ elementArrayFinder.get = function(index) {
+ var id = ptor.findElements(locator).then(function(arr) {
+ return arr[index];
+ });
+ return new webdriver.WebElement(ptor.driver, id);
+ };
+
+ elementArrayFinder.then = function() {
+ return ptor.findElements(locator);
+ };
+
+ return elementArrayFinder;
}
+
+ return element;
+};
+
+/**
+ * Build the helper '$' function for a given instance of Protractor.
+ *
+ * @private
+ * @param {Protractor} ptor
+ * @return {function(string): ElementFinder}
+ */
+var buildCssHelper = function(ptor) {
+ return function(cssSelector) {
+ return buildElementHelper(ptor)(webdriver.By.css(cssSelector));
+ };
+};
+
+/**
+ * Build the helper '$$' function for a given instance of Protractor.
+ *
+ * @private
+ * @param {Protractor} ptor
+ * @return {function(string): ElementArrayFinder}
+ */
+var buildMultiCssHelper = function(ptor) {
+ return function(cssSelector) {
+ return buildElementHelper(ptor).all(webdriver.By.css(cssSelector));
+ };
};
/**
@@ -63,6 +156,27 @@ var Protractor = function(webdriver, opt_baseUrl, opt_rootElement) {
*/
this.driver = webdriver;
+ /**
+ * Helper function for finding elements.
+ *
+ * @type {function(webdriver.Locator): ElementFinder}
+ */
+ this.element = buildElementHelper(this);
+
+ /**
+ * Helper function for finding elements by css.
+ *
+ * @type {function(string): ElementFinder}
+ */
+ this.$ = buildCssHelper(this);
+
+ /**
+ * Helper function for finding arrays of elements by css.
+ *
+ * @type {function(string): ElementArrayFinder}
+ */
+ this.$$ = buildMultiCssHelper(this);
+
/**
* All get methods will be resolved against this base URL. Relative URLs are =
* resolved the way anchor tags resolve.
@@ -134,14 +248,10 @@ Protractor.prototype.waitForAngular = function() {
*/
Protractor.prototype.wrapWebElement = function(element) {
var thisPtor = this;
- // Before any of these WebElement functions, Protractor will wait to make sure
+ // Before any of the WebElement functions, Protractor will wait to make sure
// Angular is synched up.
- var functionsToSync = [
- 'click', 'sendKeys', 'getTagName', 'getCssValue', 'getAttribute', 'getText',
- 'getSize', 'getLocation', 'isEnabled', 'isSelected', 'submit', 'clear',
- 'isDisplayed', 'getOuterHtml', 'getInnerHtml'];
var originalFns = {};
- functionsToSync.forEach(function(name) {
+ WEB_ELEMENT_FUNCTIONS.forEach(function(name) {
originalFns[name] = element[name];
element[name] = function() {
thisPtor.waitForAngular();
@@ -254,18 +364,6 @@ Protractor.prototype.wrapWebElement = function(element) {
return element;
};
-/**
- * Shortcut for querying the document directly with css.
- *
- * @param {string} selector a css selector
- * @see webdriver.WebDriver.findElement
- * @return {!webdriver.WebElement}
- */
-Protractor.prototype.$ = function(selector) {
- var locator = protractor.By.css(selector);
- return this.findElement(locator);
-};
-
/**
* Waits for Angular to finish rendering before searching for elements.
* @see webdriver.WebDriver.findElement
@@ -284,19 +382,6 @@ Protractor.prototype.findElement = function(locator, varArgs) {
return this.wrapWebElement(found);
};
-/**
- * Shortcut for querying the document directly with css.
- *
- * @param {string} selector a css selector
- * @see webdriver.WebDriver.findElements
- * @return {!webdriver.promise.Promise} A promise that will be resolved to an
- * array of the located {@link webdriver.WebElement}s.
- */
-Protractor.prototype.$$ = function(selector) {
- var locator = protractor.By.css(selector);
- return this.findElements(locator);
-};
-
/**
* Waits for Angular to finish rendering before searching for elements.
* @see webdriver.WebDriver.findElements
diff --git a/lib/runner.js b/lib/runner.js
index ea395e163..50afd731a 100644
--- a/lib/runner.js
+++ b/lib/runner.js
@@ -191,16 +191,21 @@ var runJasmineTests = function() {
sessionId = session.getId();
- var ptor = protractor.wrapDriver(
+ var browser = protractor.wrapDriver(
driver,
config.baseUrl,
config.rootElement)
- ptor.params = config.params;
+ browser.params = config.params;
- protractor.setInstance(ptor);
+ protractor.setInstance(browser);
// Export protractor to the global namespace to be used in tests.
global.protractor = protractor;
+ global.browser = browser;
+ global.$ = browser.$;
+ global.$$ = browser.$$;
+ global.element = browser.element;
+ global.by = protractor.By;
// Let the configuration configure the protractor instance before running
// the tests.
diff --git a/spec/findelements_spec.js b/spec/backwardscompat/findelements_spec.js
similarity index 100%
rename from spec/findelements_spec.js
rename to spec/backwardscompat/findelements_spec.js
diff --git a/spec/lib_spec.js b/spec/backwardscompat/lib_spec.js
similarity index 100%
rename from spec/lib_spec.js
rename to spec/backwardscompat/lib_spec.js
diff --git a/spec/mockmodule_spec.js b/spec/backwardscompat/mockmodule_spec.js
similarity index 100%
rename from spec/mockmodule_spec.js
rename to spec/backwardscompat/mockmodule_spec.js
diff --git a/spec/polling_spec.js b/spec/backwardscompat/polling_spec.js
similarity index 100%
rename from spec/polling_spec.js
rename to spec/backwardscompat/polling_spec.js
diff --git a/spec/synchronize_spec.js b/spec/backwardscompat/synchronize_spec.js
similarity index 100%
rename from spec/synchronize_spec.js
rename to spec/backwardscompat/synchronize_spec.js
diff --git a/spec/testapp_spec.js b/spec/backwardscompat/testapp_spec.js
similarity index 100%
rename from spec/testapp_spec.js
rename to spec/backwardscompat/testapp_spec.js
diff --git a/spec/backwardscompatConf.js b/spec/backwardscompatConf.js
new file mode 100644
index 000000000..bd3691f30
--- /dev/null
+++ b/spec/backwardscompatConf.js
@@ -0,0 +1,25 @@
+// The main suite of Protractor tests.
+exports.config = {
+ seleniumServerJar: './selenium/selenium-server-standalone-2.35.0.jar',
+ chromeDriver: './selenium/chromedriver',
+
+ seleniumAddress: 'http://localhost:4444/wd/hub',
+
+ // Spec patterns are relative to this directory.
+ specs: [
+ 'backwardscompat/*_spec.js'
+ ],
+
+ capabilities: {
+ 'browserName': 'chrome'
+ },
+
+ baseUrl: 'http://localhost:8000',
+
+ params: {
+ login: {
+ user: 'Jane',
+ password: '1234'
+ }
+ }
+};
diff --git a/spec/basic/findelements_spec.js b/spec/basic/findelements_spec.js
new file mode 100644
index 000000000..aace0fa40
--- /dev/null
+++ b/spec/basic/findelements_spec.js
@@ -0,0 +1,457 @@
+var util = require('util');
+
+describe('locators', function() {
+ beforeEach(function() {
+ browser.get('app/index.html#/form');
+ });
+
+ describe('by binding', function() {
+ it('should find an element by binding', function() {
+ var greeting = element(by.binding('{{greeting}}'));
+
+ expect(greeting.getText()).toEqual('Hiya');
+ });
+
+ it('should find a binding by partial match', function() {
+ var greeting = element(by.binding('greet'));
+
+ expect(greeting.getText()).toEqual('Hiya');
+ });
+
+ it('should find an element by binding with attribute', function() {
+ var name = element(by.binding('username'));
+
+ expect(name.getText()).toEqual('Anon');
+ });
+ });
+
+ describe('by model', function() {
+ it('should find an element by text input model', function() {
+ var username = element(by.model('username'));
+ username.clear();
+ username.sendKeys('Jane Doe');
+
+ var name = element(by.binding('username'));
+
+ expect(name.getText()).toEqual('Jane Doe');
+ });
+
+ it('should find an element by checkbox input model', function() {
+ expect(element(by.id('shower')).isDisplayed()).
+ toBe(true);
+
+ var colors = element(by.model('show')).click();
+
+ expect(element(by.id('shower')).isDisplayed()).
+ toBe(false);
+ });
+
+ it('should find an element by textarea model', function() {
+ var about = element(by.textarea('aboutbox'));
+ expect(about.getAttribute('value')).toEqual('This is a text box');
+
+ about.clear();
+ about.sendKeys('Something else to write about');
+
+ expect(about.getAttribute('value')).
+ toEqual('Something else to write about');
+ });
+
+ it('should find inputs with alternate attribute forms', function() {
+ var letterList = element(by.id('letterlist'));
+ expect(letterList.getText()).toBe('');
+
+ element(by.model('check.w')).click();
+ expect(letterList.getText()).toBe('w');
+
+ element(by.model('check.x')).click();
+ expect(letterList.getText()).toBe('wx');
+
+ element(by.model('check.y')).click();
+ expect(letterList.getText()).toBe('wxy');
+
+ element(by.model('check.z')).click();
+ expect(letterList.getText()).toBe('wxyz');
+ });
+
+ it('should find multiple inputs', function() {
+ browser.findElements(by.model('color')).then(function(arr) {
+ expect(arr.length).toEqual(3);
+ });
+ });
+
+ it('should find multiple selects', function() {
+ browser.findElements(by.select('dc.color')).then(function(arr) {
+ expect(arr.length).toEqual(3);
+ });
+ });
+
+ it('should find multiple selected options', function() {
+ browser.findElements(by.selectedOption('dc.color')).then(function(arr) {
+ expect(arr.length).toEqual(3);
+ expect(arr[0].getText()).toBe('red');
+ expect(arr[1].getText()).toBe('green');
+ expect(arr[2].getText()).toBe('blue');
+ });
+ });
+ });
+
+ describe('by repeater', function() {
+ it('should find by partial match', function() {
+ var fullMatch = element(
+ by.repeater('baz in days | filter:\'T\'').
+ row(0).column('{{baz}}'));
+ expect(fullMatch.getText()).toEqual('Tue');
+
+ var partialMatch = element(
+ by.repeater('baz in days').row(0).column('b'));
+ expect(partialMatch.getText()).toEqual('Tue');
+
+ var partialRowMatch = element(
+ by.repeater('baz in days').row(0));
+ expect(partialRowMatch.getText()).toEqual('Tue');
+ });
+
+ it('should return all rows when unmodified', function() {
+ var all =
+ browser.findElements(by.repeater('dayColor in dayColors'));
+ all.then(function(arr) {
+ expect(arr.length).toEqual(3);
+ expect(arr[0].getText()).toEqual('Mon red');
+ expect(arr[1].getText()).toEqual('Tue green');
+ expect(arr[2].getText()).toEqual('Wed blue');
+ });
+ });
+
+ it('should return a single column', function() {
+ var colors = browser.findElements(
+ by.repeater('dayColor in dayColors').column('color'));
+ colors.then(function(arr) {
+ expect(arr.length).toEqual(3);
+ expect(arr[0].getText()).toEqual('red');
+ expect(arr[1].getText()).toEqual('green');
+ expect(arr[2].getText()).toEqual('blue');
+ });
+ });
+
+ it('should return a single row', function() {
+ var secondRow = element(
+ by.repeater('dayColor in dayColors').row(1));
+ expect(secondRow.getText()).toEqual('Tue green');
+ });
+
+ it('should return an individual cell', function() {
+ var secondColor = element(
+ by.repeater('dayColor in dayColors').
+ row(1).
+ column('color'));
+
+ var secondColorByColumnFirst = element(
+ by.repeater('dayColor in dayColors').
+ column('color').
+ row(1));
+
+ expect(secondColor.getText()).toEqual('green');
+ expect(secondColorByColumnFirst.getText()).toEqual('green');
+ });
+
+ it('should find a using data-ng-repeat', function() {
+ var byRow =
+ element(by.repeater('day in days').row(2));
+ expect(byRow.getText()).toEqual('Wed');
+
+ var byCol =
+ element(by.repeater('day in days').row(2).
+ column('day'));
+ expect(byCol.getText()).toEqual('Wed');
+ });
+
+ it('should find using ng:repeat', function() {
+ var byRow =
+ element(by.repeater('bar in days').row(2));
+ expect(byRow.getText()).toEqual('Wed');
+
+ var byCol =
+ element(by.repeater('bar in days').row(2).
+ column('bar'));
+ expect(byCol.getText()).toEqual('Wed');
+ });
+
+ it('should find using ng_repeat', function() {
+ var byRow =
+ element(by.repeater('foo in days').row(2));
+ expect(byRow.getText()).toEqual('Wed');
+
+ var byCol =
+ element(by.repeater('foo in days').row(2).
+ column('foo'));
+ expect(byCol.getText()).toEqual('Wed');
+ });
+
+ it('should find using x-ng-repeat', function() {
+ var byRow =
+ element(by.repeater('qux in days').row(2));
+ expect(byRow.getText()).toEqual('Wed');
+
+ var byCol =
+ element(by.repeater('qux in days').row(2).
+ column('qux'));
+ expect(byCol.getText()).toEqual('Wed');
+ });
+ });
+
+ it('should determine if an element is present', function() {
+ expect(browser.isElementPresent(by.binding('greet'))).toBe(true);
+ expect(browser.isElementPresent(by.binding('nopenopenope'))).toBe(false);
+ });
+});
+
+describe('chaining find elements', function() {
+ beforeEach(function() {
+ browser.get('app/index.html#/conflict');
+ });
+
+ it('should differentiate elements with the same binding by chaining',
+ function() {
+ expect(element(
+ by.binding('item.reusedBinding')).getText()).
+ toEqual('Outer: outer');
+
+ expect(element(by.id('baz')).
+ findElement(by.binding('item.resuedBinding')).
+ getText()).
+ toEqual('Inner: inner');
+ });
+
+ it('should find multiple elements scoped properly with chaining',
+ function() {
+ element.all(by.binding('item')).then(function(elems) {
+ expect(elems.length).toEqual(4);
+ });
+ element(by.id('baz')).
+ findElements(by.binding('item')).
+ then(function(elems) {
+ expect(elems.length).toEqual(2);
+ });
+ });
+
+ it('should determine element presence properly with chaining', function() {
+ expect(element(by.id('baz')).
+ isElementPresent(by.binding('item.resuedBinding'))).
+ toBe(true);
+
+ expect(element(by.id('baz')).
+ isElementPresent(by.binding('nopenopenope'))).
+ toBe(false);
+ })
+});
+
+describe('global element function', function() {
+ it('should return the same result as browser.findElement', function() {
+ browser.get('app/index.html#/form');
+ var nameByElement = element(by.binding('username'));
+ expect(nameByElement.getText()).toEqual(
+ browser.findElement(by.binding('username')).getText());
+ });
+
+ it('should wait to grab the WebElement until a method is called', function() {
+ browser.driver.get('about:blank');
+
+ // These should throw no error before a page is loaded.
+ var usernameInput = element(by.model('username'));
+ var name = element(by.binding('username'));
+
+ browser.get('app/index.html#/form');
+
+ expect(name.getText()).toEqual('Anon');
+
+ usernameInput.clear();
+ usernameInput.sendKeys('Jane');
+ expect(name.getText()).toEqual('Jane');
+ });
+
+ it('should count all elements', function() {
+ browser.get('app/index.html#/form');
+
+ element.all(by.model('color')).count().then(function(num) {
+ expect(num).toEqual(3);
+ });
+
+ // Should also work with promise expect unwrapping
+ expect(element.all(by.model('color')).count()).toEqual(3);
+ });
+
+ it('should get an element from an array', function() {
+ var colorList = element.all(by.binding('dayColor.color'));
+
+ browser.get('app/index.html#/form');
+
+ expect(colorList.get(0).getText()).toEqual('red');
+ expect(colorList.get(1).getText()).toEqual('green');
+ expect(colorList.get(2).getText()).toEqual('blue');
+ });
+
+ it('should export an isPresent helper', function() {
+ expect(element(by.binding('greet')).isPresent()).toBe(true);
+ expect(element(by.binding('nopenopenope')).isPresent()).toBe(false);
+ });
+});
+
+describe('evaluating statements', function() {
+ beforeEach(function() {
+ browser.get('app/index.html#/bindings');
+ });
+
+ it('should evaluate statements in the context of an element', function() {
+ var firstPlanet = element(by.binding('planet.name'));
+
+ firstPlanet.evaluate('planet.radius').then(function(output) {
+ expect(output).toEqual(1516); // radius of Mercury.
+ });
+
+ // Make sure it works with a promise expectation.
+ expect(firstPlanet.evaluate('planet.radius')).toEqual(1516);
+ });
+});
+
+describe('shortcut css notation', function() {
+ beforeEach(function() {
+ browser.get('app/index.html#/bindings');
+ });
+
+ describe('via the driver', function() {
+ it('should return the same results as web driver', function() {
+ element(by.css('.planet-info')).getText().then(function(textFromLongForm) {
+ var textFromShortcut = $('.planet-info').getText();
+ expect(textFromShortcut).toEqual(textFromLongForm);
+ });
+ });
+
+ it('should return the same array results as web driver', function() {
+ element.all(by.css('option')).then(function(optionsFromLongForm) {
+ $$('option').then(function(optionsFromShortcut) {
+ expect(optionsFromShortcut.length).toEqual(optionsFromLongForm.length);
+
+ optionsFromLongForm.forEach(function(option, i) {
+ option.getText().then(function(textFromLongForm) {
+ expect(optionsFromShortcut[i].getText()).toEqual(textFromLongForm);
+ });
+ });
+ });
+ });
+ });
+ });
+
+ describe('via a web element', function() {
+ var select;
+
+ beforeEach(function() {
+ select = element(by.css('select'));
+ });
+
+ it('should return the same results as web driver', function() {
+ select.findElement(by.css('option[value="4"]')).getText().then(function(textFromLongForm) {
+ var textFromShortcut = select.$('option[value="4"]').getText();
+ expect(textFromShortcut).toEqual(textFromLongForm);
+ });
+ });
+
+ it('should return the same array results as web driver', function() {
+ select.findElements(by.css('option')).then(function(optionsFromLongForm) {
+ select.$$('option').then(function(optionsFromShortcut) {
+ expect(optionsFromShortcut.length).toEqual(optionsFromLongForm.length);
+
+ optionsFromLongForm.forEach(function(option, i) {
+ option.getText().then(function(textFromLongForm) {
+ expect(optionsFromShortcut[i].getText()).toEqual(textFromLongForm);
+ });
+ });
+ });
+ });
+ });
+ });
+});
+
+describe('wrapping web driver elements', function() {
+ var verifyMethodsAdded = function(result) {
+ expect(typeof result.evaluate).toBe('function');
+ expect(typeof result.$).toBe('function');
+ expect(typeof result.$$).toBe('function');
+ }
+
+ beforeEach(function() {
+ browser.get('app/index.html#/bindings');
+ });
+
+ describe('when found via #findElement', function() {
+ describe('when using a locator that specifies an override', function() {
+ it('should wrap the result', function() {
+ browser.findElement(by.binding('planet.name')).then(verifyMethodsAdded);
+ });
+ });
+
+ describe('when using a locator that does not specify an override', function() {
+ it('should wrap the result', function() {
+ browser.findElement(by.css('option[value="4"]')).then(verifyMethodsAdded);
+ });
+ });
+ });
+
+ describe('when found via #findElements', function() {
+ describe('when using a locator that specifies an override', function() {
+ it('should wrap the results', function() {
+ browser.findElements(by.binding('planet.name')).then(function(results) {
+ results.forEach(verifyMethodsAdded);
+ });
+ });
+ });
+
+ describe('when using a locator that does not specify an override', function() {
+ it('should wrap the results', function() {
+ browser.findElements(by.css('option[value="4"]')).then(function(results) {
+ results.forEach(verifyMethodsAdded);
+ });
+ });
+ });
+ });
+
+ describe('when querying against a found element', function() {
+ var info;
+
+ beforeEach(function() {
+ info = browser.findElement(by.css('.planet-info'));
+ });
+
+ describe('when found via #findElement', function() {
+ describe('when using a locator that specifies an override', function() {
+ it('should wrap the result', function() {
+ info.findElement(by.binding('planet.name')).then(verifyMethodsAdded);
+ });
+ });
+
+ describe('when using a locator that does not specify an override', function() {
+ it('should wrap the result', function() {
+ info.findElement(by.css('div:last-child')).then(verifyMethodsAdded);
+ });
+ });
+ });
+
+ describe('when querying for many elements', function() {
+ describe('when using a locator that specifies an override', function() {
+ it('should wrap the result', function() {
+ info.findElements(by.binding('planet.name')).then(function(results) {
+ results.forEach(verifyMethodsAdded);
+ });
+ });
+ });
+
+ describe('when using a locator that does not specify an override', function() {
+ it('should wrap the result', function() {
+ info.findElements(by.css('div:last-child')).then(function(results) {
+ results.forEach(verifyMethodsAdded);
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/spec/basic/lib_spec.js b/spec/basic/lib_spec.js
new file mode 100644
index 000000000..024fbc9e2
--- /dev/null
+++ b/spec/basic/lib_spec.js
@@ -0,0 +1,49 @@
+var util = require('util');
+
+describe('no ptor at all', function() {
+ it('should still do normal tests', function() {
+ expect(true).toBe(true);
+ });
+});
+
+describe('protractor library', function() {
+ it('should expose the correct global variables', function() {
+ expect(protractor).toBeDefined();
+ expect(browser).toBeDefined();
+ expect(by).toBeDefined();
+ expect(element).toBeDefined();
+ expect($).toBeDefined();
+ });
+
+ it('should wrap webdriver', function() {
+ browser.get('app/index.html');
+ expect(browser.getTitle()).toEqual('My AngularJS App');
+ });
+
+ it('should export custom parameters to the protractor instance', function() {
+ expect(browser.params.login).toBeDefined();
+ expect(browser.params.login.user).toEqual('Jane');
+ expect(browser.params.login.password).toEqual('1234');
+ });
+
+ it('should allow a mix of using protractor and using the driver directly',
+ function() {
+ browser.get('app/index.html');
+ expect(browser.getCurrentUrl()).
+ toEqual('http://localhost:8000/app/index.html#/http')
+
+ browser.driver.findElement(protractor.By.linkText('repeater')).click();
+ expect(browser.driver.getCurrentUrl()).
+ toEqual('http://localhost:8000/app/index.html#/repeater');
+
+ browser.navigate().back();
+ expect(browser.driver.getCurrentUrl()).
+ toEqual('http://localhost:8000/app/index.html#/http');
+ });
+
+ it('should export other webdriver classes onto the global protractor',
+ function() {
+ expect(protractor.ActionSequence).toBeDefined();
+ expect(protractor.Key.RETURN).toEqual('\uE006');
+ });
+});
diff --git a/spec/basic/mockmodule_spec.js b/spec/basic/mockmodule_spec.js
new file mode 100644
index 000000000..da6c27386
--- /dev/null
+++ b/spec/basic/mockmodule_spec.js
@@ -0,0 +1,39 @@
+var util = require('util');
+
+describe('mock modules', function() {
+ // A module to override the 'version' service. This function will be
+ // executed in the context of the application under test, so it may
+ // not refer to any local variables.
+ var mockModuleA = function() {
+ var newModule = angular.module('moduleA', []);
+ newModule.value('version', '2');
+ };
+
+ // A second module overriding the 'version' service.
+ // This module shows the use of a string for the load
+ // function.
+ // TODO(julie): Consider this syntax. Should we allow loading the
+ // modules from files? Provide helpers?
+ var mockModuleB = "angular.module('moduleB', []).value('version', '3');";
+
+ afterEach(function() {
+ browser.clearMockModules();
+ });
+
+ it('should override services via mock modules', function() {
+ browser.addMockModule('moduleA', mockModuleA);
+
+ browser.get('app/index.html');
+
+ expect(element(by.css('[app-version]')).getText()).toEqual('2');
+ });
+
+ it('should have the version of the last loaded module', function() {
+ browser.addMockModule('moduleA', mockModuleA);
+ browser.addMockModule('moduleB', mockModuleB);
+
+ browser.get('app/index.html');
+
+ expect(element(by.css('[app-version]')).getText()).toEqual('3');
+ });
+});
diff --git a/spec/basic/polling_spec.js b/spec/basic/polling_spec.js
new file mode 100644
index 000000000..138f2d8ba
--- /dev/null
+++ b/spec/basic/polling_spec.js
@@ -0,0 +1,39 @@
+var util = require('util');
+
+/**
+ * These tests show how to turn off Protractor's synchronization
+ * when using applications which poll with $http or $timeout.
+ * A better solution is to switch to the angular $interval service if possible
+ */
+describe('synchronizing with pages that poll', function() {
+ beforeEach(function() {
+ browser.get('app/index.html#/polling');
+ });
+
+ it('avoids timeouts using ignoreSynchronization', function() {
+ var startButton = element(by.id('pollstarter'));
+
+ var count = element(by.binding('count'));
+ expect(count.getText()).toEqual('0');
+
+ startButton.click();
+
+ // Turn this on to see timeouts.
+ browser.ignoreSynchronization = true;
+
+ count.getText().then(function(text) {
+ expect(text).toBeGreaterThan(-1);
+ });
+
+ browser.sleep(2000);
+
+ count.getText().then(function(text) {
+ expect(text).toBeGreaterThan(1);
+ });
+ });
+
+ afterEach(function() {
+ // Remember to turn it off when you're done!
+ browser.ignoreSynchronization = false;
+ });
+});
diff --git a/spec/basic/synchronize_spec.js b/spec/basic/synchronize_spec.js
new file mode 100644
index 000000000..8b97fa854
--- /dev/null
+++ b/spec/basic/synchronize_spec.js
@@ -0,0 +1,75 @@
+var util = require('util');
+
+describe('synchronizing with slow pages', function() {
+ beforeEach(function() {
+ browser.get('app/index.html#/async');
+ });
+
+ it('waits for http calls', function() {
+ var status = element(by.binding('slowHttpStatus'));
+ var button = element(by.css('[ng-click="slowHttp()"]'));
+
+ expect(status.getText()).toEqual('not started');
+
+ button.click();
+
+ expect(status.getText()).toEqual('done');
+ });
+
+ it('waits for long javascript execution', function() {
+ var status = element(by.binding('slowFunctionStatus'));
+ var button = element(by.css('[ng-click="slowFunction()"]'));
+
+ expect(status.getText()).toEqual('not started');
+
+ button.click();
+
+ expect(status.getText()).toEqual('done');
+ });
+
+ it('DOES NOT wait for timeout', function() {
+ var status = element(by.binding('slowTimeoutStatus'));
+ var button = element(by.css('[ng-click="slowTimeout()"]'));
+
+ expect(status.getText()).toEqual('not started');
+
+ button.click();
+
+ expect(status.getText()).toEqual('pending...');
+ });
+
+ it('waits for $timeout', function() {
+ var status = element(by.binding('slowAngularTimeoutStatus'));
+ var button = element(by.css('[ng-click="slowAngularTimeout()"]'));
+
+ expect(status.getText()).toEqual('not started');
+
+ button.click();
+
+ expect(status.getText()).toEqual('done');
+ });
+
+ it('waits for $timeout then a promise', function() {
+ var status = element(by.binding(
+ 'slowAngularTimeoutPromiseStatus'));
+ var button = element(by.css(
+ '[ng-click="slowAngularTimeoutPromise()"]'));
+
+ expect(status.getText()).toEqual('not started');
+
+ button.click();
+
+ expect(status.getText()).toEqual('done');
+ });
+
+ it('waits for long http call then a promise', function() {
+ var status = element(by.binding('slowHttpPromiseStatus'));
+ var button = element(by.css('[ng-click="slowHttpPromise()"]'));
+
+ expect(status.getText()).toEqual('not started');
+
+ button.click();
+
+ expect(status.getText()).toEqual('done');
+ });
+});
diff --git a/spec/basicConf.js b/spec/basicConf.js
index 059799967..09db2bc03 100644
--- a/spec/basicConf.js
+++ b/spec/basicConf.js
@@ -7,7 +7,7 @@ exports.config = {
// Spec patterns are relative to this directory.
specs: [
- '*_spec.js'
+ 'basic/*_spec.js'
],
capabilities: {