File Coverage

blib/lib/Mojo/Phantom.pm
Criterion Covered Total %
statement 51 66 77.2
branch 2 8 25.0
condition 1 5 20.0
subroutine 13 15 86.6
pod 2 2 100.0
total 69 96 71.8


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

Leela

203            
206            
207            
208              
209             =head1 DESCRIPTION
210              
211             L is the transport backbone for L.
212             Currently it is used to evaluate javascript tests using PhantomJS, though more is possible.
213             Please note that this class is not yet as stable as the public api for the test role.
214              
215             =head1 ATTRIBUTES
216              
217             L inherits the attributes from L and implements the following new ones.
218              
219             =head2 arguments
220              
221             An array reference containing command-line arguments to be passed directly to the PhantomJS process.
222              
223             =head2 base
224              
225             An instance of L used to make relative urls absolute.
226             This is used, for example, in setting cookies
227              
228             =head2 bind
229              
230             A hash reference used to bind JS methods and Perl functions.
231             Keys are methods to be created in the C object in javascript.
232             Values are functions for those methods to invoke when the message is received by the Perl process.
233             The functions may be relative to the L or are absolute if they contain C<::>.
234             If the function is false, then the key is used as the function name.
235              
236             =head2 cookies
237              
238             An array reference containing L objects.
239              
240             =head2 package
241              
242             The package for binding relative function names.
243             Defaults to C
244              
245             =head2 setup
246              
247             An additional string of javascript which is executed after the page object is created but before the url is opened.
248              
249             =head2 sep
250              
251             A string used to separate messages from the JS side.
252             Defaults to C<--MOJO_PHANTOM_MSG-->.
253              
254             =head2 template
255              
256             A string which is used to build a L object.
257             It takes as its arguments the instance, a target url, and a string of javascript to be evaluated.
258              
259             The default handles much of what this module does, you should be very sure of why you need to change this before doing so.
260              
261             =head2 no_exit
262              
263             Do not automatically call C after the provided JavaScript code. This is useful
264             when testing asynchronous events.
265              
266             =head2 note_console
267              
268             Redirect C output to TAP as note events. This is usually helpful when writing tests. The default is
269             off for Mojo::Phantom and on for L.
270              
271             =head2 exe
272              
273             The executable name or path to call PhantomJS. You may substitute a compatible platform, for example using C to use
274             CasperJS.
275              
276             Note that while you can use this to specify the full path of an alternate version of PhantomJS, during the install of
277             Mojo::Phantom you I have phantomjs in your C for configuration and testing.
278              
279             =head1 METHODS
280              
281             L inherits all methods from L and implements the following new ones.
282              
283             =head2 execute_file
284              
285             A lower level function which handles the message passing etc.
286             You probably want L.
287             Takes a file path to start C with and a callback.
288              
289             Returns a pre-initialized instance of L.
290             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.
291              
292             =head2 execute_url
293              
294             Builds the template for PhantomJS to execute and starts it.
295             Takes a target url, a string of javascript to be executed in the context that the template provides and a callback.
296             By default this is the page context.
297             The return value is the same as L.
298              
299             The executable name or path to call PhantomJS. You may substitute a compatible platform, for example using C to use
300             CasperJS.
301              
302             =head1 NOTES
303              
304             NOTE that if your Perl version does not provide C and C, they will be monkey-patched into the C namespace before executing the javascript.
305              
306             =head1 SOURCE REPOSITORY
307              
308             L
309              
310             =head1 AUTHOR
311              
312             Joel Berger, Ejoel.a.berger@gmail.comE
313              
314             =head1 CONTRIBUTORS
315              
316             Graham Ollis (plicease)
317              
318             Sebastian Paaske Tørholm (Eckankar)
319              
320             =head1 COPYRIGHT AND LICENSE
321              
322             Copyright (C) 2015 by L and L.
323              
324             This library is free software; you can redistribute it and/or modify
325             it under the same terms as Perl itself.