File Coverage

blib/lib/Mojo/Phantom.pm
Criterion Covered Total %
statement 43 56 76.7
branch 1 6 16.6
condition 0 3 0.0
subroutine 11 13 84.6
pod 2 2 100.0
total 57 80 71.2


line stmt bran cond sub pod time code
1             package Mojo::Phantom;
2              
3 5     5   391220 use Mojo::Base -base;
  5         8  
  5         42  
4              
5             our $VERSION = '0.01';
6             $VERSION = eval $VERSION;
7              
8 5     5   3091 use Mojo::Phantom::Process;
  5         10  
  5         43  
9              
10 5     5   4221 use File::Temp ();
  5         46519  
  5         154  
11 5     5   40 use Mojo::Util;
  5         6  
  5         258  
12 5     5   26 use Mojo::JSON;
  5         14  
  5         154  
13 5     5   25 use Mojo::Template;
  5         6  
  5         56  
14 5     5   116 use Mojo::URL;
  5         6  
  5         36  
15 5     5   2510 use JavaScript::Value::Escape;
  5         2609  
  5         317  
16              
17 5     5   27 use constant DEBUG => $ENV{MOJO_PHANTOM_DEBUG};
  5         7  
  5         3411  
18              
19             has base => sub { Mojo::URL->new };
20             has bind => sub { {} };
21             has cookies => sub { [] };
22             has package => 'main';
23             has 'setup';
24             has sep => '--MOJO_PHANTOM_MSG--';
25              
26             has template => <<'TEMPLATE';
27             % my ($self, $url, $js) = @_;
28              
29             // Setup perl function
30             function perl() {
31             var system = require('system');
32             var args = Array.prototype.slice.call(arguments);
33             system.stdout.writeLine(JSON.stringify(args));
34             system.stdout.writeLine('<%== $self->sep %>');
35             system.stdout.flush();
36             }
37              
38             // Setup error handling
39             var onError = function(msg, trace) {
40             var msgStack = ['PHANTOM ERROR: ' + msg];
41             if (trace && trace.length) {
42             msgStack.push('TRACE:');
43             trace.forEach(function(t) {
44             msgStack.push(' -> ' + (t.file || t.sourceURL) + ': ' + t.line + (t.function ? ' (in function ' + t.function +')' : ''));
45             });
46             }
47              
48             //phantom.exit isn't exitting immediately, so let Perl kill us
49             perl('CORE::die', msgStack.join('\n'));
50             //phantom.exit(1);
51             };
52             phantom.onError = onError;
53              
54             // Setup bound functions
55             % my $bind = $self->bind || {};
56             % foreach my $func (keys %$bind) {
57             % my $target = $bind->{$func} || $func;
58             perl.<%= $func %> = function() {
59             perl.apply(this, ['<%== $target %>'].concat(Array.prototype.slice.call(arguments)));
60             };
61             % }
62              
63             // Setup Cookies
64             % foreach my $cookie (@{ $self->cookies }) {
65             % my $name = $cookie->name;
66             phantom.addCookie({
67             name: '<%== $name %>',
68             value: '<%== $cookie->value %>',
69             domain: '<%== $cookie->domain || $self->base->host %>',
70             }) || perl.diag('Failed to import cookie <%== $name %>');
71             % }
72              
73             // Requst page and inject user-provided javascript
74             var page = require('webpage').create();
75             page.onError = onError;
76              
77             // Additional setup
78             <%= $self->setup || '' %>;
79              
80             page.open('<%== $url %>', function(status) {
81              
82             <%= $js %>;
83              
84             phantom.exit();
85             });
86             TEMPLATE
87              
88             sub execute_file {
89 5     5 1 17 my ($self, $file, $cb) = @_;
90             # note that $file might be an object that needs to have a strong reference
91              
92 5         79 my $proc = Mojo::Phantom::Process->new;
93              
94 5         54 my $sep = $self->sep;
95 5         47 my $package = $self->package;
96 5         25 my $buffer = '';
97 5         8 my $error;
98              
99             $proc->on(read => sub {
100 0     0   0 my ($proc, $bytes) = @_;
101 0         0 warn "\nPerl <<<< Phantom: $bytes\n" if DEBUG;
102 0         0 $buffer .= $bytes;
103 0         0 while ($buffer =~ s/^(.*)\n$sep\n//) {
104 0         0 my ($function, @args) = @{ Mojo::JSON::decode_json $1 };
  0         0  
105 0         0 eval "package $package; no strict 'refs'; &{\$function}(\@args)";
106 0 0       0 if ($@) { $error = $@; return $proc->kill }
  0         0  
  0         0  
107             }
108 5         71 });
109              
110             $proc->on(close => sub {
111 0     0   0 my ($proc) = @_;
112 0         0 undef $file;
113 0 0 0     0 $self->$cb($error || $proc->error, $proc->exit_status) if $cb;
114 5         86 });
115              
116 5         45 return $proc->start($file);
117             }
118              
119             sub execute_url {
120 5     5 1 3528 my ($self, $url, $js, $cb) = @_;
121              
122 5         56 $js = Mojo::Template
123             ->new(escape => \&javascript_value_escape)
124             ->render($self->template, $self, $url, $js);
125              
126 5 50       21872 die $js if ref $js; # Mojo::Template returns Mojo::Exception objects on failure
127              
128 5         11 warn "\nPerl >>>> Phantom:\n$js\n" if DEBUG;
129 5         69 my $tmp = File::Temp->new(SUFFIX => '.js');
130 5         3609 Mojo::Util::spurt($js => "$tmp");
131              
132 5         287287 return $self->execute_file($tmp, $cb);
133             }
134              
135             1;
136              
137              
138             =head1 NAME
139              
140             Mojo::Phantom - Interact with your client side code via PhantomJS
141              
142             =head1 SYNOPSIS
143              
144             use Mojolicious::Lite;
145              
146             use Test::More;
147             use Test::Mojo::WithRoles qw/Phantom/;
148              
149             any '/' => 'index';
150              
151             my $t = Test::Mojo::WithRoles->new;
152             $t->phantom_ok('/' => <<'JS');
153             var text = page.evaluate(function(){
154             return document.getElementById('name').innerHTML;
155             });
156             perl.is(text, 'Bender', 'name changed after loading');
157             JS
158              
159             done_testing;
160              
161             __DATA__
162              
163             @@ index.html.ep
164              
165            
166            
167            
168            
169            

