RemoteConnection.pm 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. package Selenium::Remote::RemoteConnection;
  2. use strict;
  3. use warnings;
  4. #ABSTRACT: Connect to a selenium server
  5. use Moo;
  6. use Try::Tiny;
  7. use LWP::UserAgent;
  8. use HTTP::Headers;
  9. use HTTP::Request;
  10. use Carp qw(croak);
  11. use JSON;
  12. use Selenium::Remote::ErrorHandler;
  13. use Scalar::Util qw{looks_like_number};
  14. has 'remote_server_addr' => (
  15. is => 'rw',
  16. default => '127.0.0.1',
  17. );
  18. has 'port' => (
  19. is => 'rw',
  20. 'default' => 4444,
  21. );
  22. has 'debug' => (
  23. is => 'rw',
  24. default => 0,
  25. );
  26. has 'ua' => (
  27. is => 'lazy',
  28. builder => sub { return LWP::UserAgent->new; }
  29. );
  30. has 'error_handler' => (
  31. is => 'lazy',
  32. builder => sub { return Selenium::Remote::ErrorHandler->new; }
  33. );
  34. with 'Selenium::Remote::Driver::CanSetWebdriverContext';
  35. =head1 DESCRIPTION
  36. You shouldn't really need to use this module unless debugging or checking connections when testing dangerous things.
  37. =head1 SYNOPSIS
  38. my $driver = Selenium::Remote::Driver->new();
  39. eval { $driver->remote_conn->check_status() };
  40. die "do something to kick the server" if $@;
  41. =head1 CONSTRUCTOR
  42. =head2 new(%parameters)
  43. Accepts 5 parameters:
  44. =over 4
  45. =item B<remote_server_addr> - address of selenium server
  46. =item B<port> - port of selenium server
  47. =item B<ua> - Useful to override with Test::LWP::UserAgent in unit tests
  48. =item B<debug> - Should be self-explanatory
  49. =item B<error_handler> - Defaults to Selenium::Remote::ErrorHandler.
  50. =back
  51. These can be set any time later by getter/setters with the same name.
  52. =head1 METHODS
  53. =head2 check_status
  54. Croaks unless the selenium server is responsive. Sometimes is useful to call in-between tests (the server CAN die on you...)
  55. =cut
  56. sub check_status {
  57. my $self = shift;
  58. my $status;
  59. try {
  60. $status = $self->request( { method => 'GET', url => 'status' } );
  61. }
  62. catch {
  63. croak "Could not connect to SeleniumWebDriver: $_";
  64. };
  65. my $cmdOut = $status->{cmd_status} || '';
  66. if ( $cmdOut ne 'OK' ) {
  67. # Could be grid, see if we can talk to it
  68. $status = undef;
  69. $status =
  70. $self->request( { method => 'GET', url => 'grid/api/hub/status' } );
  71. }
  72. unless ( $cmdOut eq 'OK' ) {
  73. croak "Selenium server did not return proper status";
  74. }
  75. # While we're here, let's try to find out what server version we're on.
  76. my $response = $status->{'cmd_out'};
  77. if( ref $response eq 'HASH' ) {
  78. # Selenium 4 pathway
  79. my $curr_component = $response;
  80. my $looks_like_4;
  81. foreach my $key (qw{value nodes 0 version}) {
  82. if( $key eq '0' ) { # We must at least have one node in the grid.
  83. last if !$curr_component->[0];
  84. } else {
  85. last if !$curr_component->{$key};
  86. }
  87. $looks_like_4 = $response->{'value'}{'nodes'}[0]{'version'};
  88. }
  89. if($looks_like_4) {
  90. $self->{'version'} = 4;
  91. $self->{'version_full'} = $looks_like_4;
  92. }
  93. }
  94. return;
  95. }
  96. =head2 request
  97. Make a request of the Selenium server. Mostly useful for debugging things going wrong with Selenium::Remote::Driver when not in normal operation.
  98. =cut
  99. sub request {
  100. my ( $self, $resource, $params, $dont_process_response ) = @_;
  101. my $method = $resource->{method};
  102. my $url = $resource->{url};
  103. my $no_content_success = $resource->{no_content_success} // 0;
  104. my $content = '';
  105. my $fullurl = '';
  106. # Construct full url.
  107. if ( $url =~ m/^http/g ) {
  108. $fullurl = $url;
  109. }
  110. elsif ( $url =~ m/^\// ) {
  111. # This is used when we get a 302 Redirect with a Location header.
  112. $fullurl =
  113. "http://" . $self->remote_server_addr . ":" . $self->port . $url;
  114. }
  115. elsif ( $url =~ m/grid/g ) {
  116. $fullurl =
  117. "http://" . $self->remote_server_addr . ":" . $self->port . "/$url";
  118. }
  119. else {
  120. $fullurl =
  121. "http://"
  122. . $self->remote_server_addr . ":"
  123. . $self->port
  124. . $self->wd_context_prefix . "/$url";
  125. }
  126. if ( ( defined $params ) && $params ne '' ) {
  127. #WebDriver 3 shims
  128. if ( $resource->{payload} ) {
  129. foreach my $key ( keys( %{ $resource->{payload} } ) ) {
  130. $params->{$key} = $resource->{payload}->{$key};
  131. }
  132. }
  133. my $json = JSON->new;
  134. $json->allow_blessed;
  135. $content = $json->allow_nonref->utf8->encode($params);
  136. }
  137. print "REQ: $method, $fullurl, $content\n" if $self->debug;
  138. # HTTP request
  139. my $header =
  140. HTTP::Headers->new( Content_Type => 'application/json; charset=utf-8' );
  141. $header->header( 'Accept' => 'application/json' );
  142. my $request = HTTP::Request->new( $method, $fullurl, $header, $content );
  143. my $response = $self->ua->request($request);
  144. if ($dont_process_response) {
  145. return $response;
  146. }
  147. return $self->_process_response( $response, $no_content_success );
  148. }
  149. sub _process_response {
  150. my ( $self, $response, $no_content_success ) = @_;
  151. my $data; # server response 'value' that'll be returned to the user
  152. my $json = JSON->new;
  153. if ( $response->is_redirect ) {
  154. my $redirect = {
  155. method => 'GET',
  156. url => $response->header('location')
  157. };
  158. return $self->request($redirect);
  159. }
  160. else {
  161. my $decoded_json = undef;
  162. print "RES: " . $response->decoded_content . "\n\n" if $self->debug;
  163. if ( ( $response->message ne 'No Content' )
  164. && ( $response->content ne '' ) )
  165. {
  166. if ( $response->content_type !~ m/json/i ) {
  167. $data->{'cmd_status'} = 'NOTOK';
  168. $data->{'cmd_return'}->{message} =
  169. 'Server returned error message '
  170. . $response->content
  171. . ' instead of data';
  172. return $data;
  173. }
  174. $decoded_json =
  175. $json->allow_nonref(1)->utf8(1)->decode( $response->content );
  176. $data->{'sessionId'} = $decoded_json->{'sessionId'};
  177. }
  178. if ( $response->is_error ) {
  179. $data->{'cmd_status'} = 'NOTOK';
  180. if ( defined $decoded_json ) {
  181. $data->{'cmd_return'} =
  182. $self->error_handler->process_error($decoded_json);
  183. }
  184. else {
  185. $data->{'cmd_return'} =
  186. 'Server returned error code '
  187. . $response->code
  188. . ' and no data';
  189. }
  190. return $data;
  191. }
  192. elsif ( $response->is_success ) {
  193. $data->{'cmd_status'} = 'OK';
  194. if ( defined $decoded_json ) {
  195. $data->{'cmd_out'} = $decoded_json;
  196. #XXX MS edge doesn't follow spec here either
  197. if ( looks_like_number( $decoded_json->{status} )
  198. && $decoded_json->{status} > 0
  199. && $decoded_json->{value}{message} )
  200. {
  201. $data->{cmd_status} = 'NOT OK';
  202. $data->{cmd_return} = $decoded_json->{value};
  203. return $data;
  204. }
  205. #XXX shockingly, neither does InternetExplorerDriver
  206. if ( ref $decoded_json eq 'HASH' && $decoded_json->{error} ) {
  207. $data->{cmd_status} = 'NOT OK';
  208. $data->{cmd_return} = $decoded_json;
  209. return $data;
  210. }
  211. if ($no_content_success) {
  212. $data->{'cmd_return'} = 1;
  213. }
  214. else {
  215. $data->{'cmd_return'} = $decoded_json->{'value'};
  216. if ( ref( $data->{cmd_return} ) eq 'HASH'
  217. && exists $data->{cmd_return}->{sessionId} )
  218. {
  219. $data->{sessionId} = $data->{cmd_return}->{sessionId};
  220. }
  221. }
  222. }
  223. else {
  224. $data->{'cmd_return'} =
  225. 'Server returned status code '
  226. . $response->code
  227. . ' but no data';
  228. }
  229. return $data;
  230. }
  231. else {
  232. # No idea what the server is telling me, must be high
  233. $data->{'cmd_status'} = 'NOTOK';
  234. $data->{'cmd_return'} =
  235. 'Server returned status code '
  236. . $response->code
  237. . ' which I don\'t understand';
  238. return $data;
  239. }
  240. }
  241. }
  242. 1;
  243. __END__