diff --git a/docs/config/config.md b/docs/config/config.md index 32bbae7e..da9121e8 100644 --- a/docs/config/config.md +++ b/docs/config/config.md @@ -127,3 +127,13 @@ Execute the visual test: $ uiveri5 --browsers=browser:*:android --seleniumAddress=http://127.0.0.1:4723/wd/hub --baseUrl=http://10.0.2.2:8080 ``` +## Run test files in parallel +Use the following capabilities to run every spec file in a separate browser parallelly +```javascript + browsers: [{ + browserName: "chrome", + capabilities: { + shardTestFiles: true + } + }] +``` diff --git a/e2e/Runner.js b/e2e/Runner.js index 80e311cd..0573a512 100644 --- a/e2e/Runner.js +++ b/e2e/Runner.js @@ -44,7 +44,7 @@ module.exports = class Runner { var cmdString = [ 'node', '../bin/uiveri5', - '-v', + '--v', '--browsers=chromeHeadless', '--config.specResolver="./resolver/localSpecResolver"', '--config.specs=' + opts.specs, diff --git a/package-lock.json b/package-lock.json index ce767137..3dfedc59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -73,6 +73,18 @@ "array-from": "^2.1.1" } }, + "@types/node": { + "version": "6.14.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-6.14.13.tgz", + "integrity": "sha512-J1F0XJ/9zxlZel5ZlbeSuHW2OpabrUAqpFuC2sm2I3by8sERQ8+KCjNKUcq8QHuzpGMWiJpo9ZxeHrqrP2KzQw==", + "dev": true + }, + "@types/q": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz", + "integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU=", + "dev": true + }, "@types/selenium-webdriver": { "version": "2.53.45", "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-2.53.45.tgz", @@ -111,6 +123,15 @@ "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.13.tgz", "integrity": "sha512-fERNJX8sOXfel6qCBCMPvZLzENBEhZTzKqg6vrOW5pvoEaQuJhRU4ndTAh6lHOxn1I6jnz2NHra56ZODM751uw==" }, + "agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, "ajv": { "version": "6.5.5", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.5.tgz", @@ -172,6 +193,27 @@ "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", "dev": true }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -234,6 +276,23 @@ "tweetnacl": "^0.14.3" } }, + "blocking-proxy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/blocking-proxy/-/blocking-proxy-1.0.1.tgz", + "integrity": "sha512-KE8NFMZr3mN2E0HcvCgRtX7DjhiIQrwle+nSVJVC/yqFb9+xznHl2ZcoBp2L9qzkI4t4cBFJ1efXF8Dwi132RA==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + } + } + }, "body-parser": { "version": "1.18.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", @@ -728,6 +787,21 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "requires": { + "globby": "^5.0.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "rimraf": "^2.2.8" + } + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -846,6 +920,21 @@ "is-arrayish": "^0.2.1" } }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "dev": true + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, + "requires": { + "es6-promise": "^4.0.3" + } + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -1377,6 +1466,45 @@ "integrity": "sha512-5cJVtyXWH8PiJPVLZzzoIizXx944O4OmRro5MWKx5fT4MgcN7OfaMutPeaTdJCCURwbWdhhcCWcKIffPnmTzBg==", "dev": true }, + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, "graceful-fs": { "version": "4.1.15", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", @@ -1631,6 +1759,16 @@ "integrity": "sha512-puSi8M8WNlFJm9Pk4c/Mbz9Gwparuj3gO9/RRO5zv6piQ0FY+9Qywp0PdWshYgsMJSalixFY7eC6oPu0zRxLAQ==", "dev": true }, + "https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "dev": true, + "requires": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + } + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -1680,6 +1818,12 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, "inquirer": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.0.tgz", @@ -1806,6 +1950,30 @@ "number-is-nan": "^1.0.0" } }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "dev": true, + "requires": { + "is-path-inside": "^1.0.0" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, "is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", @@ -2417,6 +2585,16 @@ "pinkie-promise": "^2.0.0" } }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + } + }, "optionator": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", @@ -2616,6 +2794,85 @@ "integrity": "sha1-StIXuzZYvKrplGsXqGaOzYUeE1Y=", "dev": true }, + "protractor": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/protractor/-/protractor-5.3.2.tgz", + "integrity": "sha512-pw4uwwiy5lHZjIguxNpkEwJJa7hVz+bJsvaTI+IbXlfn2qXwzbF8eghW/RmrZwE2sGx82I8etb8lVjQ+JrjejA==", + "dev": true, + "requires": { + "@types/node": "^6.0.46", + "@types/q": "^0.0.32", + "@types/selenium-webdriver": "~2.53.39", + "blocking-proxy": "^1.0.0", + "chalk": "^1.1.3", + "glob": "^7.0.3", + "jasmine": "2.8.0", + "jasminewd2": "^2.1.0", + "optimist": "~0.6.0", + "q": "1.4.1", + "saucelabs": "^1.5.0", + "selenium-webdriver": "3.6.0", + "source-map-support": "~0.4.0", + "webdriver-js-extender": "^1.0.0", + "webdriver-manager": "^12.0.6" + }, + "dependencies": { + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "q": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", + "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=", + "dev": true + }, + "webdriver-manager": { + "version": "12.1.8", + "resolved": "https://registry.npmjs.org/webdriver-manager/-/webdriver-manager-12.1.8.tgz", + "integrity": "sha512-qJR36SXG2VwKugPcdwhaqcLQOD7r8P2Xiv9sfNbfZrKBnX243iAkOueX1yAmeNgIKhJ3YAT/F2gq6IiEZzahsg==", + "dev": true, + "requires": { + "adm-zip": "^0.4.9", + "chalk": "^1.1.1", + "del": "^2.2.0", + "glob": "^7.0.3", + "ini": "^1.3.4", + "minimist": "^1.2.0", + "q": "^1.4.1", + "request": "^2.87.0", + "rimraf": "^2.5.2", + "semver": "^5.3.0", + "xml2js": "^0.4.17" + } + } + } + }, "proxy-addr": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", @@ -2869,6 +3126,15 @@ "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", "dev": true }, + "saucelabs": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/saucelabs/-/saucelabs-1.5.0.tgz", + "integrity": "sha512-jlX3FGdWvYf4Q3LFfFWS1QvPg3IGCGWxIc8QBFdPTbpTJnt/v17FHXYVAn7C8sHf1yUXo2c7yIM0isDryfYtHQ==", + "dev": true, + "requires": { + "https-proxy-agent": "^2.2.1" + } + }, "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", @@ -3141,6 +3407,21 @@ } } }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "requires": { + "source-map": "^0.5.6" + } + }, "spdx-correct": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.2.tgz", @@ -3746,6 +4027,12 @@ } } }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + }, "wrap-ansi": { "version": "2.1.0", "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", diff --git a/spec/StatisticCollector.spec.js b/spec/StatisticCollector.spec.js index b4fa84cd..99a6f099 100644 --- a/spec/StatisticCollector.spec.js +++ b/spec/StatisticCollector.spec.js @@ -1,9 +1,8 @@ - describe("StatisticCollector", function() { - var reporter; + var reporter = require('../src/statisticCollector'); beforeEach(function(){ - reporter = require('../src/statisticCollector')(); + reporter.reset(); }); describe("getOverview", function () { diff --git a/spec/localComparisonProvider.spec.js b/spec/localComparisonProvider.spec.js index 2a1f59f4..d26fcfc2 100644 --- a/spec/localComparisonProvider.spec.js +++ b/spec/localComparisonProvider.spec.js @@ -5,7 +5,6 @@ var LocalComparisonProvider = require('../src/image/localComparisonProvider.js') describe("LocalComparisonProvider", function () { var logger = require('../src/logger'); - var matchers = {}; var imagePath = '/localComparisonProvider/images/testSpec/platform/resolution/browser/theme/direction/mode'; function takeScreenshotMock(name){ @@ -58,7 +57,7 @@ describe("LocalComparisonProvider", function () { it('Should pass with similar images', function (done) { var comparisonProvider = new LocalComparisonProvider( comparisonConfig,comparisonInstanceConfig,logger,storageProvider); - comparisonProvider.register(matchers); + var matchers = comparisonProvider.getMatchers(); var result = matchers.toLookAs().compare(takeScreenshotMock('arrow_left'),'arrow_left'); result.pass.then( @@ -72,7 +71,7 @@ describe("LocalComparisonProvider", function () { it('Should fail with different images', function(done) { var comparisonProvider = new LocalComparisonProvider( comparisonConfig,comparisonInstanceConfig,logger,storageProvider); - comparisonProvider.register(matchers); + var matchers = comparisonProvider.getMatchers(); var result = matchers.toLookAs().compare(takeScreenshotMock('arrow_left_hover'),'arrow_left'); result.pass.then( @@ -89,7 +88,7 @@ describe("LocalComparisonProvider", function () { comparisonConfig.thresholdPercentage = 0.75; var comparisonProvider = new LocalComparisonProvider(comparisonConfig,comparisonInstanceConfig,logger,storageProvider); - comparisonProvider.register(matchers); + var matchers = comparisonProvider.getMatchers(); var result = matchers.toLookAs().compare(takeScreenshotMock('drop_down_draw'),'drop_down_clean'); result.pass.then( @@ -105,7 +104,7 @@ describe("LocalComparisonProvider", function () { comparisonConfig.thresholdPercentage = 0.1; var comparisonProvider = new LocalComparisonProvider(comparisonConfig,comparisonInstanceConfig,logger,storageProvider); - comparisonProvider.register(matchers); + var matchers = comparisonProvider.getMatchers(); var result = matchers.toLookAs().compare(takeScreenshotMock('drop_down_draw'),'drop_down_clean'); result.pass.then( @@ -121,7 +120,7 @@ describe("LocalComparisonProvider", function () { comparisonConfig.thresholdPercentage = 0.75; var comparisonProvider = new LocalComparisonProvider(comparisonConfig,comparisonInstanceConfig,logger,storageProvider); - comparisonProvider.register(matchers); + var matchers = comparisonProvider.getMatchers(); var result = matchers.toLookAs().compare(takeScreenshotMock('drop_down_draw'),'drop_down_clean'); result.pass.then( @@ -136,7 +135,7 @@ describe("LocalComparisonProvider", function () { comparisonConfig.ignoreNothing = true; var comparisonProvider = new LocalComparisonProvider(comparisonConfig,comparisonInstanceConfig,logger,storageProvider); - comparisonProvider.register(matchers); + var matchers = comparisonProvider.getMatchers(); var result = matchers.toLookAs().compare(takeScreenshotMock('calendar_act'),'calendar_ref'); result.pass.then(function(passed){ @@ -149,8 +148,8 @@ describe("LocalComparisonProvider", function () { var invalidBuffer = new Buffer('not an actual png', 'utf8'); var comparisonProvider = new LocalComparisonProvider( comparisonConfig, comparisonInstanceConfig, logger, storageProvider); + var matchers = comparisonProvider.getMatchers(); - comparisonProvider.register(matchers); var result = matchers.toLookAs().compare(invalidBuffer, 'arrow_left'); result.pass.then(function (passed) { expect(passed).toBe(false); diff --git a/src/api/body.js b/src/api/body.js index dfec27ca..633aedc0 100644 --- a/src/api/body.js +++ b/src/api/body.js @@ -2,7 +2,7 @@ var should = require('should').noConflict(); module.exports = function(){ return { - register: function(matchers) { + getMatchers: function() { var body = function() { return { compare: function(actualResponse, expectedFn) { @@ -22,7 +22,9 @@ module.exports = function(){ } }; }; - matchers.body = body; + return { + body: body + }; } }; }; diff --git a/src/api/toHaveHttpBody.js b/src/api/toHaveHttpBody.js index 3aa5bbe7..fd81b17b 100644 --- a/src/api/toHaveHttpBody.js +++ b/src/api/toHaveHttpBody.js @@ -7,7 +7,7 @@ function ToHaveHttpBody(){ } -ToHaveHttpBody.prototype.register = function(matchers) { +ToHaveHttpBody.prototype.getMatchers = function () { var toHaveHTTPBody = function() { return { compare: function(actualResponse, expectedResponse) { @@ -24,7 +24,9 @@ ToHaveHttpBody.prototype.register = function(matchers) { }; }; - matchers.toHaveHTTPBody = toHaveHTTPBody; + return { + toHaveHTTPBody: toHaveHTTPBody + }; }; module.exports = function(){ diff --git a/src/api/toHaveHttpHeader.js b/src/api/toHaveHttpHeader.js index ab962830..6ab75932 100644 --- a/src/api/toHaveHttpHeader.js +++ b/src/api/toHaveHttpHeader.js @@ -7,8 +7,8 @@ function ToHaveHttpHeader(){ } -ToHaveHttpHeader.prototype.register = function(matchers) { - var toHaveHTTPHeader = function() { +ToHaveHttpHeader.prototype.getMatchers = function () { + var toHaveHTTPHeader = function () { return { compare: function(actualResponse, expectedResponse) { var result = {}; @@ -31,7 +31,9 @@ ToHaveHttpHeader.prototype.register = function(matchers) { }; }; - matchers.toHaveHTTPHeader = toHaveHTTPHeader; + return { + toHaveHTTPHeader: toHaveHTTPHeader + }; }; module.exports = function(){ diff --git a/src/configUtils.js b/src/configUtils.js new file mode 100644 index 00000000..6f627cc0 --- /dev/null +++ b/src/configUtils.js @@ -0,0 +1,59 @@ +var _ = require('lodash'); +var logger = require('./logger'); + +module.exports = { + logFrameworkVersion: function () { + var pjson = require('../package.json'); + logger.info(pjson.name + ' v' + pjson.version); + }, + + getOSType: function () { + var os = require('os'); + var osType = ''; + + if (os.type() == 'Darwin') { + osType = 'mac64'; + } else if (os.type() == 'Linux') { + if (os.arch() == 'x64') { + osType = 'linux64'; + } else { + osType = 'linux32'; + } + } else if (os.type() == 'Windows_NT') { + osType = 'win32'; + } else { + osType = 'unknown'; + } + + return osType; + }, + + copyTimeouts: function (launcherArgv, config) { + if (config.timeouts) { + if (config.timeouts.getPageTimeout) { + var getPageTimeout = config.timeouts.getPageTimeout; + if (_.isString(getPageTimeout)) { + getPageTimeout = parseInt(getPageTimeout, 10); + } + logger.debug('Setting getPageTimeout: ' + getPageTimeout); + launcherArgv.getPageTimeout = getPageTimeout; + } + if (config.timeouts.allScriptsTimeout) { + var allScriptsTimeout = config.timeouts.allScriptsTimeout; + if (_.isString(allScriptsTimeout)) { + allScriptsTimeout = parseInt(allScriptsTimeout, 10); + } + logger.debug('Setting allScriptsTimeout: ' + allScriptsTimeout); + launcherArgv.allScriptsTimeout = allScriptsTimeout; + } + if (config.timeouts.defaultTimeoutInterval) { + var defaultTimeoutInterval = config.timeouts.defaultTimeoutInterval; + if (_.isString(defaultTimeoutInterval)) { + defaultTimeoutInterval = parseInt(defaultTimeoutInterval, 10); + } + logger.debug('Setting defaultTimeoutInterval: ' + defaultTimeoutInterval); + launcherArgv.jasmineNodeOpts.defaultTimeoutInterval = defaultTimeoutInterval; + } + } + } +}; diff --git a/src/connection/connectionProvider.js b/src/connection/connectionProvider.js new file mode 100644 index 00000000..64af6b2d --- /dev/null +++ b/src/connection/connectionProvider.js @@ -0,0 +1,22 @@ +var DEFAULT_CONNECTION_NAME = 'direct'; +var connection; + +var ConnectionProvider = function () { }; + +ConnectionProvider.prototype.verifyConfig = function (config) { + var connectionName = config.connection || DEFAULT_CONNECTION_NAME; + var connectionConfig = config.connectionConfigs[connectionName]; + if (!connectionConfig) { + throw Error('Could not find connection: ' + connectionName); + } +}; + +ConnectionProvider.prototype.setConnection = function (connectionModule) { + connection = connectionModule; +}; + +ConnectionProvider.prototype.getConnection = function () { + return connection; +}; + +module.exports = new ConnectionProvider(); diff --git a/src/connection/directConnectionProvider.js b/src/connection/directConnectionProvider.js index 04bfdd95..f7e1424f 100644 --- a/src/connection/directConnectionProvider.js +++ b/src/connection/directConnectionProvider.js @@ -38,8 +38,8 @@ var BINARIES = { * @param config - config * @param logger - logger */ -function DirectConnectionProvider(config, instanceConfig, logger, plugins) { - ConnectionProvider.call(this, config, instanceConfig, logger, plugins); +function DirectConnectionProvider(config, instanceConfig, logger) { + ConnectionProvider.call(this, config, instanceConfig, logger); this.seleniumConfig = {}; this.seleniumConfig.executables = {}; @@ -52,8 +52,6 @@ function DirectConnectionProvider(config, instanceConfig, logger, plugins) { this.seleniumConfig.seleniumLoopback = typeof config.seleniumLoopback !== 'undefined' ? config.seleniumLoopback : false; this.binaries = instanceConfig.binaries; - - this.runtimes = []; this.latestDriverVersionResolver = new LatestDriverVersionResolver(config, { latestVersionRegexp: LATEST_VERSION_REGEXP }, logger); @@ -62,87 +60,12 @@ DirectConnectionProvider.prototype = _.create(ConnectionProvider.prototype,{ 'constructor': DirectConnectionProvider }); -/** - * Prepare capabilities object for this session - * @param {Runtime} runtime - required runtime for this session - * @return {Object} capabilities of this session - */ -DirectConnectionProvider.prototype.resolveCapabilitiesFromRuntime = function(runtime){ - // save this runtime so setupEnv() could download respective drivers - this.runtimes.push(runtime); - - var capabilities = {}; - - // capabilities according: - // https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities - // http://appium.io/slate/en/master/?ruby#appium-server-capabilities - - // format browserName - if (runtime.platformName === 'android' || runtime.platformName === 'ios') { - capabilities.browserName = runtime.browserName.charAt(0).toUpperCase() + runtime.browserName.slice(1); - } else { - if (runtime.browserName === 'ie') { - capabilities.browserName = 'internet explorer'; - } else if (runtime.browserName === 'edge') { - capabilities.browserName = 'MicrosoftEdge'; - } else { - capabilities.browserName = runtime.browserName; - } - } - - // format browserVersion - if (runtime.browserVersion !== '*') { - capabilities.version = runtime.browserVersion; - } - - // format platformName - if (runtime.platformName === 'windows') { - if (runtime.platformVersion === '*') { - capabilities.platform = 'WINDOWS'; - } else if (runtime.platformVersion === 'XP') { - capabilities.platform = 'XP'; - } else if (runtime.platformVersion === 'VISTA' || runtime.platformVersion === '7') { - capabilities.platform = 'VISTA'; - } else if (runtime.platformVersion === '8') { - capabilities.platform = 'WIN8'; - } else if (runtime.platformVersion === '8.1') { - capabilities.platform = 'WIN8_1'; - } else { - throw Error('Platform version: ' + runtime.platformVersion + - ' for platformName: WINDOWS is not supported by directConnectionProvider'); - } - } else if (runtime.platformName === 'linux' || runtime.platformName === 'mac') { - capabilities.platform = runtime.platformName.toUpperCase(); - } else if (runtime.platformName === 'ios') { - capabilities.platformName = 'iOS'; - } else if (runtime.platformName === 'android') { - capabilities.platformName = 'Android'; - } else { - throw Error('Platform name: ' + runtime.platformName + - ' not supported by directConnectionProvider'); - } - - // format platformVersion - if (runtime.platformVersion !== '*'){ - capabilities.platformVersion = runtime.platformVersion; - } - - return this._mergeRuntimeCapabilities(capabilities,runtime); -}; - - -DirectConnectionProvider.prototype.buildLauncherArgv = function () { - // require direct connection as directDriverProvider will be overtaken later - return { - directConnect: true - }; -}; - /** * Setup this connection provider environment * @return {q.promise} A promise which will resolve when the environment is ready to test. */ -DirectConnectionProvider.prototype.setupEnv = function() { +DirectConnectionProvider.prototype.setupEnv = function(runtimes) { + this.logger.debug('Setting up connection provider environment'); var that = this; // prepare correct driver and/or selenium jar, download if necessary @@ -158,7 +81,7 @@ DirectConnectionProvider.prototype.setupEnv = function() { } // switch on browser - that.runtimes.forEach(function(runtime){ + runtimes.forEach(function(runtime){ var browserName = runtime.browserName; if (browserName == 'chrome' || browserName == 'chromeMobileEmulation' || browserName == 'chromeHeadless' || browserName == 'chromium') { promises.push(that._getBinaryFileName(BINARIES.CHROMEDRIVER).then( @@ -196,7 +119,7 @@ DirectConnectionProvider.prototype.setupEnv = function() { }; DirectConnectionProvider.prototype.buildDriverProvider = function (protConfig) { - return new DirectDriverProvider(protConfig, this.logger, this.seleniumConfig, this.plugins); + return new DirectDriverProvider(protConfig, this.logger, this.seleniumConfig); }; DirectConnectionProvider.prototype._getBinaryFileName = function(binaryName) { @@ -362,6 +285,6 @@ DirectConnectionProvider.prototype._getSeleniumRoot = function () { return path.resolve(__dirname + '/../../selenium').replace(/\\/g,'/'); }; -module.exports = function(config, instanceConfig, logger, plugins){ - return new DirectConnectionProvider(config, instanceConfig, logger, plugins); +module.exports = function(config, instanceConfig, logger){ + return new DirectConnectionProvider(config, instanceConfig, logger); }; diff --git a/src/connection/directDriverProvider.js b/src/connection/directDriverProvider.js index 4a9091f3..ac918b97 100644 --- a/src/connection/directDriverProvider.js +++ b/src/connection/directDriverProvider.js @@ -1,16 +1,15 @@ var path = require('path'); var _ = require('lodash'); var q = require('q'); +var plugins = require('../plugins/plugins'); // replaces ptor driverProviders -var DirectDriverProvider = function (protConfig, logger, seleniumConfig, plugins) { +var DirectDriverProvider = function (protConfig, logger, seleniumConfig) { this.protConfig = protConfig; this.logger = logger; this.seleniumConfig = seleniumConfig; - this.plugins = plugins; this.drivers = []; - // use selenium-webdriver from protractor dependecies this.deps = {}; this.deps.webdriver = require('selenium-webdriver'); this.deps.http = require('selenium-webdriver/http'); @@ -40,7 +39,7 @@ DirectDriverProvider.prototype.getNewDriver = function() { delete requiredCapabilities.runtime; delete requiredCapabilities.remoteWebDriverOptions; - this.plugins.onConnectionSetup(requiredCapabilities); + plugins.onConnectionSetup(requiredCapabilities); that.logger.info('Opening webdriver connection with capabilities: ' + JSON.stringify(requiredCapabilities)); diff --git a/src/coreReporters/paramsReporter.js b/src/coreReporters/paramsReporter.js new file mode 100644 index 00000000..eec198fc --- /dev/null +++ b/src/coreReporters/paramsReporter.js @@ -0,0 +1,17 @@ +var fs = require('fs'); +var logger = require('../logger'); + +module.exports = function (config) { + return { + register: function () { + if (config.exportParamsFile) { + jasmine.getEnv().addReporter({ + jasmineDone: function () { + logger.debug('Exporting test params to file ' + config.exportParamsFile); + fs.writeFileSync(config.exportParamsFile, JSON.stringify(browser.testrunner.config.exportParams, null, 2)); + } + }); + } + } + }; +}; diff --git a/src/coreReporters/runnerReporter.js b/src/coreReporters/runnerReporter.js new file mode 100644 index 00000000..65eeb07d --- /dev/null +++ b/src/coreReporters/runnerReporter.js @@ -0,0 +1,56 @@ +var RunnerReporter = function (emitter) { + this.emitter = emitter; + this.testResult = []; + this.failedCount = 0; +}; + +RunnerReporter.prototype.register = function () { + jasmine.getEnv().addReporter(this); +}; + +RunnerReporter.prototype.jasmineStarted = function () { + // Need to initiate startTime here, in case reportSpecStarting is not + // called (e.g. when fit is used) + this.startTime = new Date(); +}; + +RunnerReporter.prototype.specStarted = function () { + this.startTime = new Date(); +}; + +RunnerReporter.prototype.specDone = function (result) { + var specInfo = { + name: result.description, + category: result.fullName.slice(0, -result.description.length).trim() + }; + if (result.status == 'passed') { + this.emitter.emit('testPass', specInfo); + } else if (result.status == 'failed') { + this.emitter.emit('testFail', specInfo); + this.failedCount++; + } + + var entry = { + description: result.fullName, + assertions: [], + duration: new Date().getTime() - this.startTime.getTime() + }; + + if (result.failedExpectations.length === 0) { + entry.assertions.push({ + passed: true + }); + } + + result.failedExpectations.forEach(function (item) { + entry.assertions.push({ + passed: item.passed, + errorMsg: item.passed ? undefined : item.message, + stackTrace: item.passed ? undefined : item.stack + }); + }); + this.testResult.push(entry); +}; + +module.exports = RunnerReporter; + diff --git a/src/coreReporters/specLifecycleReporter.js b/src/coreReporters/specLifecycleReporter.js new file mode 100644 index 00000000..35687d21 --- /dev/null +++ b/src/coreReporters/specLifecycleReporter.js @@ -0,0 +1,92 @@ +var url = require('url'); +var logger = require('../logger'); + +module.exports = function (config, storageProvider) { + // finds a UIVeri5 spec definition by a given Jasmine suite description + // i.e. given the description of one of the suites in a spec file, find the information for that file. + function _getSpecDetails(suite) { + return config.specsWithDetails.filter(function (suiteDetails) { + return suiteDetails.fullName === suite.description; + })[0]; + } + + return { + register: function () { + jasmine.getEnv().addReporter({ + + jasmineStarted: function () { + // call storage provider beforeAll hook + if (storageProvider && storageProvider.onBeforeAllSpecs) { + storageProvider.onBeforeAllSpecs(config.specsWithDetails); + } + }, + + // open test content page before every suite + // TODO consider several describe() per spec file + suiteStarted: function (result) { + var spec = _getSpecDetails(result); + var contentUrl = spec ? spec.contentUrl : config.baseUrl; + // open content pag e if required + if (!contentUrl) { + logger.debug('Skip content page opening'); + return; + } + + // webdriverjs operations are inherently synchronized by webdriver flow + // so no need to synchronize manually with callbacks/promises + + // add request params + var specUrl = url.parse(contentUrl); + if (config.baseUrlQuery && config.baseUrlQuery.length > 0) { + if (specUrl.search == null) { + specUrl.search = ''; + } + config.baseUrlQuery.forEach(function (value, index) { + if (index > 0) { + specUrl.search += '&'; + } + specUrl.search += value; + }); + } + + // open test page + var specUrlString = url.format(specUrl); + // enclose all WebDriver operations in a new flow for a gracefull handling of failures + // will call jasmine.fail() that will handle the error + browser.controlFlow().execute(function () { + browser.testrunner.navigation.to(specUrlString).then(function () { + // call storage provider beforeEach hook + if (storageProvider && storageProvider.onBeforeEachSpec) { + storageProvider.onBeforeEachSpec(spec); + } + }); + }).catch(function (error) { + // the failure in reporter -> beforeAll will not stop further suite execution ! + // fail-fast was discussed here -> https://github.com/jasmine/jasmine/issues/778 + // stop the suite will require jasmin 3.0 -> https://github.com/jasmine/jasmine/issues/414 + // stop the spec when error require jasmin 2.4 -> https://jasmine.github.io/2.4/node.html#section-13 + // completing of this functionality in jasmine 2.8 -> https://github.com/jasmine/jasmine/issues/577 + // In jasmine 2.3 a throwOnExpectationFailure(true) was added -> https://stackoverflow.com/questions/22119193/stop-jasmine-test-after-first-expect-fails + // it does not make sense for us at it simply throws error from the first failed expectation and this kills the whole execution + fail(error); + }); + }, + + suiteDone: function (result) { + var spec = _getSpecDetails(result); + // call storage provider afterEach hook + if (spec && storageProvider && storageProvider.onAfterEachSpec) { + storageProvider.onAfterEachSpec(spec); + } + }, + + jasmineDone: function () { + // call storage provider afterAll hook + if (storageProvider && storageProvider.onAfterAllSpecs) { + storageProvider.onAfterAllSpecs(config.specsWithDetails); + } + } + }); + } + }; +}; diff --git a/src/coreReporters/statisticReporter.js b/src/coreReporters/statisticReporter.js new file mode 100644 index 00000000..6f1966bd --- /dev/null +++ b/src/coreReporters/statisticReporter.js @@ -0,0 +1,31 @@ +var statisticCollector = require('../statisticCollector'); + +module.exports = { + register: function () { + jasmine.getEnv().addReporter({ + jasmineStarted: function () { + statisticCollector.jasmineStarted(); + }, + suiteStarted: function (jasmineSuite) { + statisticCollector.suiteStarted(jasmineSuite); + }, + specStarted: function (jasmineSpec) { + statisticCollector.specStarted(jasmineSpec); + }, + specDone: function (jasmineSpec) { + statisticCollector.specDone(jasmineSpec, browser.testrunner.currentSpec._meta); + delete browser.testrunner.currentSpec._meta; + }, + suiteDone: function (jasmineSuite) { + statisticCollector.suiteDone(jasmineSuite, browser.testrunner.currentSuite._meta); + delete browser.testrunner.currentSuite._meta; + }, + jasmineDone: function () { + statisticCollector.jasmineDone({ + runtime: browser.testrunner.runtime + }); + } + }); + + } +}; diff --git a/src/image/localComparisonProvider.js b/src/image/localComparisonProvider.js index cb397960..7a80ec26 100644 --- a/src/image/localComparisonProvider.js +++ b/src/image/localComparisonProvider.js @@ -36,7 +36,7 @@ var DEFAULT_IMAGE_NAME_REGEX = /^[\w\-]{3,40}$/; * @param {Logger} logger * @param {StorageProvider} storage provider */ -function LocalComparisonProvider(config,instanceConfig,logger,storageProvider) { +function LocalComparisonProvider(config, instanceConfig, logger, storageProvider) { //this.config = config; this.instanceConfig = instanceConfig; this.logger = logger; @@ -54,204 +54,205 @@ function LocalComparisonProvider(config,instanceConfig,logger,storageProvider) { /** * Registers the custom matcher to jasmine environment - * @param {Object} matchers - jasmine matchers, adds toLookAs matcher here */ -LocalComparisonProvider.prototype.register = function (matchers) { - var that = this; +LocalComparisonProvider.prototype.register = function () { + jasmine.getEnv().addMatchers([this.getMatchers()]); +}; - // create jasmine custom matcher - var toLookAs = function () { - return { - compare: function (actEncodedImage, expectedImageName) { +LocalComparisonProvider.prototype.getMatchers = function () { + return { + toLookAs: function () { + return { + compare: this._compare.bind(this) + }; + }.bind(this) + }; +}; - // matcher returns result object - var defer = webdriver.promise.defer(); - var result = { - pass: defer.promise - }; +LocalComparisonProvider.prototype._compare = function (actEncodedImage, expectedImageName) { + // matcher returns result object + var defer = webdriver.promise.defer(); + var result = { + pass: defer.promise + }; + var that = this; - if(!expectedImageName.match(that.imgNameRegEx)) { - result.message = 'The image name is not correct! Image name: ' + expectedImageName + ' ; length: ' - + expectedImageName.length + '. '; + if (!expectedImageName.match(that.imgNameRegEx)) { + result.message = 'The image name is not correct! Image name: ' + expectedImageName + ' ; length: ' + + expectedImageName.length + '. '; - that.logger.debug(result.message + 'RegExp: ' + that.imgNameRegEx); + that.logger.debug(result.message + 'RegExp: ' + that.imgNameRegEx); - if (that.imgNameRegEx === DEFAULT_IMAGE_NAME_REGEX) { - result.message += 'Image name should be between 3 and 40 characters and contain only: A-Za-z0-9_-'; - } else { - result.message += 'Image name regExp: ' + that.imgNameRegEx; - } + if (that.imgNameRegEx === DEFAULT_IMAGE_NAME_REGEX) { + result.message += 'Image name should be between 3 and 40 characters and contain only: A-Za-z0-9_-'; + } else { + result.message += 'Image name regExp: ' + that.imgNameRegEx; + } - defer.fulfill(false); - } else if(that.take && that.compare) { - var actualImageBuffer = new Buffer(actEncodedImage, 'base64'); + defer.fulfill(false); + } else if (that.take && that.compare) { + var actualImageBuffer = new Buffer(actEncodedImage, 'base64'); - // get the reference image from storage provider - that.storageProvider.readRefImage(expectedImageName).then( - function (refImageResult) { - // check if reference image was found - if (refImageResult == null){ - var msg = 'Image comparison enabled but no reference image found: ' - + expectedImageName; + // get the reference image from storage provider + that.storageProvider.readRefImage(expectedImageName).then(function (refImageResult) { + // check if reference image was found + if (refImageResult == null) { + var msg = 'Image comparison enabled but no reference image found: ' + + expectedImageName; - if(that.update) { - msg += ' ,update enabled so storing current as reference'; - // reference image was not found => either update or throw error - that.logger.debug(msg); - var res = { - message: msg, - imageName: expectedImageName, - failureType: 'COMPARISON' - }; - - that.storageProvider.storeRefImage(expectedImageName,actualImageBuffer) - .then(function(refRes){ - // update details and store message - res.details = { - refImageUrl:refRes.refImageUrl - }; - result.message = JSON.stringify(res); - // fail - defer.fulfill(false); - }) - .catch(function(error){ - result.message = error.stack; - // fail - defer.fulfill(false); - }); - } else { - msg += ' ,update disabled'; - that.logger.debug(msg); - result.message = msg; - - // fail - defer.fulfill(false); - } - } else { - // reference image was found => compare it against actual - resemble.outputSettings(that.instanceConfig); + if (that.update) { + msg += ' ,update enabled so storing current as reference'; + // reference image was not found => either update or throw error + that.logger.debug(msg); + var res = { + message: msg, + imageName: expectedImageName, + failureType: 'COMPARISON' + }; - // compare two images and add input settings - they are chained and set to resJS object - // settings include ignore colors, ignore antialiasing, threshold and ignore rectangle - that.logger.debug('Comparing current screenshot to reference image: ' + expectedImageName); - var resComparison = resemble(refImageResult.refImageBuffer).compareTo(actualImageBuffer); - if (that.ignoreNothing) { - resComparison.ignoreNothing(that.ignoreNothing); - } + that.storageProvider.storeRefImage(expectedImageName, actualImageBuffer) + .then(function (refRes) { + // update details and store message + res.details = { + refImageUrl: refRes.refImageUrl + }; + result.message = JSON.stringify(res); + // fail + defer.fulfill(false); + }) + .catch(function (error) { + result.message = error.stack; + // fail + defer.fulfill(false); + }); + } else { + msg += ' ,update disabled'; + that.logger.debug(msg); + result.message = msg; - resComparison.onComplete(function (comparisonResult, error) { - if (error) { - result.message = 'Image comparison failed, error: ' + error; - that.logger.debug(result.message); - defer.fulfill(false); - return; - } - // resolve mismatch percentage and count - var mismatchPercentage = parseFloat(comparisonResult.misMatchPercentage); - var mismatchPixelsCount = comparisonResult.mismatchCount; + // fail + defer.fulfill(false); + } + } else { + // reference image was found => compare it against actual + resemble.outputSettings(that.instanceConfig); - // dimension difference is elevated to 100% - if (!comparisonResult.isSameDimensions) { - mismatchPercentage = 100; - mismatchPixelsCount = -1; - } + // compare two images and add input settings - they are chained and set to resJS object + // settings include ignore colors, ignore antialiasing, threshold and ignore rectangle + that.logger.debug('Comparing current screenshot to reference image: ' + expectedImageName); + var resComparison = resemble(refImageResult.refImageBuffer).compareTo(actualImageBuffer); + if (that.ignoreNothing) { + resComparison.ignoreNothing(that.ignoreNothing); + } - that.logger.trace('Image comparison done ' + - ',reference image: ${expectedImageName} ' + - ',results: ${JSON.stringify(comparisonResult)} ', { - expectedImageName: expectedImageName, - comparisonResult: comparisonResult - }); + resComparison.onComplete(function (comparisonResult, error) { + if (error) { + result.message = 'Image comparison failed, error: ' + error; + that.logger.debug(result.message); + defer.fulfill(false); + return; + } + // resolve mismatch percentage and count + var mismatchPercentage = parseFloat(comparisonResult.misMatchPercentage); + var mismatchPixelsCount = comparisonResult.mismatchCount; - // resolve pixel threshold from the given threshold percentage - var allImagePixels = comparisonResult.getDiffImage().width * comparisonResult.getDiffImage().height; - var thresholdPixelsFromPercentage = (allImagePixels * that.thresholdPercentage) / 100; - var resolvedPixelThreshold = Math.min(thresholdPixelsFromPercentage, that.thresholdPixels); - var msg; + // dimension difference is elevated to 100% + if (!comparisonResult.isSameDimensions) { + mismatchPercentage = 100; + mismatchPixelsCount = -1; + } - // check the mismatch percentage and the mismatch pixel count - if (mismatchPixelsCount > -1 && mismatchPixelsCount < resolvedPixelThreshold) { - msg = 'Image comparison passed, reference image: ' + expectedImageName + - ', difference in percentages: ' + mismatchPercentage + '% (threshold: ' + that.thresholdPercentage - + '%), difference in pixels: ' + mismatchPixelsCount + ' (threshold: ' + resolvedPixelThreshold - + ')'; - that.logger.debug(msg); - result.message = JSON.stringify({ - message: msg, - details: { - refImageUrl: refImageResult.refImageUrl - }, - imageName: expectedImageName - }); - // pass - defer.fulfill({message: result.message}); - } else { - if (mismatchPixelsCount == -1) { - msg = 'Image comparison failed, reference image: ' + expectedImageName + - ', difference in image size with: W=' + comparisonResult.dimensionDifference.width + - 'px, H=' + comparisonResult.dimensionDifference.height + 'px'; - } else { - msg = 'Image comparison failed, reference image: ' + expectedImageName + - ', difference in percentages: ' + mismatchPercentage + '% (threshold: ' - + that.thresholdPercentage + '%), difference in pixels: ' + mismatchPixelsCount - + ' (threshold: ' + resolvedPixelThreshold + ')'; - } + that.logger.trace('Image comparison done ' + + ',reference image: ${expectedImageName} ' + + ',results: ${JSON.stringify(comparisonResult)} ', { + expectedImageName: expectedImageName, + comparisonResult: comparisonResult + }); - // handle image updates - if (that.update) { - msg += ' ,update enabled so storing current as reference'; - } else { - msg += ' ,update disabled'; - } - that.logger.debug(msg); - var res = { - message: msg, - details: { - refImageUrl: refImageResult.refImageUrl - }, - imageName: expectedImageName - }; + // resolve pixel threshold from the given threshold percentage + var allImagePixels = comparisonResult.getDiffImage().width * comparisonResult.getDiffImage().height; + var thresholdPixelsFromPercentage = (allImagePixels * that.thresholdPercentage) / 100; + var resolvedPixelThreshold = Math.min(thresholdPixelsFromPercentage, that.thresholdPixels); + var msg; - that._diffImageToBuffer(comparisonResult).then(function(diffImageBuffer){ - return that.storageProvider.storeRefActDiffImage( - expectedImageName,actualImageBuffer,diffImageBuffer,that.update); - }).then(function (storeRes) { - // ref should be left the ref image before update - res.details.actImageUrl = storeRes.actImageUrl; - res.details.diffImageUrl = storeRes.diffImageUrl; - res.failureType = 'COMPARISON'; - result.message = JSON.stringify(res); - // fail - defer.fulfill(false); - }) - .catch(function (error) { - result.message = error.stack; - // fail - defer.fulfill(false); - }); - } - }); - } - }).catch(function(error){ - result.message = error.stack; - // fail - defer.fulfill(false); - }); - } else { - var msg = 'Comparison or screenshot taking disabled so skipping comparison'; - that.logger.debug(msg); - result.message = msg; + // check the mismatch percentage and the mismatch pixel count + if (mismatchPixelsCount > -1 && mismatchPixelsCount < resolvedPixelThreshold) { + msg = 'Image comparison passed, reference image: ' + expectedImageName + + ', difference in percentages: ' + mismatchPercentage + '% (threshold: ' + that.thresholdPercentage + + '%), difference in pixels: ' + mismatchPixelsCount + ' (threshold: ' + resolvedPixelThreshold + + ')'; + that.logger.debug(msg); + result.message = JSON.stringify({ + message: msg, + details: { + refImageUrl: refImageResult.refImageUrl + }, + imageName: expectedImageName + }); + // pass + defer.fulfill({ message: result.message }); + } else { + if (mismatchPixelsCount == -1) { + msg = 'Image comparison failed, reference image: ' + expectedImageName + + ', difference in image size with: W=' + comparisonResult.dimensionDifference.width + + 'px, H=' + comparisonResult.dimensionDifference.height + 'px'; + } else { + msg = 'Image comparison failed, reference image: ' + expectedImageName + + ', difference in percentages: ' + mismatchPercentage + '% (threshold: ' + + that.thresholdPercentage + '%), difference in pixels: ' + mismatchPixelsCount + + ' (threshold: ' + resolvedPixelThreshold + ')'; + } - // pass - defer.fulfill({message: result.message}); - } + // handle image updates + if (that.update) { + msg += ' ,update enabled so storing current as reference'; + } else { + msg += ' ,update disabled'; + } + that.logger.debug(msg); + var res = { + message: msg, + details: { + refImageUrl: refImageResult.refImageUrl + }, + imageName: expectedImageName + }; - return result; + that._diffImageToBuffer(comparisonResult).then(function (diffImageBuffer) { + return that.storageProvider.storeRefActDiffImage( + expectedImageName, actualImageBuffer, diffImageBuffer, that.update); + }).then(function (storeRes) { + // ref should be left the ref image before update + res.details.actImageUrl = storeRes.actImageUrl; + res.details.diffImageUrl = storeRes.diffImageUrl; + res.failureType = 'COMPARISON'; + result.message = JSON.stringify(res); + // fail + defer.fulfill(false); + }) + .catch(function (error) { + result.message = error.stack; + // fail + defer.fulfill(false); + }); + } + }); } - }; - }; + }).catch(function (error) { + result.message = error.stack; + // fail + defer.fulfill(false); + }); + } else { + var msg = 'Comparison or screenshot taking disabled so skipping comparison'; + that.logger.debug(msg); + result.message = msg; + + // pass + defer.fulfill({ message: result.message }); + } - matchers.toLookAs = toLookAs; + return result; }; LocalComparisonProvider.prototype._diffImageToBuffer = function (comparisonResult) { @@ -271,6 +272,6 @@ LocalComparisonProvider.prototype._diffImageToBuffer = function (comparisonResul }); }; -module.exports = function (config,instanceConfig,logger,storageProvider) { - return new LocalComparisonProvider(config,instanceConfig,logger,storageProvider); +module.exports = function (config, instanceConfig, logger, storageProvider) { + return new LocalComparisonProvider(config, instanceConfig, logger, storageProvider); }; diff --git a/src/interface/connectionProvider.js b/src/interface/connectionProvider.js index d9f89479..a7dc9f3c 100644 --- a/src/interface/connectionProvider.js +++ b/src/interface/connectionProvider.js @@ -1,5 +1,3 @@ - -var _ = require('lodash'); var q = require('q'); /** @@ -37,26 +35,6 @@ ConnectionProvider.prototype.teardownEnv = function() { return q(); }; -/** - * Prepare capabilities object for this session - * @param {Runtime} runtime - required runtime for this session - * @return {Object} capabilities of this session - */ -ConnectionProvider.prototype.resolveCapabilitiesFromRuntime = function(runtime) { - - return this._mergeRuntimeCapabilities({},runtime); -}; - -ConnectionProvider.prototype._mergeRuntimeCapabilities = function(capabilities,runtime) { - // merge capabilities on root level - _.merge(capabilities,runtime.capabilities); - - // provide runtime in browser capabilities - capabilities.runtime = runtime; - - return capabilities; -}; - /** * Enrich runtime with actual capabilities of this session * @param {Object} capabilities - capabilities of this session diff --git a/src/plugins/plugins.js b/src/plugins/plugins.js index b5fc6346..423f87da 100644 --- a/src/plugins/plugins.js +++ b/src/plugins/plugins.js @@ -1,7 +1,9 @@ var pluginModules = []; var currentSpecDescription; -var Plugins = function (aPluginModules) { +var Plugins = function () {}; + +Plugins.prototype.loadModules = function (aPluginModules) { pluginModules = aPluginModules; }; @@ -29,6 +31,10 @@ Plugins.prototype.addPlugin = function (plugin) { pluginModules.push(plugin); }; +Plugins.prototype.getPlugins = function () { + return pluginModules; +}; + ['setup', 'onPrepare', 'teardown', 'onConnectionSetup', 'onUI5Sync', 'onElementAction'].forEach(function (sEvent) { Plugins.prototype[sEvent] = function () { return _callPlugins(sEvent, arguments); @@ -80,4 +86,4 @@ function _callJasmineSpecPlugins(method) { }; } -module.exports = Plugins; +module.exports = new Plugins(); diff --git a/src/ptor/frameworks/jasmine.js b/src/ptor/frameworks/jasmine.js index 78b6c648..c0757133 100644 --- a/src/ptor/frameworks/jasmine.js +++ b/src/ptor/frameworks/jasmine.js @@ -1,60 +1,11 @@ var q = require('q'); var webdriver = require('selenium-webdriver'); - -var RunnerReporter = function(emitter) { - this.emitter = emitter; - this.testResult = [], - this.failedCount = 0; -}; - -RunnerReporter.prototype.jasmineStarted = function() { - // Need to initiate startTime here, in case reportSpecStarting is not - // called (e.g. when fit is used) - this.startTime = new Date(); -}; - -RunnerReporter.prototype.specStarted = function() { - this.startTime = new Date(); -}; - -RunnerReporter.prototype.specDone = function(result) { - var specInfo = { - name: result.description, - category: result.fullName.slice(0, -result.description.length).trim() - }; - if (result.status == 'passed') { - this.emitter.emit('testPass', specInfo); - } else if (result.status == 'failed') { - this.emitter.emit('testFail', specInfo); - this.failedCount++; - } - - var entry = { - description: result.fullName, - assertions: [], - duration: new Date().getTime() - this.startTime.getTime() - }; - - if (result.failedExpectations.length === 0) { - entry.assertions.push({ - passed: true - }); - } - - result.failedExpectations.forEach(function(item) { - entry.assertions.push({ - passed: item.passed, - errorMsg: item.passed ? undefined : item.message, - stackTrace: item.passed ? undefined : item.stack - }); - }); - this.testResult.push(entry); -}; +var RunnerReporter = require('../../coreReporters/runnerReporter'); /** * Execute the Runner's test cases through Jasmine. * - * @param {Runner} runner The current Protractor Runner. + * @param {Runner} runner The current test Runner. * @param {Array} specs Array of Directory Path Strings. * @return {q.Promise} Promise resolved with the test results */ @@ -73,10 +24,7 @@ module.exports.run = function(runner, specs) { // to ensure that it runs after any afterEach() blocks with webdriver tasks // get to complete first. var reporter = new RunnerReporter(runner); - jasmine.getEnv().addReporter(reporter); - - // Add hooks for afterEach - require('./setupAfterEach').setup(runner, specs); + reporter.register(); // Filter specs to run based on jasmineNodeOpts.grep and jasmineNodeOpts.invert. jasmine.getEnv().specFilter = function(spec) { @@ -90,16 +38,6 @@ module.exports.run = function(runner, specs) { return true; }; - // Run specs in semi-random order - if (jasmineNodeOpts.random) { - jasmine.getEnv().randomizeTests(true); - - // Sets the randomization seed if randomization is turned on - if (jasmineNodeOpts.seed) { - jasmine.getEnv().seed(jasmineNodeOpts.seed); - } - } - return runner.runTestPreparer().then(function() { return q.promise(function(resolve, reject) { if (jasmineNodeOpts && jasmineNodeOpts.defaultTimeoutInterval) { diff --git a/src/ptor/frameworks/setupAfterEach.js b/src/ptor/frameworks/setupAfterEach.js deleted file mode 100644 index b7ef893a..00000000 --- a/src/ptor/frameworks/setupAfterEach.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Setup afterEach hook for jasmine tests. - * - * One of the main purposes of this file is to give `__protractor_internal_afterEach_setup_spec.js` - * a place to look up `runner.afterEach` at runtime without using globals. - * This file needs to be separate from `__protractor_internal_afterEach_setup_spec.js` so that that - * file is not prematurely executed. - */ - -var path = require('path'); - -// Queried by `protractor_internal_afterEach_setup_spec.js` for the `afterEach` hook -var hooks = { - afterEach: null -}; - -module.exports.hooks = hooks; - -/** - * Setup `runner.afterEach` to be called after every spec. - * - * @param {Runner} runner The current Protractor Runner. - * @param {Array} specs Array of Directory Path Strings. Must be a reference to the same array - * instance used by the framework - */ -module.exports.setup = function(runner, specs) { - hooks.afterEach = runner.afterEach.bind(runner); - specs.push(path.resolve(__dirname, '__protractor_internal_afterEach_setup_spec.js')); -}; diff --git a/src/ptor/util.js b/src/ptor/helper.js similarity index 99% rename from src/ptor/util.js rename to src/ptor/helper.js index 8b50f375..8114d832 100644 --- a/src/ptor/util.js +++ b/src/ptor/helper.js @@ -41,7 +41,7 @@ function runFilenameOrFn_(configDir, filenameOrFn, args) { throw new Error('filenameOrFn must be a string or function'); } if (typeof filenameOrFn === 'string') { - filenameOrFn = require(path.resolve(configDir, filenameOrFn)); + filenameOrFn = require(path.resolve(configDir, filenameOrFn))(args); } if (typeof filenameOrFn === 'function') { var results = q.when(filenameOrFn.apply(null, args), null, (err) => { diff --git a/src/ptor/launcher.js b/src/ptor/launcher.js index 937272ed..218933c4 100644 --- a/src/ptor/launcher.js +++ b/src/ptor/launcher.js @@ -11,7 +11,7 @@ var exitCodes = require('./exitCodes'); var TaskScheduler = require('./taskScheduler').TaskScheduler; var logger = require('../logger'); -var helper = require('./util'); +var helper = require('./helper'); var TaskRunner = require('./taskRunner').TaskRunner; var RUNNERS_FAILED_EXIT_CODE = 100; @@ -91,7 +91,7 @@ var taskResults_ = new TaskResults(); * * @param {Object=} config */ -var init = function (config, connectionProvider, plugins) { +var init = function (config) { var configParser = new ConfigParser(); configParser.addConfig(config); config = configParser.getConfig(); @@ -102,13 +102,10 @@ var init = function (config, connectionProvider, plugins) { .then(() => { return q .Promise((resolve, reject) => { - // 1) If getMultiCapabilities is set, resolve that as - // `multiCapabilities`. - if (config.getMultiCapabilities && - typeof config.getMultiCapabilities === 'function') { + // 1) If getMultiCapabilities is set, resolve that as `multiCapabilities`. + if (config.getMultiCapabilities && typeof config.getMultiCapabilities === 'function') { if (config.multiCapabilities.length || config.capabilities) { - logger.info('getMultiCapabilities() will override both capabilities ' + - 'and multiCapabilities'); + logger.info('getMultiCapabilities() will override both capabilities and multiCapabilities'); } // If getMultiCapabilities is defined and a function, use this. q(config.getMultiCapabilities()) @@ -128,23 +125,19 @@ var init = function (config, connectionProvider, plugins) { } }) .then(() => { - // 2) Set `multicapabilities` using `capabilities`, - // `multicapabilities`, - // or default + // 2) Set `multicapabilities` using `capabilities`, `multicapabilities`, or default if (config.capabilities) { if (config.multiCapabilities.length) { - logger.info('You have specified both capabilities and ' + - 'multiCapabilities. This will result in capabilities being ' + - 'ignored'); - } - else { + logger.info('You have specified both capabilities and multiCapabilities. This will result in capabilities being ignored'); + } else { // Use capabilities if multiCapabilities is empty. config.multiCapabilities = [config.capabilities]; } - } - else if (!config.multiCapabilities.length) { + } else if (!config.multiCapabilities.length) { // Default to chrome if no capabilities given - config.multiCapabilities = [{ browserName: 'chrome' }]; + config.multiCapabilities = [{ + browserName: 'chrome' + }]; } }); }) @@ -209,7 +202,7 @@ var init = function (config, connectionProvider, plugins) { var createNextTaskRunner = () => { var task = scheduler.nextTask(); if (task) { - var taskRunner = new TaskRunner(config, task, forkProcess, connectionProvider, plugins); + var taskRunner = new TaskRunner(config, task, forkProcess); taskRunner.run() .then((result) => { if (result.exitCode && !result.failedCount) { @@ -232,8 +225,7 @@ var init = function (config, connectionProvider, plugins) { }; // Start `scheduler.maxConcurrentTasks()` workers for handling tasks in // the beginning. As a worker finishes a task, it will pick up the next - // task - // from the scheduler's queue until all tasks are gone. + // task from the scheduler's queue until all tasks are gone. for (var i = 0; i < scheduler.maxConcurrentTasks(); ++i) { createNextTaskRunner(); } diff --git a/src/ptor/runner.js b/src/ptor/runner.js index 179e6950..c275fb51 100644 --- a/src/ptor/runner.js +++ b/src/ptor/runner.js @@ -7,7 +7,9 @@ var util = require('util'); var browser = require('../browser/browser'); var logger = require('../logger'); var ptor = require('./ptor'); -var helper = require('./util'); +var helper = require('./helper'); +var uiveri5Plugins = require('../plugins/plugins'); +var connectionProvider = require('../connection/connectionProvider'); /* * Runner is responsible for starting the execution of a test run and triggering @@ -21,15 +23,18 @@ var helper = require('./util'); * @param {Object} config * @constructor */ -function Runner(config, connectionProvider, plugins) { +function Runner(config) { events.EventEmitter.apply(this, arguments); this.config_ = config; - this.plugins = plugins; - this.connectionProvider = connectionProvider; if (config.capabilities && config.capabilities.seleniumAddress) { config.seleniumAddress = config.capabilities.seleniumAddress; } + + logger.setLevel(config.verbose); + + this.moduleLoader = require('../moduleLoader')(config); + uiveri5Plugins.loadModules(this.moduleLoader.loadModule('plugins')); this.loadDriverProvider_(config); this.setTestPreparer(config.onPrepare); @@ -78,11 +83,10 @@ Runner.prototype.runTestPreparer = function (extraFlags) { } if (unknownFlags.length > 0 && !this.config_.disableChecks) { logger.info('Ignoring unknown extra flags: ' + unknownFlags.join(', ') + '. This will be' + - ' an error in future versions, please use --disableChecks flag to disable the ' + - ' Protractor CLI flag checks. '); + ' an error in future versions, please use --disableChecks flag to disable the CLI flag checks.'); } - return this.plugins.onPrepare().then(() => { - return helper.runFilenameOrFn_(this.config_.configDir, this.preparer_); + return uiveri5Plugins.onPrepare().then(() => { + return helper.runFilenameOrFn_(this.config_.configDir, this.preparer_, this.config_); }); }; @@ -106,19 +110,17 @@ Runner.prototype.afterEach = function () { }; /** - * Grab driver provider based on type + * get uiveri5 direct driver provider * @private - * - * Priority - * 1) if directConnect is true, use that - * 2) if seleniumAddress is given, use that - * 3) if a Sauce Labs account is given, use that - * 4) if a seleniumServerJar is specified, use that - * 5) try to find the seleniumServerJar in protractor/selenium */ Runner.prototype.loadDriverProvider_ = function (config) { + connectionProvider.verifyConfig(config); this.config_ = config; - this.driverprovider_ = this.connectionProvider.buildDriverProvider(this.config_); + + this.connection = this.moduleLoader.loadNamedModule('connection'); + connectionProvider.setConnection(this.connection); + + this.driverprovider_ = this.connection.buildDriverProvider(this.config_); }; /** @@ -197,7 +199,7 @@ Runner.prototype.createBrowser = function (plugins, parentBrowser) { } var browser_ = new browser.Browser(driver, initProperties.baseUrl, initProperties.rootElement); browser_.params = initProperties.params; - browser_.plugins_ = plugins || this.plugins; + browser_.plugins_ = plugins || uiveri5Plugins; if (initProperties.getPageTimeout) { browser_.getPageTimeout = initProperties.getPageTimeout; } @@ -241,7 +243,8 @@ Runner.prototype.createBrowser = function (plugins, parentBrowser) { * @private */ Runner.prototype.shutdown_ = function () { - return this.driverprovider_.teardownEnv(); + logger.debug('Tearing down connection provider environment'); + return this.connection.teardownEnv().then(() => this.driverprovider_.teardownEnv()); }; /** @@ -252,7 +255,6 @@ Runner.prototype.shutdown_ = function () { */ Runner.prototype.run = function () { var testPassed; - var plugins = this.plugins; var browser_; var results; if (this.config_.framework !== 'explorer' && !this.config_.specs.length) { @@ -261,10 +263,11 @@ Runner.prototype.run = function () { if (this.config_.SELENIUM_PROMISE_MANAGER != null) { selenium_webdriver.promise.USE_PROMISE_MANAGER = this.config_.SELENIUM_PROMISE_MANAGER; } - return this.driverprovider_.setupEnv() + return this.connection.setupEnv(this.config_.multiCapabilities) + .then(this.driverprovider_.setupEnv) .then(() => { // 2) Create a browser and setup globals - browser_ = this.createBrowser(plugins); + browser_ = this.createBrowser(uiveri5Plugins); this.setupGlobals_(browser_); return browser_.ready.then(browser_.getSession) .then((session) => { @@ -277,7 +280,7 @@ Runner.prototype.run = function () { // 3) Setup plugins }) .then(() => { - return plugins.setup(); + return uiveri5Plugins.setup(); // 4) Execute test cases }) .then(() => { @@ -300,7 +303,7 @@ Runner.prototype.run = function () { results = testResults; }) .then(() => { - return plugins.teardown(); + return uiveri5Plugins.teardown(); // 7) Teardown }) .then(() => { @@ -320,7 +323,7 @@ Runner.prototype.run = function () { return this.exit_(exitCode); }) .catch(function (e) { - logger.debug('Ptor runner error: ' + e); + logger.debug('UIVeri5 runner error: ' + e); }) .fin(() => { return this.shutdown_(); diff --git a/src/ptor/runnerCli.js b/src/ptor/runnerCli.js new file mode 100644 index 00000000..80c3e3c6 --- /dev/null +++ b/src/ptor/runnerCli.js @@ -0,0 +1,49 @@ +/** + * This serves as the main function for starting a test run that has been requested by the launcher. + */ +var ConfigParser = require('./configParser').ConfigParser; +var logger = require('../logger'); +var Runner = require('./runner').Runner; + +process.on('message', (message) => { + switch (message.command) { + case 'run': + if (!message.capabilities) { + throw new Error('Run message missing capabilities'); + } + // Merge in config file options. + var configParser = new ConfigParser(); + if (message.additionalConfig) { + configParser.addConfig(message.additionalConfig); + } + var config = configParser.getConfig(); + // Grab capabilities to run from launcher. + config.capabilities = message.capabilities; + // Get specs to be executed by this runner + config.specs = message.specs; + + // Launch test run. + var runner = new Runner(config); + // Pipe events back to the launcher. + runner.on('testPass', () => { + process.send({ event: 'testPass' }); + }); + runner.on('testFail', () => { + process.send({ event: 'testFail' }); + }); + runner.on('testsDone', (results) => { + process.send({ event: 'testsDone', results: results }); + }); + runner.run() + .then((exitCode) => { + process.exit(exitCode); + }) + .catch((err) => { + logger.info(err.message); + process.exit(1); + }); + break; + default: + throw new Error('command ' + message.command + ' is invalid'); + } +}); diff --git a/src/ptor/taskRunner.js b/src/ptor/taskRunner.js index 0c352645..87e6a057 100644 --- a/src/ptor/taskRunner.js +++ b/src/ptor/taskRunner.js @@ -3,6 +3,7 @@ var child_process = require('child_process'); var events = require('events'); var q = require('q'); +var logger = require('../logger'); var ConfigParser = require('./configParser').ConfigParser; var TaskLogger = require('./taskLogger').TaskLogger; @@ -10,8 +11,8 @@ var Runner = require('./runner').Runner; /** * A runner for running a specified task (capabilities + specs). - * The TaskRunner can either run the task from the current process (via - * './runner.js') or from a new process (via './runnerCli.js'). + * The TaskRunner can either run the task from the current process (via './runner.js') + * or from a new process (via './runnerCli.js'). * * @constructor * @param {object} additionalConfig Additional configuration. @@ -19,13 +20,11 @@ var Runner = require('./runner').Runner; * @param {boolean} runInFork Whether to run test in a forked process. * @constructor */ -function TaskRunner (additionalConfig, task, runInFork, connectionProvider, plugins) { +function TaskRunner (additionalConfig, task, runInFork) { events.EventEmitter.apply(this, arguments); this.additionalConfig = additionalConfig; this.task = task; this.runInFork = runInFork; - this.connectionProvider = connectionProvider; - this.plugins = plugins; } TaskRunner.prototype = Object.assign({}, events.EventEmitter.prototype); @@ -39,7 +38,7 @@ TaskRunner.prototype.constructor = TaskRunner; * taskId, specs, capabilities, failedCount, exitCode, specResults */ TaskRunner.prototype.run = function () { - var that = this; + var runResults = { taskId: this.task.taskId, specs: this.task.specs, @@ -57,9 +56,14 @@ TaskRunner.prototype.run = function () { var config = configParser.getConfig(); config.capabilities = this.task.capabilities; config.specs = this.task.specs; + if (this.runInFork) { + logger.debug('Running multiple browsers in child processes'); var deferred = q.defer(); - var childProcess = child_process.fork(__dirname + '/runnerCli.js', process.argv.slice(2), { cwd: process.cwd(), silent: true }); + var childProcess = child_process.fork(__dirname + '/runnerCli.js', process.argv.slice(2), { + cwd: process.cwd(), + silent: true + }); var taskLogger = new TaskLogger(this.task, childProcess.pid); // stdout pipe childProcess.stdout.on('data', (data) => { @@ -70,11 +74,11 @@ TaskRunner.prototype.run = function () { taskLogger.log(data); }); childProcess - .on('message', (m) => { + .on('message', (message) => { if (config.verboseMultiSessions) { taskLogger.peek(); } - switch (m.event) { + switch (message.event) { case 'testPass': process.stdout.write('.'); break; @@ -82,8 +86,8 @@ TaskRunner.prototype.run = function () { process.stdout.write('F'); break; case 'testsDone': - runResults.failedCount = m.results.failedCount; - runResults.specResults = m.results.specResults; + runResults.failedCount = message.results.failedCount; + runResults.specResults = message.results.specResults; break; } }) @@ -96,16 +100,18 @@ TaskRunner.prototype.run = function () { runResults.exitCode = code; deferred.resolve(runResults); }); + childProcess.send({ command: 'run', additionalConfig: this.additionalConfig, capabilities: this.task.capabilities, specs: this.task.specs }); + return deferred.promise; - } - else { - var runner = new Runner(config, that.connectionProvider, that.plugins); + } else { + logger.debug('Running one browser in the current process'); + var runner = new Runner(config); runner.on('testsDone', (results) => { runResults.failedCount = results.failedCount; runResults.specResults = results.specResults; diff --git a/src/ptor/taskScheduler.js b/src/ptor/taskScheduler.js index 86321fe9..39c1b238 100644 --- a/src/ptor/taskScheduler.js +++ b/src/ptor/taskScheduler.js @@ -2,6 +2,19 @@ var ConfigParser = require('./configParser').ConfigParser; +/** + * properties for parallelization and multi browser support: + * as taken from https://github.com/angular/protractor/blob/master/lib/config.ts + * - count - Number of times to run this set of capabilities (in parallel, unless limited by maxSessions). Default is 1. + * - shardTestFiles - If this is set to be true, specs will be sharded by file + * (i.e. all files to be run by this set of capabilities will run in parallel). Default is false. + * - maxInstances - Maximum number of browser instances that can run in parallel for this set of capabilities. + * This is only needed if shardTestFiles is true. Default is in UIVeri5 (and 1 in protractor). + * - maxSessions - Maximum number of total browser sessions to run. + * Tests are queued in sequence if number of browser sessions is limited by this parameter. + * Use a number less than 1 to denote unlimited. Default is unlimited. + */ + /** * The taskScheduler keeps track of the spec files that needs to run next * and which task is running what. @@ -33,6 +46,7 @@ function TaskScheduler(config) { .filter((path) => { return excludes.indexOf(path) < 0; }); + var taskQueues = []; config.multiCapabilities.forEach((capabilities) => { var capabilitiesSpecs = allSpecs; @@ -50,14 +64,18 @@ function TaskScheduler(config) { // If we shard, we return an array of one element arrays, each containing // the spec file. If we don't shard, we return an one element array // containing an array of all the spec files + if (capabilities.shardTestFiles) { capabilitiesSpecs.forEach((spec) => { specLists.push([spec]); }); - } - else { + + // always allow to run the maximum number of browsers in parallel + capabilities.maxInstances = capabilities.maxInstances || config.specs.length; + } else { specLists.push(capabilitiesSpecs); } + capabilities.count = capabilities.count || 1; for (var i = 0; i < capabilities.count; ++i) { taskQueues.push(new TaskQueue(capabilities, specLists)); @@ -77,8 +95,7 @@ TaskScheduler.prototype.nextTask = function () { for (var i = 0; i < this.taskQueues.length; ++i) { var rotatedIndex = ((i + this.rotationIndex) % this.taskQueues.length); var queue = this.taskQueues[rotatedIndex]; - if (queue.numRunningInstances < queue.maxInstance && - queue.specsIndex < queue.specLists.length) { + if (queue.numRunningInstances < queue.maxInstance && queue.specsIndex < queue.specLists.length) { this.rotationIndex = rotatedIndex + 1; ++queue.numRunningInstances; var taskId = '' + rotatedIndex + 1; diff --git a/src/reporter/jsonReporter.js b/src/reporter/jsonReporter.js index 6a412f34..f01aa8a5 100644 --- a/src/reporter/jsonReporter.js +++ b/src/reporter/jsonReporter.js @@ -55,7 +55,7 @@ JasmineJsonReporter.prototype.suiteDone = function() { JasmineJsonReporter.prototype.jasmineDone = function() { var overview = this.collector.getOverview(); - var jsonReport = JSON.stringify(overview); + var jsonReport = JSON.stringify(overview, null, 2); utils.saveReport(this.reportName, jsonReport); }; diff --git a/src/runtimeCapabilitiesResolver.js b/src/runtimeCapabilitiesResolver.js new file mode 100644 index 00000000..663bafcd --- /dev/null +++ b/src/runtimeCapabilitiesResolver.js @@ -0,0 +1,87 @@ +var _ = require('lodash'); + +function RuntimeCapabilitiesResolver() { + this.runtimes = []; +} + +/** + * Prepare capabilities object for this session + * @param {Runtime} runtime - required runtime for this session + * @return {Object} capabilities of this session + */ +RuntimeCapabilitiesResolver.prototype.resolveCapabilitiesFromRuntime = function (runtime) { + // save this runtime so setupEnv() could download respective drivers + this.runtimes.push(runtime); + + var capabilities = {}; + + // capabilities according to: + // https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities + // http://appium.io/slate/en/master/?ruby#appium-server-capabilities + + // format browserName + if (runtime.platformName === 'android' || runtime.platformName === 'ios') { + capabilities.browserName = runtime.browserName.charAt(0).toUpperCase() + runtime.browserName.slice(1); + } else { + if (runtime.browserName === 'ie') { + capabilities.browserName = 'internet explorer'; + } else if (runtime.browserName === 'edge') { + capabilities.browserName = 'MicrosoftEdge'; + } else { + capabilities.browserName = runtime.browserName; + } + } + + // format browserVersion + if (runtime.browserVersion !== '*') { + capabilities.version = runtime.browserVersion; + } + + // format platformName + if (runtime.platformName === 'windows') { + if (runtime.platformVersion === '*') { + capabilities.platform = 'WINDOWS'; + } else if (runtime.platformVersion === 'XP') { + capabilities.platform = 'XP'; + } else if (runtime.platformVersion === 'VISTA' || runtime.platformVersion === '7') { + capabilities.platform = 'VISTA'; + } else if (runtime.platformVersion === '8') { + capabilities.platform = 'WIN8'; + } else if (runtime.platformVersion === '8.1') { + capabilities.platform = 'WIN8_1'; + } else { + throw Error('Platform version: ' + runtime.platformVersion + + ' for platformName: WINDOWS is not supported by directConnectionProvider'); + } + } else if (runtime.platformName === 'linux' || runtime.platformName === 'mac') { + capabilities.platform = runtime.platformName.toUpperCase(); + } else if (runtime.platformName === 'ios') { + capabilities.platformName = 'iOS'; + } else if (runtime.platformName === 'android') { + capabilities.platformName = 'Android'; + } else { + throw Error('Platform name: ' + runtime.platformName + + ' not supported by directConnectionProvider'); + } + + // format platformVersion + if (runtime.platformVersion !== '*') { + capabilities.platformVersion = runtime.platformVersion; + } + + return this._mergeRuntimeCapabilities(capabilities, runtime); +}; + +RuntimeCapabilitiesResolver.prototype._mergeRuntimeCapabilities = function (capabilities, runtime) { + // merge capabilities on root level + _.merge(capabilities, runtime.capabilities); + + // provide runtime in browser capabilities + capabilities.runtime = runtime; + + return capabilities; +}; + +module.exports = function () { + return new RuntimeCapabilitiesResolver(); +}; diff --git a/src/runtimeResolver.js b/src/runtimeResolver.js index 3383e90f..b028f180 100644 --- a/src/runtimeResolver.js +++ b/src/runtimeResolver.js @@ -1,5 +1,6 @@ var _ = require('lodash'); +var logger = require('./logger'); var DEFAULT_BROWSER_NAME = 'chrome'; var DEFAULT_VERSION = '*'; @@ -63,11 +64,9 @@ var supportedUI5Modes = [ * Resolves runtime * @constructor * @param {RuntimeResolverConfig} config - * @param {Logger} logger */ -function RuntimeResolver(config, logger) { +function RuntimeResolver(config) { this.config = config; - this.logger = logger; } /** @@ -153,7 +152,7 @@ RuntimeResolver.prototype.resolveRuntimes = function(){ that._mergeMatchingCapabilities(runtime,that.config.browserCapabilities); } - that.logger.debug('Resolved runtime: ' + JSON.stringify(runtime)); + logger.debug('Resolved runtime: ' + JSON.stringify(runtime)); }); return runtimes; @@ -215,6 +214,6 @@ RuntimeResolver.prototype._isMatching = function(name,pattern){ return matchingFlag; }; -module.exports = function (config, logger) { - return new RuntimeResolver(config, logger); +module.exports = function (config) { + return new RuntimeResolver(config); }; diff --git a/src/statisticCollector.js b/src/statisticCollector.js index 6f1aaa24..aba6fbec 100644 --- a/src/statisticCollector.js +++ b/src/statisticCollector.js @@ -92,11 +92,7 @@ */ function StatisticCollector(){ - // @type Overview - this.overview = { - suites: [], - statistic: {} - }; + this.reset(); this.currentSuite = null; this.currentSpec = null; @@ -462,6 +458,12 @@ StatisticCollector.prototype.getCurrentSuite = function(){ return this.currentSuite; }; -module.exports = function(){ - return new StatisticCollector(); +StatisticCollector.prototype.reset = function () { + // @type Overview + this.overview = { + suites: [], + statistic: {} + }; }; + +module.exports = new StatisticCollector(); diff --git a/src/testPreparer.js b/src/testPreparer.js new file mode 100644 index 00000000..f0d1a75d --- /dev/null +++ b/src/testPreparer.js @@ -0,0 +1,89 @@ +// file will be executed by Runner as onPrepare + +var logger = require('./logger'); +var plugins = require('./plugins/plugins'); +var pageObjectFactory = require('./pageObjectFactory'); + +var AUTH_CONFIG_NAME = 'auth'; + +module.exports = function (config) { + // config is the test Runner's config + browser.setConfig(config); + + browser.controlFlow().execute(function () { + logger.debug('Runtime resolved from capabilities: ' + JSON.stringify(config.multiCapabilities)); + + browser.setRuntime(config.multiCapabilities); + + var moduleLoader = require('./moduleLoader')(config); + + // register screenshot provider + var screenshotProvider = moduleLoader.loadModuleIfAvailable('screenshotProvider', [config.multiCapabilities]); + if (screenshotProvider) { + screenshotProvider.register(); + } + + // load storage provider + var storageProvider = moduleLoader.loadModuleIfAvailable('storageProvider', [config.multiCapabilities]); + + plugins.loadJasminePlugins(); + + // add global matchers + beforeEach(function () { + moduleLoader.loadModuleIfAvailable('matchers', []).forEach(function (matchersModule) { + jasmine.getEnv().addMatchers(matchersModule.getMatchers()); + }); + // load comparison provider and register the custom matcher + var comparisonProvider = moduleLoader.loadModuleIfAvailable('comparisonProvider', [storageProvider]); + if (comparisonProvider) { + comparisonProvider.register(); + } + }); + + browser.setInitialWindowSize(); + + // initialize statistic collector + var statisticCollector = require('./statisticCollector'); + require('./coreReporters/statisticReporter').register(); + + require('./coreReporters/paramsReporter')(config).register(); + require('./coreReporters/specLifecycleReporter')(config, storageProvider).register(); + + var authConfigModule; + // expose navigation helpers to tests + browser.testrunner.navigation._getAuthenticator = function (authConfig) { + var authenticator; + if (authConfig) { + // programatically invoked authentication - load auth module every time + authenticator = moduleLoader.loadNamedModule(authConfig, [statisticCollector]); + } else { + // auth is declared in config - load the (global) auth module only once. + // when authOnce is enabled, auth should be done only once - before the first spec file. + if (authConfigModule) { + authenticator = config.authOnce ? null : authConfigModule; + } else { + authenticator = authConfigModule = moduleLoader.loadNamedModule(AUTH_CONFIG_NAME, [statisticCollector]); + } + } + return authenticator; + }; + + var expectationInterceptor = require('./reporter/expectationInterceptor'); + // register reporters + var jasmineEnv = jasmine.getEnv(); + moduleLoader.loadModule('reporters', [statisticCollector, expectationInterceptor]).forEach(function (reporter) { + reporter.register(jasmineEnv); + }); + + // add additional locators + moduleLoader.loadModule('locators', [statisticCollector]).forEach(function (locator) { + locator.register(by); + }); + + // register page object factory on global scope + logger.debug('Loading BDD-style page object factory'); + pageObjectFactory.register(global); + }).catch(function (error) { + logger.debug('Test preparer failed! ' + error); + }); +}; diff --git a/src/uiveri5.js b/src/uiveri5.js index f8e23f35..970979d1 100644 --- a/src/uiveri5.js +++ b/src/uiveri5.js @@ -2,15 +2,10 @@ * uses modules copied from protractor@5.3.2 */ -var fs = require('fs'); var _ = require('lodash'); -var url = require('url'); -var pageObjectFactory = require('./pageObjectFactory'); -var Plugins = require('./plugins/plugins'); +var path = require('path'); var logger = require('./logger'); - -var DEFAULT_CONNECTION_NAME = 'direct'; -var AUTH_CONFIG_NAME = 'auth'; +var utils = require('./configUtils'); /** * @typedef Config @@ -26,11 +21,10 @@ var AUTH_CONFIG_NAME = 'auth'; * @property {[]} browsers - list of browsers to drive. Single word is assumed to * be browserName, supports column delimited and json formats, overwrites *.conf.js values, defaults to: 'chrome' * @property {Object} params - params object to be passed to the tests - * @property {boolean} ignoreSync - disables waitForUI5 synchronization, defaults to: false */ /** - * Runs visual tests + * Runs e2e tests * @param {Config} config - configs * @return {Promise} resolved on success or rejected with error */ @@ -38,45 +32,30 @@ function run(config) { logger.setLevel(config.verbose); - // log framework version - var pjson = require('../package.json'); - logger.info(pjson.name + ' v' + pjson.version); + utils.logFrameworkVersion(); - // log config object so far - logger.debug('Config from command-line: ${JSON.stringify(config)}',{config:config}); + logger.debug('Config from command-line: ' + JSON.stringify({ config: config })); // merge in config files var configParser = require('./configParser')(logger); config = configParser.mergeConfigs(config); - config.osTypeString = (function() { - var os = require('os'); - var osType = ''; - - if (os.type() == 'Darwin') { - osType = 'mac64'; - } else if (os.type() == 'Linux') { - if (os.arch() == 'x64') { - osType = 'linux64'; - } else { - osType = 'linux32'; - } - } else if (os.type() == 'Windows_NT') { - osType = 'win32'; - } else { - osType = 'unknown'; - } - - return osType; - })(); + config.osTypeString = utils.getOSType(); var moduleLoader = require('./moduleLoader')(config); - var plugins = new Plugins(moduleLoader.loadModule('plugins')); // resolve runtime and set browsers with capabilities - var runtimeResolver = require('./runtimeResolver')(config,logger); + var runtimeResolver = require('./runtimeResolver')(config, logger); config.runtimes = runtimeResolver.resolveRuntimes(); + // resolve browsers capabilities from runtime + var runtimeCapabilitiesResolver = require('./runtimeCapabilitiesResolver')(); + var multiCapabilities = config.runtimes.map(function (runtime) { + // prepare capabilities from runtime for this specific connection type + return runtimeCapabilitiesResolver.resolveCapabilitiesFromRuntime(runtime); + }); + logger.debug('Resolved multiCapabilities: ' + JSON.stringify({ multiCapabilities: multiCapabilities })); + // resolve all placeholders in config configParser.resolvePlaceholders(config); @@ -84,9 +63,8 @@ function run(config) { logger.setLevel(config.verbose); // log config object so far - logger.debug('Config after resolving config file and profile: ${JSON.stringify(config)}',{config:config}); + logger.debug('Config after resolving config file and profile: ' + JSON.stringify({ config: config })); - // log cwd logger.debug('Current working directory: ' + process.cwd()); // load spec resolver @@ -95,298 +73,36 @@ function run(config) { // resolve specs. // specs refers to the collection of found .spec.js files (a file containing Jasmine suites) logger.info('Resolving specs'); - return specResolver.resolve().then(function(specs){ - if (!specs || specs.length==0){ + return specResolver.resolve().then(function (specs) { + if (!specs || specs.length == 0) { throw Error('No specs found'); } - // resolve connection - var connectionName = config.connection || DEFAULT_CONNECTION_NAME; - var connectionConfig = config.connectionConfigs[connectionName]; - if (!connectionConfig){ - throw Error('Could not find connection: ' + connectionName); - } - - // create connectionProvider - var connectionProvider = moduleLoader.loadNamedModule('connection', [plugins]); - - // prepare executor args - var launcherArgv = connectionProvider.buildLauncherArgv(); - - // enable debug logs - launcherArgv.troubleshoot = config.verbose > 0; - - // add baseUrl - launcherArgv.baseUrl = config.baseUrl; - - // use jasmine 2.0 - launcherArgv.framework = 'jasmine2'; - launcherArgv.jasmineNodeOpts = {}; - - // disable default jasmine console reporter - launcherArgv.jasmineNodeOpts.print = function() {}; - - // copy timeouts - if (config.timeouts){ - if (config.timeouts.getPageTimeout){ - var getPageTimeout = config.timeouts.getPageTimeout; - if(_.isString(getPageTimeout)){ - getPageTimeout = parseInt(getPageTimeout,10); - } - logger.debug('Setting getPageTimeout: ' + getPageTimeout); - launcherArgv.getPageTimeout = getPageTimeout; - } - if (config.timeouts.allScriptsTimeout){ - var allScriptsTimeout = config.timeouts.allScriptsTimeout; - if(_.isString(allScriptsTimeout)){ - allScriptsTimeout = parseInt(allScriptsTimeout,10); - } - logger.debug('Setting allScriptsTimeout: ' + allScriptsTimeout); - launcherArgv.allScriptsTimeout = allScriptsTimeout; - } - if (config.timeouts.defaultTimeoutInterval){ - var defaultTimeoutInterval = config.timeouts.defaultTimeoutInterval; - if(_.isString(defaultTimeoutInterval)){ - defaultTimeoutInterval = parseInt(defaultTimeoutInterval,10); - } - logger.debug('Setting defaultTimeoutInterval: ' + defaultTimeoutInterval); - launcherArgv.jasmineNodeOpts.defaultTimeoutInterval = defaultTimeoutInterval; - } - } - - // set specs - launcherArgv.specs = []; - specs.forEach(function(spec){ - launcherArgv.specs.push(spec.testPath); + var launcherArgv = _.extend(config, { + specs: _.map(specs, 'testPath'), + specsWithDetails: specs, + multiCapabilities: multiCapabilities, + // require direct connection as directDriverProvider will be overtaken later + directConnect: true, + troubleshoot: config.verbose > 0, + baseUrl: config.baseUrl, + // use jasmine 2.0 + framework: 'jasmine2', + jasmineNodeOpts: { + // disable default jasmine console reporter + print: function () { } + }, + // onPrepare is executed after test env setup and just before test execution starts (possibly in child process) + // use filename relative to launcher - instead of function, to support multi browser + onPrepare: path.join(__dirname, './testPreparer') }); - // resolve browsers capabilities from runtime - launcherArgv.multiCapabilities = config.runtimes.map(function(runtime){ - // prepare capabilities from runtime for this specific connection type - return connectionProvider.resolveCapabilitiesFromRuntime(runtime); - }); - logger.debug('Resolved multiCapabilities: ' + JSON.stringify(launcherArgv.multiCapabilities)); - - // TODO see driverProvider - // no way to implement concurrent executions with current driverProvider impl - // launcherArgv.maxSessions = 1; - - // execute after test env setup and just before test execution starts - launcherArgv.onPrepare = function () { - plugins.loadJasminePlugins(); - - browser.setConfig(config); - - var matchers = {}; - var storageProvider; - - // register a hook to be called after webdriver is created ( may not be connected yet ) - browser.getProcessedConfig().then(function (processedConfig) { - var runtime = connectionProvider.resolveRuntimeFromCapabilities(processedConfig.capabilities); - logger.debug('Runtime resolved from capabilities: ' + JSON.stringify(runtime)); - - browser.setRuntime(runtime); - - // register screenshot provider - var screenshotProvider = moduleLoader.loadModuleIfAvailable('screenshotProvider', [runtime]); - if (screenshotProvider) { - screenshotProvider.register(); - } - - // load storage provider - storageProvider = moduleLoader.loadModuleIfAvailable('storageProvider', [runtime]); + utils.copyTimeouts(launcherArgv, config); - // load comparison provider and register the custom matcher - var comparisonProvider = moduleLoader.loadModuleIfAvailable('comparisonProvider', [storageProvider]); - if (comparisonProvider) { - comparisonProvider.register(matchers); - } + logger.info('Executing ' + specs.length + ' specs'); - moduleLoader.loadModuleIfAvailable('matchers', []).forEach(function(matcher){ - matcher.register(matchers); - }); - - browser.setInitialWindowSize(); - }); - - // add global matchers - beforeEach(function() { - jasmine.getEnv().addMatchers(matchers); - }); - - // hook into specs lifecycle - // open test content page before every suite - jasmine.getEnv().addReporter({ - - jasmineStarted: function(){ - // call storage provider beforeAll hook - if (storageProvider && storageProvider.onBeforeAllSpecs){ - storageProvider.onBeforeAllSpecs(specs); - } - }, - - //TODO consider several describe() per spec file - suiteStarted: function(result){ - - // enclose all WebDriver operations in a new flow for a gracefull handling of failures - // will call jasmine.fail() that will handle the error - browser.controlFlow().execute(function() { - - var spec = _getSpecDetails(result); - var contentUrl = spec ? spec.contentUrl : config.baseUrl; - - // open content page if required - if (!contentUrl) { - logger.debug('Skip content page opening'); - return; - } - - // webdriverjs operations are inherently synchronized by webdriver flow - // so no need to synchronize manually with callbacks/promises - - // add request params - var specUrl = url.parse(contentUrl); - if (config.baseUrlQuery && config.baseUrlQuery.length >0){ - if (specUrl.search == null) { - specUrl.search = ''; - } - config.baseUrlQuery.forEach(function(value,index){ - if (index > 0){ - specUrl.search += '&'; - } - specUrl.search += value; - }); - } - - // open test page - var specUrlString = url.format(specUrl); - browser.testrunner.navigation.to(specUrlString).then(function () { - // call storage provider beforeEach hook - if (storageProvider && storageProvider.onBeforeEachSpec) { - storageProvider.onBeforeEachSpec(spec); - } - }); - }).catch(function(error){ - // the failure in reporter -> beforeAll will not stop further suite execution ! - // fail-fast was discussed here -> https://github.com/jasmine/jasmine/issues/778 - // stop the suite will require jasmin 3.0 -> https://github.com/jasmine/jasmine/issues/414 - // stop the spec when error require jasmin 2.4 -> https://jasmine.github.io/2.4/node.html#section-13 - // completing of this functionality in jasmine 2.8 -> https://github.com/jasmine/jasmine/issues/577 - // In jasmine 2.3 a throwOnExpectationFailure(true) was added -> https://stackoverflow.com/questions/22119193/stop-jasmine-test-after-first-expect-fails - // it does not make sense for us at it simply throws error from the first failed expectation and this kills the whole execution - fail(error); - }); - }, - - suiteDone: function(result){ - var spec = _getSpecDetails(result); - // call storage provider afterEach hook - if (spec && storageProvider && storageProvider.onAfterEachSpec){ - storageProvider.onAfterEachSpec(spec); - } - }, - - jasmineDone: function(){ - // call storage provider afterAll hook - if (storageProvider && storageProvider.onAfterAllSpecs){ - storageProvider.onAfterAllSpecs(specs); - } - } - }); - - // initialize statistic collector - var statisticCollector = require('./statisticCollector')(); - jasmine.getEnv().addReporter({ - jasmineStarted: function(){ - statisticCollector.jasmineStarted(); - }, - suiteStarted: function(jasmineSuite){ - statisticCollector.suiteStarted(jasmineSuite); - }, - specStarted: function(jasmineSpec){ - statisticCollector.specStarted(jasmineSpec); - }, - specDone: function(jasmineSpec){ - statisticCollector.specDone(jasmineSpec, browser.testrunner.currentSpec._meta); - delete browser.testrunner.currentSpec._meta; - }, - suiteDone: function(jasmineSuite){ - statisticCollector.suiteDone(jasmineSuite, browser.testrunner.currentSuite._meta); - delete browser.testrunner.currentSuite._meta; - }, - jasmineDone: function(){ - statisticCollector.jasmineDone({runtime:browser.testrunner.runtime}); - } - }); - - if (config.exportParamsFile) { - jasmine.getEnv().addReporter({ - jasmineDone: function () { - logger.debug('Exporting test params to file ' + config.exportParamsFile); - fs.writeFileSync(config.exportParamsFile, JSON.stringify(browser.testrunner.config.exportParams, null, 2)); - } - }); - } - - var authConfigModule; - // expose navigation helpers to tests - browser.testrunner.navigation._getAuthenticator = function (authConfig) { - var authenticator; - if (authConfig) { - // programatically invoked authentication - load auth module every time - authenticator = moduleLoader.loadNamedModule(authConfig, [statisticCollector]); - } else { - // auth is declared in config - load the (global) auth module only once. - // when authOnce is enabled, auth should be done only once - before the first spec file. - if (authConfigModule) { - authenticator = config.authOnce ? null : authConfigModule; - } else { - authenticator = authConfigModule = moduleLoader.loadNamedModule(AUTH_CONFIG_NAME, [statisticCollector]); - } - } - return authenticator; - }; - - var expectationInterceptor = require('./reporter/expectationInterceptor'); - // register reporters - var jasmineEnv = jasmine.getEnv(); - moduleLoader.loadModule('reporters',[statisticCollector, expectationInterceptor]).forEach(function(reporter){ - reporter.register(jasmineEnv); - }); - - // add additional locators - moduleLoader.loadModule('locators', [statisticCollector]).forEach(function (locator) { - locator.register(by); - }); - - }; - - launcherArgv.afterLaunch = function(){ - // teardown connection provider env - logger.debug('Tearing down connection provider environment'); - return connectionProvider.teardownEnv(); - }; - - // finds a UIVeri5 spec definition by a given Jasmine suite description - // i.e. given the description of one of the suites in a spec file, find the information for that file. - function _getSpecDetails(suite){ - return specs.filter(function (suiteDetails) { - return suiteDetails.fullName === suite.description; - })[0]; - } - - // register page object factory on global scope - logger.debug('Loading BDD-style page object factory'); - pageObjectFactory.register(global); - - // setup connection provider env - logger.debug('Setting up connection provider environment'); - return connectionProvider.setupEnv().then(function(){ - logger.info('Executing ' + specs.length + ' specs'); - - var launcher = require('./ptor/launcher'); - launcher.init(launcherArgv, connectionProvider, plugins); - }); + var launcher = require('./ptor/launcher'); + return launcher.init(launcherArgv); }); }