tikiwiki/packages/tiki-pkg-casperjs/jerome-breton/casperjs/modules/tester.js

2066 lines
61 KiB
JavaScript
Raw Permalink Normal View History

2023-11-20 21:52:04 +01:00
/*!
* Casper is a navigation utility for PhantomJS.
*
* Documentation: http://casperjs.org/
* Repository: http://github.com/casperjs/casperjs
*
* Copyright (c) 2011-2012 Nicolas Perriault
*
* Part of source code is Copyright Joyent, Inc. and other Node contributors.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*/
require = patchRequire(require);
var fs = require('fs');
var events = require('events');
var utils = require('utils');
var f = utils.format;
function AssertionError(msg, result) {
"use strict";
Error.call(this);
this.message = msg;
this.name = 'AssertionError';
this.result = result;
}
AssertionError.prototype = new Error();
exports.AssertionError = AssertionError;
function TerminationError(msg) {
"use strict";
Error.call(this);
this.message = msg;
this.name = 'TerminationError';
}
TerminationError.prototype = new Error();
exports.TerminationError = TerminationError;
function TimedOutError(msg) {
"use strict";
Error.call(this);
this.message = msg;
this.name = 'TimedOutError';
}
TimedOutError.prototype = new Error();
exports.TimedOutError = TimedOutError;
/**
* Creates a tester instance.
*
* @param Casper casper A Casper instance
* @param Object options Tester options
* @return Tester
*/
exports.create = function create(casper, options) {
"use strict";
return new Tester(casper, options);
};
/**
* Casper tester: makes assertions, stores test results and display then.
*
* @param Casper casper A valid Casper instance
* @param Object|null options Options object
*/
var Tester = function Tester(casper, options) {
"use strict";
/*eslint max-statements:0*/
if (!utils.isCasperObject(casper)) {
throw new CasperError("Tester needs a Casper instance");
}
// self reference
var self = this;
// casper reference
this.casper = casper;
// public properties
this._setUp = undefined;
this._tearDown = undefined;
this.aborted = false;
this.executed = 0;
this.currentTestFile = null;
this.currentTestStartTime = new Date();
this.currentSuite = undefined;
this.currentSuiteNum = 0;
this.lastAssertTime = 0;
this.loadIncludes = {
includes: [],
pre: [],
post: []
};
this.options = utils.mergeObjects({
concise: false, // concise output?
failFast: false, // terminates a suite as soon as a test fails?
failText: "FAIL", // text to use for a failed test
passText: "PASS", // text to use for a succesful test
skipText: "SKIP", // text to use for a skipped test
save: false, // false to not save
pad: 80 , // maximum number of chars for a result line
warnText: "WARN" // text to use for a dubious test
}, options);
this.queue = [];
this.running = false;
this.started = false;
this.suiteResults = new TestSuiteResult();
this.on('success', function onSuccess(success) {
var timeElapsed = new Date() - this.currentTestStartTime;
this.currentSuite.addSuccess(success, timeElapsed - this.lastAssertTime);
this.lastAssertTime = timeElapsed;
});
this.on('skipped', function onSkipped(skipped) {
var timeElapsed = new Date() - this.currentTestStartTime;
this.currentSuite.addSkip(skipped, timeElapsed - this.lastAssertTime);
this.lastAssertTime = timeElapsed;
});
this.on('fail', function onFail(failure) {
// export
var valueKeys = Object.keys(failure.values),
timeElapsed = new Date() - this.currentTestStartTime;
this.currentSuite.addFailure(failure, timeElapsed - this.lastAssertTime);
this.lastAssertTime = timeElapsed;
// special printing
if (failure.type) {
this.comment(' type: ' + failure.type);
}
if (failure.file) {
this.comment(' file: ' + failure.file + (failure.line ? ':' + failure.line : ''));
}
if (failure.lineContents) {
this.comment(' code: ' + failure.lineContents);
}
if (!failure.values || valueKeys.length === 0) {
return;
}
valueKeys.forEach(function(name) {
this.comment(f(' %s: %s', name, utils.formatTestValue(failure.values[name], name)));
}.bind(this));
// check for fast failing
if (this.options.failFast) {
return this.terminate('--fail-fast: aborted all remaining tests');
}
});
function errorHandler(error, backtrace) {
self.casper.unwait();
if (error instanceof Error) {
self.processError(error);
return;
}
if (utils.isString(error) && /^(Assertion|Termination|TimedOut)Error/.test(error)) {
return;
}
var line = 0;
try {
line = (backtrace || []).filter(function(entry) {
return self.currentTestFile === entry.file;
})[0].line;
} catch (e) {}
self.uncaughtError(error, self.currentTestFile, line, backtrace);
}
function errorHandlerAndDone(error, backtrace) {
errorHandler(error, backtrace);
self.done();
}
// casper events
this.casper.on('error', function onCasperError(msg, backtrace) {
self.processPhantomError(msg, backtrace);
});
[
'wait.error',
'waitFor.timeout.error',
'event.error',
'complete.error'
].forEach(function(event) {
self.casper.on(event, errorHandlerAndDone);
});
self.casper.on('step.error', errorHandler);
this.casper.on('warn', function(warning) {
if (self.currentSuite) {
self.currentSuite.addWarning(warning);
}
});
// Do not hook casper if we're not testing
if (!phantom.casperTest) {
return;
}
// specific timeout callbacks
this.casper.options.onStepTimeout = function test_onStepTimeout(timeout, step) {
throw new TimedOutError(f("Step timeout occured at step %s (%dms)", step, timeout));
};
this.casper.options.onTimeout = function test_onTimeout(timeout) {
throw new TimedOutError(f("Timeout occured (%dms)", timeout));
};
this.casper.options.onWaitTimeout = function test_onWaitTimeout(timeout, details) {
/*eslint complexity:0*/
var message = f("Wait timeout occured (%dms)", timeout);
details = details || {};
if (details.selector) {
message = f(details.waitWhile ? '"%s" never went away in %dms' : '"%s" still did not exist in %dms', details.selector, timeout);
}
else if (details.visible) {
message = f(details.waitWhile ? '"%s" never disappeared in %dms' : '"%s" never appeared in %dms', details.visible, timeout);
}
else if (details.url || details.resource) {
message = f('%s did not load in %dms', details.url || details.resource, timeout);
}
else if (details.popup) {
message = f('%s did not pop up in %dms', details.popup, timeout);
}
else if (details.text) {
message = f('"%s" did not appear in the page in %dms', details.text, timeout);
}
else if (details.selectorTextChange) {
message = f('"%s" did not have a text change in %dms', details.selectorTextChange, timeout);
}
else if (utils.isFunction(details.testFx)) {
message = f('"%s" did not evaluate to something truthy in %dms', details.testFx.toString(), timeout);
}
errorHandlerAndDone(new TimedOutError(message));
};
};
// Tester class is an EventEmitter
utils.inherits(Tester, events.EventEmitter);
exports.Tester = Tester;
/**
* Aborts current test suite.
*
* @param String message Warning message (optional)
*/
Tester.prototype.abort = function abort(message) {
"use strict";
throw new TerminationError(message || 'test suite aborted');
};
/**
* Skip `nb` tests.
*
* @param Integer nb Number of tests to skip
* @param String message Message to display
* @return Object
*/
Tester.prototype.skip = function skip(nb, message) {
"use strict";
return this.processAssertionResult({
success: null,
standard: f("%d test%s skipped", nb, nb > 1 ? "s" : ""),
message: message,
type: "skip",
number: nb,
skipped: true
});
};
/**
* Skip `nb` test on specific engine(s).
*
* A skip specifier is an object of the form:
* {
* name: 'casperjs' | 'phantomjs',
* version: {
* min: Object,
* max: Object
* },
* message: String
* }
*
* Minimal and maximal versions to be skipped are determined using
* utils.matchEngine.
*
* @param Integer nb Number of tests to skip
* @param Mixed skipSpec a single skip specifier object or
* an Array of skip specifier objects
* @return Object
*/
Tester.prototype.skipIfEngine = function skipIfEngine(nb, skipSpec) {
skipSpec = utils.matchEngine(skipSpec);
if (skipSpec) {
var message = skipSpec.name;
var version = skipSpec.version;
var skipMessage = skipSpec.message;
if (version) {
var min = version.min;
var max = version.max;
if (min && min === max) {
message += ' ' + min;
} else {
if (min) {
message += ' from ' + min;
}
if (max) {
message += ' to ' + max;
}
}
}
if (skipMessage) {
message += ' ' + skipMessage;
}
return this.skip(nb, message);
}
return false;
};
/**
* Asserts that a condition strictly resolves to true. Also returns an
* "assertion object" containing useful informations about the test case
* results.
*
* This method is also used as the base one used for all other `assert*`
* family methods; supplementary informations are then passed using the
* `context` argument.
*
* Note: an AssertionError is thrown if the assertion fails.
*
* @param Boolean subject The condition to test
* @param String message Test description
* @param Object|null context Assertion context object (Optional)
* @return Object An assertion result object if test passed
* @throws AssertionError in case the test failed
*/
Tester.prototype.assert =
Tester.prototype.assertTrue = function assert(subject, message, context) {
"use strict";
this.executed++;
var result = utils.mergeObjects({
success: subject === true,
type: "assert",
standard: "Subject is strictly true",
message: message,
file: this.currentTestFile,
doThrow: true,
values: {
subject: utils.getPropertyPath(context, 'values.subject') || subject
}
}, context || {});
if (!result.success && result.doThrow) {
throw new AssertionError(message || result.standard, result);
}
return this.processAssertionResult(result);
};
/**
* Asserts that two values are strictly equals.
*
* @param Mixed subject The value to test
* @param Mixed expected The expected value
* @param String message Test description (Optional)
* @return Object An assertion result object
*/
Tester.prototype.assertEquals =
Tester.prototype.assertEqual = function assertEquals(subject, expected, message) {
"use strict";
return this.assert(utils.equals(subject, expected), message, {
type: "assertEquals",
standard: "Subject equals the expected value",
values: {
subject: subject,
expected: expected
}
});
};
/**
* Asserts that two values are strictly not equals.
*
* @param Mixed subject The value to test
* @param Mixed expected The unwanted value
* @param String|null message Test description (Optional)
* @return Object An assertion result object
*/
Tester.prototype.assertNotEquals = function assertNotEquals(subject, shouldnt, message) {
"use strict";
return this.assert(!this.testEquals(subject, shouldnt), message, {
type: "assertNotEquals",
standard: "Subject doesn't equal what it shouldn't be",
values: {
subject: subject,
shouldnt: shouldnt
}
});
};
/**
* Asserts that a selector expression matches n elements.
*
* @param Mixed selector A selector expression
* @param Number count Expected number of matching elements
* @param String message Test description (Optional)
* @return Object An assertion result object
*/
Tester.prototype.assertElementCount = function assertElementCount(selector, count, message) {
"use strict";
if (!utils.isNumber(count) || count < 0) {
throw new CasperError('assertElementCount() needs a positive integer count');
}
var elementCount = this.casper.evaluate(function(selector) {
try {
return __utils__.findAll(selector).length;
} catch (e) {
return -1;
}
}, selector);
return this.assert(elementCount === count, message, {
type: "assertElementCount",
standard: f('%d element%s matching selector "%s" found',
count,
count > 1 ? 's' : '',
selector),
values: {
selector: selector,
expected: count,
obtained: elementCount
}
});
};
/**
* Asserts that a code evaluation in remote DOM resolves to true.
*
* @param Function fn A function to be evaluated in remote DOM
* @param String message Test description
* @param Object params Object/Array containing the parameters to inject into
* the function (optional)
* @return Object An assertion result object
*/
Tester.prototype.assertEval =
Tester.prototype.assertEvaluate = function assertEval(fn, message, params) {
"use strict";
return this.assert(this.casper.evaluate(fn, params), message, {
type: "assertEval",
standard: "Evaluated function returns true",
values: {
fn: fn,
params: params
}
});
};
/**
* Asserts that the result of a code evaluation in remote DOM equals
* an expected value.
*
* @param Function fn The function to be evaluated in remote DOM
* @param Boolean expected The expected value
* @param String|null message Test description
* @param Object|null params Object containing the parameters to inject into the
* function (optional)
* @return Object An assertion result object
*/
Tester.prototype.assertEvalEquals =
Tester.prototype.assertEvalEqual = function assertEvalEquals(fn, expected, message, params) {
"use strict";
var subject = this.casper.evaluate(fn, params);
return this.assert(utils.equals(subject, expected), message, {
type: "assertEvalEquals",
standard: "Evaluated function returns the expected value",
values: {
fn: fn,
params: params,
subject: subject,
expected: expected
}
});
};
function baseFieldAssert(inputName, expected, actual, message) {
"use strict";
return this.assert(utils.equals(actual, expected), message, {
type: 'assertField',
standard: f('"%s" input field has the value "%s"', inputName, expected),
values: {
inputName: inputName,
actual: actual,
expected: expected
}
});
}
/**
* Asserts that the provided assertion fails (used for internal testing).
*
* @param Function fn A closure calling an assertion
* @param String|null message Test description
* @return Object An assertion result object
*/
Tester.prototype.assertFail = function assertFail(fn, message) {
"use strict";
var failed = false;
try {
fn();
} catch (e) {
failed = true;
}
return this.assert(failed, message, {
type: "assertFail",
standard: "Assertion fails as expected"
});
};
/**
* Asserts that a given input field has the provided value.
*
* @param String|Object input The name attribute of the input element
* or an object with the selector
* @param String expected The expected value of the input element
* @param String message Test description
* @param Object options ClientUtils#getFieldValue options (optional)
* @return Object An assertion result object
*/
Tester.prototype.assertField = function assertField(input, expected, message, options) {
"use strict";
if (typeof input === 'object') {
switch (input.type) {
case 'css':
return this.assertFieldCSS(input.path, expected, message);
case 'xpath':
return this.assertFieldXPath(input.path, expected, message);
default:
throw new CasperError('Invalid regexp.');
// no default
}
}
var actual = this.casper.evaluate(function(inputName) {
return __utils__.getFieldValue(__utils__.makeSelector(inputName,'name'));
}, input);
return baseFieldAssert.call(this, input, expected, actual, message);
};
/**
* Asserts that a given input field by CSS selector has the provided value.
*
* @param Object cssSelector The CSS selector to use for the assert field value
* @param String expected The expected value of the input element
* @param String message Test description
* @return Object An assertion result object
*/
Tester.prototype.assertFieldCSS = function assertFieldCSS(cssSelector, expected, message) {
"use strict";
var actual = this.casper.evaluate(function(inputName) {
return __utils__.getFieldValue(__utils__.makeSelector(inputName,'css'));
}, cssSelector);
return baseFieldAssert.call(this, null, expected, actual, message);
};
/**
* Asserts that a given input field by XPath selector has the provided value.
*
* @param Object xPathSelector The XPath selector to use for the assert field value
* @param String expected The expected value of the input element
* @param String message Test description
* @return Object An assertion result object
*/
Tester.prototype.assertFieldXPath = function assertFieldXPath(xPathSelector, expected, message) {
"use strict";
var actual = this.casper.evaluate(function(inputName) {
return __utils__.getFieldValue(__utils__.makeSelector(inputName,'xpath'));
}, xPathSelector);
return baseFieldAssert.call(this, null, expected, actual, message);
};
/**
* Asserts that an element matching the provided selector expression exists in
* remote DOM.
*
* @param String selector Selector expression
* @param String message Test description
* @return Object An assertion result object
*/
Tester.prototype.assertExists =
Tester.prototype.assertExist =
Tester.prototype.assertSelectorExists =
Tester.prototype.assertSelectorExist = function assertExists(selector, message) {
"use strict";
return this.assert(this.casper.exists(selector), message, {
type: "assertExists",
standard: f("Find an element matching: %s", selector),
values: {
selector: selector
}
});
};
/**
* Asserts that an element matching the provided selector expression does not
* exist in remote DOM.
*
* @param String selector Selector expression
* @param String message Test description
* @return Object An assertion result object
*/
Tester.prototype.assertDoesntExist =
Tester.prototype.assertNotExists = function assertDoesntExist(selector, message) {
"use strict";
return this.assert(!this.casper.exists(selector), message, {
type: "assertDoesntExist",
standard: f("Fail to find element matching selector: %s", selector),
values: {
selector: selector
}
});
};
/**
* Asserts that current HTTP status is the one passed as argument.
*
* @param Number status HTTP status code
* @param String message Test description
* @return Object An assertion result object
*/
Tester.prototype.assertHttpStatus = function assertHttpStatus(status, message) {
"use strict";
var currentHTTPStatus = this.casper.currentHTTPStatus;
return this.assert(utils.equals(this.casper.currentHTTPStatus, status), message, {
type: "assertHttpStatus",
standard: f("HTTP status code is: %s", status),
values: {
current: currentHTTPStatus,
expected: status
}
});
};
/**
* Asserts that a provided string matches a provided RegExp pattern.
*
* @param String subject The string to test
* @param RegExp pattern A RegExp object instance
* @param String message Test description
* @return Object An assertion result object
*/
Tester.prototype.assertMatch =
Tester.prototype.assertMatches = function assertMatch(subject, pattern, message) {
"use strict";
if (utils.betterTypeOf(pattern) !== "regexp") {
throw new CasperError('Invalid regexp.');
}
return this.assert(pattern.test(subject), message, {
type: "assertMatch",
standard: "Subject matches the provided pattern",
values: {
subject: subject,
pattern: pattern.toString()
}
});
};
/**
* Asserts a condition resolves to false.
*
* @param Boolean condition The condition to test
* @param String message Test description
* @return Object An assertion result object
*/
Tester.prototype.assertNot =
Tester.prototype.assertFalse = function assertNot(condition, message) {
"use strict";
return this.assert(!condition, message, {
type: "assertNot",
standard: "Subject is falsy",
values: {
condition: condition
}
});
};
/**
* Asserts that a selector expression is not currently visible.
*
* @param String expected selector expression
* @param String message Test description
* @return Object An assertion result object
*/
Tester.prototype.assertNotVisible =
Tester.prototype.assertInvisible = function assertNotVisible(selector, message) {
"use strict";
return this.assert(!this.casper.visible(selector), message, {
type: "assertNotVisible",
standard: "Selector is not visible",
values: {
selector: selector
}
});
};
/**
* Asserts that the provided function called with the given parameters
* will raise an exception.
*
* @param Function fn The function to test
* @param Array args The arguments to pass to the function
* @param String message Test description
* @return Object An assertion result object
*/
Tester.prototype.assertRaises =
Tester.prototype.assertRaise =
Tester.prototype.assertThrows = function assertRaises(fn, args, message) {
"use strict";
var error, thrown = false, context = {
type: "assertRaises",
standard: "Function raises an error"
};
try {
fn.apply(null, args);
} catch (err) {
thrown = true;
error = err;
}
this.assert(thrown, message, utils.mergeObjects(context, {
values: {
error: error
}
}));
};
/**
* Asserts that the current page has a resource that matches the provided test
*
* @param Function/String test A test function that is called with every response
* @param String message Test description
* @return Object An assertion result object
*/
Tester.prototype.assertResourceExists =
Tester.prototype.assertResourceExist = function assertResourceExists(test, message) {
"use strict";
return this.assert(this.casper.resourceExists(test), message, {
type: "assertResourceExists",
standard: "Confirm page has resource",
values: {
test: test
}
});
};
/**
* Asserts that given text doesn't exist in the document body.
*
* @param String text Text not to be found
* @param String message Test description
* @return Object An assertion result object
*/
Tester.prototype.assertTextDoesntExist =
Tester.prototype.assertTextDoesntExist = function assertTextDoesntExist(text, message) {
"use strict";
var textFound = (this.casper.evaluate(function _evaluate() {
return document.body.textContent || document.body.innerText;
}).indexOf(text) === -1);
return this.assert(textFound, message, {
type: "assertTextDoesntExists",
standard: "Text doesn't exist within the document body",
values: {
text: text
}
});
};
/**
* Asserts that given text exists in the document body.
*
* @param String text Text to be found
* @param String message Test description
* @return Object An assertion result object
*/
Tester.prototype.assertTextExists =
Tester.prototype.assertTextExist = function assertTextExists(text, message) {
"use strict";
var textFound = (this.casper.evaluate(function _evaluate() {
return document.body.textContent || document.body.innerText;
}).indexOf(text) !== -1);
return this.assert(textFound, message, {
type: "assertTextExists",
standard: "Find text within the document body",
values: {
text: text
}
});
};
/**
* Asserts a subject is truthy.
*
* @param Mixed subject Test subject
* @param String message Test description
* @return Object An assertion result object
*/
Tester.prototype.assertTruthy = function assertTruthy(subject, message) {
"use strict";
/*eslint eqeqeq:0*/
return this.assert(utils.isTruthy(subject), message, {
type: "assertTruthy",
standard: "Subject is truthy",
values: {
subject: subject
}
});
};
/**
* Asserts a subject is falsy.
*
* @param Mixed subject Test subject
* @param String message Test description
* @return Object An assertion result object
*/
Tester.prototype.assertFalsy = function assertFalsy(subject, message) {
"use strict";
/*eslint eqeqeq:0*/
return this.assert(utils.isFalsy(subject), message, {
type: "assertFalsy",
standard: "Subject is falsy",
values: {
subject: subject
}
});
};
/**
* Asserts that given text exists in the provided selector.
*
* @param String selector Selector expression
* @param String text Text to be found
* @param String message Test description
* @return Object An assertion result object
*/
Tester.prototype.assertSelectorHasText =
Tester.prototype.assertSelectorContains = function assertSelectorHasText(selector, text, message) {
"use strict";
var got = this.casper.fetchText(selector);
var textFound = got.indexOf(text) !== -1;
return this.assert(textFound, message, {
type: "assertSelectorHasText",
standard: f('Find "%s" within the selector "%s"', text, selector),
values: {
selector: selector,
text: text,
actualContent: got
}
});
};
/**
* Asserts that given text does not exist in the provided selector.
*
* @param String selector Selector expression
* @param String text Text not to be found
* @param String message Test description
* @return Object An assertion result object
*/
Tester.prototype.assertSelectorDoesntHaveText =
Tester.prototype.assertSelectorDoesntContain = function assertSelectorDoesntHaveText(selector, text, message) {
"use strict";
var textFound = this.casper.fetchText(selector).indexOf(text) === -1;
return this.assert(textFound, message, {
type: "assertSelectorDoesntHaveText",
standard: f('Did not find "%s" within the selector "%s"', text, selector),
values: {
selector: selector,
text: text
}
});
};
/**
* Asserts that title of the remote page equals to the expected one.
*
* @param String expected The expected title string
* @param String message Test description
* @return Object An assertion result object
*/
Tester.prototype.assertTitle = function assertTitle(expected, message) {
"use strict";
var currentTitle = this.casper.getTitle();
return this.assert(utils.equals(currentTitle, expected), message, {
type: "assertTitle",
standard: f('Page title is: "%s"', expected),
values: {
subject: currentTitle,
expected: expected
}
});
};
/**
* Asserts that title of the remote page matched the provided pattern.
*
* @param RegExp pattern The pattern to test the title against
* @param String message Test description
* @return Object An assertion result object
*/
Tester.prototype.assertTitleMatch =
Tester.prototype.assertTitleMatches = function assertTitleMatch(pattern, message) {
"use strict";
if (utils.betterTypeOf(pattern) !== "regexp") {
throw new CasperError('Invalid regexp.');
}
var currentTitle = this.casper.getTitle();
return this.assert(pattern.test(currentTitle), message, {
type: "assertTitle",
details: "Page title does not match the provided pattern",
values: {
subject: currentTitle,
pattern: pattern.toString()
}
});
};
/**
* Asserts that the provided subject is of the given type.
*
* @param mixed subject The value to test
* @param String type The javascript type name
* @param String message Test description
* @return Object An assertion result object
*/
Tester.prototype.assertType = function assertType(subject, type, message) {
"use strict";
var actual = utils.betterTypeOf(subject);
return this.assert(utils.equals(actual, type), message, {
type: "assertType",
standard: f('Subject type is: "%s"', type),
values: {
subject: subject,
type: type,
actual: actual
}
});
};
/**
* Asserts that the provided subject has the provided constructor in its prototype hierarchy.
*
* @param mixed subject The value to test
* @param Function constructor The javascript type name
* @param String message Test description
* @return Object An assertion result object
*/
Tester.prototype.assertInstanceOf = function assertInstanceOf(subject, constructor, message) {
"use strict";
if (utils.betterTypeOf(constructor) !== "function") {
throw new CasperError('Subject is null or undefined.');
}
return this.assert(utils.betterInstanceOf(subject, constructor), message, {
type: "assertInstanceOf",
standard: f('Subject is instance of: "%s"', constructor.name),
values: {
subject: subject,
constructorName: constructor.name
}
});
};
/**
* Asserts that a the current page url matches a given pattern. A pattern may be
* either a RegExp object or a String. The method will test if the URL matches
* the pattern or contains the String.
*
* @param RegExp|String pattern The test pattern
* @param String message Test description
* @return Object An assertion result object
*/
Tester.prototype.assertUrlMatch =
Tester.prototype.assertUrlMatches = function assertUrlMatch(pattern, message) {
"use strict";
var currentUrl = this.casper.getCurrentUrl(),
patternType = utils.betterTypeOf(pattern),
result;
if (patternType === "regexp") {
result = pattern.test(currentUrl);
} else if (patternType === "string") {
result = currentUrl.indexOf(pattern) !== -1;
} else {
throw new CasperError("assertUrlMatch() only accepts strings or regexps");
}
return this.assert(result, message, {
type: "assertUrlMatch",
standard: "Current url matches the provided pattern",
values: {
currentUrl: currentUrl,
pattern: pattern.toString()
}
});
};
/**
* Asserts that a selector expression is currently visible.
*
* @param String expected selector expression
* @param String message Test description
* @return Object An assertion result object
*/
Tester.prototype.assertVisible = function assertVisible(selector, message) {
"use strict";
return this.assert(this.casper.visible(selector), message, {
type: "assertVisible",
standard: "Selector is visible",
values: {
selector: selector
}
});
};
/**
* Asserts that all elements matching selector expression are currently visible.
* Fails if even one element is not visible.
*
* @param String expected selector expression
* @param String message Test description
* @return Object An assertion result object
*/
Tester.prototype.assertAllVisible = function assertAllVisible(selector, message) {
"use strict";
return this.assert(this.casper.allVisible(selector), message, {
type: "assertAllVisible",
standard: "All elements matching selector are visible",
values: {
selector: selector
}
});
};
/**
* Prints out a colored bar onto the console.
*
*/
Tester.prototype.bar = function bar(text, style) {
"use strict";
this.casper.echo(text, style, this.options.pad);
};
/**
* Defines a function which will be executed before every test.
*
* @param Function fn
*/
Tester.prototype.setUp = function setUp(fn) {
"use strict";
this._setUp = fn;
};
/**
* Defines a function which will be executed after every test.
*
* @param Function fn
*/
Tester.prototype.tearDown = function tearDown(fn) {
"use strict";
this._tearDown = fn;
};
/**
* Starts a suite.
*
* Can be invoked different ways:
*
* casper.test.begin("suite description", plannedTests, function(test){})
* casper.test.begin("suite description", function(test){})
*/
Tester.prototype.begin = function begin() {
"use strict";
if (this.started && this.running)
return this.queue.push(arguments);
function getConfig(args) {
var config = {
setUp: function(){},
tearDown: function(){}
};
if (utils.isFunction(args[1])) {
config.test = args[1];
} else if (utils.isObject(args[1])) {
config = utils.mergeObjects(config, args[1]);
} else if (utils.isNumber(args[1]) && utils.isFunction(args[2])) {
config.planned = ~~args[1] || undefined;
config.test = args[2];
} else if (utils.isNumber(args[1]) && utils.isObject(args[2])) {
config.config = utils.mergeObjects(config, args[2]);
config.planned = ~~args[1] || undefined;
} else {
throw new CasperError('Invalid call');
}
if (!utils.isFunction(config.test))
throw new CasperError('begin() is missing a mandatory test function');
return config;
}
var description = arguments[0] || f("Untitled suite in %s", this.currentTestFile),
config = getConfig([].slice.call(arguments)),
next = function() {
config.test(this, this.casper);
}.bind(this);
if (!this.options.concise)
this.comment(description);
this.currentSuite = new TestCaseResult({
name: description,
file: this.currentTestFile,
config: config,
planned: config.planned || undefined
});
this.executed = 0;
this.running = this.started = true;
try {
if (config.setUp)
config.setUp(this, this.casper);
if (!this._setUp)
return next();
if (this._setUp.length > 0)
return this._setUp.call(this, next); // async
this._setUp.call(this); // sync
next();
} catch (err) {
this.processError(err);
this.done();
}
};
/**
* Render a colorized output. Basically a proxy method for
* `Casper.Colorizer#colorize()`.
*
* @param String message
* @param String style The style name
* @return String
*/
Tester.prototype.colorize = function colorize(message, style) {
"use strict";
return this.casper.getColorizer().colorize(message, style);
};
/**
* Writes a comment-style formatted message to stdout.
*
* @param String message
*/
Tester.prototype.comment = function comment(message) {
"use strict";
this.casper.echo('# ' + message, 'COMMENT');
};
/**
* Declares the current test suite done.
*
*/
Tester.prototype.done = function done() {
"use strict";
/*eslint max-statements:0, complexity:0*/
var planned, config = this.currentSuite && this.currentSuite.config || {};
if (arguments.length && utils.isNumber(arguments[0])) {
this.casper.warn('done() `planned` arg is deprecated as of 1.1');
planned = arguments[0];
}
if (config && config.tearDown && utils.isFunction(config.tearDown)) {
try {
config.tearDown(this, this.casper);
} catch (error) {
this.processError(error);
}
}
var next = function() {
if (this.currentSuite && this.currentSuite.planned &&
this.currentSuite.planned !== this.executed + this.currentSuite.skipped &&
!this.currentSuite.failed) {
this.dubious(this.currentSuite.planned, this.executed, this.currentSuite.name);
} else if (planned && planned !== this.executed) {
// BC
this.dubious(planned, this.executed);
}
if (this.currentSuite) {
this.suiteResults.push(this.currentSuite);
if (!this.options.concise) {
var message = [
this.colorize('PASS', 'INFO'),
this.formatMessage(this.currentSuite.name)
];
if (config.planned) {
message.push([
this.colorize(f('(%d test%s)',
config.planned,
config.planned > 1 ? 's' : ''), 'INFO')
]);
}
this.casper.echo(message.join(' '));
}
this.currentSuite = undefined;
this.executed = 0;
}
this.emit('test.done');
this.casper.currentHTTPResponse = {};
this.running = this.started = false;
var nextTest = this.queue.shift();
if (nextTest) {
this.begin.apply(this, nextTest);
}
}.bind(this);
if (!this._tearDown) {
return next();
}
try {
if (this._tearDown.length > 0) {
// async
this._tearDown.call(this, next);
} else {
// sync
this._tearDown.call(this);
next();
}
} catch (error) {
this.processError(error);
}
};
/**
* Marks a test as dubious, when the number of planned tests doesn't match the
* number of actually executed one.
*
* @param String message
*/
Tester.prototype.dubious = function dubious(planned, executed, suite) {
"use strict";
var message = f('%s: %d tests planned, %d tests executed', suite || 'global', planned, executed);
this.casper.warn(message);
if (!this.currentSuite) return;
this.currentSuite.addFailure({
type: "dubious",
file: this.currentTestFile,
standard: message
});
};
/**
* Writes an error-style formatted message to stdout.
*
* @param String message
*/
Tester.prototype.error = function error(message) {
"use strict";
this.casper.echo(message, 'ERROR');
};
/**
* Executes a file, wraping and evaluating its code in an isolated
* environment where only the current `casper` instance is passed.
*
* @param String file Absolute path to some js/coffee file
*/
Tester.prototype.exec = function exec(file) {
"use strict";
file = this.filter('exec.file', file) || file;
if (!fs.isFile(file) || !utils.isJsFile(file)) {
var e = new CasperError(f("Cannot exec %s: can only exec() files with .js or .coffee extensions",
file));
e.fileName = e.file = e.sourceURL = file;
throw e;
}
this.currentTestFile = file;
phantom.injectJs(file);
};
/**
* Adds a failed test entry to the stack.
*
* @param String message
* @param Object Failure context (optional)
*/
Tester.prototype.fail = function fail(message, context) {
"use strict";
context = context || {};
return this.assert(false, message, utils.mergeObjects({
type: "fail",
standard: "explicit call to fail()"
}, context));
};
/**
* Recursively finds all test files contained in a given directory.
*
* @param String dir Path to some directory to scan
*/
Tester.prototype.findTestFiles = function findTestFiles(dir) {
"use strict";
var self = this;
if (!fs.isDirectory(dir)) {
return [];
}
var entries = fs.list(dir).filter(function _filter(entry) {
return entry !== '.' && entry !== '..';
}).map(function _map(entry) {
return fs.absolute(fs.pathJoin(dir, entry));
});
entries.forEach(function _forEach(entry) {
if (fs.isDirectory(entry)) {
entries = entries.concat(self.findTestFiles(entry));
}
});
return entries.filter(function _filter(entry) {
return utils.isJsFile(entry);
}).sort();
};
/**
* Computes current suite identifier.
*
* @return String
*/
Tester.prototype.getCurrentSuiteId = function getCurrentSuiteId() {
"use strict";
return this.casper.test.currentSuiteNum + "-" + this.casper.step;
};
/**
* Formats a message to highlight some parts of it.
*
* @param String message
* @param String style
*/
Tester.prototype.formatMessage = function formatMessage(message, style) {
"use strict";
var parts = /^([a-z0-9_\.]+\(\))(.*)/i.exec(message);
if (!parts) {
return message;
}
return this.colorize(parts[1], 'PARAMETER') + this.colorize(parts[2], style);
};
/**
* Writes an info-style formatted message to stdout.
*
* @param String message
*/
Tester.prototype.info = function info(message) {
"use strict";
this.casper.echo(message, 'PARAMETER');
};
/**
* Adds a succesful test entry to the stack.
*
* @param String message
*/
Tester.prototype.pass = function pass(message) {
"use strict";
return this.assert(true, message, {
type: "pass",
standard: "explicit call to pass()"
});
};
function getStackEntry(error, testFile) {
"use strict";
if ("stackArray" in error) {
// PhantomJS has changed the API of the Error object :-/
// https://github.com/ariya/phantomjs/commit/c9cf14f221f58a3daf585c47313da6fced0276bc
return error.stackArray.filter(function(entry) {
return testFile === entry.sourceURL;
})[0];
}
if (! ('stack' in error))
return null;
var r = /\r?\n\s*(.*?)(at |@)([^:]*?):(\d+):?(\d*)/g;
var m;
while ((m = r.exec(error.stack))) {
var sourceURL = m[3];
if (sourceURL.indexOf('->') !== -1) {
sourceURL = sourceURL.split('->')[1].trim();
}
if (sourceURL === testFile) {
return { sourceURL: sourceURL, line: m[4], column: m[5]};
}
}
return null;
}
/**
* Processes an assertion error.
*
* @param AssertionError error
*/
Tester.prototype.processAssertionError = function(error) {
"use strict";
var result = error && error.result || {},
testFile = this.currentTestFile,
stackEntry;
try {
stackEntry = getStackEntry(error, testFile);
} catch (e) {}
if (stackEntry) {
result.line = stackEntry.line;
try {
result.lineContents = fs.read(this.currentTestFile).split('\n')[result.line - 1].trim();
} catch (e) {}
}
return this.processAssertionResult(result);
};
/**
* Processes an assertion result by emitting the appropriate event and
* printing result onto the console.
*
* @param Object result An assertion result object
* @return Object The passed assertion result Object
*/
Tester.prototype.processAssertionResult = function processAssertionResult(result) {
"use strict";
if (!this.currentSuite) {
// this is for BC when begin() didn't exist
this.currentSuite = new TestCaseResult({
name: "Untitled suite in " + this.currentTestFile,
file: this.currentTestFile,
planned: undefined
});
}
var eventName = 'success',
message = result.message || result.standard,
style = 'INFO',
status = this.options.passText;
if (null === result.success) {
eventName = 'skipped';
style = 'SKIP';
status = this.options.skipText;
} else if (!result.success) {
eventName = 'fail';
style = 'RED_BAR';
status = this.options.failText;
}
if (!this.options.concise) {
this.casper.echo([this.colorize(status, style), this.formatMessage(message)].join(' '));
}
this.emit(eventName, result);
return result;
};
/**
* Processes an error.
*
* @param Error error
*/
Tester.prototype.processError = function processError(error) {
"use strict";
if (error instanceof AssertionError) {
return this.processAssertionError(error);
}
if (error instanceof TerminationError) {
return this.terminate(error.message);
}
return this.uncaughtError(error, this.currentTestFile, error.line);
};
/**
* Processes a PhantomJS error, which is an error message and a backtrace.
*
* @param String message
* @param Array backtrace
*/
Tester.prototype.processPhantomError = function processPhantomError(msg, backtrace) {
"use strict";
if (/^AssertionError/.test(msg)) {
this.casper.warn('looks like you did not use begin(), which is mandatory since 1.1');
}
var termination = /^TerminationError:?\s?(.*)/.exec(msg);
if (termination) {
var message = termination[1];
if (backtrace && backtrace[0]) {
message += ' at ' + backtrace[0].file + backtrace[0].line;
}
return this.terminate(message);
}
this.fail(msg, {
type: "error",
doThrow: false,
values: {
error: msg,
stack: backtrace
}
});
this.done();
};
/**
* Renders a detailed report for each failed test.
*
*/
Tester.prototype.renderFailureDetails = function renderFailureDetails() {
"use strict";
if (!this.suiteResults.isFailed()) {
return;
}
var failures = this.suiteResults.getAllFailures();
this.casper.echo(f("\nDetails for the %d failed test%s:\n",
failures.length, failures.length > 1 ? "s" : ""), "PARAMETER");
failures.forEach(function _forEach(failure) {
this.casper.echo(f('In %s%s', failure.file, ~~failure.line ? ':' + ~~failure.line : ''));
if (failure.suite) {
this.casper.echo(f(' %s', failure.suite), "PARAMETER");
}
this.casper.echo(f(' %s: %s', failure.type || "unknown",
failure.message || failure.standard || "(no message was entered)"), "COMMENT");
}.bind(this));
};
/**
* Render tests results, an optionally exit phantomjs.
*
* @param Boolean exit Exit casper after results have been rendered?
* @param Number status Exit status code (default: 0)
* @param String save Optional path to file where to save the results log
*/
Tester.prototype.renderResults = function renderResults(exit, status, save) {
"use strict";
/*eslint max-statements:0*/
save = save || this.options.save;
var exitStatus = 0,
failed = this.suiteResults.countFailed(),
total = this.suiteResults.countExecuted(),
statusText,
style,
result;
if (total === 0) {
exitStatus = 1;
statusText = this.options.warnText;
style = 'WARN_BAR';
result = f("%s Looks like you didn't run any tests.", statusText);
} else {
if (this.suiteResults.isFailed()) {
exitStatus = 1;
statusText = this.options.failText;
style = 'RED_BAR';
} else {
statusText = this.options.passText;
style = 'GREEN_BAR';
}
result = f('%s %d test%s executed in %ss, %d passed, %d failed, %d dubious, %d skipped.',
statusText,
total,
total > 1 ? "s" : "",
utils.ms2seconds(this.suiteResults.calculateDuration()),
this.suiteResults.countPassed(),
failed,
this.suiteResults.countDubious(),
this.suiteResults.countSkipped());
}
this.casper.echo(result, style, this.options.pad);
this.renderFailureDetails();
if (save) {
this.saveResults(save);
}
if (exit === true) {
this.emit("exit");
this.casper.exit(status ? ~~status : exitStatus);
}
};
/**
* Runs all suites contained in the paths passed as arguments.
*
*/
Tester.prototype.runSuites = function runSuites() {
"use strict";
var testFiles = [], self = this;
if (arguments.length === 0) {
throw new CasperError("runSuites() needs at least one path argument");
}
this.loadIncludes.includes.forEach(function _forEachInclude(include) {
phantom.injectJs(include);
});
this.loadIncludes.pre.forEach(function _forEachPreTest(preTestFile) {
testFiles = testFiles.concat(preTestFile);
});
Array.prototype.forEach.call(arguments, function _forEachArgument(path) {
if (!fs.exists(path)) {
self.bar(f("Path %s doesn't exist", path), "RED_BAR");
}
if (fs.isDirectory(path)) {
testFiles = testFiles.concat(self.findTestFiles(path));
} else if (fs.isFile(path)) {
testFiles.push(path);
}
});
this.loadIncludes.post.forEach(function _forEachPostTest(postTestFile) {
testFiles = testFiles.concat(postTestFile);
});
if (testFiles.length === 0) {
this.bar(f("No test file found in %s, terminating.",
Array.prototype.slice.call(arguments)), "RED_BAR");
this.casper.exit(1);
}
self.currentSuiteNum = 0;
self.currentTestStartTime = new Date();
self.lastAssertTime = 0;
var interval = setInterval(function _check(self) {
if (self.running) {
return;
}
if (self.currentSuiteNum === testFiles.length || self.aborted) {
self.emit('tests.complete');
clearInterval(interval);
self.aborted = false;
} else {
self.runTest(testFiles[self.currentSuiteNum]);
self.currentSuiteNum++;
}
}, 20, this);
};
/**
* Runs a test file
*
*/
Tester.prototype.runTest = function runTest(testFile) {
"use strict";
this.bar(f('Test file: %s', testFile), 'INFO_BAR');
this.running = true; // this.running is set back to false with done()
this.executed = 0;
this.exec(testFile);
};
/**
* Terminates current suite.
*
*/
Tester.prototype.terminate = function(message) {
"use strict";
if (message) {
this.casper.warn(message);
}
this.done();
this.aborted = true;
this.emit('tests.complete');
};
/**
* Saves results to file.
*
* @param String filename Target file path.
*/
Tester.prototype.saveResults = function saveResults(filepath) {
"use strict";
var exporter = require('xunit').create();
exporter.setResults(this.suiteResults);
try {
fs.write(filepath, exporter.getSerializedXML(), 'w');
this.casper.echo(f('Result log stored in %s', filepath), 'INFO', 80);
} catch (e) {
this.casper.echo(f('Unable to write results to %s: %s', filepath, e), 'ERROR', 80);
}
};
/**
* Tests equality between the two passed arguments.
*
* @param Mixed v1
* @param Mixed v2
* @param Boolean
*/
Tester.prototype.testEquals = Tester.prototype.testEqual = function testEquals(v1, v2) {
"use strict";
return utils.equals(v1, v2);
};
/**
* Processes an error caught while running tests contained in a given test
* file.
*
* @param Error|String error The error
* @param String file Test file where the error occurred
* @param Number line Line number (optional)
* @param Array backtrace Error stack trace (optional)
*/
Tester.prototype.uncaughtError = function uncaughtError(error, file, line, backtrace) {
"use strict";
// XXX: this is NOT an assertion scratch that
return this.processAssertionResult({
success: false,
type: "uncaughtError",
file: file,
line: ~~line,
message: utils.isObject(error) ? error.message : error,
values: {
error: error,
stack: backtrace
}
});
};
/**
* Test suites array.
*
*/
function TestSuiteResult() {}
TestSuiteResult.prototype = [];
exports.TestSuiteResult = TestSuiteResult;
/**
* Returns the number of tests.
*
* @return Number
*/
TestSuiteResult.prototype.countTotal = function countTotal() {
"use strict";
return this.countPassed() + this.countFailed() + this.countDubious();
};
/**
* Returns the number of dubious results.
*
* @return Number
*/
TestSuiteResult.prototype.countDubious = function countDubious() {
"use strict";
return this.map(function(result) {
return result.dubious;
}).reduce(function(a, b) {
return a + b;
}, 0);
};
/**
* Returns the number of executed tests.
*
* @return Number
*/
TestSuiteResult.prototype.countExecuted = function countTotal() {
"use strict";
return this.countTotal() - this.countDubious();
};
/**
* Returns the number of errors.
*
* @return Number
*/
TestSuiteResult.prototype.countErrors = function countErrors() {
"use strict";
return this.map(function(result) {
return result.crashed;
}).reduce(function(a, b) {
return a + b;
}, 0);
};
/**
* Returns the number of failed tests.
*
* @return Number
*/
TestSuiteResult.prototype.countFailed = function countFailed() {
"use strict";
return this.map(function(result) {
return result.failed - result.dubious;
}).reduce(function(a, b) {
return a + b;
}, 0);
};
/**
* Returns the number of succesful tests.
*
* @return Number
*/
TestSuiteResult.prototype.countPassed = function countPassed() {
"use strict";
return this.map(function(result) {
return result.passed;
}).reduce(function(a, b) {
return a + b;
}, 0);
};
/**
* Returns the number of skipped tests.
*
* @return Number
*/
TestSuiteResult.prototype.countSkipped = function countSkipped() {
"use strict";
return this.map(function(result) {
return result.skipped;
}).reduce(function(a, b) {
return a + b;
}, 0);
};
/**
* Returns the number of warnings.
*
* @return Number
*/
TestSuiteResult.prototype.countWarnings = function countWarnings() {
"use strict";
return this.map(function(result) {
return result.warned;
}).reduce(function(a, b) {
return a + b;
}, 0);
};
/**
* Checks if the suite has failed.
*
* @return Number
*/
TestSuiteResult.prototype.isFailed = function isFailed() {
"use strict";
return this.countErrors() + this.countFailed() + this.countDubious() > 0;
};
/**
* Checks if the suite has skipped tests.
*
* @return Number
*/
TestSuiteResult.prototype.isSkipped = function isSkipped() {
"use strict";
return this.countSkipped() > 0;
};
/**
* Returns all failures from this suite.
*
* @return Array
*/
TestSuiteResult.prototype.getAllFailures = function getAllFailures() {
"use strict";
var failures = [];
this.forEach(function(result) {
failures = failures.concat(result.failures);
});
return failures;
};
/**
* Returns all succesful tests from this suite.
*
* @return Array
*/
TestSuiteResult.prototype.getAllPasses = function getAllPasses() {
"use strict";
var passes = [];
this.forEach(function(result) {
passes = passes.concat(result.passes);
});
return passes;
};
/**
* Returns all skipped tests from this suite.
*
* @return Array
*/
TestSuiteResult.prototype.getAllSkips = function getAllSkips() {
"use strict";
var skipped = [];
this.forEach(function(result) {
skipped = skipped.concat(result.skipped);
});
return skipped;
};
/**
* Returns all results from this suite.
*
* @return Array
*/
TestSuiteResult.prototype.getAllResults = function getAllResults() {
"use strict";
return this.getAllPasses().concat(this.getAllFailures());
};
/**
* Computes the sum of all durations of the tests which were executed in the
* current suite.
*
* @return Number
*/
TestSuiteResult.prototype.calculateDuration = function calculateDuration() {
"use strict";
return this.getAllResults().map(function(result) {
return ~~result.time;
}).reduce(function add(a, b) {
return a + b;
}, 0);
};
/**
* Test suite results object.
*
* @param Object options
*/
function TestCaseResult(options) {
"use strict";
this.name = options && options.name;
this.file = options && options.file;
this.planned = ~~(options && options.planned) || undefined;
this.errors = [];
this.failures = [];
this.passes = [];
this.skips = [];
this.warnings = [];
this.config = options && options.config;
this.__defineGetter__("assertions", function() {
return this.passed + this.failed;
});
this.__defineGetter__("crashed", function() {
return this.errors.length;
});
this.__defineGetter__("failed", function() {
return this.failures.length;
});
this.__defineGetter__("dubious", function() {
return this.failures.filter(function(failure) {
return failure.type === "dubious";
}).length;
});
this.__defineGetter__("passed", function() {
return this.passes.length;
});
this.__defineGetter__("skipped", function() {
return this.skips.map(function(skip) {
return skip.number;
}).reduce(function(a, b) {
return a + b;
}, 0);
});
}
exports.TestCaseResult = TestCaseResult;
/**
* Adds a failure record and its execution time.
*
* @param Object failure
* @param Number time
*/
TestCaseResult.prototype.addFailure = function addFailure(failure, time) {
"use strict";
failure.suite = this.name;
failure.time = time;
this.failures.push(failure);
};
/**
* Adds an error record.
*
* @param Object failure
*/
TestCaseResult.prototype.addError = function addFailure(error) {
"use strict";
error.suite = this.name;
this.errors.push(error);
};
/**
* Adds a success record and its execution time.
*
* @param Object success
* @param Number time
*/
TestCaseResult.prototype.addSuccess = function addSuccess(success, time) {
"use strict";
success.suite = this.name;
success.time = time;
this.passes.push(success);
};
/**
* Adds a success record and its execution time.
*
* @param Object success
* @param Number time
*/
TestCaseResult.prototype.addSkip = function addSkip(skipped, time) {
"use strict";
skipped.suite = this.name;
skipped.time = time;
this.skips.push(skipped);
};
/**
* Adds a warning message.
* NOTE: quite contrary to addError, addSuccess, and addSkip
* this adds a String value, NOT an Object
*
* @param String warning
*/
TestCaseResult.prototype.addWarning = function addWarning(warning) {
"use strict";
this.warnings.push(warning);
};
/**
* Computes total duration for this suite.
*
* @return Number
*/
TestCaseResult.prototype.calculateDuration = function calculateDuration() {
"use strict";
function add(a, b) {
return a + b;
}
var passedTimes = this.passes.map(function(success) {
return ~~success.time;
}).reduce(add, 0);
var failedTimes = this.failures.map(function(failure) {
return ~~failure.time;
}).reduce(add, 0);
return passedTimes + failedTimes;
};