Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add New spooky.exec("code") method. #43

Closed
wants to merge 16 commits into from
Closed

Conversation

jeresig
Copy link

@jeresig jeresig commented Apr 22, 2013

This adds a new method to the Spooky API named exec(). This method takes in a string of JavaScript and executes it in the CasperJS/PhantomJS environment. Additionally it stops waiting for stdin if the stdio transport is being used.

With this particular method it becomes really easy to load a CasperJS file (using fs.readFileSync, or some such) and pass it to Spooky/Casper for execution.

An example of how it would be used:

var spooky = new Spooky({
    casper: {
        logLevel: 'debug',
        verbose: true
    }
}, function (err) {
    if (err) {
        e = new Error('Failed to initialize SpookyJS');
        e.details = err;
        throw e;
    }

    spooky.on('error', function (e) {
        console.error(e);
    });

    spooky.on('log', function (log) {
        if (log.space === 'remote') {
            console.log(log.message.replace(/ \- .*/, ''));
        }
    });

    var file = fs.readFileSync("casper.js").toString();
    spooky.exec(file, args);
});

I also added in a new option to pass to Spooky so that you can do this even more simply, like so:

var spooky = new Spooky({
    exec: {
        file: "casper.js",
        options: args
    },
    casper: {
        logLevel: 'debug',
        verbose: true
    }
});

spooky.on('error', function (e) {
    console.error(e);
});

spooky.on('log', function (log) {
    if (log.space === 'remote') {
        console.log(log.message.replace(/ \- .*/, ''));
    }
});

This makes it so that you can use Spooky as a purely event-driven tool for handling Casper.

@santiago
Copy link

Cool! I need this, please merge and promote to npm if possible

@jeresig
Copy link
Author

jeresig commented Apr 23, 2013

I just added in a unit test to make sure that the exec option was working as expected.

@lawnsea
Copy link
Contributor

lawnsea commented Apr 25, 2013

Ok, finally have a sec to look at this. Sorry for the delay.

Thanks for this work. It's a legit problem that Spooky can't execute code in the Casper/Phantom context outside of a then call.

I see two main contributions in this PR:

  1. an exec method which takes a string of JS in the Spooky context and evals it in the Casper/Phantom context
  2. an exec config option to exec a string of JS or the contents of a file when Spooky loads

I am sold on the former, but not the latter. In particular, I don't like adding this much complexity to the Spooky constructor (which is already way too complicated). I think it would be better for the exec sugar to live in its own module:

require('spooky-exec-or-whatevs').exec({
  file: 'my-casper-test.js',
  args: [42]
});

I also don't think the stdio server should stop listening when it hears exec, as that means users can't do things like this:

spooky.exec('/* do some setup in casper/phantom context */');

spooky.start();
spooky.thenOpen('http://example.com');

spooky.exec('/* do something else in casper/phantom context */');
spooky.run();

So, I propose:

  1. paring this PR down to just add the exec method and its tests
  2. opening a new PR for the config option sugar

What do you think?

// Attempt to evaluate the code
// Note that we also redefine the `instance` variable to be
// `casper` in the context of this code, which makes more sense.
eval("(function(casper){\n" + code + "\n})(instance);");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will evaluate code in the local scope, giving it access to the internals of bootstrap/casper, which is probably not desirable.

Did you consider using createFunction here instead?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Single quotes, please.

@souri
Copy link

souri commented May 21, 2013

The exec seems to get stuck when I fork your code and run it.

andresgottlieb and others added 12 commits June 26, 2013 22:19
If an unhandled error occurs, the bootstrap script emits an error event and
exits with a non-zero exit code. Partial progress toward SpookyJS#4.
Include offending line in the error message

Close SpookyJS#46
Accept npm's format for package.json so --save is less disruptive in the future.
Also factor child spawning out as a separate module for tests
`lib/bootstrap/emit` now provides `emit(<event>, params...)` and
`console.log(what...)` and friends. For now, `console` methods with a level
other than `log` prepend their level to the message. So:

```javascript
var console = require(options.spooky_lib + 'lib/bootstrap/emit').console;

console.log('hey', 'yo'); // -> emit('console', 'hey yo');
console.debug('hey', 'yo'); // -> emit('console', 'debug hey yo');
```

close SpookyJS#45
Previously, the constructor did not pass new instances through EventEmitter, so
all Spooky instances shared the same EventEmitter instance state. Instead, use
util.inherits and call EventEmitter when constructing Spooky instances.

Thanks to @tomchentw for figuring this one out.

Close SpookyJS#51, SpookyJS#60
@v4l3r10
Copy link

v4l3r10 commented Sep 12, 2013

News about this pull?

@lawnsea
Copy link
Contributor

lawnsea commented Sep 12, 2013

I never got a response from @jeresig to my comments, so there's been no progress thus far.

I may actually hack on this off and on over the next week. As a first step, I'm going to think about the right API and post it up for comment on this issue. Look for that later tonight.

@lawnsea
Copy link
Contributor

lawnsea commented Sep 17, 2013

Ok, had to fix #73 before I could get started on this.

As I said before, I think this is a great idea. In fact, on further reflection,
I think an exec method is the right core primitive for Spooky to expose.

Given exec, Spooky could just drive Phantom directly without depending on
Casper. We could then provide Casper support implemented via exec.

Here's a strawman API:

  • exec(String code[, Object context]): Execute some JavaScript code in
    Phantom. If context is provided, it must be an object that can be
    serialized to JSON. Its keys will be exposed as variables in the scope of the
    code when executed.
  • execFn(Function toExec[, arg1[, arg2[, ...]]], Function callback): Execute
    a JavaScript function in Phantom. callback will be invoked with the
    function's return value or any error raised.
  • execFnAsync(Function toExec[, arg1[, arg2[, ...]]], Function callback): As
    execFn, but async. The first argument passed to toExec will be a callback
    it must invoke when finished with its work.

execFnAsync should probably also have a configurable timeout. I'm not sure how
to specify it. One option would be to expose an async method on the function's context
like Intern does. If we do that, we don't need execFnAsync and can just provide execFn.

@davidlinse
Copy link

Any news on this ?

@lawnsea
Copy link
Contributor

lawnsea commented Apr 24, 2014

@davidlinse I started work on this but had to set it aside last fall. I don't expect to be able to pick it back up for a month or two at least. I'd love help if anyone is game.

@davidlinse
Copy link

oh, thats sad to hear.. maybe i'll find the time to have look.
thanks

~david

@lawnsea lawnsea closed this Oct 21, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants