From a2cd6c8baf242a81c4efea1f55249d597de95329 Mon Sep 17 00:00:00 2001 From: Julie Date: Sat, 26 Oct 2013 23:02:19 -0700 Subject: [PATCH] feat(syntax): big syntax reboot, expose global $, $$, element, and by In an effort to make tests more readable and clear, a few more global variables will now be exported. `browser` is an instance of protractor. This was previously accessed using `protractor.getInstance`. `by` is a collection of element locators. Previously, this was `protractor.By`. `$` is a shortcut for getting elements by css. `$('.foo')` === `element(by.css('.foo'))` All changes should be backwards compatible, as tested with the new 'backwardscompat' tests. Closes #156. --- lib/cli.js | 2 +- lib/locators.js | 20 + lib/protractor.js | 147 ++++-- lib/runner.js | 11 +- .../findelements_spec.js | 0 spec/{ => backwardscompat}/lib_spec.js | 0 spec/{ => backwardscompat}/mockmodule_spec.js | 0 spec/{ => backwardscompat}/polling_spec.js | 0 .../{ => backwardscompat}/synchronize_spec.js | 0 spec/{ => backwardscompat}/testapp_spec.js | 0 spec/backwardscompatConf.js | 25 + spec/basic/findelements_spec.js | 457 ++++++++++++++++++ spec/basic/lib_spec.js | 49 ++ spec/basic/mockmodule_spec.js | 39 ++ spec/basic/polling_spec.js | 39 ++ spec/basic/synchronize_spec.js | 75 +++ spec/basicConf.js | 2 +- 17 files changed, 830 insertions(+), 36 deletions(-) rename spec/{ => backwardscompat}/findelements_spec.js (100%) rename spec/{ => backwardscompat}/lib_spec.js (100%) rename spec/{ => backwardscompat}/mockmodule_spec.js (100%) rename spec/{ => backwardscompat}/polling_spec.js (100%) rename spec/{ => backwardscompat}/synchronize_spec.js (100%) rename spec/{ => backwardscompat}/testapp_spec.js (100%) create mode 100644 spec/backwardscompatConf.js create mode 100644 spec/basic/findelements_spec.js create mode 100644 spec/basic/lib_spec.js create mode 100644 spec/basic/mockmodule_spec.js create mode 100644 spec/basic/polling_spec.js create mode 100644 spec/basic/synchronize_spec.js 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: {