Driver.pm 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874
  1. package Test::Selenium::Remote::Driver;
  2. # ABSTRACT: Useful testing subclass for Selenium::Remote::Driver
  3. use Moo;
  4. use Test::Selenium::Remote::WebElement;
  5. use Test::LongString;
  6. use IO::Socket;
  7. use Sub::Install;
  8. use Try::Tiny;
  9. extends 'Selenium::Remote::Driver';
  10. # move_mouse_to_location_ok # TODO # move_to_ok # TODO
  11. has func_list => (
  12. is => 'lazy',
  13. builder => sub {
  14. return [
  15. 'alert_text_is', 'alert_text_isnt',
  16. 'alert_text_like', 'alert_text_unlike',
  17. 'current_window_handle_is', 'current_window_handle_isnt',
  18. 'current_window_handle_like', 'current_window_handle_unlike',
  19. 'window_handles_is', 'window_handles_isnt',
  20. 'window_handles_like', 'window_handles_unlike',
  21. 'window_size_is', 'window_size_isnt',
  22. 'window_size_like', 'window_size_unlike',
  23. 'window_position_is', 'window_position_isnt',
  24. 'window_position_like', 'window_position_unlike',
  25. 'current_url_is', 'current_url_isnt',
  26. 'current_url_like', 'current_url_unlike',
  27. 'title_is', 'title_isnt',
  28. 'title_like', 'title_unlike',
  29. 'active_element_is', 'active_element_isnt',
  30. 'active_element_like', 'active_element_unlike',
  31. 'send_keys_to_active_element_ok', 'send_keys_to_alert_ok',
  32. 'send_keys_to_prompt_ok', 'send_modifier_ok',
  33. 'accept_alert_ok', 'dismiss_alert_ok',
  34. 'get_ok', 'go_back_ok',
  35. 'go_forward_ok', 'add_cookie_ok',
  36. 'get_page_source_ok', 'find_element_ok',
  37. 'find_elements_ok', 'find_child_element_ok',
  38. 'find_child_elements_ok', 'find_no_element_ok',
  39. 'compare_elements_ok', 'click_ok',
  40. 'double_click_ok', 'body_like',
  41. ];
  42. },
  43. );
  44. =for Pod::Coverage has_args
  45. =cut
  46. sub has_args {
  47. my $self = shift;
  48. my $fun_name = shift;
  49. my $hash_fun_args = {
  50. 'find_element' => 2,
  51. 'find_no_element' => 2,
  52. 'find_child_element' => 3,
  53. 'find_child_elements' => 3,
  54. 'find_element' => 2,
  55. 'find_elements' => 2,
  56. 'compare_elements' => 2,
  57. 'get' => 1,
  58. };
  59. return ( $hash_fun_args->{$fun_name} // 0 );
  60. }
  61. with 'Test::Selenium::Remote::Role::DoesTesting';
  62. =for Pod::Coverage BUILD
  63. =cut
  64. sub BUILD {
  65. my $self = shift;
  66. foreach my $method_name ( @{ $self->func_list } ) {
  67. unless ( defined( __PACKAGE__->can($method_name) ) ) {
  68. my $sub = $self->_build_sub($method_name);
  69. Sub::Install::install_sub(
  70. {
  71. code => $sub,
  72. into => __PACKAGE__,
  73. as => $method_name
  74. }
  75. );
  76. }
  77. }
  78. }
  79. =head1 NAME
  80. Test::Selenium::Remote::Driver - add testing methods to L<Selenium::Remote::Driver>
  81. =head1 DESCRIPTION
  82. A subclass of L<Selenium::Remote::Driver> which provides useful testing
  83. methods.
  84. This is an I<experimental> addition to the Selenium::Remote::Driver
  85. distribution. Some interfaces may change.
  86. =head1 Methods
  87. =head2 new ( %opts )
  88. This will create a new Test::Selenium::Remote::Driver object, which subclasses
  89. L<Selenium::Remote::Driver>. This subclass provides useful testing
  90. functions. It is modeled on L<Test::WWW::Selenium>.
  91. Environment vars can be used to specify options to pass to
  92. L<Selenium::Remote::Driver>. ENV vars are prefixed with C<TWD_>.
  93. ( After the old fork name, "Test::WebDriver" ). The explicity passed
  94. options have precedence. ENV vars take only effect when they are
  95. actually set. This important e.g. for the option C<javascript>, which
  96. is turned on per default in L<Selenium::Remote::Driver>.
  97. Set the Selenium server address with C<$TWD_HOST> and C<$TWD_PORT>.
  98. Pick which browser is used using the C<$TWD_BROWSER>, C<$TWD_VERSION>,
  99. C<$TWD_PLATFORM>, C<$TWD_JAVASCRIPT>, C<$TWD_EXTRA_CAPABILITIES>.
  100. C<$TWD_BROWSER> is actually an alias for C<$TWD_BROWSER_NAME>.
  101. C<$TWD_HOST> is actually an alias for C<$TWD_REMOTE_SERVER_ADDR>.
  102. The aliases habe lower precedence than the original values.
  103. See L<Selenium::Remote::Driver> for the meanings of these options.
  104. =for Pod::Coverage BUILDARGS
  105. =cut
  106. sub BUILDARGS {
  107. my ( undef, %p ) = @_;
  108. OPT:
  109. for my $opt (
  110. qw/remote_server_addr port browser_name version platform
  111. javascript auto_close extra_capabilities/
  112. )
  113. {
  114. my $env_var_name = 'TWD_' . uc($opt);
  115. next OPT unless exists $ENV{$env_var_name};
  116. $p{$opt} //= $ENV{$env_var_name};
  117. }
  118. $p{browser_name} //= $ENV{TWD_BROWSER} if exists $ENV{TWD_BROWSER}; # ykwim
  119. $p{remote_server_addr} //= $ENV{TWD_HOST} if exists $ENV{TWD_HOST}; # ykwim
  120. $p{webelement_class} //= 'Test::Selenium::Remote::WebElement';
  121. return \%p;
  122. }
  123. =head2 verbose
  124. Enable/disable debugging output, or view the status of verbosity.
  125. =cut
  126. has verbose => ( is => 'rw', );
  127. =head2 server_is_running( $host, $port )
  128. Returns true if a Selenium server is running. The host and port
  129. parameters are optional, and they default to C<localhost:4444>.
  130. The environment vars C<TWD_HOST> and C<TWD_PORT> can also be used to
  131. determine which server should be checked.
  132. =cut
  133. sub server_is_running {
  134. my $host = $ENV{TWD_HOST} || shift || 'localhost';
  135. my $port = $ENV{TWD_PORT} || shift || 4444;
  136. return ( $host, $port )
  137. if IO::Socket::INET->new(
  138. PeerAddr => $host,
  139. PeerPort => $port,
  140. );
  141. return;
  142. }
  143. =head2 error_handler
  144. As for L<Selenium::Remote::Driver>, this class also supports adding an
  145. optional C<error_handler> attribute during instantiation :
  146. my $test_driver = Test::Selenium::Remote::Driver->new(
  147. error_handler => sub { print $_[1]; croak 'goodbye'; }
  148. );
  149. Additionally, you can set and/or clear it at any time on an
  150. already-instantiated driver:
  151. # later, change the error handler to something else
  152. $driver->error_handler( sub { print $_[1]; croak 'hello'; } );
  153. # stop handling errors manually and use the default S:R:D behavior
  154. # (we will croak about the exception)
  155. $driver->clear_error_handler;
  156. Your error handler will receive two arguments,
  157. The first argument is the C<$driver> object itself.
  158. Due to some specificities of this class, the second argument passed to the
  159. handler can be:
  160. =over
  161. =item the error message from the Webdriver
  162. This is the case when the error message is raised by a WebDriver failure
  163. =item "Failed to find ..."
  164. This message is raised when the Webdriver call is successful but the failure
  165. occurs on the test performed aftwerwards. This is the case for functions like
  166. C<body_text_like>, C<body_text_unlike>, C<body_text_contains>, C<body_text_lacks>,
  167. C<content_like>, C<content_unlike>, C<content_contains>, C<content_lacks>.
  168. =back
  169. If you set your own handler, you should not rely that much on the message returned.
  170. You should also remember that you are entirely responsible for handling exceptions,
  171. which means that should the error handler be called, it means that the test you are
  172. doing has failed, so you should croak.
  173. You should also call fail() in your handler, in case the function called raised a
  174. webdriver error, because, as exceptions are not caught anymore when you specify a
  175. handler, the function will not fail anymore, which translates to a 'ok' in your TAP
  176. output if you do not handle it properly.
  177. =head1 Testing Methods
  178. The following testing methods are available.
  179. For more documentation, see the related methods in L<Selenium::Remote::Driver>.
  180. (And feel free to submit a patch to flesh out the documentation for these here).
  181. Defaults for optional arguments B<should> be the same as for their analogues in
  182. L<Selenium::Remote::Driver>.
  183. alert_text_is
  184. alert_text_isnt
  185. alert_text_like
  186. alert_text_unlike
  187. current_window_handle_is
  188. current_window_handle_isnt
  189. current_window_handle_like
  190. current_window_handle_unlike
  191. window_handles_is
  192. window_handles_isnt
  193. window_handles_like
  194. window_handles_unlike
  195. window_size_is
  196. window_size_isnt
  197. window_size_like
  198. window_size_unlike
  199. window_position_is
  200. window_position_isnt
  201. window_position_like
  202. window_position_unlike
  203. current_url_is
  204. current_url_isnt
  205. current_url_like
  206. current_url_unlike
  207. title_is
  208. title_isnt
  209. title_like
  210. title_unlike
  211. active_element_is
  212. active_element_isnt
  213. active_element_like
  214. active_element_unlike
  215. # Basically the same as 'content_like()', but content_like() supports multiple regex's.
  216. page_source_is
  217. page_source_isnt
  218. page_source_like
  219. page_source_unlike
  220. send_keys_to_active_element_ok
  221. send_keys_to_alert_ok
  222. send_keys_to_prompt_ok
  223. send_modifier_ok
  224. accept_alert_ok
  225. dismiss_alert_ok
  226. move_mouse_to_location_ok # TODO
  227. move_to_ok # TODO
  228. get_ok
  229. go_back_ok
  230. go_forward_ok
  231. add_cookie_ok
  232. get_page_source_ok
  233. find_element_ok($search_target)
  234. find_element_ok($search_target)
  235. find_elements_ok
  236. find_child_element_ok
  237. find_child_elements_ok
  238. compare_elements_ok
  239. click_ok
  240. double_click_ok
  241. =cut
  242. # function composing a find_element with locator with a webelement test
  243. sub _find_element_with_action {
  244. my $self = shift;
  245. my $method = shift;
  246. my ( $locator, $locator_strategy, $params, $desc ) = @_;
  247. $locator_strategy //= 'xpath';
  248. # case 4 args
  249. if ($desc) {
  250. $self->croak('Invalid locator strategy')
  251. unless ( $self->FINDERS->{$locator_strategy} );
  252. }
  253. else {
  254. if ($params) {
  255. # means that we called it the 'old way' (no locator strategy)
  256. if ( !defined( $self->FINDERS->{$locator_strategy} ) ) {
  257. $desc = $params;
  258. $params = $locator_strategy;
  259. $locator_strategy =
  260. $self->_get_finder_key( $self->default_finder );
  261. }
  262. }
  263. else {
  264. # means it was called with no locator strategy and no desc
  265. if ($locator_strategy) {
  266. if ( !defined( $self->FINDERS->{$locator_strategy} ) ) {
  267. $params = $locator_strategy;
  268. $locator_strategy =
  269. $self->_get_finder_key( $self->default_finder );
  270. }
  271. }
  272. else {
  273. $self->croak('Not enough arguments');
  274. }
  275. }
  276. }
  277. unless ($desc) {
  278. $desc = $method;
  279. $desc .= "'" . join( " ", ( $params // '' ) ) . "'";
  280. }
  281. my $element;
  282. eval { $element = $self->find_element( $locator, $locator_strategy ); };
  283. if ($@) {
  284. print "# Error: $@\n";
  285. return 0;
  286. }
  287. return $element->$method( $params, $desc );
  288. }
  289. =head2 $twd->type_element_ok($search_target [,$locator], $keys, [, $desc ]);
  290. $twd->type_element_ok( $search_target [,$locator], $keys [, $desc ] );
  291. Use L<Selenium::Remote::Driver/find_element> to resolve the C<$search_target>
  292. to a web element and an optional locator, and then type C<$keys> into it, providing an optional test
  293. label.
  294. =cut
  295. sub type_element_ok {
  296. my $self = shift;
  297. my $method = 'send_keys_ok';
  298. return $self->_find_element_with_action( $method, @_ );
  299. }
  300. =head2 $twd->element_text_is($search_target[,$finder],$expected_text [,$desc]);
  301. $twd->element_text_is($search_target[,$finder],$expected_text [,$desc]);
  302. =cut
  303. sub element_text_is {
  304. my $self = shift;
  305. my $method = 'text_is';
  306. return $self->_find_element_with_action( $method, @_ );
  307. }
  308. =head2 $twd->element_value_is($search_target[,$finder],$expected_value [,$desc]);
  309. $twd->element_value_is($search_target[,$finder],$expected_value [,$desc]);
  310. =cut
  311. sub element_value_is {
  312. my $self = shift;
  313. my $method = 'value_is';
  314. return $self->_find_element_with_action( $method, @_ );
  315. }
  316. =head2 $twd->click_element_ok($search_target [,$finder ,$desc]);
  317. $twd->click_element_ok($search_target [,$finder ,$desc]);
  318. Find an element and then click on it.
  319. =cut
  320. sub click_element_ok {
  321. my $self = shift;
  322. my $method = 'click_ok';
  323. return $self->_find_element_with_action( $method, @_ );
  324. }
  325. =head2 $twd->clear_element_ok($search_target [,$finder ,$desc]);
  326. $twd->clear_element_ok($search_target [,$finder ,$desc]);
  327. Find an element and then clear on it.
  328. =cut
  329. sub clear_element_ok {
  330. my $self = shift;
  331. my $method = 'clear_ok';
  332. return $self->_find_element_with_action( $method, @_ );
  333. }
  334. =head2 $twd->is_element_displayed_ok($search_target [,$finder ,$desc]);
  335. $twd->is_element_displayed_ok($search_target [,$finder ,$desc]);
  336. Find an element and check to confirm that it is displayed. (visible)
  337. =cut
  338. sub is_element_displayed_ok {
  339. my $self = shift;
  340. my $method = 'is_displayed_ok';
  341. return $self->_find_element_with_action( $method, @_ );
  342. }
  343. =head2 $twd->is_element_enabled_ok($search_target [,$finder ,$desc]);
  344. $twd->is_element_enabled_ok($search_target [,$finder ,$desc]);
  345. Find an element and check to confirm that it is enabled.
  346. =cut
  347. sub is_element_enabled_ok {
  348. my $self = shift;
  349. my $method = 'is_enabled_ok';
  350. return $self->_find_element_with_action( $method, @_ );
  351. }
  352. =head2 $twd->find_element_ok($search_target [,$finder, $desc ]);
  353. $twd->find_element_ok( $search_target [,$finder, $desc ] );
  354. Returns true if C<$search_target> is successfully found on the page. C<$search_target>
  355. is passed to L<Selenium::Remote::Driver/find_element> using a finder or the C<default_finder>
  356. if none passed.
  357. See there for more details on the format for C<find_element_ok()>.
  358. =head2 $twd->find_no_element_ok($search_target [,$finder, $desc ]);
  359. $twd->find_no_element_ok( $search_target [,$finder, $desc ] );
  360. Returns true if C<$search_target> is I<not> found on the page. C<$search_target>
  361. is passed to L<Selenium::Remote::Driver/find_element> using a finder or the
  362. C<default_finder> if none passed. See there for more details on the format for C<find_no_element_ok()>.
  363. =head2 $twd->content_like( $regex [, $desc ] )
  364. $twd->content_like( $regex [, $desc ] )
  365. $twd->content_like( [$regex_1, $regex_2] [, $desc ] )
  366. Tells if the content of the page matches I<$regex>. If an arrayref of regex's
  367. are provided, one 'test' is run for each regex against the content of the
  368. current page.
  369. A default description of 'Content is like "$regex"' will be provided if there
  370. is no description.
  371. =cut
  372. sub content_like {
  373. my $self = shift;
  374. my $regex = shift;
  375. my $desc = shift;
  376. local $Test::Builder::Level = $Test::Builder::Level + 1;
  377. my $content = $self->get_page_source();
  378. my $ret;
  379. if ( not ref $regex eq 'ARRAY' ) {
  380. $desc = qq{Content is like "$regex"} if ( not defined $desc );
  381. $ret = like_string( $content, $regex, $desc );
  382. if ( !$ret && $self->has_error_handler ) {
  383. $self->error_handler->( $self, "Failed to find $regex" );
  384. }
  385. return $ret;
  386. }
  387. elsif ( ref $regex eq 'ARRAY' ) {
  388. for my $re (@$regex) {
  389. $desc = qq{Content is like "$re"} if ( not defined $desc );
  390. $ret = like_string( $content, $re, $desc );
  391. if ( !$ret && $self->has_error_handler ) {
  392. $self->error_handler->( $self, "Failed to find $re" );
  393. }
  394. }
  395. }
  396. }
  397. =head2 $twd->content_unlike( $regex [, $desc ] )
  398. $twd->content_unlike( $regex [, $desc ] )
  399. $twd->content_unlike( [$regex_1, $regex_2] [, $desc ] )
  400. Tells if the content of the page does NOT match I<$regex>. If an arrayref of regex's
  401. are provided, one 'test' is run for each regex against the content of the
  402. current page.
  403. A default description of 'Content is unlike "$regex"' will be provided if there
  404. is no description.
  405. =cut
  406. sub content_unlike {
  407. my $self = shift;
  408. my $regex = shift;
  409. my $desc = shift;
  410. local $Test::Builder::Level = $Test::Builder::Level + 1;
  411. my $content = $self->get_page_source();
  412. my $ret;
  413. if ( not ref $regex eq 'ARRAY' ) {
  414. $desc = qq{Content is unlike "$regex"} if ( not defined $desc );
  415. $ret = unlike_string( $content, $regex, $desc );
  416. if ( !$ret && $self->has_error_handler ) {
  417. $self->error_handler->( $self, "Failed to find $regex" );
  418. }
  419. }
  420. elsif ( ref $regex eq 'ARRAY' ) {
  421. for my $re (@$regex) {
  422. $desc = qq{Content is unlike "$re"} if ( not defined $desc );
  423. $ret = unlike_string( $content, $re, $desc );
  424. if ( !$ret && $self->has_error_handler ) {
  425. $self->error_handler->( $self, "Failed to find $re" );
  426. }
  427. }
  428. }
  429. }
  430. =head2 $twd->body_text_like( $regex [, $desc ] )
  431. $twd->body_text_like( $regex [, $desc ] )
  432. $twd->body_text_like( [$regex_1, $regex_2] [, $desc ] )
  433. Tells if the text of the page (as returned by C<< get_body() >>) matches
  434. I<$regex>. If an arrayref of regex's are provided, one 'test' is run for each
  435. regex against the text of the current page.
  436. A default description of 'Text is like "$regex"' will be provided if there
  437. is no description.
  438. To also match the HTML, see C<< content_unlike() >>.
  439. =cut
  440. sub body_text_like {
  441. my $self = shift;
  442. my $regex = shift;
  443. my $desc = shift;
  444. local $Test::Builder::Level = $Test::Builder::Level + 1;
  445. my $text = $self->get_body();
  446. my $ret;
  447. if ( not ref $regex eq 'ARRAY' ) {
  448. $desc = qq{Text is like "$regex"} if ( not defined $desc );
  449. $ret = like_string( $text, $regex, $desc );
  450. if ( !$ret && $self->has_error_handler ) {
  451. $self->error_handler->( $self, "Failed to find $regex" );
  452. }
  453. return $ret;
  454. }
  455. elsif ( ref $regex eq 'ARRAY' ) {
  456. for my $re (@$regex) {
  457. $desc = qq{Text is like "$re"} if ( not defined $desc );
  458. $ret = like_string( $text, $re, $desc );
  459. if ( !$ret && $self->has_error_handler ) {
  460. $self->error_handler->( $self, "Failed to find $re" );
  461. }
  462. }
  463. }
  464. }
  465. =head2 $twd->body_text_unlike( $regex [, $desc ] )
  466. $twd->body_text_unlike( $regex [, $desc ] )
  467. $twd->body_text_unlike( [$regex_1, $regex_2] [, $desc ] )
  468. Tells if the text of the page (as returned by C<< get_body() >>)
  469. does NOT match I<$regex>. If an arrayref of regex's
  470. are provided, one 'test' is run for each regex against the text of the
  471. current page.
  472. A default description of 'Text is unlike "$regex"' will be provided if there
  473. is no description.
  474. To also match the HTML, see C<< content_unlike() >>.
  475. =cut
  476. sub body_text_unlike {
  477. my $self = shift;
  478. my $regex = shift;
  479. my $desc = shift;
  480. local $Test::Builder::Level = $Test::Builder::Level + 1;
  481. my $text = $self->get_body();
  482. my $ret;
  483. if ( not ref $regex eq 'ARRAY' ) {
  484. $desc = qq{Text is unlike "$regex"} if ( not defined $desc );
  485. $ret = unlike_string( $text, $regex, $desc );
  486. if ( !$ret && $self->has_error_handler ) {
  487. $self->error_handler->( $self, "Failed to find $regex" );
  488. }
  489. return $ret;
  490. }
  491. elsif ( ref $regex eq 'ARRAY' ) {
  492. for my $re (@$regex) {
  493. $desc = qq{Text is unlike "$re"} if ( not defined $desc );
  494. $ret = unlike_string( $text, $re, $desc );
  495. if ( !$ret && $self->has_error_handler ) {
  496. $self->error_handler->( $self, "Failed to find $re" );
  497. }
  498. }
  499. }
  500. }
  501. #####
  502. =head2 $twd->content_contains( $str [, $desc ] )
  503. $twd->content_contains( $str [, $desc ] )
  504. $twd->content_contains( [$str_1, $str_2] [, $desc ] )
  505. Tells if the content of the page contains I<$str>. If an arrayref of strings
  506. are provided, one 'test' is run for each string against the content of the
  507. current page.
  508. A default description of 'Content contains "$str"' will be provided if there
  509. is no description.
  510. =cut
  511. sub content_contains {
  512. my $self = shift;
  513. my $str = shift;
  514. my $desc = shift;
  515. local $Test::Builder::Level = $Test::Builder::Level + 1;
  516. my $content = $self->get_page_source();
  517. my $ret;
  518. if ( not ref $str eq 'ARRAY' ) {
  519. $desc = qq{Content contains "$str"} if ( not defined $desc );
  520. $ret = contains_string( $content, $str, $desc );
  521. if ( !$ret && $self->has_error_handler ) {
  522. $self->error_handler->( $self, "Failed to find $str" );
  523. }
  524. return $ret;
  525. }
  526. elsif ( ref $str eq 'ARRAY' ) {
  527. for my $s (@$str) {
  528. $desc = qq{Content contains "$s"} if ( not defined $desc );
  529. $ret = contains_string( $content, $s, $desc );
  530. if ( !$ret && $self->has_error_handler ) {
  531. $self->error_handler->( $self, "Failed to find $s" );
  532. }
  533. }
  534. }
  535. }
  536. =head2 $twd->content_lacks( $str [, $desc ] )
  537. $twd->content_lacks( $str [, $desc ] )
  538. $twd->content_lacks( [$str_1, $str_2] [, $desc ] )
  539. Tells if the content of the page does NOT contain I<$str>. If an arrayref of strings
  540. are provided, one 'test' is run for each string against the content of the
  541. current page.
  542. A default description of 'Content lacks "$str"' will be provided if there
  543. is no description.
  544. =cut
  545. sub content_lacks {
  546. my $self = shift;
  547. my $str = shift;
  548. my $desc = shift;
  549. local $Test::Builder::Level = $Test::Builder::Level + 1;
  550. my $content = $self->get_page_source();
  551. my $ret;
  552. if ( not ref $str eq 'ARRAY' ) {
  553. $desc = qq{Content lacks "$str"} if ( not defined $desc );
  554. $ret = lacks_string( $content, $str, $desc );
  555. if ( !$ret && $self->has_error_handler ) {
  556. $self->error_handler->( $self, "Failed to find $str" );
  557. }
  558. return $ret;
  559. }
  560. elsif ( ref $str eq 'ARRAY' ) {
  561. for my $s (@$str) {
  562. $desc = qq{Content lacks "$s"} if ( not defined $desc );
  563. $ret = lacks_string( $content, $s, $desc );
  564. if ( !$ret && $self->has_error_handler ) {
  565. $self->error_handler->( $self, "Failed to find $s" );
  566. }
  567. }
  568. }
  569. }
  570. =head2 $twd->body_text_contains( $str [, $desc ] )
  571. $twd->body_text_contains( $str [, $desc ] )
  572. $twd->body_text_contains( [$str_1, $str_2] [, $desc ] )
  573. Tells if the text of the page (as returned by C<< get_body() >>) contains
  574. I<$str>. If an arrayref of strings are provided, one 'test' is run for each
  575. string against the text of the current page.
  576. A default description of 'Text contains "$str"' will be provided if there
  577. is no description.
  578. To also match the HTML, see C<< content_lacks() >>.
  579. =cut
  580. sub body_text_contains {
  581. my $self = shift;
  582. my $str = shift;
  583. my $desc = shift;
  584. local $Test::Builder::Level = $Test::Builder::Level + 1;
  585. my $text = $self->get_body();
  586. my $ret;
  587. if ( not ref $str eq 'ARRAY' ) {
  588. $desc = qq{Text contains "$str"} if ( not defined $desc );
  589. $ret = contains_string( $text, $str, $desc );
  590. if ( !$ret && $self->has_error_handler ) {
  591. $self->error_handler->( $self, "Failed to find $str" );
  592. }
  593. return $ret;
  594. }
  595. elsif ( ref $str eq 'ARRAY' ) {
  596. for my $s (@$str) {
  597. $desc = qq{Text contains "$s"} if ( not defined $desc );
  598. $ret = contains_string( $text, $s, $desc );
  599. if ( !$ret && $self->has_error_handler ) {
  600. $self->error_handler->( $self, "Failed to find $s" );
  601. }
  602. }
  603. }
  604. }
  605. =head2 $twd->body_text_lacks( $str [, $desc ] )
  606. $twd->body_text_lacks( $str [, $desc ] )
  607. $twd->body_text_lacks( [$str_1, $str_2] [, $desc ] )
  608. Tells if the text of the page (as returned by C<< get_body() >>)
  609. does NOT contain I<$str>. If an arrayref of strings
  610. are provided, one 'test' is run for each string against the content of the
  611. current page.
  612. A default description of 'Text lacks "$str"' will be provided if there
  613. is no description.
  614. To also match the HTML, see C<< content_lacks() >>.
  615. =cut
  616. sub body_text_lacks {
  617. my $self = shift;
  618. my $str = shift;
  619. my $desc = shift;
  620. local $Test::Builder::Level = $Test::Builder::Level + 1;
  621. my $text = $self->get_body();
  622. my $ret;
  623. if ( not ref $str eq 'ARRAY' ) {
  624. $desc = qq{Text lacks "$str"} if ( not defined $desc );
  625. $ret = lacks_string( $text, $str, $desc );
  626. if ( !$ret && $self->has_error_handler ) {
  627. $self->error_handler->( $self, "Failed to find $str" );
  628. }
  629. return $ret;
  630. }
  631. elsif ( ref $str eq 'ARRAY' ) {
  632. for my $s (@$str) {
  633. $desc = qq{Text lacks "$s"} if ( not defined $desc );
  634. $ret = lacks_string( $text, $s, $desc );
  635. if ( !$ret && $self->has_error_handler ) {
  636. $self->error_handler->( $self, "Failed to find $s" );
  637. }
  638. }
  639. }
  640. }
  641. 1;
  642. __END__
  643. =head1 NOTES
  644. This module was forked from Test::WebDriver 0.01.
  645. For Best Practice - I recommend subclassing Test::Selenium::Remote::Driver for your application,
  646. and then refactoring common or app specific methods into MyApp::WebDriver so that
  647. your test files do not have much duplication. As your app changes, you can update
  648. MyApp::WebDriver rather than all the individual test files.
  649. =head1 AUTHORS
  650. =over 4
  651. =item *
  652. Created by: Luke Closs <lukec@cpan.org>, but inspired by
  653. L<Test::WWW::Selenium> and its authors.
  654. =back
  655. =head1 CONTRIBUTORS
  656. Test::WebDriver work was sponsored by Prime Radiant, Inc.
  657. Mark Stosberg <mark@stosberg.com> forked it as Test::Selenium::Remote::Driver
  658. and significantly expanded it.
  659. =head1 COPYRIGHT AND LICENSE
  660. Parts Copyright (c) 2012 Prime Radiant, Inc.
  661. This program is free software; you can redistribute it and/or
  662. modify it under the same terms as Perl itself.