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 diff --git a/.travis.yml b/.travis.yml index 163b7f6..2fce81e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ +sudo: false language: node_js node_js: - - "0.10" + - "4" script: - npm test diff --git a/CHANGELOG.md b/CHANGELOG.md index a27c807..ff14811 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,42 +1,73 @@ -# 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.8 -# 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 +- ([5abc745](https://github.com/angular/protractor/commit/5abc7457cd73a4a4ba70b3c9ceeadac6d42bbd76)) + chore(jasmine): update MatchFactory to allow message as function - Previously, it used to call the resets if anything matched 'timeout'. This was too - vague, since many error messages contain that string. +- ([750898c](https://github.com/angular/protractor/commit/750898c90a1cc1bef09384b60ef6e15adfe734f7)) + fix(expectation): expectations without promises no longer add to task queue - Closes #8 + Instead, expectations without promises in either expected or actual are unchanged from the + original Jasmine implementation. -# 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 + See https://github.com/angular/protractor/issues/2894 -# 1.0.2 +# 0.0.7 -## Bug Fixes -- ([30b6811](https://github.com/angular/jasminewd/commit/30b68113759a7cb5c8dabc5b16ffcd89516882d8)) fix(timeout): output more information about the current task when a timeout occurs +- ([55fd11e](https://github.com/angular/protractor/commit/55fd11e69c2f1ba8fba9a19a8acccbe933896084)) + fix(index): forward it's return value -# 1.0.1 +- ([f4c30a0](https://github.com/angular/protractor/commit/f4c30a0023c6ec33b15df762226c3fe8e741d26e)) + fix: allow empty it functions -## Bug Fixes -- ([c507b37](https://github.com/angular/jasminewd/commit/c507b37dd04cf267a437a579fc3b14063abb2ef8)) - fix(index): stop infinite promise resolution +# 0.0.6 -1.0.0 -===== +- ([4776c16](https://github.com/angular/jasminewd/commit/4776c16b9a9f3a9a3de8a8dddc0e051cb32331b4)) + chore(selenium-webdriver): update selenium webdriver to 2.47.0 -Support for Jasmine 1.3.1. Tested against minijasminenode @ 0.4.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 -Features + 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. - - Automatically makes tests asynchronously wait until the WebDriverJS control flow is empty. +# 0.0.5 - - If a `done` function is passed to the test, waits for both the control flow and until done is called. +- ([037c7de](https://github.com/angular/jasminewd/commit/037c7de7fea4de068734b6fa250d145800863633)) + chore(dependencies): update Jasmine to 2.3.1 - - Enhances `expect` so that it automatically unwraps promises before performing the assertion. +# 0.0.4 +- ([8f8b8b3](https://github.com/angular/jasminewd/commit/8f8b8b39e779559fd3b29b138d7577658b8a64b7)) + 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. + +- ([c0f13d2](https://github.com/angular/jasminewd/commit/c0f13d254966c859db22d020a5390138dbf48e64)) + 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. + +# 0.0.3 + +- ([161e1fa](https://github.com/angular/jasminewd/commit/161e1fa48deaa5ea0f485027ea8ae41562864936)) + 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 + +- ([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/README.md b/README.md index 9cd01dc..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 -------- @@ -16,7 +20,7 @@ Features Installation ------------ ``` -npm install jasminewd +npm install jasminewd2 ``` Usage @@ -26,15 +30,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..d88d3ad 100644 --- a/index.js +++ b/index.js @@ -72,39 +72,33 @@ function wrapInControlFlow(globalFn, fnName) { var driverError = new Error(); driverError.stack = driverError.stack.replace(/ +at.+jasminewd.+\n/, ''); - function asyncTestFn(fn, desc) { + function asyncTestFn(fn, description) { + description = description ? ('("' + description + '")') : ''; 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(); - - 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 +'"'); - } + var async = fn.length > 0; + testFn = fn.bind(this); - var flowFinished = flow.execute(function() { - fn.call(jasmine.getEnv().currentSpec, function(userError) { - if (userError) { - asyncFnDone.reject(new Error(userError)); + 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) { + var wrappedErr = new Error(err); + reject(wrappedErr); + }; + testFn(proxyDone); } else { - asyncFnDone.fulfill(); + // Without a callback, testFn can return a promise, or it will + // be assumed to have completed synchronously. + fulfill(testFn()); } - }); - }, desc_); - - webdriver.promise.all([asyncFnDone, flowFinished]).then(function() { - seal(done)(); - }, function(e) { - e.stack = e.stack + '==== async task ====\n' + driverError.stack; - done(e); + }, flow); + }, 'Run ' + fnName + description + ' in control flow').then(seal(done), function(err) { + err.stack = err.stack + '\nFrom asynchronous test: \n' + driverError.stack; + done.fail(err); }); }; } @@ -112,18 +106,23 @@ function wrapInControlFlow(globalFn, fnName) { var description, func, timeout; switch (fnName) { case 'it': - case 'iit': + case 'fit': description = validateString(arguments[0]); + if (!arguments[1]) { + return globalFn(description); + } func = validateFunction(arguments[1]); if (!arguments[2]) { - globalFn(description, asyncTestFn(func)); + return globalFn(description, asyncTestFn(func, description)); } else { timeout = validateNumber(arguments[2]); - globalFn(description, asyncTestFn(func), timeout); + return globalFn(description, asyncTestFn(func, description), timeout); } break; case 'beforeEach': case 'afterEach': + case 'beforeAll': + case 'afterAll': func = validateFunction(arguments[0]); if (!arguments[1]) { globalFn(asyncTestFn(func)); @@ -139,117 +138,112 @@ 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; + 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); }); - } 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); - } -}; + if (webdriver.promise.isPromise(result.pass)) { + return webdriver.promise.when(result.pass).then(compareDone); + } else { + return compareDone(result.pass); + } -// 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); + function compareDone(pass) { + var message = ''; - if (webdriver.promise.isPromise(result)) { - result.then(function(resolution) { - matcherFnArgs[1] = function() { - return resolution; - }; - originalMatcherFn.apply(matcherFnThis, matcherFnArgs). - apply(matcherThis, matcherArgs); - }); - } else { - originalMatcherFn.apply(matcherFnThis, matcherFnArgs). - apply(matcherThis, matcherArgs); + 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; + } + } + } + + 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); + 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 +252,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; - - var items_length = result.getItems().length; - for (var i = 0; i < items_length; i++) { - if (result.getItems()[i].passed_ === false) { - failureItem = result.getItems()[i]; +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 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 @@ -290,10 +271,5 @@ OnTimeoutReporter.prototype.log = function() {}; // 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 1f0390d..7387ac3 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", @@ -11,10 +11,12 @@ "jasmine" ], "author": "Julie Ralph ", + "dependencies": { + "selenium-webdriver": "2.48.2" + }, "devDependencies": { "jshint": "2.5.0", - "minijasminenode": "1.1.1", - "selenium-webdriver": "2.43.4" + "jasmine": "2.4.1" }, "repository": { "type": "git", @@ -23,8 +25,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.8" } diff --git a/scripts/test.sh b/scripts/test.sh new file mode 100755 index 0000000..20553d8 --- /dev/null +++ b/scripts/test.sh @@ -0,0 +1,21 @@ +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 +echo "### $CMD" +$CMD +[ "$?" -eq 0 ] || exit 1 +echo + +EXPECTED_RESULTS="16 specs, 15 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" +[ "$results_line" = "$EXPECTED_RESULTS" ] || exit 1 + +echo "all pass" diff --git a/spec/adapterSpec.js b/spec/adapterSpec.js index e6fa185..065ab46 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,100 +8,55 @@ 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() { 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.getEnv().defaultTimeoutInterval = 2000; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 2000; + var beforeEachMsg; 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); + beforeEachMsg = value; }); }); + afterEach(function() { + beforeEachMsg = ''; + }); + it('should pass normal synchronous tests', function() { expect(true).toEqual(true); }); @@ -110,6 +66,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); @@ -136,6 +96,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() { @@ -153,6 +115,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); @@ -165,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() { @@ -176,21 +171,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'); }); @@ -217,4 +197,45 @@ 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'); + }); + }); + + 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'); + }); + }); }); diff --git a/spec/common.js b/spec/common.js new file mode 100644 index 0000000..da64f33 --- /dev/null +++ b/spec/common.js @@ -0,0 +1,124 @@ +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'); + }, '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() { + return webdriver.promise.fulfilled({ + isDisplayed: function() { + return webdriver.promise.fulfilled(true); + } + }); + }, 'getDisplayedElement'); + }, + getHiddenElement: function() { + return flow.execute(function() { + return webdriver.promise.fulfilled({ + isDisplayed: function() { + return webdriver.promise.fulfilled(false); + } + }); + }, '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'); + }, + displayedElement: { + isDisplayed: function() { + return webdriver.promise.fulfilled(true); + } + }, + hiddenElement: { + 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..4ca3092 --- /dev/null +++ b/spec/errorSpec.js @@ -0,0 +1,106 @@ +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 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).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', 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" + ] +}