Driver.pm 24 KB

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