Commit 918e9906 918e990687f8a43441bd3c0521cb6f002c6ae85f by Nicolas Perriault

merged with master

2 parents 789f60d0 c335de2e
CasperJS Changelog
==================
XXXX-XX-XX, v0.6.11
-------------------
- fixed [#132](https://github.com/n1k0/casperjs/issues/132) - added ability to include js/coffee files using a dedicated option when using the [`casper test` command](http://casperjs.org/testing.html)
- fixed [#140](https://github.com/n1k0/casperjs/issues/140) - `casper test` now resolves local paths urls
- fixed [#148](https://github.com/n1k0/casperjs/issues/148) - [`utils.isWebPage()`](http://casperjs.org/api.html#utils.isWebPage) was broken
- fixed [#149](https://github.com/n1k0/casperjs/issues/149) - [`ClientUtils.fill()`](http://casperjs.org/api.html#casper.fill) was searching elements globally
- fixed [#144](https://github.com/n1k0/casperjs/issues/144) - added a [`safeLogs` option](http://casperjs.org/api.html#casper.options) to blur password values in debug logs. **This option is set to `true` by default.**
- added [`Casper.userAgent()`](http://casperjs.org/api.html#casper.userAgent) to ease a more dynamic setting of user-agent string
- added [`Tester.assertTitleMatch()`](http://casperjs.org/api.html#tester.assertTitleMatch) method
- added experimental support of custom headers sending in outgoing request (refs [#137](https://github.com/n1k0/casperjs/issues/137) - PhantomJS 1.6 required)
- switched to more standard `.textContent` property to get a node text; this allows a better compatibility of the clientutils bookmarklet with non-webkit browsers
- casper modules now all use [javascript strict mode](http://www.nczonline.net/blog/2012/03/13/its-time-to-start-using-javascript-strict-mode/)
2012-06-04, v0.6.10
-------------------
......
......@@ -4,15 +4,15 @@ CasperJS is a navigation scripting & testing utility for [PhantomJS](http://www.
It eases the process of defining a full navigation scenario and provides useful
high-level functions, methods & syntaxic sugar for doing common tasks such as:
- defining & ordering [navigation steps](http://casperjs.org/#quickstart)
- [filling forms](http://casperjs.org/#phantom_Casper_fill)
- [clicking links](http://casperjs.org/#phantom_Casper_click)
- [capturing screenshots](http://casperjs.org/#phantom_Casper_captureSelector) of a page (or an area)
- [making assertions on remote DOM](http://casperjs.org/#phantom_Casper_Tester)
- [logging](http://casperjs.org/#logging) & [events](http://casperjs.org/#events-filters)
- [downloading base64](http://casperjs.org/#phantom_Casper_download) encoded resources, even binary ones
- defining & ordering [navigation steps](http://casperjs.org/quickstart.html)
- [filling forms](http://casperjs.org/api.html#casper.fill)
- [clicking links](http://casperjs.org/api.html#casper.click)
- [capturing screenshots](http://casperjs.org/api.html#casper.captureSelector) of a page (or an area)
- [making assertions on remote DOM](http://casperjs.org/api.html#tester)
- [logging](http://casperjs.org/logging.html) & [events](http://casperjs.org/events-filters.html)
- [downloading base64](http://casperjs.org/api.html#casper.download) encoded resources, even binary ones
- catching errors and react accordingly
- writing [functional test suites](http://casperjs.org/#testing), exporting results as JUnit XML (xUnit)
- writing [functional test suites](http://casperjs.org/testing.html), exporting results as JUnit XML (xUnit)
Browse the [sample examples repository](https://github.com/n1k0/casperjs/tree/master/samples).
Don't hesitate to pull request for any cool example of yours as well!
......
......@@ -27,259 +27,266 @@
* DEALINGS IN THE SOFTWARE.
*
*/
/*global console phantom require*/
if (phantom.version.major !== 1 || phantom.version.minor < 5) {
console.error('CasperJS needs at least PhantomJS v1.5.0');
phantom.exit(1);
}
/**
* Loads and initialize the CasperJS environment.
*/
phantom.loadCasper = function loadCasper() {
// Patching fs
// TODO: watch for these methods being implemented in official fs module
var fs = (function _fs(fs) {
if (!fs.hasOwnProperty('basename')) {
fs.basename = function basename(path) {
return path.replace(/.*\//, '');
};
}
if (!fs.hasOwnProperty('dirname')) {
fs.dirname = function dirname(path) {
return path.replace(/\\/g, '/').replace(/\/[^\/]*$/, '');
};
}
if (!fs.hasOwnProperty('isWindows')) {
fs.isWindows = function isWindows() {
var testPath = arguments[0] || this.workingDirectory;
return (/^[a-z]{1,2}:/i).test(testPath) || testPath.indexOf("\\\\") === 0;
};
}
if (!fs.hasOwnProperty('pathJoin')) {
fs.pathJoin = function pathJoin() {
return Array.prototype.join.call(arguments, this.separator);
};
}
return fs;
})(require('fs'));
// casper root path
if (!phantom.casperPath) {
try {
phantom.casperPath = phantom.args.map(function _map(i) {
var match = i.match(/^--casper-path=(.*)/);
if (match) {
return fs.absolute(match[1]);
}
}).filter(function _filter(path) {
return fs.isDirectory(path);
}).pop();
} catch (e) {}
}
if (!phantom.casperPath) {
console.error("Couldn't find nor compute phantom.casperPath, exiting.");
phantom.exit(1);
}
(function bootstrap(global) {
"use strict";
/**
* Loads and initialize the CasperJS environment.
*/
phantom.loadCasper = function loadCasper() {
// Patching fs
// TODO: watch for these methods being implemented in official fs module
var fs = (function _fs(fs) {
if (!fs.hasOwnProperty('basename')) {
fs.basename = function basename(path) {
return path.replace(/.*\//, '');
};
}
if (!fs.hasOwnProperty('dirname')) {
fs.dirname = function dirname(path) {
return path.replace(/\\/g, '/').replace(/\/[^\/]*$/, '');
};
}
if (!fs.hasOwnProperty('isWindows')) {
fs.isWindows = function isWindows() {
var testPath = arguments[0] || this.workingDirectory;
return (/^[a-z]{1,2}:/i).test(testPath) || testPath.indexOf("\\\\") === 0;
};
}
if (!fs.hasOwnProperty('pathJoin')) {
fs.pathJoin = function pathJoin() {
return Array.prototype.join.call(arguments, this.separator);
};
}
return fs;
})(require('fs'));
// Embedded, up-to-date, validatable & controlable CoffeeScript
phantom.injectJs(fs.pathJoin(phantom.casperPath, 'modules', 'vendors', 'coffee-script.js'));
// casper root path
if (!phantom.casperPath) {
try {
phantom.casperPath = phantom.args.map(function _map(i) {
var match = i.match(/^--casper-path=(.*)/);
if (match) {
return fs.absolute(match[1]);
}
}).filter(function _filter(path) {
return fs.isDirectory(path);
}).pop();
} catch (e) {}
}
// custom global CasperError
window.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";
if (!phantom.casperPath) {
console.error("Couldn't find nor compute phantom.casperPath, exiting.");
phantom.exit(1);
}
this.message = msg;
this.name = 'CasperError';
};
// standard Error prototype inheritance
window.CasperError.prototype = Object.getPrototypeOf(new Error());
// Embedded, up-to-date, validatable & controlable CoffeeScript
phantom.injectJs(fs.pathJoin(phantom.casperPath, 'modules', 'vendors', 'coffee-script.js'));
// CasperJS version, extracted from package.json - see http://semver.org/
phantom.casperVersion = (function getVersion(path) {
var parts, patchPart, pkg, pkgFile;
var fs = require('fs');
pkgFile = fs.absolute(fs.pathJoin(path, 'package.json'));
if (!fs.exists(pkgFile)) {
throw new CasperError('Cannot find package.json at ' + pkgFile);
}
try {
pkg = JSON.parse(require('fs').read(pkgFile));
} catch (e) {
throw new CasperError('Cannot read package file contents: ' + e);
}
parts = pkg.version.trim().split(".");
if (parts < 3) {
throw new CasperError("Invalid version number");
}
patchPart = parts[2].split('-');
return {
major: ~~parts[0] || 0,
minor: ~~parts[1] || 0,
patch: ~~patchPart[0] || 0,
ident: patchPart[1] || "",
toString: function toString() {
var version = [this.major, this.minor, this.patch].join('.');
if (this.ident) {
version = [version, this.ident].join('-');
}
return version;
// 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';
};
})(phantom.casperPath);
/**
* Retrieves the javascript source code from a given .js or .coffee file.
*
* @param String file The path to the file
* @param Function|null onError An error callback (optional)
*/
phantom.getScriptCode = function getScriptCode(file, onError) {
var scriptCode = fs.read(file);
if (/\.coffee$/i.test(file)) {
scriptCode = CoffeeScript.compile(scriptCode);
}
return scriptCode;
};
// standard Error prototype inheritance
global.CasperError.prototype = Object.getPrototypeOf(new Error());
/**
* Patching require() to allow loading of other modules than PhantomJS'
* builtin ones.
* Inspired by phantomjs-nodify: https://github.com/jgonera/phantomjs-nodify/
* TODO: remove when PhantomJS has full module support
*/
require = (function _require(require, requireDir) {
var phantomBuiltins = ['fs', 'webpage', 'webserver', 'system'];
var phantomRequire = phantom.__orig__require = require;
var requireCache = {};
return function _require(path) {
var i, dir, paths = [],
fileGuesses = [],
file,
module = {
exports: {}
};
if (phantomBuiltins.indexOf(path) !== -1) {
return phantomRequire(path);
}
if (path[0] === '.') {
paths.push.apply(paths, [
fs.absolute(path),
fs.absolute(fs.pathJoin(requireDir, path))
]);
} else if (path[0] === '/') {
paths.push(path);
} else {
dir = fs.absolute(requireDir);
while (dir !== '' && dir.lastIndexOf(':') !== dir.length - 1) {
// nodejs compatibility
paths.push(fs.pathJoin(dir, 'node_modules', path));
dir = fs.dirname(dir);
}
paths.push(fs.pathJoin(requireDir, 'lib', path));
paths.push(fs.pathJoin(requireDir, 'modules', path));
}
paths.forEach(function _forEach(testPath) {
fileGuesses.push.apply(fileGuesses, [
testPath,
testPath + '.js',
testPath + '.coffee',
fs.pathJoin(testPath, 'index.js'),
fs.pathJoin(testPath, 'index.coffee'),
fs.pathJoin(testPath, 'lib', fs.basename(testPath) + '.js'),
fs.pathJoin(testPath, 'lib', fs.basename(testPath) + '.coffee')
]);
});
file = null;
for (i = 0; i < fileGuesses.length && !file; ++i) {
if (fs.isFile(fileGuesses[i])) {
file = fileGuesses[i];
}
}
if (!file) {
throw new Error("CasperJS couldn't find module " + path);
}
if (file in requireCache) {
return requireCache[file].exports;
// CasperJS version, extracted from package.json - see http://semver.org/
phantom.casperVersion = (function getVersion(path) {
var parts, patchPart, pkg, pkgFile;
var fs = require('fs');
pkgFile = fs.absolute(fs.pathJoin(path, 'package.json'));
if (!fs.exists(pkgFile)) {
throw new global.CasperError('Cannot find package.json at ' + pkgFile);
}
var scriptCode = phantom.getScriptCode(file);
var fn = new Function('__file__', 'require', 'module', 'exports', scriptCode);
try {
fn(file, _require, module, module.exports);
pkg = JSON.parse(require('fs').read(pkgFile));
} catch (e) {
var error = new CasperError('__mod_error(' + path + '):: ' + e);
error.file = file;
throw error;
throw new global.CasperError('Cannot read package file contents: ' + e);
}
requireCache[file] = module;
return module.exports;
parts = pkg.version.trim().split(".");
if (parts < 3) {
throw new global.CasperError("Invalid version number");
}
patchPart = parts[2].split('-');
return {
major: ~~parts[0] || 0,
minor: ~~parts[1] || 0,
patch: ~~patchPart[0] || 0,
ident: patchPart[1] || "",
toString: function toString() {
var version = [this.major, this.minor, this.patch].join('.');
if (this.ident) {
version = [version, this.ident].join('-');
}
return version;
}
};
})(phantom.casperPath);
/**
* Retrieves the javascript source code from a given .js or .coffee file.
*
* @param String file The path to the file
* @param Function|null onError An error callback (optional)
*/
phantom.getScriptCode = function getScriptCode(file, onError) {
var scriptCode = fs.read(file);
if (/\.coffee$/i.test(file)) {
/*global CoffeeScript*/
scriptCode = CoffeeScript.compile(scriptCode);
}
return scriptCode;
};
})(require, phantom.casperPath);
// BC < 0.6
phantom.Casper = require('casper').Casper;
/**
* Patching require() to allow loading of other modules than PhantomJS'
* builtin ones.
* Inspired by phantomjs-nodify: https://github.com/jgonera/phantomjs-nodify/
* TODO: remove when PhantomJS has full module support
*/
require = (function _require(require, requireDir) {
var phantomBuiltins = ['fs', 'webpage', 'webserver', 'system'];
var phantomRequire = phantom.__orig__require = require;
var requireCache = {};
return function _require(path) {
var i, dir, paths = [],
fileGuesses = [],
file,
module = {
exports: {}
};
if (phantomBuiltins.indexOf(path) !== -1) {
return phantomRequire(path);
}
if (path[0] === '.') {
paths.push.apply(paths, [
fs.absolute(path),
fs.absolute(fs.pathJoin(requireDir, path))
]);
} else if (path[0] === '/') {
paths.push(path);
} else {
dir = fs.absolute(requireDir);
while (dir !== '' && dir.lastIndexOf(':') !== dir.length - 1) {
// nodejs compatibility
paths.push(fs.pathJoin(dir, 'node_modules', path));
dir = fs.dirname(dir);
}
paths.push(fs.pathJoin(requireDir, 'lib', path));
paths.push(fs.pathJoin(requireDir, 'modules', path));
}
paths.forEach(function _forEach(testPath) {
fileGuesses.push.apply(fileGuesses, [
testPath,
testPath + '.js',
testPath + '.coffee',
fs.pathJoin(testPath, 'index.js'),
fs.pathJoin(testPath, 'index.coffee'),
fs.pathJoin(testPath, 'lib', fs.basename(testPath) + '.js'),
fs.pathJoin(testPath, 'lib', fs.basename(testPath) + '.coffee')
]);
});
file = null;
for (i = 0; i < fileGuesses.length && !file; ++i) {
if (fs.isFile(fileGuesses[i])) {
file = fileGuesses[i];
}
}
if (!file) {
throw new Error("CasperJS couldn't find module " + path);
}
if (file in requireCache) {
return requireCache[file].exports;
}
var scriptCode = phantom.getScriptCode(file);
var fn = new Function('__file__', 'require', 'module', 'exports', scriptCode);
try {
fn(file, _require, module, module.exports);
} catch (e) {
var error = new global.CasperError('__mod_error(' + path + '):: ' + e);
error.file = file;
throw error;
}
requireCache[file] = module;
return module.exports;
};
})(require, phantom.casperPath);
// casper cli args
phantom.casperArgs = require('cli').parse(phantom.args);
// BC < 0.6
phantom.Casper = require('casper').Casper;
// loaded status
phantom.casperLoaded = true;
};
// casper cli args
phantom.casperArgs = require('cli').parse(phantom.args);
/**
* Initializes the CasperJS Command Line Interface.
*/
phantom.initCasperCli = function initCasperCli() {
var fs = require("fs");
// loaded status
phantom.casperLoaded = true;
};
if (!!phantom.casperArgs.options.version) {
console.log(phantom.casperVersion.toString());
phantom.exit(0);
} else if (phantom.casperArgs.get(0) === "test") {
phantom.casperScript = fs.absolute(fs.pathJoin(phantom.casperPath, 'tests', 'run.js'));
phantom.casperArgs.drop("test");
} else if (phantom.casperArgs.args.length === 0 || !!phantom.casperArgs.options.help) {
var phantomVersion = [phantom.version.major, phantom.version.minor, phantom.version.patch].join('.');
var f = require("utils").format;
console.log(f('CasperJS version %s at %s, using PhantomJS version %s',
phantom.casperVersion.toString(),
phantom.casperPath, phantomVersion));
console.log(fs.read(fs.pathJoin(phantom.casperPath, 'bin', 'usage.txt')));
phantom.exit(0);
}
/**
* Initializes the CasperJS Command Line Interface.
*/
phantom.initCasperCli = function initCasperCli() {
var fs = require("fs");
if (!!phantom.casperArgs.options.version) {
console.log(phantom.casperVersion.toString());
phantom.exit(0);
} else if (phantom.casperArgs.get(0) === "test") {
phantom.casperScript = fs.absolute(fs.pathJoin(phantom.casperPath, 'tests', 'run.js'));
phantom.casperArgs.drop("test");
} else if (phantom.casperArgs.args.length === 0 || !!phantom.casperArgs.options.help) {
var phantomVersion = [phantom.version.major, phantom.version.minor, phantom.version.patch].join('.');
var f = require("utils").format;
console.log(f('CasperJS version %s at %s, using PhantomJS version %s',
phantom.casperVersion.toString(),
phantom.casperPath, phantomVersion));
console.log(fs.read(fs.pathJoin(phantom.casperPath, 'bin', 'usage.txt')));
phantom.exit(0);
}
if (!phantom.casperScript) {
phantom.casperScript = phantom.casperArgs.get(0);
}
if (!fs.isFile(phantom.casperScript)) {
console.error('Unable to open file: ' + phantom.casperScript);
phantom.exit(1);
}
if (!phantom.casperScript) {
phantom.casperScript = phantom.casperArgs.get(0);
}
// filter out the called script name from casper args
phantom.casperArgs.drop(phantom.casperScript);
if (!fs.isFile(phantom.casperScript)) {
console.error('Unable to open file: ' + phantom.casperScript);
phantom.exit(1);
}
// passed casperjs script execution
phantom.injectJs(phantom.casperScript);
};
// filter out the called script name from casper args
phantom.casperArgs.drop(phantom.casperScript);
if (!phantom.casperLoaded) {
try {
phantom.loadCasper();
} catch (e) {
console.error("Unable to load casper environment: " + e);
phantom.exit();
// passed casperjs script execution
phantom.injectJs(phantom.casperScript);
};
if (!phantom.casperLoaded) {
try {
phantom.loadCasper();
} catch (e) {
console.error("Unable to load casper environment: " + e);
phantom.exit();
}
}
}
if (true === phantom.casperArgs.get('cli')) {
phantom.initCasperCli();
}
if (true === phantom.casperArgs.get('cli')) {
phantom.initCasperCli();
}
})(window);
......
......@@ -48,6 +48,6 @@ CASPER_COMMAND.extend(CASPER_ARGS)
try:
os.execvp(CASPER_COMMAND[0], CASPER_COMMAND)
except OSError, err:
print('Fatal: %s; did you install phantomjs?' % err)
except OSError as err:
print(('Fatal: %s; did you install phantomjs?' % err))
sys.exit(1)
......
Subproject commit ead2191ceb086cd5665e18a822f72689d90a6c3d
Subproject commit 76cc32f262a3ef924a67f33bb17432e3a5c33e03
......
......@@ -28,6 +28,8 @@
*
*/
/*global CasperError console exports phantom require*/
var colorizer = require('colorizer');
var events = require('events');
var fs = require('fs');
......@@ -37,11 +39,24 @@ var tester = require('tester');
var utils = require('utils');
var f = utils.format;
var defaultUserAgent = phantom.defaultPageSettings.userAgent
.replace('PhantomJS', f("CasperJS/%s", phantom.casperVersion) + '+Phantomjs');
exports.create = function create(options) {
"use strict";
return new Casper(options);
};
exports.selectXPath = function selectXPath(expression) {
/**
* Shortcut to build an XPath selector object.
*
* @param String expression The XPath expression
* @return Object
* @see http://casperjs.org/selectors.html
*/
function selectXPath(expression) {
"use strict";
return {
type: 'xpath',
path: expression,
......@@ -49,7 +64,8 @@ exports.selectXPath = function selectXPath(expression) {
return this.type + ' selector: ' + this.path;
}
};
};
}
exports.selectXPath = selectXPath;
/**
* Main Casper object.
......@@ -57,10 +73,9 @@ exports.selectXPath = function selectXPath(expression) {
* @param Object options Casper options
*/
var Casper = function Casper(options) {
var DEFAULT_DIE_MESSAGE = "Suite explicitely interrupted without any message given.";
var DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.112 Safari/535.1";
"use strict";
// init & checks
if (!(this instanceof arguments.callee)) {
if (!(this instanceof Casper)) {
return new Casper(options);
}
// default options
......@@ -70,6 +85,7 @@ var Casper = function Casper(options) {
exitOnError: true,
logLevel: "error",
httpStatusHandlers: {},
safeLogs: true,
onAlert: null,
onDie: null,
onError: null,
......@@ -83,7 +99,7 @@ var Casper = function Casper(options) {
page: null,
pageSettings: {
localToRemoteUrlAccessEnabled: true,
userAgent: DEFAULT_USER_AGENT
userAgent: defaultUserAgent
},
stepTimeout: null,
timeout: null,
......@@ -150,7 +166,7 @@ var Casper = function Casper(options) {
// deprecated feature event handler
this.on('deprecated', function onDeprecated(message) {
this.echo('[deprecated] ' + message, 'COMMENT');
this.warn('[deprecated] ' + message);
});
// dispatching an event when instance has been constructed
......@@ -166,6 +182,7 @@ utils.inherits(Casper, events.EventEmitter);
* @return Casper
*/
Casper.prototype.back = function back() {
"use strict";
return this.then(function _step() {
this.emit('back');
this.evaluate(function _evaluate() {
......@@ -186,8 +203,9 @@ Casper.prototype.back = function back() {
* @return string Base64 encoded result
*/
Casper.prototype.base64encode = function base64encode(url, method, data) {
"use strict";
return this.evaluate(function _evaluate(url, method, data) {
return __utils__.getBase64(url, method, data);
return window.__utils__.getBase64(url, method, data);
}, { url: url, method: method, data: data });
};
......@@ -202,6 +220,7 @@ Casper.prototype.base64encode = function base64encode(url, method, data) {
* @return Casper
*/
Casper.prototype.capture = function capture(targetFile, clipRect) {
"use strict";
var previousClipRect;
targetFile = fs.absolute(targetFile);
if (clipRect) {
......@@ -234,6 +253,7 @@ Casper.prototype.capture = function capture(targetFile, clipRect) {
* @return Casper
*/
Casper.prototype.captureSelector = function captureSelector(targetFile, selector) {
"use strict";
return this.capture(targetFile, this.getElementBounds(selector));
};
......@@ -244,6 +264,7 @@ Casper.prototype.captureSelector = function captureSelector(targetFile, selector
* @param function onComplete An options callback to apply on completion
*/
Casper.prototype.checkStep = function checkStep(self, onComplete) {
"use strict";
if (self.pendingWait || self.loadInProgress) {
return;
}
......@@ -274,6 +295,7 @@ Casper.prototype.checkStep = function checkStep(self, onComplete) {
* @return Casper
*/
Casper.prototype.clear = function clear() {
"use strict";
this.page.content = '';
return this;
};
......@@ -288,6 +310,7 @@ Casper.prototype.clear = function clear() {
* @return Boolean
*/
Casper.prototype.click = function click(selector) {
"use strict";
return this.mouseEvent('click', selector);
};
......@@ -296,10 +319,14 @@ Casper.prototype.click = function click(selector) {
* element matching this label will be selected, so use with caution.
*
* @param String label Element innerText value
* @param String tag An element tag name (eg. `a` or `button`) (optional)
* @return Boolean
*/
Casper.prototype.clickLabel = function clickLabel(label) {
var selector = exports.selectXPath('//*[text()="' + label.toString() + '"]');
Casper.prototype.clickLabel = function clickLabel(label, tag) {
"use strict";
tag = tag || "*";
var escapedLabel = label.toString().replace(/"/g, '\\"');
var selector = selectXPath(f('//%s[text()="%s"]', tag, escapedLabel));
return this.click(selector);
};
......@@ -311,6 +338,7 @@ Casper.prototype.clickLabel = function clickLabel(label) {
* @return Function The final step function
*/
Casper.prototype.createStep = function createStep(fn, options) {
"use strict";
if (!utils.isFunction(fn)) {
throw new CasperError("createStep(): a step definition must be a function");
}
......@@ -325,6 +353,7 @@ Casper.prototype.createStep = function createStep(fn, options) {
* @return Casper
*/
Casper.prototype.debugHTML = function debugHTML() {
"use strict";
this.echo(this.page.content);
return this;
};
......@@ -335,8 +364,9 @@ Casper.prototype.debugHTML = function debugHTML() {
* @return Casper
*/
Casper.prototype.debugPage = function debugPage() {
"use strict";
this.echo(this.evaluate(function _evaluate() {
return document.body.innerText;
return document.body.textContent || document.body.innerText;
}));
return this;
};
......@@ -349,9 +379,12 @@ Casper.prototype.debugPage = function debugPage() {
* @return Casper
*/
Casper.prototype.die = function die(message, status) {
"use strict";
this.result.status = "error";
this.result.time = new Date().getTime() - this.startTime;
message = utils.isString(message) && message.length > 0 ? message : DEFAULT_DIE_MESSAGE;
if (!utils.isString(message) || !message.length) {
message = "Suite explicitely interrupted without any message given.";
}
this.log(message, "error");
this.emit('die', message, status);
if (utils.isFunction(this.options.onDie)) {
......@@ -365,10 +398,13 @@ Casper.prototype.die = function die(message, status) {
*
* @param String url The url of the resource to download
* @param String targetPath The destination file path
* @param String method The HTTP method to use (default: GET)
* @param String data Optional data to pass performing the request
* @return Casper
*/
Casper.prototype.download = function download(url, targetPath, method, data) {
var cu = require('clientutils').create();
"use strict";
var cu = require('clientutils').create(this.options);
try {
fs.write(targetPath, cu.decode(this.base64encode(url, method, data)), 'wb');
this.emit('downloaded.file', targetPath);
......@@ -388,6 +424,7 @@ Casper.prototype.download = function download(url, targetPath, method, data) {
* @return Casper
*/
Casper.prototype.each = function each(array, fn) {
"use strict";
if (!utils.isArray(array)) {
this.log("each() only works with arrays", "error");
return this;
......@@ -409,6 +446,7 @@ Casper.prototype.each = function each(array, fn) {
* @return Casper
*/
Casper.prototype.echo = function echo(text, style, pad) {
"use strict";
var message = style ? this.colorizer.colorize(text, style, pad) : text;
console.log(this.filter('echo.message', message) || message);
return this;
......@@ -438,6 +476,7 @@ Casper.prototype.echo = function echo(text, style, pad) {
* @see WebPage#evaluate
*/
Casper.prototype.evaluate = function evaluate(fn, context) {
"use strict";
// ensure client utils are always injected
this.injectClientUtils();
// function processing
......@@ -455,6 +494,7 @@ Casper.prototype.evaluate = function evaluate(fn, context) {
* @return Casper
*/
Casper.prototype.evaluateOrDie = function evaluateOrDie(fn, message) {
"use strict";
if (!this.evaluate(fn)) {
return this.die(message);
}
......@@ -469,8 +509,9 @@ Casper.prototype.evaluateOrDie = function evaluateOrDie(fn, message) {
* @return Boolean
*/
Casper.prototype.exists = function exists(selector) {
"use strict";
return this.evaluate(function _evaluate(selector) {
return __utils__.exists(selector);
return window.__utils__.exists(selector);
}, { selector: selector });
};
......@@ -481,6 +522,7 @@ Casper.prototype.exists = function exists(selector) {
* @return Casper
*/
Casper.prototype.exit = function exit(status) {
"use strict";
this.emit('exit', status);
phantom.exit(status);
return this;
......@@ -494,8 +536,9 @@ Casper.prototype.exit = function exit(status) {
* @return String
*/
Casper.prototype.fetchText = function fetchText(selector) {
"use strict";
return this.evaluate(function _evaluate(selector) {
return __utils__.fetchText(selector);
return window.__utils__.fetchText(selector);
}, { selector: selector });
};
......@@ -507,13 +550,14 @@ Casper.prototype.fetchText = function fetchText(selector) {
* @param Boolean submit Submit the form?
*/
Casper.prototype.fill = function fill(selector, vals, submit) {
"use strict";
submit = submit === true ? submit : false;
if (!utils.isObject(vals)) {
throw new CasperError("Form values must be provided as an object");
}
this.emit('fill', selector, vals, submit);
var fillResults = this.evaluate(function _evaluate(selector, values) {
return __utils__.fill(selector, values);
return window.__utils__.fill(selector, values);
}, {
selector: selector,
values: vals
......@@ -548,10 +592,10 @@ Casper.prototype.fill = function fill(selector, vals, submit) {
// Form submission?
if (submit) {
this.evaluate(function _evaluate(selector) {
var form = __utils__.findOne(selector);
var form = window.__utils__.findOne(selector);
var method = (form.getAttribute('method') || "GET").toUpperCase();
var action = form.getAttribute('action') || "unknown";
__utils__.log('submitting form to ' + action + ', HTTP ' + method, 'info');
window.__utils__.log('submitting form to ' + action + ', HTTP ' + method, 'info');
if (typeof form.submit === "function") {
form.submit();
} else {
......@@ -568,6 +612,7 @@ Casper.prototype.fill = function fill(selector, vals, submit) {
* @return Casper
*/
Casper.prototype.forward = function forward(then) {
"use strict";
return this.then(function _step() {
this.emit('forward');
this.evaluate(function _evaluate() {
......@@ -583,6 +628,7 @@ Casper.prototype.forward = function forward(then) {
* @return Object
*/
Casper.prototype.getColorizer = function getColorizer() {
"use strict";
return colorizer.create(this.options.colorizerType || 'Colorizer');
};
......@@ -592,6 +638,7 @@ Casper.prototype.getColorizer = function getColorizer() {
* @return String
*/
Casper.prototype.getCurrentUrl = function getCurrentUrl() {
"use strict";
return decodeURIComponent(this.evaluate(function _evaluate() {
return document.location.href;
}));
......@@ -604,11 +651,12 @@ Casper.prototype.getCurrentUrl = function getCurrentUrl() {
* @return Object
*/
Casper.prototype.getElementBounds = function getElementBounds(selector) {
"use strict";
if (!this.exists(selector)) {
throw new CasperError("No element matching selector found: " + selector);
}
var clipRect = this.evaluate(function _evaluate(selector) {
return __utils__.getElementBounds(selector);
return window.__utils__.getElementBounds(selector);
}, { selector: selector });
if (!utils.isClipRect(clipRect)) {
throw new CasperError('Could not fetch boundaries for element matching selector: ' + selector);
......@@ -623,13 +671,14 @@ Casper.prototype.getElementBounds = function getElementBounds(selector) {
* @return mixed
*/
Casper.prototype.getGlobal = function getGlobal(name) {
"use strict";
var result = this.evaluate(function _evaluate(name) {
var result = {};
try {
result.value = JSON.stringify(window[name]);
} catch (e) {
var message = f("Unable to JSON encode window.%s: %s", name, e);
__utils__.log(message, "error");
window.__utils__.log(message, "error");
result.error = message;
}
return result;
......@@ -649,6 +698,7 @@ Casper.prototype.getGlobal = function getGlobal(name) {
* @return String
*/
Casper.prototype.getTitle = function getTitle() {
"use strict";
return this.evaluate(function _evaluate() {
return document.title;
});
......@@ -659,6 +709,7 @@ Casper.prototype.getTitle = function getTitle() {
*
*/
Casper.prototype.initErrorHandler = function initErrorHandler() {
"use strict";
var casper = this;
phantom.onError = function phantom_onError(msg, backtrace) {
casper.emit('error', msg, backtrace);
......@@ -673,8 +724,9 @@ Casper.prototype.initErrorHandler = function initErrorHandler() {
*
*/
Casper.prototype.injectClientUtils = function injectClientUtils() {
"use strict";
var clientUtilsInjected = this.page.evaluate(function() {
return typeof __utils__ === "object";
return typeof window.__utils__ === "object";
});
if (true === clientUtilsInjected) {
return;
......@@ -683,8 +735,13 @@ Casper.prototype.injectClientUtils = function injectClientUtils() {
if (true === this.page.injectJs(clientUtilsPath)) {
this.log("Successfully injected Casper client-side utilities", "debug");
} else {
this.log("Failed to instantiate Casper client-side utilities!", "warning");
this.warn("Failed to inject Casper client-side utilities");
}
// ClientUtils and Casper shares the same options
// These are not the lines I'm the most proud of in my life, but it works.
this.page.evaluate(function() {
window.__utils__ = new ClientUtils(__options);
}.toString().replace('__options', JSON.stringify(this.options)));
};
/**
......@@ -696,6 +753,7 @@ Casper.prototype.injectClientUtils = function injectClientUtils() {
* @return Casper
*/
Casper.prototype.log = function log(message, level, space) {
"use strict";
level = level && this.logLevels.indexOf(level) > -1 ? level : "debug";
space = space ? space : "phantom";
if (level === "error" && utils.isFunction(this.options.onError)) {
......@@ -735,12 +793,13 @@ Casper.prototype.log = function log(message, level, space) {
* @return Boolean
*/
Casper.prototype.mouseEvent = function mouseEvent(type, selector) {
"use strict";
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));
}
var eventSuccess = this.evaluate(function(type, selector) {
return __utils__.mouseEvent(type, selector);
return window.__utils__.mouseEvent(type, selector);
}, {
type: type,
selector: selector
......@@ -758,13 +817,20 @@ Casper.prototype.mouseEvent = function mouseEvent(type, selector) {
};
/**
* Performs an HTTP request.
* Performs an HTTP request, with optional settings.
*
* Available settings are:
*
* - String method: The HTTP method to use
* - Object data: The data to use to perform the request, eg. {foo: 'bar'}
* - Array headers: An array of request headers, eg. [{'Cache-Control': 'max-age=0'}]
*
* @param String location The url to open
* @param Object settings The request settings
* @param Object settings The request settings (optional)
* @return Casper
*/
Casper.prototype.open = function open(location, settings) {
"use strict";
// settings validation
if (!settings) {
settings = {
......@@ -805,9 +871,13 @@ 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
data: settings.data,
headers: settings.headers
}, this.page.settings);
this.resources = [];
return this;
......@@ -822,6 +892,7 @@ Casper.prototype.open = function open(location, settings) {
* @see Casper#then
*/
Casper.prototype.repeat = function repeat(times, then) {
"use strict";
for (var i = 0; i < times; i++) {
this.then(then);
}
......@@ -836,6 +907,7 @@ Casper.prototype.repeat = function repeat(times, then) {
* @return Boolean
*/
Casper.prototype.resourceExists = function resourceExists(test) {
"use strict";
var testFn;
switch (utils.betterTypeOf(test)) {
case "string":
......@@ -866,6 +938,7 @@ Casper.prototype.resourceExists = function resourceExists(test) {
* @return Casper
*/
Casper.prototype.run = function run(onComplete, time) {
"use strict";
if (!this.steps || this.steps.length < 1) {
this.log("No steps defined, aborting", "error");
return this;
......@@ -882,6 +955,7 @@ Casper.prototype.run = function run(onComplete, time) {
* @param Function step
*/
Casper.prototype.runStep = function runStep(step) {
"use strict";
var skipLog = utils.isObject(step.options) && step.options.skipLog === true;
var stepInfo = f("Step %d/%d", this.step, this.steps.length);
var stepResult;
......@@ -891,7 +965,7 @@ Casper.prototype.runStep = function runStep(step) {
if (utils.isNumber(this.options.stepTimeout) && this.options.stepTimeout > 0) {
var stepTimeoutCheckInterval = setInterval(function _check(self, start, stepNum) {
if (new Date().getTime() - start > self.options.stepTimeout) {
if (self.step == stepNum) {
if (self.step === stepNum) {
self.emit('step.timeout');
if (utils.isFunction(self.options.onStepTimeout)) {
self.options.onStepTimeout.call(self, self);
......@@ -922,6 +996,7 @@ Casper.prototype.runStep = function runStep(step) {
* @return Casper
*/
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");
}
......@@ -943,6 +1018,7 @@ Casper.prototype.setHttpAuth = function setHttpAuth(username, password) {
* @return Casper
*/
Casper.prototype.start = function start(location, then) {
"use strict";
this.emit('starting');
this.log('Starting...', "info");
this.startTime = new Date().getTime();
......@@ -997,6 +1073,7 @@ Casper.prototype.start = function start(location, then) {
* @return Casper
*/
Casper.prototype.then = function then(step) {
"use strict";
if (!this.started) {
throw new CasperError("Casper not started; please use Casper#start");
}
......@@ -1036,6 +1113,7 @@ Casper.prototype.then = function then(step) {
* @see Casper#then
*/
Casper.prototype.thenClick = function thenClick(selector, then, fallbackToHref) {
"use strict";
if (arguments.length > 2) {
this.emit("deprecated", "The thenClick() method does not process the fallbackToHref argument since 0.6");
}
......@@ -1055,6 +1133,7 @@ Casper.prototype.thenClick = function thenClick(selector, then, fallbackToHref)
* @see Casper#evaluate
*/
Casper.prototype.thenEvaluate = function thenEvaluate(fn, context) {
"use strict";
return this.then(function _step() {
this.evaluate(fn, context);
});
......@@ -1069,6 +1148,7 @@ Casper.prototype.thenEvaluate = function thenEvaluate(fn, context) {
* @see Casper#open
*/
Casper.prototype.thenOpen = function thenOpen(location, then) {
"use strict";
this.then(this.createStep(function _step() {
this.open(location);
}, {
......@@ -1089,10 +1169,26 @@ Casper.prototype.thenOpen = function thenOpen(location, then) {
* @see Casper#open
*/
Casper.prototype.thenOpenAndEvaluate = function thenOpenAndEvaluate(location, fn, context) {
"use strict";
return this.thenOpen(location).thenEvaluate(fn, context);
};
/**
* Sets the user-agent string currently used when requesting urls.
*
* @param String userAgent User agent string
* @return String
*/
Casper.prototype.userAgent = function userAgent(agent) {
"use strict";
if (!this.started) {
throw new CasperError("Casper not started, can't set userAgent");
}
this.options.pageSettings.userAgent = this.page.settings.userAgent = agent;
return this;
};
/**
* Changes the current viewport size.
*
* @param Number width The viewport width, in pixels
......@@ -1100,6 +1196,7 @@ Casper.prototype.thenOpenAndEvaluate = function thenOpenAndEvaluate(location, fn
* @return Casper
*/
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");
}
......@@ -1123,12 +1220,26 @@ Casper.prototype.viewport = function viewport(width, height) {
* @return Boolean
*/
Casper.prototype.visible = function visible(selector) {
"use strict";
return this.evaluate(function _evaluate(selector) {
return __utils__.visible(selector);
return window.__utils__.visible(selector);
}, { selector: selector });
};
/**
* Displays a warning message onto the console and logs the event.
*
* @param String message
* @return Casper
*/
Casper.prototype.warn = function warn(message) {
"use strict";
this.log(message, "warning", "phantom");
var formatted = f.apply(null, ["⚠  " + message].concat([].slice.call(arguments, 1)));
return this.echo(formatted, 'COMMENT');
};
/**
* Adds a new step that will wait for a given amount of time (expressed
* in milliseconds) before processing an optional next one.
*
......@@ -1137,6 +1248,7 @@ Casper.prototype.visible = function visible(selector) {
* @return Casper
*/
Casper.prototype.wait = function wait(timeout, then) {
"use strict";
timeout = ~~timeout;
if (timeout < 1) {
this.die("wait() only accepts a positive integer > 0 as a timeout value");
......@@ -1157,11 +1269,13 @@ Casper.prototype.wait = function wait(timeout, then) {
};
Casper.prototype.waitStart = function waitStart() {
"use strict";
this.emit('wait.start');
this.pendingWait = true;
};
Casper.prototype.waitDone = function waitDone() {
"use strict";
this.emit('wait.done');
this.pendingWait = false;
};
......@@ -1176,6 +1290,7 @@ Casper.prototype.waitDone = function waitDone() {
* @return Casper
*/
Casper.prototype.waitFor = function waitFor(testFx, then, onTimeout, timeout) {
"use strict";
timeout = timeout ? timeout : this.defaultWaitTimeout;
if (!utils.isFunction(testFx)) {
this.die("waitFor() needs a test function");
......@@ -1223,6 +1338,7 @@ Casper.prototype.waitFor = function waitFor(testFx, then, onTimeout, timeout) {
* @return Casper
*/
Casper.prototype.waitForResource = function waitForResource(test, then, onTimeout, timeout) {
"use strict";
timeout = timeout ? timeout : this.defaultWaitTimeout;
return this.waitFor(function _check() {
return this.resourceExists(test);
......@@ -1240,6 +1356,7 @@ Casper.prototype.waitForResource = function waitForResource(test, then, onTimeou
* @return Casper
*/
Casper.prototype.waitForSelector = function waitForSelector(selector, then, onTimeout, timeout) {
"use strict";
timeout = timeout ? timeout : this.defaultWaitTimeout;
return this.waitFor(function _check() {
return this.exists(selector);
......@@ -1257,6 +1374,7 @@ Casper.prototype.waitForSelector = function waitForSelector(selector, then, onTi
* @return Casper
*/
Casper.prototype.waitWhileSelector = function waitWhileSelector(selector, then, onTimeout, timeout) {
"use strict";
timeout = timeout ? timeout : this.defaultWaitTimeout;
return this.waitFor(function _check() {
return !this.exists(selector);
......@@ -1274,6 +1392,7 @@ Casper.prototype.waitWhileSelector = function waitWhileSelector(selector, then,
* @return Casper
*/
Casper.prototype.waitUntilVisible = function waitUntilVisible(selector, then, onTimeout, timeout) {
"use strict";
timeout = timeout ? timeout : this.defaultWaitTimeout;
return this.waitFor(function _check() {
return this.visible(selector);
......@@ -1291,6 +1410,7 @@ Casper.prototype.waitUntilVisible = function waitUntilVisible(selector, then, on
* @return Casper
*/
Casper.prototype.waitWhileVisible = function waitWhileVisible(selector, then, onTimeout, timeout) {
"use strict";
timeout = timeout ? timeout : this.defaultWaitTimeout;
return this.waitFor(function _check() {
return !this.visible(selector);
......@@ -1305,7 +1425,8 @@ Casper.prototype.waitWhileVisible = function waitWhileVisible(selector, then, on
* @since 0.6
*/
Casper.extend = function(proto) {
console.warn('Casper.extend() has been deprecated since 0.6; check the docs');
"use strict";
this.warn('Casper.extend() has been deprecated since 0.6; check the docs');
if (!utils.isObject(proto)) {
throw new CasperError("extends() only accept objects as prototypes");
}
......@@ -1321,12 +1442,8 @@ exports.Casper = Casper;
* @return WebPage
*/
function createPage(casper) {
var page;
if (phantom.version.major <= 1 && phantom.version.minor < 3 && utils.isFunction(require)) {
page = new WebPage();
} else {
page = require('webpage').create();
}
"use strict";
var page = require('webpage').create();
page.onAlert = function onAlert(message) {
casper.log('[alert] ' + message, "info", "remote");
casper.emit('remote.alert', message);
......@@ -1375,17 +1492,19 @@ function createPage(casper) {
}
}
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");
} else {
casper.options.clientScripts.forEach(function _forEach(script) {
if (casper.page.injectJs(script)) {
casper.log(f('Automatically injected %s client side', script), "debug");
} else {
casper.log(f('Failed injecting %s client side', script), "warning");
}
});
}
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);
}
});
}
// Client-side utils injection
casper.injectClientUtils();
......
......@@ -28,6 +28,8 @@
*
*/
/*global CasperError console exports phantom require*/
var utils = require('utils');
/**
......@@ -38,6 +40,7 @@ var utils = require('utils');
* @return Object
*/
exports.parse = function parse(phantomArgs) {
"use strict";
var extract = {
args: [],
options: {},
......@@ -110,6 +113,7 @@ exports.parse = function parse(phantomArgs) {
* @return Mixed
*/
function castArgument(arg) {
"use strict";
if (arg.match(/^-?\d+$/)) {
return parseInt(arg, 10);
} else if (arg.match(/^-?\d+\.\d+$/)) {
......
......@@ -27,15 +27,21 @@
* DEALINGS IN THE SOFTWARE.
*
*/
/*global console escape exports NodeList window*/
(function(exports) {
exports.create = function create() {
return new this.ClientUtils();
"use strict";
exports.create = function create(options) {
return new this.ClientUtils(options);
};
/**
* Casper client-side helpers.
*/
exports.ClientUtils = function ClientUtils() {
exports.ClientUtils = function ClientUtils(options) {
// private members
var BASE64_ENCODE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var BASE64_DECODE_CHARS = new Array(
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
......@@ -49,6 +55,9 @@
);
var SUPPORTED_SELECTOR_TYPES = ['css', 'xpath'];
// public members
this.options = options || {};
/**
* Clicks on the DOM element behind the provided selector.
*
......@@ -70,35 +79,35 @@
while (i < len) {
do {
c1 = BASE64_DECODE_CHARS[str.charCodeAt(i++) & 0xff];
} while (i < len && c1 == -1);
if (c1 == -1) {
} while (i < len && c1 === -1);
if (c1 === -1) {
break;
}
do {
c2 = BASE64_DECODE_CHARS[str.charCodeAt(i++) & 0xff];
} while (i < len && c2 == -1);
if (c2 == -1) {
} while (i < len && c2 === -1);
if (c2 === -1) {
break;
}
out += String.fromCharCode((c1 << 2) | ((c2 & 0x30) >> 4));
do {
c3 = str.charCodeAt(i++) & 0xff;
if (c3 == 61)
if (c3 === 61)
return out;
c3 = BASE64_DECODE_CHARS[c3];
} while (i < len && c3 == -1);
if (c3 == -1) {
} while (i < len && c3 === -1);
if (c3 === -1) {
break;
}
out += String.fromCharCode(((c2 & 0XF) << 4) | ((c3 & 0x3C) >> 2));
do {
c4 = str.charCodeAt(i++) & 0xff;
if (c4 == 61) {
if (c4 === 61) {
return out;
}
c4 = BASE64_DECODE_CHARS[c4];
} while (i < len && c4 == -1);
if (c4 == -1) {
} while (i < len && c4 === -1);
if (c4 === -1) {
break;
}
out += String.fromCharCode(((c3 & 0x03) << 6) | c4);
......@@ -117,14 +126,14 @@
var out = "", i = 0, len = str.length, c1, c2, c3;
while (i < len) {
c1 = str.charCodeAt(i++) & 0xff;
if (i == len) {
if (i === len) {
out += BASE64_ENCODE_CHARS.charAt(c1 >> 2);
out += BASE64_ENCODE_CHARS.charAt((c1 & 0x3) << 4);
out += "==";
break;
}
c2 = str.charCodeAt(i++);
if (i == len) {
if (i === len) {
out += BASE64_ENCODE_CHARS.charAt(c1 >> 2);
out += BASE64_ENCODE_CHARS.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4));
out += BASE64_ENCODE_CHARS.charAt((c2 & 0xF) << 2);
......@@ -165,7 +174,7 @@
var text = '', elements = this.findAll(selector);
if (elements && elements.length) {
Array.prototype.forEach.call(elements, function _forEach(element) {
text += element.innerText;
text += element.textContent || element.innerText;
});
}
return text;
......@@ -185,7 +194,7 @@
files: []
};
if (!(form instanceof HTMLElement) || typeof form === "string") {
__utils__.log("attempting to fetch form element from selector: '" + form + "'", "info");
this.log("attempting to fetch form element from selector: '" + form + "'", "info");
try {
form = this.findOne(form);
} catch (e) {
......@@ -203,7 +212,7 @@
if (!vals.hasOwnProperty(name)) {
continue;
}
var field = this.findAll('[name="' + name + '"]');
var field = this.findAll('[name="' + name + '"]', form);
var value = vals[name];
if (!field) {
out.errors.push('no field named "' + name + '" in form');
......@@ -229,16 +238,18 @@
/**
* Finds all DOM elements matching by the provided selector.
*
* @param String selector CSS3 selector
* @param String selector CSS3 selector
* @param HTMLElement|null scope Element to search child elements within
* @return NodeList|undefined
*/
this.findAll = function findAll(selector) {
this.findAll = function findAll(selector, scope) {
scope = scope || document;
try {
var pSelector = this.processSelector(selector);
if (pSelector.type === 'xpath') {
return this.getElementsByXPath(pSelector.path);
} else {
return document.querySelectorAll(pSelector.path);
return scope.querySelectorAll(pSelector.path);
}
} catch (e) {
this.log('findAll(): invalid selector provided "' + selector + '":' + e, "error");
......@@ -248,16 +259,18 @@
/**
* Finds a DOM element by the provided selector.
*
* @param String selector CSS3 selector
* @param String selector CSS3 selector
* @param HTMLElement|null scope Element to search child elements within
* @return HTMLElement|undefined
*/
this.findOne = function findOne(selector) {
this.findOne = function findOne(selector, scope) {
scope = scope || document;
try {
var pSelector = this.processSelector(selector);
if (pSelector.type === 'xpath') {
return this.getElementByXPath(pSelector.path);
} else {
return document.querySelector(pSelector.path);
return scope.querySelector(pSelector.path);
}
} catch (e) {
this.log('findOne(): invalid selector provided "' + selector + '":' + e, "error");
......@@ -462,8 +475,8 @@
* @param mixed value The field value to set
*/
this.setField = function setField(field, value) {
var fields, out;
value = value || "";
var logValue, fields, out;
value = logValue = (value || "");
if (field instanceof NodeList) {
fields = field;
field = fields[0];
......@@ -471,11 +484,15 @@
if (!field instanceof HTMLElement) {
this.log("Invalid field type; only HTMLElement and NodeList are supported", "error");
}
this.log('Set "' + field.getAttribute('name') + '" field value to ' + value, "debug");
if (this.options && this.options.safeLogs && field.getAttribute('type') === "password") {
// obfuscate password value
logValue = new Array(value.length + 1).join("*");
}
this.log('Set "' + field.getAttribute('name') + '" field value to ' + logValue, "debug");
try {
field.focus();
} catch (e) {
__utils__.log("Unable to focus() input field " + field.getAttribute('name') + ": " + e, "warning");
this.log("Unable to focus() input field " + field.getAttribute('name') + ": " + e, "warning");
}
var nodeName = field.nodeName.toLowerCase();
switch (nodeName) {
......@@ -544,7 +561,7 @@
try {
field.blur();
} catch (err) {
__utils__.log("Unable to blur() input field " + field.getAttribute('name') + ": " + err, "warning");
this.log("Unable to blur() input field " + field.getAttribute('name') + ": " + err, "warning");
}
return out;
};
......@@ -570,7 +587,4 @@
}
};
};
// silly "hack" to force having an instance available
exports.__utils__ = new exports.ClientUtils();
})(typeof exports === "object" ? exports : window);
......
......@@ -28,10 +28,13 @@
*
*/
/*global exports console require*/
var fs = require('fs');
var utils = require('utils');
exports.create = function create(type) {
"use strict";
if (!type) {
return;
}
......@@ -48,6 +51,7 @@ exports.create = function create(type) {
* (c) Fabien Potencier, Symfony project, MIT license
*/
var Colorizer = function Colorizer() {
"use strict";
var options = { bold: 1, underscore: 4, blink: 5, reverse: 7, conceal: 8 };
var foreground = { black: 30, red: 31, green: 32, yellow: 33, blue: 34, magenta: 35, cyan: 36, white: 37 };
var background = { black: 40, red: 41, green: 42, yellow: 43, blue: 44, magenta: 45, cyan: 46, white: 47 };
......@@ -104,7 +108,7 @@ var Colorizer = function Colorizer() {
if (typeof pad === "number" && text.length < pad) {
text += new Array(pad - text.length + 1).join(' ');
}
return "\033[" + codes.join(';') + 'm' + text + "\033[0m";
return "\u001b[" + codes.join(';') + 'm' + text + "\u001b[0m";
};
};
exports.Colorizer = Colorizer;
......@@ -114,6 +118,7 @@ exports.Colorizer = Colorizer;
*
*/
var Dummy = function Dummy() {
"use strict";
this.colorize = function colorize(text, styleName, pad) {
return text;
};
......
......@@ -28,9 +28,12 @@
*
*/
/*global CasperError console encodeURIComponent escape exports require*/
var utils = require('utils');
exports.create = function create(fn) {
"use strict";
return new FunctionArgsInjector(fn);
};
......@@ -40,6 +43,7 @@ exports.create = function create(fn) {
* FIXME: use new Function() instead of eval()
*/
var FunctionArgsInjector = function FunctionArgsInjector(fn) {
"use strict";
if (!utils.isFunction(fn)) {
throw new CasperError("FunctionArgsInjector() can only process functions");
}
......
......@@ -28,13 +28,17 @@
*
*/
/*global CasperError exports require*/
var utils = require('utils');
exports.create = function create(casper) {
"use strict";
return new Mouse(casper);
};
var Mouse = function Mouse(casper) {
"use strict";
if (!utils.isCasperObject(casper)) {
throw new CasperError('Mouse() needs a Casper instance');
}
......
......@@ -28,7 +28,7 @@
*
*/
/*global exports:false, phantom:false, require:false, CasperError:false*/
/*global CasperError exports phantom require*/
var fs = require('fs');
var events = require('events');
......@@ -55,6 +55,7 @@ var Tester = function Tester(casper, options) {
this.currentTestFile = null;
this.exporter = require('xunit').create();
this.includes = [];
this.running = false;
this.suites = [];
this.options = utils.mergeObjects({
......@@ -287,7 +288,7 @@ var Tester = function Tester(casper, options) {
standard: "Subject matches the provided pattern",
values: {
subject: subject,
pattern: pattern
pattern: pattern.toString()
}
});
};
......@@ -361,7 +362,7 @@ var Tester = function Tester(casper, options) {
*/
this.assertTextExists = this.assertTextExist = function assertTextExists(text, message) {
var textFound = (casper.evaluate(function _evaluate() {
return document.body.innerText;
return document.body.textContent || document.body.innerText;
}).indexOf(text) !== -1);
return this.assert(textFound, message, {
type: "assertTextExists",
......@@ -392,6 +393,25 @@ var Tester = function Tester(casper, options) {
};
/**
* 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 the provided subject is of the given type.
*
* @param mixed subject The value to test
......@@ -427,7 +447,7 @@ var Tester = function Tester(casper, options) {
standard: "Current url matches the provided pattern",
values: {
currentUrl: currentUrl,
pattern: pattern
pattern: pattern.toString()
}
});
};
......@@ -695,6 +715,9 @@ var Tester = function Tester(casper, options) {
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.includes.forEach(function(include) {
phantom.injectJs(include);
});
this.exec(testFile);
};
......
......@@ -28,329 +28,343 @@
*
*/
/**
* 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;
}
}
exports.betterTypeOf = betterTypeOf;
/*global CasperError console exports phantom require*/
/**
* 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));
}
exports.dump = dump;
(function(exports) {
"use strict";
/**
* 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 '';
/**
* 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;
}
}
}
exports.fileExt = fileExt;
exports.betterTypeOf = betterTypeOf;
/**
* 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(' ');
/**
* 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));
}
return text;
}
exports.fillBlanks = fillBlanks;
exports.dump = dump;
/**
* Formats a string with passed parameters. Ported from nodejs `util.format()`.
*
* @return String
*/
function format(f) {
var i;
if (typeof f !== 'string') {
var objects = [];
for (i = 0; i < arguments.length; i++) {
objects.push(inspect(arguments[i]));
/**
* 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 '';
}
return objects.join(' ');
}
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 += ' ' + inspect(x);
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;
}
return str;
}
exports.format = format;
exports.fillBlanks = fillBlanks;
/**
* 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
/**
* 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]';
}
}
});
}
exports.inherits = inherits;
return str;
}
exports.format = format;
/**
* 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) {
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) {
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) {
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) {
return isType(value, "cliprect") || (
isObject(value) &&
isNumber(value.top) && isNumber(value.left) &&
isNumber(value.width) && isNumber(value.height)
);
}
exports.isClipRect = isClipRect;
/**
* 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 value is a javascript Function
*
* @param mixed value
* @return Boolean
*/
function isFunction(value) {
return isType(value, "function");
}
exports.isFunction = isFunction;
/**
* 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) {
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) {
return isType(value, "null");
}
exports.isNull = isNull;
/**
* Checks if value is a javascript Object
*
* @param mixed value
* @return Boolean
*/
function isObject(value) {
return isType(value, "object");
}
exports.isObject = isObject;
/**
* Checks if value is a javascript Number
*
* @param mixed value
* @return Boolean
*/
function isNumber(value) {
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) {
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");
/**
* Checks if value is a javascript String
*
* @param mixed value
* @return Boolean
*/
function isString(value) {
return isType(value, "string");
}
return betterTypeOf(what).toLowerCase() === typeName.toLowerCase();
}
exports.isType = isType;
exports.isString = isString;
/**
* Checks if the provided value is undefined
*
* @return Boolean
*/
function isUndefined(value) {
return isType(value, "undefined");
}
exports.isUndefined = isUndefined;
/**
* 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 the provided var is a WebPage instance
*
* @param mixed what
* @return Boolean
*/
function isWebPage(what) {
if (!what || !isObject(what)) {
return false;
/**
* Checks if the provided value is undefined
*
* @return Boolean
*/
function isUndefined(value) {
return isType(value, "undefined");
}
if (phantom.version.major <= 1 && phantom.version.minor < 3 && isFunction(require)) {
return what instanceof WebPage;
} else {
return what.toString().indexOf('WebPage(') === 0;
exports.isUndefined = isUndefined;
/**
* 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;
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];
/**
* 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];
}
} catch(e) {
origin[p] = add[p];
}
return origin;
}
return origin;
}
exports.mergeObjects = mergeObjects;
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) {
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;
}
return _node;
}
exports.node = node;
exports.node = node;
/**
* Serializes a value using JSON.
*
* @param Mixed value
* @return String
*/
function serialize(value) {
if (isArray(value)) {
value = value.map(function _map(prop) {
return isFunction(prop) ? prop.toString().replace(/\s{2,}/, '') : prop;
});
/**
* Serializes a value using JSON.
*
* @param Mixed value
* @return String
*/
function serialize(value) {
if (isArray(value)) {
value = value.map(function _map(prop) {
return isFunction(prop) ? prop.toString().replace(/\s{2,}/, '') : prop;
});
}
return JSON.stringify(value, null, 4);
}
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;
}
}
return r;
}
return JSON.stringify(value, null, 4);
}
exports.serialize = serialize;
exports.unique = unique;
})(exports);
......
This diff could not be displayed because it is too large.
......@@ -27,13 +27,15 @@
* DEALINGS IN THE SOFTWARE.
*
*/
/*global exports, phantom, require, CasperError*/
/*global CasperError console exports phantom require*/
var utils = require('utils');
var fs = require('fs');
exports.create = function create() {
"use strict";
return new this.XUnitExporter();
return new XUnitExporter();
};
/**
......
casper = require("casper").create
verbose: true
# If we don't set a limit, it could go on forever
upTo = ~~casper.cli.get(0) || 10
###
Fetch all <a> elements from the page and return
the ones which contains a href starting with 'http://'
###
searchLinks = ->
filter = Array::filter
map = Array::map
map.call filter.call(document.querySelectorAll("a"), (a) ->
(/^http:\/\/.*/i).test a.getAttribute("href")
), (a) ->
a.getAttribute "href"
# The base links array
links = [
"http://google.com/"
......@@ -23,10 +8,10 @@ links = [
"http://bing.com/"
]
# Just opens the page and prints the title
start = (link) ->
@start link, ->
@echo "Page title: #{ @getTitle() }"
currentLink = 0;
# If we don't set a limit, it could go on forever
upTo = ~~casper.cli.get(0) || 10
###
Get the links, and add them to the links array
......@@ -38,12 +23,22 @@ addLinks = (link) ->
@echo "#{found.length} links found on #{link}"
links = links.concat found
casper.start()
casper.then ->
@echo "Starting"
###
Fetch all <a> elements from the page and return
the ones which contains a href starting with 'http://'
###
searchLinks = ->
filter = Array::filter
map = Array::map
map.call filter.call(document.querySelectorAll("a"), (a) ->
(/^http:\/\/.*/i).test a.getAttribute("href")
), (a) ->
a.getAttribute "href"
currentLink = 0;
# Just opens the page and prints the title
start = (link) ->
@start link, ->
@echo "Page title: #{ @getTitle() }"
# As long as it has a next link, and is under the maximum limit, will keep running
check = ->
......@@ -57,4 +52,9 @@ check = ->
@echo "All done."
@exit()
casper.start()
casper.then ->
@echo "Starting"
casper.run check
......
......@@ -2,24 +2,6 @@ var casper = require("casper").create({
verbose: true
});
// If we don't set a limit, it could go on forever
var upTo = ~~casper.cli.get(0) || 10;
/*
Fetch all <a> elements from the page and return
the ones which contains a href starting with 'http://'
*/
var searchLinks = function() {
var filter, map;
filter = Array.prototype.filter;
map = Array.prototype.map;
return map.call(filter.call(document.querySelectorAll("a"), function(a) {
return /^http:\/\/.*/i.test(a.getAttribute("href"));
}), function(a) {
return a.getAttribute("href");
});
};
// The base links array
var links = [
"http://google.com/",
......@@ -27,30 +9,40 @@ var links = [
"http://bing.com/"
];
// Just opens the page and prints the title
var start = function(link) {
this.start(link, function() {
this.echo('Page title: ' + this.getTitle());
});
};
// If we don't set a limit, it could go on forever
var upTo = ~~casper.cli.get(0) || 10;
var currentLink = 0;
// Get the links, and add them to the links array
// (It could be done all in one step, but it is intentionally splitted)
var addLinks = function(link) {
function addLinks(link) {
this.then(function() {
var found = this.evaluate(searchLinks);
this.echo(found.length + " links found on " + link);
links = links.concat(found);
});
};
}
casper.start();
casper.then(function() {
this.echo("Starting");
});
// Fetch all <a> elements from the page and return
// the ones which contains a href starting with 'http://'
function searchLinks() {
var filter, map;
filter = Array.prototype.filter;
map = Array.prototype.map;
return map.call(filter.call(document.querySelectorAll("a"), function(a) {
return (/^http:\/\/.*/i).test(a.getAttribute("href"));
}), function(a) {
return a.getAttribute("href");
});
}
var currentLink = 0;
// Just opens the page and prints the title
function start(link) {
this.start(link, function() {
this.echo('Page title: ' + this.getTitle());
});
}
// As long as it has a next link, and is under the maximum limit, will keep running
function check() {
......@@ -64,6 +56,10 @@ function check() {
this.echo("All done.");
this.exit();
}
};
}
casper.start().then(function() {
this.echo("Starting");
});
casper.run(check);
......
......@@ -6,24 +6,47 @@ if (!phantom.casperLoaded) {
var fs = require('fs');
var utils = require('utils');
var f = utils.format;
var includes = [];
var tests = [];
var casper = require('casper').create({
exitOnError: false
});
// Options from cli
// local utils
function checkIncludeFile(include) {
var absInclude = fs.absolute(include.trim());
if (!fs.exists(absInclude)) {
casper.warn("%s file not found, can't be included", absInclude);
return;
}
if (!utils.isJsFile(absInclude)) {
casper.warn("%s is not a supported file type, can't be included", absInclude);
return;
}
if (fs.isDirectory(absInclude)) {
casper.warn("%s is a directory, can't be included", absInclude);
return;
}
if (tests.indexOf(include) > -1 || tests.indexOf(absInclude) > -1) {
casper.warn("%s is a test file, can't be included", absInclude);
return;
}
return absInclude;
}
// parse some options from cli
casper.options.verbose = casper.cli.get('direct') || false;
casper.options.logLevel = casper.cli.get('log-level') || "error";
// Overriding Casper.open to prefix all test urls
// overriding Casper.open to prefix all test urls
casper.setFilter('open.location', function(location) {
if (!/^http/.test(location)) {
return f('file://%s/%s', phantom.casperPath, location);
return f('file://%s/%s', fs.workingDirectory, location);
}
return location;
});
var tests = [];
// test paths are passed as args
if (casper.cli.args.length) {
tests = casper.cli.args.filter(function(path) {
return fs.isFile(path) || fs.isDirectory(path);
......@@ -33,8 +56,21 @@ if (casper.cli.args.length) {
casper.exit(1);
}
// includes handling
if (casper.cli.has('includes')) {
includes = casper.cli.get('includes').split(',').map(function(include) {
// we can't use filter() directly because of abspath transformation
return checkIncludeFile(include);
}).filter(function(include) {
return utils.isString(include);
});
casper.test.includes = utils.unique(includes);
}
// test suites completion listener
casper.test.on('tests.complete', function() {
this.renderResults(true, undefined, casper.cli.get('xunit') || undefined);
});
// run all the suites
casper.test.runSuites.apply(casper.test, tests);
......
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>CasperJS test form</title>
</head>
<body>
<form action="result.html" enctype="multipart/form-data">
<input type="text" name="email" placeholder="email" />
<input type="text" name="email" placeholder="email">
<input type="password" name="password" placeholder="password">
<textarea name="content"></textarea>
<select name="topic">
<option>foo</option>
<option value="bar">baz</option>
</select>
<input type="checkbox" name="check" />
<input type="radio" name="choice" value="yes"/>
<input type="radio" name="choice" value="no"/>
<input type="file" name="file"/>
<input type="checkbox" name="checklist[]" value="1" />
<input type="checkbox" name="checklist[]" value="2" />
<input type="checkbox" name="checklist[]" value="3" />
<input type="submit" name="submit" value="submit" />
<input type="checkbox" name="check">
<input type="radio" name="choice" value="yes">
<input type="radio" name="choice" value="no">
<input type="file" name="file">
<input type="checkbox" name="checklist[]" value="1">
<input type="checkbox" name="checklist[]" value="2">
<input type="checkbox" name="checklist[]" value="3">
<input type="submit" name="submit" value="submit">
</form>
</body>
</html>
......
<!DOCTYPE html>
<html>
<head>
<title>Multiple forms test</title>
</head>
<body>
<form name="f1">
<input type="hidden" name="f" value="f1">
<input type="text" name="yo">
</form>
<form name="f2">
<input type="hidden" name="f" value="f2">
<input type="text" name="yo">
</form>
</body>
</html>
function testUA(ua, match) {
casper.test.assertMatch(
ua, match, 'Default user agent matches ' + match
);
}
function fetchUA(request) {
testUA(request.headers.filter(function(header) {
return header.name === "User-Agent";
}).pop().value, /plop/);
}
testUA(casper.options.pageSettings.userAgent, /CasperJS/);
casper.start();
casper.userAgent('plop').on('resource.requested', fetchUA);
casper.thenOpen('tests/site/index.html');
casper.run(function() {
this.removeListener('resource.requested', fetchUA);
this.test.done();
});
var fs = require('fs');
var clientutils = require('clientutils').create();
var testCases = {
'an empty string': '',
'a word': 'plop',
'a null char': 'a\u0000',
'an utf8 string': 'ÀÁÃÄÅÇÈÉÊËÌÍÎÏÒÓÔÕÖÙÚÛÜÝàáâãäåçèéêëìíîïðòóôõöùúûüýÿ',
'song lyrics': ("Voilà l'été, j'aperçois le soleil\n" +
"Les nuages filent et le ciel s'éclaircit\n" +
"Et dans ma tête qui bourdonnent?\n" +
"Les abeilles!"),
'a file contents': fs.read(phantom.casperPath + '/tests/site/alert.html')
};
casper.test.comment('ClientUtils.encode()');
for (var what in testCases) {
var source = testCases[what];
var encoded = clientutils.encode(source);
casper.test.assertEquals(clientutils.decode(encoded), source, 'ClientUtils can encode and decode ' + what);
}
casper.test.done();
......@@ -2,6 +2,7 @@ casper.start('tests/site/form.html', function() {
this.test.comment('Casper.fill()');
this.fill('form[action="result.html"]', {
email: 'chuck@norris.com',
password: 'chuck',
content: 'Am watching thou',
check: true,
choice: 'no',
......@@ -13,6 +14,9 @@ casper.start('tests/site/form.html', function() {
return document.querySelector('input[name="email"]').value;
}, 'chuck@norris.com', 'Casper.fill() can fill an input[type=text] form field');
this.test.assertEvalEquals(function() {
return document.querySelector('input[name="password"]').value;
}, 'chuck', 'Casper.fill() can fill an input[type=password] form field');
this.test.assertEvalEquals(function() {
return document.querySelector('textarea[name="content"]').value;
}, 'Am watching thou', 'Casper.fill() can fill a textarea form field');
this.test.assertEvalEquals(function() {
......@@ -41,12 +45,25 @@ casper.start('tests/site/form.html', function() {
casper.then(function() {
this.test.comment('Form submitted');
this.test.assertUrlMatch(/email=chuck@norris.com/, 'Casper.fill() input[type=email] field was submitted');
this.test.assertUrlMatch(/password=chuck/, 'Casper.fill() input[type=password] field was submitted');
this.test.assertUrlMatch(/content=Am\+watching\+thou/, 'Casper.fill() textarea field was submitted');
this.test.assertUrlMatch(/check=on/, 'Casper.fill() input[type=checkbox] field was submitted');
this.test.assertUrlMatch(/choice=no/, 'Casper.fill() input[type=radio] field was submitted');
this.test.assertUrlMatch(/topic=bar/, 'Casper.fill() select field was submitted');
});
// multiple forms
casper.thenOpen('tests/site/multiple-forms.html', function() {
this.test.comment('Multiple forms');
this.fill('form[name="f2"]', {
yo: "ok"
}, true);
});
casper.then(function() {
this.test.assertUrlMatch(/\?f=f2&yo=ok$/, 'Casper.fill() handles multiple forms');
}),
casper.run(function() {
this.test.done();
});
......
var fs = require('fs');
var x = require('casper').selectXPath;
function fakeDocument(html) {
window.document.body.innerHTML = html;
}
(function(casper) {
casper.test.comment('ClientUtils.encode()');
var clientutils = require('clientutils').create();
var testCases = {
'an empty string': '',
'a word': 'plop',
'a null char': 'a\u0000',
'an utf8 string': 'ÀÁÃÄÅÇÈÉÊËÌÍÎÏÒÓÔÕÖÙÚÛÜÝàáâãäåçèéêëìíîïðòóôõöùúûüýÿ',
'song lyrics': ("Voilà l'été, j'aperçois le soleil\n" +
"Les nuages filent et le ciel s'éclaircit\n" +
"Et dans ma tête qui bourdonnent?\n" +
"Les abeilles!"),
'a file contents': fs.read(phantom.casperPath + '/tests/site/alert.html')
};
for (var what in testCases) {
var source = testCases[what];
var encoded = clientutils.encode(source);
casper.test.assertEquals(clientutils.decode(encoded), source, 'ClientUtils.encode() encodes and decodes ' + what);
}
})(casper);
(function(casper) {
casper.test.comment('ClientUtils.exists()');
var clientutils = require('clientutils').create();
fakeDocument('<ul class="foo"><li>bar</li><li>baz</li></ul>');
casper.test.assert(clientutils.exists('ul'), 'ClientUtils.exists() checks that an element exist');
casper.test.assertNot(clientutils.exists('ol'), 'ClientUtils.exists() checks that an element exist');
casper.test.assert(clientutils.exists('ul.foo li'), 'ClientUtils.exists() checks that an element exist');
// xpath
casper.test.assert(clientutils.exists(x('//ul')), 'ClientUtils.exists() checks that an element exist using XPath');
casper.test.assertNot(clientutils.exists(x('//ol')), 'ClientUtils.exists() checks that an element exist using XPath');
fakeDocument(null);
})(casper);
(function(casper) {
casper.test.comment('ClientUtils.findAll()');
var clientutils = require('clientutils').create();
fakeDocument('<ul class="foo"><li>bar</li><li>baz</li></ul>');
casper.test.assertType(clientutils.findAll('li'), 'nodelist', 'ClientUtils.findAll() can find matching DOM elements');
casper.test.assertEquals(clientutils.findAll('li').length, 2, 'ClientUtils.findAll() can find matching DOM elements');
casper.test.assertType(clientutils.findAll('ol'), 'nodelist', 'ClientUtils.findAll() can find matching DOM elements');
casper.test.assertEquals(clientutils.findAll('ol').length, 0, 'ClientUtils.findAll() can find matching DOM elements');
// scoped
var scope = clientutils.findOne('ul');
casper.test.assertType(clientutils.findAll('li', scope), 'nodelist', 'ClientUtils.findAll() can find matching DOM elements within a given scope');
fakeDocument(null);
})(casper);
(function(casper) {
casper.test.comment('ClientUtils.findOne()');
var clientutils = require('clientutils').create();
fakeDocument('<ul class="foo"><li>bar</li><li>baz</li></ul>');
casper.test.assertType(clientutils.findOne('ul'), 'htmlulistelement', 'ClientUtils.findOne() can find a matching DOM element');
casper.test.assertNot(clientutils.findOne('ol'), 'ClientUtils.findOne() can find a matching DOM element');
// scoped
var scope = clientutils.findOne('ul');
casper.test.assertType(clientutils.findAll('li', scope), 'nodelist', 'ClientUtils.findAll() can find matching DOM elements within a given scope');
casper.test.assertEquals(clientutils.findAll('li', scope).length, 2, 'ClientUtils.findAll() can find matching DOM elements within a given scope');
fakeDocument(null);
})(casper);
(function(casper) {
casper.test.comment('ClientUtils.processSelector()');
var clientutils = require('clientutils').create();
// CSS3 selector
var cssSelector = clientutils.processSelector('html body > ul.foo li');
casper.test.assertType(cssSelector, 'object', 'ClientUtils.processSelector() can process a CSS3 selector');
casper.test.assertEquals(cssSelector.type, 'css', 'ClientUtils.processSelector() can process a CSS3 selector');
casper.test.assertEquals(cssSelector.path, 'html body > ul.foo li', 'ClientUtils.processSelector() can process a CSS3 selector');
// XPath selector
var xpathSelector = clientutils.processSelector(x('//li[text()="blah"]'));
casper.test.assertType(xpathSelector, 'object', 'ClientUtils.processSelector() can process a XPath selector');
casper.test.assertEquals(xpathSelector.type, 'xpath', 'ClientUtils.processSelector() can process a XPath selector');
casper.test.assertEquals(xpathSelector.path, '//li[text()="blah"]', 'ClientUtils.processSelector() can process a XPath selector');
})(casper);
casper.test.done();
......@@ -96,6 +96,9 @@ casper.then(function() {
t.comment('Tester.assertTitle()');
t.assertTitle('CasperJS test index', 'Tester.assertTitle() works as expected');
t.comment('Tester.assertTitleMatch()');
t.assertTitleMatch(/test index/, 'Tester.assertTitleMatch() works as expected');
t.comment('Tester.assertType()');
t.assertType("plop", "string", "Tester.assertType() works as expected");
......
......@@ -29,6 +29,13 @@ t.comment('fillBlanks()');
}
})();
t.comment('isArray()');
(function() {
t.assertEquals(utils.isArray([]), true, 'isArray() checks for an Array');
t.assertEquals(utils.isArray({}), false, 'isArray() checks for an Array');
t.assertEquals(utils.isArray("foo"), false, 'isArray() checks for an Array');
})();
t.comment('isClipRect()');
(function() {
testCases = [
......@@ -44,6 +51,26 @@ t.comment('isClipRect()');
});
})();
t.comment('isObject()');
(function() {
t.assertEquals(utils.isObject({}), true, 'isObject() checks for an Object');
t.assertEquals(utils.isObject([]), true, 'isObject() checks for an Object');
t.assertEquals(utils.isObject(1), false, 'isObject() checks for an Object');
t.assertEquals(utils.isObject("1"), false, 'isObject() checks for an Object');
t.assertEquals(utils.isObject(function(){}), false, 'isObject() checks for an Object');
t.assertEquals(utils.isObject(new Function('return {};')()), true, 'isObject() checks for an Object');
t.assertEquals(utils.isObject(require('webpage').create()), true, 'isObject() checks for an Object');
t.assertEquals(utils.isObject(null), false, 'isObject() checks for an Object');
})();
t.comment('isWebPage()');
(function() {
var pageModule = require('webpage');
t.assertEquals(utils.isWebPage(pageModule), false, 'isWebPage() checks for a WebPage instance');
t.assertEquals(utils.isWebPage(pageModule.create()), true, 'isWebPage() checks for a WebPage instance');
t.assertEquals(utils.isWebPage(null), false, 'isWebPage() checks for a WebPage instance');
})();
t.comment('isJsFile()');
(function() {
testCases = {
......@@ -90,4 +117,29 @@ t.comment('mergeObjects()');
});
})();
t.comment('unique()');
(function() {
testCases = [
{
input: [1,2,3],
output: [1,2,3]
},
{
input: [1,2,3,2,1],
output: [1,2,3]
},
{
input: ["foo", "bar", "foo"],
output: ["foo", "bar"]
},
{
input: [],
output: []
}
];
testCases.forEach(function(testCase) {
t.assertEquals(utils.unique(testCase.input), testCase.output, 'unique() computes unique values of an array');
});
})();
t.done();
......