package Mojo::Phantom; use Mojo::Base -base; our $VERSION = '0.12'; $VERSION = eval $VERSION; use Mojo::Phantom::Process; use Mojo::File; use Mojo::JSON; use Mojo::Template; use Mojo::URL; use JavaScript::Value::Escape; use constant DEBUG => $ENV{MOJO_PHANTOM_DEBUG}; use constant CAN_CORE_DIE => !! CORE->can('die'); use constant CAN_CORE_WARN => !! CORE->can('warn'); has arguments => sub { [] }; has base => sub { Mojo::URL->new }; has bind => sub { {} }; has cookies => sub { [] }; has package => 'main'; has 'setup'; has sep => '--MOJO_PHANTOM_MSG--'; has no_exit => 0; has note_console => 0; has 'exe'; has template => <<'TEMPLATE'; % my ($self, $url, $js) = @_; // Setup perl function function perl() { var system = require('system'); var args = Array.prototype.slice.call(arguments); system.stdout.writeLine(JSON.stringify(args)); system.stdout.writeLine('<%== $self->sep %>'); system.stdout.flush(); } // Setup error handling var onError = function(msg, trace) { var msgStack = ['PHANTOM ERROR: ' + msg]; if (trace && trace.length) { msgStack.push('TRACE:'); trace.forEach(function(t) { msgStack.push(' -> ' + (t.file || t.sourceURL) + ': ' + t.line + (t.function ? ' (in function ' + t.function +')' : '')); }); } //phantom.exit isn't exitting immediately, so let Perl kill us perl('CORE::die', msgStack.join('\n')); //phantom.exit(1); }; phantom.onError = onError; // Setup bound functions % my $bind = $self->bind || {}; % foreach my $func (keys %$bind) { % my $target = $bind->{$func} || $func; perl.<%= $func %> = function() { perl.apply(this, ['<%== $target %>'].concat(Array.prototype.slice.call(arguments))); }; % } // Setup Cookies % foreach my $cookie (@{ $self->cookies }) { % my $name = $cookie->name; phantom.addCookie({ name: '<%== $name %>', value: '<%== $cookie->value %>', domain: '<%== $cookie->domain || $self->base->host %>', }) || perl.diag('Failed to import cookie <%== $name %>'); % } // Requst page and inject user-provided javascript var page = require('webpage').create(); page.onError = onError; % if($self->note_console) { // redirect browser console log to TAP page.onConsoleMessage = function(msg) { perl.note('js console: ' + msg); }; // redirect console log to TAP (function() { var old = console.log; console.log = function(msg) { perl.note('phantom console: ' + msg); old.apply(this, Array.prototype.slice.call(arguments)); }; }()); % } // Additional setup <%= $self->setup || '' %>; page.open('<%== $url %>', function(status) { <%= $js %>; % unless($self->no_exit) { phantom.exit(); % } }); TEMPLATE sub execute_file { my ($self, $file, $cb) = @_; # note that $file might be an object that needs to have a strong reference my $arguments = $self->arguments // []; my $proc = Mojo::Phantom::Process->new(arguments => $arguments); $proc->exe($self->exe) if $self->exe; my $sep = $self->sep; my $package = $self->package; my $buffer = ''; my $error; $proc->on(read => sub { my ($proc, $bytes) = @_; warn "\nPerl <<<< Phantom: $bytes\n" if DEBUG; $buffer .= $bytes; while ($buffer =~ s/^(.*)\n$sep\n//) { my ($function, @args) = @{ Mojo::JSON::decode_json $1 }; local *CORE::die = sub { die @_ } unless CAN_CORE_DIE; local *CORE::warn = sub { warn @_ } unless CAN_CORE_WARN; eval "package $package; no strict 'refs'; &{\$function}(\@args)"; if ($@) { $error = $@; return $proc->kill } } }); $proc->on(close => sub { my ($proc) = @_; undef $file; $self->$cb($error || $proc->error, $proc->exit_status) if $cb; }); return $proc->start($file); } sub execute_url { my ($self, $url, $js, $cb) = @_; $js = Mojo::Template ->new(escape => \&javascript_value_escape) ->render($self->template, $self, $url, $js); die $js if ref $js; # Mojo::Template returns Mojo::Exception objects on failure warn "\nPerl >>>> Phantom:\n$js\n" if DEBUG; my $tmp = Mojo::File::tempfile(SUFFIX => '.js')->spurt($js); return $self->execute_file($tmp, $cb); } 1; =encoding utf8 =head1 NAME Mojo::Phantom - Interact with your client side code via PhantomJS =head1 SYNOPSIS use Mojolicious::Lite; use Test::More; use Test::Mojo::WithRoles qw/Phantom/; any '/' => 'index'; my $t = Test::Mojo::WithRoles->new; $t->phantom_ok('/' => <<'JS'); var text = page.evaluate(function(){ return document.getElementById('name').innerHTML; }); perl.is(text, 'Bender', 'name changed after loading'); JS done_testing; __DATA__ @@ index.html.ep
Leela
=head1 DESCRIPTION L