Commit 93dcfbd4 93dcfbd4ef31e52e86ba4a0bd164ec295f027f23 by Nicolas Perriault

jshint configuration, code cleaning

1 parent bcaf4e30
{
"asi": true,
"browser": true,
"debug": true,
"devel": true,
"eqeqeq": true,
"evil": true,
"maxparams": 5,
"maxdepth": 3,
"maxstatements": 15,
"maxcomplexity": 7,
"regexdash": true,
"strict": true,
"sub": true,
"trailing": true,
"undef": true,
"predef" : [
"exports",
"phantom",
"require",
"window"
]
}
docs
modules/vendors
modules/events.js
modules/querystring.js
samples
tests/site
tests/testdir
tests/suites
......@@ -208,12 +208,6 @@ function bootstrap(global) {
// custom global CasperError
global.CasperError = function CasperError(msg) {
Error.call(this);
try {
// let's get where this error has been thrown from, if we can
this._from = arguments.callee.caller.name;
} catch (e) {
this._from = "anonymous";
}
this.message = msg;
this.name = 'CasperError';
};
......
......@@ -33,6 +33,7 @@
var colorizer = require('colorizer');
var events = require('events');
var fs = require('fs');
var http = require('http');
var mouse = require('mouse');
var qs = require('querystring');
var tester = require('tester');
......@@ -192,6 +193,7 @@ utils.inherits(Casper, events.EventEmitter);
*/
Casper.prototype.back = function back() {
"use strict";
this.checkStarted();
return this.then(function _step() {
this.emit('back');
this.evaluate(function _evaluate() {
......@@ -230,9 +232,8 @@ Casper.prototype.base64encode = function base64encode(url, method, data) {
*/
Casper.prototype.capture = function capture(targetFile, clipRect) {
"use strict";
if (!this.started) {
throw new CasperError("Casper not started, can't capture()");
}
/*jshint maxstatements:20*/
this.checkStarted();
var previousClipRect;
targetFile = fs.absolute(targetFile);
if (clipRect) {
......@@ -270,16 +271,9 @@ Casper.prototype.capture = function capture(targetFile, clipRect) {
*/
Casper.prototype.captureBase64 = function captureBase64(format, area) {
"use strict";
if (!this.started) {
throw new CasperError("Casper not started, can't captureBase64()");
}
if (!('renderBase64' in this.page)) {
this.warn('captureBase64() requires PhantomJS >= 1.6');
return;
}
var base64;
var previousClipRect;
var formats = ['bmp', 'jpg', 'jpeg', 'png', 'ppm', 'tiff', 'xbm', 'xpm'];
/*jshint maxstatements:20*/
this.checkStarted();
var base64, previousClipRect, formats = ['bmp', 'jpg', 'jpeg', 'png', 'ppm', 'tiff', 'xbm', 'xpm'];
if (formats.indexOf(format.toLowerCase()) === -1) {
throw new CasperError(f('Unsupported format "%s"', format));
}
......@@ -346,6 +340,20 @@ Casper.prototype.checkStep = function checkStep(self, onComplete) {
};
/**
* Checks if this instance is started.
*
* @return Boolean
* @throws CasperError
*/
Casper.prototype.checkStarted = function checkStarted() {
"use strict";
if (!this.started) {
throw new CasperError(f("Casper is not started, can't execute `%s()`",
checkStarted.caller.name));
}
};
/**
* Clears the current page execution environment context. Useful to avoid
* having previously loaded DOM contents being still active (refs #34).
*
......@@ -356,6 +364,7 @@ Casper.prototype.checkStep = function checkStep(self, onComplete) {
*/
Casper.prototype.clear = function clear() {
"use strict";
this.checkStarted();
this.page.content = '';
return this;
};
......@@ -371,6 +380,7 @@ Casper.prototype.clear = function clear() {
*/
Casper.prototype.click = function click(selector) {
"use strict";
this.checkStarted();
return this.mouseEvent('click', selector);
};
......@@ -384,6 +394,7 @@ Casper.prototype.click = function click(selector) {
*/
Casper.prototype.clickLabel = function clickLabel(label, tag) {
"use strict";
this.checkStarted();
tag = tag || "*";
var escapedLabel = label.toString().replace(/"/g, '\\"');
var selector = selectXPath(f('//%s[text()="%s"]', tag, escapedLabel));
......@@ -416,6 +427,7 @@ Casper.prototype.createStep = function createStep(fn, options) {
*/
Casper.prototype.debugHTML = function debugHTML(selector, outer) {
"use strict";
this.checkStarted();
return this.echo(this.getHTML(selector, outer));
};
......@@ -426,6 +438,7 @@ Casper.prototype.debugHTML = function debugHTML(selector, outer) {
*/
Casper.prototype.debugPage = function debugPage() {
"use strict";
this.checkStarted();
this.echo(this.evaluate(function _evaluate() {
return document.body.textContent || document.body.innerText;
}));
......@@ -441,6 +454,7 @@ Casper.prototype.debugPage = function debugPage() {
*/
Casper.prototype.die = function die(message, status) {
"use strict";
this.checkStarted();
this.result.status = "error";
this.result.time = new Date().getTime() - this.startTime;
if (!utils.isString(message) || !message.length) {
......@@ -465,6 +479,7 @@ Casper.prototype.die = function die(message, status) {
*/
Casper.prototype.download = function download(url, targetPath, method, data) {
"use strict";
this.checkStarted();
var cu = require('clientutils').create(utils.mergeObjects({}, this.options));
try {
fs.write(targetPath, cu.decode(this.base64encode(url, method, data)), 'wb');
......@@ -548,6 +563,7 @@ Casper.prototype.echo = function echo(text, style, pad) {
*/
Casper.prototype.evaluate = function evaluate(fn, context) {
"use strict";
this.checkStarted();
// ensure client utils are always injected
this.injectClientUtils();
// function context
......@@ -571,6 +587,7 @@ Casper.prototype.evaluate = function evaluate(fn, context) {
*/
Casper.prototype.evaluateOrDie = function evaluateOrDie(fn, message, status) {
"use strict";
this.checkStarted();
if (!this.evaluate(fn)) {
return this.die(message, status);
}
......@@ -586,6 +603,7 @@ Casper.prototype.evaluateOrDie = function evaluateOrDie(fn, message, status) {
*/
Casper.prototype.exists = function exists(selector) {
"use strict";
this.checkStarted();
return this.evaluate(function _evaluate(selector) {
return window.__utils__.exists(selector);
}, { selector: selector });
......@@ -612,9 +630,7 @@ Casper.prototype.exit = function exit(status) {
*/
Casper.prototype.fetchText = function fetchText(selector) {
"use strict";
if (!this.started) {
throw new CasperError("Casper not started, can't fetchText()");
}
this.checkStarted();
return this.evaluate(function _evaluate(selector) {
return window.__utils__.fetchText(selector);
}, { selector: selector });
......@@ -629,6 +645,7 @@ Casper.prototype.fetchText = function fetchText(selector) {
*/
Casper.prototype.fill = function fill(selector, vals, submit) {
"use strict";
this.checkStarted();
submit = submit === true ? submit : false;
if (!utils.isObject(vals)) {
throw new CasperError("Form values must be provided as an object");
......@@ -643,15 +660,8 @@ Casper.prototype.fill = function fill(selector, vals, submit) {
if (!fillResults) {
throw new CasperError("Unable to fill form");
} else if (fillResults.errors.length > 0) {
(function _each(self){
fillResults.errors.forEach(function _forEach(error) {
throw new CasperError(error);
});
})(this);
if (submit) {
this.warn("Errors encountered while filling form; submission aborted");
submit = false;
}
throw new CasperError(f('Errors encountered while filling form: %s',
fillResults.errors.join('; ')));
}
// File uploads
if (fillResults.files && fillResults.files.length > 0) {
......@@ -691,6 +701,7 @@ Casper.prototype.fill = function fill(selector, vals, submit) {
*/
Casper.prototype.forward = function forward(then) {
"use strict";
this.checkStarted();
return this.then(function _step() {
this.emit('forward');
this.evaluate(function _evaluate() {
......@@ -717,9 +728,7 @@ Casper.prototype.getColorizer = function getColorizer() {
*/
Casper.prototype.getPageContent = function getPageContent() {
"use strict";
if (!this.started) {
throw new CasperError("Casper not started, can't getPageContent()");
}
this.checkStarted();
var contentType = utils.getPropertyPath(this, 'currentResponse.contentType');
if (!utils.isString(contentType)) {
return this.page.content;
......@@ -742,6 +751,7 @@ Casper.prototype.getPageContent = function getPageContent() {
*/
Casper.prototype.getCurrentUrl = function getCurrentUrl() {
"use strict";
this.checkStarted();
var url = this.evaluate(function _evaluate() {
return document.location.href;
});
......@@ -762,6 +772,7 @@ Casper.prototype.getCurrentUrl = function getCurrentUrl() {
*/
Casper.prototype.getElementAttribute = Casper.prototype.getElementAttr = function getElementAttr(selector, attribute) {
"use strict";
this.checkStarted();
return this.evaluate(function _evaluate(selector, attribute) {
return document.querySelector(selector).getAttribute(attribute);
}, { selector: selector, attribute: attribute });
......@@ -775,6 +786,7 @@ Casper.prototype.getElementAttribute = Casper.prototype.getElementAttr = functio
*/
Casper.prototype.getElementBounds = function getElementBounds(selector) {
"use strict";
this.checkStarted();
if (!this.exists(selector)) {
throw new CasperError("No element matching selector found: " + selector);
}
......@@ -795,6 +807,7 @@ Casper.prototype.getElementBounds = function getElementBounds(selector) {
*/
Casper.prototype.getElementsBounds = function getElementBounds(selector) {
"use strict";
this.checkStarted();
if (!this.exists(selector)) {
throw new CasperError("No element matching selector found: " + selector);
}
......@@ -811,6 +824,7 @@ Casper.prototype.getElementsBounds = function getElementBounds(selector) {
*/
Casper.prototype.getGlobal = function getGlobal(name) {
"use strict";
this.checkStarted();
var result = this.evaluate(function _evaluate(name) {
var result = {};
try {
......@@ -843,9 +857,7 @@ Casper.prototype.getGlobal = function getGlobal(name) {
*/
Casper.prototype.getHTML = function getHTML(selector, outer) {
"use strict";
if (!this.started) {
throw new CasperError("Casper not started, can't getHTML()");
}
this.checkStarted();
if (!selector) {
return this.page.content;
}
......@@ -865,12 +877,43 @@ Casper.prototype.getHTML = function getHTML(selector, outer) {
*/
Casper.prototype.getTitle = function getTitle() {
"use strict";
this.checkStarted();
return this.evaluate(function _evaluate() {
return document.title;
});
};
/**
* Handles received HTTP resource.
*
* @param Object resource PhantomJS HTTP resource
*/
Casper.prototype.handleReceivedResource = function(resource) {
"use strict";
if (resource.stage !== "end") {
return;
}
this.resources.push(resource);
if (resource.url !== this.requestUrl) {
return;
}
this.currentHTTPStatus = null;
this.currentResponse = undefined;
if (utils.isHTTPResource(resource)) {
this.currentResponse = resource;
this.currentHTTPStatus = resource.status;
this.emit('http.status.' + resource.status, resource);
if (utils.isObject(this.options.httpStatusHandlers) &&
resource.status in this.options.httpStatusHandlers &&
utils.isFunction(this.options.httpStatusHandlers[resource.status])) {
this.options.httpStatusHandlers[resource.status].call(this, this, resource);
}
}
this.currentUrl = resource.url;
this.emit('location.changed', resource.url);
};
/**
* Initializes PhantomJS error handler.
*
*/
......@@ -886,11 +929,39 @@ Casper.prototype.initErrorHandler = function initErrorHandler() {
};
/**
* Injects configured client scripts.
*
* @return Casper
*/
Casper.prototype.injectClientScripts = function injectClientScripts() {
"use strict";
this.checkStarted();
if (!this.options.clientScripts) {
return;
}
if (utils.isString(this.options.clientScripts)) {
this.options.clientScripts = [this.options.clientScripts];
}
if (!utils.isArray(this.options.clientScripts)) {
throw new CasperError("The clientScripts option must be an array");
}
this.options.clientScripts.forEach(function _forEach(script) {
if (this.page.injectJs(script)) {
this.log(f('Automatically injected %s client side', script), "debug");
} else {
this.warn('Failed injecting %s client side', script);
}
});
return this;
};
/**
* Injects Client-side utilities in current page context.
*
*/
Casper.prototype.injectClientUtils = function injectClientUtils() {
"use strict";
this.checkStarted();
var clientUtilsInjected = this.page.evaluate(function() {
return typeof window.__utils__ === "object";
});
......@@ -938,8 +1009,10 @@ Casper.prototype.log = function log(message, level, space) {
if (level in this.logFormats && utils.isFunction(this.logFormats[level])) {
message = this.logFormats[level](message, level, space);
} else {
var levelStr = this.colorizer.colorize(f('[%s]', level), this.logStyles[level]);
message = f('%s [%s] %s', levelStr, space, message);
message = f('%s [%s] %s',
this.colorizer.colorize(f('[%s]', level), this.logStyles[level]),
space,
message);
}
if (this.options.verbose) {
this.echo(this.filter('log.message', message) || message); // direct output
......@@ -961,6 +1034,7 @@ Casper.prototype.log = function log(message, level, space) {
*/
Casper.prototype.mouseEvent = function mouseEvent(type, selector) {
"use strict";
this.checkStarted();
this.log("Mouse event '" + type + "' on selector: " + selector, "debug");
if (!this.exists(selector)) {
throw new CasperError(f("Cannot dispatch %s event on nonexistent selector: %s", type, selector));
......@@ -998,6 +1072,7 @@ Casper.prototype.mouseEvent = function mouseEvent(type, selector) {
*/
Casper.prototype.open = function open(location, settings) {
"use strict";
this.checkStarted();
// settings validation
if (!settings) {
settings = {
......@@ -1040,9 +1115,6 @@ Casper.prototype.open = function open(location, settings) {
}
this.emit('open', this.requestUrl, settings);
this.log(f('opening url: %s, HTTP %s', this.requestUrl, settings.method.toUpperCase()), "debug");
if ('headers' in settings && phantom.version.minor < 6) {
this.warn('Custom headers in outgoing requests are supported in PhantomJS >= 1.6');
}
this.page.openUrl(this.requestUrl, {
operation: settings.method,
data: settings.data,
......@@ -1060,9 +1132,7 @@ Casper.prototype.open = function open(location, settings) {
*/
Casper.prototype.reload = function reload(then) {
"use strict";
if (!this.started) {
throw new CasperError("Casper not started, can't reload()");
}
this.checkStarted();
this.evaluate(function() {
window.location.reload();
});
......@@ -1097,6 +1167,7 @@ Casper.prototype.repeat = function repeat(times, then) {
*/
Casper.prototype.resourceExists = function resourceExists(test) {
"use strict";
this.checkStarted();
var testFn;
switch (utils.betterTypeOf(test)) {
case "string":
......@@ -1128,6 +1199,7 @@ Casper.prototype.resourceExists = function resourceExists(test) {
*/
Casper.prototype.run = function run(onComplete, time) {
"use strict";
this.checkStarted();
if (!this.steps || this.steps.length < 1) {
this.log("No steps defined, aborting", "error");
return this;
......@@ -1145,6 +1217,7 @@ Casper.prototype.run = function run(onComplete, time) {
*/
Casper.prototype.runStep = function runStep(step) {
"use strict";
this.checkStarted();
var skipLog = utils.isObject(step.options) && step.options.skipLog === true;
var stepInfo = f("Step %d/%d", this.step, this.steps.length);
var stepResult;
......@@ -1184,9 +1257,7 @@ Casper.prototype.runStep = function runStep(step) {
*/
Casper.prototype.setHttpAuth = function setHttpAuth(username, password) {
"use strict";
if (!this.started) {
throw new CasperError("Casper must be started in order to use the setHttpAuth() method");
}
this.checkStarted();
if (!utils.isString(username) || !utils.isString(password)) {
throw new CasperError("Both username and password must be strings");
}
......@@ -1258,6 +1329,7 @@ Casper.prototype.start = function start(location, then) {
* @return Object
*/
Casper.prototype.status = function status(asString) {
"use strict";
var properties = ['currentHTTPStatus', 'loadInProgress', 'navigationRequested',
'options', 'pendingWait', 'requestUrl', 'started', 'step', 'url'];
var currentStatus = {};
......@@ -1275,9 +1347,7 @@ Casper.prototype.status = function status(asString) {
*/
Casper.prototype.then = function then(step) {
"use strict";
if (!this.started) {
throw new CasperError("Casper not started; please use Casper#start");
}
this.checkStarted();
if (!utils.isFunction(step)) {
throw new CasperError("You can only define a step as a function");
}
......@@ -1315,6 +1385,7 @@ Casper.prototype.then = function then(step) {
*/
Casper.prototype.thenClick = function thenClick(selector, then, fallbackToHref) {
"use strict";
this.checkStarted();
if (arguments.length > 2) {
this.emit("deprecated", "The thenClick() method does not process the fallbackToHref argument since 0.6");
}
......@@ -1335,6 +1406,7 @@ Casper.prototype.thenClick = function thenClick(selector, then, fallbackToHref)
*/
Casper.prototype.thenEvaluate = function thenEvaluate(fn, context) {
"use strict";
this.checkStarted();
return this.then(function _step() {
this.evaluate(fn, context);
});
......@@ -1350,6 +1422,7 @@ Casper.prototype.thenEvaluate = function thenEvaluate(fn, context) {
*/
Casper.prototype.thenOpen = function thenOpen(location, settings, then) {
"use strict";
this.checkStarted();
if (!(settings && !utils.isFunction(settings))) {
then = settings;
settings = null;
......@@ -1375,6 +1448,7 @@ Casper.prototype.thenOpen = function thenOpen(location, settings, then) {
*/
Casper.prototype.thenOpenAndEvaluate = function thenOpenAndEvaluate(location, fn, context) {
"use strict";
this.checkStarted();
return this.thenOpen(location).thenEvaluate(fn, context);
};
......@@ -1384,6 +1458,7 @@ Casper.prototype.thenOpenAndEvaluate = function thenOpenAndEvaluate(location, fn
* @return String
*/
Casper.prototype.toString = function toString() {
"use strict";
return '[object Casper], currently at ' + this.getCurrentUrl();
};
......@@ -1395,9 +1470,7 @@ Casper.prototype.toString = function toString() {
*/
Casper.prototype.userAgent = function userAgent(agent) {
"use strict";
if (!this.started) {
throw new CasperError("Casper not started, can't set userAgent");
}
this.checkStarted();
this.options.pageSettings.userAgent = this.page.settings.userAgent = agent;
return this;
};
......@@ -1411,9 +1484,7 @@ Casper.prototype.userAgent = function userAgent(agent) {
*/
Casper.prototype.viewport = function viewport(width, height) {
"use strict";
if (!this.started) {
throw new CasperError("Casper must be started in order to set viewport at runtime");
}
this.checkStarted();
if (!utils.isNumber(width) || !utils.isNumber(height) || width <= 0 || height <= 0) {
throw new CasperError(f("Invalid viewport: %dx%d", width, height));
}
......@@ -1435,6 +1506,7 @@ Casper.prototype.viewport = function viewport(width, height) {
*/
Casper.prototype.visible = function visible(selector) {
"use strict";
this.checkStarted();
return this.evaluate(function _evaluate(selector) {
return window.__utils__.visible(selector);
}, { selector: selector });
......@@ -1463,6 +1535,7 @@ Casper.prototype.warn = function warn(message) {
*/
Casper.prototype.wait = function wait(timeout, then) {
"use strict";
this.checkStarted();
timeout = ~~timeout;
if (timeout < 1) {
this.die("wait() only accepts a positive integer > 0 as a timeout value");
......@@ -1505,6 +1578,7 @@ Casper.prototype.waitDone = function waitDone() {
*/
Casper.prototype.waitFor = function waitFor(testFx, then, onTimeout, timeout) {
"use strict";
this.checkStarted();
timeout = timeout ? timeout : this.options.waitTimeout;
if (!utils.isFunction(testFx)) {
this.die("waitFor() needs a test function");
......@@ -1553,6 +1627,7 @@ Casper.prototype.waitFor = function waitFor(testFx, then, onTimeout, timeout) {
*/
Casper.prototype.waitForResource = function waitForResource(test, then, onTimeout, timeout) {
"use strict";
this.checkStarted();
timeout = timeout ? timeout : this.options.waitTimeout;
return this.waitFor(function _check() {
return this.resourceExists(test);
......@@ -1571,6 +1646,7 @@ Casper.prototype.waitForResource = function waitForResource(test, then, onTimeou
*/
Casper.prototype.waitForSelector = function waitForSelector(selector, then, onTimeout, timeout) {
"use strict";
this.checkStarted();
timeout = timeout ? timeout : this.options.waitTimeout;
return this.waitFor(function _check() {
return this.exists(selector);
......@@ -1589,6 +1665,7 @@ Casper.prototype.waitForSelector = function waitForSelector(selector, then, onTi
*/
Casper.prototype.waitWhileSelector = function waitWhileSelector(selector, then, onTimeout, timeout) {
"use strict";
this.checkStarted();
timeout = timeout ? timeout : this.options.waitTimeout;
return this.waitFor(function _check() {
return !this.exists(selector);
......@@ -1607,6 +1684,7 @@ Casper.prototype.waitWhileSelector = function waitWhileSelector(selector, then,
*/
Casper.prototype.waitUntilVisible = function waitUntilVisible(selector, then, onTimeout, timeout) {
"use strict";
this.checkStarted();
timeout = timeout ? timeout : this.options.waitTimeout;
return this.waitFor(function _check() {
return this.visible(selector);
......@@ -1625,6 +1703,7 @@ Casper.prototype.waitUntilVisible = function waitUntilVisible(selector, then, on
*/
Casper.prototype.waitWhileVisible = function waitWhileVisible(selector, then, onTimeout, timeout) {
"use strict";
this.checkStarted();
timeout = timeout ? timeout : this.options.waitTimeout;
return this.waitFor(function _check() {
return !this.visible(selector);
......@@ -1639,17 +1718,11 @@ Casper.prototype.waitWhileVisible = function waitWhileVisible(selector, then, on
*/
Casper.prototype.zoom = function zoom(factor) {
"use strict";
if (!this.started) {
throw new CasperError("Casper has not been started, can't set zoom factor");
}
this.checkStarted();
if (!utils.isNumber(factor) || factor <= 0) {
throw new CasperError("Invalid zoom factor: " + factor);
}
if ('zoomFactor' in this.page) {
this.page.zoomFactor = factor;
} else {
this.warn("zoom() requires PhantomJS >= 1.6");
}
this.page.zoomFactor = factor;
return this;
};
......@@ -1662,7 +1735,7 @@ Casper.prototype.zoom = function zoom(factor) {
*/
Casper.extend = function(proto) {
"use strict";
this.warn('Casper.extend() has been deprecated since 0.6; check the docs');
this.emit("deprecated", "Casper.extend() has been deprecated since 0.6; check the docs")
if (!utils.isObject(proto)) {
throw new CasperError("extends() only accept objects as prototypes");
}
......@@ -1731,21 +1804,7 @@ function createPage(casper) {
casper.options.onLoadError.call(casper, casper, casper.requestUrl, status);
}
}
if (casper.options.clientScripts) {
if (utils.isString(casper.options.clientScripts)) {
casper.options.clientScripts = [casper.options.clientScripts];
}
if (!utils.isArray(casper.options.clientScripts)) {
throw new CasperError("The clientScripts option must be an array");
}
casper.options.clientScripts.forEach(function _forEach(script) {
if (casper.page.injectJs(script)) {
casper.log(f('Automatically injected %s client side', script), "debug");
} else {
casper.warn('Failed injecting %s client side', script);
}
});
}
casper.injectClientScripts();
// Client-side utils injection
casper.injectClientUtils();
// history
......@@ -1765,34 +1824,12 @@ function createPage(casper) {
return casper.filter('page.prompt', message, value);
};
page.onResourceReceived = function onResourceReceived(resource) {
if (utils.isHTTPResource(resource)) {
require('http').augmentResponse(resource);
} else {
casper.log(f('Non-HTTP resource received from %s', resource.url), "debug");
}
http.augmentResponse(resource);
casper.emit('resource.received', resource);
if (utils.isFunction(casper.options.onResourceReceived)) {
casper.options.onResourceReceived.call(casper, casper, resource);
}
if (resource.stage === "end") {
casper.resources.push(resource);
}
if (resource.url === casper.requestUrl && resource.stage === "end") {
casper.currentHTTPStatus = null;
casper.currentResponse = undefined;
if (utils.isHTTPResource(resource)) {
casper.currentResponse = resource;
casper.currentHTTPStatus = resource.status;
casper.emit('http.status.' + resource.status, resource);
if (utils.isObject(casper.options.httpStatusHandlers) &&
resource.status in casper.options.httpStatusHandlers &&
utils.isFunction(casper.options.httpStatusHandlers[resource.status])) {
casper.options.httpStatusHandlers[resource.status].call(casper, casper, resource);
}
}
casper.currentUrl = resource.url;
casper.emit('location.changed', resource.url);
}
casper.handleReceivedResource(resource);
};
page.onResourceRequested = function onResourceRequested(request) {
casper.emit('resource.requested', request);
......
......@@ -75,6 +75,7 @@
* @return string
*/
this.decode = function decode(str) {
/*jshint maxstatements:30 maxcomplexity:30 */
var c1, c2, c3, c4, i = 0, len = str.length, out = "";
while (i < len) {
do {
......@@ -123,6 +124,7 @@
* @return string
*/
this.encode = function encode(str) {
/*jshint maxstatements:30 */
var out = "", i = 0, len = str.length, c1, c2, c3;
while (i < len) {
c1 = str.charCodeAt(i++) & 0xff;
......@@ -183,9 +185,9 @@
/**
* Fills a form with provided field values, and optionnaly submits it.
*
* @param HTMLElement|String form A form element, or a CSS3 selector to a form element
* @param Object vals Field values
* @return Object An object containing setting result for each field, including file uploads
* @param HTMLElement|String form A form element, or a CSS3 selector to a form element
* @param Object vals Field values
* @return Object An object containing setting result for each field, including file uploads
*/
this.fill = function fill(form, vals) {
var out = {
......@@ -295,37 +297,14 @@
* Retrieves string contents from a binary file behind an url. Silently
* fails but log errors.
*
* @param String url
* @param String method
* @param Object data
* @return string
* @param String url Url.
* @param String method HTTP method.
* @param Object data Request parameters.
* @return String
*/
this.getBinary = function getBinary(url, method, data) {
try {
var xhr = new XMLHttpRequest(), dataString = "";
if (typeof method !== "string" || ["GET", "POST"].indexOf(method.toUpperCase()) === -1) {
method = "GET";
} else {
method = method.toUpperCase();
}
xhr.open(method, url, false);
this.log("getBinary(): Using HTTP method: '" + method + "'", "debug");
xhr.overrideMimeType("text/plain; charset=x-user-defined");
if (method === "POST") {
if (typeof data === "object") {
var dataList = [];
for (var k in data) {
dataList.push(encodeURIComponent(k) + "=" + encodeURIComponent(data[k].toString()));
}
dataString = dataList.join('&');
this.log("getBinary(): Using request data: '" + dataString + "'", "debug");
} else if (typeof data === "string") {
dataString = data;
}
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
}
xhr.send(method === "POST" ? dataString : null);
return xhr.responseText;
return this.sendAJAX(url, method, data, false);
} catch (e) {
if (e.name === "NETWORK_ERR" && e.code === 101) {
this.log("getBinary(): Unfortunately, casperjs cannot make cross domain ajax requests", "warning");
......@@ -529,6 +508,42 @@
};
/**
* Performs an AJAX request.
*
* @param String url Url.
* @param String method HTTP method.
* @param Object data Request parameters.
* @param Boolean async Asynchroneous request? (default: false)
* @return String Response text.
*/
this.sendAJAX = function sendAJAX(url, method, data, async) {
var xhr = new XMLHttpRequest(), dataString = "";
if (typeof method !== "string" || ["GET", "POST"].indexOf(method.toUpperCase()) === -1) {
method = "GET";
} else {
method = method.toUpperCase();
}
xhr.open(method, url, !!async);
this.log("getBinary(): Using HTTP method: '" + method + "'", "debug");
xhr.overrideMimeType("text/plain; charset=x-user-defined");
if (method === "POST") {
if (typeof data === "object") {
var dataList = [];
for (var k in data) {
dataList.push(encodeURIComponent(k) + "=" + encodeURIComponent(data[k].toString()));
}
dataString = dataList.join('&');
this.log("sendAJAX(): Using request data: '" + dataString + "'", "debug");
} else if (typeof data === "string") {
dataString = data;
}
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
}
xhr.send(method === "POST" ? dataString : null);
return xhr.responseText;
};
/**
* Sets a field (or a set of fields) value. Fails silently, but log
* error messages.
*
......
......@@ -28,6 +28,8 @@
*
*/
var utils = require('utils');
/*
* Building an Array subclass
*/
......@@ -59,7 +61,8 @@ responseHeaders.prototype.get = function get(name){
* @param mixed response Phantom response or undefined (generally with local files)
*/
exports.augmentResponse = function(response) {
if (response === undefined) {
"use strict";
if (!utils.isHTTPResource(response)) {
return;
}
response.headers.__proto__ = responseHeaders.prototype;
......
......@@ -53,6 +53,8 @@ var Tester = function Tester(casper, options) {
throw new CasperError("Tester needs a Casper instance");
}
this.casper = casper;
this.currentTestFile = null;
this.currentSuiteNum = 0;
this.exporter = require('xunit').create();
......@@ -132,7 +134,7 @@ var Tester = function Tester(casper, options) {
} catch (e) {
try {
comment += utils.serialize(failure.values[name].toString());
} catch (e) {
} catch (e2) {
comment += '(unserializable value)';
}
}
......@@ -140,789 +142,829 @@ var Tester = function Tester(casper, options) {
}
}
});
};
// methods
/**
* 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.
*
* @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
*/
this.assert = this.assertTrue = function assert(subject, message, context) {
return this.processAssertionResult(utils.mergeObjects({
success: subject === true,
type: "assert",
standard: "Subject is strictly true",
message: message,
file: this.currentTestFile,
values: {
subject: utils.getPropertyPath(context, 'values.subject') || subject
}
}, context || {}));
};
// Tester class is an EventEmitter
utils.inherits(Tester, events.EventEmitter);
exports.Tester = Tester;
/**
* 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
*/
this.assertEquals = this.assertEqual = function assertEquals(subject, expected, message) {
return this.assert(this.testEquals(subject, expected), message, {
type: "assertEquals",
standard: "Subject equals the expected value",
values: {
subject: subject,
expected: expected
}
});
};
/**
* 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.
*
* @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
*/
Tester.prototype.assert = Tester.prototype.assertTrue = function assert(subject, message, context) {
"use strict";
return this.processAssertionResult(utils.mergeObjects({
success: subject === true,
type: "assert",
standard: "Subject is strictly true",
message: message,
file: this.currentTestFile,
values: {
subject: utils.getPropertyPath(context, 'values.subject') || subject
}
}, context || {}));
};
/**
* 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
*/
this.assertNotEquals = function assertNotEquals(subject, shouldnt, message) {
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 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(this.testEquals(subject, expected), message, {
type: "assertEquals",
standard: "Subject equals the expected value",
values: {
subject: subject,
expected: expected
}
});
};
/**
* 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 containing the parameters to inject into the function (optional)
* @return Object An assertion result object
*/
this.assertEval = this.assertEvaluate = function assertEval(fn, message, params) {
return this.assert(casper.evaluate(fn, params), message, {
type: "assertEval",
standard: "Evaluated function returns true",
values: {
fn: fn,
params: params
}
});
};
/**
* 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 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
*/
this.assertEvalEquals = this.assertEvalEqual = function assertEvalEquals(fn, expected, message, params) {
var subject = casper.evaluate(fn, params);
return this.assert(this.testEquals(subject, expected), message, {
type: "assertEvalEquals",
standard: "Evaluated function returns the expected value",
values: {
fn: fn,
params: params,
subject: subject,
expected: expected
}
});
};
/**
* 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 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 a given input field has the provided value.
*
* @param String input_name The name attribute of the input element
* @param String expected_value The expected value of the input element
* @param String message Test description
* @return Object An assertion result object
*/
this.assertField = function assertField(input_name, expected_value, message) {
var actual_value = casper.evaluate(function(input_name) {
var input = document.querySelector('input[name="' + input_name + '"]');
return input ? input.value : null;
}, { input_name: input_name });
return this.assert(this.testEquals(actual_value, expected_value), message, {
type: 'assertField',
standard: f('"%s" input field has the value "%s"', input_name, expected_value),
values: {
input_name: input_name,
actual_value: actual_value,
expected_value: expected_value
}
});
};
/**
* 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(this.testEquals(subject, expected), message, {
type: "assertEvalEquals",
standard: "Evaluated function returns the expected value",
values: {
fn: fn,
params: params,
subject: subject,
expected: expected
}
});
};
/**
* 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
*/
this.assertExists = this.assertExist = this.assertSelectorExists = this.assertSelectorExist = function assertExists(selector, message) {
return this.assert(casper.exists(selector), message, {
type: "assertExists",
standard: f("Found an element matching: %s", selector),
values: {
selector: selector
}
});
};
/**
* Asserts that a given input field has the provided value.
*
* @param String input_name The name attribute of the input element
* @param String expected_value The expected value of the input element
* @param String message Test description
* @return Object An assertion result object
*/
Tester.prototype.assertField = function assertField(input_name, expected_value, message) {
"use strict";
var actual_value = this.casper.evaluate(function(input_name) {
var input = document.querySelector('input[name="' + input_name + '"]');
return input ? input.value : null;
}, { input_name: input_name });
return this.assert(this.testEquals(actual_value, expected_value), message, {
type: 'assertField',
standard: f('"%s" input field has the value "%s"', input_name, expected_value),
values: {
input_name: input_name,
actual_value: actual_value,
expected_value: expected_value
}
});
};
/**
* Asserts that an element matching the provided selector expression does not
* exists in remote DOM.
*
* @param String selector Selector expression
* @param String message Test description
* @return Object An assertion result object
*/
this.assertDoesntExist = this.assertNotExists = function assertDoesntExist(selector, message) {
return this.assert(!casper.exists(selector), message, {
type: "assertDoesntExist",
standard: f("No element found matching selector: %s", selector),
values: {
selector: selector
}
});
};
/**
* 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 = this.assertSelectorExists = Tester.prototype.assertSelectorExist = function assertExists(selector, message) {
"use strict";
return this.assert(this.casper.exists(selector), message, {
type: "assertExists",
standard: f("Found an element matching: %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
*/
this.assertHttpStatus = function assertHttpStatus(status, message) {
var currentHTTPStatus = casper.currentHTTPStatus;
return this.assert(this.testEquals(casper.currentHTTPStatus, status), message, {
type: "assertHttpStatus",
standard: f("HTTP status code is: %s", status),
values: {
current: currentHTTPStatus,
expected: status
}
});
};
/**
* Asserts that an element matching the provided selector expression does not
* exists 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("No element found matching selector: %s", selector),
values: {
selector: selector
}
});
};
/**
* 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
*/
this.assertMatch = this.assertMatches = function assertMatch(subject, pattern, message) {
return this.assert(pattern.test(subject), message, {
type: "assertMatch",
standard: "Subject matches the provided pattern",
values: {
subject: subject,
pattern: pattern.toString()
}
});
};
/**
* 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(this.testEquals(this.casper.currentHTTPStatus, status), message, {
type: "assertHttpStatus",
standard: f("HTTP status code is: %s", status),
values: {
current: currentHTTPStatus,
expected: status
}
});
};
/**
* Asserts a condition resolves to false.
*
* @param Boolean condition The condition to test
* @param String message Test description
* @return Object An assertion result object
*/
this.assertNot = function assertNot(condition, message) {
return this.assert(!condition, message, {
type: "assertNot",
standard: "Subject is falsy",
values: {
condition: condition
}
});
};
/**
* 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";
return this.assert(pattern.test(subject), message, {
type: "assertMatch",
standard: "Subject matches the provided pattern",
values: {
subject: subject,
pattern: pattern.toString()
}
});
};
/**
* 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
*/
this.assertNotVisible = this.assertInvisible = function assertNotVisible(selector, message) {
return this.assert(!casper.visible(selector), message, {
type: "assertVisible",
standard: "Selector is not visible",
values: {
selector: selector
}
});
};
/**
* 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 = function assertNot(condition, message) {
"use strict";
return this.assert(!condition, message, {
type: "assertNot",
standard: "Subject is falsy",
values: {
condition: condition
}
});
};
/**
* 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
*/
this.assertRaises = this.assertRaise = this.assertThrows = function assertRaises(fn, args, message) {
var context = {
type: "assertRaises",
standard: "Function raises an error"
};
try {
fn.apply(null, args);
this.assert(false, message, context);
} catch (error) {
this.assert(true, message, utils.mergeObjects(context, {
values: {
error: error
}
}));
/**
* 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: "assertVisible",
standard: "Selector is not visible",
values: {
selector: selector
}
};
});
};
/**
* 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
*/
this.assertResourceExists = this.assertResourceExist = function assertResourceExists(test, message) {
return this.assert(casper.resourceExists(test), message, {
type: "assertResourceExists",
standard: "Expected resource has been found",
/**
* 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 = this.assertThrows = function assertRaises(fn, args, message) {
"use strict";
var context = {
type: "assertRaises",
standard: "Function raises an error"
};
try {
fn.apply(null, args);
this.assert(false, message, context);
} catch (error) {
this.assert(true, message, utils.mergeObjects(context, {
values: {
test: test
error: error
}
});
};
}));
}
};
/**
* 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
*/
this.assertTextExists = this.assertTextExist = function assertTextExists(text, message) {
var textFound = (casper.evaluate(function _evaluate() {
return document.body.textContent || document.body.innerText;
}).indexOf(text) !== -1);
return this.assert(textFound, message, {
type: "assertTextExists",
standard: "Found expected text within the document body",
values: {
text: text
}
});
};
/**
* 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: "Expected resource has been found",
values: {
test: test
}
});
};
/**
* 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
*/
this.assertSelectorHasText = function assertSelectorHasText(selector, text, message) {
var textFound = casper.fetchText(selector).indexOf(text) !== -1;
return this.assert(textFound, message, {
type: "assertTextInSelector",
standard: f('Found "%s" within the selector "%s"', text, selector),
values: {
selector: selector,
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: "Found expected text within the document body",
values: {
text: text
}
});
};
/**
* 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
*/
this.assertSelectorDoesntHaveText = function assertSelectorDoesntHaveText(selector, text, message) {
var textFound = casper.fetchText(selector).indexOf(text) === -1;
return this.assert(textFound, message, {
type: "assertNoTextInSelector",
standard: f('Did not find "%s" within the selector "%s"', text, selector),
values: {
selector: selector,
text: text
}
});
};
/**
* 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 = function assertSelectorHasText(selector, text, message) {
"use strict";
var textFound = this.casper.fetchText(selector).indexOf(text) !== -1;
return this.assert(textFound, message, {
type: "assertTextInSelector",
standard: f('Found "%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
*/
this.assertTitle = function assertTitle(expected, message) {
var currentTitle = casper.getTitle();
return this.assert(this.testEquals(currentTitle, expected), message, {
type: "assertTitle",
standard: f('Page title is: "%s"', expected),
values: {
subject: currentTitle,
expected: expected
}
});
};
/**
* 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 = function assertSelectorDoesntHaveText(selector, text, message) {
"use strict";
var textFound = this.casper.fetchText(selector).indexOf(text) === -1;
return this.assert(textFound, message, {
type: "assertNoTextInSelector",
standard: f('Did not find "%s" within the selector "%s"', text, selector),
values: {
selector: selector,
text: text
}
});
};
/**
* 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
*/
this.assertTitleMatch = this.assertTitleMatches = function assertTitleMatch(pattern, message) {
var currentTitle = 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 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(this.testEquals(currentTitle, expected), message, {
type: "assertTitle",
standard: f('Page title is: "%s"', expected),
values: {
subject: currentTitle,
expected: expected
}
});
};
/**
* 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
*/
this.assertType = function assertType(subject, type, message) {
var actual = utils.betterTypeOf(subject);
return this.assert(this.testEquals(actual, type), message, {
type: "assertType",
standard: f('Subject type is: "%s"', type),
values: {
subject: subject,
type: type,
actual: actual
}
});
};
/**
* 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";
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 a the current page url matches the provided RegExp
* pattern.
*
* @param RegExp pattern A RegExp object instance
* @param String message Test description
* @return Object An assertion result object
*/
this.assertUrlMatch = this.assertUrlMatches = function assertUrlMatch(pattern, message) {
var currentUrl = casper.getCurrentUrl();
return this.assert(pattern.test(currentUrl), message, {
type: "assertUrlMatch",
standard: "Current url matches the provided pattern",
values: {
currentUrl: currentUrl,
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(this.testEquals(actual, type), message, {
type: "assertType",
standard: f('Subject type is: "%s"', type),
values: {
subject: subject,
type: type,
actual: actual
}
});
};
/**
* Asserts that a selector expression is currently visible.
*
* @param String expected selector expression
* @param String message Test description
* @return Object An assertion result object
*/
this.assertVisible = function assertVisible(selector, message) {
return this.assert(casper.visible(selector), message, {
type: "assertVisible",
standard: "Selector is visible",
values: {
selector: selector
}
});
};
/**
* Asserts that a the current page url matches the provided RegExp
* pattern.
*
* @param RegExp pattern A RegExp object instance
* @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();
return this.assert(pattern.test(currentUrl), message, {
type: "assertUrlMatch",
standard: "Current url matches the provided pattern",
values: {
currentUrl: currentUrl,
pattern: pattern.toString()
}
});
};
/**
* Prints out a colored bar onto the console.
*
*/
this.bar = function bar(text, style) {
casper.echo(text, style, this.options.pad);
};
/**
* 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
}
});
};
/**
* Render a colorized output. Basically a proxy method for
* Casper.Colorizer#colorize()
*/
this.colorize = function colorize(message, style) {
return casper.getColorizer().colorize(message, style);
};
/**
* 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);
};
/**
* Writes a comment-style formatted message to stdout.
*
* @param String message
*/
this.comment = function comment(message) {
casper.echo('# ' + message, 'COMMENT');
};
/**
* Render a colorized output. Basically a proxy method for
* Casper.Colorizer#colorize()
*/
Tester.prototype.colorize = function colorize(message, style) {
"use strict";
return this.casper.getColorizer().colorize(message, style);
};
/**
* Declares the current test suite done.
*
*/
this.done = function done() {
this.emit('test.done');
this.running = false;
};
/**
* Writes a comment-style formatted message to stdout.
*
* @param String message
*/
Tester.prototype.comment = function comment(message) {
"use strict";
this.casper.echo('# ' + message, 'COMMENT');
};
/**
* Writes an error-style formatted message to stdout.
*
* @param String message
*/
this.error = function error(message) {
casper.echo(message, 'ERROR');
};
/**
* Declares the current test suite done.
*
*/
Tester.prototype.done = function done() {
"use strict";
this.emit('test.done');
this.running = false;
};
/**
* 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
*/
this.exec = function exec(file) {
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 = file;
throw e;
}
this.currentTestFile = file;
phantom.injectJs(file);
};
/**
* Writes an error-style formatted message to stdout.
*
* @param String message
*/
Tester.prototype.error = function error(message) {
"use strict";
this.casper.echo(message, 'ERROR');
};
/**
* Adds a failed test entry to the stack.
*
* @param String message
*/
this.fail = function fail(message) {
return this.assert(false, message, {
type: "fail",
standard: "explicit call to fail()"
});
};
/**
* 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 = file;
throw e;
}
this.currentTestFile = file;
phantom.injectJs(file);
};
/**
* Recursively finds all test files contained in a given directory.
*
* @param String dir Path to some directory to scan
*/
this.findTestFiles = function findTestFiles(dir) {
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(fs.absolute(fs.pathJoin(dir, entry)));
}).sort();
};
/**
* Adds a failed test entry to the stack.
*
* @param String message
*/
Tester.prototype.fail = function fail(message) {
"use strict";
return this.assert(false, message, {
type: "fail",
standard: "explicit call to fail()"
});
};
/**
* Formats a message to highlight some parts of it.
*
* @param String message
* @param String style
*/
this.formatMessage = function formatMessage(message, style) {
var parts = /^([a-z0-9_\.]+\(\))(.*)/i.exec(message);
if (!parts) {
return message;
/**
* 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 this.colorize(parts[1], 'PARAMETER') + this.colorize(parts[2], style);
};
});
return entries.filter(function _filter(entry) {
return utils.isJsFile(fs.absolute(fs.pathJoin(dir, entry)));
}).sort();
};
/**
* Retrieves current failure data and all failed cases.
*
* @return Object casedata An object containg information about cases
* @return Number casedata.length The number of failed cases
* @return Array casedata.cases An array of all the failed case objects
*/
this.getFailures = function getFailures() {
return {
length: this.testResults.failed,
cases: this.testResults.failures
};
};
/**
* 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);
};
/**
* Retrieves current passed data and all passed cases.
*
* @return Object casedata An object containg information about cases
* @return Number casedata.length The number of passed cases
* @return Array casedata.cases An array of all the passed case objects
*/
this.getPasses = function getPasses() {
return {
length: this.testResults.passed,
cases: this.testResults.passes
};
/**
* Retrieves current failure data and all failed cases.
*
* @return Object casedata An object containg information about cases
* @return Number casedata.length The number of failed cases
* @return Array casedata.cases An array of all the failed case objects
*/
Tester.prototype.getFailures = function getFailures() {
"use strict";
return {
length: this.testResults.failed,
cases: this.testResults.failures
};
};
/**
* Writes an info-style formatted message to stdout.
*
* @param String message
*/
this.info = function info(message) {
casper.echo(message, 'PARAMETER');
/**
* Retrieves current passed data and all passed cases.
*
* @return Object casedata An object containg information about cases
* @return Number casedata.length The number of passed cases
* @return Array casedata.cases An array of all the passed case objects
*/
Tester.prototype.getPasses = function getPasses() {
"use strict";
return {
length: this.testResults.passed,
cases: this.testResults.passes
};
};
/**
* Adds a successful test entry to the stack.
*
* @param String message
*/
this.pass = function pass(message) {
return this.assert(true, message, {
type: "pass",
standard: "explicit call to pass()"
});
};
/**
* Writes an info-style formatted message to stdout.
*
* @param String message
*/
Tester.prototype.info = function info(message) {
"use strict";
this.casper.echo(message, 'PARAMETER');
};
/**
* 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
*/
this.processAssertionResult = function processAssertionResult(result) {
var eventName, style, status;
if (result.success === true) {
eventName = 'success';
style = 'INFO';
status = this.options.passText;
this.testResults.passed++;
} else {
eventName = 'fail';
style = 'RED_BAR';
status = this.options.failText;
this.testResults.failed++;
}
var message = result.message || result.standard;
casper.echo([this.colorize(status, style), this.formatMessage(message)].join(' '));
this.emit(eventName, result);
return result;
};
/**
* Adds a successful 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()"
});
};
/**
* Renders a detailed report for each failed test.
*
* @param Array failures
*/
this.renderFailureDetails = function renderFailureDetails(failures) {
if (failures.length === 0) {
return;
}
casper.echo(f("\nDetails for the %d failed test%s:\n", failures.length, failures.length > 1 ? "s" : ""), "PARAMETER");
failures.forEach(function _forEach(failure) {
var type, message, line;
type = failure.type || "unknown";
line = ~~failure.line;
message = failure.message;
casper.echo(f('In %s:%s', failure.file, line));
casper.echo(f(' %s: %s', type, message || failure.standard || "(no message was entered)"), "COMMENT");
});
};
/**
* 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";
var eventName, style, status;
if (result.success === true) {
eventName = 'success';
style = 'INFO';
status = this.options.passText;
this.testResults.passed++;
} else {
eventName = 'fail';
style = 'RED_BAR';
status = this.options.failText;
this.testResults.failed++;
}
var message = result.message || result.standard;
this.casper.echo([this.colorize(status, style), this.formatMessage(message)].join(' '));
this.emit(eventName, result);
return result;
};
/**
* Render tests results, an optionally exit phantomjs.
*
* @param Boolean exit
*/
this.renderResults = function renderResults(exit, status, save) {
save = utils.isString(save) ? save : this.options.save;
var total = this.testResults.passed + this.testResults.failed, statusText, style, result;
var exitStatus = ~~(status || (this.testResults.failed > 0 ? 1 : 0));
if (total === 0) {
/**
* Renders a detailed report for each failed test.
*
* @param Array failures
*/
Tester.prototype.renderFailureDetails = function renderFailureDetails(failures) {
"use strict";
if (failures.length === 0) {
return;
}
this.casper.echo(f("\nDetails for the %d failed test%s:\n", failures.length, failures.length > 1 ? "s" : ""), "PARAMETER");
failures.forEach(function _forEach(failure) {
var type, message, line;
type = failure.type || "unknown";
line = ~~failure.line;
message = failure.message;
this.casper.echo(f('In %s:%s', failure.file, line));
this.casper.echo(f(' %s: %s', type, message || failure.standard || "(no message was entered)"), "COMMENT");
});
};
/**
* Render tests results, an optionally exit phantomjs.
*
* @param Boolean exit
*/
Tester.prototype.renderResults = function renderResults(exit, status, save) {
"use strict";
save = utils.isString(save) ? save : this.options.save;
var total = this.testResults.passed + this.testResults.failed, statusText, style, result;
var exitStatus = ~~(status || (this.testResults.failed > 0 ? 1 : 0));
if (total === 0) {
statusText = this.options.failText;
style = 'RED_BAR';
result = f("%s Looks like you didn't run any test.", statusText);
} else {
if (this.testResults.failed > 0) {
statusText = this.options.failText;
style = 'RED_BAR';
result = f("%s Looks like you didn't run any test.", statusText);
} else {
if (this.testResults.failed > 0) {
statusText = this.options.failText;
style = 'RED_BAR';
} else {
statusText = this.options.passText;
style = 'GREEN_BAR';
}
result = f('%s %s tests executed, %d passed, %d failed.',
statusText, total, this.testResults.passed, this.testResults.failed);
statusText = this.options.passText;
style = 'GREEN_BAR';
}
casper.echo(result, style, this.options.pad);
if (this.testResults.failed > 0) {
this.renderFailureDetails(this.testResults.failures);
}
if (save && utils.isFunction(require)) {
try {
fs.write(save, this.exporter.getXML(), 'w');
casper.echo(f('Result log stored in %s', save), 'INFO', 80);
} catch (e) {
casper.echo(f('Unable to write results to %s: %s', save, e), 'ERROR', 80);
}
}
if (exit === true) {
casper.exit(exitStatus);
}
};
/**
* Runs al suites contained in the paths passed as arguments.
*
*/
this.runSuites = function runSuites() {
var testFiles = [], self = this;
if (arguments.length === 0) {
throw new CasperError("runSuites() needs at least one path argument");
result = f('%s %s tests executed, %d passed, %d failed.',
statusText, total, this.testResults.passed, this.testResults.failed);
}
this.casper.echo(result, style, this.options.pad);
if (this.testResults.failed > 0) {
this.renderFailureDetails(this.testResults.failures);
}
if (save && utils.isFunction(require)) {
try {
fs.write(save, this.exporter.getXML(), 'w');
this.casper.echo(f('Result log stored in %s', save), 'INFO', 80);
} catch (e) {
this.casper.echo(f('Unable to write results to %s: %s', save, e), 'ERROR', 80);
}
this.loadIncludes.includes.forEach(function _forEachInclude(include) {
phantom.injectJs(include);
});
this.loadIncludes.pre.forEach(function _forEachPreTest(preTestFile) {
testFiles = testFiles.concat(preTestFile);
});
}
if (exit === true) {
this.casper.exit(exitStatus);
}
};
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);
}
});
/**
* Runs al 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.post.forEach(function _forEachPostTest(postTestFile) {
testFiles = testFiles.concat(postTestFile);
});
this.loadIncludes.pre.forEach(function _forEachPreTest(preTestFile) {
testFiles = testFiles.concat(preTestFile);
});
if (testFiles.length === 0) {
this.bar(f("No test file found in %s, aborting.", Array.prototype.slice.call(arguments)), "RED_BAR");
casper.exit(1);
Array.prototype.forEach.call(arguments, function _forEachArgument(path) {
if (!fs.exists(path)) {
self.bar(f("Path %s doesn't exist", path), "RED_BAR");
}
self.currentSuiteNum = 0;
var interval = setInterval(function _check(self) {
if (self.running) {
return;
}
if (self.currentSuiteNum === testFiles.length) {
self.emit('tests.complete');
clearInterval(interval);
} else {
self.runTest(testFiles[self.currentSuiteNum]);
self.currentSuiteNum++;
}
}, 100, this);
};
if (fs.isDirectory(path)) {
testFiles = testFiles.concat(self.findTestFiles(path));
} else if (fs.isFile(path)) {
testFiles.push(path);
}
});
/**
* Runs a test file
*
*/
this.runTest = function runTest(testFile) {
this.bar(f('Test file: %s', testFile), 'INFO_BAR');
this.running = true; // this.running is set back to false with done()
this.exec(testFile);
};
this.loadIncludes.post.forEach(function _forEachPostTest(postTestFile) {
testFiles = testFiles.concat(postTestFile);
});
/**
* Tests equality between the two passed arguments.
*
* @param Mixed v1
* @param Mixed v2
* @param Boolean
*/
this.testEquals = this.testEqual = function testEquals(v1, v2) {
return utils.equals(v1, v2);
};
if (testFiles.length === 0) {
this.bar(f("No test file found in %s, aborting.", Array.prototype.slice.call(arguments)), "RED_BAR");
this.casper.exit(1);
}
self.currentSuiteNum = 0;
var interval = setInterval(function _check(self) {
if (self.running) {
return;
}
if (self.currentSuiteNum === testFiles.length) {
self.emit('tests.complete');
clearInterval(interval);
} else {
self.runTest(testFiles[self.currentSuiteNum]);
self.currentSuiteNum++;
}
}, 100, this);
};
/**
* 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)
*/
this.uncaughtError = function uncaughtError(error, file, line) {
return this.processAssertionResult({
success: false,
type: "uncaughtError",
file: file,
line: ~~line || "unknown",
message: utils.isObject(error) ? error.message : error,
values: {
error: error
}
});
};
/**
* 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.exec(testFile);
};
// Tester class is an EventEmitter
utils.inherits(Tester, events.EventEmitter);
/**
* 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);
};
exports.Tester = Tester;
/**
* 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)
*/
Tester.prototype.uncaughtError = function uncaughtError(error, file, line) {
"use strict";
return this.processAssertionResult({
success: false,
type: "uncaughtError",
file: file,
line: ~~line || "unknown",
message: utils.isObject(error) ? error.message : error,
values: {
error: error
}
});
};
......
......@@ -30,459 +30,482 @@
/*global CasperError console exports phantom require*/
(function(exports) {
/**
* Provides a better typeof operator equivalent, able to retrieve the array
* type.
*
* @param mixed input
* @return String
* @see http://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/
*/
function betterTypeOf(input) {
"use strict";
/**
* Provides a better typeof operator equivalent, able to retrieve the array
* type.
*
* @param mixed input
* @return String
* @see http://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/
*/
function betterTypeOf(input) {
try {
return Object.prototype.toString.call(input).match(/^\[object\s(.*)\]$/)[1].toLowerCase();
} catch (e) {
return typeof input;
}
try {
return Object.prototype.toString.call(input).match(/^\[object\s(.*)\]$/)[1].toLowerCase();
} catch (e) {
return typeof input;
}
exports.betterTypeOf = betterTypeOf;
}
exports.betterTypeOf = betterTypeOf;
/**
* Cleans a passed URL if it lacks a slash at the end when a sole domain is used.
*
* @param String url An HTTP URL
* @return String
*/
function cleanUrl(url) {
var parts = /(https?):\/\/(.*)/i.exec(url);
if (!parts) {
return url;
}
var protocol = parts[1];
var subparts = parts[2].split('/');
if (subparts.length === 1) {
return format("%s://%s/", protocol, subparts[0]);
}
/**
* Cleans a passed URL if it lacks a slash at the end when a sole domain is used.
*
* @param String url An HTTP URL
* @return String
*/
function cleanUrl(url) {
"use strict";
var parts = /(https?):\/\/(.*)/i.exec(url);
if (!parts) {
return url;
}
exports.cleanUrl = cleanUrl;
/**
* Dumps a JSON representation of passed value to the console. Used for
* debugging purpose only.
*
* @param Mixed value
*/
function dump(value) {
console.log(serialize(value, 4));
var protocol = parts[1];
var subparts = parts[2].split('/');
if (subparts.length === 1) {
return format("%s://%s/", protocol, subparts[0]);
}
exports.dump = dump;
return url;
}
exports.cleanUrl = cleanUrl;
/**
* Dumps a JSON representation of passed value to the console. Used for
* debugging purpose only.
*
* @param Mixed value
*/
function dump(value) {
"use strict";
console.log(serialize(value, 4));
}
exports.dump = dump;
/**
* Tests equality between the two passed arguments.
*
* @param Mixed v1
* @param Mixed v2
* @param Boolean
*/
function equals(v1, v2) {
if (betterTypeOf(v1) !== betterTypeOf(v2)) {
/**
* Tests equality between the two passed arguments.
*
* @param Mixed v1
* @param Mixed v2
* @param Boolean
*/
function equals(v1, v2) {
"use strict";
if (betterTypeOf(v1) !== betterTypeOf(v2)) {
return false;
}
if (isFunction(v1)) {
return v1.toString() === v2.toString();
}
if (v1 instanceof Object) {
if (Object.keys(v1).length !== Object.keys(v2).length) {
return false;
}
if (isFunction(v1)) {
return v1.toString() === v2.toString();
}
if (v1 instanceof Object) {
if (Object.keys(v1).length !== Object.keys(v2).length) {
for (var k in v1) {
if (!equals(v1[k], v2[k])) {
return false;
}
for (var k in v1) {
if (!equals(v1[k], v2[k])) {
return false;
}
}
return true;
}
return v1 === v2;
return true;
}
exports.equals = equals;
return v1 === v2;
}
exports.equals = equals;
/**
* Returns the file extension in lower case.
*
* @param String file File path
* @return string
*/
function fileExt(file) {
try {
return file.split('.').pop().toLowerCase().trim();
} catch(e) {
return '';
}
/**
* Returns the file extension in lower case.
*
* @param String file File path
* @return string
*/
function fileExt(file) {
"use strict";
try {
return file.split('.').pop().toLowerCase().trim();
} catch(e) {
return '';
}
exports.fileExt = fileExt;
}
exports.fileExt = fileExt;
/**
* Takes a string and append blanks until the pad value is reached.
*
* @param String text
* @param Number pad Pad value (optional; default: 80)
* @return String
*/
function fillBlanks(text, pad) {
pad = pad || 80;
if (text.length < pad) {
text += new Array(pad - text.length + 1).join(' ');
}
return text;
/**
* Takes a string and append blanks until the pad value is reached.
*
* @param String text
* @param Number pad Pad value (optional; default: 80)
* @return String
*/
function fillBlanks(text, pad) {
"use strict";
pad = pad || 80;
if (text.length < pad) {
text += new Array(pad - text.length + 1).join(' ');
}
exports.fillBlanks = fillBlanks;
return text;
}
exports.fillBlanks = fillBlanks;
/**
* Formats a string with passed parameters. Ported from nodejs `util.format()`.
*
* @return String
*/
function format(f) {
var i = 1;
var args = arguments;
var len = args.length;
var str = String(f).replace(/%[sdj%]/g, function _replace(x) {
if (i >= len) return x;
switch (x) {
case '%s':
return String(args[i++]);
case '%d':
return Number(args[i++]);
case '%j':
return JSON.stringify(args[i++]);
case '%%':
return '%';
default:
return x;
}
});
for (var x = args[i]; i < len; x = args[++i]) {
if (x === null || typeof x !== 'object') {
str += ' ' + x;
} else {
str += '[obj]';
}
/**
* Formats a string with passed parameters. Ported from nodejs `util.format()`.
*
* @return String
*/
function format(f) {
"use strict";
var i = 1;
var args = arguments;
var len = args.length;
var str = String(f).replace(/%[sdj%]/g, function _replace(x) {
if (i >= len) return x;
switch (x) {
case '%s':
return String(args[i++]);
case '%d':
return Number(args[i++]);
case '%j':
return JSON.stringify(args[i++]);
case '%%':
return '%';
default:
return x;
}
return str;
}
exports.format = format;
/**
* Retrieves the value of an Object foreign property using a dot-separated
* path string.
*
* Beware, this function doesn't handle object key names containing a dot.
*
* @param Object obj The source object
* @param String path Dot separated path, eg. "x.y.z"
*/
function getPropertyPath(obj, path) {
if (!isObject(obj) || !isString(path)) {
return undefined;
});
for (var x = args[i]; i < len; x = args[++i]) {
if (x === null || typeof x !== 'object') {
str += ' ' + x;
} else {
str += '[obj]';
}
var value = obj;
path.split('.').forEach(function(property) {
if (typeof value === "object" && property in value) {
value = value[property];
} else {
value = undefined;
}
});
return value;
}
exports.getPropertyPath = getPropertyPath;
return str;
}
exports.format = format;
/**
* Inherit the prototype methods from one constructor into another.
*
* @param {function} ctor Constructor function which needs to inherit the
* prototype.
* @param {function} superCtor Constructor function to inherit prototype from.
*/
function inherits(ctor, superCtor) {
ctor.super_ = ctor.__super__ = superCtor;
ctor.prototype = Object.create(superCtor.prototype, {
constructor: {
value: ctor,
enumerable: false,
writable: true,
configurable: true
}
});
/**
* Retrieves the value of an Object foreign property using a dot-separated
* path string.
*
* Beware, this function doesn't handle object key names containing a dot.
*
* @param Object obj The source object
* @param String path Dot separated path, eg. "x.y.z"
*/
function getPropertyPath(obj, path) {
"use strict";
if (!isObject(obj) || !isString(path)) {
return undefined;
}
exports.inherits = inherits;
var value = obj;
path.split('.').forEach(function(property) {
if (typeof value === "object" && property in value) {
value = value[property];
} else {
value = undefined;
}
});
return value;
}
exports.getPropertyPath = getPropertyPath;
/**
* Checks if value is a javascript Array
*
* @param mixed value
* @return Boolean
*/
function isArray(value) {
return Array.isArray(value) || isType(value, "array");
}
exports.isArray = isArray;
/**
* Inherit the prototype methods from one constructor into another.
*
* @param {function} ctor Constructor function which needs to inherit the
* prototype.
* @param {function} superCtor Constructor function to inherit prototype from.
*/
function inherits(ctor, superCtor) {
"use strict";
ctor.super_ = ctor.__super__ = superCtor;
ctor.prototype = Object.create(superCtor.prototype, {
constructor: {
value: ctor,
enumerable: false,
writable: true,
configurable: true
}
});
}
exports.inherits = inherits;
/**
* Checks if passed argument is an instance of Capser object.
*
* @param mixed value
* @return Boolean
*/
function isCasperObject(value) {
return value instanceof require('casper').Casper;
}
exports.isCasperObject = isCasperObject;
/**
* Checks if value is a javascript Array
*
* @param mixed value
* @return Boolean
*/
function isArray(value) {
"use strict";
return Array.isArray(value) || isType(value, "array");
}
exports.isArray = isArray;
/**
* Checks if value is a phantomjs clipRect-compatible object
*
* @param mixed value
* @return Boolean
*/
function isClipRect(value) {
return isType(value, "cliprect") || (
isObject(value) &&
isNumber(value.top) && isNumber(value.left) &&
isNumber(value.width) && isNumber(value.height)
);
}
exports.isClipRect = isClipRect;
/**
* Checks if passed argument is an instance of Capser object.
*
* @param mixed value
* @return Boolean
*/
function isCasperObject(value) {
"use strict";
return value instanceof require('casper').Casper;
}
exports.isCasperObject = isCasperObject;
/**
* Checks if value is a javascript Function
*
* @param mixed value
* @return Boolean
*/
function isFunction(value) {
return isType(value, "function");
}
exports.isFunction = isFunction;
/**
* Checks if value is a phantomjs clipRect-compatible object
*
* @param mixed value
* @return Boolean
*/
function isClipRect(value) {
"use strict";
return isType(value, "cliprect") || (
isObject(value) &&
isNumber(value.top) && isNumber(value.left) &&
isNumber(value.width) && isNumber(value.height)
);
}
exports.isClipRect = isClipRect;
/**
* Checks if passed resource involves an HTTP url.
*
* @param Object resource The PhantomJS HTTP resource object
* @return Boolean
*/
function isHTTPResource(resource) {
return isObject(resource) && /^http/i.test(resource.url);
}
exports.isHTTPResource = isHTTPResource;
/**
* Checks if value is a javascript Function
*
* @param mixed value
* @return Boolean
*/
function isFunction(value) {
"use strict";
return isType(value, "function");
}
exports.isFunction = isFunction;
/**
* Checks if a file is apparently javascript compatible (.js or .coffee).
*
* @param String file Path to the file to test
* @return Boolean
*/
function isJsFile(file) {
var ext = fileExt(file);
return isString(ext, "string") && ['js', 'coffee'].indexOf(ext) !== -1;
}
exports.isJsFile = isJsFile;
/**
* Checks if passed resource involves an HTTP url.
*
* @param Object resource The PhantomJS HTTP resource object
* @return Boolean
*/
function isHTTPResource(resource) {
"use strict";
return isObject(resource) && /^http/i.test(resource.url);
}
exports.isHTTPResource = isHTTPResource;
/**
* Checks if the provided value is null
*
* @return Boolean
*/
function isNull(value) {
return isType(value, "null");
}
exports.isNull = isNull;
/**
* Checks if a file is apparently javascript compatible (.js or .coffee).
*
* @param String file Path to the file to test
* @return Boolean
*/
function isJsFile(file) {
"use strict";
var ext = fileExt(file);
return isString(ext, "string") && ['js', 'coffee'].indexOf(ext) !== -1;
}
exports.isJsFile = isJsFile;
/**
* Checks if value is a javascript Number
*
* @param mixed value
* @return Boolean
*/
function isNumber(value) {
return isType(value, "number");
}
exports.isNumber = isNumber;
/**
* Checks if the provided value is null
*
* @return Boolean
*/
function isNull(value) {
"use strict";
return isType(value, "null");
}
exports.isNull = isNull;
/**
* Checks if value is a javascript Object
*
* @param mixed value
* @return Boolean
*/
function isObject(value) {
var objectTypes = ["array", "object", "qtruntimeobject"];
return objectTypes.indexOf(betterTypeOf(value)) >= 0;
}
exports.isObject = isObject;
/**
* Checks if value is a javascript Number
*
* @param mixed value
* @return Boolean
*/
function isNumber(value) {
"use strict";
return isType(value, "number");
}
exports.isNumber = isNumber;
/**
* Checks if value is a javascript String
*
* @param mixed value
* @return Boolean
*/
function isString(value) {
return isType(value, "string");
}
exports.isString = isString;
/**
* Checks if value is a javascript Object
*
* @param mixed value
* @return Boolean
*/
function isObject(value) {
"use strict";
var objectTypes = ["array", "object", "qtruntimeobject"];
return objectTypes.indexOf(betterTypeOf(value)) >= 0;
}
exports.isObject = isObject;
/**
* Shorthands for checking if a value is of the given type. Can check for
* arrays.
*
* @param mixed what The value to check
* @param String typeName The type name ("string", "number", "function", etc.)
* @return Boolean
*/
function isType(what, typeName) {
if (typeof typeName !== "string" || !typeName) {
throw new CasperError("You must pass isType() a typeName string");
}
return betterTypeOf(what).toLowerCase() === typeName.toLowerCase();
}
exports.isType = isType;
/**
* Checks if value is a javascript String
*
* @param mixed value
* @return Boolean
*/
function isString(value) {
"use strict";
return isType(value, "string");
}
exports.isString = isString;
/**
* Checks if the provided value is undefined
*
* @return Boolean
*/
function isUndefined(value) {
return isType(value, "undefined");
/**
* Shorthands for checking if a value is of the given type. Can check for
* arrays.
*
* @param mixed what The value to check
* @param String typeName The type name ("string", "number", "function", etc.)
* @return Boolean
*/
function isType(what, typeName) {
"use strict";
if (typeof typeName !== "string" || !typeName) {
throw new CasperError("You must pass isType() a typeName string");
}
exports.isUndefined = isUndefined;
return betterTypeOf(what).toLowerCase() === typeName.toLowerCase();
}
exports.isType = isType;
/**
* Checks if value is a valid selector Object.
*
* @param mixed value
* @return Boolean
*/
function isValidSelector(value) {
if (isString(value)) {
try {
// phantomjs env has a working document object, let's use it
document.querySelector(value);
} catch(e) {
if ('name' in e && e.name === 'SYNTAX_ERR') {
return false;
}
}
return true;
} else if (isObject(value)) {
if (!value.hasOwnProperty('type')) {
return false;
}
if (!value.hasOwnProperty('path')) {
return false;
}
if (['css', 'xpath'].indexOf(value.type) === -1) {
/**
* Checks if the provided value is undefined
*
* @return Boolean
*/
function isUndefined(value) {
"use strict";
return isType(value, "undefined");
}
exports.isUndefined = isUndefined;
/**
* Checks if value is a valid selector Object.
*
* @param mixed value
* @return Boolean
*/
function isValidSelector(value) {
"use strict";
if (isString(value)) {
try {
// phantomjs env has a working document object, let's use it
document.querySelector(value);
} catch(e) {
if ('name' in e && e.name === 'SYNTAX_ERR') {
return false;
}
return true;
}
return false;
return true;
} else if (isObject(value)) {
if (!value.hasOwnProperty('type')) {
return false;
}
if (!value.hasOwnProperty('path')) {
return false;
}
if (['css', 'xpath'].indexOf(value.type) === -1) {
return false;
}
return true;
}
exports.isValidSelector = isValidSelector;
return false;
}
exports.isValidSelector = isValidSelector;
/**
* Checks if the provided var is a WebPage instance
*
* @param mixed what
* @return Boolean
*/
function isWebPage(what) {
return betterTypeOf(what) === "qtruntimeobject" && what.objectName === 'WebPage';
}
exports.isWebPage = isWebPage;
/**
* Checks if the provided var is a WebPage instance
*
* @param mixed what
* @return Boolean
*/
function isWebPage(what) {
"use strict";
return betterTypeOf(what) === "qtruntimeobject" && what.objectName === 'WebPage';
}
exports.isWebPage = isWebPage;
/**
* Object recursive merging utility.
*
* @param Object origin the origin object
* @param Object add the object to merge data into origin
* @return Object
*/
function mergeObjects(origin, add) {
for (var p in add) {
try {
if (add[p].constructor === Object) {
origin[p] = mergeObjects(origin[p], add[p]);
} else {
origin[p] = add[p];
}
} catch(e) {
origin[p] = add[p];
/**
* Object recursive merging utility.
*
* @param Object origin the origin object
* @param Object add the object to merge data into origin
* @return Object
*/
function mergeObjects(origin, add) {
"use strict";
for (var p in add) {
try {
if (add[p].constructor === Object) {
origin[p] = mergeObjects(origin[p], add[p]);
} else {
origin[p] = add[p];
}
} catch(e) {
origin[p] = add[p];
}
return origin;
}
exports.mergeObjects = mergeObjects;
return origin;
}
exports.mergeObjects = mergeObjects;
/**
* Creates an (SG|X)ML node element.
*
* @param String name The node name
* @param Object attributes Optional attributes
* @return HTMLElement
*/
function node(name, attributes) {
var _node = document.createElement(name);
for (var attrName in attributes) {
var value = attributes[attrName];
if (attributes.hasOwnProperty(attrName) && isString(attrName)) {
_node.setAttribute(attrName, value);
}
/**
* Creates an (SG|X)ML node element.
*
* @param String name The node name
* @param Object attributes Optional attributes
* @return HTMLElement
*/
function node(name, attributes) {
"use strict";
var _node = document.createElement(name);
for (var attrName in attributes) {
var value = attributes[attrName];
if (attributes.hasOwnProperty(attrName) && isString(attrName)) {
_node.setAttribute(attrName, value);
}
return _node;
}
exports.node = node;
return _node;
}
exports.node = node;
/**
* Serializes a value using JSON.
*
* @param Mixed value
* @return String
*/
function serialize(value, indent) {
if (isArray(value)) {
value = value.map(function _map(prop) {
return isFunction(prop) ? prop.toString().replace(/\s{2,}/, '') : prop;
});
}
return JSON.stringify(value, null, indent);
/**
* Serializes a value using JSON.
*
* @param Mixed value
* @return String
*/
function serialize(value, indent) {
"use strict";
if (isArray(value)) {
value = value.map(function _map(prop) {
return isFunction(prop) ? prop.toString().replace(/\s{2,}/, '') : prop;
});
}
exports.serialize = serialize;
return JSON.stringify(value, null, indent);
}
exports.serialize = serialize;
/**
* Returns unique values from an array.
*
* Note: ugly code is ugly, but efficient: http://jsperf.com/array-unique2/8
*
* @param Array array
* @return Array
*/
function unique(array) {
var o = {},
r = [];
for (var i = 0, len = array.length; i !== len; i++) {
var d = array[i];
if (o[d] !== 1) {
o[d] = 1;
r[r.length] = d;
}
/**
* Returns unique values from an array.
*
* Note: ugly code is ugly, but efficient: http://jsperf.com/array-unique2/8
*
* @param Array array
* @return Array
*/
function unique(array) {
"use strict";
var o = {},
r = [];
for (var i = 0, len = array.length; i !== len; i++) {
var d = array[i];
if (o[d] !== 1) {
o[d] = 1;
r[r.length] = d;
}
return r;
}
exports.unique = unique;
})(exports);
return r;
}
exports.unique = unique;
......
......@@ -33,6 +33,36 @@
var utils = require('utils');
var fs = require('fs');
/**
* Generates a value for 'classname' attribute of the JUnit XML report.
*
* Uses the (relative) file name of the current casper script without file
* extension as classname.
*
* @param String classname
* @return String
*/
function generateClassName(classname) {
"use strict";
classname = classname.replace(phantom.casperPath, "").trim();
var script = classname || phantom.casperScript;
if (script.indexOf(fs.workingDirectory) === 0) {
script = script.substring(fs.workingDirectory.length + 1);
}
if (script.indexOf('/') === 0) {
script = script.substring(1, script.length);
}
if (~script.indexOf('.')) {
script = script.substring(0, script.lastIndexOf('.'));
}
return script || "unknown";
}
/**
* Creates a XUnit instance
*
* @return XUnit
*/
exports.create = function create() {
"use strict";
return new XUnitExporter();
......@@ -88,31 +118,6 @@ XUnitExporter.prototype.addFailure = function addFailure(classname, name, messag
};
/**
* Generates a value for 'classname' attribute of the JUnit XML report.
*
* Uses the (relative) file name of the current casper script without file
* extension as classname.
*
* @param String classname
* @return String
*/
function generateClassName(classname) {
"use strict";
classname = classname.replace(phantom.casperPath, "").trim();
var script = classname || phantom.casperScript;
if (script.indexOf(fs.workingDirectory) === 0) {
script = script.substring(fs.workingDirectory.length + 1);
}
if (script.indexOf('/') === 0) {
script = script.substring(1, script.length);
}
if (~script.indexOf('.')) {
script = script.substring(0, script.lastIndexOf('.'));
}
return script || "unknown";
}
/**
* Retrieves generated XML object - actually an HTMLElement.
*
* @return HTMLElement
......
/*global phantom*/
if (!phantom.casperLoaded) {
console.log('This script must be invoked using the casperjs executable');
phantom.exit(1);
......@@ -15,6 +17,7 @@ var casper = require('casper').create({
// local utils
function checkSelfTest(tests) {
"use strict";
var isCasperTest = false;
tests.forEach(function(test) {
var testDir = fs.absolute(fs.dirname(test));
......@@ -28,6 +31,7 @@ function checkSelfTest(tests) {
}
function checkIncludeFile(include) {
"use strict";
var absInclude = fs.absolute(include.trim());
if (!fs.exists(absInclude)) {
casper.warn("%s file not found, can't be included", absInclude);
......@@ -60,6 +64,7 @@ if (casper.cli.get('no-colors') === true) {
// test paths are passed as args
if (casper.cli.args.length) {
tests = casper.cli.args.filter(function(path) {
"use strict";
return fs.isFile(path) || fs.isDirectory(path);
});
} else {
......@@ -75,6 +80,7 @@ if (!phantom.casperSelfTest && checkSelfTest(tests)) {
// includes handling
this.loadIncludes.forEach(function(include){
"use strict";
var container;
if (casper.cli.has(include)) {
container = casper.cli.get(include).split(',').map(function(file) {
......@@ -89,6 +95,7 @@ this.loadIncludes.forEach(function(include){
// test suites completion listener
casper.test.on('tests.complete', function() {
"use strict";
this.renderResults(true, undefined, casper.cli.get('xunit') || undefined);
});
......
/**
* CasperJS local HTTP test server
*/
/*global phantom casper require*/
var colorizer = require('colorizer').create('Colorizer');
var fs = require('fs');
var utils = require('utils');
......@@ -9,10 +12,12 @@ var service;
var testServerPort = 54321;
function info(message) {
"use strict";
console.log(colorizer.colorize('INFO', 'INFO_BAR') + ' ' + message);
}
service = server.listen(testServerPort, function(request, response) {
"use strict";
var pageFile = fs.pathJoin(phantom.casperPath, request.url);
if (!fs.exists(pageFile) || !fs.isFile(pageFile)) {
response.statusCode = 404;
......@@ -26,16 +31,18 @@ service = server.listen(testServerPort, function(request, response) {
// overriding Casper.open to prefix all test urls
casper.setFilter('open.location', function(location) {
"use strict";
if (/^file/.test(location)) {
return location;
}
if (!/^http/.test(location)) {
return f('http://localhost:%d/%s', testServerPort, location);
return utils.format('http://localhost:%d/%s', testServerPort, location);
}
return location;
});
// test suites completion listener
casper.test.on('tests.complete', function() {
"use strict";
server.close();
});
......