-
Notifications
You must be signed in to change notification settings - Fork 106
Introduction
SpookyJS makes it possible to drive CasperJS suites from Node.js. At a high level, Spooky accomplishes this by spawning Casper as a child process and controlling it via RPC.
Specifically, each Spooky instance spawns a child Casper process that runs a bootstrap script. The bootstrap script sets up a JSON-RPC server that listens for commands from the parent Spooky instance over a transport (either HTTP or stdio). The script also sets up a JSON-RPC client that sends events to the parent Spooky instance via stdout.
The tricky part of this arrangement is that there are now three distinct JavaScript environments to consider: Spooky (Node.js), Casper (PhantomJS), and the web page under test.
spooky.start('http://example.com/the-page.html');
spooky.then(function () {
// this function runs in Casper's environment
});
spooky.thenEvaluate(function () {
// this function runs in the page's environment
})
// this function (and the three spooky calls above) runs in Spooky's environment
spooky.run();
The Node.js, PhantomJS, and web page JavaScript environments are isolated from one another. It is not possible to reference values in one environment from another. Spooky serializes the functions it is passed and sends them to the child, which deserializes them in its environment. This means that variables can appear to be in scope when in fact they aren't even in the same JavaScript environment!
var x = 'spooky';
spooky.start('http://example.com/the-page.html');
spooky.then(function () {
var y = 'casper';
console.log('x:', x); // -> x: undefined
});
spooky.thenEvaluate(function () {
console.log('x:', x); // -> x: undefined
console.log('y:', y); // -> y: undefined
});
spooky.run();
However, it is possible to copy values from one environment to another - provided the value can be serialized to JSON. Spooky provides two ways to do this.
First, Spooky's analogues of Casper methods that accept an argument hash also accept a hash. Spooky does not yet support the new calling convention introduced in Casper 1.0; see #68.
var x = 'spooky';
// spooky.thenEvaluate accepts an options argument (just like Casper)
spooky.thenEvaluate(function (x) {
console.log('x:', x); // -> x: spooky
}, {
x: x
});
Second, wherever a Casper method accepts a function, its Spooky analogue accepts a function tuple. A function tuple is an array of length two. The first element is an argument hash. The second is the function that will be passed to the Casper method. Each key in the argument hash is made available as a variable of the same name in the function's scope, initialized to the key's value in the hash. This makes it possible to use a function tuple and an argument hash in the same call.
var x = 'spooky';
var y = 'kooky';
// spooky.then accepts a function tuple
spooky.then([{
x: x
}, function () {
console.log('x:', x); // -> x: spooky
}]);
// spooky.thenEvaluate accepts both a function tuple and an argument hash
spooky.thenEvaluate([{
y: y
}, function (x) {
console.log('x:', x); // -> x: spooky
console.log('y:', y); // -> y: kooky
}], {
x: x
});
The observed variable values in Casper are the result of serializing to JSON and then deserializing. This means that values like undefined
will not be passed through to Casper.
spooky.then([{
x: undefined
}, function () {
console.log('x:', x); // -> ReferenceError: `x` is not defined
}]);
Remember that function tuples receive the value of any variables passed to them. If the function modifies its copy, that change is not observed in Spooky's environment. This is true of argument hashes as well.
var x = 'spooky';
spooky.then([{
x: x
}, function () {
x = 'casper';
console.log('x:', x); // -> x: casper
}]);
console.log('x:', x); // -> x: spooky
It's possible to do complicated things with these tools, but use restraint: it can quickly become difficult to keep the scopes and environments straight.
spooky.start('http://example.com/the-page.html');
var x = 'spooky';
spooky.then([{
x: x
}, function () {
var y = 'casper';
var z = this.evaluate(function (x, y) {
console.log('x:', x); // -> x: spooky
console.log('y:', y); // -> y: casper
return [x, y, 'and the-page.html'].join(', ');
}, {
x: x,
y: y
});
console.log('z:', z); // -> z: spooky, casper, and the-page.html
}]);
spooky.run();