File Coverage

blib/lib/Selenium/CanStartBinary.pm
Criterion Covered Total %
statement 36 115 31.3
branch 5 44 11.3
condition 2 12 16.6
subroutine 11 25 44.0
pod 0 5 0.0
total 54 201 26.8


line stmt bran cond sub pod time code
1             package Selenium::CanStartBinary;
2             $Selenium::CanStartBinary::VERSION = '1.47';
3 1     1   468 use strict;
  1         1  
  1         25  
4 1     1   5 use warnings;
  1         1  
  1         21  
5              
6             # ABSTRACT: Teach a WebDriver how to start its own binary aka no JRE!
7 1     1   4 use File::Spec;
  1         2  
  1         25  
8             use Selenium::CanStartBinary::ProbePort
9 1     1   390 qw/find_open_port_above find_open_port probe_port/;
  1         3  
  1         54  
10 1     1   7 use Selenium::Firefox::Binary qw/setup_firefox_binary_env/;
  1         2  
  1         39  
11 1     1   4 use Selenium::Waiter qw/wait_until/;
  1         2  
  1         27  
12 1     1   4 use Moo::Role;
  1         2  
  1         4  
13              
14 1     1   336 use constant IS_WIN => $^O eq 'MSWin32';
  1         2  
  1         1433  
