RemoteConnection.pm 6.9 KB

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