RemoteConnection.pm 7.5 KB

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