15              
16              
17             requires 'binary';
18              
19              
20             requires 'binary_port';
21              
22              
23             requires '_binary_args';
24              
25              
26             has '_real_binary' => (
27             is => 'lazy',
28             builder => sub {
29 0     0   0 my ($self) = @_;
30              
31 0 0       0 if ( $self->_is_old_ff ) {
32 0         0 return $self->firefox_binary;
33             }
34             else {
35 0         0 return $self->binary;
36             }
37             }
38             );
39              
40             has '_is_old_ff' => (
41             is => 'lazy',
42             builder => sub {
43 0     0   0 my ($self) = @_;
44              
45 0   0     0 return $self->isa('Selenium::Firefox') && !$self->marionette_enabled;
46             }
47             );
48              
49             has '+port' => (
50             is => 'lazy',
51             builder => sub {
52 0     0   0 my ($self) = @_;
53              
54 0 0       0 if ( $self->_real_binary ) {
55 0 0       0 if ( $self->fixed_ports ) {
56 0         0 return find_open_port( $self->binary_port );
57             }
58             else {
59 0         0 return find_open_port_above( $self->binary_port );
60             }
61             }
62             else {
63 0         0 return 4444;
64             }
65             }
66             );
67              
68              
69             has 'fixed_ports' => (
70             is => 'lazy',
71             default => sub { 0 }
72             );
73              
74              
75             has custom_args => (
76             is => 'lazy',
77             predicate => 1,
78             default => sub { '' }
79             );
80              
81             has 'marionette_port' => (
82             is => 'lazy',
83             builder => sub {
84 0     0   0 my ($self) = @_;
85              
86 0 0       0 if ( $self->_is_old_ff ) {
87 0         0 return 0;
88             }
89             else {
90 0 0       0 if ( $self->fixed_ports ) {
91 0         0 return find_open_port( $self->marionette_binary_port );
92             }
93             else {
94 0         0 return find_open_port_above( $self->marionette_binary_port );
95             }
96             }
97             }
98             );
99              
100              
101             has startup_timeout => (
102             is => 'lazy',
103             default => sub { 10 }
104             );
105              
106              
107             has 'binary_mode' => (
108             is => 'lazy',
109             init_arg => undef,
110             builder => 1,
111             predicate => 1
112             );
113              
114             has 'try_binary' => (
115             is => 'lazy',
116             default => sub { 0 },
117             trigger => sub {
118             my ($self) = @_;
119             $self->binary_mode if $self->try_binary;
120             }
121             );
122              
123              
124             has 'window_title' => (
125             is => 'lazy',
126             init_arg => undef,
127             builder => sub {
128 0     0   0 my ($self) = @_;
129 0         0 my ( undef, undef, $file ) =
130             File::Spec->splitpath( $self->_real_binary );
131 0         0 my $port = $self->port;
132              
133 0         0 return $file . ':' . $port;
134             }
135             );
136              
137              
138             has '_command' => (
139             is => 'lazy',
140             init_arg => undef,
141             builder => sub {
142 0     0   0 my ($self) = @_;
143 0         0 return $self->_construct_command;
144             }
145             );
146              
147              
148             has 'logfile' => (
149             is => 'lazy',
150             default => sub {
151             return '/nul' if IS_WIN;
152             return '/dev/null';
153             }
154             );
155              
156             sub BUILDARGS {
157              
158             # There's a bit of finagling to do to since we can't ensure the
159             # attribute instantiation order. To decide whether we're going into
160             # binary mode, we need the remote_server_addr and port. But, they're
161             # both lazy and only instantiated immediately before S:R:D's
162             # remote_conn attribute. Once remote_conn is set, we can't change it,
163             # so we need the following order:
164             #
165             # parent: remote_server_addr, port
166             # role: binary_mode (aka _build_binary_mode)
167             # parent: remote_conn
168             #
169             # Since we can't force an order, we introduced try_binary which gets
170             # decided during BUILDARGS to tip us off as to whether we should try
171             # binary mode or not.
172 4     4 0 30266 my ( undef, %args ) = @_;
173              
174 4 50 33     16 if ( !exists $args{remote_server_addr} && !exists $args{port} ) {
175 0         0 $args{try_binary} = 1;
176              
177             # Windows may throw a fit about invalid pointers if we try to
178             # connect to localhost instead of 127.1
179 0         0 $args{remote_server_addr} = '127.0.0.1';
180             }
181             else {
182 4         9 $args{try_binary} = 0;
183 4         6 $args{binary_mode} = 0;
184             }
185              
186 4         70 return {%args};
187             }
188              
189             sub _build_binary_mode {
190 0     0   0 my ($self) = @_;
191              
192             # We don't know what to do without a binary driver to start up
193 0 0       0 return unless $self->_real_binary;
194              
195             # Either the user asked for 4444, or we couldn't find an open port
196 0         0 my $port = $self->port + 0;
197 0 0       0 return if $port == 4444;
198 0 0 0     0 if ( $self->fixed_ports && $port == 0 ) {
199 0         0 die 'port '
200             . $self->binary_port
201             . ' is not free and have requested fixed ports';
202             }
203              
204 0         0 $self->_handle_firefox_setup($port);
205              
206 0         0 system( $self->_command );
207              
208             my $success =
209 0     0   0 wait_until { probe_port($port) } timeout => $self->startup_timeout;
  0         0  
210 0 0       0 if ($success) {
211 0         0 return 1;
212             }
213             else {
214 0         0 die 'Unable to connect to the '
215             . $self->_real_binary
216             . ' binary on port '
217             . $port;
218             }
219             }
220              
221             sub _handle_firefox_setup {
222 0     0   0 my ( $self, $port ) = @_;
223              
224             # This is a no-op for other browsers
225 0 0       0 return unless $self->isa('Selenium::Firefox');
226              
227 0 0       0 my $user_profile =
228             $self->has_firefox_profile
229             ? $self->firefox_profile
230             : 0;
231              
232 0         0 my $profile =
233             setup_firefox_binary_env( $port, $self->marionette_port, $user_profile );
234              
235 0 0       0 if ( $self->_is_old_ff ) {
236              
237             # For non-geckodriver/non-marionette, we want to get rid of
238             # the profile so that we don't accidentally zip it and encode
239             # it down the line while Firefox is trying to read from it.
240 0 0       0 $self->clear_firefox_profile if $self->has_firefox_profile;
241             }
242             else {
243             # For geckodriver/marionette, we keep the enhanced profile around because
244             # we need to send it to geckodriver as a zipped b64-encoded
245             # directory.
246 0         0 $self->firefox_profile($profile);
247             }
248             }
249              
250             sub shutdown_binary {
251 4     4 0 7 my ($self) = @_;
252              
253 4 50       69 return unless $self->auto_close();
254 4 50       49 if ( defined $self->session_id ) {
255 0         0 $self->quit();
256             }
257 4 50 33     63 if ( $self->has_binary_mode && $self->binary_mode ) {
258              
259             # Tell the binary itself to shutdown
260 0         0 my $port = $self->port;
261 0         0 my $ua = $self->ua;
262 0         0 $ua->get( 'http://127.0.0.1:' . $port . '/wd/hub/shutdown' );
263              
264             # Close the orphaned command windows on windows
265 0         0 $self->shutdown_windows_binary;
266 0         0 $self->shutdown_unix_binary;
267             }
268              
269             }
270              
271             sub shutdown_unix_binary {
272 0     0 0 0 my ($self) = @_;
273 0         0 if (!IS_WIN) {
274 0         0 my $cmd = "lsof -t -i :".$self->port();
275 0 0       0 my ( $pid ) = grep { $_ && $_ ne $$ } split( /\s+/, scalar `$cmd` );
  0         0  
276 0 0       0 if ($pid) {
277 0         0 print "Killing Driver PID $pid listening on port "
278             . $self->port . "...\n";
279 0         0 eval { kill 'KILL', $pid };
  0         0  
280 0 0       0 warn
281             "Could not kill driver process! you may have to clean up manually."
282             if $@;
283             }
284             }
285             }
286              
287             sub shutdown_windows_binary {
288 0     0 0 0 my ($self) = @_;
289              
290 0         0 if (IS_WIN) {
291             if ( $self->_is_old_ff ) {
292              
293             # FIXME: Blech, handle a race condition that kills the
294             # driver before it's finished cleaning up its sessions. In
295             # particular, when the perl process ends, it wants to
296             # clean up the temp directory it created for the Firefox
297             # profile. But, if the Firefox process is still running,
298             # it will have a lock on the temp profile directory, and
299             # perl will get upset. This "solution" is _very_ bad.
300             sleep(2);
301              
302             # Firefox doesn't have a Driver/Session architecture - the
303             # only thing running is Firefox itself, so there's no
304             # other task to kill.
305             return;
306             }
307             system( 'taskkill /FI "WINDOWTITLE eq '
308             . $self->window_title
309             . '" > nul 2>&1' );
310             }
311             }
312              
313             sub DEMOLISH {
314 4     4 0 4828 my ( $self, $in_gd ) = @_;
315              
316             # if we're in global destruction, all bets are off.
317 4 50       12 return if $in_gd;
318 4         24 $self->shutdown_binary;
319             }
320              
321             sub _construct_command {
322 0     0     my ($self) = @_;
323 0           my $executable = $self->_real_binary;
324              
325             # Executable path names may have spaces
326 0           $executable = '"' . $executable . '"';
327              
328             # The different binaries take different arguments for proper setup
329 0           $executable .= $self->_binary_args;
330 0 0         if ( $self->has_custom_args ) {
331 0           $executable .= ' ' . $self->custom_args;
332             }
333              
334             # Handle Windows vs Unix discrepancies for invoking shell commands
335 0           my ( $prefix, $suffix ) = ( $self->_cmd_prefix, $self->_cmd_suffix );
336 0           return join( ' ', ( $prefix, $executable, $suffix ) );
337             }
338              
339             sub _cmd_prefix {
340 0     0     my ($self) = @_;
341              
342 0           my $prefix = '';
343 0           if (IS_WIN) {
344             $prefix = 'start "' . $self->window_title . '"';
345              
346             if ( $self->_is_old_ff ) {
347              
348             # For older versions of Firefox that run without
349             # marionette, the command we're running actually starts up
350             # the browser itself, so we don't want to minimize it.
351             return $prefix;
352             }
353             else {
354             # If we're firefox with marionette, or any other browser,
355             # the command we're running is the driver, and we don't
356             # need want the command window in the foreground.
357             return $prefix . ' /MIN ';
358             }
359             }
360 0           return $prefix;
361             }
362              
363             sub _cmd_suffix {
364 0     0     my ($self) = @_;
365 0           return " > " . $self->logfile . " 2>&1 " if IS_WIN;
366 0           return " > " . $self->logfile . " 2>&1 &";
367             }
368              
369              
370             1;
371              
372             __END__