460 lines
17 KiB
JavaScript
460 lines
17 KiB
JavaScript
|
/*!
|
||
|
* Casper is a navigation utility for PhantomJS.
|
||
|
*
|
||
|
* Documentation: http://casperjs.org/
|
||
|
* Repository: http://github.com/casperjs/casperjs
|
||
|
*
|
||
|
* Copyright (c) 2011-2012 Nicolas Perriault
|
||
|
*
|
||
|
* Part of source code is Copyright Joyent, Inc. and other Node contributors.
|
||
|
*
|
||
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
||
|
* copy of this software and associated documentation files (the "Software"),
|
||
|
* to deal in the Software without restriction, including without limitation
|
||
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||
|
* and/or sell copies of the Software, and to permit persons to whom the
|
||
|
* Software is furnished to do so, subject to the following conditions:
|
||
|
*
|
||
|
* The above copyright notice and this permission notice shall be included
|
||
|
* in all copies or substantial portions of the Software.
|
||
|
*
|
||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||
|
* DEALINGS IN THE SOFTWARE.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
/*eslint max-statements:0, complexity:0*/
|
||
|
|
||
|
// node check
|
||
|
if ('process' in this && this.process.title === "node") {
|
||
|
console.error('CasperJS cannot be executed within a nodejs environment');
|
||
|
this.process.exit(1);
|
||
|
}
|
||
|
|
||
|
// phantom check
|
||
|
if (!('phantom' in this)) {
|
||
|
console.error('CasperJS needs to be executed in a PhantomJS environment http://phantomjs.org/');
|
||
|
}
|
||
|
|
||
|
// Common polyfills
|
||
|
void function() {
|
||
|
|
||
|
// cujos bind shim instead of MDN shim, see #1396
|
||
|
var isFunction = function(o) {
|
||
|
return 'function' === typeof o;
|
||
|
};
|
||
|
var bind;
|
||
|
var slice = [].slice;
|
||
|
var proto = Function.prototype;
|
||
|
var featureMap = {
|
||
|
'function-bind': 'bind'
|
||
|
};
|
||
|
function has(feature) {
|
||
|
var prop = featureMap[feature];
|
||
|
return isFunction(proto[prop]);
|
||
|
}
|
||
|
// check for missing features
|
||
|
if (!has('function-bind')) {
|
||
|
// adapted from Mozilla Developer Network example at
|
||
|
// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
|
||
|
bind = function bind(obj) {
|
||
|
var args = slice.call(arguments, 1),
|
||
|
self = this,
|
||
|
nop = function() {
|
||
|
},
|
||
|
bound = function() {
|
||
|
return self.apply(this instanceof nop ? this : (obj || {}), args.concat(slice.call(arguments)));
|
||
|
};
|
||
|
nop.prototype = this.prototype || {}; // Firefox cries sometimes if prototype is undefined
|
||
|
bound.prototype = new nop();
|
||
|
return bound;
|
||
|
};
|
||
|
proto.bind = bind;
|
||
|
}
|
||
|
|
||
|
}();
|
||
|
|
||
|
// Custom base error
|
||
|
var CasperError = function CasperError(msg) {
|
||
|
"use strict";
|
||
|
Error.call(this);
|
||
|
this.message = msg;
|
||
|
this.name = 'CasperError';
|
||
|
};
|
||
|
CasperError.prototype = Object.getPrototypeOf(new Error());
|
||
|
|
||
|
// casperjs env initialization
|
||
|
(function(global, phantom, system){
|
||
|
"use strict";
|
||
|
// phantom args
|
||
|
var phantomArgs = system.args.slice(1);
|
||
|
|
||
|
if ("slimer" in global) {
|
||
|
phantom.casperEngine = "slimerjs";
|
||
|
} else {
|
||
|
phantom.casperEngine = "phantomjs";
|
||
|
}
|
||
|
|
||
|
if (phantom.casperLoaded) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
function __exit(statusCode){
|
||
|
setTimeout(function() { phantom.exit(statusCode); }, 0);
|
||
|
}
|
||
|
|
||
|
function __die(message) {
|
||
|
if (message) {
|
||
|
console.error(message);
|
||
|
}
|
||
|
__exit(1);
|
||
|
}
|
||
|
|
||
|
function __terminate(message) {
|
||
|
if (message) {
|
||
|
console.log(message);
|
||
|
}
|
||
|
__exit();
|
||
|
}
|
||
|
|
||
|
(function (version) {
|
||
|
// required version check
|
||
|
if (phantom.casperEngine === 'phantomjs') {
|
||
|
if (version.major === 1) {
|
||
|
if (version.minor < 9) {
|
||
|
return __die('CasperJS needs at least PhantomJS v1.9 or later.');
|
||
|
}
|
||
|
if (version.minor === 9 && version.patch < 1) {
|
||
|
return __die('CasperJS needs at least PhantomJS v1.9.1 or later.');
|
||
|
}
|
||
|
} else if (version.major === 2) {
|
||
|
// No requirements yet known
|
||
|
} else {
|
||
|
return __die('CasperJS needs PhantomJS v1.9.x or v2.x');
|
||
|
}
|
||
|
}
|
||
|
})(phantom.version);
|
||
|
|
||
|
// Hooks in default phantomjs error handler
|
||
|
phantom.onError = function onPhantomError(msg, trace) {
|
||
|
phantom.defaultErrorHandler.apply(phantom, arguments);
|
||
|
// print a hint when a possible casperjs command misuse is detected
|
||
|
if (msg.indexOf("ReferenceError: Can't find variable: casper") === 0) {
|
||
|
console.error('Hint: you may want to use the `casperjs test` command.');
|
||
|
}
|
||
|
// exits on syntax error
|
||
|
if (msg.indexOf('SyntaxError: ') === 0) {
|
||
|
__die();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// Patching fs
|
||
|
var fs = (function patchFs(fs) {
|
||
|
if (!fs.hasOwnProperty('basename')) {
|
||
|
fs.basename = function basename(path) {
|
||
|
return path.replace(/.*\//, '');
|
||
|
};
|
||
|
}
|
||
|
if (!fs.hasOwnProperty('dirname')) {
|
||
|
fs.dirname = function dirname(path) {
|
||
|
if (!path) return undefined;
|
||
|
return path.toString().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('joinPath')) {
|
||
|
fs.pathJoin = fs.joinPath;
|
||
|
} else if (!fs.hasOwnProperty('pathJoin')) {
|
||
|
fs.pathJoin = function pathJoin() {
|
||
|
return Array.prototype.filter.call(arguments,function(elm){
|
||
|
return typeof elm !== "undefined" && elm !== null;
|
||
|
}).join('/');
|
||
|
};
|
||
|
}
|
||
|
|
||
|
if (phantom.casperEngine === 'slimerjs' && slimer.version.major === 0 && slimer.version.minor <= 9){
|
||
|
fs.size = function (filename){
|
||
|
var t = fs.read(filename, "rt");
|
||
|
return t.length;
|
||
|
};
|
||
|
}
|
||
|
return fs;
|
||
|
})(require('fs'));
|
||
|
|
||
|
// CasperJS root path
|
||
|
if (!phantom.casperPath) {
|
||
|
try {
|
||
|
phantom.casperPath = phantomArgs.map(function _map(arg) {
|
||
|
var match = arg.match(/^--casper-path=(.*)/);
|
||
|
if (match) {
|
||
|
return fs.absolute(match[1]);
|
||
|
}
|
||
|
}).filter(function _filter(path) {
|
||
|
return fs.isDirectory(path);
|
||
|
}).pop();
|
||
|
} catch (e) {
|
||
|
return __die("Couldn't find nor compute phantom.casperPath, exiting.");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Prints CasperJS help.
|
||
|
*/
|
||
|
function printHelp() {
|
||
|
/* global slimer */
|
||
|
var engine = phantom.casperEngine === 'slimerjs' ? slimer : phantom;
|
||
|
var version = [engine.version.major, engine.version.minor, engine.version.patch].join('.');
|
||
|
return __terminate([
|
||
|
'CasperJS version ' + phantom.casperVersion.toString() +
|
||
|
' at ' + phantom.casperPath + ', using ' + phantom.casperEngine + ' version ' + version,
|
||
|
fs.read(fs.pathJoin(phantom.casperPath, 'bin', 'usage.txt'))
|
||
|
].join('\n'));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Patched require to allow loading of native casperjs modules.
|
||
|
* Every casperjs module have to first call this function in order to
|
||
|
* load a native casperjs module:
|
||
|
*
|
||
|
* var require = patchRequire(require);
|
||
|
* var utils = require('utils');
|
||
|
*
|
||
|
* Useless for SlimerJS
|
||
|
*/
|
||
|
function patchRequire(require) {
|
||
|
if (require.patched) {
|
||
|
return require;
|
||
|
}
|
||
|
function fromPackageJson(module, dir) {
|
||
|
var pkgPath, pkgContents, pkg;
|
||
|
pkgPath = fs.pathJoin(dir, module, 'package.json');
|
||
|
if (!fs.exists(pkgPath)) {
|
||
|
return;
|
||
|
}
|
||
|
pkgContents = fs.read(pkgPath);
|
||
|
if (!pkgContents) {
|
||
|
return;
|
||
|
}
|
||
|
try {
|
||
|
pkg = JSON.parse(pkgContents);
|
||
|
} catch (e) {
|
||
|
return;
|
||
|
}
|
||
|
if (typeof pkg === "object" && pkg.main) {
|
||
|
return fs.absolute(fs.pathJoin(dir, module, pkg.main));
|
||
|
}
|
||
|
}
|
||
|
function resolveFile(path, dir) {
|
||
|
var extensions = ['js', 'coffee', 'json'];
|
||
|
var basenames = [path, path + '/index'];
|
||
|
var paths = [];
|
||
|
var nodejsScript = fromPackageJson(path, dir);
|
||
|
if (nodejsScript) {
|
||
|
return nodejsScript;
|
||
|
}
|
||
|
basenames.forEach(function(basename) {
|
||
|
paths.push(fs.absolute(fs.pathJoin(dir, basename)));
|
||
|
extensions.forEach(function(extension) {
|
||
|
paths.push(fs.absolute(fs.pathJoin(dir, [basename, extension].join('.'))));
|
||
|
});
|
||
|
});
|
||
|
for (var i = 0; i < paths.length; i++) {
|
||
|
if (fs.isFile(paths[i])) {
|
||
|
return paths[i];
|
||
|
}
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
function getCurrentScriptRoot() {
|
||
|
if ((phantom.casperScriptBaseDir || "").indexOf(fs.workingDirectory) === 0) {
|
||
|
return phantom.casperScriptBaseDir;
|
||
|
}
|
||
|
return fs.absolute(fs.pathJoin(fs.workingDirectory, phantom.casperScriptBaseDir));
|
||
|
}
|
||
|
function casperBuiltinPath(path) {
|
||
|
return resolveFile(path, fs.pathJoin(phantom.casperPath, 'modules'));
|
||
|
}
|
||
|
function nodeModulePath(path) {
|
||
|
var resolved, prevBaseDir;
|
||
|
var baseDir = getCurrentScriptRoot();
|
||
|
do {
|
||
|
resolved = resolveFile(path, fs.pathJoin(baseDir, 'node_modules'));
|
||
|
prevBaseDir = baseDir;
|
||
|
baseDir = fs.absolute(fs.pathJoin(prevBaseDir, '..'));
|
||
|
} while (!resolved && baseDir !== '/' && prevBaseDir !== '/' && baseDir !== prevBaseDir);
|
||
|
return resolved;
|
||
|
}
|
||
|
function localModulePath(path) {
|
||
|
return resolveFile(path, phantom.casperScriptBaseDir || fs.workingDirectory);
|
||
|
}
|
||
|
var patchedRequire = function patchedRequire(path) {
|
||
|
try {
|
||
|
return require(casperBuiltinPath(path) ||
|
||
|
nodeModulePath(path) ||
|
||
|
localModulePath(path) ||
|
||
|
path);
|
||
|
} catch (e) {
|
||
|
throw new CasperError("Can't find module " + path);
|
||
|
}
|
||
|
};
|
||
|
patchedRequire.cache = require.cache;
|
||
|
patchedRequire.extensions = require.extensions;
|
||
|
patchedRequire.stubs = require.stubs;
|
||
|
patchedRequire.patched = true;
|
||
|
return patchedRequire;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Initializes the CasperJS Command Line Interface.
|
||
|
*/
|
||
|
function initCasperCli(casperArgs) {
|
||
|
/*eslint complexity:0*/
|
||
|
var baseTestsPath = fs.pathJoin(phantom.casperPath, 'tests');
|
||
|
|
||
|
function setScriptBaseDir(scriptName) {
|
||
|
var dir = fs.dirname(scriptName);
|
||
|
if (dir === scriptName) {
|
||
|
dir = '.';
|
||
|
}
|
||
|
phantom.casperScriptBaseDir = dir;
|
||
|
}
|
||
|
|
||
|
if (!!casperArgs.options.version) {
|
||
|
return __terminate(phantom.casperVersion.toString());
|
||
|
} else if (casperArgs.get(0) === "test") {
|
||
|
phantom.casperScript = fs.absolute(fs.pathJoin(baseTestsPath, 'run.js'));
|
||
|
phantom.casperTest = true;
|
||
|
casperArgs.drop("test");
|
||
|
setScriptBaseDir(casperArgs.get(0));
|
||
|
} else if (casperArgs.get(0) === "selftest") {
|
||
|
phantom.casperScript = fs.absolute(fs.pathJoin(baseTestsPath, 'run.js'));
|
||
|
phantom.casperSelfTest = phantom.casperTest = true;
|
||
|
casperArgs.options.includes = fs.pathJoin(baseTestsPath, 'selftest.js');
|
||
|
if (casperArgs.args.length <= 1) {
|
||
|
casperArgs.args.push(fs.pathJoin(baseTestsPath, 'suites'));
|
||
|
}
|
||
|
casperArgs.drop("selftest");
|
||
|
phantom.casperScriptBaseDir = fs.dirname(casperArgs.get(1) || fs.dirname(phantom.casperScript));
|
||
|
} else if (casperArgs.args.length === 0 || !!casperArgs.options.help) {
|
||
|
return printHelp();
|
||
|
}
|
||
|
|
||
|
if (!phantom.casperScript) {
|
||
|
phantom.casperScript = casperArgs.get(0);
|
||
|
}
|
||
|
|
||
|
if (phantom.casperScript !== "/dev/stdin" && !fs.isFile(phantom.casperScript)) {
|
||
|
return __die('Unable to open file: ' + phantom.casperScript);
|
||
|
}
|
||
|
|
||
|
if (!phantom.casperScriptBaseDir) {
|
||
|
setScriptBaseDir(phantom.casperScript);
|
||
|
}
|
||
|
|
||
|
// filter out the called script name from casper args
|
||
|
casperArgs.drop(phantom.casperScript);
|
||
|
}
|
||
|
|
||
|
// CasperJS version, extracted from package.json - see http://semver.org/
|
||
|
phantom.casperVersion = (function getCasperVersion(path) {
|
||
|
var parts, patchPart, pkg, pkgFile;
|
||
|
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.length < 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;
|
||
|
}
|
||
|
};
|
||
|
})(phantom.casperPath);
|
||
|
|
||
|
// phantomjs2 has paths in require, but needs patchRequire anyway
|
||
|
if (!("paths" in global.require) ||
|
||
|
('phantomjs' === phantom.casperEngine && 1 < phantom.version.major)
|
||
|
) {
|
||
|
global.__require = require;
|
||
|
global.patchRequire = patchRequire; // must be called in every casperjs module as of 1.1
|
||
|
global.require = patchRequire(global.require);
|
||
|
} else {
|
||
|
// declare a dummy patchRequire function
|
||
|
global.patchRequire = function(req) {return req;};
|
||
|
require.paths.push(fs.pathJoin(phantom.casperPath, 'modules'));
|
||
|
require.paths.push(fs.workingDirectory);
|
||
|
}
|
||
|
|
||
|
if (phantom.casperEngine === 'slimerjs') {
|
||
|
require.globals.patchRequire = global.patchRequire;
|
||
|
require.globals.CasperError = CasperError;
|
||
|
}
|
||
|
|
||
|
// casper cli args
|
||
|
phantom.casperArgs = require('cli').parse(phantomArgs);
|
||
|
|
||
|
if (true === phantom.casperArgs.get('cli')) {
|
||
|
initCasperCli(phantom.casperArgs);
|
||
|
}
|
||
|
|
||
|
if ("paths" in global.require) {
|
||
|
if ((phantom.casperScriptBaseDir || "").indexOf(fs.workingDirectory) === 0) {
|
||
|
require.paths.push(phantom.casperScriptBaseDir);
|
||
|
} else {
|
||
|
require.paths.push(fs.pathJoin(fs.workingDirectory, phantom.casperScriptBaseDir));
|
||
|
}
|
||
|
(function nodeModulePath(path) {
|
||
|
var resolved = false, prevBaseDir;
|
||
|
var baseDir = path;
|
||
|
do {
|
||
|
path = fs.pathJoin(baseDir, 'node_modules');
|
||
|
resolved = fs.exists(path) && fs.isDirectory(path);
|
||
|
prevBaseDir = baseDir;
|
||
|
baseDir = fs.absolute(fs.pathJoin(prevBaseDir, '..'));
|
||
|
} while (!resolved && baseDir !== '/' && prevBaseDir !== '/' && baseDir !== prevBaseDir);
|
||
|
if (!resolved) return;
|
||
|
require.paths.push(fs.pathJoin(prevBaseDir, 'node_modules'));
|
||
|
})(fs.pathJoin(fs.workingDirectory, phantom.casperScriptBaseDir));
|
||
|
}
|
||
|
|
||
|
// casper loading status flag
|
||
|
phantom.casperLoaded = true;
|
||
|
if (phantom.version.major === 2
|
||
|
&& phantom.casperScript
|
||
|
&& phantom.casperScript.split('.').pop() === 'coffee'
|
||
|
) {
|
||
|
return __terminate('CoffeeScript is not supported by PhantomJS > 2.');
|
||
|
}
|
||
|
|
||
|
// passed casperjs script execution
|
||
|
if (phantom.casperScript && !phantom.injectJs(phantom.casperScript)) {
|
||
|
return __die('Unable to load script ' + phantom.casperScript + '; check file syntax');
|
||
|
}
|
||
|
})(this, phantom, require('system'));
|