Leela

170            
173            
174            
175              
176             =head1 DESCRIPTION
177              
178             L is the transport backbone for L.
179             Currently it is used to evaluate javascript tests using PhantomJS, though more is possible.
180             Please note that this class is not yet as stable as the public api for the test role.
181              
182             =head1 ATTRIBUTES
183              
184             L inherits the attributes from L and implements the following new ones.
185              
186             =head2 base
187              
188             An instance of L used to make relative urls absolute.
189             This is used, for example, in setting cookies
190              
191             =head2 bind
192              
193             A hash reference used to bind JS methods and Perl functions.
194             Keys are methods to be created in the C object in javascript.
195             Values are functions for those methods to invoke when the message is received by the Perl process.
196             The functions may be relative to the L or are absolute if they contain C<::>.
197             If the function is false, then the key is used as the function name.
198              
199             =head2 cookies
200              
201             An array reference containing L objects.
202              
203             =head2 package
204              
205             The package for binding relative function names.
206             Defaults to C
207              
208             =head2 setup
209              
210             An additional string of javascript which is executed after the page object is created but before the url is opened.
211              
212             =head2 sep
213              
214             A string used to separate messages from the JS side.
215             Defaults to C<--MOJO_PHANTOM_MSG-->.
216              
217             =head2 template
218              
219             A string which is used to build a L object.
220             It takes as its arguments the instance, a target url, and a string of javascript to be evaluated.
221              
222             The default handles much of what this module does, you should be very sure of why you need to change this before doing so.
223              
224             =head1 METHODS
225              
226             L inherits all methods from L and implements the following new ones.
227              
228             =head2 execute_file
229              
230             A lower level function which handles the message passing etc.
231             You probably want L.
232             Takes a file path to start C with and a callback.
233              
234             Returns a pre-initialized instance of L.
235             The end user likely does not need to worry about this object, though it might be useful if the process needs to be killed or the stream timeout needs to be lengthened.
236              
237             =head2 execute_url
238              
239             Builds the template for PhantomJS to execute and starts it.
240             Takes a target url, a string of javascript to be executed in the context that the template provides and a callback.
241             By default this is the page context.
242             The return value is the same as L.
243              
244             =head1 SOURCE REPOSITORY
245              
246             L
247              
248             =head1 AUTHOR
249              
250             Joel Berger, Ejoel.a.berger@gmail.comE
251              
252             =head1 COPYRIGHT AND LICENSE
253              
254             Copyright (C) 2015 by Joel Berger
255              
256             This library is free software; you can redistribute it and/or modify
257             it under the same terms as Perl itself.