nightwatch
Version:
Easy to use Node.js based end-to-end testing solution for web applications using the W3C WebDriver API.
421 lines (338 loc) • 10.2 kB
JavaScript
const AssertionError = require('assertion-error');
const Utils = require('../utils');
const {Logger} = Utils;
module.exports = class Results {
static getErrorMessage(err) {
let errMessage;
if (err instanceof AssertionError) {
errMessage = ` ${Logger.colors.red(Utils.symbols.fail)} ${err.message}`;
let stackTrace = Utils.filterStack(err);
stackTrace = Logger.colors.stack_trace(stackTrace);
return `${errMessage}\n${stackTrace}`;
}
return Utils.errorToStackTrace(err);
}
constructor(tests = [], opts) {
this.skipped = tests.slice(0);
this.testcases = {};
this.suiteName = opts.suiteName;
this.moduleKey = opts.moduleKey;
this.modulePath = opts.modulePath;
this.groupName = opts.groupName;
this.reportPrefix = opts.reportPrefix;
this.globalStartTime = new Date().getTime();
this.currentTestName = '';
this.__currentTest = null;
this.__initialResult = {
errors: 0,
failed: 0,
passed: 0,
assertions: [],
tests: 0
};
this.initCount();
}
get initialResult() {
return this.__initialResult;
}
get currentTestResult() {
const testName = this.currentTest.name;
return this.getTestResult(testName, {returnFullResult: true});
}
/**
* @param {TestCase} value
*/
set currentTest(value) {
this.__currentTest = value;
}
getTestResult(testName, {returnFullResult = false} = {}) {
if (!testName || !this.testcases[testName]) {
return this.initialResult;
}
const currentTest = this.testcases[testName];
if (returnFullResult) {
currentTest.steps = this.skipped;
currentTest.stackTrace = this.stackTrace;
currentTest.testcases = Object.keys(this.testcases).reduce((prev, key) => {
prev[key] = Object.keys(this.testcases[key]).reduce((prevVal, prop) => {
if (prop !== 'testcases') {
prevVal[prop] = this.testcases[key][prop];
}
if (prop === 'assertions') {
prevVal.tests = this.testcases[key].assertions;
}
return prevVal;
}, {});
return prev;
}, {});
}
return currentTest;
}
/**
* @return {TestCase}
*/
getCurrentTest() {
return this.__currentTest;
}
get currentTest() {
if (!this.__currentTest) {
return null;
}
const name = this.__currentTest.testName;
return {
name,
module: this.moduleKey,
group: this.groupName,
results: this.getTestResult(name, {returnFullResult: true}),
timestamp: this.timestamp
};
}
////////////////////////////////////////////////////////////
// Counters
////////////////////////////////////////////////////////////
get passedCount() {
return this.__passedCount;
}
get failedCount() {
return this.__failedCount;
}
get errorsCount() {
return this.__errorsCount;
}
get skippedCount() {
return this.__skippedCount;
}
get timestamp() {
return this.__timestamp;
}
get testsCount() {
return this.__testsCount;
}
get lastError() {
return this.__lastError;
}
get stackTrace() {
return (this.__lastError && this.__lastError instanceof Error) ? this.__lastError.stack : '';
}
get currentTestElapsedTime() {
return this.currentTestResult.timeMs;
}
get currentTestCasePassed() {
return this.currentTestResult.errors === 0 && this.currentTestResult.failed === 0;
}
/**
* Exports the complete results for the entire testsuite
*
* @return {object}
*/
get export() {
let lastError = null;
const {moduleKey, reportPrefix} = this;
const suiteResults = {
moduleKey,
hasFailures: !this.testsPassed(),
results: {
reportPrefix,
assertionsCount: this.initialResult.assertions.length
}
};
Object.keys(this.testcases).forEach(key => {
let testcase = this.testcases[key];
if (testcase.lastError) {
//lastError = testcase.lastError;
}
suiteResults.results.assertionsCount += testcase.assertions.length;
});
if (!lastError && this.lastError) {
lastError = this.lastError;
}
suiteResults.results.lastError = lastError;
suiteResults.results.skipped = this.skipped;
suiteResults.results.time = this.time;
suiteResults.results.completed = this.testcases;
suiteResults.results.errmessages = this.errmessages;
suiteResults.results.testsCount = this.testsCount;
suiteResults.results.skippedCount = this.skippedCount;
suiteResults.results.failedCount = this.failedCount;
suiteResults.results.errorsCount = this.errorsCount;
suiteResults.results.passedCount = this.passedCount;
suiteResults.results.group = this.groupName;
suiteResults.results.modulePath = this.modulePath;
// Backwards compat
suiteResults.results.tests = this.testsCount;
suiteResults.results.failures = this.failedCount;
suiteResults.results.errors = this.errorsCount;
suiteResults.results.group = this.groupName;
return suiteResults;
}
initCurrentTest(testcase) {
this.currentTest = testcase;
return this;
}
resetCurrentTestName() {
// FIXME: in v2, this needs to be this.__currentTest.testName, but it isn't desirable now because will make
// client.currentTest.name empty in the after() hook and potentially introduce breaking changes
//this.__currentTest.testName = '';
this.currentTestName = '';
}
getCurrentTestName() {
// FIXME: see resetCurrentTestName()
const {testName} = this.getCurrentTest() || {};
return this.currentTestName;
}
/**
* @param {TestCase} testcase
* @return {Object}
*/
createTestCaseResults(testcase) {
return {
time: 0,
assertions: [],
passed: 0,
errors: 0,
failed: 0,
retries: testcase.retriesCount,
skipped: 0,
tests: 0
};
}
setLastError(err, {incrementTotal, addToErrArray = false} = {}) {
const testName = this.getCurrentTestName();
this.__lastError = err;
if (testName && this.testcases[testName]) {
this.testcases[testName].lastError = err;
}
const detailedLogging = err.detailedLogging || Utils.isUndefined(err.detailedLogging);
if ((!testName || addToErrArray) && incrementTotal && detailedLogging) {
const errorMessage = Results.getErrorMessage(err);
this.errmessages.push(errorMessage);
}
return this;
}
/**
* @param {Boolean} incrementTotal
* @return {Results}
*/
incrementErrorCount(incrementTotal = true) {
if (incrementTotal) {
this.__errorsCount++;
}
const result = this.getTestResult(this.currentTestName);
result.errors++;
return this;
}
/**
* @param {Boolean} incrementTotal
* @return {Results}
*/
incrementFailedCount(incrementTotal = true) {
if (incrementTotal) {
this.__failedCount++;
}
const result = this.getTestResult(this.currentTestName);
result.failed++;
return this;
}
incrementPassedCount() {
this.__passedCount++;
const result = this.getTestResult(this.currentTestName);
result.passed++;
return this;
}
subtractPassedCount(count = 0) {
this.__passedCount = this.__passedCount - count;
return this;
}
/**
* @param {Object} assertion
* @return {module.Results}
*/
logAssertion(assertion) {
const result = this.getTestResult(this.currentTestName);
result.assertions.push(assertion);
// backwards compatibility
result.tests++;
return this;
}
initCount() {
this.__passedCount = 0;
this.__failedCount = 0;
this.__errorsCount = 0;
this.__skippedCount = 0;
this.__testsCount = 0;
this.__timestamp = new Date().toUTCString();
this.errmessages = [];
this.time = 0;
}
setElapsedTime() {
const currentTest = this.getCurrentTest();
const startTime = currentTest ? currentTest.startTime : this.globalStartTime;
const elapsedTime = new Date().getTime() - startTime;
this.time += elapsedTime;
if (currentTest) {
this.currentTestResult.time = (elapsedTime/1000).toPrecision(4);
this.currentTestResult.timeMs = elapsedTime;
}
return this;
}
setTotalElapsedTime() {
this.time = (this.time/1000).toPrecision(4);
return this;
}
/**
* Sets the currently running testcase
*
* @param {TestCase} testcase
* @return {Results}
*/
setCurrentTest(testcase) {
this.currentTest = testcase;
let testName = testcase.testName;
this.currentTestName = testName;
this.testcases[testName] = this.createTestCaseResults(testcase);
let index = this.skipped.indexOf(testName);
if (index > -1) {
this.skipped.splice(index, 1);
}
this.__testsCount++;
return this;
}
logScreenshotFile(screenshotFile) {
let assertions = this.currentTestResult.assertions;
let lastAssertion = assertions[assertions.length - 1];
if (lastAssertion) {
lastAssertion.screenshots = lastAssertion.screenshots || [];
lastAssertion.screenshots.push(screenshotFile);
}
}
testsPassed() {
return this.failedCount === 0 && this.errorsCount === 0;
}
/**
* Combines all the individual test suite reports into a global one
*
* @param {Array} suiteResultsArr
* @param {Object} initialReport
* @return {Object}
*/
static createGlobalReport(suiteResultsArr, initialReport) {
return suiteResultsArr.reduce((prev, item) => {
let results = item.results;
if (results.lastError) {
prev.lastError = results.lastError;
}
// Accumulating stats
prev.passed += results.passedCount;
prev.failed += results.failedCount;
prev.errors += results.errorsCount;
prev.skipped += results.skippedCount;
prev.assertions += results.assertionsCount;
// Accumulating error messages
if (Array.isArray(results.errmessages) && results.errmessages.length > 0) {
prev.errmessages = prev.errmessages.concat(results.errmessages);
}
prev.modules[item.moduleKey] = results;
return prev;
}, initialReport);
}
};