Driver.pm 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. package Selenium::Remote::Driver;
  2. use strict;
  3. use warnings;
  4. use Selenium::Remote::RemoteConnection;
  5. # ABSTRACT: Perl Client for Selenium Remote Driver
  6. =for Pod::Coverage BUILD
  7. =for Pod::Coverage DEMOLISH
  8. =head1 SYNOPSIS
  9. use Selenium::Remote::Driver;
  10. my $driver = Selenium::Remote::Driver->new;
  11. $driver->get('http://www.google.com');
  12. print $driver->get_title();
  13. $driver->quit();
  14. =cut
  15. =head1 DESCRIPTION
  16. Selenium is a test tool that allows you to write
  17. automated web application UI tests in any programming language against
  18. any HTTP website using any mainstream JavaScript-enabled browser. This module is
  19. an implementation of the client for the Remote driver that Selenium provides.
  20. You can find bindings for other languages at this location:
  21. L<https://www.seleniumhq.org/download/>
  22. This module sends commands directly to the Server using HTTP. Using this module
  23. together with the Selenium Server, you can automatically control any supported
  24. browser. To use this module, you need to have already downloaded and started
  25. the Selenium Server (Selenium Server is a Java application).
  26. =cut
  27. =head1 USAGE
  28. =head2 Without Standalone Server
  29. As of v0.25, it's possible to use this module without a standalone
  30. server - that is, you would not need the JRE or the JDK to run your
  31. Selenium tests. See L<Selenium::Chrome>, L<Selenium::PhantomJS>,
  32. L<Selenium::Edge>, L<Selenium::InternetExplorer>,and L<Selenium::Firefox>
  33. for details. If you'd like additional browsers besides these,
  34. give us a holler over in
  35. L<Github|https://github.com/teodesian/Selenium-Remote-Driver/issues>.
  36. =head2 Remote Driver Response
  37. Selenium::Remote::Driver uses the
  38. L<JsonWireProtocol|https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol>
  39. And the
  40. L<WC3 WebDriver Protocol|https://www.w3.org/TR/webdriver/>
  41. to communicate with the Selenium Server. If an error occurs while
  42. executing the command then the server sends back an HTTP error code
  43. with a JSON encoded reponse that indicates the precise
  44. L<Response Error Code|https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#response-status-codes>.
  45. The module will then croak with the error message associated with this
  46. code. If no error occurred, then the subroutine called will return the
  47. value sent back from the server (if a return value was sent).
  48. So a rule of thumb while invoking methods on the driver is if the method did not
  49. croak when called, then you can safely assume the command was successful even if
  50. nothing was returned by the method.
  51. =head2 WebElement
  52. Selenium Webdriver represents all the HTML elements as WebElement, which is
  53. in turn represented by L<Selenium::Remote::WebElement> module. So any method that
  54. deals with WebElements will return and/or expect WebElement object. The POD for
  55. that module describes all the methods that perform various actions on the
  56. WebElements like click, submit etc.
  57. To interact with any WebElement you have to first "find" it, read the POD for
  58. find_element or find_elements for further info. Once you find the required element
  59. then you can perform various actions. If you don't call find_* method first, all
  60. your further actions will fail for that element. Finally, just remember that you
  61. don't have to instantiate WebElement objects at all - they will be automatically
  62. created when you use the find_* methods.
  63. A sub-class of Selenium::Remote::WebElement may be used instead of Selenium::Remote::WebElement,
  64. by providing that class name as an option the constructor:
  65. my $driver = Selenium::Remote::Driver->new( webelement_class => ... );
  66. For example, a testing-subclass may extend the web-element object with testing methods.
  67. =head2 LWP Read Timeout errors
  68. It's possible to make Selenium calls that take longer than the default
  69. L<LWP::UserAgent> timeout. For example, setting the asynchronous
  70. script timeout greater than the LWP::UserAgent timeout and then
  71. executing a long running asynchronous snippet of javascript will
  72. immediately trigger an error like:
  73. Error while executing command: executeAsyncScript: Server returned
  74. error message read timeout at...
  75. You can get around this by configuring LWP's timeout value, either by
  76. constructing your own LWP and passing it in to ::Driver during
  77. instantiation
  78. my $timeout_ua = LWP::UserAgent->new;
  79. $timeout_ua->timeout(360); # this value is in seconds!
  80. my $d = Selenium::Remote::Driver->new( ua => $timeout_ua );
  81. or by configuring the timeout on the fly as necessary:
  82. use feature qw/say/;
  83. use Selenium::Remote::Driver;
  84. my $d = Selenium::Remote::Driver->new;
  85. say $d->ua->timeout; # 180 seconds is the default
  86. $d->ua->timeout(2); # LWP wants seconds, not milliseconds!
  87. $d->set_timeout('script', 1000); # S::R::D wants milliseconds!
  88. # Async scripts only return when the callback is invoked. Since there
  89. # is no callback here, Selenium will block for the entire duration of
  90. # the async timeout script. This will hit Selenium's async script
  91. # timeout before hitting LWP::UserAgent's read timeout
  92. $d->execute_async_script('return "hello"');
  93. $d->quit;
  94. =head1 TESTING
  95. If are writing automated tests using this module, you may be
  96. interested in L<Test::Selenium::Remote::Driver> which is also included
  97. in this distribution. It includes convenience testing methods for many
  98. of the selenum methods available here.
  99. Your other option is to use this module in conjunction with your
  100. choice of testing modules, like L<Test::Spec> or L<Test::More> as
  101. you please.
  102. =head1 WC3 WEBDRIVER COMPATIBILITY
  103. WC3 Webdriver is a constantly evolving standard, so some things may or may not work at any given time.
  104. Furthermore, out of date drivers probably identify as WD3, while only implementing a few methods and retaining JSONWire functionality.
  105. One way of dealing with this is setting:
  106. $driver->{is_wd3} = 0
  107. Of course, this will prevent access of any new WC3 methods, but will probably make your tests pass until your browser's driver gets it's act together.
  108. There are also some JSONWire behaviors that we emulate in methods, such as Selenium::Remote::WebElement::get_attribute.
  109. You can get around that by passing an extra flag to the sub, or setting:
  110. $driver->{emulate_jsonwire} = 0;
  111. When in WC3 Webdriver mode.
  112. =head2 FINDERS
  113. This constant is a hashref map of the old element finder aliases from wd2 to wd3.
  114. use Data::Dumper;
  115. print Dumper($Selenium::Remote::Driver::FINDERS);
  116. =head2 WC3 WEBDRIVER CURRENT STATUS
  117. That said, the following 'sanity tests' in the at/ (acceptance test) directory of the module passed on the following versions:
  118. =over 4
  119. =item Selenium Server: 3.8.1 - all tests
  120. =item geckodriver: 0.19.1 - at/sanity.test, at/firefox.test (Selenium::Firefox)
  121. =item chromedriver: 2.35 - at/sanity-chrome.test, at/chrome.test (Selenium::Chrome)
  122. =item edgedriver: 5.16299 - at/sanity-edge.test
  123. =item InternetExplorerDriver : 3.8.1 - at/sanity-ie.test (be sure to enable 'allow local files to run active content in your 'advanced settings' pane)
  124. =item safaridriver : 11.0.2 - at/sanity-safari.test (be sure to enable 'allow automated testing' in the developer menu) -- it appears WC3 spec is *unimplemented*
  125. =back
  126. These tests are intended to be run directly against a working selenium server on the local host with said drivers configured.
  127. If you are curious as to what 'works and does not' on your driver versions (and a few other quirks),
  128. it is strongly encouraged you look at where the test calls the methods you are interested in.
  129. While other browsers/drivers (especially legacy ones) likely work fine as well,
  130. any new browser/driver will likely have problems if it's not listed above.
  131. There is also a 'legacy.test' file available to run against old browsers/selenium (2.x servers, pre geckodriver).
  132. This should only be used to verify backwards-compatibility has not been broken.
  133. =head2 Firefox Notes
  134. If you are intending to pass extra_capabilities to firefox on a WD3 enabled server with geckodriver, you MUST do the following:
  135. $Selenium::Remote::Driver::FORCE_WD3=1;
  136. This is because the gecko driver prefers legacy capabilities, both of which are normally passed for compatibility reasons.
  137. =head2 Chrome Notes
  138. Use the option goog:chromeOptions instead of chromeOptions, if you are intending to pass extra_capabilities on a
  139. WD3 enabled server with chromedriver enabled.
  140. https://sites.google.com/a/chromium.org/chromedriver/capabilities
  141. Also, if you instantiate the object in WC3 mode (which is the default), the remote driver will throw exceptions you have no choice but to catch,
  142. rather than falling back to JSONWire methods where applicable like geckodriver does.
  143. As of chrome 75 (and it's appropriate driver versions), the WC3 spec has finally been implemented.
  144. As such, to use chrome older than this, you will have to manually force on JSONWire mode:
  145. $Selenium::Remote::Driver::FORCE_WD2=1;
  146. =head2 Notes on Running Selenium at Scale via selenium.jar
  147. When running many, many tests in parallel you can eventually reach resource exhaustion.
  148. You have to instruct the Selenium JAR to do some cleanup to avoid explosions:
  149. Inside of your selenium server's node.json (running a grid), you would put in the following:
  150. "configuration" :
  151. {
  152. "cleanUpCycle":2000
  153. }
  154. Or run the selenium jar with the -cleanupCycle parameter. Of course use whatever # of seconds is appropriate to your situation.
  155. =head1 CONSTRUCTOR
  156. =head2 new
  157. Dies if communication with the selenium server cannot be established.
  158. Input: (all optional)
  159. Desired capabilities - HASH - Following options are accepted:
  160. =over 4
  161. =item B<remote_server_addr> - <string> - IP or FQDN of the Webdriver server machine. Default: 'localhost'
  162. =item B<port> - <string> - Port on which the Webdriver server is listening. Default: 4444
  163. =item B<browser_name> - <string> - desired browser string: {phantomjs|firefox|internet explorer|MicrosoftEdge|safari|htmlunit|iphone|chrome}
  164. =item B<version> - <string> - desired browser version number
  165. =item B<platform> - <string> - desired platform: {WINDOWS|XP|VISTA|MAC|LINUX|UNIX|ANY}
  166. =item B<accept_ssl_certs> - <boolean> - whether SSL certs should be accepted, default is true.
  167. =item B<firefox_profile> - Profile - Use Selenium::Firefox::Profile to create a Firefox profile for the browser to use.
  168. =item B<javascript> - <boolean> - Whether or not to use Javascript. You probably won't disable this, as you would be using L<WWW::Mechanize> instead. Default: True
  169. =item B<auto_close> - <boolean> - Whether to automatically close the browser session on the server when the object goes out of scope. Default: False.
  170. =item B<default_finder> - <string> - Default method by which to evaluate selectors. Default: 'xpath'
  171. =item B<session_id> - <string> - Provide a Session ID to highjack a browser session on the remote server. Useful for micro-optimizers. Default: undef
  172. =item B<pageLoadStrategy> - STRING - OPTIONAL, 'normal|eager|none'. default 'normal'. WebDriver3 only.
  173. =item B<extra_capabilities> - HASH - Any other extra capabilities. Accepted keys will vary by browser. If firefox_profile is passed, the args (or profile) key will be overwritten, depending on how it was passed.
  174. =item B<debug> - BOOL - Turn Debug mode on from the start if true, rather than having to call debug_on().
  175. =back
  176. On WebDriver3 the 'extra_capabilities' will be automatically converted into the parameter needed by your browser.
  177. For example, extra_capabilities is passed to the server as the moz:firefoxOptions parameter.
  178. You can also specify some options in the constructor hash that are
  179. not part of the browser-related desired capabilities.
  180. =over 4
  181. =item B<auto_close> - <boolean> - whether driver should end session on remote server on close.
  182. =item B<base_url> - <string> - OPTIONAL, base url for the website Selenium acts on. This can save you from repeating the domain in every call to $driver->get()
  183. =item B<default_finder> - <string> - choose default finder used for find_element* {class|class_name|css|id|link|link_text|name|partial_link_text|tag_name|xpath}
  184. =item B<inner_window_size> - <aref[Int]> - An array ref [ height, width ] that the browser window should use as its initial size immediately after instantiation
  185. =item B<error_handler> - CODEREF - A CODEREF that we will call in event of any exceptions. See L</error_handler> for more details.
  186. =item B<webelement_class> - <string> - sub-class of Selenium::Remote::WebElement if you wish to use an alternate WebElement class.
  187. =item B<ua> - LWP::UserAgent instance - if you wish to use a specific $ua, like from Test::LWP::UserAgent
  188. =item B<proxy> - HASH - Proxy configuration with the following keys:
  189. =over 4
  190. =item B<proxyType> - <string> - REQUIRED, Possible values are:
  191. direct - A direct connection - no proxy in use,
  192. manual - Manual proxy settings configured, e.g. setting a proxy for HTTP, a proxy for FTP, etc,
  193. pac - Proxy autoconfiguration from a URL,
  194. autodetect - proxy autodetection, probably with WPAD,
  195. system - Use system settings
  196. =item B<proxyAutoconfigUrl> - <string> - REQUIRED if proxyType is 'pac', ignored otherwise. Expected format: http://hostname.com:1234/pacfile or file:///path/to/pacfile
  197. =item B<ftpProxy> - <string> - OPTIONAL, ignored if proxyType is not 'manual'. Expected format: hostname.com:1234
  198. =item B<httpProxy> - <string> - OPTIONAL, ignored if proxyType is not 'manual'. Expected format: hostname.com:1234
  199. =item B<sslProxy> - <string> - OPTIONAL, ignored if proxyType is not 'manual'. Expected format: hostname.com:1234
  200. =item B<socksProxy> - <string> - OPTIONAL, ignored if proxyType is not 'manual'. Expected format: hostname.com:1234. WebDriver 3 only.
  201. =item B<socksVersion> - <int> - OPTIONAL, ignored if proxyType is not 'manual'. WebDriver 3 only.
  202. =item B<noProxy> - <ARRAY> - OPTIONAL, list of URLs to bypass the proxy for. WebDriver3 only.
  203. =item B<firefox_profile> - <string> - Base64 encoded ZIP file of a firefox profile directory, for use when you don't want/need Selenium::Firefox::Profile.
  204. =back
  205. =back
  206. Output:
  207. Selenium::Remote::Driver object
  208. Usage:
  209. my $driver = Selenium::Remote::Driver->new;
  210. #or
  211. my $driver = Selenium::Remote::Driver->new('browser_name' => 'firefox',
  212. 'platform' => 'MAC');
  213. #or (for Firefox 47 or lower on Selenium 3+)
  214. my $driver = Selenium::Remote::Driver->new('browser_name' => 'firefox',
  215. 'platform' => 'MAC',
  216. 'extra_capabilities' => {
  217. 'marionette' => \0,
  218. });
  219. #or
  220. my $driver = Selenium::Remote::Driver->new('remote_server_addr' => '10.10.1.1',
  221. 'port' => '2222',
  222. 'auto_close' => 0);
  223. #or
  224. my $driver = Selenium::Remote::Driver->new('browser_name' =>'chrome',
  225. 'extra_capabilities' => {
  226. 'goog:chromeOptions' => {
  227. 'args' => [
  228. 'window-size=1260,960',
  229. 'incognito'
  230. ],
  231. 'prefs' => {
  232. 'session' => {
  233. 'restore_on_startup' => 4,
  234. 'urls_to_restore_on_startup' => [
  235. 'http://www.google.com',
  236. 'http://docs.seleniumhq.org'
  237. ]},
  238. 'first_run_tabs' => [
  239. 'http://www.google.com',
  240. 'http://docs.seleniumhq.org'
  241. ]
  242. }
  243. }
  244. });
  245. #or
  246. my $driver = Selenium::Remote::Driver->new('proxy' => {'proxyType' => 'manual', 'httpProxy' => 'myproxy.com:1234'});
  247. #or
  248. my $driver = Selenium::Remote::Driver->new('default_finder' => 'css');
  249. =head3 error_handler
  250. =head3 clear_error_handler
  251. OPTIONAL constructor arg & associated setter/clearer: if you wish to
  252. install your own error handler, you may pass a code ref in to
  253. C<error_handler> during instantiation like follows:
  254. my $driver = Selenium::Remote::Driver->new(
  255. error_handler => sub { print $_[1]; croak 'goodbye'; }
  256. );
  257. Additionally, you can set and/or clear it at any time on an
  258. already-instantiated driver:
  259. # later, change the error handler to something else
  260. $driver->error_handler( sub { print $_[1]; croak 'hello'; } );
  261. # stop handling errors manually and use the default S:R:D behavior
  262. # (we will croak about the exception)
  263. $driver->clear_error_handler;
  264. Your error handler will receive three arguments: the first argument is
  265. the C<$driver> object itself, and the second argument is the exception
  266. message and stack trace in one multiline string. The final argument(s) are the
  267. argument array to the command just executed.
  268. B<N.B.>: If you set your own error handler, you are entirely
  269. responsible for handling webdriver exceptions, _including_ croaking
  270. behavior. That is, when you set an error handler, we will no longer
  271. croak on Webdriver exceptions - it's up to you to do so. For
  272. consistency with the standard S:R:D behavior, we recommend your error
  273. handler also croak when it's done, especially since your test
  274. shouldn't be running into unexpected errors. Catching specific or
  275. desired errors in your error handler makes sense, but not croaking at
  276. all can leave you in unfamiliar territory. Reaching an unexpected
  277. exception might mean your test has gone off the rails, and the further
  278. your test gets from the source of the of the exception, the harder it
  279. will be to debug.
  280. B<N.B.>: Four methods will still croak on their own: L</find_element>,
  281. L</find_elements>, L</find_child_element>, and
  282. L</find_child_elements>. If these methods throw a Webdriver Exception,
  283. your error handler _will still be_ invoked inside an C<eval>, and then
  284. they'll croak with their own error message that indicates the locator
  285. and strategy used. So, your strategies for avoiding exceptions when
  286. finding elements do not change (either use find_elements and check
  287. the returned array size, wrap your calls to find_element* in an
  288. C<eval>, or use the parameterized versions find_element_*).
  289. =head2 new_from_caps
  290. Description:
  291. For experienced users who want complete control over the desired
  292. capabilities, use this alternative constructor along with the
  293. C<desired_capabilities> hash key in the init hash. Unlike "new",
  294. this constructor will not assume any defaults for your desired
  295. capabilities.
  296. This alternate constructor IGNORES all other browser-related
  297. desiredCapability options; the only options that will be respected
  298. are those that are NOT part of the Capabilities JSON Object as
  299. described in the Json Wire Protocol.
  300. Input:
  301. The only respected keys in the input hash are:
  302. desired_capabilities - HASHREF - defaults to {}
  303. remote_server_addr - STRING - defaults to localhost
  304. port - INTEGER - defaults to 4444
  305. default_finder - STRING - defaults to xpath
  306. webelement_class - STRING - defaults to Selenium::Remote::WebElement
  307. auto_close - BOOLEAN - defaults to 1
  308. error_handler - CODEREF - defaults to croaking on exceptions
  309. Except for C<desired_capabilities>, these keys perform exactly the
  310. same as listed in the regular "new" constructor.
  311. The hashref you pass in as desired_capabilities only gets json
  312. encoded before being passed to the Selenium server; no default
  313. options of any sort will be added.
  314. This means you must handle normalization and casing of the input
  315. options (like "browser_name" vs "browserName") and take care of
  316. things like encoding the firefox profile if applicable. More
  317. information about the desired capabilities object is available on
  318. the Selenium wiki:
  319. https://code.google.com/p/selenium/wiki/JsonWireProtocol#Capabilities_JSON_Object
  320. Output:
  321. Remote Driver object
  322. Usage:
  323. my $driver = Selenium::Remote::Driver->new_from_caps(
  324. 'desired_capabilities' => {'browserName' => 'firefox'}
  325. );
  326. The above would generate a POST to the webdriver server at
  327. localhost:4444 with the exact payload of '{"desiredCapabilities":
  328. {"browserName": "firefox" }}'.
  329. =for Pod::Coverage has_base_url
  330. =for Pod::Coverage has_desired_capabilities
  331. =for Pod::Coverage has_error_handler
  332. =for Pod::Coverage has_firefox_profile
  333. =for Pod::Coverage has_inner_window_size
  334. =for Pod::Coverage has_javascript
  335. =for Pod::Coverage has_port
  336. =for Pod::Coverage has_remote_server_addr
  337. =cut
  338. sub new {
  339. my ( $class, %opts ) = @_;
  340. my $conn = Selenium::Remote::RemoteConnection->new(
  341. remote_server_addr => $opts{remote_server_addr},
  342. port => $opts{port},
  343. );
  344. $conn->check_status();
  345. if( $opts{'force_version'} eq '4' || ( $conn->{'version'} && $conn->{'version'} == 4 ) ) {
  346. require Selenium::Remote::Driver::v4;
  347. return Selenium::Remote::Driver::v4->new(
  348. 'port' => $opts{port},
  349. 'host' => $opts{remote_server_addr},
  350. 'browser' => $opts{browser_name},
  351. 'debug' => $opts{debug},
  352. );
  353. }
  354. require Selenium::Remote::Driver::v3;
  355. return Selenium::Remote::Driver::v3->new(%opts);
  356. }
  357. 1;