File Coverage

blib/lib/Mojo/Phantom.pm
Criterion Covered Total %
statement 50 65 76.9
branch 2 8 25.0
condition 0 3 0.0
subroutine 13 15 86.6
pod 2 2 100.0
total 67 93 72.0


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

Leela

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