From acaec8bdd157e9933d608c66204a52335fb46ee4 Mon Sep 17 00:00:00 2001 From: Hank Duan Date: Fri, 2 Jan 2015 20:45:14 -0800 Subject: [PATCH 01/55] feat(index): add jasmine2.0 support --- README.md | 10 +- index.js | 220 ++++++++++++++------------------ package.json | 10 +- scripts/test.sh | 19 +++ spec/adapterSpec.js | 99 ++------------ spec/common.js | 93 ++++++++++++++ spec/errorSpec.js | 89 +++++++++++++ spec/support/failing_specs.json | 6 + spec/support/passing_specs.json | 6 + 9 files changed, 326 insertions(+), 226 deletions(-) create mode 100755 scripts/test.sh create mode 100644 spec/common.js create mode 100644 spec/errorSpec.js create mode 100644 spec/support/failing_specs.json create mode 100644 spec/support/passing_specs.json diff --git a/README.md b/README.md index 9cd01dc..9ec0f3a 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Features Installation ------------ ``` -npm install jasminewd +npm install jasminewd2 ``` Usage @@ -26,15 +26,17 @@ Assumes selenium-webdriver as a peer dependency. ```js // In your setup. -var minijn = require('minijasminenode'); -require('jasminewd'); +var JasmineRunner = require('jasmine'); +var jrunner = new JasmineRunner(); +require('jasminewd2'); global.driver = new webdriver.Builder(). usingServer('http://localhost:4444/wd/hub'). withCapabilities({browserName: 'chrome'}). build(); -minijn.executeSpecs(/* ... */); +jrunner.projectBaseDir = ''; +jrunner.execute(['**/*_spec.js']); // In your tests diff --git a/index.js b/index.js index b73969e..04f8a47 100644 --- a/index.js +++ b/index.js @@ -72,39 +72,36 @@ function wrapInControlFlow(globalFn, fnName) { var driverError = new Error(); driverError.stack = driverError.stack.replace(/ +at.+jasminewd.+\n/, ''); - function asyncTestFn(fn, desc) { + function asyncTestFn(fn) { return function(done) { - var desc_ = 'Asynchronous test function: ' + fnName + '('; - if (desc) { - desc_ += '"' + desc + '"'; - } - desc_ += ')'; - // deferred object for signaling completion of asychronous function within globalFn - var asyncFnDone = webdriver.promise.defer(); + var asyncFnDone = webdriver.promise.defer(), + originalFail = jasmine.getEnv().fail; if (fn.length === 0) { // function with globalFn not asychronous asyncFnDone.fulfill(); } else if (fn.length > 1) { throw Error('Invalid # arguments (' + fn.length + ') within function "' + fnName +'"'); + } else { + // Add fail method to async done callback and override env fail to + // reject async done promise + jasmine.getEnv().fail = asyncFnDone.fulfill.fail = function(userError) { + asyncFnDone.reject(new Error(userError)); + }; } var flowFinished = flow.execute(function() { - fn.call(jasmine.getEnv().currentSpec, function(userError) { - if (userError) { - asyncFnDone.reject(new Error(userError)); - } else { - asyncFnDone.fulfill(); - } - }); - }, desc_); + fn.call(jasmine.getEnv(), asyncFnDone.fulfill); + }); webdriver.promise.all([asyncFnDone, flowFinished]).then(function() { + jasmine.getEnv().fail = originalFail; seal(done)(); }, function(e) { - e.stack = e.stack + '==== async task ====\n' + driverError.stack; - done(e); + jasmine.getEnv().fail = originalFail; + e.stack = e.stack + '\n==== async task ====\n' + driverError.stack; + done.fail(e); }); }; } @@ -112,7 +109,7 @@ function wrapInControlFlow(globalFn, fnName) { var description, func, timeout; switch (fnName) { case 'it': - case 'iit': + case 'fit': description = validateString(arguments[0]); func = validateFunction(arguments[1]); if (!arguments[2]) { @@ -124,6 +121,8 @@ function wrapInControlFlow(globalFn, fnName) { break; case 'beforeEach': case 'afterEach': + case 'beforeAll': + case 'afterAll': func = validateFunction(arguments[0]); if (!arguments[1]) { globalFn(asyncTestFn(func)); @@ -139,117 +138,99 @@ function wrapInControlFlow(globalFn, fnName) { } global.it = wrapInControlFlow(global.it, 'it'); -global.iit = wrapInControlFlow(global.iit, 'iit'); +global.fit = wrapInControlFlow(global.fit, 'fit'); global.beforeEach = wrapInControlFlow(global.beforeEach, 'beforeEach'); global.afterEach = wrapInControlFlow(global.afterEach, 'afterEach'); +global.beforeAll = wrapInControlFlow(global.beforeAll, 'beforeAll'); +global.afterAll = wrapInControlFlow(global.afterAll, 'afterAll'); +var originalExpect = global.expect; +global.expect = function(actual) { + if (actual instanceof webdriver.WebElement) { + throw 'expect called with WebElement argument, expected a Promise. ' + + 'Did you mean to use .getText()?'; + } + return originalExpect(actual); +}; /** - * Wrap a Jasmine matcher function so that it can take webdriverJS promises. - * @param {!Function} matcher The matcher function to wrap. - * @param {webdriver.promise.Promise} actualPromise The promise which will - * resolve to the actual value being tested. - * @param {boolean} not Whether this is being called with 'not' active. + * Creates a matcher wrapper that resolves any promises given for actual and + * expected values, as well as the `pass` property of the result. */ -function wrapMatcher(matcher, actualPromise, not) { +jasmine.Expectation.prototype.wrapCompare = function(name, matcherFactory) { return function() { - var originalArgs = arguments; - var matchError = new Error("Failed expectation"); - matchError.stack = matchError.stack.replace(/ +at.+jasminewd.+\n/, ''); - actualPromise.then(function(actual) { - var expected = originalArgs[0]; + var expected = Array.prototype.slice.call(arguments, 0), + expectation = this, + matchError = new Error("Failed expectation"); - var expectation = originalExpect(actual); - if (not) { - expectation = expectation.not; - } - var originalAddMatcherResult = expectation.spec.addMatcherResult; - var error = matchError; - expectation.spec.addMatcherResult = function(result) { - result.trace = error; - jasmine.Spec.prototype.addMatcherResult.call(this, result); - }; + matchError.stack = matchError.stack.replace(/ +at.+jasminewd.+\n/, ''); - if (webdriver.promise.isPromise(expected)) { - if (originalArgs.length > 1) { - throw error('Multi-argument matchers with promises are not ' + - 'supported.'); - } - expected.then(function(exp) { - expectation[matcher].apply(expectation, [exp]); - expectation.spec.addMatcherResult = originalAddMatcherResult; + flow.execute(function() { + return webdriver.promise.when(expectation.actual).then(function(actual) { + return webdriver.promise.all(expected).then(function(expected) { + return compare(actual, expected); }); - } else { - expectation.spec.addMatcherResult = function(result) { - result.trace = error; - originalAddMatcherResult.call(this, result); - }; - expectation[matcher].apply(expectation, originalArgs); - expectation.spec.addMatcherResult = originalAddMatcherResult; - } + }); }); - }; -} -/** - * Return a chained set of matcher functions which will be evaluated - * after actualPromise is resolved. - * @param {webdriver.promise.Promise} actualPromise The promise which will - * resolve to the actual value being tested. - */ -function promiseMatchers(actualPromise) { - var promises = {not: {}}; - var env = jasmine.getEnv(); - var matchersClass = env.currentSpec.matchersClass || env.matchersClass; + function compare(actual, expected) { + var args = expected.slice(0); + args.unshift(actual); - for (var matcher in matchersClass.prototype) { - promises[matcher] = wrapMatcher(matcher, actualPromise, false); - promises.not[matcher] = wrapMatcher(matcher, actualPromise, true); - } + var matcher = matcherFactory(expectation.util, expectation.customEqualityTesters); + var matcherCompare = matcher.compare; - return promises; -} + if (expectation.isNot) { + matcherCompare = matcher.negativeCompare || defaultNegativeCompare; + } -var originalExpect = global.expect; + var result = matcherCompare.apply(null, args); -global.expect = function(actual) { - if (actual instanceof webdriver.WebElement) { - throw 'expect called with WebElement argument, expected a Promise. ' + - 'Did you mean to use .getText()?'; - } - if (webdriver.promise.isPromise(actual)) { - return promiseMatchers(actual); - } else { - return originalExpect(actual); - } -}; + return webdriver.promise.when(result.pass).then(function(pass) { + var message = ""; -// Wrap internal Jasmine function to allow custom matchers -// to return promises that resolve to truthy or falsy values -var originalMatcherFn = jasmine.Matchers.matcherFn_; -jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) { - var matcherFnThis = this; - var matcherFnArgs = jasmine.util.argsToArray(arguments); - return function() { - var matcherThis = this; - var matcherArgs = jasmine.util.argsToArray(arguments); - var result = matcherFunction.apply(this, arguments); + if (!pass) { + if (!result.message) { + args.unshift(expectation.isNot); + args.unshift(name); + message = expectation.util.buildFailureMessage.apply(null, args); + } else { + message = result.message; + } + } - if (webdriver.promise.isPromise(result)) { - result.then(function(resolution) { - matcherFnArgs[1] = function() { - return resolution; + if (expected.length == 1) { + expected = expected[0]; + } + var res = { + matcherName: name, + passed: pass, + message: message, + actual: actual, + expected: expected, + error: matchError }; - originalMatcherFn.apply(matcherFnThis, matcherFnArgs). - apply(matcherThis, matcherArgs); + expectation.addExpectationResult(pass, res); }); - } else { - originalMatcherFn.apply(matcherFnThis, matcherFnArgs). - apply(matcherThis, matcherArgs); + + function defaultNegativeCompare() { + var result = matcher.compare.apply(null, args); + if (webdriver.promise.isPromise(result.pass)) { + result.pass = result.pass.then(function(pass) { + return !pass; + }); + } else { + result.pass = !result.pass; + } + return result; + } } }; }; +// Re-add core matchers so they are wrapped. +jasmine.Expectation.addCoreMatchers(jasmine.matchers); + /** * A Jasmine reporter which does nothing but execute the input function * on a timeout failure. @@ -258,30 +239,17 @@ var OnTimeoutReporter = function(fn) { this.callback = fn; }; -OnTimeoutReporter.prototype.reportRunnerStarting = function() {}; -OnTimeoutReporter.prototype.reportRunnerResults = function() {}; -OnTimeoutReporter.prototype.reportSuiteResults = function() {}; -OnTimeoutReporter.prototype.reportSpecStarting = function() {}; -OnTimeoutReporter.prototype.reportSpecResults = function(spec) { - if (!spec.results().passed()) { - var result = spec.results(); - var failureItem = null; +OnTimeoutReporter.prototype.specDone = function(result) { + if (result.status === 'failed') { + for (var i = 0; i < result.failedExpectations.length; i++) { + var failureMessage = result.failedExpectations[i].message; - var items_length = result.getItems().length; - for (var i = 0; i < items_length; i++) { - if (result.getItems()[i].passed_ === false) { - failureItem = result.getItems()[i]; - - var jasmineTimeoutRegexp = - /timed out after \d+ msec waiting for spec to complete/; - if (failureItem.toString().match(jasmineTimeoutRegexp)) { - this.callback(); - } + if (failureMessage.match(/Timeout/)) { + this.callback(); } } } }; -OnTimeoutReporter.prototype.log = function() {}; // On timeout, the flow should be reset. This will prevent webdriver tasks // from overflowing into the next test and causing it to fail or timeout @@ -292,7 +260,7 @@ jasmine.getEnv().addReporter(new OnTimeoutReporter(function() { console.warn('A Jasmine spec timed out. Resetting the WebDriver Control Flow.'); console.warn('The last active task was: '); console.warn( - (flow.activeFrame_ && flow.activeFrame_.getPendingTask() ? + (flow.activeFrame_ && flow.activeFrame_.getPendingTask() ? flow.activeFrame_.getPendingTask().toString() : 'unknown')); flow.reset(); diff --git a/package.json b/package.json index 1f0390d..50f5434 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "jasminewd", - "description": "WebDriverJS adapter for Jasmine.", + "name": "jasminewd2", + "description": "WebDriverJS adapter for Jasmine2.", "homepage": "https://github.com/angular/jasminewd", "keywords": [ "test", @@ -13,7 +13,7 @@ "author": "Julie Ralph ", "devDependencies": { "jshint": "2.5.0", - "minijasminenode": "1.1.1", + "jasmine": "2.1.1", "selenium-webdriver": "2.43.4" }, "repository": { @@ -23,8 +23,8 @@ "main": "index.js", "scripts": { "pretest": "node_modules/.bin/jshint index.js spec", - "test": "node node_modules/.bin/minijasminenode spec/adapterSpec.js" + "test": "scripts/test.sh" }, "license": "MIT", - "version": "1.1.0" + "version": "0.0.2" } diff --git a/scripts/test.sh b/scripts/test.sh new file mode 100755 index 0000000..c7c555a --- /dev/null +++ b/scripts/test.sh @@ -0,0 +1,19 @@ +PASSING_SPECS="spec/support/passing_specs.json" +FAILING_SPECS="spec/support/failing_specs.json" +CMD_BASE="node node_modules/.bin/jasmine JASMINE_CONFIG_PATH=" + +echo "### running passing specs" +CMD=$CMD_BASE$PASSING_SPECS +$CMD +[ "$?" -eq 0 ] || exit 1 +echo + +EXPECTED_RESULTS="13 specs, 12 failures" +echo "### running failing specs (expecting $EXPECTED_RESULTS)" +CMD=$CMD_BASE$FAILING_SPECS +res=`$CMD 2>/dev/null` +results_line=`echo "$res" | tail -2 | head -1` +echo "result: $results_line" +[ "$results_line" = "$EXPECTED_RESULTS" ] || exit 1 + +echo "all pass" diff --git a/spec/adapterSpec.js b/spec/adapterSpec.js index e6fa185..852bd2a 100644 --- a/spec/adapterSpec.js +++ b/spec/adapterSpec.js @@ -1,5 +1,6 @@ -require('../index.js'); var webdriver = require('selenium-webdriver'); +var common = require('./common.js'); +require('../index.js'); /** * Tests for the WebDriverJS Jasmine-Node Adapter. These tests use @@ -7,69 +8,7 @@ var webdriver = require('selenium-webdriver'); * webdriver. */ -var getFakeDriver = function() { - var flow = webdriver.promise.controlFlow(); - return { - controlFlow: function() { - return flow; - }, - sleep: function(ms) { - return flow.timeout(ms); - }, - setUp: function() { - return flow.execute(function() { - return webdriver.promise.fulfilled('setup done'); - }); - }, - getValueA: function() { - return flow.execute(function() { - return webdriver.promise.delayed(500).then(function() { - return webdriver.promise.fulfilled('a'); - }); - }); - }, - getOtherValueA: function() { - return flow.execute(function() { - return webdriver.promise.fulfilled('a'); - }); - }, - getValueB: function() { - return flow.execute(function() { - return webdriver.promise.fulfilled('b'); - }); - }, - getBigNumber: function() { - return flow.execute(function() { - return webdriver.promise.fulfilled(1111); - }); - }, - getDecimalNumber: function() { - return flow.execute(function() { - return webdriver.promise.fulfilled(3.14159); - }); - }, - getDisplayedElement: function() { - return flow.execute(function() { - return webdriver.promise.fulfilled({ - isDisplayed: function() { - return webdriver.promise.fulfilled(true); - } - }); - }); - }, - getHiddenElement: function() { - return flow.execute(function() { - return webdriver.promise.fulfilled({ - isDisplayed: function() { - return webdriver.promise.fulfilled(false); - } - }); - }); - } - }; -}; - -var fakeDriver = getFakeDriver(); +var fakeDriver = common.getFakeDriver(); describe('webdriverJS Jasmine adapter plain', function() { it('should pass normal synchronous tests', function() { @@ -80,24 +19,15 @@ describe('webdriverJS Jasmine adapter plain', function() { describe('webdriverJS Jasmine adapter', function() { // Shorten this and you should see tests timing out. - jasmine.getEnv().defaultTimeoutInterval = 2000; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 2000; beforeEach(function() { - // 'this' should work properly to add matchers. - this.addMatchers({ - toBeLotsMoreThan: function(expected) { - return this.actual > expected + 100; - }, - // Example custom matcher returning a promise that resolves to true/false. - toBeDisplayed: function() { - return this.actual.isDisplayed(); - } - }); + jasmine.addMatchers(common.getMatchers()); }); beforeEach(function() { fakeDriver.setUp().then(function(value) { - console.log('This should print before each test: ' + value); + // console.log('This should print before each test: ' + value); }); }); @@ -136,6 +66,8 @@ describe('webdriverJS Jasmine adapter', function() { it('should allow the use of custom matchers', function() { expect(500).toBeLotsMoreThan(3); expect(fakeDriver.getBigNumber()).toBeLotsMoreThan(33); + expect(fakeDriver.getBigNumber()).toBeLotsMoreThan(fakeDriver.getSmallNumber()); + expect(fakeDriver.getSmallNumber()).not.toBeLotsMoreThan(fakeDriver.getBigNumber()); }); it('should allow custom matchers to return a promise', function() { @@ -176,21 +108,6 @@ describe('webdriverJS Jasmine adapter', function() { 'Did you mean to use .getText()?'); }); - // Uncomment to see timeout failures. - - // it('should timeout after 200ms', function() { - // expect(fakeDriver.getValueA()).toEqual('a'); - // }, 300); - - // it('should timeout after 300ms', function() { - // fakeDriver.sleep(9999); - // expect(fakeDriver.getValueB()).toEqual('b'); - // }, 300); - - // it('should pass errors from done callback', function(done) { - // done('an error'); - // }); - it('should pass after the timed out tests', function() { expect(fakeDriver.getValueA()).toEqual('a'); }); diff --git a/spec/common.js b/spec/common.js new file mode 100644 index 0000000..03e6c86 --- /dev/null +++ b/spec/common.js @@ -0,0 +1,93 @@ +require('../index.js'); +var webdriver = require('selenium-webdriver'); + +exports.getFakeDriver = function() { + var flow = webdriver.promise.controlFlow(); + return { + controlFlow: function() { + return flow; + }, + sleep: function(ms) { + return flow.timeout(ms); + }, + setUp: function() { + return flow.execute(function() { + return webdriver.promise.fulfilled('setup done'); + }); + }, + getValueA: function() { + return flow.execute(function() { + return webdriver.promise.delayed(500).then(function() { + return webdriver.promise.fulfilled('a'); + }); + }); + }, + getOtherValueA: function() { + return flow.execute(function() { + return webdriver.promise.fulfilled('a'); + }); + }, + getValueB: function() { + return flow.execute(function() { + return webdriver.promise.fulfilled('b'); + }); + }, + getBigNumber: function() { + return flow.execute(function() { + return webdriver.promise.fulfilled(1111); + }); + }, + getSmallNumber: function() { + return flow.execute(function() { + return webdriver.promise.fulfilled(11); + }); + }, + getDecimalNumber: function() { + return flow.execute(function() { + return webdriver.promise.fulfilled(3.14159); + }); + }, + getDisplayedElement: function() { + return flow.execute(function() { + return webdriver.promise.fulfilled({ + isDisplayed: function() { + return webdriver.promise.fulfilled(true); + } + }); + }); + }, + getHiddenElement: function() { + return flow.execute(function() { + return webdriver.promise.fulfilled({ + isDisplayed: function() { + return webdriver.promise.fulfilled(false); + } + }); + }); + } + }; +}; + +exports.getMatchers = function() { + return { + toBeLotsMoreThan: function() { + return { + compare: function(actual, expected) { + return { + pass: actual > expected + 100 + }; + } + }; + }, + // Example custom matcher returning a promise that resolves to true/false. + toBeDisplayed: function() { + return { + compare: function(actual, expected) { + return { + pass: actual.isDisplayed() + }; + } + }; + } + }; +}; diff --git a/spec/errorSpec.js b/spec/errorSpec.js new file mode 100644 index 0000000..be9b714 --- /dev/null +++ b/spec/errorSpec.js @@ -0,0 +1,89 @@ +var webdriver = require('selenium-webdriver'); +var common = require('./common.js'); +require('../index.js'); + +/** + * Error tests for the WebDriverJS Jasmine-Node Adapter. These tests use + * WebDriverJS's control flow and promises without setting up the whole + * webdriver. + */ + +var fakeDriver = common.getFakeDriver(); + +describe('Timeout cases', function() { + it('should timeout after 200ms', function(done) { + expect(fakeDriver.getValueA()).toEqual('a'); + }, 200); + + it('should timeout after 300ms', function() { + fakeDriver.sleep(9999); + expect(fakeDriver.getValueB()).toEqual('b'); + }, 300); + + it('should pass after the timed out tests', function() { + expect(true).toEqual(true); + }); +}); + +describe('things that should fail', function() { + beforeEach(function() { + jasmine.addMatchers(common.getMatchers()); + }); + + it('should pass errors from done callback', function(done) { + done.fail('an error'); + }); + + it('should fail normal synchronous tests', function() { + expect(true).toBe(false); + }); + + it('should compare a promise to a primitive', function() { + expect(fakeDriver.getValueA()).toEqual('d'); + expect(fakeDriver.getValueB()).toEqual('e'); + }); + + it('should wait till the expect to run the flow', function() { + var promiseA = fakeDriver.getValueA(); + expect(promiseA.isPending()).toBe(true); + expect(promiseA).toEqual('a'); + expect(promiseA.isPending()).toBe(false); + }); + + it('should compare a promise to a promise', function() { + expect(fakeDriver.getValueA()).toEqual(fakeDriver.getValueB()); + }); + + it('should still allow use of the underlying promise', function() { + var promiseA = fakeDriver.getValueA(); + promiseA.then(function(value) { + expect(value).toEqual('b'); + }); + }); + + it('should allow scheduling of tasks', function() { + fakeDriver.sleep(300); + expect(fakeDriver.getValueB()).toEqual('c'); + }); + + it('should allow the use of custom matchers', function() { + expect(1000).toBeLotsMoreThan(999); + expect(fakeDriver.getBigNumber()).toBeLotsMoreThan(1110); + expect(fakeDriver.getBigNumber()).not.toBeLotsMoreThan(fakeDriver.getSmallNumber()); + expect(fakeDriver.getSmallNumber()).toBeLotsMoreThan(fakeDriver.getBigNumber()); + }); + + it('should allow custom matchers to return a promise', function() { + expect(fakeDriver.getDisplayedElement()).not.toBeDisplayed(); + expect(fakeDriver.getHiddenElement()).toBeDisplayed(); + }); + + it('should pass multiple arguments to matcher', function() { + // Passing specific precision + expect(fakeDriver.getDecimalNumber()).toBeCloseTo(3.5, 1); + + // Using default precision (2) + expect(fakeDriver.getDecimalNumber()).toBeCloseTo(3.1); + expect(fakeDriver.getDecimalNumber()).not.toBeCloseTo(3.14); + }); +}); diff --git a/spec/support/failing_specs.json b/spec/support/failing_specs.json new file mode 100644 index 0000000..4545241 --- /dev/null +++ b/spec/support/failing_specs.json @@ -0,0 +1,6 @@ +{ + "spec_dir": "spec", + "spec_files": [ + "errorSpec.js" + ] +} diff --git a/spec/support/passing_specs.json b/spec/support/passing_specs.json new file mode 100644 index 0000000..e5466b6 --- /dev/null +++ b/spec/support/passing_specs.json @@ -0,0 +1,6 @@ +{ + "spec_dir": "spec", + "spec_files": [ + "adapterSpec.js" + ] +} From fdb03a388d4846952c09fb0ad75a37b46674c750 Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Fri, 30 Jan 2015 14:54:05 -0800 Subject: [PATCH 02/55] docs(readme): add note about jasmine 1 vs jasmine 2 --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 9ec0f3a..3d90e22 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,10 @@ jasminewd [![Build Status](https://travis-ci.org/angular/jasminewd.png?branch=ma Adapter for Jasmine-to-WebDriverJS. Used by [Protractor](http://www.github.com/angular/protractor). +**Important:** There are two active branches of jasminewd. + + - [master](https://github.com/angular/jasminewd/tree/master) is an adapter for Jasmine 1.3, and uses the package minijasminenode. It is published to npm as `jasminewd`. + - [jasminewd2](https://github.com/angular/jasminewd/tree/jasminewd2) is an adapter for Jasmine 2.x, and uses the package jasmine. It is published to npm as `jasminewd2`. Features -------- From 161e1fa48deaa5ea0f485027ea8ae41562864936 Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Tue, 17 Mar 2015 11:44:45 -0700 Subject: [PATCH 03/55] fix(errors): update webdriverjs, fix asynchronous error output Add some console logging, remove useless info about the last running task in the control flow, and fix error where problems reported from done.fail were getting pushed into the following spec. Closes #18 --- index.js | 17 +++++------------ package.json | 2 +- spec/common.js | 18 +++++++++--------- spec/errorSpec.js | 2 +- 4 files changed, 16 insertions(+), 23 deletions(-) diff --git a/index.js b/index.js index 04f8a47..64c79d1 100644 --- a/index.js +++ b/index.js @@ -75,8 +75,7 @@ function wrapInControlFlow(globalFn, fnName) { function asyncTestFn(fn) { return function(done) { // deferred object for signaling completion of asychronous function within globalFn - var asyncFnDone = webdriver.promise.defer(), - originalFail = jasmine.getEnv().fail; + var asyncFnDone = webdriver.promise.defer(); if (fn.length === 0) { // function with globalFn not asychronous @@ -86,20 +85,19 @@ function wrapInControlFlow(globalFn, fnName) { } else { // Add fail method to async done callback and override env fail to // reject async done promise - jasmine.getEnv().fail = asyncFnDone.fulfill.fail = function(userError) { + asyncFnDone.fulfill.fail = function(userError) { asyncFnDone.reject(new Error(userError)); }; + } var flowFinished = flow.execute(function() { fn.call(jasmine.getEnv(), asyncFnDone.fulfill); - }); + }, 'Run ' + fnName + ' in control flow'); webdriver.promise.all([asyncFnDone, flowFinished]).then(function() { - jasmine.getEnv().fail = originalFail; seal(done)(); }, function(e) { - jasmine.getEnv().fail = originalFail; e.stack = e.stack + '\n==== async task ====\n' + driverError.stack; done.fail(e); }); @@ -171,7 +169,7 @@ jasmine.Expectation.prototype.wrapCompare = function(name, matcherFactory) { return compare(actual, expected); }); }); - }); + }, 'Expect ' + name); function compare(actual, expected) { var args = expected.slice(0); @@ -258,10 +256,5 @@ OnTimeoutReporter.prototype.specDone = function(result) { // get to complete first. jasmine.getEnv().addReporter(new OnTimeoutReporter(function() { console.warn('A Jasmine spec timed out. Resetting the WebDriver Control Flow.'); - console.warn('The last active task was: '); - console.warn( - (flow.activeFrame_ && flow.activeFrame_.getPendingTask() ? - flow.activeFrame_.getPendingTask().toString() : - 'unknown')); flow.reset(); })); diff --git a/package.json b/package.json index 50f5434..76d6418 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "devDependencies": { "jshint": "2.5.0", "jasmine": "2.1.1", - "selenium-webdriver": "2.43.4" + "selenium-webdriver": "2.45.1" }, "repository": { "type": "git", diff --git a/spec/common.js b/spec/common.js index 03e6c86..de956e4 100644 --- a/spec/common.js +++ b/spec/common.js @@ -13,39 +13,39 @@ exports.getFakeDriver = function() { setUp: function() { return flow.execute(function() { return webdriver.promise.fulfilled('setup done'); - }); + }, 'setUp'); }, getValueA: function() { return flow.execute(function() { return webdriver.promise.delayed(500).then(function() { return webdriver.promise.fulfilled('a'); }); - }); + }, 'getValueA'); }, getOtherValueA: function() { return flow.execute(function() { return webdriver.promise.fulfilled('a'); - }); + }, 'getOtherValueA'); }, getValueB: function() { return flow.execute(function() { return webdriver.promise.fulfilled('b'); - }); + }, 'getValueB'); }, getBigNumber: function() { return flow.execute(function() { return webdriver.promise.fulfilled(1111); - }); + }, 'getBigNumber'); }, getSmallNumber: function() { return flow.execute(function() { return webdriver.promise.fulfilled(11); - }); + }, 'getSmallNumber'); }, getDecimalNumber: function() { return flow.execute(function() { return webdriver.promise.fulfilled(3.14159); - }); + }, 'getDecimalNumber'); }, getDisplayedElement: function() { return flow.execute(function() { @@ -54,7 +54,7 @@ exports.getFakeDriver = function() { return webdriver.promise.fulfilled(true); } }); - }); + }, 'getDisplayedElement'); }, getHiddenElement: function() { return flow.execute(function() { @@ -63,7 +63,7 @@ exports.getFakeDriver = function() { return webdriver.promise.fulfilled(false); } }); - }); + }, 'getHiddenElement'); } }; }; diff --git a/spec/errorSpec.js b/spec/errorSpec.js index be9b714..79352f4 100644 --- a/spec/errorSpec.js +++ b/spec/errorSpec.js @@ -31,7 +31,7 @@ describe('things that should fail', function() { }); it('should pass errors from done callback', function(done) { - done.fail('an error'); + done.fail('an error from done.fail'); }); it('should fail normal synchronous tests', function() { From 759dc736a2ee10917fa3f7d6906566686eb91158 Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Tue, 17 Mar 2015 17:16:47 -0700 Subject: [PATCH 04/55] chore(release): version bump for 0.0.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 76d6418..f814612 100644 --- a/package.json +++ b/package.json @@ -26,5 +26,5 @@ "test": "scripts/test.sh" }, "license": "MIT", - "version": "0.0.2" + "version": "0.0.3" } From c0f13d254966c859db22d020a5390138dbf48e64 Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Mon, 13 Apr 2015 14:42:07 -0700 Subject: [PATCH 05/55] refactor(asyncTestFn): refactor async test wrapping to show more info Test wrapping for Jasmine 2 now more closely follows the test wrapping for Mocha at https://github.com/SeleniumHQ/selenium/blob/master/javascript/node/selenium-webdriver/testing/index.js This also adds more information to the task names in the control flow, for easier debugging. --- index.js | 54 +++++++++++++++++++++++------------------------ scripts/test.sh | 2 +- spec/errorSpec.js | 4 ++++ 3 files changed, 31 insertions(+), 29 deletions(-) diff --git a/index.js b/index.js index 64c79d1..74a6949 100644 --- a/index.js +++ b/index.js @@ -72,34 +72,32 @@ function wrapInControlFlow(globalFn, fnName) { var driverError = new Error(); driverError.stack = driverError.stack.replace(/ +at.+jasminewd.+\n/, ''); - function asyncTestFn(fn) { + function asyncTestFn(fn, description) { + description = description ? ('("' + description + '")') : ''; return function(done) { - // deferred object for signaling completion of asychronous function within globalFn - var asyncFnDone = webdriver.promise.defer(); + var async = fn.length > 0; + testFn = fn.bind(this); - if (fn.length === 0) { - // function with globalFn not asychronous - asyncFnDone.fulfill(); - } else if (fn.length > 1) { - throw Error('Invalid # arguments (' + fn.length + ') within function "' + fnName +'"'); - } else { - // Add fail method to async done callback and override env fail to - // reject async done promise - asyncFnDone.fulfill.fail = function(userError) { - asyncFnDone.reject(new Error(userError)); - }; - - } - - var flowFinished = flow.execute(function() { - fn.call(jasmine.getEnv(), asyncFnDone.fulfill); - }, 'Run ' + fnName + ' in control flow'); - - webdriver.promise.all([asyncFnDone, flowFinished]).then(function() { - seal(done)(); - }, function(e) { - e.stack = e.stack + '\n==== async task ====\n' + driverError.stack; - done.fail(e); + flow.execute(function controlFlowExecute() { + return new webdriver.promise.Promise(function(fulfill, reject) { + if (async) { + // If testFn is async (it expects a done callback), resolve the promise of this + // test whenever that callback says to. Any promises returned from testFn are + // ignored. + var proxyDone = fulfill; + proxyDone.fail = function(err) { + reject(err); + }; + testFn(proxyDone); + } else { + // Without a callback, testFn can return a promise, or it will + // be assumed to have completed synchronously. + fulfill(testFn()); + } + }, flow); + }, 'Run ' + fnName + description + ' in control flow').then(seal(done), function(err) { + err.stack = err.stack + '\n==== async task ====\n' + driverError.stack; + done.fail(err); }); }; } @@ -111,10 +109,10 @@ function wrapInControlFlow(globalFn, fnName) { description = validateString(arguments[0]); func = validateFunction(arguments[1]); if (!arguments[2]) { - globalFn(description, asyncTestFn(func)); + globalFn(description, asyncTestFn(func, description)); } else { timeout = validateNumber(arguments[2]); - globalFn(description, asyncTestFn(func), timeout); + globalFn(description, asyncTestFn(func, description), timeout); } break; case 'beforeEach': diff --git a/scripts/test.sh b/scripts/test.sh index c7c555a..31be5d2 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -8,7 +8,7 @@ $CMD [ "$?" -eq 0 ] || exit 1 echo -EXPECTED_RESULTS="13 specs, 12 failures" +EXPECTED_RESULTS="14 specs, 13 failures" echo "### running failing specs (expecting $EXPECTED_RESULTS)" CMD=$CMD_BASE$FAILING_SPECS res=`$CMD 2>/dev/null` diff --git a/spec/errorSpec.js b/spec/errorSpec.js index 79352f4..811548a 100644 --- a/spec/errorSpec.js +++ b/spec/errorSpec.js @@ -38,6 +38,10 @@ describe('things that should fail', function() { expect(true).toBe(false); }); + it('should fail when an error is thrown', function() { + throw new Error('I am an intentional error'); + }); + it('should compare a promise to a primitive', function() { expect(fakeDriver.getValueA()).toEqual('d'); expect(fakeDriver.getValueB()).toEqual('e'); From 8f8b8b39e779559fd3b29b138d7577658b8a64b7 Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Mon, 13 Apr 2015 17:55:07 -0700 Subject: [PATCH 06/55] tests(context): test that the `this` variable points to the right thing Note: this means that using `this.addMatchers` no longer works inside before blocks or specs. It should have been changed to `jamsine.addMatchers` since the upgrade to Jasmine 2. It was still working by accident up until the previous commit. --- spec/adapterSpec.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/spec/adapterSpec.js b/spec/adapterSpec.js index 852bd2a..ffaed21 100644 --- a/spec/adapterSpec.js +++ b/spec/adapterSpec.js @@ -16,6 +16,21 @@ describe('webdriverJS Jasmine adapter plain', function() { }); }); +describe('context', function() { + beforeEach(function() { + this.foo = 0; + }); + + it('can use the `this` to share state', function() { + expect(this.foo).toEqual(0); + this.bar = 'test pollution?'; + }); + + it('prevents test pollution by having an empty `this` created for the next spec', function() { + expect(this.foo).toEqual(0); + expect(this.bar).toBe(undefined); + }); +}); describe('webdriverJS Jasmine adapter', function() { // Shorten this and you should see tests timing out. From e758e2f24e8635d972abe9d0809329f84020bea8 Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Mon, 13 Apr 2015 18:12:44 -0700 Subject: [PATCH 07/55] chore(release): version bump for 0.0.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f814612..0c3fbce 100644 --- a/package.json +++ b/package.json @@ -26,5 +26,5 @@ "test": "scripts/test.sh" }, "license": "MIT", - "version": "0.0.3" + "version": "0.0.4" } From 6eb4301aa1cc163a2c57fb73aec931e1bf5c2d2a Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Tue, 14 Apr 2015 14:10:12 -0700 Subject: [PATCH 08/55] tests(beforeall): add tests to verify beforeAll working with async --- scripts/test.sh | 2 +- spec/adapterSpec.js | 40 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/scripts/test.sh b/scripts/test.sh index 31be5d2..119e1a2 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -9,7 +9,7 @@ $CMD echo EXPECTED_RESULTS="14 specs, 13 failures" -echo "### running failing specs (expecting $EXPECTED_RESULTS)" +echo "### running failing specs (expecting $EXPECTED_RESULTS)" CMD=$CMD_BASE$FAILING_SPECS res=`$CMD 2>/dev/null` results_line=`echo "$res" | tail -2 | head -1` diff --git a/spec/adapterSpec.js b/spec/adapterSpec.js index ffaed21..98b2878 100644 --- a/spec/adapterSpec.js +++ b/spec/adapterSpec.js @@ -35,6 +35,7 @@ describe('context', function() { describe('webdriverJS Jasmine adapter', function() { // Shorten this and you should see tests timing out. jasmine.DEFAULT_TIMEOUT_INTERVAL = 2000; + var beforeEachMsg; beforeEach(function() { jasmine.addMatchers(common.getMatchers()); @@ -42,10 +43,14 @@ describe('webdriverJS Jasmine adapter', function() { beforeEach(function() { fakeDriver.setUp().then(function(value) { - // console.log('This should print before each test: ' + value); + beforeEachMsg = value; }); }); + afterEach(function() { + beforeEachMsg = ''; + }); + it('should pass normal synchronous tests', function() { expect(true).toEqual(true); }); @@ -55,6 +60,10 @@ describe('webdriverJS Jasmine adapter', function() { expect(fakeDriver.getValueB()).toEqual('b'); }); + it('beforeEach should wait for control flow', function() { + expect(beforeEachMsg).toEqual('setup done'); + }); + it('should wait till the expect to run the flow', function() { var promiseA = fakeDriver.getValueA(); expect(promiseA.isPending()).toBe(true); @@ -149,4 +158,33 @@ describe('webdriverJS Jasmine adapter', function() { }, 500); }); }); + + describe('beforeAll and afterAll', function() { + var asyncValue, setupMsg; + + beforeAll(function(done) { + setTimeout(function() { + asyncValue = 5; + done(); + }, 500); + }); + + beforeAll(function() { + fakeDriver.setUp().then(function(msg) { + setupMsg = msg; + }); + }); + + afterAll(function() { + setupMsg = ''; + }); + + it('should have set asyncValue', function() { + expect(asyncValue).toEqual(5); + }); + + it('should wait for control flow', function() { + expect(setupMsg).toEqual('setup done'); + }); + }); }); From 037c7de7fea4de068734b6fa250d145800863633 Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Thu, 14 May 2015 11:42:32 -0700 Subject: [PATCH 09/55] chore(dependencies): update Jasmine to 2.3.1 --- index.js | 5 +++-- package.json | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 74a6949..4e11f7c 100644 --- a/index.js +++ b/index.js @@ -86,7 +86,8 @@ function wrapInControlFlow(globalFn, fnName) { // ignored. var proxyDone = fulfill; proxyDone.fail = function(err) { - reject(err); + var wrappedErr = new Error(err); + reject(wrappedErr); }; testFn(proxyDone); } else { @@ -96,7 +97,7 @@ function wrapInControlFlow(globalFn, fnName) { } }, flow); }, 'Run ' + fnName + description + ' in control flow').then(seal(done), function(err) { - err.stack = err.stack + '\n==== async task ====\n' + driverError.stack; + err.stack = err.stack + '\nFrom asynchronous test: \n' + driverError.stack; done.fail(err); }); }; diff --git a/package.json b/package.json index 0c3fbce..94c4ee2 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "author": "Julie Ralph ", "devDependencies": { "jshint": "2.5.0", - "jasmine": "2.1.1", + "jasmine": "2.3.1", "selenium-webdriver": "2.45.1" }, "repository": { From 521939b83e8d96ea4cb5d639d6c508e031fcd20c Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Fri, 15 May 2015 10:54:22 -0700 Subject: [PATCH 10/55] chore(release): changelog and version bump for 0.0.5 --- CHANGELOG.md | 53 +++++++++++++++++++++++++--------------------------- package.json | 2 +- 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a27c807..bc3a3ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,42 +1,39 @@ -# 1.1.0 +# Changelog for jasminewd2 -## Dependency Updates -- ([daa67d6])(https://github.com/angular/jasminewd/commit/daa67d6eabdd9c70306748da8a0dc0a6f2edb90f)) chore(dependencies): update to selenium-webdriver 2.43.4 +# 0.0.5 -# 1.0.4 -## Bug Fixes -- ([a088e6f](https://github.com/angular/jasminewd/commit/a088e6f175ca817f59d5eea99549e45ab5861ce0)) fix(timeouts): should call special timeout handlers only for a jasmine timeout +- ([037c7de](https://github.com/angular/jasminewd/commit/037c7de7fea4de068734b6fa250d145800863633)) + chore(dependencies): update Jasmine to 2.3.1 - Previously, it used to call the resets if anything matched 'timeout'. This was too - vague, since many error messages contain that string. +# 0.0.4 - Closes #8 +- ([8f8b8b3](https://github.com/angular/jasminewd/commit/8f8b8b39e779559fd3b29b138d7577658b8a64b7)) + tests(context): test that the `this` variable points to the right thing -# 1.0.3 -## Bug Fixes -- ([00821b3](https://github.com/angular/jasminewd/commit/00821b3180a6674012fdccab106835f5ce94bb3f)) fix(timeout): better messaging if the control flow does not have a listed last task + Note: this means that using `this.addMatchers` no longer works inside before blocks or specs. It + should have been changed to `jamsine.addMatchers` since the upgrade to Jasmine 2. It was still + working by accident up until the previous commit. -# 1.0.2 +- ([c0f13d2](https://github.com/angular/jasminewd/commit/c0f13d254966c859db22d020a5390138dbf48e64)) + refactor(asyncTestFn): refactor async test wrapping to show more info -## Bug Fixes -- ([30b6811](https://github.com/angular/jasminewd/commit/30b68113759a7cb5c8dabc5b16ffcd89516882d8)) fix(timeout): output more information about the current task when a timeout occurs + Test wrapping for Jasmine 2 now more closely follows the test wrapping for Mocha at + https://github.com/SeleniumHQ/selenium/blob/master/javascript/node/selenium-webdriver/testing/index.js -# 1.0.1 + This also adds more information to the task names in the control flow, for easier debugging. -## Bug Fixes -- ([c507b37](https://github.com/angular/jasminewd/commit/c507b37dd04cf267a437a579fc3b14063abb2ef8)) - fix(index): stop infinite promise resolution +# 0.0.3 -1.0.0 -===== +- ([161e1fa](https://github.com/angular/jasminewd/commit/161e1fa48deaa5ea0f485027ea8ae41562864936)) + fix(errors): update webdriverjs, fix asynchronous error output -Support for Jasmine 1.3.1. Tested against minijasminenode @ 0.4.0. + Add some console logging, remove useless info about the last running task in the control flow, and + fix error where problems reported from done.fail were getting pushed into the following spec. -Features + Closes #18 - - Automatically makes tests asynchronously wait until the WebDriverJS control flow is empty. - - - If a `done` function is passed to the test, waits for both the control flow and until done is called. - - - Enhances `expect` so that it automatically unwraps promises before performing the assertion. +- ([fdb03a3](https://github.com/angular/jasminewd/commit/fdb03a388d4846952c09fb0ad75a37b46674c750)) + docs(readme): add note about jasmine 1 vs jasmine 2 +- ([acaec8b](https://github.com/angular/jasminewd/commit/acaec8bdd157e9933d608c66204a52335fb46ee4)) + feat(index): add jasmine2.0 support diff --git a/package.json b/package.json index 94c4ee2..3e9181d 100644 --- a/package.json +++ b/package.json @@ -26,5 +26,5 @@ "test": "scripts/test.sh" }, "license": "MIT", - "version": "0.0.4" + "version": "0.0.5" } From 4776c16b9a9f3a9a3de8a8dddc0e051cb32331b4 Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Tue, 29 Sep 2015 12:53:08 -0700 Subject: [PATCH 11/55] chore(selenium-webdriver): update selenium webdriver to 2.47.0 Update selenium-webdriver to 2.47.0 from 2.45.1. This update introduces a convoluted situation where some tests in Proractor's suite would hang - see https://github.com/angular/protractor/issues/2245 This change includes a fix for those issues which removes the explicit `flow.execute` wrapper around `expect` calls. This appears not to introduce any issues to existing tests. --- index.js | 10 ++++------ package.json | 4 ++-- spec/adapterSpec.js | 28 ++++++++++++++++++++++++++++ spec/common.js | 21 +++++++++++++++++++++ 4 files changed, 55 insertions(+), 8 deletions(-) diff --git a/index.js b/index.js index 4e11f7c..3572b53 100644 --- a/index.js +++ b/index.js @@ -162,13 +162,11 @@ jasmine.Expectation.prototype.wrapCompare = function(name, matcherFactory) { matchError.stack = matchError.stack.replace(/ +at.+jasminewd.+\n/, ''); - flow.execute(function() { - return webdriver.promise.when(expectation.actual).then(function(actual) { - return webdriver.promise.all(expected).then(function(expected) { - return compare(actual, expected); - }); + webdriver.promise.when(expectation.actual).then(function(actual) { + return webdriver.promise.all(expected).then(function(expected) { + return compare(actual, expected); }); - }, 'Expect ' + name); + }); function compare(actual, expected) { var args = expected.slice(0); diff --git a/package.json b/package.json index 3e9181d..a18d85a 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,8 @@ "author": "Julie Ralph ", "devDependencies": { "jshint": "2.5.0", - "jasmine": "2.3.1", - "selenium-webdriver": "2.45.1" + "jasmine": "2.3.2", + "selenium-webdriver": "2.47.0" }, "repository": { "type": "git", diff --git a/spec/adapterSpec.js b/spec/adapterSpec.js index 98b2878..a1eb3f7 100644 --- a/spec/adapterSpec.js +++ b/spec/adapterSpec.js @@ -109,6 +109,34 @@ describe('webdriverJS Jasmine adapter', function() { expect(fakeDriver.getDecimalNumber()).toBeCloseTo(3.14); }); + it('should allow iterating through arrays', function() { + // This is a convoluted test which shows a real issue which + // cropped up in version changes to the selenium-webdriver module. + // See https://github.com/angular/protractor/pull/2263 + var checkTexts = function(webElems) { + var texts = webElems.then(function(arr) { + var results = arr.map(function(webElem) { + return webElem.getText(); + }); + return webdriver.promise.all(results); + }); + + expect(texts).not.toContain('e'); + + return true; + }; + + fakeDriver.getValueList().then(function(list) { + var result = list.map(function(webElem) { + var webElemsPromise = webdriver.promise.fulfilled(webElem).then(function(webElem) { + return [webElem]; + }); + return webdriver.promise.fullyResolved(checkTexts(webElemsPromise)); + }); + return webdriver.promise.all(result); + }); + }); + describe('not', function() { it('should still pass normal synchronous tests', function() { expect(4).not.toEqual(5); diff --git a/spec/common.js b/spec/common.js index de956e4..df75e6b 100644 --- a/spec/common.js +++ b/spec/common.js @@ -64,6 +64,27 @@ exports.getFakeDriver = function() { } }); }, 'getHiddenElement'); + }, + getValueList: function() { + return flow.execute(function() { + return webdriver.promise.fulfilled([{ + getText: function() { + return flow.execute(function() { return webdriver.promise.fulfilled('a');}); + } + }, { + getText: function() { + return flow.execute(function() { return webdriver.promise.fulfilled('b');}); + } + }, { + getText: function() { + return flow.execute(function() { return webdriver.promise.fulfilled('c');}); + } + }, { + getText: function() { + return flow.execute(function() { return webdriver.promise.fulfilled('d');}); + } + }]); + }, 'getValueList'); } }; }; From 7a912951b4470b50725f28aed7f535e893bb4347 Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Tue, 29 Sep 2015 13:05:07 -0700 Subject: [PATCH 12/55] chore(ci): Use container based travis and test against node 4 --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 163b7f6..6c690ea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,8 @@ +sudo: false language: node_js node_js: - - "0.10" + - "0.12" + - "4" script: - npm test From 764b4329aa3cbcafdbcab6799105cabb07808bd5 Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Tue, 29 Sep 2015 13:11:51 -0700 Subject: [PATCH 13/55] chore(release): version bump and changelog for 0.0.6 --- CHANGELOG.md | 13 +++++++++++++ package.json | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc3a3ff..cb64151 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog for jasminewd2 +# 0.0.6 + +- ([4776c16](https://github.com/angular/jasminewd/commit/4776c16b9a9f3a9a3de8a8dddc0e051cb32331b4)) + chore(selenium-webdriver): update selenium webdriver to 2.47.0 + + Update selenium-webdriver to 2.47.0 from 2.45.1. This update introduces a convoluted situation + where some tests in Proractor's suite would hang - see + https://github.com/angular/protractor/issues/2245 + + This change includes a fix for those issues which removes the explicit + `flow.execute` wrapper around `expect` calls. This appears not to introduce any issues to existing + tests. + # 0.0.5 - ([037c7de](https://github.com/angular/jasminewd/commit/037c7de7fea4de068734b6fa250d145800863633)) diff --git a/package.json b/package.json index a18d85a..c491ec1 100644 --- a/package.json +++ b/package.json @@ -26,5 +26,5 @@ "test": "scripts/test.sh" }, "license": "MIT", - "version": "0.0.5" + "version": "0.0.6" } From f4c30a0023c6ec33b15df762226c3fe8e741d26e Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Fri, 2 Oct 2015 14:51:34 -0700 Subject: [PATCH 14/55] fix: allow empty it functions --- index.js | 4 ++++ spec/adapterSpec.js | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/index.js b/index.js index 3572b53..714c763 100644 --- a/index.js +++ b/index.js @@ -108,6 +108,10 @@ function wrapInControlFlow(globalFn, fnName) { case 'it': case 'fit': description = validateString(arguments[0]); + if (!arguments[1]) { + globalFn(description); + break; + } func = validateFunction(arguments[1]); if (!arguments[2]) { globalFn(description, asyncTestFn(func, description)); diff --git a/spec/adapterSpec.js b/spec/adapterSpec.js index a1eb3f7..fc1ae02 100644 --- a/spec/adapterSpec.js +++ b/spec/adapterSpec.js @@ -14,6 +14,12 @@ describe('webdriverJS Jasmine adapter plain', function() { it('should pass normal synchronous tests', function() { expect(true).toBe(true); }); + + it('should allow an empty it block and mark as pending'); + + xit('should allow a spec marked as pending with xit', function() { + expect(true).toBe(false); + }); }); describe('context', function() { From 55fd11e69c2f1ba8fba9a19a8acccbe933896084 Mon Sep 17 00:00:00 2001 From: Shahar Talmi Date: Fri, 8 Jan 2016 15:48:38 +0200 Subject: [PATCH 15/55] fix(index): forward it's return value --- index.js | 7 +++---- spec/adapterSpec.js | 12 ++++++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 714c763..93cb2c3 100644 --- a/index.js +++ b/index.js @@ -109,15 +109,14 @@ function wrapInControlFlow(globalFn, fnName) { case 'fit': description = validateString(arguments[0]); if (!arguments[1]) { - globalFn(description); - break; + return globalFn(description); } func = validateFunction(arguments[1]); if (!arguments[2]) { - globalFn(description, asyncTestFn(func, description)); + return globalFn(description, asyncTestFn(func, description)); } else { timeout = validateNumber(arguments[2]); - globalFn(description, asyncTestFn(func, description), timeout); + return globalFn(description, asyncTestFn(func, description), timeout); } break; case 'beforeEach': diff --git a/spec/adapterSpec.js b/spec/adapterSpec.js index fc1ae02..3c85c24 100644 --- a/spec/adapterSpec.js +++ b/spec/adapterSpec.js @@ -221,4 +221,16 @@ describe('webdriverJS Jasmine adapter', function() { expect(setupMsg).toEqual('setup done'); }); }); + + describe('it return value', function() { + var spec1 = it('test1'); + var spec2 = it('test2', function() {}); + var spec3 = it('test3', function() {}, 1); + + it('should return the spec', function() { + expect(spec1.description).toBe('test1'); + expect(spec2.description).toBe('test2'); + expect(spec3.description).toBe('test3'); + }); + }); }); From 9c2083e6b18862b9f825bbbb85e04e045137c070 Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Mon, 8 Feb 2016 13:46:36 -0800 Subject: [PATCH 16/55] chore(release): version bump and changelog for 0.0.7 --- CHANGELOG.md | 8 ++++++++ package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb64151..a345207 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog for jasminewd2 +# 0.0.7 + +- ([55fd11e](https://github.com/angular/protractor/commit/55fd11e69c2f1ba8fba9a19a8acccbe933896084)) + fix(index): forward it's return value + +- ([f4c30a0](https://github.com/angular/protractor/commit/f4c30a0023c6ec33b15df762226c3fe8e741d26e)) + fix: allow empty it functions + # 0.0.6 - ([4776c16](https://github.com/angular/jasminewd/commit/4776c16b9a9f3a9a3de8a8dddc0e051cb32331b4)) diff --git a/package.json b/package.json index c491ec1..f9a30a3 100644 --- a/package.json +++ b/package.json @@ -26,5 +26,5 @@ "test": "scripts/test.sh" }, "license": "MIT", - "version": "0.0.6" + "version": "0.0.7" } From 550dd96e3833729e8678e54b31f40827019700d6 Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Mon, 8 Feb 2016 13:51:02 -0800 Subject: [PATCH 17/55] chore(test): print command line --- scripts/test.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/test.sh b/scripts/test.sh index 119e1a2..160fad8 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -4,6 +4,7 @@ CMD_BASE="node node_modules/.bin/jasmine JASMINE_CONFIG_PATH=" echo "### running passing specs" CMD=$CMD_BASE$PASSING_SPECS +echo "### $CMD" $CMD [ "$?" -eq 0 ] || exit 1 echo @@ -11,6 +12,7 @@ echo EXPECTED_RESULTS="14 specs, 13 failures" echo "### running failing specs (expecting $EXPECTED_RESULTS)" CMD=$CMD_BASE$FAILING_SPECS +echo "### $CMD" res=`$CMD 2>/dev/null` results_line=`echo "$res" | tail -2 | head -1` echo "result: $results_line" From 38748afbe91e5550a1e9d72b2cf192819468a1ba Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Mon, 8 Feb 2016 14:01:03 -0800 Subject: [PATCH 18/55] chore(test): add a test to make sure that asynchronous failures fail See https://github.com/angular/protractor/issues/2894 --- scripts/test.sh | 2 +- spec/errorSpec.js | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/test.sh b/scripts/test.sh index 160fad8..bda5dcc 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -9,7 +9,7 @@ $CMD [ "$?" -eq 0 ] || exit 1 echo -EXPECTED_RESULTS="14 specs, 13 failures" +EXPECTED_RESULTS="15 specs, 14 failures" echo "### running failing specs (expecting $EXPECTED_RESULTS)" CMD=$CMD_BASE$FAILING_SPECS echo "### $CMD" diff --git a/spec/errorSpec.js b/spec/errorSpec.js index 811548a..6b10b8a 100644 --- a/spec/errorSpec.js +++ b/spec/errorSpec.js @@ -34,6 +34,13 @@ describe('things that should fail', function() { done.fail('an error from done.fail'); }); + it('should error asynchronously within done callback', function(done) { + setTimeout(function() { + expect(false).toBeTruthy(); + done(); + }, 200); + }); + it('should fail normal synchronous tests', function() { expect(true).toBe(false); }); From 4717c0b496a5191a97ae54fafb2c0c2bd2d00a15 Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Mon, 8 Feb 2016 14:15:50 -0800 Subject: [PATCH 19/55] chore: npmignore scripts --- .npmignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.npmignore b/.npmignore index 24efbee..9af0ef0 100644 --- a/.npmignore +++ b/.npmignore @@ -1,4 +1,5 @@ node_modules/ spec/ +scripts/ .travis.yml .npmignore From 750898c90a1cc1bef09384b60ef6e15adfe734f7 Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Mon, 8 Feb 2016 23:25:14 -0800 Subject: [PATCH 20/55] fix(expectation): expectations without promises no longer add to task queue Instead, expectations without promises in either expected or actual are unchanged from the original Jasmine implementation. See https://github.com/angular/protractor/issues/2894 --- index.js | 15 +++++++++++---- package.json | 4 ++-- scripts/test.sh | 2 +- spec/errorSpec.js | 8 +++++++- 4 files changed, 21 insertions(+), 8 deletions(-) diff --git a/index.js b/index.js index 93cb2c3..e37287a 100644 --- a/index.js +++ b/index.js @@ -153,6 +153,8 @@ global.expect = function(actual) { return originalExpect(actual); }; +var originalWrapCompare = jasmine.Expectation.prototype.wrapCompare; + /** * Creates a matcher wrapper that resolves any promises given for actual and * expected values, as well as the `pass` property of the result. @@ -165,11 +167,16 @@ jasmine.Expectation.prototype.wrapCompare = function(name, matcherFactory) { matchError.stack = matchError.stack.replace(/ +at.+jasminewd.+\n/, ''); - webdriver.promise.when(expectation.actual).then(function(actual) { - return webdriver.promise.all(expected).then(function(expected) { - return compare(actual, expected); + if (!webdriver.promise.isPromise(expectation.actual) && + !webdriver.promise.isPromise(expected)) { + originalWrapCompare(name, matcherFactory).apply(this, arguments); + } else { + webdriver.promise.when(expectation.actual).then(function(actual) { + return webdriver.promise.all(expected).then(function(expected) { + return compare(actual, expected); + }); }); - }); + } function compare(actual, expected) { var args = expected.slice(0); diff --git a/package.json b/package.json index f9a30a3..715e04d 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,8 @@ "author": "Julie Ralph ", "devDependencies": { "jshint": "2.5.0", - "jasmine": "2.3.2", - "selenium-webdriver": "2.47.0" + "jasmine": "2.4.1", + "selenium-webdriver": "2.48.2" }, "repository": { "type": "git", diff --git a/scripts/test.sh b/scripts/test.sh index bda5dcc..20553d8 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -9,7 +9,7 @@ $CMD [ "$?" -eq 0 ] || exit 1 echo -EXPECTED_RESULTS="15 specs, 14 failures" +EXPECTED_RESULTS="16 specs, 15 failures" echo "### running failing specs (expecting $EXPECTED_RESULTS)" CMD=$CMD_BASE$FAILING_SPECS echo "### $CMD" diff --git a/spec/errorSpec.js b/spec/errorSpec.js index 6b10b8a..4ca3092 100644 --- a/spec/errorSpec.js +++ b/spec/errorSpec.js @@ -34,9 +34,15 @@ describe('things that should fail', function() { done.fail('an error from done.fail'); }); + it('should error asynchronously in promise callbacks', function() { + fakeDriver.sleep(50).then(function() { + expect(true).toEqual(false); + }); + }); + it('should error asynchronously within done callback', function(done) { setTimeout(function() { - expect(false).toBeTruthy(); + expect(false).toEqual(true); done(); }, 200); }); From 5abc7457cd73a4a4ba70b3c9ceeadac6d42bbd76 Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Mon, 8 Feb 2016 23:39:21 -0800 Subject: [PATCH 21/55] chore(jasmine): update MatchFactory to allow message as function --- index.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index e37287a..d22198a 100644 --- a/index.js +++ b/index.js @@ -192,7 +192,7 @@ jasmine.Expectation.prototype.wrapCompare = function(name, matcherFactory) { var result = matcherCompare.apply(null, args); return webdriver.promise.when(result.pass).then(function(pass) { - var message = ""; + var message = ''; if (!pass) { if (!result.message) { @@ -200,7 +200,11 @@ jasmine.Expectation.prototype.wrapCompare = function(name, matcherFactory) { args.unshift(name); message = expectation.util.buildFailureMessage.apply(null, args); } else { - message = result.message; + if (Object.prototype.toString.apply(result.message) === '[object Function]') { + message = result.message(); + } else { + message = result.message; + } } } From 6bb173bd5ffe4feb082d8c36acc8be737d125f22 Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Mon, 8 Feb 2016 23:41:35 -0800 Subject: [PATCH 22/55] chore(release): changelog and version bump for 0.0.8 --- CHANGELOG.md | 13 +++++++++++++ package.json | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a345207..ff14811 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog for jasminewd2 +# 0.0.8 + +- ([5abc745](https://github.com/angular/protractor/commit/5abc7457cd73a4a4ba70b3c9ceeadac6d42bbd76)) + chore(jasmine): update MatchFactory to allow message as function + +- ([750898c](https://github.com/angular/protractor/commit/750898c90a1cc1bef09384b60ef6e15adfe734f7)) + fix(expectation): expectations without promises no longer add to task queue + + Instead, expectations without promises in either expected or actual are unchanged from the + original Jasmine implementation. + + See https://github.com/angular/protractor/issues/2894 + # 0.0.7 - ([55fd11e](https://github.com/angular/protractor/commit/55fd11e69c2f1ba8fba9a19a8acccbe933896084)) diff --git a/package.json b/package.json index 715e04d..961ecd1 100644 --- a/package.json +++ b/package.json @@ -26,5 +26,5 @@ "test": "scripts/test.sh" }, "license": "MIT", - "version": "0.0.7" + "version": "0.0.8" } From b48551b5cf980fe396bc0fe764e72a8ca47a1250 Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Mon, 8 Feb 2016 23:53:05 -0800 Subject: [PATCH 23/55] chore(travis): don't test on node 0.12 Latest selenium-webdriver no longer supports node 0.12. Stop testing on it. --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6c690ea..2fce81e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ sudo: false language: node_js node_js: - - "0.12" - "4" script: From 790c81eb0aba880fffbdcb4e834eb2161141620c Mon Sep 17 00:00:00 2001 From: Sjur Bakka Date: Fri, 26 Feb 2016 16:40:31 +0100 Subject: [PATCH 24/55] fix(expectations): allow custom matchers to return a promise when actual is not a promise See angular/protractor#2964 --- index.js | 64 ++++++++++++++++++++++++--------------------- spec/adapterSpec.js | 5 ++++ spec/common.js | 10 +++++++ 3 files changed, 49 insertions(+), 30 deletions(-) diff --git a/index.js b/index.js index d22198a..d88d3ad 100644 --- a/index.js +++ b/index.js @@ -153,8 +153,6 @@ global.expect = function(actual) { return originalExpect(actual); }; -var originalWrapCompare = jasmine.Expectation.prototype.wrapCompare; - /** * Creates a matcher wrapper that resolves any promises given for actual and * expected values, as well as the `pass` property of the result. @@ -169,7 +167,7 @@ jasmine.Expectation.prototype.wrapCompare = function(name, matcherFactory) { if (!webdriver.promise.isPromise(expectation.actual) && !webdriver.promise.isPromise(expected)) { - originalWrapCompare(name, matcherFactory).apply(this, arguments); + compare(expectation.actual, expected); } else { webdriver.promise.when(expectation.actual).then(function(actual) { return webdriver.promise.all(expected).then(function(expected) { @@ -191,36 +189,42 @@ jasmine.Expectation.prototype.wrapCompare = function(name, matcherFactory) { var result = matcherCompare.apply(null, args); - return webdriver.promise.when(result.pass).then(function(pass) { - var message = ''; + if (webdriver.promise.isPromise(result.pass)) { + return webdriver.promise.when(result.pass).then(compareDone); + } else { + return compareDone(result.pass); + } - if (!pass) { - if (!result.message) { - args.unshift(expectation.isNot); - args.unshift(name); - message = expectation.util.buildFailureMessage.apply(null, args); - } else { - if (Object.prototype.toString.apply(result.message) === '[object Function]') { - message = result.message(); - } else { - message = result.message; - } - } - } + function compareDone(pass) { + var message = ''; - if (expected.length == 1) { - expected = expected[0]; + if (!pass) { + if (!result.message) { + args.unshift(expectation.isNot); + args.unshift(name); + message = expectation.util.buildFailureMessage.apply(null, args); + } else { + if (Object.prototype.toString.apply(result.message) === '[object Function]') { + message = result.message(); + } else { + message = result.message; + } } - var res = { - matcherName: name, - passed: pass, - message: message, - actual: actual, - expected: expected, - error: matchError - }; - expectation.addExpectationResult(pass, res); - }); + } + + if (expected.length == 1) { + expected = expected[0]; + } + var res = { + matcherName: name, + passed: pass, + message: message, + actual: actual, + expected: expected, + error: matchError + }; + expectation.addExpectationResult(pass, res); + } function defaultNegativeCompare() { var result = matcher.compare.apply(null, args); diff --git a/spec/adapterSpec.js b/spec/adapterSpec.js index 3c85c24..065ab46 100644 --- a/spec/adapterSpec.js +++ b/spec/adapterSpec.js @@ -155,6 +155,11 @@ describe('webdriverJS Jasmine adapter', function() { it('should compare a promise to a promise', function() { expect(fakeDriver.getValueA()).not.toEqual(fakeDriver.getValueB()); }); + + it('should allow custom matchers to return a promise when actual is not a promise', function() { + expect(fakeDriver.displayedElement).toBeDisplayed(); + expect(fakeDriver.hiddenElement).not.toBeDisplayed(); + }); }); it('should throw an error with a WebElement actual value', function() { diff --git a/spec/common.js b/spec/common.js index df75e6b..da64f33 100644 --- a/spec/common.js +++ b/spec/common.js @@ -85,6 +85,16 @@ exports.getFakeDriver = function() { } }]); }, 'getValueList'); + }, + displayedElement: { + isDisplayed: function() { + return webdriver.promise.fulfilled(true); + } + }, + hiddenElement: { + isDisplayed: function() { + return webdriver.promise.fulfilled(false); + } } }; }; From b0577ddd7ea2538ab3ac10022d29bbc117c84e18 Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Sun, 24 Apr 2016 20:18:12 -0700 Subject: [PATCH 25/55] chore(release): changelog and version bump for 0.0.9 --- CHANGELOG.md | 8 ++++++++ package.json | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff14811..9773c07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog for jasminewd2 +# 0.0.9 + +- ([790c81e](https://github.com/angular/protractor/commit/790c81eb0aba880fffbdcb4e834eb2161141620c)) + fix(expectations): allow custom matchers to return a promise when actual is not a promise + + See angular/protractor#2964 + + # 0.0.8 - ([5abc745](https://github.com/angular/protractor/commit/5abc7457cd73a4a4ba70b3c9ceeadac6d42bbd76)) diff --git a/package.json b/package.json index 961ecd1..66001f7 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "devDependencies": { "jshint": "2.5.0", "jasmine": "2.4.1", - "selenium-webdriver": "2.48.2" + "selenium-webdriver": "2.52.0" }, "repository": { "type": "git", @@ -26,5 +26,5 @@ "test": "scripts/test.sh" }, "license": "MIT", - "version": "0.0.8" + "version": "0.0.9" } From db26b1a1e66477a6f526dac56ecaaa50d2cf4700 Mon Sep 17 00:00:00 2001 From: Sammy Jelin Date: Mon, 27 Jun 2016 16:14:52 -0700 Subject: [PATCH 26/55] fix(stacktrace): do not crash if beforeEach block is rejected without any stated reason (#45) Closes #44 --- index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/index.js b/index.js index d88d3ad..d29ce88 100644 --- a/index.js +++ b/index.js @@ -97,6 +97,10 @@ function wrapInControlFlow(globalFn, fnName) { } }, flow); }, 'Run ' + fnName + description + ' in control flow').then(seal(done), function(err) { + if (!err) { + err = new Error('Unknown Error'); + err.stack = ''; + } err.stack = err.stack + '\nFrom asynchronous test: \n' + driverError.stack; done.fail(err); }); From a8ca7e325be67a9228addadb24495585836390c3 Mon Sep 17 00:00:00 2001 From: Sammy Jelin Date: Mon, 27 Jun 2016 16:15:06 -0700 Subject: [PATCH 27/55] chore(tests): test for async support (#49) --- .gitignore | 1 + package.json | 7 ++++--- spec/asyncAwaitSpec.ts | 16 ++++++++++++++++ spec/support/passing_specs.json | 3 ++- 4 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 spec/asyncAwaitSpec.ts diff --git a/.gitignore b/.gitignore index 3351ce2..77dfc75 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.log node_modules +spec/asyncAwaitSpec.js diff --git a/package.json b/package.json index 66001f7..eb57735 100644 --- a/package.json +++ b/package.json @@ -12,9 +12,10 @@ ], "author": "Julie Ralph ", "devDependencies": { - "jshint": "2.5.0", "jasmine": "2.4.1", - "selenium-webdriver": "2.52.0" + "jshint": "2.5.0", + "selenium-webdriver": "2.52.0", + "typescript": "^1.8.10" }, "repository": { "type": "git", @@ -22,7 +23,7 @@ }, "main": "index.js", "scripts": { - "pretest": "node_modules/.bin/jshint index.js spec", + "pretest": "node_modules/.bin/jshint index.js spec --exclude spec/asyncAwaitSpec.js; tsc -t ES2015 spec/asyncAwaitSpec.ts", "test": "scripts/test.sh" }, "license": "MIT", diff --git a/spec/asyncAwaitSpec.ts b/spec/asyncAwaitSpec.ts new file mode 100644 index 0000000..f8a308b --- /dev/null +++ b/spec/asyncAwaitSpec.ts @@ -0,0 +1,16 @@ +"use strict"; + +declare var describe; +declare var it; +declare var expect; + +async function asyncHelper() { + return 7; +} + +describe('async function', function() { + it('should wait on async functions', async function() { + let helperVal = await asyncHelper(); + expect(helperVal).toBe(7); + }); +}); diff --git a/spec/support/passing_specs.json b/spec/support/passing_specs.json index e5466b6..ced6d64 100644 --- a/spec/support/passing_specs.json +++ b/spec/support/passing_specs.json @@ -1,6 +1,7 @@ { "spec_dir": "spec", "spec_files": [ - "adapterSpec.js" + "adapterSpec.js", + "asyncAwaitSpec.js" ] } From ff2e624159344cd83b04c6a6648334ba12e78ea6 Mon Sep 17 00:00:00 2001 From: Michael Giambalvo Date: Thu, 1 Sep 2016 16:12:40 -0700 Subject: [PATCH 28/55] fix(webdriver): Pass in the control flow. Fixex angular/protractor#3505, which was caused by Protractor and Jasminewd finding different webdriver instances through require(), and thus using different ControlFlows. --- index.js | 53 ++++++++++++++++++++++++++++----------------- spec/adapterSpec.js | 10 ++++++--- spec/common.js | 5 +++-- spec/errorSpec.js | 1 - 4 files changed, 43 insertions(+), 26 deletions(-) diff --git a/index.js b/index.js index d29ce88..3c54ca8 100644 --- a/index.js +++ b/index.js @@ -6,8 +6,6 @@ var webdriver = require('selenium-webdriver'); -var flow = webdriver.promise.controlFlow(); - /** * Wraps a function so that all passed arguments are ignored. * @param {!Function} fn The function to wrap. @@ -67,7 +65,7 @@ function validateString(stringtoValidate) { * @param {!Function} globalFn The function to wrap. * @return {!Function} The new function. */ -function wrapInControlFlow(globalFn, fnName) { +function wrapInControlFlow(flow, globalFn, fnName) { return function() { var driverError = new Error(); driverError.stack = driverError.stack.replace(/ +at.+jasminewd.+\n/, ''); @@ -141,18 +139,41 @@ function wrapInControlFlow(globalFn, fnName) { }; } -global.it = wrapInControlFlow(global.it, 'it'); -global.fit = wrapInControlFlow(global.fit, 'fit'); -global.beforeEach = wrapInControlFlow(global.beforeEach, 'beforeEach'); -global.afterEach = wrapInControlFlow(global.afterEach, 'afterEach'); -global.beforeAll = wrapInControlFlow(global.beforeAll, 'beforeAll'); -global.afterAll = wrapInControlFlow(global.afterAll, 'afterAll'); +/** + * Initialize the JasmineWd adapter with a particlar webdriver instance. We + * pass webdriver here instead of using require() in order to ensure Protractor + * and Jasminews are using the same webdriver instance. + * @param {Object} flow. The ControlFlow to wrap tests in. + */ +function initJasmineWd(flow) { + if (jasmine.JasmineWdInitialized) { + throw Error('JasmineWd already initialized when init() was called'); + } + jasmine.JasmineWdInitialized = true; + + global.it = wrapInControlFlow(flow, global.it, 'it'); + global.fit = wrapInControlFlow(flow, global.fit, 'fit'); + global.beforeEach = wrapInControlFlow(flow, global.beforeEach, 'beforeEach'); + global.afterEach = wrapInControlFlow(flow, global.afterEach, 'afterEach'); + global.beforeAll = wrapInControlFlow(flow, global.beforeAll, 'beforeAll'); + global.afterAll = wrapInControlFlow(flow, global.afterAll, 'afterAll'); + + // On timeout, the flow should be reset. This will prevent webdriver tasks + // from overflowing into the next test and causing it to fail or timeout + // as well. This is done in the reporter instead of an afterEach block + // to ensure that it runs after any afterEach() blocks with webdriver tasks + // get to complete first. + jasmine.getEnv().addReporter(new OnTimeoutReporter(function() { + console.warn('A Jasmine spec timed out. Resetting the WebDriver Control Flow.'); + flow.reset(); + })); +} var originalExpect = global.expect; global.expect = function(actual) { if (actual instanceof webdriver.WebElement) { - throw 'expect called with WebElement argument, expected a Promise. ' + - 'Did you mean to use .getText()?'; + throw Error('expect called with WebElement argument, expected a Promise. ' + + 'Did you mean to use .getText()?'); } return originalExpect(actual); }; @@ -268,12 +289,4 @@ OnTimeoutReporter.prototype.specDone = function(result) { } }; -// On timeout, the flow should be reset. This will prevent webdriver tasks -// from overflowing into the next test and causing it to fail or timeout -// as well. This is done in the reporter instead of an afterEach block -// to ensure that it runs after any afterEach() blocks with webdriver tasks -// get to complete first. -jasmine.getEnv().addReporter(new OnTimeoutReporter(function() { - console.warn('A Jasmine spec timed out. Resetting the WebDriver Control Flow.'); - flow.reset(); -})); +module.exports.init = initJasmineWd; diff --git a/spec/adapterSpec.js b/spec/adapterSpec.js index 065ab46..6fce99e 100644 --- a/spec/adapterSpec.js +++ b/spec/adapterSpec.js @@ -1,6 +1,5 @@ var webdriver = require('selenium-webdriver'); var common = require('./common.js'); -require('../index.js'); /** * Tests for the WebDriverJS Jasmine-Node Adapter. These tests use @@ -57,6 +56,11 @@ describe('webdriverJS Jasmine adapter', function() { beforeEachMsg = ''; }); + it('should only allow initializing once', function() { + expect(require('../index.js').init).toThrow( + Error('JasmineWd already initialized when init() was called')); + }); + it('should pass normal synchronous tests', function() { expect(true).toEqual(true); }); @@ -167,8 +171,8 @@ describe('webdriverJS Jasmine adapter', function() { expect(function() { expect(webElement).toEqual(4); - }).toThrow('expect called with WebElement argument, expected a Promise. ' + - 'Did you mean to use .getText()?'); + }).toThrow(Error('expect called with WebElement argument, expected a Promise. ' + + 'Did you mean to use .getText()?')); }); it('should pass after the timed out tests', function() { diff --git a/spec/common.js b/spec/common.js index da64f33..efbd48f 100644 --- a/spec/common.js +++ b/spec/common.js @@ -1,8 +1,9 @@ -require('../index.js'); var webdriver = require('selenium-webdriver'); +var flow = webdriver.promise.controlFlow(); +require('../index.js').init(flow); + exports.getFakeDriver = function() { - var flow = webdriver.promise.controlFlow(); return { controlFlow: function() { return flow; diff --git a/spec/errorSpec.js b/spec/errorSpec.js index 4ca3092..e508891 100644 --- a/spec/errorSpec.js +++ b/spec/errorSpec.js @@ -1,6 +1,5 @@ var webdriver = require('selenium-webdriver'); var common = require('./common.js'); -require('../index.js'); /** * Error tests for the WebDriverJS Jasmine-Node Adapter. These tests use From 4408ae4b77817520c7060b95cad6bb555ab20e32 Mon Sep 17 00:00:00 2001 From: mgiambalvo Date: Tue, 6 Sep 2016 17:11:05 -0700 Subject: [PATCH 29/55] chore(release): changelog and version bump for 0.0.10 (#53) --- CHANGELOG.md | 15 +++++++++++++++ package.json | 6 +++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9773c07..01cf07c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog for jasminewd2 +# 0.0.10 + +- ([ff2e624](https://github.com/angular/jasminewd/commit/ff2e624159344cd83b04c6a6648334ba12e78ea6)) + fix(webdriver): Pass in the control flow. + + BREAKING CHANGE: The control flow now needs to be passed in when using jasminewd. This fixes + an issue where having multiple versions of selenium-webdriver in a package's dependency tree would + result in jasminewd and protractor using different control flows. You now have to initialize + jasminewd before you can use it, like so: `require('jasminewd2').init(webdriver.promise.controlFlow());` + + See https://github.com/angular/protractor/issues/3505 + +- ([db26b1a](https://github.com/angular/jasminewd/commit/db26b1a1e66477a6f526dac56ecaaa50d2cf4700)) + fix(stacktrace): do not crash if beforeEach block is rejected without any stated reason (#45) + # 0.0.9 - ([790c81e](https://github.com/angular/protractor/commit/790c81eb0aba880fffbdcb4e834eb2161141620c)) diff --git a/package.json b/package.json index eb57735..4997a3e 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,8 @@ "devDependencies": { "jasmine": "2.4.1", "jshint": "2.5.0", - "selenium-webdriver": "2.52.0", - "typescript": "^1.8.10" + "selenium-webdriver": "2.53.3", + "typescript": "^2.0.0" }, "repository": { "type": "git", @@ -27,5 +27,5 @@ "test": "scripts/test.sh" }, "license": "MIT", - "version": "0.0.9" + "version": "0.0.10" } From 88703656b4f8a012a084ba184a4fe473f423a200 Mon Sep 17 00:00:00 2001 From: Craig Date: Thu, 27 Oct 2016 16:25:43 -0700 Subject: [PATCH 30/55] deps(selenium-webdriver): upgrade to 3.0.0-beta-3 (#57) --- .jshintignore | 1 + package.json | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 .jshintignore diff --git a/.jshintignore b/.jshintignore new file mode 100644 index 0000000..8b7e536 --- /dev/null +++ b/.jshintignore @@ -0,0 +1 @@ +spec/asyncAwaitSpec.js diff --git a/package.json b/package.json index 4997a3e..d0d3ca0 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "devDependencies": { "jasmine": "2.4.1", "jshint": "2.5.0", - "selenium-webdriver": "2.53.3", + "selenium-webdriver": "^3.0.0-beta-3", "typescript": "^2.0.0" }, "repository": { @@ -23,7 +23,9 @@ }, "main": "index.js", "scripts": { - "pretest": "node_modules/.bin/jshint index.js spec --exclude spec/asyncAwaitSpec.js; tsc -t ES2015 spec/asyncAwaitSpec.ts", + "jshint": "jshint index.js spec", + "tsc": "tsc -t ES2015 spec/asyncAwaitSpec.ts", + "pretest": "npm run jshint && npm run tsc", "test": "scripts/test.sh" }, "license": "MIT", From 70c9f62af50018bea6ad326e12bacd9ca03e6ae5 Mon Sep 17 00:00:00 2001 From: Craig Date: Thu, 27 Oct 2016 18:30:26 -0700 Subject: [PATCH 31/55] upgrade(isPromise): expose the deferred object's promise (#58) - isPromise checks to see if the input parameter has a then method - Deferred class has a promise property and no longer has a then method --- index.js | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index 3c54ca8..0172445 100644 --- a/index.js +++ b/index.js @@ -190,12 +190,28 @@ jasmine.Expectation.prototype.wrapCompare = function(name, matcherFactory) { matchError.stack = matchError.stack.replace(/ +at.+jasminewd.+\n/, ''); - if (!webdriver.promise.isPromise(expectation.actual) && - !webdriver.promise.isPromise(expected)) { - compare(expectation.actual, expected); + var actualValue = null; + var expectedValue = null; + + // Check to see if the value is of type webdriver.promise.Deferred + // since deferred is not a promise. + if (expectation.actual instanceof webdriver.promise.Deferred) { + actualValue = expectation.actual.promise; + } else { + actualValue = expectation.actual; + } + if (expected instanceof webdriver.promise.Deferred) { + expectedValue = expected.promise; + } else { + expectedValue = expected; + } + + if (!webdriver.promise.isPromise(actualValue) && + !webdriver.promise.isPromise(expectedValue)) { + compare(actualValue, expectedValue); } else { - webdriver.promise.when(expectation.actual).then(function(actual) { - return webdriver.promise.all(expected).then(function(expected) { + webdriver.promise.when(actualValue).then(function(actual) { + return webdriver.promise.all(expectedValue).then(function(expected) { return compare(actual, expected); }); }); From 10f76537da61615c1ac3c5655528a60f6c8a28ac Mon Sep 17 00:00:00 2001 From: Craig Nishina Date: Thu, 27 Oct 2016 18:37:19 -0700 Subject: [PATCH 32/55] chore(release): version bump and update changelog for beta release --- CHANGELOG.md | 14 ++++++++++++++ package.json | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01cf07c..820837a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog for jasminewd2 +# 0.1.0-beta.0 + +This beta release is for the selenium-webdriver 3.0.0-beta-3 upgrade. + +## Dependencies + +- ([70c9f62](https://github.com/angular/jasminewd/commit/70c9f62af50018bea6ad326e12bacd9ca03e6ae5)) + upgrade(isPromise): expose the deferred object's promise (#58) + + - isPromise checks to see if the input parameter has a then method + - Deferred class has a promise property and no longer has a then method +- ([8870365](https://github.com/angular/jasminewd/commit/88703656b4f8a012a084ba184a4fe473f423a200)) + deps(selenium-webdriver): upgrade to 3.0.0-beta-3 (#57) + # 0.0.10 - ([ff2e624](https://github.com/angular/jasminewd/commit/ff2e624159344cd83b04c6a6648334ba12e78ea6)) diff --git a/package.json b/package.json index d0d3ca0..00b64fa 100644 --- a/package.json +++ b/package.json @@ -29,5 +29,5 @@ "test": "scripts/test.sh" }, "license": "MIT", - "version": "0.0.10" + "version": "0.1.0-beta.0" } From 5fe36a60102b9033180d68b238ab233a25a52393 Mon Sep 17 00:00:00 2001 From: Craig Date: Mon, 7 Nov 2016 16:22:21 -0800 Subject: [PATCH 33/55] deps(selenium-webdriver): upgrade to 3.0.0 (#63) fix test "should wait till the expect to run the flow" - `isPending` exists but it is no longer part of `ManagedPromise` - `isPending` also is no longer exported in `lib/promise.js` - wrote an `isPending` similar to selenium-webdriver in common.js require a minimum node version - selenium-webdriver 3.0.0 requires node >= 6.9.0 - update travis test to use node 6 --- .travis.yml | 2 +- package.json | 5 ++++- spec/adapterSpec.js | 4 ++-- spec/common.js | 4 ++++ 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2fce81e..f7fe068 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ sudo: false language: node_js node_js: - - "4" + - "6" script: - npm test diff --git a/package.json b/package.json index 00b64fa..0c47a33 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "devDependencies": { "jasmine": "2.4.1", "jshint": "2.5.0", - "selenium-webdriver": "^3.0.0-beta-3", + "selenium-webdriver": "^3.0.0", "typescript": "^2.0.0" }, "repository": { @@ -29,5 +29,8 @@ "test": "scripts/test.sh" }, "license": "MIT", + "engines": { + "node": ">= 6.9.0" + }, "version": "0.1.0-beta.0" } diff --git a/spec/adapterSpec.js b/spec/adapterSpec.js index 6fce99e..6d4722f 100644 --- a/spec/adapterSpec.js +++ b/spec/adapterSpec.js @@ -76,9 +76,9 @@ describe('webdriverJS Jasmine adapter', function() { it('should wait till the expect to run the flow', function() { var promiseA = fakeDriver.getValueA(); - expect(promiseA.isPending()).toBe(true); + expect(common.isPending(promiseA)).toBe(true); expect(promiseA).toEqual('a'); - expect(promiseA.isPending()).toBe(true); + expect(common.isPending(promiseA)).toBe(true); }); it('should compare a promise to a promise', function() { diff --git a/spec/common.js b/spec/common.js index efbd48f..6786640 100644 --- a/spec/common.js +++ b/spec/common.js @@ -123,3 +123,7 @@ exports.getMatchers = function() { } }; }; + +exports.isPending = function(managedPromise) { + return managedPromise.state_ === 'pending'; +}; From 4b0b308e876202d5e4c9c39dfb6bf0647ada0e03 Mon Sep 17 00:00:00 2001 From: Craig Nishina Date: Mon, 7 Nov 2016 16:31:19 -0800 Subject: [PATCH 34/55] chore(release): beta version bump and changelog update --- CHANGELOG.md | 15 +++++++++++++++ package.json | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 820837a..a318202 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog for jasminewd2 +# 0.1.0-beta.1 + +- ([5fe36a6](https://github.com/angular/jasminewd/commit/5fe36a60102b9033180d68b238ab233a25a52393)) + deps(selenium-webdriver): upgrade to 3.0.0 (#63) + + fix test "should wait till the expect to run the flow" + + - `isPending` exists but it is no longer part of `ManagedPromise` + - `isPending` also is no longer exported in `lib/promise.js` + - wrote an `isPending` similar to selenium-webdriver in common.js + require a minimum node version + + - selenium-webdriver 3.0.0 requires node >= 6.9.0 + - update travis test to use node 6 + # 0.1.0-beta.0 This beta release is for the selenium-webdriver 3.0.0-beta-3 upgrade. diff --git a/package.json b/package.json index 0c47a33..0391bd5 100644 --- a/package.json +++ b/package.json @@ -32,5 +32,5 @@ "engines": { "node": ">= 6.9.0" }, - "version": "0.1.0-beta.0" + "version": "0.1.0-beta.1" } From 79c7bf25b74ecffa3196b332a46bc9c7b85de719 Mon Sep 17 00:00:00 2001 From: Craig Date: Tue, 6 Dec 2016 11:36:34 -0800 Subject: [PATCH 35/55] chore(package): add dependencies, update outdated packages (#66) --- package.json | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 0391bd5..5e93f62 100644 --- a/package.json +++ b/package.json @@ -11,11 +11,13 @@ "jasmine" ], "author": "Julie Ralph ", - "devDependencies": { + "dependencies": { "jasmine": "2.4.1", - "jshint": "2.5.0", - "selenium-webdriver": "^3.0.0", - "typescript": "^2.0.0" + "selenium-webdriver": "3.0.1" + }, + "devDependencies": { + "jshint": "^2.9.4", + "typescript": "^2.0.10" }, "repository": { "type": "git", From baf6b3e403a65223ce92742087cf2a823d299951 Mon Sep 17 00:00:00 2001 From: Sammy Jelin Date: Tue, 1 Nov 2016 13:49:18 -0700 Subject: [PATCH 36/55] chore(async/await): document async/await better (#60) --- README.md | 12 ++++++++++++ spec/asyncAwaitSpec.ts | 28 +++++++++++++++++++++++----- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 3d90e22..484aa43 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,8 @@ Features - If a `done` function is passed to the test, waits for both the control flow and until done is called. + - If a test returns a promise, waits for both the the control flow and the promise to resolve. + - Enhances `expect` so that it automatically unwraps promises before performing the assertion. Installation @@ -58,3 +60,13 @@ describe('tests with webdriver', function() { }); }) ``` + +`async` functions / `await` +--------------------------- + +`async` functions and the `await` keyword are likely coming in ES7, and +available via several compilers. At the moment, they often break the WebDriver +control flow. +([GitHub issue](https://github.com/SeleniumHQ/selenium/issues/3037)). You can +still use them, but if you do then you will have to use `await`/Promises for +almost all your synchronization. See `spec/asyncAwaitSpec.ts` for details. diff --git a/spec/asyncAwaitSpec.ts b/spec/asyncAwaitSpec.ts index f8a308b..e094354 100644 --- a/spec/asyncAwaitSpec.ts +++ b/spec/asyncAwaitSpec.ts @@ -1,16 +1,34 @@ +/* Here we have an example of using async/await with jasminewd. We are using + * typescript to gain access to async/await, but mostly this should look like + * normal javascript to you. + * + * The key thing to note here is that once you use async/await, the webdriver + * control flow is no longer reliable. This means you have to use async/await + * or promises for all your asynchronous behavior. In Protractor this would + * mean putting `await` before every line interacting with the browser. In this + * example, we have to put `await` before `driver.sleep()`. + */ "use strict"; +// Declare globals declare var describe; declare var it; declare var expect; +declare var require; -async function asyncHelper() { - return 7; -} +let driver = require('./common.js').getFakeDriver(); describe('async function', function() { + let sharedVal: any; it('should wait on async functions', async function() { - let helperVal = await asyncHelper(); - expect(helperVal).toBe(7); + sharedVal = await driver.getValueA(); // Async unwraps this to 'a' + expect(sharedVal).toBe('a'); + await driver.sleep(1000); // Normally you wouldn't need to `await` this, but + // the control flow is broken for async functions. + sharedVal = await driver.getValueB(); + }); + + it('should have waited until the end of the last it() block', function() { + expect(sharedVal).toBe('b'); }); }); From 84c5a9607c8cb4079b5d96be6a5c11a87ca9cbf8 Mon Sep 17 00:00:00 2001 From: Ariel Serafini Date: Mon, 21 Nov 2016 16:22:01 -0200 Subject: [PATCH 37/55] chore(strict mode): Fix undefined variable testFn (#67) * Fix undefined variable testFn this causes jasminewd2 to be incompatible with strict mode. * fix variable definition style --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 0172445..4d10791 100644 --- a/index.js +++ b/index.js @@ -74,7 +74,7 @@ function wrapInControlFlow(flow, globalFn, fnName) { description = description ? ('("' + description + '")') : ''; return function(done) { var async = fn.length > 0; - testFn = fn.bind(this); + var testFn = fn.bind(this); flow.execute(function controlFlowExecute() { return new webdriver.promise.Promise(function(fulfill, reject) { From 90ec028eedaae6f5a604f34818d83aae4081b4d6 Mon Sep 17 00:00:00 2001 From: Georgii Dolzhykov Date: Mon, 5 Dec 2016 23:53:25 +0300 Subject: [PATCH 38/55] docs(readme): async/await is ES8 not ES7 (#64) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 484aa43..dbbe9f2 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ describe('tests with webdriver', function() { `async` functions / `await` --------------------------- -`async` functions and the `await` keyword are likely coming in ES7, and +`async` functions and the `await` keyword are likely coming in ES2017 (ES8), and available via several compilers. At the moment, they often break the WebDriver control flow. ([GitHub issue](https://github.com/SeleniumHQ/selenium/issues/3037)). You can From e9ef598d9cf14f4b72b8ec94683078e9207ad721 Mon Sep 17 00:00:00 2001 From: Sammy Jelin Date: Mon, 5 Dec 2016 17:13:11 -0800 Subject: [PATCH 39/55] chore(promises): Wait for promises explicitly (#70) See https://github.com/angular/jasminewd/issues/68 for details --- .jshintignore | 2 +- index.js | 61 ++++++++++++++++++++++++++++----------------- scripts/test.sh | 2 +- spec/adapterSpec.js | 21 ++++++++++++++++ spec/errorSpec.js | 20 +++++++++++++++ 5 files changed, 81 insertions(+), 25 deletions(-) diff --git a/.jshintignore b/.jshintignore index 8b7e536..4979ff3 100644 --- a/.jshintignore +++ b/.jshintignore @@ -1 +1 @@ -spec/asyncAwaitSpec.js +./spec/asyncAwaitSpec.js diff --git a/index.js b/index.js index 4d10791..94684e7 100644 --- a/index.js +++ b/index.js @@ -6,17 +6,6 @@ var webdriver = require('selenium-webdriver'); -/** - * Wraps a function so that all passed arguments are ignored. - * @param {!Function} fn The function to wrap. - * @return {!Function} The wrapped function. - */ -function seal(fn) { - return function() { - fn(); - }; -} - /** * Validates that the parameter is a function. * @param {Object} functionToValidate The function to validate. @@ -59,10 +48,28 @@ function validateString(stringtoValidate) { } } +/** + * Calls a function once the control flow is idle + * @param {webdriver.promise.ControlFlow} flow The Web Driver control flow + * @param {!Function} fn The function to call + */ +function callWhenIdle(flow, fn) { + if (flow.isIdle()) { + fn(); + } else { + flow.once(webdriver.promise.ControlFlow.EventType.IDLE, function() { + fn(); + }); + } +} + + /** * Wraps a function so it runs inside a webdriver.promise.ControlFlow and * waits for the flow to complete before continuing. + * @param {!webdriver.promise.ControlFlow} flow The WebDriver control flow. * @param {!Function} globalFn The function to wrap. + * @param {!string} fnName The name of the function being wrapped (e.g. `'it'`). * @return {!Function} The new function. */ function wrapInControlFlow(flow, globalFn, fnName) { @@ -78,30 +85,38 @@ function wrapInControlFlow(flow, globalFn, fnName) { flow.execute(function controlFlowExecute() { return new webdriver.promise.Promise(function(fulfill, reject) { + function wrappedReject(err) { + var wrappedErr = new Error(err); + reject(wrappedErr); + } if (async) { // If testFn is async (it expects a done callback), resolve the promise of this // test whenever that callback says to. Any promises returned from testFn are // ignored. var proxyDone = fulfill; - proxyDone.fail = function(err) { - var wrappedErr = new Error(err); - reject(wrappedErr); - }; + proxyDone.fail = wrappedReject; testFn(proxyDone); } else { // Without a callback, testFn can return a promise, or it will // be assumed to have completed synchronously. - fulfill(testFn()); + var ret = testFn(); + if (webdriver.promise.isPromise(ret)) { + ret.then(fulfill, wrappedReject); + } else { + fulfill(ret); + } } }, flow); - }, 'Run ' + fnName + description + ' in control flow').then(seal(done), function(err) { - if (!err) { - err = new Error('Unknown Error'); - err.stack = ''; + }, 'Run ' + fnName + description + ' in control flow').then( + callWhenIdle.bind(null, flow, done), function(err) { + if (!err) { + err = new Error('Unknown Error'); + err.stack = ''; + } + err.stack = err.stack + '\nFrom asynchronous test: \n' + driverError.stack; + callWhenIdle(flow, done.fail.bind(done, err)); } - err.stack = err.stack + '\nFrom asynchronous test: \n' + driverError.stack; - done.fail(err); - }); + ); }; } diff --git a/scripts/test.sh b/scripts/test.sh index 20553d8..0194052 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -9,7 +9,7 @@ $CMD [ "$?" -eq 0 ] || exit 1 echo -EXPECTED_RESULTS="16 specs, 15 failures" +EXPECTED_RESULTS="18 specs, 16 failures" echo "### running failing specs (expecting $EXPECTED_RESULTS)" CMD=$CMD_BASE$FAILING_SPECS echo "### $CMD" diff --git a/spec/adapterSpec.js b/spec/adapterSpec.js index 6d4722f..bb09093 100644 --- a/spec/adapterSpec.js +++ b/spec/adapterSpec.js @@ -242,4 +242,25 @@ describe('webdriverJS Jasmine adapter', function() { expect(spec3.description).toBe('test3'); }); }); + + describe('native promises', function() { + var currentTest = null; + + it('should wait for webdriver events sent from native promise', function() { + currentTest = 'A'; + return new Promise(function(resolve) { + setTimeout(function() { + fakeDriver.sleep(100).then(function() { + expect(currentTest).toBe('A'); + }); + resolve(); + }, 100); + }); + }); + + it('should not start a test before another finishes', function(done) { + currentTest = 'B'; + setTimeout(done, 200); + }); + }); }); diff --git a/spec/errorSpec.js b/spec/errorSpec.js index e508891..8f3ad88 100644 --- a/spec/errorSpec.js +++ b/spec/errorSpec.js @@ -102,4 +102,24 @@ describe('things that should fail', function() { expect(fakeDriver.getDecimalNumber()).toBeCloseTo(3.1); expect(fakeDriver.getDecimalNumber()).not.toBeCloseTo(3.14); }); + + describe('native promises', function() { + var testADone = false; + + it('should handle rejection from native promise', function() { + return new Promise(function(resolve, reject) { + setTimeout(function() { + fakeDriver.sleep(100).then(function() { + testADone = true; + }); + reject('Rejected promise'); + }, 100); + }); + }); + + it('should not start a test before another finishes', function(done) { + expect(testADone).toBe(true); // this test actually passes + setTimeout(done, 200); + }); + }); }); From 60230667f1d6b63f0f570a9e5e785b824e3f7074 Mon Sep 17 00:00:00 2001 From: Chris Antaki Date: Mon, 5 Dec 2016 17:23:13 -0800 Subject: [PATCH 40/55] docs(readme): removed an extra word --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dbbe9f2..1426d5a 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Features - If a `done` function is passed to the test, waits for both the control flow and until done is called. - - If a test returns a promise, waits for both the the control flow and the promise to resolve. + - If a test returns a promise, waits for both the control flow and the promise to resolve. - Enhances `expect` so that it automatically unwraps promises before performing the assertion. From 14dbf62cadf341e8a4de2dc3d92f844ece1fc3ce Mon Sep 17 00:00:00 2001 From: Craig Nishina Date: Fri, 6 Jan 2017 12:35:29 -0800 Subject: [PATCH 41/55] chore(release): version bump and changelog update --- .npmignore | 4 +++- CHANGELOG.md | 4 ++++ LICENSE | 2 +- package.json | 4 ++-- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.npmignore b/.npmignore index 9af0ef0..195f4bf 100644 --- a/.npmignore +++ b/.npmignore @@ -1,5 +1,7 @@ node_modules/ spec/ scripts/ -.travis.yml +.gitignore +.jshintignore .npmignore +.travis.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index a318202..405f7eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog for jasminewd2 +# 0.1.0 + +Release for the selenium-webdriver 3.0.1 upgrade. + # 0.1.0-beta.1 - ([5fe36a6](https://github.com/angular/jasminewd/commit/5fe36a60102b9033180d68b238ab233a25a52393)) diff --git a/LICENSE b/LICENSE index 969c1d6..7b799d3 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014 Google, Inc. +Copyright (c) 2017 Google, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/package.json b/package.json index 5e93f62..5654dd6 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ }, "license": "MIT", "engines": { - "node": ">= 6.9.0" + "node": ">= 6.9.x" }, - "version": "0.1.0-beta.1" + "version": "0.1.0" } From ed2d3e8af75fb0d7fa344c9ae00d8c74718f3560 Mon Sep 17 00:00:00 2001 From: Craig Date: Sat, 7 Jan 2017 01:57:52 -0800 Subject: [PATCH 42/55] chore(license): update license and update travis shield (#77) --- LICENSE | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 7b799d3..df2ef23 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2017 Google, Inc. +Copyright (c) 2014-2017 Google, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 1426d5a..3c4f603 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -jasminewd [![Build Status](https://travis-ci.org/angular/jasminewd.png?branch=master)](https://travis-ci.org/angular/jasminewd) +jasminewd [![Build Status](https://travis-ci.org/angular/jasminewd.svg?branch=master)](https://travis-ci.org/angular/jasminewd) ========= Adapter for Jasmine-to-WebDriverJS. Used by [Protractor](http://www.github.com/angular/protractor). From cf1cd34a4089b6492160349a10d717c7bcaa2c31 Mon Sep 17 00:00:00 2001 From: Craig Date: Tue, 10 Jan 2017 11:22:40 -0800 Subject: [PATCH 43/55] chore(isPromise): revert expose deferred object's promise (#78) --- index.js | 26 +++++--------------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/index.js b/index.js index 94684e7..ab1b10a 100644 --- a/index.js +++ b/index.js @@ -205,28 +205,12 @@ jasmine.Expectation.prototype.wrapCompare = function(name, matcherFactory) { matchError.stack = matchError.stack.replace(/ +at.+jasminewd.+\n/, ''); - var actualValue = null; - var expectedValue = null; - - // Check to see if the value is of type webdriver.promise.Deferred - // since deferred is not a promise. - if (expectation.actual instanceof webdriver.promise.Deferred) { - actualValue = expectation.actual.promise; - } else { - actualValue = expectation.actual; - } - if (expected instanceof webdriver.promise.Deferred) { - expectedValue = expected.promise; - } else { - expectedValue = expected; - } - - if (!webdriver.promise.isPromise(actualValue) && - !webdriver.promise.isPromise(expectedValue)) { - compare(actualValue, expectedValue); + if (!webdriver.promise.isPromise(expectation.actual) && + !webdriver.promise.isPromise(expected)) { + compare(expectation.actual, expected); } else { - webdriver.promise.when(actualValue).then(function(actual) { - return webdriver.promise.all(expectedValue).then(function(expected) { + webdriver.promise.when(expectation.actual).then(function(actual) { + return webdriver.promise.all(expected).then(function(expected) { return compare(actual, expected); }); }); From f0d0f06bdff3a5786e4dd23d4b7e6e3ffac9173e Mon Sep 17 00:00:00 2001 From: Craig Nishina Date: Tue, 10 Jan 2017 11:26:37 -0800 Subject: [PATCH 44/55] chore(release): version bump and changelog update --- CHANGELOG.md | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 405f7eb..8b978ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog for jasminewd2 +# 0.1.1 + +- ([cf1cd34](https://github.com/angular/jasminewd/commit/cf1cd34a4089b6492160349a10d717c7bcaa2c31)) + chore(isPromise): revert expose deferred object's promise (#78) + # 0.1.0 Release for the selenium-webdriver 3.0.1 upgrade. diff --git a/package.json b/package.json index 5654dd6..ef5f9a7 100644 --- a/package.json +++ b/package.json @@ -34,5 +34,5 @@ "engines": { "node": ">= 6.9.x" }, - "version": "0.1.0" + "version": "0.1.1" } From 27b485019589cd662ee69e7920893ffa50774b97 Mon Sep 17 00:00:00 2001 From: Sammy Jelin Date: Thu, 12 Jan 2017 16:52:14 -0800 Subject: [PATCH 45/55] feat(SELENIUM_PROMISE_MANAGER): Support `SELENIUM_PROMISE_MANAGER=0` (#72) There are three major ways this was done in this change: * In `callWhenIdle`, if `flow.isIdle` is not defined, we assume we are working with a `SimpleScheduler` instance, and so the flow is effectively idle. * In `initJasmineWd`, if `flow.reset` is not defined, we assume we are working with a `SimpleScheduler` instance, and so don't bother resetting the flow. * In `wrapInControlFlow`, we use `flow.promise` to create a new promise if possible. Since `new webdriver.promise.Promise()` would have always made a `ManagedPromise`, but `flow.promise` will do the right thing. * In `wrapCompare`, we avoid the webdriver library entirely, and never instance any extra promises. Using `webdriver.promise.when` and `webdriver.promise.all` could have been a problem if our instance of `webdriver` had the control flow turned on, but another instance somewhere did not (or even the same instance, but just at a different point in time). Instead we use the new `maybePromise` tool, which is a mess but is also exactly what we want. * In `specs/*`, we replace `webdriver.promise.fulfilled` with `webdriver.promise.when`. * In `specs/*`, a new version of `adapterSpec.js` and `errorSpec.js` are created: `asyncAwaitAdapterSpec.ts` and `asyncAwaitErrorSpec.ts`. I also also fixed a minor bug where we weren't correctly checking for promises inside an array of expected results. Before we had ```js expected = Array.prototype.slice.call(arguments, 0) ... webdriver.promise.isPromise(expected) ``` I thought about it for a little while, and there's no way that's correct. `expected` is an `Array`, there's no way it has a `.then` function. Closes https://github.com/angular/jasminewd/issues/69 --- .gitignore | 2 +- .jshintignore | 2 +- .npmignore | 3 + README.md | 3 +- index.js | 69 +++--- maybePromise.js | 58 +++++ package.json | 15 +- scripts/test.sh | 44 +++- spec/adapterSpec.js | 8 +- spec/asyncAwaitAdapterSpec.ts | 295 ++++++++++++++++++++++++++ spec/asyncAwaitErrorSpec.ts | 152 +++++++++++++ spec/asyncAwaitSpec.ts | 34 --- spec/common.js | 36 ++-- spec/errorSpec.js | 6 + spec/maybePromiseSpec.js | 155 ++++++++++++++ spec/support/failing_specs.json | 4 +- spec/support/lib_specs.json | 6 + spec/support/no_cf_failing_specs.json | 6 + spec/support/no_cf_passing_specs.json | 6 + spec/support/passing_specs.json | 5 +- tsconfig.json | 15 ++ tslint.json | 15 ++ 22 files changed, 839 insertions(+), 100 deletions(-) create mode 100644 maybePromise.js create mode 100644 spec/asyncAwaitAdapterSpec.ts create mode 100644 spec/asyncAwaitErrorSpec.ts delete mode 100644 spec/asyncAwaitSpec.ts create mode 100644 spec/maybePromiseSpec.js create mode 100644 spec/support/lib_specs.json create mode 100644 spec/support/no_cf_failing_specs.json create mode 100644 spec/support/no_cf_passing_specs.json create mode 100644 tsconfig.json create mode 100644 tslint.json diff --git a/.gitignore b/.gitignore index 77dfc75..943eec3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ *.log node_modules -spec/asyncAwaitSpec.js +built_spec diff --git a/.jshintignore b/.jshintignore index 4979ff3..31373a0 100644 --- a/.jshintignore +++ b/.jshintignore @@ -1 +1 @@ -./spec/asyncAwaitSpec.js +./built_spec/* diff --git a/.npmignore b/.npmignore index 195f4bf..23c47b1 100644 --- a/.npmignore +++ b/.npmignore @@ -1,6 +1,9 @@ node_modules/ spec/ +built_spec/ scripts/ +tsconfig.json +tslint.json .gitignore .jshintignore .npmignore diff --git a/README.md b/README.md index 3c4f603..a27d4ed 100644 --- a/README.md +++ b/README.md @@ -69,4 +69,5 @@ available via several compilers. At the moment, they often break the WebDriver control flow. ([GitHub issue](https://github.com/SeleniumHQ/selenium/issues/3037)). You can still use them, but if you do then you will have to use `await`/Promises for -almost all your synchronization. See `spec/asyncAwaitSpec.ts` for details. +almost all your synchronization. See `spec/asyncAwaitAdapterSpec.ts` and +`spec/asyncAwaitErrorSpec.ts` for examples. diff --git a/index.js b/index.js index ab1b10a..5875a0f 100644 --- a/index.js +++ b/index.js @@ -5,6 +5,7 @@ */ var webdriver = require('selenium-webdriver'); +var maybePromise = require('./maybePromise'); /** * Validates that the parameter is a function. @@ -54,7 +55,7 @@ function validateString(stringtoValidate) { * @param {!Function} fn The function to call */ function callWhenIdle(flow, fn) { - if (flow.isIdle()) { + if (!flow.isIdle || flow.isIdle()) { fn(); } else { flow.once(webdriver.promise.ControlFlow.EventType.IDLE, function() { @@ -84,7 +85,14 @@ function wrapInControlFlow(flow, globalFn, fnName) { var testFn = fn.bind(this); flow.execute(function controlFlowExecute() { - return new webdriver.promise.Promise(function(fulfill, reject) { + function newPromise(resolver) { + if (typeof flow.promise == 'function') { + return flow.promise(resolver); + } else { + return new webdriver.promise.Promise(resolver, flow); + } + } + return newPromise(function(fulfill, reject) { function wrappedReject(err) { var wrappedErr = new Error(err); reject(wrappedErr); @@ -106,7 +114,7 @@ function wrapInControlFlow(flow, globalFn, fnName) { fulfill(ret); } } - }, flow); + }); }, 'Run ' + fnName + description + ' in control flow').then( callWhenIdle.bind(null, flow, done), function(err) { if (!err) { @@ -173,15 +181,17 @@ function initJasmineWd(flow) { global.beforeAll = wrapInControlFlow(flow, global.beforeAll, 'beforeAll'); global.afterAll = wrapInControlFlow(flow, global.afterAll, 'afterAll'); - // On timeout, the flow should be reset. This will prevent webdriver tasks - // from overflowing into the next test and causing it to fail or timeout - // as well. This is done in the reporter instead of an afterEach block - // to ensure that it runs after any afterEach() blocks with webdriver tasks - // get to complete first. - jasmine.getEnv().addReporter(new OnTimeoutReporter(function() { - console.warn('A Jasmine spec timed out. Resetting the WebDriver Control Flow.'); - flow.reset(); - })); + if (flow.reset) { + // On timeout, the flow should be reset. This will prevent webdriver tasks + // from overflowing into the next test and causing it to fail or timeout + // as well. This is done in the reporter instead of an afterEach block + // to ensure that it runs after any afterEach() blocks with webdriver tasks + // get to complete first. + jasmine.getEnv().addReporter(new OnTimeoutReporter(function() { + console.warn('A Jasmine spec timed out. Resetting the WebDriver Control Flow.'); + flow.reset(); + })); + } } var originalExpect = global.expect; @@ -196,6 +206,10 @@ global.expect = function(actual) { /** * Creates a matcher wrapper that resolves any promises given for actual and * expected values, as well as the `pass` property of the result. + * + * Wrapped matchers will return either `undefined` or a promise which resolves + * when the matcher is complete, depending on if the matcher had to resolve any + * promises. */ jasmine.Expectation.prototype.wrapCompare = function(name, matcherFactory) { return function() { @@ -205,16 +219,12 @@ jasmine.Expectation.prototype.wrapCompare = function(name, matcherFactory) { matchError.stack = matchError.stack.replace(/ +at.+jasminewd.+\n/, ''); - if (!webdriver.promise.isPromise(expectation.actual) && - !webdriver.promise.isPromise(expected)) { - compare(expectation.actual, expected); - } else { - webdriver.promise.when(expectation.actual).then(function(actual) { - return webdriver.promise.all(expected).then(function(expected) { - return compare(actual, expected); - }); + // Return either undefined or a promise of undefined + return maybePromise(expectation.actual, function(actual) { + return maybePromise.all(expected, function(expected) { + return compare(actual, expected); }); - } + }); function compare(actual, expected) { var args = expected.slice(0); @@ -229,12 +239,9 @@ jasmine.Expectation.prototype.wrapCompare = function(name, matcherFactory) { var result = matcherCompare.apply(null, args); - if (webdriver.promise.isPromise(result.pass)) { - return webdriver.promise.when(result.pass).then(compareDone); - } else { - return compareDone(result.pass); - } + return maybePromise(result.pass, compareDone); + // compareDone always returns undefined function compareDone(pass) { var message = ''; @@ -268,13 +275,9 @@ jasmine.Expectation.prototype.wrapCompare = function(name, matcherFactory) { function defaultNegativeCompare() { var result = matcher.compare.apply(null, args); - if (webdriver.promise.isPromise(result.pass)) { - result.pass = result.pass.then(function(pass) { - return !pass; - }); - } else { - result.pass = !result.pass; - } + result.pass = maybePromise(result.pass, function(pass) { + return !pass; + }); return result; } } diff --git a/maybePromise.js b/maybePromise.js new file mode 100644 index 0000000..5a125fe --- /dev/null +++ b/maybePromise.js @@ -0,0 +1,58 @@ +/** + * This file implements jasminewd's peculiar alternatives to Promise.resolve() + * and Promise.all(). Do not use the code from this file as polyfill for + * Promise.resolve() or Promise.all(). There are a number of reasons why this + * implementation will cause unexpected errors in most codebases. + * + * Called "maybePromise" because both the parameters and the return values may + * or may not be promises, and code execution may or may not be synchronous. + */ + +/** + * Runs a callback synchronously against non-promise values and asynchronously + * against promises. Similar to ES6's `Promise.resolve` except that it is + * synchronous when possible and won't wrap the return value. + * + * This is not what you normally want. Normally you want the code to be + * consistently asynchronous, and you want the result wrapped into a promise. + * But because of webdriver's control flow, we're better off not introducing any + * extra layers of promises or asynchronous activity. + * + * @param {*} val The value to call the callback with. + * @param {!Function} callback The callback function + * @return {*} If val isn't a promise, the return value of the callback is + * directly returned. If val is a promise, a promise (generated by val.then) + * resolving to the callback's return value is returned. + */ +var maybePromise = module.exports = function maybePromise(val, callback) { + if (val && (typeof val.then == 'function')) { + return val.then(callback); + } else { + return callback(val); + } +} + +/** + * Like maybePromise() but for an array of values. Analogous to `Promise.all`. + * + * @param {!Array<*>} vals An array of values to call the callback with + * @param {!Function} callback the callback function + * @return {*} If nothing in vals is a promise, the return value of the callback + * is directly returned. Otherwise, a promise (generated by the .then + * functions in vals) resolving to the callback's return value is returned. + */ +maybePromise.all = function all(vals, callback) { + var resolved = new Array(vals.length); + function resolveAt(i) { + if (i >= vals.length) { + return callback(resolved); + } else { + return maybePromise(vals[i], function(val) { + resolved[i] = val; + return resolveAt(i+1); + }); + } + } + return resolveAt(0); +} + diff --git a/package.json b/package.json index ef5f9a7..79c7bc3 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,15 @@ "selenium-webdriver": "3.0.1" }, "devDependencies": { + "@types/node": "^6.0.56", + "@types/selenium-webdriver": "^2.53.38", + "jasmine": "2.4.1", "jshint": "^2.9.4", - "typescript": "^2.0.10" + "selenium-webdriver": "2.53.3", + "tslint": "^4.2.0", + "tslint-eslint-rules": "^3.2.3", + "typescript": "^2.0.10", + "vrsource-tslint-rules": "^4.0.0" }, "repository": { "type": "git", @@ -26,8 +33,10 @@ "main": "index.js", "scripts": { "jshint": "jshint index.js spec", - "tsc": "tsc -t ES2015 spec/asyncAwaitSpec.ts", - "pretest": "npm run jshint && npm run tsc", + "tslint": "tslint spec/*.ts", + "lint": "npm run jshint && npm run tslint", + "tsc": "tsc; cp spec/*.js built_spec", + "pretest": "npm run lint && npm run tsc", "test": "scripts/test.sh" }, "license": "MIT", diff --git a/scripts/test.sh b/scripts/test.sh index 0194052..d11a5d1 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -1,16 +1,33 @@ +LIB_SPECS="spec/support/lib_specs.json" PASSING_SPECS="spec/support/passing_specs.json" FAILING_SPECS="spec/support/failing_specs.json" +NO_CF_PASSING_SPECS="spec/support/no_cf_passing_specs.json" +NO_CF_FAILING_SPECS="spec/support/no_cf_failing_specs.json" CMD_BASE="node node_modules/.bin/jasmine JASMINE_CONFIG_PATH=" -echo "### running passing specs" +# Run unit tests + +echo "### running all unit tests" +CMD=$CMD_BASE$LIB_SPECS +echo "### $CMD" +$CMD +[ "$?" -eq 0 ] || exit 1 +echo + + +# Run all tests when the control flow is enabled + +export SELENIUM_PROMISE_MANAGER=1 + +echo "### running all passing specs" CMD=$CMD_BASE$PASSING_SPECS echo "### $CMD" $CMD [ "$?" -eq 0 ] || exit 1 echo -EXPECTED_RESULTS="18 specs, 16 failures" -echo "### running failing specs (expecting $EXPECTED_RESULTS)" +EXPECTED_RESULTS="38 specs, 34 failures" +echo "### running all failing specs (expecting $EXPECTED_RESULTS)" CMD=$CMD_BASE$FAILING_SPECS echo "### $CMD" res=`$CMD 2>/dev/null` @@ -18,4 +35,25 @@ results_line=`echo "$res" | tail -2 | head -1` echo "result: $results_line" [ "$results_line" = "$EXPECTED_RESULTS" ] || exit 1 +# Run only the async/await tests when the control flow is disabled + +export SELENIUM_PROMISE_MANAGER=0 + +echo "### running async/await passing specs" +CMD=$CMD_BASE$NO_CF_PASSING_SPECS +echo "### $CMD" +$CMD +[ "$?" -eq 0 ] || exit 1 +echo + +EXPECTED_RESULTS="19 specs, 17 failures" +echo "### running async/await failing specs (expecting $EXPECTED_RESULTS)" +CMD=$CMD_BASE$NO_CF_FAILING_SPECS +echo "### $CMD" +res=`$CMD 2>/dev/null` +results_line=`echo "$res" | tail -2 | head -1` +echo "result: $results_line" +[ "$results_line" = "$EXPECTED_RESULTS" ] || exit 1 + + echo "all pass" diff --git a/spec/adapterSpec.js b/spec/adapterSpec.js index bb09093..401a667 100644 --- a/spec/adapterSpec.js +++ b/spec/adapterSpec.js @@ -138,7 +138,7 @@ describe('webdriverJS Jasmine adapter', function() { fakeDriver.getValueList().then(function(list) { var result = list.map(function(webElem) { - var webElemsPromise = webdriver.promise.fulfilled(webElem).then(function(webElem) { + var webElemsPromise = webdriver.promise.when(webElem).then(function(webElem) { return [webElem]; }); return webdriver.promise.fullyResolved(checkTexts(webElemsPromise)); @@ -244,6 +244,12 @@ describe('webdriverJS Jasmine adapter', function() { }); describe('native promises', function() { + it('should have done argument override return returned promise', function(done) { + var ret = new Promise(function() {}); + done(); + return ret; + }); + var currentTest = null; it('should wait for webdriver events sent from native promise', function() { diff --git a/spec/asyncAwaitAdapterSpec.ts b/spec/asyncAwaitAdapterSpec.ts new file mode 100644 index 0000000..6c095c8 --- /dev/null +++ b/spec/asyncAwaitAdapterSpec.ts @@ -0,0 +1,295 @@ +import {promise as wdpromise, WebElement} from 'selenium-webdriver'; +const common = require('./common'); + +declare function expect(actual: any): any; +declare function describe(description: string, tests: Function): void; +declare function it(description: string, test?: Function, timeout?: number): any; +declare function xit(description: string, test?: Function, timeout?: number): any; +declare function beforeEach(setup: Function): void; +declare function beforeAll(setup: Function): void; +declare function afterEach(setup: Function): void; +declare function afterAll(setup: Function): void; +declare var jasmine; + +/** + * This file is very similar to adapterSpec.ts, but we use async/await instead + * of the WebDriver Control Flow for synchronization. These tests are desgined + * to work regardless of if the WebDriver Control Flow is disabled. + */ + +const fakeDriver = common.getFakeDriver(); + +/* jshint esversion: 6 */ +describe('webdriverJS Jasmine adapter plain', function() { + it('should pass normal synchronous tests', function() { + expect(true).toBe(true); + }); + + it('should allow an empty it block and mark as pending'); + + xit('should allow a spec marked as pending with xit', function() { + expect(true).toBe(false); + }); +}); + +describe('context', function() { + beforeEach(function() { + this.foo = 0; + }); + + it('can use the `this` to share state', function() { + expect(this.foo).toEqual(0); + this.bar = 'test pollution?'; + }); + + it('prevents test pollution by having an empty `this` created for the next spec', function() { + expect(this.foo).toEqual(0); + expect(this.bar).toBe(undefined); + }); +}); + +describe('webdriverJS Jasmine adapter', function() { + // Shorten this and you should see tests timing out. + jasmine.DEFAULT_TIMEOUT_INTERVAL = 2000; + let beforeEachMsg: string; + + beforeEach(function() { + jasmine.addMatchers(common.getMatchers()); + }); + + beforeEach(async function() { + await fakeDriver.setUp().then(function(value) { + beforeEachMsg = value; + }); + }); + + afterEach(function() { + beforeEachMsg = ''; + }); + + it('should only allow initializing once', function() { + expect(require('../index.js').init).toThrow( + Error('JasmineWd already initialized when init() was called')); + }); + + it('should pass normal synchronous tests', function() { + expect(true).toEqual(true); + }); + + it('should compare a promise to a primitive', async function() { + // You need `await` before `expect` if the expect needs to unwrap promises + await expect(fakeDriver.getValueA()).toEqual('a'); + await expect(fakeDriver.getValueB()).toEqual('b'); + }); + + it('beforeEach should wait for control flow', async function() { + // But you can also just add `await` wherever you like + await expect(beforeEachMsg).toEqual('setup done'); + }); + + it('should wait till the expect to run the flow', async function() { + const promiseA = fakeDriver.getValueA(); + // isPending() is only defined for WebDriver's ManagedPromise + if (!promiseA.isPending) { + promiseA.isPending = () => { return true; }; + } + + await expect(promiseA.isPending()).toBe(true); + const expectation = expect(promiseA).toEqual('a'); + await expect(promiseA.isPending()).toBe(true); + + // We still need to wait for the expectation to finish, since the control + // flow might be disabled + await expectation; + }); + + it('should compare a promise to a promise', async function() { + await expect(fakeDriver.getValueA()).toEqual(fakeDriver.getOtherValueA()); + }); + + it('should still allow use of the underlying promise', async function() { + const promiseA = fakeDriver.getValueA(); + await promiseA.then(function(value) { + expect(value).toEqual('a'); + }); + }); + + it('should allow scheduling of tasks', async function() { + await fakeDriver.sleep(300); + await expect(fakeDriver.getValueB()).toEqual('b'); + }); + + it('should allow the use of custom matchers', async function() { + await expect(500).toBeLotsMoreThan(3); + await expect(fakeDriver.getBigNumber()).toBeLotsMoreThan(33); + await expect(fakeDriver.getBigNumber()).toBeLotsMoreThan(fakeDriver.getSmallNumber()); + await expect(fakeDriver.getSmallNumber()).not.toBeLotsMoreThan(fakeDriver.getBigNumber()); + }); + + it('should allow custom matchers to return a promise', async function() { + await expect(fakeDriver.getDisplayedElement()).toBeDisplayed(); + await expect(fakeDriver.getHiddenElement()).not.toBeDisplayed(); + }); + + it('should pass multiple arguments to matcher', async function() { + // Passing specific precision + await expect(fakeDriver.getDecimalNumber()).toBeCloseTo(3.1, 1); + await expect(fakeDriver.getDecimalNumber()).not.toBeCloseTo(3.1, 2); + + // Using default precision (2) + await expect(fakeDriver.getDecimalNumber()).not.toBeCloseTo(3.1); + await expect(fakeDriver.getDecimalNumber()).toBeCloseTo(3.14); + }); + + it('should allow iterating through arrays', async function() { + // This is a convoluted test which shows a real issue which + // cropped up in version changes to the selenium-webdriver module. + // See https://github.com/angular/protractor/pull/2263 + const checkTexts = async function(webElems: wdpromise.Promise wdpromise.Promise}>>) { + const texts = webElems.then(function(arr) { + const results = arr.map(function(webElem) { + return webElem.getText(); + }); + return wdpromise.all(results); + }); + + await expect(texts).not.toContain('e'); + + return true; + }; + + await fakeDriver.getValueList().then(function(list) { + const result = list.map(function(webElem) { + const webElemsPromise = wdpromise.when(webElem).then(function(webElem) { + return [webElem]; + }); + return wdpromise.fullyResolved(checkTexts(webElemsPromise)); + }); + return wdpromise.all(result); + }); + }); + + describe('not', async function() { + it('should still pass normal synchronous tests', async function() { + expect(4).not.toEqual(5); + }); + + it('should compare a promise to a primitive', async function() { + await expect(fakeDriver.getValueA()).not.toEqual('b'); + }); + + it('should compare a promise to a promise', async function() { + await expect(fakeDriver.getValueA()).not.toEqual(fakeDriver.getValueB()); + }); + + it('should allow custom matchers to return a promise when actual is not a promise', async function() { + await expect(fakeDriver.displayedElement).toBeDisplayed(); + await expect(fakeDriver.hiddenElement).not.toBeDisplayed(); + }); + }); + + it('should throw an error with a WebElement actual value', function() { + const webElement = new WebElement(fakeDriver as any, 'idstring'); + + expect(function() { + expect(webElement).toEqual(4); + }).toThrow(Error('expect called with WebElement argument, expected a Promise. ' + + 'Did you mean to use .getText()?')); + }); + + it('should pass after the timed out tests', async function() { + await expect(fakeDriver.getValueA()).toEqual('a'); + }); + + describe('should work for both synchronous and asynchronous tests', function() { + let x: number; + + beforeEach(function() { + x = 0; + }); + + afterEach(function() { + expect(x).toBe(1); + }); + + it('should execute a synchronous test', function() { + x = 1; + }); + + it('should execute an asynchronous test', function(done) { + setTimeout(function(){ + x = 1; + done(); + }, 500); + }); + }); + + describe('beforeAll and afterAll', function() { + let asyncValue: number; + let setupMsg: string; + + beforeAll(function(done) { + setTimeout(function() { + asyncValue = 5; + done(); + }, 500); + }); + + beforeAll(async function() { + await fakeDriver.setUp().then(function(msg) { + setupMsg = msg; + }); + }); + + afterAll(function() { + setupMsg = ''; + }); + + it('should have set asyncValue', function() { + expect(asyncValue).toEqual(5); + }); + + it('should wait for control flow', function() { + expect(setupMsg).toEqual('setup done'); + }); + }); + + describe('it return value', function() { + const spec1 = it('test1') as any; + const spec2 = it('test2', function() {}) as any; + const spec3 = it('test3', function() {}, 1) as any; + + it('should return the spec', function() { + expect(spec1.description).toBe('test1'); + expect(spec2.description).toBe('test2'); + expect(spec3.description).toBe('test3'); + }); + }); + + describe('native promises', function() { + it('should have done argument override returned promise', async function(done) { + const ret = new Promise(function() {}); + done(); + await ret; + }); + + let currentTest: string = null; + + it('should wait for webdriver events sent from native promise', function() { + currentTest = 'A'; + return new Promise(function(resolve) { + setTimeout(async function() { + await fakeDriver.sleep(100).then(function() { + expect(currentTest).toBe('A'); + }); + resolve(); + }, 100); + }); + }); + + it('should not start a test before another finishes', function(done) { + currentTest = 'B'; + setTimeout(done, 200); + }); + }); +}); diff --git a/spec/asyncAwaitErrorSpec.ts b/spec/asyncAwaitErrorSpec.ts new file mode 100644 index 0000000..3a70155 --- /dev/null +++ b/spec/asyncAwaitErrorSpec.ts @@ -0,0 +1,152 @@ +const common = require('./common'); + +declare function expect(actual: any): any; +declare function describe(description: string, tests: Function): void; +declare function it(description: string, test?: Function, timeout?: number): any; +declare function xit(description: string, test?: Function, timeout?: number): any; +declare function beforeEach(setup: Function): void; +declare function beforeAll(setup: Function): void; +declare function afterEach(setup: Function): void; +declare function afterAll(setup: Function): void; +declare var jasmine; + +/** + * This file is very similar to errorSpec.ts, but we use async/await instead of + * the WebDriver Control Flow for synchronization. These tests are desgined to + * work regardless of if the WebDriver Control Flow is disabled. + */ + +const fakeDriver = common.getFakeDriver(); + +/* jshint esversion: 6 */ +describe('Timeout cases', function() { + it('should timeout after 200ms', async function(done) { + // The returned promise is ignored and jasminewd will wait for the `done` + // callback to be called + await expect(fakeDriver.getValueA()).toEqual('a'); + }, 200); + + it('should timeout after 300ms', async function() { + await fakeDriver.sleep(9999); + await expect(fakeDriver.getValueB()).toEqual('b'); + }, 300); + + it('should pass after the timed out tests', function() { + expect(true).toEqual(true); + }); +}); + +describe('things that should fail', function() { + beforeEach(function() { + jasmine.addMatchers(common.getMatchers()); + }); + + it('should pass errors from done callback', function(done) { + done.fail('an error from done.fail'); + }); + + it('should error asynchronously in promise callbacks', async function() { + await fakeDriver.sleep(50).then(function() { + expect(true).toEqual(false); + }); + }); + + it('should error asynchronously within done callback', function(done) { + setTimeout(async function() { + await expect(false).toEqual(true); + done(); + }, 200); + }); + + it('should fail normal synchronous tests', function() { + expect(true).toBe(false); + }); + + it('should fail when an error is thrown', function() { + throw new Error('I am an intentional error'); + }); + + it('should compare a promise to a primitive', async function() { + await expect(fakeDriver.getValueA()).toEqual('d'); + await expect(fakeDriver.getValueB()).toEqual('e'); + }); + + it('should wait till the expect to run the flow', async function() { + const promiseA = fakeDriver.getValueA(); + // isPending() is only defined for WebDriver's ManagedPromise + if (!promiseA.isPending) { + promiseA.isPending = () => { return true; }; + } + + await expect(promiseA.isPending()).toBe(true); + const expectation = expect(promiseA).toEqual('a'); + await expect(promiseA.isPending()).toBe(false); + + // We still need to wait for the expectation to finish, since the control + // flow might be disabled + await expectation; + }); + + it('should compare a promise to a promise', async function() { + await expect(fakeDriver.getValueA()).toEqual(fakeDriver.getValueB()); + }); + + it('should still allow use of the underlying promise', async function() { + const promiseA = fakeDriver.getValueA(); + await promiseA.then(function(value) { + expect(value).toEqual('b'); + }); + }); + + it('should allow scheduling of tasks', async function() { + await fakeDriver.sleep(300); + await expect(fakeDriver.getValueB()).toEqual('c'); + }); + + it('should allow the use of custom matchers', async function() { + await expect(1000).toBeLotsMoreThan(999); + await expect(fakeDriver.getBigNumber()).toBeLotsMoreThan(1110); + await expect(fakeDriver.getBigNumber()).not.toBeLotsMoreThan(fakeDriver.getSmallNumber()); + await expect(fakeDriver.getSmallNumber()).toBeLotsMoreThan(fakeDriver.getBigNumber()); + }); + + it('should allow custom matchers to return a promise', async function() { + await expect(fakeDriver.getDisplayedElement()).not.toBeDisplayed(); + await expect(fakeDriver.getHiddenElement()).toBeDisplayed(); + }); + + it('should pass multiple arguments to matcher', async function() { + // Passing specific precision + await expect(fakeDriver.getDecimalNumber()).toBeCloseTo(3.5, 1); + + // Using default precision (2) + await expect(fakeDriver.getDecimalNumber()).toBeCloseTo(3.1); + await expect(fakeDriver.getDecimalNumber()).not.toBeCloseTo(3.14); + }); + + describe('native promises', function() { + it('should time out if done argument is never called, even if promise is returned', + async function(done) { + await new Promise(function() {}); + } + ); + + let testADone = false; + + it('should handle rejection from native promise', function() { + return new Promise(async function(resolve, reject) { + setTimeout(async function() { + await fakeDriver.sleep(100).then(function() { + testADone = true; + }); + reject('Rejected promise'); + }, 100); + }); + }); + + it('should not start a test before another finishes', function(done) { + expect(testADone).toBe(true); // this test actually passes + setTimeout(done, 200); + }); + }); +}); diff --git a/spec/asyncAwaitSpec.ts b/spec/asyncAwaitSpec.ts deleted file mode 100644 index e094354..0000000 --- a/spec/asyncAwaitSpec.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* Here we have an example of using async/await with jasminewd. We are using - * typescript to gain access to async/await, but mostly this should look like - * normal javascript to you. - * - * The key thing to note here is that once you use async/await, the webdriver - * control flow is no longer reliable. This means you have to use async/await - * or promises for all your asynchronous behavior. In Protractor this would - * mean putting `await` before every line interacting with the browser. In this - * example, we have to put `await` before `driver.sleep()`. - */ -"use strict"; - -// Declare globals -declare var describe; -declare var it; -declare var expect; -declare var require; - -let driver = require('./common.js').getFakeDriver(); - -describe('async function', function() { - let sharedVal: any; - it('should wait on async functions', async function() { - sharedVal = await driver.getValueA(); // Async unwraps this to 'a' - expect(sharedVal).toBe('a'); - await driver.sleep(1000); // Normally you wouldn't need to `await` this, but - // the control flow is broken for async functions. - sharedVal = await driver.getValueB(); - }); - - it('should have waited until the end of the last it() block', function() { - expect(sharedVal).toBe('b'); - }); -}); diff --git a/spec/common.js b/spec/common.js index 6786640..4ab4964 100644 --- a/spec/common.js +++ b/spec/common.js @@ -13,88 +13,88 @@ exports.getFakeDriver = function() { }, setUp: function() { return flow.execute(function() { - return webdriver.promise.fulfilled('setup done'); + return webdriver.promise.when('setup done'); }, 'setUp'); }, getValueA: function() { return flow.execute(function() { return webdriver.promise.delayed(500).then(function() { - return webdriver.promise.fulfilled('a'); + return webdriver.promise.when('a'); }); }, 'getValueA'); }, getOtherValueA: function() { return flow.execute(function() { - return webdriver.promise.fulfilled('a'); + return webdriver.promise.when('a'); }, 'getOtherValueA'); }, getValueB: function() { return flow.execute(function() { - return webdriver.promise.fulfilled('b'); + return webdriver.promise.when('b'); }, 'getValueB'); }, getBigNumber: function() { return flow.execute(function() { - return webdriver.promise.fulfilled(1111); + return webdriver.promise.when(1111); }, 'getBigNumber'); }, getSmallNumber: function() { return flow.execute(function() { - return webdriver.promise.fulfilled(11); + return webdriver.promise.when(11); }, 'getSmallNumber'); }, getDecimalNumber: function() { return flow.execute(function() { - return webdriver.promise.fulfilled(3.14159); + return webdriver.promise.when(3.14159); }, 'getDecimalNumber'); }, getDisplayedElement: function() { return flow.execute(function() { - return webdriver.promise.fulfilled({ + return webdriver.promise.when({ isDisplayed: function() { - return webdriver.promise.fulfilled(true); + return webdriver.promise.when(true); } }); }, 'getDisplayedElement'); }, getHiddenElement: function() { return flow.execute(function() { - return webdriver.promise.fulfilled({ + return webdriver.promise.when({ isDisplayed: function() { - return webdriver.promise.fulfilled(false); + return webdriver.promise.when(false); } }); }, 'getHiddenElement'); }, getValueList: function() { return flow.execute(function() { - return webdriver.promise.fulfilled([{ + return webdriver.promise.when([{ getText: function() { - return flow.execute(function() { return webdriver.promise.fulfilled('a');}); + return flow.execute(function() { return webdriver.promise.when('a');}); } }, { getText: function() { - return flow.execute(function() { return webdriver.promise.fulfilled('b');}); + return flow.execute(function() { return webdriver.promise.when('b');}); } }, { getText: function() { - return flow.execute(function() { return webdriver.promise.fulfilled('c');}); + return flow.execute(function() { return webdriver.promise.when('c');}); } }, { getText: function() { - return flow.execute(function() { return webdriver.promise.fulfilled('d');}); + return flow.execute(function() { return webdriver.promise.when('d');}); } }]); }, 'getValueList'); }, displayedElement: { isDisplayed: function() { - return webdriver.promise.fulfilled(true); + return webdriver.promise.when(true); } }, hiddenElement: { isDisplayed: function() { - return webdriver.promise.fulfilled(false); + return webdriver.promise.when(false); } } }; diff --git a/spec/errorSpec.js b/spec/errorSpec.js index 8f3ad88..bdee761 100644 --- a/spec/errorSpec.js +++ b/spec/errorSpec.js @@ -104,6 +104,12 @@ describe('things that should fail', function() { }); describe('native promises', function() { + it('should time out if done argument is never called, even if promise is returned', + function(done) { + return new Promise(function() {}); + } + ); + var testADone = false; it('should handle rejection from native promise', function() { diff --git a/spec/maybePromiseSpec.js b/spec/maybePromiseSpec.js new file mode 100644 index 0000000..57d173f --- /dev/null +++ b/spec/maybePromiseSpec.js @@ -0,0 +1,155 @@ +var maybePromise = require('../maybePromise.js'); +var webdriver = require('selenium-webdriver'); + +describe('maybePromise', function() { + // Helper values + var num = 588.79; // From Math.random() + var str = 'qqqpqc0'; // From math.random().toString(36); + var obj = { num: num, str: str, obj: obj, then: true }; + function idFun(x) { return x; } + function promiseMe(x) { + var promise = { then: function(callback) { return callback(x); } }; + spyOn(promise, 'then').and.callThrough(); + return promise; + } + + describe('singletons', function() { + it('should be able to use non-promises', function(done) { + maybePromise(num, function(n) { + expect(n).toBe(num); + done(); + }); + }); + + it('should not wrap non-promise values', function() { + expect(maybePromise(num, idFun)).toBe(num); + expect(maybePromise(str, idFun)).toBe(str); + expect(maybePromise(obj, idFun)).toBe(obj); + }); + + it('should be able to use promises', function(done) { + maybePromise(promiseMe(str), function(s) { + expect(s).toBe(str); + done(); + }); + }); + + it('should use a promise\'s own then() function without any wrapping', function() { + var promise = promiseMe(num); + var callback = jasmine.createSpy('callback', idFun).and.callThrough(); + expect(maybePromise(promise, callback)).toBe(num); + expect(promise.then).toHaveBeenCalled(); + expect(callback).toHaveBeenCalled(); + + promise = promiseMe(str); + callback.calls.reset(); + expect(maybePromise(promise, callback)).toBe(str); + expect(promise.then).toHaveBeenCalled(); + expect(callback).toHaveBeenCalled(); + + promise = promiseMe(obj); + callback.calls.reset(); + expect(maybePromise(promise, callback)).toBe(obj); + expect(promise.then).toHaveBeenCalled(); + expect(callback).toHaveBeenCalled(); + }); + + it('should work with a real promise implementation', function(done) { + var promise = webdriver.promise.when(str); + maybePromise(promise, function(s) { + expect(s).toBe(str); + return webdriver.promise.when(num); + }).then(function(n) { + expect(n).toEqual(num); + done(); + }); + }); + + it('should fail in an expected way with poorly implemented promises', function() { + var badPromise = promiseMe(obj); + badPromise.then.and.stub(); + badPromise.then.and.returnValue(str); + var callback = jasmine.createSpy('callback'); + var ret = maybePromise(badPromise, callback); + expect(badPromise.then).toHaveBeenCalled(); + expect(callback).not.toHaveBeenCalled(); + expect(ret).toBe(str); + }); + }); + + describe('.all', function() { + it('should work with an empty array, without wrapping', function() { + var callback = jasmine.createSpy('callback', idFun).and.callThrough(); + expect(maybePromise.all([], callback)).toEqual([]); + expect(callback).toHaveBeenCalled(); + }); + + it('should work with an array of non-promises', function(done) { + var arr = [num, str, obj]; + maybePromise.all(arr, function(a) { + expect(a).toEqual(arr); + done(); + }); + }); + + it('should not wrap non-promise values', function() { + var arr = [num, str, obj]; + expect(maybePromise.all(arr, idFun)).toEqual(arr); + }); + + it('should work with array of promises', function(done) { + var arr = [num, str, obj]; + maybePromise.all(arr.map(promiseMe), function(a) { + expect(a).toEqual(arr); + done(); + }); + }); + + it('should use promise\'s own then() function without any wrapping', function() { + var arr = [num, str, obj]; + var promiseArr = arr.map(promiseMe); + var callback = jasmine.createSpy('callback', idFun).and.callThrough(); + expect(maybePromise.all(promiseArr, callback)).toEqual(arr); + expect(callback.calls.count()).toBe(1); + for (var i = 0; i < promiseArr.length; i++) { + expect(promiseArr[i].then.calls.count()).toBe(1); + } + }); + + it('should work with a real promise implementation', function(done) { + var arr = [str, obj]; + maybePromise.all(arr.map(webdriver.promise.when), function(a) { + expect(a).toEqual(arr); + return webdriver.promise.when(num); + }).then(function(n) { + expect(n).toEqual(num); + done(); + }); + }); + + it('should work with a mix of promises and non-promises', function(done) { + var arr = [num, promiseMe(str), webdriver.promise.when(obj), + webdriver.promise.when(str), webdriver.promise.when(num), + str, promiseMe(num), obj, promiseMe(obj)]; // Random order + maybePromise.all(arr, function(resolved) { + maybePromise(webdriver.promise.all(arr), function(wdResolved) { + expect(resolved).toEqual(wdResolved); + done(); + }); + }); + }); + + it('should fail in an expected way with poorly implemented promises', function() { + var arr = [num, promiseMe(str), str, promiseMe(num), obj, promiseMe(obj)]; // Random order + var badPromise = promiseMe(obj); + badPromise.then.and.stub(); + badPromise.then.and.returnValue(str); + arr.push(badPromise); + var callback = jasmine.createSpy('callback'); + var ret = maybePromise.all(arr, callback); + expect(badPromise.then).toHaveBeenCalled(); + expect(callback).not.toHaveBeenCalled(); + expect(ret).toBe(str); + }); + }); +}); diff --git a/spec/support/failing_specs.json b/spec/support/failing_specs.json index 4545241..03954c5 100644 --- a/spec/support/failing_specs.json +++ b/spec/support/failing_specs.json @@ -1,6 +1,6 @@ { - "spec_dir": "spec", + "spec_dir": "built_spec", "spec_files": [ - "errorSpec.js" + "*rrorSpec.js" ] } diff --git a/spec/support/lib_specs.json b/spec/support/lib_specs.json new file mode 100644 index 0000000..91e9cf4 --- /dev/null +++ b/spec/support/lib_specs.json @@ -0,0 +1,6 @@ +{ + "spec_dir": "built_spec", + "spec_files": [ + "maybePromiseSpec.js" + ] +} diff --git a/spec/support/no_cf_failing_specs.json b/spec/support/no_cf_failing_specs.json new file mode 100644 index 0000000..a3ef0b4 --- /dev/null +++ b/spec/support/no_cf_failing_specs.json @@ -0,0 +1,6 @@ +{ + "spec_dir": "built_spec", + "spec_files": [ + "asyncAwaitErrorSpec.js" + ] +} diff --git a/spec/support/no_cf_passing_specs.json b/spec/support/no_cf_passing_specs.json new file mode 100644 index 0000000..ec49d46 --- /dev/null +++ b/spec/support/no_cf_passing_specs.json @@ -0,0 +1,6 @@ +{ + "spec_dir": "built_spec", + "spec_files": [ + "asyncAwaitAdapterSpec.js" + ] +} diff --git a/spec/support/passing_specs.json b/spec/support/passing_specs.json index ced6d64..3c91a6e 100644 --- a/spec/support/passing_specs.json +++ b/spec/support/passing_specs.json @@ -1,7 +1,6 @@ { - "spec_dir": "spec", + "spec_dir": "built_spec", "spec_files": [ - "adapterSpec.js", - "asyncAwaitSpec.js" + "*dapterSpec.js" ] } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..9d274dd --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2015", + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": false, + "declaration": false, + "removeComments": false, + "noImplicitAny": false, + "outDir": "built_spec" + }, + "include": [ + "spec/*.ts" + ] +} diff --git a/tslint.json b/tslint.json new file mode 100644 index 0000000..5935ad9 --- /dev/null +++ b/tslint.json @@ -0,0 +1,15 @@ +{ + "rulesDirectory": [ + "node_modules/vrsource-tslint-rules/rules", + "node_modules/tslint-eslint-rules/dist/rules" + ], + "rules": { + "no-duplicate-imports": true, + "no-duplicate-variable": true, + "no-jasmine-focus": true, + "no-var-keyword": true, + "semicolon": [true], + "variable-name": [true, "ban-keywords"], + "no-inner-declarations": [true, "function"] + } +} From 171cbde22f307bd3cc35c4c1785f171392dca8da Mon Sep 17 00:00:00 2001 From: Sammy Jelin Date: Fri, 13 Jan 2017 15:17:04 -0800 Subject: [PATCH 46/55] feat(typescript): add types (#79) --- README.md | 18 +++++++++ package.json | 1 + spec/@types_jasminewd2.d.ts | 73 ++++++++++++++++++++++++++++++++++ spec/asyncAwaitAdapterSpec.ts | 16 ++------ spec/asyncAwaitErrorSpec.ts | 16 ++------ spec/{common.js => common.ts} | 75 ++++++++++++++++++++--------------- tsconfig.json | 2 +- 7 files changed, 142 insertions(+), 59 deletions(-) create mode 100644 spec/@types_jasminewd2.d.ts rename spec/{common.js => common.ts} (51%) diff --git a/README.md b/README.md index a27d4ed..87f461f 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,24 @@ describe('tests with webdriver', function() { }) ``` +TypeScript +---------- + +For the typings related to the changes in the global jasmine variables (e.g. +allowing `it()` blocks to return a promise), we publish the package +`@types/jasminewd2`. If you are writing tests using jasminewd (including +Protractor tests), be sure to include `@types/jasminewd2` in your +`devDependencies`, as these global type modifications are ***not*** bundled with +the `jasminewd2` npm module. + +jasminewd also exports one function directly: `init`. Unfortunately, we do not +publish typings for this function. If you call this function directly (e.g. you +are a Protractor dev), you should simply do: + +```ts +require('jasminewd2').init(controlFlow); +``` + `async` functions / `await` --------------------------- diff --git a/package.json b/package.json index 79c7bc3..d6ed1a1 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "selenium-webdriver": "3.0.1" }, "devDependencies": { + "@types/jasmine": "^2.5.40", "@types/node": "^6.0.56", "@types/selenium-webdriver": "^2.53.38", "jasmine": "2.4.1", diff --git a/spec/@types_jasminewd2.d.ts b/spec/@types_jasminewd2.d.ts new file mode 100644 index 0000000..dc584b6 --- /dev/null +++ b/spec/@types_jasminewd2.d.ts @@ -0,0 +1,73 @@ +// This is jasminewd's internal version of @types/jasminewd2. If you need types +// for jasminewd2, please use @types/jasminewd2 instead + +// Type definitions for jasminewd2 +// Project https://github.com/angular/jasminewd +// Definitions by: Sammy Jelin + +declare function it(expectation: string, assertion?: () => Promise, timeout?: number): void; +declare function fit(expectation: string, assertion?: () => Promise, timeout?: number): void; +declare function xit(expectation: string, assertion?: () => Promise, timeout?: number): void; +declare function beforeEach(action: () => Promise, timeout?: number): void; +declare function afterEach(action: () => Promise, timeout?: number): void; +declare function beforeAll(action: () => Promise, timeout?: number): void; +declare function afterAll(action: () => Promise, timeout?: number): void; + +declare namespace jasmine { + // The global `Promise` type is too strict and kinda wrong + interface Promise { + then(onFulfill?: (value: T) => U | Promise, onReject?: (error: any) => U | Promise): Promise; + } + + interface Matchers { + toBe(expected: any, expectationFailOutput?: any): Promise; + toEqual(expected: any, expectationFailOutput?: any): Promise; + toMatch(expected: string | RegExp | Promise, expectationFailOutput?: any): Promise; + toBeDefined(expectationFailOutput?: any): Promise; + toBeUndefined(expectationFailOutput?: any): Promise; + toBeNull(expectationFailOutput?: any): Promise; + toBeNaN(): Promise; + toBeTruthy(expectationFailOutput?: any): Promise; + toBeFalsy(expectationFailOutput?: any): Promise; + toHaveBeenCalled(): Promise; + toHaveBeenCalledWith(...params: any[]): Promise; + toHaveBeenCalledTimes(expected: number | Promise): Promise; + toContain(expected: any, expectationFailOutput?: any): Promise; + toBeLessThan(expected: number | Promise, expectationFailOutput?: any): Promise; + toBeLessThanOrEqual(expected: number | Promise, expectationFailOutput?: any): Promise; + toBeGreaterThan(expected: number | Promise, expectationFailOutput?: any): Promise; + toBeGreaterThanOrEqual(expected: number | Promise, expectationFailOutput?: any): Promise; + toBeCloseTo(expected: number | Promise, precision?: any, expectationFailOutput?: any): Promise; + toThrow(expected?: any): Promise; + toThrowError(message?: string | RegExp | Promise): Promise; + toThrowError(expected?: new (...args: any[]) => Error | Promise Error>, message?: string | RegExp | Promise): Promise; + } + + function addMatchers(matchers: AsyncCustomMatcherFactories): void; + + interface Env { + addMatchers(matchers: AsyncCustomMatcherFactories): void; + } + + interface Spec { + addMatchers(matchers: AsyncCustomMatcherFactories): void; + } + + interface AsyncCustomMatcherFactories { + [index: string]: AsyncCustomMatcherFactory; + } + + interface AsyncCustomMatcherFactory { + (util: MatchersUtil, customEqualityTesters: Array): AsyncCustomMatcher; + } + + interface AsyncCustomMatcher { + compare(actual: T, expected: T): AsyncCustomMatcherResult; + compare(actual: any, expected: any): AsyncCustomMatcherResult; + } + + interface AsyncCustomMatcherResult { + pass: boolean | Promise; + message?: string; + } +} diff --git a/spec/asyncAwaitAdapterSpec.ts b/spec/asyncAwaitAdapterSpec.ts index 6c095c8..54f177f 100644 --- a/spec/asyncAwaitAdapterSpec.ts +++ b/spec/asyncAwaitAdapterSpec.ts @@ -1,15 +1,5 @@ import {promise as wdpromise, WebElement} from 'selenium-webdriver'; -const common = require('./common'); - -declare function expect(actual: any): any; -declare function describe(description: string, tests: Function): void; -declare function it(description: string, test?: Function, timeout?: number): any; -declare function xit(description: string, test?: Function, timeout?: number): any; -declare function beforeEach(setup: Function): void; -declare function beforeAll(setup: Function): void; -declare function afterEach(setup: Function): void; -declare function afterAll(setup: Function): void; -declare var jasmine; +import {getFakeDriver, getMatchers} from './common.js'; /** * This file is very similar to adapterSpec.ts, but we use async/await instead @@ -17,7 +7,7 @@ declare var jasmine; * to work regardless of if the WebDriver Control Flow is disabled. */ -const fakeDriver = common.getFakeDriver(); +const fakeDriver = getFakeDriver(); /* jshint esversion: 6 */ describe('webdriverJS Jasmine adapter plain', function() { @@ -54,7 +44,7 @@ describe('webdriverJS Jasmine adapter', function() { let beforeEachMsg: string; beforeEach(function() { - jasmine.addMatchers(common.getMatchers()); + jasmine.addMatchers(getMatchers()); }); beforeEach(async function() { diff --git a/spec/asyncAwaitErrorSpec.ts b/spec/asyncAwaitErrorSpec.ts index 3a70155..37adebc 100644 --- a/spec/asyncAwaitErrorSpec.ts +++ b/spec/asyncAwaitErrorSpec.ts @@ -1,14 +1,4 @@ -const common = require('./common'); - -declare function expect(actual: any): any; -declare function describe(description: string, tests: Function): void; -declare function it(description: string, test?: Function, timeout?: number): any; -declare function xit(description: string, test?: Function, timeout?: number): any; -declare function beforeEach(setup: Function): void; -declare function beforeAll(setup: Function): void; -declare function afterEach(setup: Function): void; -declare function afterAll(setup: Function): void; -declare var jasmine; +import {getFakeDriver, getMatchers} from './common.js'; /** * This file is very similar to errorSpec.ts, but we use async/await instead of @@ -16,7 +6,7 @@ declare var jasmine; * work regardless of if the WebDriver Control Flow is disabled. */ -const fakeDriver = common.getFakeDriver(); +const fakeDriver = getFakeDriver(); /* jshint esversion: 6 */ describe('Timeout cases', function() { @@ -38,7 +28,7 @@ describe('Timeout cases', function() { describe('things that should fail', function() { beforeEach(function() { - jasmine.addMatchers(common.getMatchers()); + jasmine.addMatchers(getMatchers()); }); it('should pass errors from done callback', function(done) { diff --git a/spec/common.js b/spec/common.ts similarity index 51% rename from spec/common.js rename to spec/common.ts index 4ab4964..45640c2 100644 --- a/spec/common.js +++ b/spec/common.ts @@ -1,110 +1,110 @@ -var webdriver = require('selenium-webdriver'); +import {promise as wdpromise, WebElement} from 'selenium-webdriver'; -var flow = webdriver.promise.controlFlow(); +const flow = wdpromise.controlFlow(); require('../index.js').init(flow); -exports.getFakeDriver = function() { +export function getFakeDriver() { return { controlFlow: function() { return flow; }, - sleep: function(ms) { + sleep: function(ms: number) { return flow.timeout(ms); }, setUp: function() { return flow.execute(function() { - return webdriver.promise.when('setup done'); + return wdpromise.when('setup done'); }, 'setUp'); }, getValueA: function() { return flow.execute(function() { - return webdriver.promise.delayed(500).then(function() { - return webdriver.promise.when('a'); + return wdpromise.delayed(500).then(function() { + return wdpromise.when('a'); }); }, 'getValueA'); }, getOtherValueA: function() { return flow.execute(function() { - return webdriver.promise.when('a'); + return wdpromise.when('a'); }, 'getOtherValueA'); }, getValueB: function() { return flow.execute(function() { - return webdriver.promise.when('b'); + return wdpromise.when('b'); }, 'getValueB'); }, - getBigNumber: function() { + getBigNumber: function(): wdpromise.Promise { return flow.execute(function() { - return webdriver.promise.when(1111); + return wdpromise.when(1111); }, 'getBigNumber'); }, - getSmallNumber: function() { + getSmallNumber: function(): wdpromise.Promise { return flow.execute(function() { - return webdriver.promise.when(11); + return wdpromise.when(11); }, 'getSmallNumber'); }, - getDecimalNumber: function() { + getDecimalNumber: function(): wdpromise.Promise { return flow.execute(function() { - return webdriver.promise.when(3.14159); + return wdpromise.when(3.14159); }, 'getDecimalNumber'); }, getDisplayedElement: function() { return flow.execute(function() { - return webdriver.promise.when({ + return wdpromise.when({ isDisplayed: function() { - return webdriver.promise.when(true); + return wdpromise.when(true); } }); }, 'getDisplayedElement'); }, getHiddenElement: function() { return flow.execute(function() { - return webdriver.promise.when({ + return wdpromise.when({ isDisplayed: function() { - return webdriver.promise.when(false); + return wdpromise.when(false); } }); }, 'getHiddenElement'); }, - getValueList: function() { + getValueList: function(): wdpromise.Promise wdpromise.Promise}>> { return flow.execute(function() { - return webdriver.promise.when([{ + return wdpromise.when([{ getText: function() { - return flow.execute(function() { return webdriver.promise.when('a');}); + return flow.execute(function() { return wdpromise.when('a');}); } }, { getText: function() { - return flow.execute(function() { return webdriver.promise.when('b');}); + return flow.execute(function() { return wdpromise.when('b');}); } }, { getText: function() { - return flow.execute(function() { return webdriver.promise.when('c');}); + return flow.execute(function() { return wdpromise.when('c');}); } }, { getText: function() { - return flow.execute(function() { return webdriver.promise.when('d');}); + return flow.execute(function() { return wdpromise.when('d');}); } }]); }, 'getValueList'); }, displayedElement: { isDisplayed: function() { - return webdriver.promise.when(true); + return wdpromise.when(true); } }, hiddenElement: { isDisplayed: function() { - return webdriver.promise.when(false); + return wdpromise.when(false); } } }; }; -exports.getMatchers = function() { +export function getMatchers() { return { toBeLotsMoreThan: function() { return { - compare: function(actual, expected) { + compare: function(actual: number, expected: number) { return { pass: actual > expected + 100 }; @@ -114,7 +114,7 @@ exports.getMatchers = function() { // Example custom matcher returning a promise that resolves to true/false. toBeDisplayed: function() { return { - compare: function(actual, expected) { + compare: function(actual: WebElement, expected: void) { return { pass: actual.isDisplayed() }; @@ -124,6 +124,17 @@ exports.getMatchers = function() { }; }; -exports.isPending = function(managedPromise) { - return managedPromise.state_ === 'pending'; +// declare custom matcher types +declare global { + namespace jasmine { + interface Matchers { + toBeLotsMoreThan(expected: number | Promise): Promise; + toBeDisplayed(): Promise; + } + } +} + + +export function isPending(managedPromise: wdpromise.Promise) { + return (managedPromise as any).state_ === 'pending'; }; diff --git a/tsconfig.json b/tsconfig.json index 9d274dd..ec87d58 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,7 @@ "sourceMap": false, "declaration": false, "removeComments": false, - "noImplicitAny": false, + "noImplicitAny": true, "outDir": "built_spec" }, "include": [ From 369a2499189fbcdc541f354cfede49dba9335e6b Mon Sep 17 00:00:00 2001 From: Sammy Jelin Date: Fri, 20 Jan 2017 17:09:47 -0800 Subject: [PATCH 47/55] feat(SELENIUM_PROMISE_MANAGER): Don't rely on `webdriver.promise` functions (#82) While we support `SELENIUM_PROMISE_MANAGER=0` already, we rely on `SimpleScheduler` and some other utility functions which will be going away after the control flow has been fully deprecated. This commit allows jasminewd to work without those utility functions, and even allows people to pass jasminewd their own custom scheduler implementation. This does not fix our tests, which will also break when those utility functions go away. See https://github.com/angular/jasminewd/issues/81 Closes https://github.com/angular/jasminewd/issues/80 --- index.js | 102 +++++++++++++++++++++++++-------------- maybePromise.js | 16 +++++- scheduler.md | 68 ++++++++++++++++++++++++++ scripts/test.sh | 22 ++++++++- spec/common.ts | 2 +- spec/maybePromiseSpec.js | 11 +++++ 6 files changed, 182 insertions(+), 39 deletions(-) create mode 100644 scheduler.md diff --git a/index.js b/index.js index 5875a0f..d8dfca9 100644 --- a/index.js +++ b/index.js @@ -49,31 +49,42 @@ function validateString(stringtoValidate) { } } +var idleEventName = 'idle'; +try { + idleEventName = webdriver.promise.ControlFlow.EventType.IDLE; +} catch(e) {} + /** - * Calls a function once the control flow is idle - * @param {webdriver.promise.ControlFlow} flow The Web Driver control flow - * @param {!Function} fn The function to call + * Calls a function once the scheduler is idle. If the scheduler does not support the idle API, + * calls the function immediately. See scheduler.md#idle-api for details. + * + * @param {Object} scheduler The scheduler to wait for. + * @param {!Function} fn The function to call. */ -function callWhenIdle(flow, fn) { - if (!flow.isIdle || flow.isIdle()) { +function callWhenIdle(scheduler, fn) { + if (!scheduler.once || !scheduler.isIdle || scheduler.isIdle()) { fn(); } else { - flow.once(webdriver.promise.ControlFlow.EventType.IDLE, function() { - fn(); - }); + scheduler.once(idleEventName, function() { fn(); }); } } /** - * Wraps a function so it runs inside a webdriver.promise.ControlFlow and - * waits for the flow to complete before continuing. - * @param {!webdriver.promise.ControlFlow} flow The WebDriver control flow. + * Wraps a function so it runs inside a scheduler's `execute()` block. + * + * In the most common case, this means wrapping in a `webdriver.promise.ControlFlow` instance + * to wait for the control flow to complete one task before starting the next. See scheduler.md + * for details. + * + * @param {!Object} scheduler See scheduler.md for details. + * @param {!Function} newPromise Makes a new promise using whatever implementation the scheduler + * prefers. * @param {!Function} globalFn The function to wrap. * @param {!string} fnName The name of the function being wrapped (e.g. `'it'`). * @return {!Function} The new function. */ -function wrapInControlFlow(flow, globalFn, fnName) { +function wrapInScheduler(scheduler, newPromise, globalFn, fnName) { return function() { var driverError = new Error(); driverError.stack = driverError.stack.replace(/ +at.+jasminewd.+\n/, ''); @@ -84,14 +95,7 @@ function wrapInControlFlow(flow, globalFn, fnName) { var async = fn.length > 0; var testFn = fn.bind(this); - flow.execute(function controlFlowExecute() { - function newPromise(resolver) { - if (typeof flow.promise == 'function') { - return flow.promise(resolver); - } else { - return new webdriver.promise.Promise(resolver, flow); - } - } + scheduler.execute(function schedulerExecute() { return newPromise(function(fulfill, reject) { function wrappedReject(err) { var wrappedErr = new Error(err); @@ -108,7 +112,7 @@ function wrapInControlFlow(flow, globalFn, fnName) { // Without a callback, testFn can return a promise, or it will // be assumed to have completed synchronously. var ret = testFn(); - if (webdriver.promise.isPromise(ret)) { + if (maybePromise.isPromise(ret)) { ret.then(fulfill, wrappedReject); } else { fulfill(ret); @@ -116,13 +120,13 @@ function wrapInControlFlow(flow, globalFn, fnName) { } }); }, 'Run ' + fnName + description + ' in control flow').then( - callWhenIdle.bind(null, flow, done), function(err) { + callWhenIdle.bind(null, scheduler, done), function(err) { if (!err) { err = new Error('Unknown Error'); err.stack = ''; } err.stack = err.stack + '\nFrom asynchronous test: \n' + driverError.stack; - callWhenIdle(flow, done.fail.bind(done, err)); + callWhenIdle(scheduler, done.fail.bind(done, err)); } ); }; @@ -163,25 +167,51 @@ function wrapInControlFlow(flow, globalFn, fnName) { } /** - * Initialize the JasmineWd adapter with a particlar webdriver instance. We - * pass webdriver here instead of using require() in order to ensure Protractor - * and Jasminews are using the same webdriver instance. - * @param {Object} flow. The ControlFlow to wrap tests in. + * Initialize the JasmineWd adapter with a particlar scheduler, generally a webdriver control flow. + * + * @param {Object=} scheduler The scheduler to wrap tests in. See scheduler.md for details. + * Defaults to a mock scheduler that calls functions immediately. */ -function initJasmineWd(flow) { +function initJasmineWd(scheduler) { if (jasmine.JasmineWdInitialized) { throw Error('JasmineWd already initialized when init() was called'); } jasmine.JasmineWdInitialized = true; - global.it = wrapInControlFlow(flow, global.it, 'it'); - global.fit = wrapInControlFlow(flow, global.fit, 'fit'); - global.beforeEach = wrapInControlFlow(flow, global.beforeEach, 'beforeEach'); - global.afterEach = wrapInControlFlow(flow, global.afterEach, 'afterEach'); - global.beforeAll = wrapInControlFlow(flow, global.beforeAll, 'beforeAll'); - global.afterAll = wrapInControlFlow(flow, global.afterAll, 'afterAll'); - if (flow.reset) { + // Default to mock scheduler + if (!scheduler) { + scheduler = { execute: function(fn) { + return Promise.resolve().then(fn); + } }; + } + + // Figure out how we're getting new promises + var newPromise; + if (typeof scheduler.promise == 'function') { + newPromise = scheduler.promise.bind(scheduler); + } else if (webdriver.promise && webdriver.promise.ControlFlow && + (scheduler instanceof webdriver.promise.ControlFlow) && + (webdriver.promise.USE_PROMISE_MANAGER !== false)) { + newPromise = function(resolver) { + return new webdriver.promise.Promise(resolver, scheduler); + }; + } else { + newPromise = function(resolver) { + return new Promise(resolver); + }; + } + + // Wrap functions + global.it = wrapInScheduler(scheduler, newPromise, global.it, 'it'); + global.fit = wrapInScheduler(scheduler, newPromise, global.fit, 'fit'); + global.beforeEach = wrapInScheduler(scheduler, newPromise, global.beforeEach, 'beforeEach'); + global.afterEach = wrapInScheduler(scheduler, newPromise, global.afterEach, 'afterEach'); + global.beforeAll = wrapInScheduler(scheduler, newPromise, global.beforeAll, 'beforeAll'); + global.afterAll = wrapInScheduler(scheduler, newPromise, global.afterAll, 'afterAll'); + + // Reset API + if (scheduler.reset) { // On timeout, the flow should be reset. This will prevent webdriver tasks // from overflowing into the next test and causing it to fail or timeout // as well. This is done in the reporter instead of an afterEach block @@ -189,7 +219,7 @@ function initJasmineWd(flow) { // get to complete first. jasmine.getEnv().addReporter(new OnTimeoutReporter(function() { console.warn('A Jasmine spec timed out. Resetting the WebDriver Control Flow.'); - flow.reset(); + scheduler.reset(); })); } } diff --git a/maybePromise.js b/maybePromise.js index 5a125fe..2bac170 100644 --- a/maybePromise.js +++ b/maybePromise.js @@ -8,6 +8,18 @@ * or may not be promises, and code execution may or may not be synchronous. */ + +/** + * Determines if a value is a promise. + * + * @param {*} val The value to check. + * @return {boolean} true if val is a promise, false otherwise. + */ +function isPromise(val) { + return val && (typeof val.then == 'function'); +} + + /** * Runs a callback synchronously against non-promise values and asynchronously * against promises. Similar to ES6's `Promise.resolve` except that it is @@ -25,13 +37,15 @@ * resolving to the callback's return value is returned. */ var maybePromise = module.exports = function maybePromise(val, callback) { - if (val && (typeof val.then == 'function')) { + if (isPromise(val)) { return val.then(callback); } else { return callback(val); } } +maybePromise.isPromise = isPromise; + /** * Like maybePromise() but for an array of values. Analogous to `Promise.all`. * diff --git a/scheduler.md b/scheduler.md new file mode 100644 index 0000000..b30e2fc --- /dev/null +++ b/scheduler.md @@ -0,0 +1,68 @@ +# Schedulers + +Many of the core features of jasminewd are centered around automatically synchronizing your tests +with the WebDriver control flow. However, jasminewd can synchronize with any scheduler as long as +it implements the following interface: + +```ts +interface Scheduler { + execute(fn: () => Promise|T): Promise; +} +``` + +Where `execute` is the function used to put something on the scheduler. As long as your scheduler +implements this interface, you can pass it into `require('jasminewd2').init`. + +## Custom Promise Implementation + +Some schedulers need scheduled functions to use a specific implementation of the promise API. For +instance, WebDriver has its `ManagedPromise` implementation, which it needs in order to track +tasks across `then()` blocks. If your scheduler has its own promise implementation, you can +implement the following interface: + +```ts +interface SchedulerWithCustomPromises { + execute(fn: () => CustomPromise|T): CustomPromise; + promise(resolver: (resolve: (T) => void, reject: (any) => void) => void): CustomPromise; +} +``` + +If the `promise` function is specified, jasminewd will use that function to generate all of its +internal promises. If `scheduler.promise` is not specified, jasminewd will try to use WebDriver's +`ManagedPromise`. If `ManagedPromise` is not available (e.g. the control flow is disabled), +jasminewd will default to using native promises. + +### Idle API + +If your scheduler requires a custom promise implementation, it is highly recommended that you +implement the Idle API. This will help to mitigate issues with users who sometimes use other +promise implementations (see https://github.com/angular/jasminewd/issues/68#issuecomment-262317167). +To do this, implement the following interface: + +```ts +var EventEmitter = require('events'); + +interface SchedulerWithIdleAPI extends EventEmitter { + execute(fn: () => CustomPromise|T): CustomPromise; + promise(resolver: (resolve: (T) => void, reject: (any) => void) => void): CustomPromise; + isIdle(): boolean; +} +``` + +Your scheduler must emit `"idle"` when it becomes idle. + + +### Reset API + +If you want your scheduler to be reset whenever a spec times out, implement the following interface: + +```ts +interface SchedulerWithResetAPI { + execute(fn: () => CustomPromise|T): CustomPromise; + reset(): void; +} +``` + +jasminewd will automatically look for a `reset` function and call it when specs time out. This is +useful so that tasks from a timed out spec get cleared instead of continuing to tie up the scheduler +and potentially getting executed during future specs. diff --git a/scripts/test.sh b/scripts/test.sh index d11a5d1..bb80af6 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -39,7 +39,27 @@ echo "result: $results_line" export SELENIUM_PROMISE_MANAGER=0 -echo "### running async/await passing specs" +echo "### running async/await passing specs with control flow disabled" +CMD=$CMD_BASE$NO_CF_PASSING_SPECS +echo "### $CMD" +$CMD +[ "$?" -eq 0 ] || exit 1 +echo + +EXPECTED_RESULTS="19 specs, 17 failures" +echo "### running async/await failing specs (expecting $EXPECTED_RESULTS)" +CMD=$CMD_BASE$NO_CF_FAILING_SPECS +echo "### $CMD" +res=`$CMD 2>/dev/null` +results_line=`echo "$res" | tail -2 | head -1` +echo "result: $results_line" +[ "$results_line" = "$EXPECTED_RESULTS" ] || exit 1 + +# Run only the async/await tests with no scheduler + +export JASMINEWD_TESTS_NO_SCHEDULER=1 + +echo "### running async/await passing specs with no scheduler" CMD=$CMD_BASE$NO_CF_PASSING_SPECS echo "### $CMD" $CMD diff --git a/spec/common.ts b/spec/common.ts index 45640c2..39e84f2 100644 --- a/spec/common.ts +++ b/spec/common.ts @@ -1,7 +1,7 @@ import {promise as wdpromise, WebElement} from 'selenium-webdriver'; const flow = wdpromise.controlFlow(); -require('../index.js').init(flow); +require('../index.js').init(process.env['JASMINEWD_TESTS_NO_SCHEDULER'] ? null : flow); export function getFakeDriver() { return { diff --git a/spec/maybePromiseSpec.js b/spec/maybePromiseSpec.js index 57d173f..1503530 100644 --- a/spec/maybePromiseSpec.js +++ b/spec/maybePromiseSpec.js @@ -13,6 +13,17 @@ describe('maybePromise', function() { return promise; } + it('should be able to tell promises from non-promises', function() { + expect(maybePromise.isPromise(num)).toBe(false); + expect(maybePromise.isPromise(str)).toBe(false); + expect(maybePromise.isPromise(obj)).toBe(false); + expect(maybePromise.isPromise(idFun)).toBe(false); + expect(maybePromise.isPromise(promiseMe(num))).toBe(true); + expect(maybePromise.isPromise(promiseMe(str))).toBe(true); + expect(maybePromise.isPromise(promiseMe(obj))).toBe(true); + expect(maybePromise.isPromise(promiseMe(idFun))).toBe(true); + }); + describe('singletons', function() { it('should be able to use non-promises', function(done) { maybePromise(num, function(n) { From fae803cd294e5413523d37bdaa282a9f96cd65a1 Mon Sep 17 00:00:00 2001 From: Sammy Jelin Date: Fri, 20 Jan 2017 17:51:48 -0800 Subject: [PATCH 48/55] feat(init): ***breaking change*** pass webdriver instance into `init()` instead of using `require()` (#83) So where as before you would write: ```js require('jasminewd').init(webdriver.promise.controlFlow()); ``` Now you will write: ```js require('jasminewd').init(webdriver.promise.controlFlow(), webdriver); ``` This removes the dependency on `selenium-webdriver` and protects jasminewd from having a different webdriver instance than Protractor, which could be a huge problem if they had different control flow settings. This is a breaking change because it changes the API for the `init` function. I also removed the dependency on jasmine, which didn't do anything anyway. Maybe it should have been a peerDependency but those are deprecated. --- README.md | 13 ++++++++----- index.js | 30 +++++++++++++++++++++--------- package.json | 6 +----- spec/common.ts | 3 ++- 4 files changed, 32 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 87f461f..a650670 100644 --- a/README.md +++ b/README.md @@ -28,24 +28,27 @@ npm install jasminewd2 Usage ----- -Assumes selenium-webdriver as a peer dependency. +In your setup: ```js -// In your setup. var JasmineRunner = require('jasmine'); var jrunner = new JasmineRunner(); -require('jasminewd2'); +var webdriver = require('selenium-webdriver'); global.driver = new webdriver.Builder(). usingServer('http://localhost:4444/wd/hub'). withCapabilities({browserName: 'chrome'}). build(); +require('jasminewd2').init(driver.controlFlow(), webdriver); + jrunner.projectBaseDir = ''; jrunner.execute(['**/*_spec.js']); +``` -// In your tests +In your tests: +```js describe('tests with webdriver', function() { it('will wait until webdriver is done', function() { // This will be an asynchronous test. It will finish once webdriver has @@ -76,7 +79,7 @@ publish typings for this function. If you call this function directly (e.g. you are a Protractor dev), you should simply do: ```ts -require('jasminewd2').init(controlFlow); +require('jasminewd2').init(controlFlow, webdriver); ``` `async` functions / `await` diff --git a/index.js b/index.js index d8dfca9..3fa0fb0 100644 --- a/index.js +++ b/index.js @@ -4,7 +4,8 @@ * https://code.google.com/p/selenium/source/browse/javascript/node/selenium-webdriver/testing/index.js */ -var webdriver = require('selenium-webdriver'); +var WebElement; // Equal to webdriver.WebElement +var idleEventName = 'idle'; // Equal to webdriver.promise.ControlFlow.EventType.IDLE var maybePromise = require('./maybePromise'); /** @@ -49,11 +50,6 @@ function validateString(stringtoValidate) { } } -var idleEventName = 'idle'; -try { - idleEventName = webdriver.promise.ControlFlow.EventType.IDLE; -} catch(e) {} - /** * Calls a function once the scheduler is idle. If the scheduler does not support the idle API, * calls the function immediately. See scheduler.md#idle-api for details. @@ -171,14 +167,30 @@ function wrapInScheduler(scheduler, newPromise, globalFn, fnName) { * * @param {Object=} scheduler The scheduler to wrap tests in. See scheduler.md for details. * Defaults to a mock scheduler that calls functions immediately. + * @param {Object=} webdriver The result of `require('selenium-webdriver')`. Passed in here rather + * than required by jasminewd directly so that jasminewd can't end up up with a different version + * of `selenium-webdriver` than your tests use. If not specified, jasminewd will still work, but + * it won't check for `WebElement` instances in expect() statements and could cause control flow + * problems if your tests are using an old version of `selenium-webdriver` (e.g. version 2.53.0). */ -function initJasmineWd(scheduler) { +function initJasmineWd(scheduler, webdriver) { if (jasmine.JasmineWdInitialized) { throw Error('JasmineWd already initialized when init() was called'); } jasmine.JasmineWdInitialized = true; + // Pull information from webdriver instance + if (webdriver) { + WebElement = webdriver.WebElement || WebElement; + idleEventName = ( + webdriver.promise && + webdriver.promise.ControlFlow && + webdriver.promise.ControlFlow.EventType && + webdriver.promise.ControlFlow.EventType.IDLE + ) || idleEventname; + } + // Default to mock scheduler if (!scheduler) { scheduler = { execute: function(fn) { @@ -190,7 +202,7 @@ function initJasmineWd(scheduler) { var newPromise; if (typeof scheduler.promise == 'function') { newPromise = scheduler.promise.bind(scheduler); - } else if (webdriver.promise && webdriver.promise.ControlFlow && + } else if (webdriver && webdriver.promise && webdriver.promise.ControlFlow && (scheduler instanceof webdriver.promise.ControlFlow) && (webdriver.promise.USE_PROMISE_MANAGER !== false)) { newPromise = function(resolver) { @@ -226,7 +238,7 @@ function initJasmineWd(scheduler) { var originalExpect = global.expect; global.expect = function(actual) { - if (actual instanceof webdriver.WebElement) { + if (WebElement && (actual instanceof WebElement)) { throw Error('expect called with WebElement argument, expected a Promise. ' + 'Did you mean to use .getText()?'); } diff --git a/package.json b/package.json index d6ed1a1..a18b717 100644 --- a/package.json +++ b/package.json @@ -11,17 +11,13 @@ "jasmine" ], "author": "Julie Ralph ", - "dependencies": { - "jasmine": "2.4.1", - "selenium-webdriver": "3.0.1" - }, "devDependencies": { "@types/jasmine": "^2.5.40", "@types/node": "^6.0.56", "@types/selenium-webdriver": "^2.53.38", "jasmine": "2.4.1", "jshint": "^2.9.4", - "selenium-webdriver": "2.53.3", + "selenium-webdriver": "3.0.1", "tslint": "^4.2.0", "tslint-eslint-rules": "^3.2.3", "typescript": "^2.0.10", diff --git a/spec/common.ts b/spec/common.ts index 39e84f2..b99f142 100644 --- a/spec/common.ts +++ b/spec/common.ts @@ -1,7 +1,8 @@ import {promise as wdpromise, WebElement} from 'selenium-webdriver'; const flow = wdpromise.controlFlow(); -require('../index.js').init(process.env['JASMINEWD_TESTS_NO_SCHEDULER'] ? null : flow); +require('../index.js').init(process.env['JASMINEWD_TESTS_NO_SCHEDULER'] ? null : flow, + require('selenium-webdriver')); export function getFakeDriver() { return { From a4ae33c049870fdb6b8744e3718d15c60087e889 Mon Sep 17 00:00:00 2001 From: Sammy Jelin Date: Fri, 20 Jan 2017 18:11:24 -0800 Subject: [PATCH 49/55] chore(release): version bump and changelog update --- CHANGELOG.md | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b978ea..4ccd481 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,91 @@ # Changelog for jasminewd2 +# 1.0.0 + +## Breaking changes + +- ([fae803c](https://github.com/angular/protractor/commit/fae803cd294e5413523d37bdaa282a9f96cd65a1)) + pass webdriver instance into `init()` instead of using `require()` (#83) + + So where as before you would write: + + ```js + require('jasminewd').init(webdriver.promise.controlFlow()); + ``` + Now you will write: + + ```js + require('jasminewd').init(webdriver.promise.controlFlow(), webdriver); + ``` + + This removes the dependency on `selenium-webdriver` and protects jasminewd from having a + different webdriver instance than Protractor, which could be a huge problem if they had different + control flow settings. + + This is a breaking change because it changes the API for the `init` function. + + I also removed the dependency on jasmine, which didn't do anything anyway. Maybe it should have + been a peerDependency but those are deprecated. + + +## Features + +- ([171cbde](https://github.com/angular/protractor/commit/171cbde22f307bd3cc35c4c1785f171392dca8da)) + Added types (though you'll have to wait for `@types/jasminewd2` to use them) (#79) + + +- ([27b4850](https://github.com/angular/protractor/commit/27b485019589cd662ee69e7920893ffa50774b97)) + Support `SELENIUM_PROMISE_MANAGER=0` (#72) + + There are three major ways this was done in this change: + * In `callWhenIdle`, if `flow.isIdle` is not defined, we assume we are working with a + `SimpleScheduler` instance, and so the flow is effectively idle. + * In `initJasmineWd`, if `flow.reset` is not defined, we assume we are working with a + `SimpleScheduler` instance, and so don't bother resetting the flow. + * In `wrapInControlFlow`, we use `flow.promise` to create a new promise if possible. Since + `new webdriver.promise.Promise()` would have always made a `ManagedPromise`, but `flow.promise` + will do the right thing. + * In `wrapCompare`, we avoid the webdriver library entirely, and never instance any extra + promises. Using `webdriver.promise.when` and `webdriver.promise.all` could have been a problem + if our instance of `webdriver` had the control flow turned on, but another instance somewhere + did not (or even the same instance, but just at a different point in time). Instead we use the + new `maybePromise` tool, which is a mess but is also exactly what we want. + * In `specs/*`, we replace `webdriver.promise.fulfilled` with `webdriver.promise.when`. + * In `specs/*`, a new version of `adapterSpec.js` and `errorSpec.js` are created: + `asyncAwaitAdapterSpec.ts` and `asyncAwaitErrorSpec.ts`. + + I also also fixed a minor bug where we weren't correctly checking for promises inside an array of + expected results. Before we had: + + ```js + expected = Array.prototype.slice.call(arguments, 0); + + ... + + webdriver.promise.isPromise(expected); + ``` + + I thought about it for a little while, and there's no way that's correct. `expected` is an + `Array`, there's no way it has a `.then` function. + + Closes https://github.com/angular/jasminewd/issues/69 + + +## Bug Fixes + +- ([369a249](https://github.com/angular/protractor/commit/369a2499189fbcdc541f354cfede49dba9335e6b)) + Don't rely on `webdriver.promise` functions (#82) + + While we support `SELENIUM_PROMISE_MANAGER=0` already, we rely on `SimpleScheduler` and some other + utility functions which will be going away after the control flow has been fully deprecated. This + commit allows jasminewd to work without those utility functions, and even allows people to pass + jasminewd their own custom scheduler implementation. + + This does not fix our tests, which will also break when those utility functions go away. See + https://github.com/angular/jasminewd/issues/81 + + Closes https://github.com/angular/jasminewd/issues/80 + # 0.1.1 - ([cf1cd34](https://github.com/angular/jasminewd/commit/cf1cd34a4089b6492160349a10d717c7bcaa2c31)) diff --git a/package.json b/package.json index a18b717..1b63b27 100644 --- a/package.json +++ b/package.json @@ -40,5 +40,5 @@ "engines": { "node": ">= 6.9.x" }, - "version": "0.1.1" + "version": "1.0.0" } From ff79938e5f3d1a2c0ffde989b2221a4054066b4a Mon Sep 17 00:00:00 2001 From: Sammy Jelin Date: Fri, 20 Jan 2017 18:19:39 -0800 Subject: [PATCH 50/55] chore(release): skipping straight to 2.0.0 since 1.0.0 was accidently used in the past --- CHANGELOG.md | 4 +++- package.json | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ccd481..d349b6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog for jasminewd2 -# 1.0.0 +# 2.0.0 + +(Skipping 1.x because `0.0.1` was originally accidently published as `1.0.0`.) ## Breaking changes diff --git a/package.json b/package.json index 1b63b27..4bc7031 100644 --- a/package.json +++ b/package.json @@ -40,5 +40,5 @@ "engines": { "node": ">= 6.9.x" }, - "version": "1.0.0" + "version": "2.0.0" } From 96824d4a3801a28501e1286d83dfafa227f0350f Mon Sep 17 00:00:00 2001 From: Sammy Jelin Date: Fri, 20 Jan 2017 18:38:32 -0800 Subject: [PATCH 51/55] chore(README): I moved `master` to `jasminewd1` (#84) It was confusing having `master` refer to the branch for the old version of jasmine. I'm not moving `jasminewd2` to `master` though because that could make old links very confusing. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a650670..3be4e3e 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -jasminewd [![Build Status](https://travis-ci.org/angular/jasminewd.svg?branch=master)](https://travis-ci.org/angular/jasminewd) +jasminewd2 [![Build Status](https://travis-ci.org/angular/jasminewd.svg?branch=jasminewd2)](https://travis-ci.org/angular/jasminewd) ========= Adapter for Jasmine-to-WebDriverJS. Used by [Protractor](http://www.github.com/angular/protractor). **Important:** There are two active branches of jasminewd. - - [master](https://github.com/angular/jasminewd/tree/master) is an adapter for Jasmine 1.3, and uses the package minijasminenode. It is published to npm as `jasminewd`. + - [jasminewd1](https://github.com/angular/jasminewd/tree/jasminewd1) is an adapter for Jasmine 1.3, and uses the package minijasminenode. It is published to npm as `jasminewd`. - [jasminewd2](https://github.com/angular/jasminewd/tree/jasminewd2) is an adapter for Jasmine 2.x, and uses the package jasmine. It is published to npm as `jasminewd2`. Features From 41577a5e10420d255fb2ec12aa0ea3a8e72f14ca Mon Sep 17 00:00:00 2001 From: Alexey Raspopov Date: Tue, 18 Apr 2017 21:03:00 +0300 Subject: [PATCH 52/55] support native async functions (node 7.6+) (#87) In Node v7, Object.prototype.toString.call(async function(){}) returns '[object AsyncFunction]' This makes the check more robust by using typeof --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 3fa0fb0..191b135 100644 --- a/index.js +++ b/index.js @@ -15,7 +15,7 @@ var maybePromise = require('./maybePromise'); * @return {Object} The original parameter. */ function validateFunction(functionToValidate) { - if (functionToValidate && Object.prototype.toString.call(functionToValidate) === '[object Function]') { + if (functionToValidate && typeof functionToValidate === 'function') { return functionToValidate; } else { throw Error(functionToValidate + ' is not a function'); From 374f4946972673f86e06a011e20fc039bb73e234 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 18 Apr 2017 20:03:21 +0200 Subject: [PATCH 53/55] Allow to specify a function as a custom matcher's message. (#29) In case the matcher assigns a promise to the "pass" property, it wasn't possible to distinguish between positive and negative expectations to form a correct failure message. After this change a function taking an "isNot" argument and returning a failure message can be specified. Since it's called only when the matcher fails, there is enough information to produce that message. --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 191b135..e8c85fa 100644 --- a/index.js +++ b/index.js @@ -294,7 +294,7 @@ jasmine.Expectation.prototype.wrapCompare = function(name, matcherFactory) { message = expectation.util.buildFailureMessage.apply(null, args); } else { if (Object.prototype.toString.apply(result.message) === '[object Function]') { - message = result.message(); + message = result.message(expectation.isNot); } else { message = result.message; } From 0137d3f2ae96ef6d51d00055d64b5a8103ae83d0 Mon Sep 17 00:00:00 2001 From: gustavomick Date: Tue, 18 Apr 2017 15:16:01 -0300 Subject: [PATCH 54/55] minor fix to keep stack from original error (#86) --- index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index e8c85fa..559d816 100644 --- a/index.js +++ b/index.js @@ -94,8 +94,10 @@ function wrapInScheduler(scheduler, newPromise, globalFn, fnName) { scheduler.execute(function schedulerExecute() { return newPromise(function(fulfill, reject) { function wrappedReject(err) { - var wrappedErr = new Error(err); - reject(wrappedErr); + if(err instanceof Error) + reject(err); + else + reject(new Error(err)); } if (async) { // If testFn is async (it expects a done callback), resolve the promise of this From 3280865bc7602143c3e576e6262c5e2c3b0de4b9 Mon Sep 17 00:00:00 2001 From: Vikram Subramanian Date: Tue, 9 May 2017 10:52:57 -0700 Subject: [PATCH 55/55] chore(release): version bump and changelog update --- CHANGELOG.md | 17 +++++++++++++++++ package.json | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d349b6b..a4dad3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog for jasminewd2 +# 2.1.0 + +## Features + +- ([41577a5](https://github.com/angular/jasminewd/commit/41577a5e10420d255fb2ec12aa0ea3a8e72f14ca)) + support native async functions (node 7.6+) (#87) + +## Bug Fixes + +- ([0137d3f](https://github.com/angular/jasminewd/commit/0137d3f2ae96ef6d51d00055d64b5a8103ae83d0)) + minor fix to keep stack from original error (#86) + +- ([374f494](https://github.com/angular/jasminewd/commit/374f4946972673f86e06a011e20fc039bb73e234)) + Allow to specify a function as a custom matcher's message. (#29) + + + # 2.0.0 (Skipping 1.x because `0.0.1` was originally accidently published as `1.0.0`.) diff --git a/package.json b/package.json index 4bc7031..c1dd667 100644 --- a/package.json +++ b/package.json @@ -40,5 +40,5 @@ "engines": { "node": ">= 6.9.x" }, - "version": "2.0.0" + "version": "2.1.0" }