| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645 |
- var path = require('path');
- var util = require('util');
- var events = require('events');
- var Q = require('q');
- var ClientManager = require('./clientmanager.js');
- var Module = require('./module.js');
- var TestCase = require('./testcase.js');
- var Logger = require('../util/logger.js');
- var Utils = require('../util/utils.js');
- var DEFAULT_ASYNC_HOOK_TIMEOUT = 10000;
- function noop() {}
- function TestSuite(modulePath, fullPaths, opts, addtOpts) {
- events.EventEmitter.call(this);
- this['@testCase'] = null;
- this.deferred = Q.defer();
- this.module = new Module(modulePath, opts, addtOpts);
- this.setTestResult();
- this.currentTest = '';
- this.module.setReportKey(fullPaths, addtOpts.src_folders);
- this.options = opts;
- this.testMaxRetries = addtOpts.retries || 0;
- this.suiteMaxRetries = addtOpts.suite_retries || 0;
- this.suiteRetries = 0;
- this.suiteName = this.module.getTestSuiteName() || Utils.getTestSuiteName(this.module.moduleKey);
- this.setupClient();
- this.expectedAsyncArgs = this.options.compatible_testcase_support ? 1 : 2;
- this.asyncHookTimeout = this.client.globals('asyncHookTimeout') || DEFAULT_ASYNC_HOOK_TIMEOUT;
- this.currentHookTimeoutId = null;
- this.initHooks();
- }
- util.inherits(TestSuite, events.EventEmitter);
- TestSuite.prototype.setupClient = function() {
- this.updateDesiredCapabilities();
- this.client = new ClientManager();
- var self = this;
- this.client.on('error', function(err) {
- self.deferred.reject(err);
- });
- this.client.init(this.options);
- this.client.api('currentEnv', this.options.currentEnv);
- this.module.set('client', this.client.api());
- if (this.client.endSessionOnFail() && !this.module.endSessionOnFail()) {
- this.client.endSessionOnFail(false);
- }
- };
- TestSuite.prototype.initHooks = function() {
- var self = this;
- var context = this.module.get();
- var argsCount;
- this.hooks = {};
- var hooks = ['before', 'after', ['beforeEach', 'setUp']];
- hooks.forEach(function(item) {
- var key;
- if (Array.isArray(item)) {
- key = item[0];
- } else {
- key = item;
- }
- var index = self.testResults.steps.indexOf(key);
- if (index === -1 && Array.isArray(item)) {
- index = self.testResults.steps.indexOf(item[1]);
- }
- if (Array.isArray(item) && (item.length == 3)) {
- argsCount = item[2];
- } else {
- argsCount = self.expectedAsyncArgs;
- }
- self.hooks[key] = (function(item, argsCount) {
- return function() {
- return self.makePromise(function(done) {
- var callbackDeffered = false;
- var called = false;
- var originalFn = self.module.get(key);
- var doneFn = function(err) {
- called = true;
- if (err && key == 'after') {
- self.executeHookCallback(err, done);
- return;
- }
- if (callbackDeffered) {
- return;
- }
- return done(err, true);
- };
- self.runHookMethod(item, context, argsCount, key, doneFn, this.deferred);
- if (originalFn && (originalFn.length == self.expectedAsyncArgs) && self.client.shouldRestartQueue()) {
- callbackDeffered = true;
- var asyncDoneFn = function(err) {
- if (called) {
- done();
- } else {
- callbackDeffered = false;
- }
- };
- if (key == 'before' || key == 'beforeEach') {
- self.client.start(asyncDoneFn);
- } else if (key == 'after') {
- self.client.restartQueue(asyncDoneFn);
- }
- }
- });
- };
- })(item, argsCount);
- if (index > -1) {
- self.testResults.steps.splice(index, 1);
- self.module.removeKey(item);
- }
- });
- this.initAfterEachHook();
- };
- TestSuite.prototype.initAfterEachHook = function() {
- var self = this;
- var api = this.client.api();
- var module = this.module.get();
- var key = 'afterEach';
- var hookFn;
- var index = self.testResults.steps.indexOf(key);
- if (!module[key]) {
- // backwards compatibility
- key = 'tearDown';
- index = self.testResults.steps.indexOf(key);
- }
- if (module[key]) {
- hookFn = module[key];
- if (index > -1) {
- // not running with --testcase
- self.testResults.steps.splice(index, 1);
- self.module.removeKey(key);
- }
- } else {
- hookFn = function() {};
- }
- var expectedArgs = hookFn.length;
- this.hooks.afterEach = function(results, errors) {
- self.module
- .set('results', results)
- .set('errors', errors);
- var asyncFn, asyncArgsCount;
- if (expectedArgs <= 1) {
- asyncArgsCount = 1;
- } else {
- asyncArgsCount = 2;
- }
- asyncFn = Utils.makeFnAsync(asyncArgsCount, hookFn, module);
- return self.makePromise(function(done, deferred) {
- var doneFn = self.adaptDoneCallback(done, 'afterEach', deferred);
- self.client.publishTestResults(self.currentTest, self.module.get('results'), errors);
- if (expectedArgs < 2) {
- // user has only supplied the done callback argument (pre v0.6 behaviour), e.g.:
- // afterEach : function(done) { ... }
- asyncFn.call(module, doneFn);
- } else {
- // user has supplied both the client and the done callback argument (v0.6 behaviour), e.g.:
- // afterEach : function(browser, done) { ... }
- // in which case we may need to restart the queue if any selenium related actions are added
- if (self.options.compatible_testcase_support) {
- asyncFn.call(module, doneFn);
- } else {
- asyncFn.call(module, api, function(err) {
- self.executeHookCallback(err, doneFn);
- });
- }
- // this will restart the queue if needed
- self.client.checkQueue();
- }
- });
- };
- return this;
- };
- TestSuite.prototype.executeHookCallback = function(potentialErr, doneFn) {
- if (potentialErr instanceof Error) {
- this.testResults.errors++;
- this.client.handleException(potentialErr);
- }
- doneFn();
- };
- TestSuite.prototype.run = function() {
- var self = this;
- this.print();
- if (this.module.isDisabled()) {
- if (this.options.output) {
- console.log(Logger.colors.cyan(this.module.getName()), 'module is disabled, skipping...');
- }
- this.deferred.resolve(this.testResults);
- } else {
- this.setCurrentTest();
- this.globalBeforeEach()
- .then(function() {
- if (self.client.terminated() && self.client.skipTestcasesOnFail()) {
- self.testResults.errmessages = self.client.errors();
- self.deferred.resolve(self.testResults);
- return null;
- }
- return self.runTestSuiteModule();
- })
- .then(function(results) {
- return self.globalAfterEach();
- })
- .then(function() {
- self.deferred.resolve(self.testResults);
- })
- .catch(function(e) {
- self.deferred.reject(e);
- });
- }
- return this.deferred.promise;
- };
- TestSuite.prototype.getCurrentTestCase = function() {
- return this['@testCase'];
- };
- TestSuite.prototype.getReportKey = function() {
- return this.module.moduleKey;
- };
- TestSuite.prototype.getGroupName = function() {
- return this.module.groupName;
- };
- TestSuite.prototype.printResult = function(startTime) {
- return this.client.print(startTime);
- };
- TestSuite.prototype.shouldRetrySuite = function() {
- return this.suiteMaxRetries > this.suiteRetries && (this.testResults.failed > 0 || this.testResults.errors > 0);
- };
- TestSuite.prototype.setTestResult = function() {
- this.testResults = {};
- this.testResults.steps = this.module.keys.slice(0);
- this.clearTestResult();
- return this;
- };
- TestSuite.prototype.clearTestResult = function() {
- this.testResults.passed = 0;
- this.testResults.failed = 0;
- this.testResults.errors = 0;
- this.testResults.skipped = 0;
- this.testResults.tests = 0;
- this.testResults.testcases = {};
- this.testResults.timestamp = new Date().toUTCString();
- this.testResults.time = 0;
- return this;
- };
- TestSuite.prototype.clearResult = function() {
- this.clearTestResult();
- this.client.clearGlobalResult();
- return this;
- };
- TestSuite.prototype.printRetry = function() {
- if (this.options.output) {
- console.log('Retrying: ',
- Logger.colors.red('[' + this.suiteName + '] Test Suite '),
- '(' + this.suiteRetries + '/' + this.suiteMaxRetries + '): ');
- }
- };
- TestSuite.prototype.retryTestSuiteModule = function() {
- this.client.resetTerminated();
- this.client.setLocateStrategy();
- this.clearResult();
- this.suiteRetries +=1;
- this.resetTestCases();
- this.printRetry();
- return this.globalBeforeEach().then(function() {
- return this.runTestSuiteModule();
- }.bind(this));
- };
- TestSuite.prototype.runTestSuiteModule = function() {
- var self = this;
- return this.before()
- .then(function() {
- return self.runNextTestCase();
- })
- .then(function(skipped) {
- return self.after();
- })
- .then(function() {
- return self.client.checkQueue();
- })
- .then(function() {
- return self.shouldRetrySuite();
- })
- .then(function(shouldRetrySuite) {
- if (shouldRetrySuite) {
- return self.globalAfterEach().then(function() {
- return self.retryTestSuiteModule();
- });
- }
- })
- .catch(function(e) {
- self.testResults.errors++;
- throw e;
- });
- };
- TestSuite.prototype.onTestCaseFinished = function(results, errors, time) {
- this.testResults.time += time;
- this.testResults.testcases[this.currentTest] = this.testResults.testcases[this.currentTest] || {};
- this.testResults.testcases[this.currentTest].time = (time/1000).toPrecision(4);
- this.emit('testcase:finished', results, errors, time);
- };
- TestSuite.prototype.resetTestCases = function() {
- var self = this;
- this.module.resetKeys();
- Object.keys(this.hooks).forEach(function(hook) {
- self.module.removeKey(hook);
- });
- };
- TestSuite.prototype.setCurrentTest = function() {
- var moduleKey = this.getReportKey();
- this.client.clearGlobalResult();
- this.client.api('currentTest', {
- name : this.currentTest,
- module : moduleKey.replace(path.sep , '/'),
- results : this.testResults,
- group : this.getGroupName()
- });
- return this;
- };
- TestSuite.prototype.runNextTestCase = function(deferred) {
- this.currentTest = this.module.getNextKey();
- this.setCurrentTest();
- deferred = deferred || Q.defer();
- if (this.currentTest) {
- this.testResults.steps.splice(this.testResults.steps.indexOf(this.currentTest), 1);
- this.runTestCase(this.currentTest, deferred, 0);
- } else {
- deferred.resolve();
- }
- return deferred.promise;
- };
- TestSuite.prototype.runTestCase = function(currentTest, deferred, numRetries) {
- var self = this;
- this['@testCase'] = new TestCase(this, currentTest, numRetries, this.testMaxRetries);
- if (self.client.terminated() && self.client.skipTestcasesOnFail()) {
- deferred.resolve(self.module.keys);
- return deferred;
- }
- this['@testCase'].print().run().then(function(response) {
- var foundFailures = !!(response.results.failed || response.results.errors);
- if (foundFailures && numRetries < self.testMaxRetries) {
- numRetries++;
- self.client.resetTerminated();
- self.clearResult();
- self.runTestCase(currentTest, deferred, numRetries);
- } else if (foundFailures && self.suiteRetries < self.suiteMaxRetries) {
- deferred.resolve(self.module.keys);
- } else {
- self.onTestCaseFinished(response.results, response.errors, response.time);
- if (self.client.terminated() && self.client.skipTestcasesOnFail()) {
- deferred.resolve(self.module.keys);
- } else {
- process.nextTick(function() {
- self.runNextTestCase(deferred);
- });
- }
- }
- }, function(error) {
- deferred.reject(error);
- });
- return deferred;
- };
- //////////////////////////////////////////////////////////////////////
- // Test suite hooks
- //////////////////////////////////////////////////////////////////////
- TestSuite.prototype.before = function() {
- return this.hooks.before();
- };
- TestSuite.prototype.after = function() {
- return this.hooks.after();
- };
- TestSuite.prototype.beforeEach = function() {
- return this.hooks.beforeEach();
- };
- TestSuite.prototype.afterEach = function(results, errors) {
- return this.hooks.afterEach(results, errors);
- };
- //////////////////////////////////////////////////////////////////////
- // Global hooks
- //////////////////////////////////////////////////////////////////////
- TestSuite.prototype.globalBeforeEach = function() {
- return this.adaptGlobalHook('beforeEach');
- };
- TestSuite.prototype.globalAfterEach = function() {
- return this.adaptGlobalHook('afterEach');
- };
- TestSuite.prototype.adaptGlobalHook = function(hookName) {
- return this.makePromise(function(done, deffered) {
- var callbackDeffered = false;
- var doneFn = function(err) {
- if (callbackDeffered) {
- return;
- }
- var fn = this.adaptDoneCallback(done, 'global ' + hookName, deffered);
- return this.onGlobalHookError(err, true, fn);
- }.bind(this);
- var argsCount;
- var expectedCount = 1;
- if (Utils.checkFunction(hookName, this.options.globals)) {
- argsCount = this.options.globals[hookName].length;
- expectedCount = argsCount == 2 ? 2 : 1;
- }
- var globalHook = this.adaptHookMethod(hookName, this.options.globals, expectedCount);
- var args = [doneFn];
- if (argsCount == 2) {
- args.unshift(this.client.api());
- }
- globalHook.apply(this.options.globals, args);
- if (this.client.shouldRestartQueue()) {
- callbackDeffered = true;
- if (hookName == 'before' || hookName == 'beforeEach') {
- this.client.start(function(err) {
- return this.onGlobalHookError(err, false, done);
- //if (err) {
- // this.addErrorToResults(err);
- //}
- //done(err);
- }.bind(this));
- } else {
- this.client.restartQueue(function() {
- done();
- });
- }
- }
- });
- };
- TestSuite.prototype.onGlobalHookError = function(err, hookErr, done) {
- if (err && err.message) {
- this.addErrorToResults(err);
- }
- return done(err, hookErr);
- };
- TestSuite.prototype.addErrorToResults = function(err) {
- this.testResults.errors++;
- this.testResults.errmessages = [err.message];
- return this;
- };
- TestSuite.prototype.adaptDoneCallback = function(done, hookName, deferred) {
- return Utils.setCallbackTimeout(done, hookName, this.asyncHookTimeout, function(err) {
- deferred.reject(err);
- }, function(timeoutId) {
- this.currentHookTimeoutId = timeoutId;
- }.bind(this));
- };
- //////////////////////////////////////////////////////////////////////
- // Utilities
- //////////////////////////////////////////////////////////////////////
- TestSuite.prototype.makePromise = function (fn) {
- var deferred = Q.defer();
- try {
- fn.call(this, function(err, rejectOnError) {
- if (rejectOnError && Utils.isErrorObject(err)) {
- deferred.reject(err);
- } else {
- deferred.resolve();
- }
- }, deferred);
- } catch (e) {
- deferred.reject(e);
- }
- return deferred.promise;
- };
- TestSuite.prototype.updateDesiredCapabilities = function() {
- this.options.desiredCapabilities = this.options.desiredCapabilities || {};
- if (this.options.sync_test_names || (typeof this.options.sync_test_names == 'undefined')) {
- // optionally send the local test name (derived from filename)
- // to the remote selenium server. useful for test reporting in cloud service providers
- this.options.desiredCapabilities.name = this.suiteName;
- }
- if (this.module.desiredCapabilities()) {
- for (var capability in this.module.desiredCapabilities()) {
- if (this.module.desiredCapabilities().hasOwnProperty(capability)) {
- this.options.desiredCapabilities[capability] = this.module.desiredCapabilities(capability);
- }
- }
- }
- };
- TestSuite.prototype.print = function() {
- if (this.options.output) {
- var testSuiteDisplay;
- if (this.options.start_session) {
- testSuiteDisplay = '[' + this.suiteName + '] Test Suite';
- } else {
- testSuiteDisplay = this.module.getTestSuiteName() || this.module.moduleKey;
- }
- if (this.options.test_worker && !this.options.live_output) {
- process.stdout.write('\\n');
- }
- var output = '\n' + Logger.colors.cyan(testSuiteDisplay) + '\n' + Logger.colors.purple(new Array(testSuiteDisplay.length + 5).join('='));
- console.log(output);
- }
- };
- TestSuite.prototype.runHookMethod = function(fn, context, asyncArgCount, hookName, doneFn, deferred) {
- var hookFn = this.adaptHookMethod(fn, context, asyncArgCount);
- doneFn = Utils.setCallbackTimeout(doneFn, hookName, this.asyncHookTimeout, function(err) {
- this.addErrorToResults(err);
- this.deferred.resolve(this.testResults);
- }.bind(this));
- if (this.options.compatible_testcase_support) {
- return hookFn.call(context, doneFn);
- }
- return hookFn.call(context, this.client.api(), function(err) {
- var doneCalled = false;
- if (err instanceof Error) {
- if (hookName != 'after') {
- this.testResults.errors++;
- }
- this.testResults.errmessages = [err.stack];
- if (hookName == 'beforeEach') {
- // FIXME: hack to close the session properly after an error thrown in the beforeEach
- (function() {
- if (this.sessionId) {
- doneCalled = true;
- setImmediate(function() {
- this.queue.reset();
- this.queue.empty();
- this.terminate();
- doneFn();
- }.bind(this));
- } else {
- this.once('selenium:session_create', function() {
- // we need to wait for the session to be created to attempt to close it
- this.queue.reset();
- this.queue.empty();
- setImmediate(function() {
- this.terminate();
- }.bind(this));
- }.bind(this));
- }
- }.bind(this.client['@client'])());
- } else {
- this.client.terminate();
- }
- }
- if (!doneCalled) {
- doneFn((hookName == 'afterEach' || hookName == 'after') ? err : undefined);
- }
- }.bind(this));
- };
- TestSuite.prototype.adaptHookMethod = function(fn, context, asyncArgCount) {
- var hookFn;
- if (Array.isArray(fn) && (fn.length >= 2)) {
- hookFn = Utils.checkFunction(fn[0], context) || Utils.checkFunction(fn[1], context) || noop;
- } else {
- hookFn = Utils.checkFunction(fn, context) || noop;
- }
- return Utils.makeFnAsync(asyncArgCount, hookFn, context);
- };
- module.exports = TestSuite;
|