1
0

CanStartBinary.pm 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. package Selenium::CanStartBinary;
  2. # ABSTRACT: Teach a WebDriver how to start its own binary aka no JRE!
  3. use File::Which qw/which/;
  4. use IO::Socket::INET;
  5. use Selenium::Waiter qw/wait_until/;
  6. use Selenium::Firefox::Binary qw/firefox_path setup_firefox_binary_env/;
  7. use Selenium::Firefox::Profile;
  8. use Try::Tiny;
  9. use Moo::Role;
  10. has 'binary_mode' => (
  11. is => 'lazy',
  12. init_arg => undef,
  13. builder => 1,
  14. predicate => 1
  15. );
  16. has 'try_binary' => (
  17. is => 'lazy',
  18. default => sub { 0 },
  19. trigger => sub {
  20. my ($self) = @_;
  21. $self->binary_mode if $self->try_binary;
  22. }
  23. );
  24. sub BUILDARGS {
  25. my ( $class, %args ) = @_;
  26. if ( ! exists $args{remote_server_addr} && ! exists $args{port} ) {
  27. $args{try_binary} = 1;
  28. # Windows may throw a fit about invalid pointers if we try to
  29. # connect to localhost instead of 127.1
  30. $args{remote_server_addr} = '127.0.0.1';
  31. }
  32. return { %args };
  33. }
  34. sub _build_binary_mode {
  35. my ($self) = @_;
  36. return try {
  37. my $port = start_binary_on_port($self->binary_name, $self->binary_port);
  38. $self->port($port);
  39. return 1;
  40. }
  41. catch {
  42. warn $_;
  43. return 0;
  44. };
  45. }
  46. sub probe_port {
  47. my ($port) = @_;
  48. return IO::Socket::INET->new(
  49. PeerAddr => '127.0.0.1',
  50. PeerPort => $port,
  51. Timeout => 3
  52. );
  53. }
  54. sub start_binary_on_port {
  55. my ($process, $port) = @_;
  56. my $executable = _find_executable($process);
  57. $port = _find_open_port_above($port);
  58. if ($process eq 'firefox') {
  59. setup_firefox_binary_env($port);
  60. }
  61. my $command = _construct_command($executable, $port);
  62. system($command);
  63. my $success = wait_until { probe_port($port) } timeout => 10;
  64. if ($success) {
  65. return $port;
  66. }
  67. else {
  68. die 'Unable to connect to the ' . $executable . ' binary on port ' . $port;
  69. }
  70. }
  71. sub shutdown_binary {
  72. my ($self) = @_;
  73. if ($self->has_binary_mode && $self->binary_mode) {
  74. my $port = $self->port;
  75. my $ua = $self->ua;
  76. $ua->get('127.0.0.1:' . $port . '/wd/hub/shutdown');
  77. }
  78. }
  79. sub _find_executable {
  80. my ($binary) = @_;
  81. if ($binary eq 'firefox') {
  82. return firefox_path();
  83. }
  84. else {
  85. my $executable = which($binary);
  86. if (not defined $executable) {
  87. die qq(Unable to find the $binary binary in your \$PATH. We'll try falling back to standard Remote Driver);
  88. }
  89. else {
  90. return $executable;
  91. }
  92. }
  93. }
  94. sub _construct_command {
  95. my ($executable, $port) = @_;
  96. my %args;
  97. if ($executable =~ /chromedriver(\.exe)?$/i) {
  98. %args = (
  99. port => $port,
  100. 'url-base' => 'wd/hub'
  101. );
  102. }
  103. elsif ($executable =~ /phantomjs(\.exe)?$/i) {
  104. %args = (
  105. webdriver => '127.0.0.1:' . $port
  106. );
  107. }
  108. elsif ($executable =~ /firefox/i) {
  109. $executable .= ' -no-remote ';
  110. }
  111. my @args = map { '--' . $_ . '=' . $args{$_} } keys %args;
  112. # Handle Windows vs Unix discrepancies for invoking shell commands
  113. my ($prefix, $suffix) = (_command_prefix(), _command_suffix());
  114. return join(' ', ($prefix, $executable, @args, $suffix) );
  115. }
  116. sub _command_prefix {
  117. if ($^O eq 'MSWin32') {
  118. return 'start /MAX '
  119. }
  120. else {
  121. return '';
  122. }
  123. }
  124. sub _command_suffix {
  125. if ($^O eq 'MSWin32') {
  126. return ' > /nul 2>&1 ';
  127. }
  128. else {
  129. # TODO: allow users to specify whether & where they want
  130. # driver output to go
  131. return ' > /dev/null 2>&1 &';
  132. }
  133. }
  134. sub _find_open_port_above {
  135. my ($port) = @_;
  136. my $free_port = wait_until {
  137. if ( probe_port($port) ) {
  138. $port++;
  139. return 0;
  140. }
  141. else {
  142. return $port;
  143. }
  144. };
  145. return $free_port;
  146. }
  147. 1;