2066 lines
61 KiB
JavaScript
2066 lines
61 KiB
JavaScript
|
/*!
|
||
|
* 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;
|
||
|
};
|