File Coverage

blib/lib/Test/WWW/Mechanize.pm
Criterion Covered Total %
statement 627 822 76.2
branch 179 288 62.1
condition 44 91 48.3
subroutine 73 91 80.2
pod 61 61 100.0
total 984 1353 72.7


line stmt bran cond sub pod time code
1             package Test::WWW::Mechanize;
2              
3 33     33   2785996 use strict;
  33         305  
  33         968  
4 33     33   181 use warnings;
  33         73  
  33         793  
5 33     33   685 use 5.010;
  33         117  
6              
7             =head1 NAME
8              
9             Test::WWW::Mechanize - Testing-specific WWW::Mechanize subclass
10              
11             =head1 VERSION
12              
13             Version 1.60
14              
15             =cut
16              
17             our $VERSION = '1.60';
18              
19             =head1 SYNOPSIS
20              
21             Test::WWW::Mechanize is a subclass of L that incorporates
22             features for web application testing. For example:
23              
24             use Test::More tests => 5;
25             use Test::WWW::Mechanize;
26              
27             my $mech = Test::WWW::Mechanize->new;
28             $mech->get_ok( $page );
29             $mech->base_is( 'http://petdance.com/', 'Proper ' );
30             $mech->title_is( 'Invoice Status', "Make sure we're on the invoice page" );
31             $mech->text_contains( 'Andy Lester', 'My name somewhere' );
32             $mech->content_like( qr/(cpan|perl)\.org/, 'Link to perl.org or CPAN' );
33              
34             This is equivalent to:
35              
36             use Test::More tests => 5;
37             use WWW::Mechanize;
38              
39             my $mech = WWW::Mechanize->new;
40             $mech->get( $page );
41             ok( $mech->success );
42             is( $mech->base, 'http://petdance.com', 'Proper ' );
43             is( $mech->title, 'Invoice Status', "Make sure we're on the invoice page" );
44             ok( index( $mech->content( format => 'text' ), 'Andy Lester' ) >= 0, 'My name somewhere' );
45             like( $mech->content, qr/(cpan|perl)\.org/, 'Link to perl.org or CPAN' );
46              
47             but has nicer diagnostics if they fail.
48              
49             Default descriptions will be supplied for most methods if you omit them. e.g.
50              
51             my $mech = Test::WWW::Mechanize->new;
52             $mech->get_ok( 'http://petdance.com/' );
53             $mech->base_is( 'http://petdance.com/' );
54             $mech->title_is( 'Invoice Status' );
55             $mech->content_contains( 'Andy Lester' );
56             $mech->content_like( qr/(cpan|perl)\.org/ );
57              
58             results in
59              
60             ok - Got 'http://petdance.com/' ok
61             ok - Base is 'http://petdance.com/'
62             ok - Title is 'Invoice Status'
63             ok - Text contains 'Andy Lester'
64             ok - Content is like '(?-xism:(cpan|perl)\.org)'
65              
66             =cut
67              
68 33     33   16433 use HTML::TokeParser ();
  33         334131  
  33         898  
69 33     33   26492 use WWW::Mechanize ();
  33         4836745  
  33         1145  
70 33     33   17543 use Test::LongString;
  33         76482  
  33         242  
71 33     33   2805 use Test::Builder ();
  33         90  
  33         494  
72 33     33   182 use Carp ();
  33         80  
  33         817  
73 33         3176 use Carp::Assert::More qw(
74             assert_arrayref
75             assert_in
76             assert_is
77             assert_isa
78             assert_nonblank
79 33     33   17884 );
  33         134690  
80              
81 33     33   280 use parent 'WWW::Mechanize';
  33         85  
  33         317  
82              
83             my $TB = Test::Builder->new();
84              
85              
86             =head1 CONSTRUCTOR
87              
88             =head2 new( %args )
89              
90             Behaves like, and calls, L's C method. Any parms
91             passed in get passed to WWW::Mechanize's constructor.
92              
93             You can pass in C<< autolint => 1 >> to make Test::WWW::Mechanize
94             automatically run HTML::Lint after any of the following methods are
95             called. You can also pass in an HTML::Lint object like this:
96              
97             my $lint = HTML::Lint->new( only_types => HTML::Lint::Error::STRUCTURE );
98             my $mech = Test::WWW::Mechanize->new( autolint => $lint );
99              
100             The same is also possible with C<< autotidy => 1 >> to use HTML::Tidy5.
101              
102             =over
103              
104             =item * get_ok()
105              
106             =item * post_ok()
107              
108             =item * submit_form_ok()
109              
110             =item * follow_link_ok()
111              
112             =item * click_ok()
113              
114             =back
115              
116             This means you no longer have to do the following:
117              
118             my $mech = Test::WWW::Mechanize->new();
119             $mech->get_ok( $url, 'Fetch the intro page' );
120             $mech->html_lint_ok( 'Intro page looks OK' );
121              
122             and can simply do
123              
124             my $mech = Test::WWW::Mechanize->new( autolint => 1 );
125             $mech->get_ok( $url, 'Fetch the intro page' );
126              
127             The C<< $mech->get_ok() >> only counts as one test in the test count. Both the
128             main IO operation and the linting must pass for the entire test to pass.
129              
130             You can control autolint and autotidy on the fly with the C
131             and C methods.
132              
133             =cut
134              
135             sub new {
136 40     40 1 5059362 my $class = shift;
137              
138 40         474 my %args = (
139             agent => "Test-WWW-Mechanize/$VERSION",
140             @_
141             );
142              
143 40         214 my $autolint = delete $args{autolint};
144 40         138 my $autotidy = delete $args{autotidy};
145              
146 40         1387 my $self = $class->SUPER::new( %args );
147              
148 40         538 $self->autolint( $autolint );
149 40         187 $self->autotidy( $autotidy );
150              
151 40         181 return $self;
152             }
153              
154              
155             # Override WWW::Mechanize->_reset_page() to handle Test::WWW::Mechanize-specific data.
156             sub _reset_page {
157 429     429   2476643 my $self = shift;
158              
159             # Parent object stuff
160 429         2164 $self->SUPER::_reset_page( @_ );
161              
162 429         6501 $self->{ids} = undef;
163              
164 429         883 return;
165             }
166              
167              
168             =head1 METHODS: HTTP VERBS
169              
170             =head2 $mech->get_ok($url, [ \%LWP_options ,] $desc)
171              
172             A wrapper around WWW::Mechanize's get(), with similar options, except
173             the second argument needs to be a hash reference, not a hash. Like
174             well-behaved C<*_ok()> functions, it returns true if the test passed,
175             or false if not.
176              
177             A default description of "GET $url" is used if none if provided.
178              
179             =cut
180              
181             sub get_ok {
182 45     45 1 242795 my $self = shift;
183              
184 45         679 my ($url,$desc,%opts) = $self->_unpack_args( 'GET', @_ );
185              
186 45 100       330 if ( !defined($url) ) {
187 1         10 my $ok = $TB->ok( 0, $desc );
188 1         1053 $TB->diag( 'URL cannot be undef.' );
189 1         264 return $ok;
190             }
191              
192 44         1301 $self->get( $url, %opts );
193 44         1141 my $ok = $self->success;
194              
195 44         1084 $ok = $self->_post_load_validation( $ok, $desc );
196              
197 44         202 return $ok;
198             }
199              
200             sub _post_load_validation {
201 53     53   135 my $self = shift;
202 53         136 my $ok = shift;
203 53         127 my $desc = shift;
204              
205 53         317 local $Test::Builder::Level = $Test::Builder::Level + 1;
206              
207 53 100       248 if ( $ok ) {
208 51         114 my $emitted_ok = 0;
209 51 100       200 if ( $self->is_html ) {
210 44 50 33     1161 if ( $self->autolint && $self->autotidy ) {
211 0         0 my $msg = 'autolint & autotidy';
212 0 0       0 $msg .= ": $desc" if defined $desc;
213             $TB->subtest(
214             $desc,
215             sub {
216 0     0   0 $self->_lint_content_ok();
217 0         0 $self->_tidy_content_ok();
218             }
219 0         0 );
220 0         0 ++$emitted_ok;
221             }
222             else {
223 44 50       166 if ( $self->autolint ) {
    50          
224 0         0 $ok = $self->_lint_content_ok( $desc );
225 0         0 ++$emitted_ok;
226             }
227             elsif ( $self->autotidy ) {
228 0         0 $ok = $self->_tidy_content_ok( $desc );
229 0         0 ++$emitted_ok;
230             }
231             }
232             }
233              
234 51 50       276 if ( !$emitted_ok ) {
235 51         657 $TB->ok( $ok, $desc );
236 51 50       34733 if ( !$ok ) {
237             # Only show the URL and not the response message, because the
238             # problem is with the lint/tidy, not the fetching of the URL.
239 0         0 my $url = $self->_diag_url();
240 0 0       0 $TB->diag( $url ) if $url;
241             }
242             }
243             }
244             else {
245 2         37 $TB->ok( $ok, $desc );
246 2         3005 my $url = $self->_diag_url();
247 2 50       70 $TB->diag( $url ) if $url;
248 2         570 $TB->diag( $self->status );
249 2 50       601 $TB->diag( $self->response->message ) if $self->response;
250             }
251              
252 53         790 return $ok;
253             }
254              
255              
256             =head2 $mech->head_ok($url, [ \%LWP_options ,] $desc)
257              
258             A wrapper around WWW::Mechanize's head(), with similar options, except
259             the second argument needs to be a hash reference, not a hash. Like
260             well-behaved C<*_ok()> functions, it returns true if the test passed,
261             or false if not.
262              
263             A default description of "HEAD $url" is used if none if provided.
264              
265             =cut
266              
267             sub head_ok {
268 11     11 1 18984 my $self = shift;
269              
270 11         87 my ($url,$desc,%opts) = $self->_unpack_args( 'HEAD', @_ );
271              
272 11 100       42 if ( !defined($url) ) {
273 1         18 my $ok = $TB->ok( 0, $desc );
274 1         1058 $TB->diag( 'URL cannot be undef.' );
275 1         270 return $ok;
276             }
277              
278 10         46 $self->head( $url, %opts );
279 10         242 my $ok = $self->success;
280              
281 10         98 $TB->ok( $ok, $desc );
282 10 100       4792 if ( !$ok ) {
283 1         27 my $url = $self->_diag_url();
284 1 50       31 $TB->diag( $url ) if $url;
285 1         282 $TB->diag( $self->status );
286 1 50       277 $TB->diag( $self->response->message ) if $self->response;
287             }
288              
289 10         318 return $ok;
290             }
291              
292              
293             =head2 $mech->post_ok( $url, [ \%LWP_options ,] $desc )
294              
295             A wrapper around WWW::Mechanize's post(), with similar options, except
296             the second argument needs to be a hash reference, not a hash. Like
297             well-behaved C<*_ok()> functions, it returns true if the test passed,
298             or false if not.
299              
300             B Due to compatibility reasons it is not possible to pass
301             additional LWP_options beyond form data via this method (such as
302             Content or Content-Type). It is recommend that you use WWW::Mechanize's
303             post() directly for instances where more granular control of the post
304             is needed.
305              
306             A default description of "POST to $url" is used if none if provided.
307              
308             =cut
309              
310             sub post_ok {
311 1     1 1 2110 my $self = shift;
312              
313 1         5 my ($url,$desc,%opts) = $self->_unpack_args( 'POST', @_ );
314              
315 1 50       4 if ( !defined($url) ) {
316 1         4 my $ok = $TB->ok( 0, $desc );
317 1         1071 $TB->diag( 'URL cannot be undef.' );
318 1         231 return $ok;
319             }
320              
321 0         0 $self->post( $url, \%opts );
322 0         0 my $ok = $self->success;
323 0         0 $ok = $self->_post_load_validation( $ok, $desc );
324              
325 0         0 return $ok;
326             }
327              
328             =head2 $mech->put_ok( $url, [ \%LWP_options ,] $desc )
329              
330             A wrapper around WWW::Mechanize's put(), with similar options, except
331             the second argument needs to be a hash reference, not a hash. Like
332             well-behaved C<*_ok()> functions, it returns true if the test passed,
333             or false if not.
334              
335             A default description of "PUT to $url" is used if none if provided.
336              
337             =cut
338              
339             sub put_ok {
340 4     4 1 9663 my $self = shift;
341              
342 4         32 my ($url,$desc,%opts) = $self->_unpack_args( 'PUT', @_ );
343              
344 4 100       13 if ( !defined($url) ) {
345 1         6 my $ok = $TB->ok( 0, $desc );
346 1         1291 $TB->diag( 'URL cannot be undef.' );
347 1         304 return $ok;
348             }
349              
350 3 100       24 $opts{content} = '' if !exists $opts{content};
351 3         56 $self->put( $url, %opts );
352              
353 3         69 my $ok = $self->success;
354 3         79 $TB->ok( $ok, $desc );
355 3 50       1237 if ( !$ok ) {
356 0         0 my $url = $self->_diag_url();
357 0 0       0 $TB->diag( $url ) if $url;
358 0         0 $TB->diag( $self->status );
359 0 0       0 $TB->diag( $self->response->message ) if $self->response;
360             }
361              
362 3         14 return $ok;
363             }
364              
365             =head2 $mech->delete_ok( $url, [ \%LWP_options ,] $desc )
366              
367             A wrapper around WWW::Mechanize's delete(), with similar options, except
368             the second argument needs to be a hash reference, not a hash. Like
369             well-behaved C<*_ok()> functions, it returns true if the test passed,
370             or false if not.
371              
372             A default description of "DELETE to $url" is used if none if provided.
373              
374             =cut
375              
376             sub delete_ok {
377 5     5 1 10979 my $self = shift;
378              
379 5         33 my ($url,$desc,%opts) = $self->_unpack_args( 'DELETE', @_ );
380              
381 5 100       21 if ( !defined($url) ) {
382 1         5 my $ok = $TB->ok( 0, $desc );
383 1         1324 $TB->diag( 'URL cannot be undef.' );
384 1         282 return $ok;
385             }
386              
387 4 100       60 if ($self->can('delete')) {
388 3         13 $self->delete( $url, %opts );
389             }
390             else {
391             # When version of LWP::UserAgent is older than 6.04.
392 1         8 $self->_delete( $url, %opts );
393             }
394 4         90 my $ok = $self->success;
395              
396 4         101 $ok = $self->_post_load_validation( $ok, $desc );
397              
398 4         21 return $ok;
399             }
400              
401             sub _delete {
402 1     1   10 require URI;
403 1         5 require HTTP::Request::Common;
404 1         2 my $self = shift;
405 1         4 my $uri = shift;
406              
407 1 50       5 $uri = $uri->url if ref($uri) eq 'WWW::Mechanize::Link';
408 1 50       12 $uri = $self->base
409             ? URI->new_abs( $uri, $self->base )
410             : URI->new($uri);
411              
412 1         178 my @parameters = ( $uri->as_string, @_ );
413 1         9 my @suff = $self->_process_colonic_headers( \@parameters, 1 );
414 1         14 return $self->request( HTTP::Request::Common::DELETE(@parameters), @suff );
415             }
416              
417             =head2 $mech->submit_form_ok( \%parms [, $desc] )
418              
419             Makes a C call and executes tests on the results.
420             The form must be found, and then submitted successfully. Otherwise,
421             this test fails.
422              
423             I<%parms> is a hashref containing the parms to pass to C.
424             Note that the parms to C are a hash whereas the parms to
425             this function are a hashref. You have to call this function like:
426              
427             $mech->submit_form_ok( {
428             form_number => 3,
429             fields => {
430             answer => 42
431             },
432             }, 'now we just need the question'
433             );
434              
435             As with other test functions, C<$desc> is optional. If it is supplied
436             then it will display when running the test harness in verbose mode.
437              
438             Returns true value if the specified link was found and followed
439             successfully. The L object returned by submit_form()
440             is not available.
441              
442             =cut
443              
444             sub submit_form_ok {
445 1     1 1 19 my $self = shift;
446 1   50     4 my $parms = shift || {};
447 1         2 my $desc = shift;
448              
449 1 50       5 if ( ref $parms ne 'HASH' ) {
450 0         0 Carp::croak 'FATAL: parameters must be given as a hashref';
451             }
452              
453             # return from submit_form() is an HTTP::Response or undef
454 1         2 my $response = $self->submit_form( %{$parms} );
  1         29  
455              
456 1   33     75 my $ok = $response && $response->is_success;
457 1         15 $ok = $self->_post_load_validation( $ok, $desc );
458              
459 1         34 return $ok;
460             }
461              
462              
463             =head2 $mech->follow_link_ok( \%parms [, $desc] )
464              
465             Makes a C call and executes tests on the results.
466             The link must be found, and then followed successfully. Otherwise,
467             this test fails.
468              
469             I<%parms> is a hashref containing the parms to pass to C.
470             Note that the parms to C are a hash whereas the parms to
471             this function are a hashref. You have to call this function like:
472              
473             $mech->follow_link_ok( {n=>3}, 'looking for 3rd link' );
474              
475             As with other test functions, C<$desc> is optional. If it is supplied
476             then it will display when running the test harness in verbose mode.
477              
478             Returns a true value if the specified link was found and followed
479             successfully. The L object returned by follow_link()
480             is not available.
481              
482             =cut
483              
484             sub follow_link_ok {
485 2     2 1 4294 my $self = shift;
486 2   50     20 my $parms = shift || {};
487 2         18 my $desc = shift;
488              
489 2 50       12 if (!defined($desc)) {
490 0         0 my $parms_str = join(', ', map { join('=', $_, $parms->{$_}) } keys(%{$parms}));
  0         0  
  0         0  
491 0 0       0 $desc = qq{Followed link with "$parms_str"} if !defined($desc);
492             }
493              
494 2 50       11 if ( ref $parms ne 'HASH' ) {
495 0         0 Carp::croak 'FATAL: parameters must be given as a hashref';
496             }
497              
498             # The return from follow_link() is an HTTP::Response or undef.
499 2         4 my $response = $self->follow_link( %{$parms} );
  2         36  
500              
501 2   66     191 my $ok = $response && $response->is_success;
502 2         26 $ok = $self->_post_load_validation( $ok, $desc );
503              
504 2         7 return $ok;
505             }
506              
507              
508             =head2 $mech->click_ok( $button[, $desc] )
509              
510             =head2 $mech->click_ok( \@button-and-coordinates [, $desc ] )
511              
512             Clicks the button named by C<$button>. An optional C<$desc> can be
513             given for the test.
514              
515             $mech->click_ok( 'continue', 'Clicking the "Continue" button' );
516              
517             Alternatively the first argument can be an arrayref with three elements:
518             The name of the button and the X and Y coordinates of the button.
519              
520             $mech->click_ok( [ 'continue', 12, 47 ], 'Clicking the "Continue" button' );
521              
522             =cut
523              
524             sub click_ok {
525 2     2 1 16 my $self = shift;
526 2         5 my $button = shift;
527 2         3 my $desc = shift;
528              
529 2         3 my $response;
530 2 100       11 if ( ref($button) eq 'ARRAY' ) {
531 1         6 $response = $self->click( $button->[0], $button->[1], $button->[2] );
532             }
533             else {
534 1         43 $response = $self->click( $button );
535             }
536              
537 2 50       36 if ( !$response ) {
538 0         0 return $TB->ok( 0, $desc );
539             }
540              
541 2         7 my $ok = $response->is_success;
542              
543 2         21 $ok = $self->_post_load_validation( $ok, $desc );
544              
545 2         76 return $ok;
546             }
547              
548              
549             sub _unpack_args {
550 66     66   302 my $self = shift;
551 66         361 my $method = shift;
552 66         262 my $url = shift;
553              
554 66         256 my $desc;
555             my %opts;
556              
557 66 100       511 if ( @_ ) {
558 26         75 my $flex = shift; # The flexible argument
559              
560 26 100       166 if ( !defined( $flex ) ) {
    100          
    100          
561 2         6 $desc = shift;
562             }
563             elsif ( ref $flex eq 'HASH' ) {
564 5         10 %opts = %{$flex};
  5         21  
565 5         13 $desc = shift;
566             }
567             elsif ( ref $flex eq 'ARRAY' ) {
568 4         12 %opts = @{$flex};
  4         12  
569 4         11 $desc = shift;
570             }
571             else {
572 15         64 $desc = $flex;
573             }
574             } # parms left
575              
576 66 100       506 if ( not defined $desc ) {
577 45 50       342 $url = $url->url if ref($url) eq 'WWW::Mechanize::Link';
578 45         266 $desc = "$method $url";
579             }
580              
581 66         500 return ($url, $desc, %opts);
582             }
583              
584              
585             =head1 METHODS: HEADER CHECKING
586              
587             =head2 $mech->header_exists_ok( $header [, $desc ] )
588              
589             Assures that a given response header exists. The actual value of the
590             response header is not checked, only that the header exists.
591              
592             =cut
593              
594             sub header_exists_ok {
595 3     3 1 9575 my $self = shift;
596 3         34 my $header = shift;
597 3   66     28 my $desc = shift || qq{Response has $header header};
598              
599 3         34 return $TB->ok( defined($self->response->header($header)), $desc );
600             }
601              
602              
603             =head2 $mech->lacks_header_ok( $header [, $desc ] )
604              
605             Assures that a given response header does NOT exist.
606              
607             =cut
608              
609             sub lacks_header_ok {
610 3     3 1 12176 my $self = shift;
611 3         35 my $header = shift;
612 3   66     19 my $desc = shift || qq{Response lacks $header header};
613              
614 3         13 return $TB->ok( !defined($self->response->header($header)), $desc );
615             }
616              
617              
618             =head2 $mech->header_is( $header, $value [, $desc ] )
619              
620             Assures that a given response header exists and has the given value.
621              
622             =cut
623              
624             sub header_is {
625 4     4 1 14993 my $self = shift;
626 4         54 my $header = shift;
627 4         21 my $value = shift;
628 4   66     30 my $desc = shift || qq{Response has $header header with value "$value"};
629              
630             # Force scalar context.
631 4         17 my $actual_value = $self->response->header($header);
632              
633 4         228 my $ok;
634 4 100       12 if ( defined( $actual_value ) ) {
635 3         11 $ok = $TB->is_eq( $actual_value, $value, $desc );
636             }
637             else {
638 1         6 $ok = $TB->ok( 0, $desc );
639 1         985 $TB->diag( "Header $header does not exist" );
640             }
641              
642 4         2863 return $ok;
643             }
644              
645              
646             =head2 $mech->header_like( $header, $value [, $desc ] )
647              
648             Assures that a given response header exists and has the given value.
649              
650             =cut
651              
652             sub header_like {
653 2     2 1 6185 my $self = shift;
654 2         17 my $header = shift;
655 2         5 my $regex = shift;
656 2   33     15 my $desc = shift || qq{Response has $header header that matches regex $regex};
657              
658             # Force scalar context.
659 2         11 my $actual_value = $self->response->header($header);
660 2         120 return $TB->like( $self->response->header($header), $regex, $desc );
661             }
662              
663              
664             =head1 METHODS: CONTENT CHECKING
665              
666             =head2 $mech->html_lint_ok( [$desc] )
667              
668             Checks the validity of the HTML on the current page using the HTML::Lint
669             module. If the page is not HTML, then it fails. The URI is automatically
670             appended to the I<$desc>.
671              
672             Note that HTML::Lint must be installed for this to work. Otherwise,
673             it will blow up.
674              
675             =cut
676              
677             sub html_lint_ok {
678 0     0 1 0 my $self = shift;
679 0         0 my $desc = shift;
680              
681 0         0 my $uri = $self->uri;
682 0 0       0 $desc = $desc ? "$desc ($uri)" : $uri;
683              
684 0         0 my $ok;
685              
686 0 0       0 if ( $self->is_html ) {
687 0         0 $ok = $self->_lint_content_ok( $desc );
688             }
689             else {
690 0         0 $ok = $TB->ok( 0, $desc );
691 0         0 $TB->diag( q{This page doesn't appear to be HTML, or didn't get the proper text/html content type returned.} );
692             }
693              
694 0         0 return $ok;
695             }
696              
697              
698             sub _lint_content_ok {
699 0     0   0 local $Test::Builder::Level = $Test::Builder::Level + 1;
700              
701 0         0 my $self = shift;
702 0         0 my $desc = shift;
703              
704 0         0 my $module = "HTML::Lint 2.20";
705 0 0       0 if ( not ( eval "use $module; 1;" ) ) {
706 0         0 die "Test::WWW::Mechanize can't do linting without $module: $@";
707             }
708              
709 0         0 my $lint = $self->{autolint};
710 0 0 0     0 if ( ref $lint && $lint->isa('HTML::Lint') ) {
711 0         0 $lint->newfile;
712 0         0 $lint->clear_errors;
713             }
714             else {
715 0         0 $lint = HTML::Lint->new();
716             }
717              
718 0         0 $lint->parse( $self->content );
719 0         0 $lint->eof();
720              
721 0         0 my @errors = $lint->errors;
722 0         0 my $nerrors = @errors;
723 0         0 my $ok;
724 0 0       0 if ( $nerrors ) {
725 0         0 $ok = $TB->ok( 0, $desc );
726 0         0 $TB->diag( 'HTML::Lint errors for ' . $self->uri );
727 0         0 $TB->diag( $_->as_string ) for @errors;
728 0 0       0 my $s = $nerrors == 1 ? '' : 's';
729 0         0 $TB->diag( "$nerrors error$s on the page" );
730             }
731             else {
732 0         0 $ok = $TB->ok( 1, $desc );
733             }
734              
735 0         0 return $ok;
736             }
737              
738              
739             =head2 $mech->html_tidy_ok( [$desc] )
740              
741             Checks the validity of the HTML on the current page using the HTML::Tidy
742             module. If the page is not HTML, then it fails. The URI is automatically
743             appended to the I<$desc>.
744              
745             Note that HTML::tidy must be installed for this to work. Otherwise,
746             it will blow up.
747              
748             =cut
749              
750             sub html_tidy_ok {
751 0     0 1 0 my $self = shift;
752 0         0 my $desc = shift;
753              
754 0         0 my $uri = $self->uri;
755 0 0       0 $desc = $desc ? "$desc ($uri)" : $uri;
756              
757 0         0 my $ok;
758              
759 0 0       0 if ( $self->is_html ) {
760 0         0 $ok = $self->_tidy_content_ok( $desc );
761             }
762             else {
763 0         0 $ok = $TB->ok( 0, $desc );
764 0         0 $TB->diag( q{This page doesn't appear to be HTML, or didn't get the proper text/html content type returned.} );
765             }
766              
767 0         0 return $ok;
768             }
769              
770              
771             sub _tidy_content_ok {
772 0     0   0 local $Test::Builder::Level = $Test::Builder::Level + 1;
773              
774 0         0 my $self = shift;
775 0         0 my $desc = shift;
776              
777 0         0 my $module = 'HTML::Tidy5 1.00';
778              
779 0 0       0 if ( not ( eval "use $module; 1;" ) ) {
780 0         0 die "Test::WWW::Mechanize can't do tidying without $module: $@";
781             }
782              
783 0         0 my $tidy = $self->{autotidy};
784 0 0 0     0 if ( ref $tidy && $tidy->isa('HTML::Tidy5') ) {
785 0         0 $tidy->clear_messages();
786             }
787             else {
788 0         0 $tidy = HTML::Tidy5->new();
789             }
790              
791 0         0 $tidy->parse( '', $self->content_for_tidy );
792              
793 0         0 my @messages = $tidy->messages;
794 0         0 my $nmessages = @messages;
795 0         0 my $ok;
796 0 0       0 if ( $nmessages ) {
797 0         0 $ok = $TB->ok( 0, $desc );
798 0         0 $TB->diag( 'HTML::Tidy5 messages for ' . $self->uri );
799 0         0 $TB->diag( $_->as_string ) for @messages;
800 0 0       0 my $s = $nmessages == 1 ? '' : 's';
801 0         0 $TB->diag( "$nmessages message$s on the page" );
802             }
803             else {
804 0         0 $ok = $TB->ok( 1, $desc );
805             }
806              
807 0         0 return $ok;
808             }
809              
810              
811             =head2 $mech->content_for_tidy()
812              
813             This method is called by C to get the content that should
814             be validated by HTML::Tidy5. By default, this is just C,
815             but subclasses can override it to modify the content before validation.
816              
817             This method should not change any state in the Mech object. Specifically,
818             it should not actually modify any of the actual content.
819              
820             =cut
821              
822             sub content_for_tidy {
823 0     0 1 0 my $self = shift;
824              
825 0         0 return $self->content;
826             }
827              
828              
829             =head2 $mech->title_is( $str [, $desc ] )
830              
831             Tells if the title of the page is the given string.
832              
833             $mech->title_is( 'Invoice Summary' );
834              
835             =cut
836              
837             sub title_is {
838 2     2 1 2977 my $self = shift;
839 2         16 my $str = shift;
840 2         29 my $desc = shift;
841 2 100       14 $desc = qq{Title is "$str"} if !defined($desc);
842              
843 2         4 local $Test::Builder::Level = $Test::Builder::Level + 1;
844 2         11 return is_string( $self->title, $str, $desc );
845             }
846              
847             =head2 $mech->title_like( $regex [, $desc ] )
848              
849             Tells if the title of the page matches the given regex.
850              
851             $mech->title_like( qr/Invoices for (.+)/ );
852              
853             =cut
854              
855             sub title_like {
856 1     1 1 2951 my $self = shift;
857 1         8 my $regex = shift;
858 1         7 my $desc = shift;
859 1 50       5 $desc = qq{Title is like "$regex"} if !defined($desc);
860              
861 1         4 local $Test::Builder::Level = $Test::Builder::Level + 1;
862 1         5 return like_string( $self->title, $regex, $desc );
863             }
864              
865             =head2 $mech->title_unlike( $regex [, $desc ] )
866              
867             Tells if the title of the page matches the given regex.
868              
869             $mech->title_unlike( qr/Invoices for (.+)/ );
870              
871             =cut
872              
873             sub title_unlike {
874 0     0 1 0 my $self = shift;
875 0         0 my $regex = shift;
876 0         0 my $desc = shift;
877 0 0       0 $desc = qq{Title is unlike "$regex"} if !defined($desc);
878              
879 0         0 local $Test::Builder::Level = $Test::Builder::Level + 1;
880 0         0 return unlike_string( $self->title, $regex, $desc );
881             }
882              
883             =head2 $mech->base_is( $str [, $desc ] )
884              
885             Tells if the base of the page is the given string.
886              
887             $mech->base_is( 'http://example.com/' );
888              
889             =cut
890              
891             sub base_is {
892 0     0 1 0 my $self = shift;
893 0         0 my $str = shift;
894 0         0 my $desc = shift;
895 0 0       0 $desc = qq{Base is "$str"} if !defined($desc);
896              
897 0         0 local $Test::Builder::Level = $Test::Builder::Level + 1;
898 0         0 return is_string( $self->base, $str, $desc );
899             }
900              
901             =head2 $mech->base_like( $regex [, $desc ] )
902              
903             Tells if the base of the page matches the given regex.
904              
905             $mech->base_like( qr{http://example.com/index.php?PHPSESSID=(.+)});
906              
907             =cut
908              
909             sub base_like {
910 0     0 1 0 my $self = shift;
911 0         0 my $regex = shift;
912 0         0 my $desc = shift;
913 0 0       0 $desc = qq{Base is like "$regex"} if !defined($desc);
914              
915 0         0 local $Test::Builder::Level = $Test::Builder::Level + 1;
916 0         0 return like_string( $self->base, $regex, $desc );
917             }
918              
919             =head2 $mech->base_unlike( $regex [, $desc ] )
920              
921             Tells if the base of the page matches the given regex.
922              
923             $mech->base_unlike( qr{http://example.com/index.php?PHPSESSID=(.+)});
924              
925             =cut
926              
927             sub base_unlike {
928 0     0 1 0 my $self = shift;
929 0         0 my $regex = shift;
930 0         0 my $desc = shift;
931 0 0       0 $desc = qq{Base is unlike "$regex"} if !defined($desc);
932              
933 0         0 local $Test::Builder::Level = $Test::Builder::Level + 1;
934 0         0 return unlike_string( $self->base, $regex, $desc );
935             }
936              
937             =head2 $mech->content_is( $str [, $desc ] )
938              
939             Tells if the content of the page matches the given string
940              
941             =cut
942              
943             sub content_is {
944 0     0 1 0 my $self = shift;
945 0         0 my $str = shift;
946 0         0 my $desc = shift;
947              
948 0         0 local $Test::Builder::Level = $Test::Builder::Level + 1;
949 0 0       0 $desc = qq{Content is "$str"} if !defined($desc);
950              
951 0         0 return is_string( $self->content, $str, $desc );
952             }
953              
954             =head2 $mech->content_contains( $str [, $desc ] )
955              
956             Tells if the content of the page contains I<$str>.
957              
958             =cut
959              
960             sub content_contains {
961 7     7 1 35427 my $self = shift;
962 7         28 my $str = shift;
963 7         23 my $desc = shift;
964              
965 7         49 local $Test::Builder::Level = $Test::Builder::Level + 1;
966              
967 7 100       26 if ( ref($str) ) {
968 4         16 return $TB->ok( 0, 'Test::WWW::Mechanize->content_contains called incorrectly. It requires a scalar, not a reference.' );
969             }
970 3 100       12 $desc = qq{Content contains "$str"} if !defined($desc);
971              
972 3         35 return contains_string( $self->content, $str, $desc );
973             }
974              
975             =head2 $mech->content_lacks( $str [, $desc ] )
976              
977             Tells if the content of the page lacks I<$str>.
978              
979             =cut
980              
981             sub content_lacks {
982 7     7 1 26844 my $self = shift;
983 7         24 my $str = shift;
984 7         18 my $desc = shift;
985              
986 7         16 local $Test::Builder::Level = $Test::Builder::Level + 1;
987 7 100       31 if ( ref($str) ) {
988 4         17 return $TB->ok( 0, 'Test::WWW::Mechanize->content_lacks called incorrectly. It requires a scalar, not a reference.' );
989             }
990 3 100       23 $desc = qq{Content lacks "$str"} if !defined($desc);
991              
992 3         32 return lacks_string( $self->content, $str, $desc );
993             }
994              
995             =head2 $mech->content_like( $regex [, $desc ] )
996              
997             Tells if the content of the page matches I<$regex>.
998              
999             =cut
1000              
1001             sub content_like {
1002 0     0 1 0 my $self = shift;
1003 0         0 my $regex = shift;
1004 0         0 my $desc = shift;
1005 0 0       0 $desc = qq{Content is like "$regex"} if !defined($desc);
1006              
1007 0         0 local $Test::Builder::Level = $Test::Builder::Level + 1;
1008 0         0 return like_string( $self->content, $regex, $desc );
1009             }
1010              
1011             =head2 $mech->content_unlike( $regex [, $desc ] )
1012              
1013             Tells if the content of the page does NOT match I<$regex>.
1014              
1015             =cut
1016              
1017             sub content_unlike {
1018 0     0 1 0 my $self = shift;
1019 0         0 my $regex = shift;
1020 0   0     0 my $desc = shift || qq{Content is unlike "$regex"};
1021              
1022 0         0 local $Test::Builder::Level = $Test::Builder::Level + 1;
1023 0         0 return unlike_string( $self->content, $regex, $desc );
1024             }
1025              
1026             =head2 $mech->text_contains( $str [, $desc ] )
1027              
1028             Tells if the text form of the page's content contains I<$str>.
1029              
1030             When your page contains HTML which is difficult, unimportant, or
1031             unlikely to match over time as designers alter markup, use
1032             C instead of C.
1033              
1034             # Hi, User!
1035             $mech->content_contains('Hi, User'); # Fails.
1036             $mech->text_contains('Hi, User'); # Passes.
1037              
1038             Text is determined by calling C<< $mech->text() >>.
1039             See L.
1040              
1041             =cut
1042              
1043             sub text_contains {
1044 7     7 1 54324 my $self = shift;
1045 7         24 my $str = shift;
1046 7   66     35 my $desc = shift || qq{Text contains "$str"};
1047              
1048 7         21 local $Test::Builder::Level = $Test::Builder::Level + 1;
1049 7 100       24 if ( ref($str) ) {
1050 4         14 return $TB->ok( 0, 'Test::WWW::Mechanize->text_contains called incorrectly. It requires a scalar, not a reference.' );
1051             }
1052              
1053 3         23 return contains_string( $self->text, $str, $desc );
1054             }
1055              
1056             =head2 $mech->text_lacks( $str [, $desc ] )
1057              
1058             Tells if the text of the page lacks I<$str>.
1059              
1060             =cut
1061              
1062             sub text_lacks {
1063 4     4 1 17517 my $self = shift;
1064 4         11 my $str = shift;
1065 4         6 my $desc = shift;
1066              
1067 4         8 local $Test::Builder::Level = $Test::Builder::Level + 1;
1068 4 50       15 if ( ref($str) ) {
1069 4         14 return $TB->ok( 0, 'Test::WWW::Mechanize->text_lacks called incorrectly. It requires a scalar, not a reference.' );
1070             }
1071 0 0       0 $desc = qq{Text lacks "$str"} if !defined($desc);
1072              
1073 0         0 return lacks_string( $self->text, $str, $desc );
1074             }
1075              
1076             =head2 $mech->text_like( $regex [, $desc ] )
1077              
1078             Tells if the text form of the page's content matches I<$regex>.
1079              
1080             =cut
1081              
1082             sub text_like {
1083 0     0 1 0 my $self = shift;
1084 0         0 my $regex = shift;
1085 0   0     0 my $desc = shift || qq{Text is like "$regex"};
1086              
1087 0         0 local $Test::Builder::Level = $Test::Builder::Level + 1;
1088 0         0 return like_string( $self->text, $regex, $desc );
1089             }
1090              
1091             =head2 $mech->text_unlike( $regex [, $desc ] )
1092              
1093             Tells if the text format of the page's content does NOT match I<$regex>.
1094              
1095             =cut
1096              
1097             sub text_unlike {
1098 0     0 1 0 my $self = shift;
1099 0         0 my $regex = shift;
1100 0   0     0 my $desc = shift || qq{Text is unlike "$regex"};
1101              
1102 0         0 local $Test::Builder::Level = $Test::Builder::Level + 1;
1103 0         0 return unlike_string( $self->text, $regex, $desc );
1104             }
1105              
1106             =head2 $mech->has_tag( $tag, $text [, $desc ] )
1107              
1108             Tells if the page has a C<$tag> tag with the given content in its text.
1109              
1110             =cut
1111              
1112             sub has_tag {
1113 10     10 1 26795 my $self = shift;
1114 10         35 my $tag = shift;
1115 10         41 my $text = shift;
1116 10   66     71 my $desc = shift || qq{Page has $tag tag with "$text"};
1117              
1118 10     18   99 my $found = $self->_tag_walk( $tag, sub { $text eq $_[0] } );
  18         201  
1119              
1120 10         63 return $TB->ok( $found, $desc );
1121             }
1122              
1123              
1124             =head2 $mech->has_tag_like( $tag, $regex [, $desc ] )
1125              
1126             Tells if the page has a C<$tag> tag with the given content in its text.
1127              
1128             =cut
1129              
1130             sub has_tag_like {
1131 2     2 1 6994 my $self = shift;
1132 2         10 my $tag = shift;
1133 2         4 my $regex = shift;
1134 2         12 my $desc = shift;
1135 2 50       9 $desc = qq{Page has $tag tag like "$regex"} if !defined($desc);
1136              
1137 2     6   20 my $found = $self->_tag_walk( $tag, sub { $_[0] =~ $regex } );
  6         95  
1138              
1139 2         16 return $TB->ok( $found, $desc );
1140             }
1141              
1142              
1143             sub _tag_walk {
1144 12     12   34 my $self = shift;
1145 12         17 my $tag = shift;
1146 12         22 my $match = shift;
1147              
1148 12         59 my $p = HTML::TokeParser->new( \($self->content) );
1149              
1150 12         2484 while ( my $token = $p->get_tag( $tag ) ) {
1151 24         8205 my $tagtext = $p->get_trimmed_text();
1152 24 100       2093 return 1 if $match->( $tagtext );
1153             }
1154 2         415 return;
1155             }
1156              
1157             =head2 $mech->page_links_ok( [ $desc ] )
1158              
1159             Follow all links on the current page and test for HTTP status 200
1160              
1161             $mech->page_links_ok('Check all links');
1162              
1163             =cut
1164              
1165             sub page_links_ok {
1166 3     3 1 6703 my $self = shift;
1167 3         10 my $desc = shift;
1168              
1169 3 100       15 $desc = 'All links ok' unless defined $desc;
1170              
1171 3         25 my @links = $self->followable_links();
1172 3         13632 my @urls = _format_links(\@links);
1173              
1174 3         70 my @failures = $self->_check_links_status( \@urls );
1175 3         12 my $ok = (@failures==0);
1176              
1177 3         15 $TB->ok( $ok, $desc );
1178 3         3710 $TB->diag( $_ ) for @failures;
1179              
1180 3         810 return $ok;
1181             }
1182              
1183             =head2 $mech->page_links_content_like( $regex [, $desc ] )
1184              
1185             Follow all links on the current page and test their contents for I<$regex>.
1186              
1187             $mech->page_links_content_like( qr/foo/,
1188             'Check all links contain "foo"' );
1189              
1190             =cut
1191              
1192             sub page_links_content_like {
1193 4     4 1 10266 my $self = shift;
1194 4         13 my $regex = shift;
1195 4         13 my $desc = shift;
1196              
1197 4 100       16 $desc = qq{All links are like "$regex"} unless defined $desc;
1198              
1199 4         23 my $usable_regex=$TB->maybe_regex( $regex );
1200              
1201 4 100       99 if ( !defined( $usable_regex ) ) {
1202 1         4 my $ok = $TB->ok( 0, 'page_links_content_like' );
1203 1         1447 $TB->diag(qq{ "$regex" doesn't look much like a regex to me.});
1204 1         272 return $ok;
1205             }
1206              
1207 3         16 my @links = $self->followable_links();
1208 3         13360 my @urls = _format_links(\@links);
1209              
1210 3         14 my @failures = $self->_check_links_content( \@urls, $regex );
1211 3         18 my $ok = (@failures==0);
1212              
1213 3         24 $TB->ok( $ok, $desc );
1214 3         2007 $TB->diag( $_ ) for @failures;
1215              
1216 3         775 return $ok;
1217             }
1218              
1219             =head2 $mech->page_links_content_unlike( $regex [, $desc ] )
1220              
1221             Follow all links on the current page and test their contents do not
1222             contain the specified regex.
1223              
1224             $mech->page_links_content_unlike(qr/Restricted/,
1225             'Check all links do not contain Restricted');
1226              
1227             =cut
1228              
1229             sub page_links_content_unlike {
1230 3     3 1 8165 my $self = shift;
1231 3         7 my $regex = shift;
1232 3         7 my $desc = shift;
1233 3 50       15 $desc = qq{All links are unlike "$regex"} unless defined($desc);
1234              
1235 3         12 my $usable_regex=$TB->maybe_regex( $regex );
1236              
1237 3 100       63 if ( !defined( $usable_regex ) ) {
1238 1         4 my $ok = $TB->ok( 0, 'page_links_content_unlike' );
1239 1         1034 $TB->diag(qq{ "$regex" doesn't look much like a regex to me.});
1240 1         247 return $ok;
1241             }
1242              
1243 2         10 my @links = $self->followable_links();
1244 2         3438 my @urls = _format_links(\@links);
1245              
1246 2         12 my @failures = $self->_check_links_content( \@urls, $regex, 'unlike' );
1247 2         9 my $ok = (@failures==0);
1248              
1249 2         20 $TB->ok( $ok, $desc );
1250 2         1885 $TB->diag( $_ ) for @failures;
1251              
1252 2         765 return $ok;
1253             }
1254              
1255             =head2 $mech->links_ok( $links [, $desc ] )
1256              
1257             Follow specified links on the current page and test for HTTP status
1258             200. The links may be specified as a reference to an array containing
1259             L objects, an array of URLs, or a scalar URL
1260             name.
1261              
1262             my @links = $mech->find_all_links( url_regex => qr/cnn\.com$/ );
1263             $mech->links_ok( \@links, 'Check all links for cnn.com' );
1264              
1265             my @links = qw( index.html search.html about.html );
1266             $mech->links_ok( \@links, 'Check main links' );
1267              
1268             $mech->links_ok( 'index.html', 'Check link to index' );
1269              
1270             =cut
1271              
1272             sub links_ok {
1273 6     6 1 17336 my $self = shift;
1274 6         24 my $links = shift;
1275 6         26 my $desc = shift;
1276              
1277 6         43 my @urls = _format_links( $links );
1278 6 50       33 $desc = _default_links_desc(\@urls, 'are ok') unless defined $desc;
1279 6         32 my @failures = $self->_check_links_status( \@urls );
1280 6         25 my $ok = (@failures == 0);
1281              
1282 6         35 $TB->ok( $ok, $desc );
1283 6         4851 $TB->diag( $_ ) for @failures;
1284              
1285 6         1362 return $ok;
1286             }
1287              
1288             =head2 $mech->link_status_is( $links, $status [, $desc ] )
1289              
1290             Follow specified links on the current page and test for HTTP status
1291             passed. The links may be specified as a reference to an array
1292             containing L objects, an array of URLs, or a
1293             scalar URL name.
1294              
1295             my @links = $mech->followable_links();
1296             $mech->link_status_is( \@links, 403,
1297             'Check all links are restricted' );
1298              
1299             =cut
1300              
1301             sub link_status_is {
1302 3     3 1 12080 my $self = shift;
1303 3         13 my $links = shift;
1304 3         6 my $status = shift;
1305 3         11 my $desc = shift;
1306              
1307 3         22 my @urls = _format_links( $links );
1308 3 100       20 $desc = _default_links_desc(\@urls, "have status $status") if !defined($desc);
1309 3         18 my @failures = $self->_check_links_status( \@urls, $status );
1310 3         11 my $ok = (@failures == 0);
1311              
1312 3         18 $TB->ok( $ok, $desc );
1313 3         2389 $TB->diag( $_ ) for @failures;
1314              
1315 3         297 return $ok;
1316             }
1317              
1318             =head2 $mech->link_status_isnt( $links, $status [, $desc ] )
1319              
1320             Follow specified links on the current page and test for HTTP status
1321             passed. The links may be specified as a reference to an array
1322             containing L objects, an array of URLs, or a
1323             scalar URL name.
1324              
1325             my @links = $mech->followable_links();
1326             $mech->link_status_isnt( \@links, 404,
1327             'Check all links are not 404' );
1328              
1329             =cut
1330              
1331             sub link_status_isnt {
1332 2     2 1 4377 my $self = shift;
1333 2         4 my $links = shift;
1334 2         4 my $status = shift;
1335 2         10 my $desc = shift;
1336              
1337 2         10 my @urls = _format_links( $links );
1338 2 50       8 $desc = _default_links_desc(\@urls, "do not have status $status") if !defined($desc);
1339 2         8 my @failures = $self->_check_links_status( \@urls, $status, 'isnt' );
1340 2         7 my $ok = (@failures == 0);
1341              
1342 2         14 $TB->ok( $ok, $desc );
1343 2         2021 $TB->diag( $_ ) for @failures;
1344              
1345 2         288 return $ok;
1346             }
1347              
1348              
1349             =head2 $mech->link_content_like( $links, $regex [, $desc ] )
1350              
1351             Follow specified links on the current page and test the resulting
1352             content of each against I<$regex>. The links may be specified as a
1353             reference to an array containing L objects, an
1354             array of URLs, or a scalar URL name.
1355              
1356             my @links = $mech->followable_links();
1357             $mech->link_content_like( \@links, qr/Restricted/,
1358             'Check all links are restricted' );
1359              
1360             =cut
1361              
1362             sub link_content_like {
1363 4     4 1 15322 my $self = shift;
1364 4         9 my $links = shift;
1365 4         11 my $regex = shift;
1366 4         16 my $desc = shift;
1367              
1368 4         19 my $usable_regex=$TB->maybe_regex( $regex );
1369              
1370 4 100       101 if ( !defined( $usable_regex ) ) {
1371 1         5 my $ok = $TB->ok( 0, 'link_content_like' );
1372 1         1449 $TB->diag(qq{ "$regex" doesn't look much like a regex to me.});
1373 1         267 return $ok;
1374             }
1375              
1376 3         25 my @urls = _format_links( $links );
1377 3 100       23 $desc = _default_links_desc( \@urls, qq{are like "$regex"} ) if !defined($desc);
1378 3         14 my @failures = $self->_check_links_content( \@urls, $regex );
1379 3         12 my $ok = (@failures == 0);
1380              
1381 3         19 $TB->ok( $ok, $desc );
1382 3         2014 $TB->diag( $_ ) for @failures;
1383              
1384 3         819 return $ok;
1385             }
1386              
1387             =head2 $mech->link_content_unlike( $links, $regex [, $desc ] )
1388              
1389             Follow specified links on the current page and test that the resulting
1390             content of each does not match I<$regex>. The links may be specified as a
1391             reference to an array containing L objects, an array
1392             of URLs, or a scalar URL name.
1393              
1394             my @links = $mech->followable_links();
1395             $mech->link_content_unlike( \@links, qr/Restricted/,
1396             'No restricted links' );
1397              
1398             =cut
1399              
1400             sub link_content_unlike {
1401 4     4 1 12573 my $self = shift;
1402 4         8 my $links = shift;
1403 4         17 my $regex = shift;
1404 4         11 my $desc = shift;
1405              
1406 4         17 my $usable_regex=$TB->maybe_regex( $regex );
1407              
1408 4 100       98 if ( !defined( $usable_regex ) ) {
1409 1         5 my $ok = $TB->ok( 0, 'link_content_unlike' );
1410 1         1439 $TB->diag(qq{ "$regex" doesn't look much like a regex to me.});
1411 1         311 return $ok;
1412             }
1413              
1414 3         14 my @urls = _format_links( $links );
1415 3 100       19 $desc = _default_links_desc( \@urls, qq{are not like "$regex"} ) if !defined($desc);
1416 3         14 my @failures = $self->_check_links_content( \@urls, $regex, 'unlike' );
1417 3         15 my $ok = (@failures == 0);
1418              
1419 3         19 $TB->ok( $ok, $desc );
1420 3         2097 $TB->diag( $_ ) for @failures;
1421              
1422 3         831 return $ok;
1423             }
1424              
1425             # Create a default description for the link_* methods, including the link count.
1426             sub _default_links_desc {
1427 3     3   16 my ($urls, $desc_suffix) = @_;
1428 3         6 my $url_count = scalar(@{$urls});
  3         10  
1429 3 50       44 return sprintf( '%d link%s %s', $url_count, $url_count == 1 ? '' : 's', $desc_suffix );
1430             }
1431              
1432             # This actually performs the status check of each URL.
1433             sub _check_links_status {
1434 14     14   38 my $self = shift;
1435 14         22 my $urls = shift;
1436 14   100     89 my $status = shift || 200;
1437 14   100     87 my $test = shift || 'is';
1438              
1439             # Create a clone of the $mech used during the test as to not disrupt
1440             # the original.
1441 14         137 my $mech = $self->clone();
1442              
1443 14         7098 my @failures;
1444              
1445 14         26 for my $url ( @{$urls} ) {
  14         63  
1446 39 100       587 if ( $mech->follow_link( url => $url ) ) {
1447 37 100       2304 if ( $test eq 'is' ) {
1448 30 100       115 push( @failures, $url ) unless $mech->status() == $status;
1449             }
1450             else {
1451 7 100       22 push( @failures, $url ) if $mech->status() == $status;
1452             }
1453 37         358 $mech->back();
1454             }
1455             else {
1456 2         301 push( @failures, $url );
1457             }
1458             } # for
1459              
1460 14         517 return @failures;
1461             }
1462              
1463             # This actually performs the content check of each URL.
1464             sub _check_links_content {
1465 11     11   23 my $self = shift;
1466 11         29 my $urls = shift;
1467 11   33     36 my $regex = shift || qr//;
1468 11   100     66 my $test = shift || 'like';
1469              
1470             # Create a clone of the $mech used during the test as to not disrupt
1471             # the original.
1472 11         58 my $mech = $self->clone();
1473              
1474 11         5078 my @failures;
1475 11         25 for my $url ( @{$urls} ) {
  11         50  
1476 33 50       502 if ( $mech->follow_link( url => $url ) ) {
1477 33         580 my $content=$mech->content();
1478 33 100       1260 if ( $test eq 'like' ) {
1479 18 100       166 push( @failures, $url ) unless $content =~ /$regex/;
1480             }
1481             else {
1482 15 100       136 push( @failures, $url ) if $content =~ /$regex/;
1483             }
1484 33         139 $mech->back();
1485             }
1486             else {
1487 0         0 push( @failures, $url );
1488             }
1489             } # for
1490              
1491 11         496 return @failures;
1492             }
1493              
1494             # Return a list of URLs to match for Mech to follow.
1495             sub _format_links {
1496 25     25   70 my $links = shift;
1497              
1498 25         53 my @urls;
1499 25 100       98 if (ref($links) eq 'ARRAY') {
1500 22         49 my $link = $links->[0];
1501 22 50       120 if ( defined($link) ) {
1502 22 100       91 if ( ref($link) eq 'WWW::Mechanize::Link' ) {
1503 21         43 @urls = map { $_->url() } @{$links};
  67         325  
  21         52  
1504             }
1505             else {
1506 1         4 @urls = @{$links};
  1         4  
1507             }
1508             }
1509             }
1510             else {
1511 3         18 push(@urls,$links);
1512             }
1513 25         240 return @urls;
1514             }
1515              
1516             =head1 METHODS: SCRAPING
1517              
1518             =head2 $mech->scrape_text_by_attr( $attr, $attr_value [, $html ] )
1519              
1520             =head2 $mech->scrape_text_by_attr( $attr, $attr_regex [, $html ] )
1521              
1522             Returns a list of strings, each string the text surrounded by an
1523             element with attribute I<$attr> of value I<$value>. You can also pass in
1524             a regular expression. If nothing is found the return is an empty list.
1525             In scalar context the return is the first string found.
1526              
1527             If passed, I<$html> is scraped instead of the current page's content.
1528              
1529             =cut
1530              
1531             sub scrape_text_by_attr {
1532 0     0 1 0 my $self = shift;
1533 0         0 my $attr = shift;
1534 0         0 my $value = shift;
1535              
1536 0         0 my $html = $self->_get_optional_html( @_ );
1537              
1538 0         0 my @results;
1539              
1540 0 0       0 if ( defined $html ) {
1541 0         0 my $parser = HTML::TokeParser->new(\$html);
1542              
1543 0         0 while ( my $token = $parser->get_tag() ) {
1544 0 0       0 if ( ref $token->[1] eq 'HASH' ) {
1545 0 0       0 if ( exists $token->[1]->{$attr} ) {
1546             my $matched =
1547             (ref $value eq 'Regexp')
1548             ? $token->[1]->{$attr} =~ $value
1549 0 0       0 : $token->[1]->{$attr} eq $value;
1550 0 0       0 if ( $matched ) {
1551 0         0 my $tag = $token->[ 0 ];
1552 0         0 push @results, $parser->get_trimmed_text( "/$tag" );
1553 0 0       0 if ( !wantarray ) {
1554 0         0 last;
1555             }
1556             }
1557             }
1558             }
1559             }
1560             }
1561              
1562 0 0       0 return $results[0] if !wantarray;
1563 0         0 return @results;
1564             }
1565              
1566              
1567             =head2 $mech->scrape_text_by_id( $id [, $html ] )
1568              
1569             Finds all elements with the given ID attribute and pulls out the text that that element encloses.
1570              
1571             In list context, returns a list of all strings found. In scalar context, returns the first one found.
1572              
1573             If C<$html> is not provided then the current content is used.
1574              
1575             =cut
1576              
1577             sub scrape_text_by_id {
1578 28     28 1 125 my $self = shift;
1579 28         74 my $id = shift;
1580              
1581 28         78 my $html = $self->_get_optional_html( @_ );
1582              
1583 28         58 my @results;
1584              
1585 28 50       72 if ( defined $html ) {
1586             # If the ID doesn't appear anywhere in the text, then there's no point in parsing.
1587 28         81 my $found = index( $html, $id );
1588 28 100       76 if ( $found >= 0 ) {
1589 24         126 my $parser = HTML::TokeParser->new( \$html );
1590              
1591 24         3691 while ( my $token = $parser->get_tag() ) {
1592 190 100       5820 if ( ref $token->[1] eq 'HASH' ) {
1593 125         191 my $actual_id = $token->[1]->{id};
1594 125 100       266 $actual_id = '' unless defined $actual_id;
1595 125 100       399 if ( $actual_id eq $id ) {
1596 25         46 my $tag = $token->[ 0 ];
1597 25         97 push @results, $parser->get_trimmed_text( "/$tag" );
1598 25 100       2175 if ( !wantarray ) {
1599 19         131 last;
1600             }
1601             }
1602             }
1603             }
1604             }
1605             }
1606              
1607 28 100       238 return $results[0] if !wantarray;
1608 6         76 return @results;
1609             }
1610              
1611              
1612             sub _get_optional_html {
1613 28     28   42 my $self = shift;
1614              
1615 28         40 my $html;
1616 28 50       71 if ( @_ ) {
1617 0         0 $html = shift;
1618 0         0 assert_nonblank( $html, '$html passed in is a populated scalar' );
1619             }
1620             else {
1621 28 50       88 if ( $self->is_html ) {
1622 28         414 $html = $self->content();
1623             }
1624             }
1625              
1626 28         943 return $html;
1627             }
1628              
1629              
1630             =head2 $mech->scraped_id_is( $id, $expected [, $msg] )
1631              
1632             Scrapes the current page for given ID and tests that it matches the expected value.
1633              
1634             =cut
1635              
1636             sub scraped_id_is {
1637 11     11 1 3506 my $self = shift;
1638 11         32 my $id = shift;
1639 11         26 my $expected = shift;
1640 11         26 my $msg = shift;
1641              
1642 11         22 my $ok;
1643 11         45 my $got = $self->scrape_text_by_id( $id );
1644 11 100       45 if ( defined( $got ) ) {
1645 10         47 $ok = $TB->is_eq( $got, $expected, $msg );
1646             }
1647             else {
1648 1         6 $ok = $TB->ok( 0, $msg );
1649 1         1499 $TB->diag( qq{Can't find ID "$id" to compare to "$expected"} );
1650             }
1651              
1652 11         8174 return $ok;
1653             }
1654              
1655              
1656             =head2 $mech->scraped_id_like( $id, $expected_regex [, $msg] )
1657              
1658             Scrapes the current page for given id and tests that it matches the expected regex.
1659              
1660             =cut
1661              
1662             sub scraped_id_like {
1663 5     5 1 3090 my $self = shift;
1664 5         18 my $id = shift;
1665 5         14 my $expected = shift;
1666 5         11 my $msg = shift;
1667              
1668 5         13 my $ok;
1669 5         18 my $got = $self->scrape_text_by_id( $id );
1670 5 100       22 if ( defined($got) ) {
1671 4         26 $ok = $TB->like( $got, $expected, $msg );
1672             }
1673             else {
1674 1         4 $ok = $TB->ok( 0, $msg );
1675 1         1331 $TB->diag( qq{Can't find ID "$id" to match against $expected} );
1676             }
1677              
1678 5         3562 return $ok;
1679             }
1680              
1681              
1682             =head2 $mech->id_exists( $id )
1683              
1684             Returns TRUE/FALSE if the given ID exists in the given HTML, or if none
1685             is provided, then the current page.
1686              
1687             The Mech object caches the IDs so that it doesn't bother reparsing every
1688             time it's asked about an ID.
1689              
1690             =cut
1691              
1692             sub id_exists {
1693 28     28 1 6414 my $self = shift;
1694 28         57 my $id = shift;
1695              
1696 28         89 assert_is( $self->ct, 'text/html', 'Can only call id_exists on HTML pages' );
1697              
1698 28 100       428 if ( !$self->{ids} ) {
1699 2         6 my $ids = $self->{ids} = {};
1700             my $p = HTML::Parser->new(
1701             handlers => {
1702             start => [
1703             sub {
1704 10     10   127 my $attr = shift;
1705              
1706 10 100       54 if ( my $id = $attr->{id} ) {
1707 4         27 $ids->{$id} = 1;
1708             }
1709             },
1710 2         37 'attr'
1711             ],
1712             },
1713             );
1714 2         118 $p->parse( $self->content );
1715 2         22 $p->eof;
1716             }
1717              
1718 28         95 return $self->{ids}->{$id};
1719             }
1720              
1721              
1722             =head2 $agent->id_exists_ok( $id [, $msg] )
1723              
1724             Verifies there is an HTML element with ID C<$id> in the page.
1725              
1726             =cut
1727              
1728             sub id_exists_ok {
1729 11     11 1 3323 local $Test::Builder::Level = $Test::Builder::Level + 1;
1730              
1731 11         21 my $self = shift;
1732 11         15 my $id = shift;
1733 11   33     73 my $msg = shift || ('ID "' . ($id || '') . '" should exist');
1734              
1735 11         26 my $exists = $self->id_exists( $id );
1736              
1737 11         43 return $TB->ok( $exists, $msg );
1738             }
1739              
1740              
1741             =head2 $agent->ids_exist_ok( \@ids [, $msg] )
1742              
1743             Verifies an HTML element exists with each ID in C<\@ids>.
1744              
1745             =cut
1746              
1747             sub ids_exist_ok {
1748 4     4 1 1458 local $Test::Builder::Level = $Test::Builder::Level + 1;
1749              
1750 4         9 my $self = shift;
1751 4         7 my $ids = shift;
1752 4         14 my $msg = shift;
1753              
1754 4         25 assert_arrayref( $ids );
1755              
1756 4         49 my $subtest_name = 'ids_exist_ok( [' . join( ', ', @{$ids} ) . ']';
  4         20  
1757 4 100       13 $subtest_name .= ", $msg" if defined $msg;
1758 4         9 $subtest_name .= ' )';
1759              
1760             return $TB->subtest(
1761             $subtest_name,
1762             sub {
1763 4     4   4201 $TB->plan( tests => scalar @{$ids} );
  4         24  
1764              
1765 4         2976 foreach my $id ( @$ids ) {
1766 6         835 $self->id_exists_ok( $id );
1767             }
1768             }
1769 4         38 );
1770             }
1771              
1772             =head2 $agent->lacks_id_ok( $id [, $msg] )
1773              
1774             Verifies there is NOT an HTML element with ID C<$id> in the page.
1775              
1776             =cut
1777              
1778             sub lacks_id_ok {
1779 12     12 1 3948 local $Test::Builder::Level = $Test::Builder::Level + 1;
1780              
1781 12         15 my $self = shift;
1782 12         21 my $id = shift;
1783 12   66     41 my $msg = shift || ('ID "' . ($id || '') . '" should not exist');
1784              
1785 12         47 assert_nonblank( $id );
1786              
1787 12         136 my $exists = $self->id_exists( $id );
1788              
1789 12         38 return $TB->ok( !$exists, $msg );
1790             }
1791              
1792              
1793             =head2 $agent->lacks_ids_ok( \@ids [, $msg] )
1794              
1795             Verifies there are no HTML elements with any of the ids given in C<\@ids>.
1796              
1797             =cut
1798              
1799             sub lacks_ids_ok {
1800 4     4 1 1973 local $Test::Builder::Level = $Test::Builder::Level + 1;
1801              
1802 4         8 my $self = shift;
1803 4         6 my $ids = shift;
1804 4         9 my $msg = shift;
1805              
1806 4         15 assert_arrayref( $ids );
1807              
1808 4         34 my $subtest_name = 'lacks_ids_ok( [' . join( ', ', @{$ids} ) . ']';
  4         17  
1809 4 100       15 $subtest_name .= ", $msg" if defined $msg;
1810 4         8 $subtest_name .= ' )';
1811              
1812             return $TB->subtest(
1813             $subtest_name,
1814             sub {
1815 4     4   3422 $TB->plan( tests => scalar @{$ids} );
  4         33  
1816              
1817 4         2664 foreach my $id ( @$ids ) {
1818 9 50       2091 my $id_disp = defined($id) ? $id : '';
1819 9         36 $self->lacks_id_ok( $id, "ID '$id_disp' should not exist" );
1820             }
1821             }
1822 4         56 );
1823             }
1824              
1825              
1826             =head2 $mech->button_exists( $button )
1827              
1828             Returns a boolean saying whether a submit button with the name C<$button> exists. Does not
1829             do a test. For that you want C or C.
1830              
1831             =cut
1832              
1833             sub button_exists {
1834 4     4 1 19 my $self = shift;
1835 4         16 my $button = shift;
1836              
1837 4         84 my $input = $self->grep_inputs( {
1838             type => qr/^submit$/,
1839             name => qr/^$button$/
1840             } );
1841              
1842 4         38 return !!$input;
1843             }
1844              
1845              
1846             =head2 $mech->button_exists_ok( $button [, $msg] )
1847              
1848             Asserts that the button exists on the page.
1849              
1850             =cut
1851              
1852             sub button_exists_ok {
1853 1     1 1 5 local $Test::Builder::Level = $Test::Builder::Level + 1;
1854              
1855 1         2 my $self = shift;
1856 1         2 my $button = shift;
1857 1   33     10 my $msg = shift || qq{Button named "$button" exists};
1858              
1859 1         7 return $TB->ok( $self->button_exists( $button ), $msg );
1860             }
1861              
1862              
1863             =head2 $mech->lacks_button_ok( $button [, $msg] )
1864              
1865             Asserts that no button named C<$button> exists on the page.
1866              
1867             =cut
1868              
1869             sub lacks_button_ok {
1870 1     1 1 3 local $Test::Builder::Level = $Test::Builder::Level + 1;
1871              
1872 1         2 my $self = shift;
1873 1         2 my $button = shift;
1874 1   33     4 my $msg = shift || qq{No button named "$button" exists};
1875              
1876 1         4 return $TB->ok( !$self->button_exists( $button ), $msg );
1877             }
1878              
1879              
1880             =head1 METHODS: MISCELLANEOUS
1881              
1882             =head2 $mech->autolint( [$status] )
1883              
1884             Without an argument, this method returns a true or false value indicating
1885             whether autolint is active.
1886              
1887             When passed an argument, autolint is turned on or off depending on whether
1888             the argument is true or false, and the previous autolint status is returned.
1889             As with the autolint option of C<< new >>, C<< $status >> can be an
1890             L<< HTML::Lint >> object.
1891              
1892             If autolint is currently using an L<< HTML::Lint >> object you provided,
1893             the return is that object, so you can change and exactly restore
1894             autolint status:
1895              
1896             my $old_status = $mech->autolint( 0 );
1897             ... operations that should not be linted ...
1898             $mech->autolint( $old_status );
1899              
1900             =cut
1901              
1902             sub autolint {
1903 128     128 1 319 my $self = shift;
1904              
1905 128         275 my $ret = $self->{autolint};
1906 128 100       469 if ( @_ ) {
1907 40         150 $self->{autolint} = shift;
1908             }
1909              
1910 128         523 return $ret;
1911             }
1912              
1913              
1914             =head2 $mech->autotidy( [$status] )
1915              
1916             Without an argument, this method returns a true or false value indicating
1917             whether autotidy is active.
1918              
1919             When passed an argument, autotidy is turned on or off depending on whether
1920             the argument is true or false, and the previous autotidy status is returned.
1921             As with the autotidy option of C<< new >>, C<< $status >> can be an
1922             L<< HTML::Tidy5 >> object.
1923              
1924             If autotidy is currently using an L<< HTML::Tidy5 >> object you provided,
1925             the return is that object, so you can change and exactly restore
1926             autotidy status:
1927              
1928             my $old_status = $mech->autotidy( 0 );
1929             ... operations that should not be tidied ...
1930             $mech->autotidy( $old_status );
1931              
1932             =cut
1933              
1934             sub autotidy {
1935 84     84 1 214 my $self = shift;
1936              
1937 84         195 my $ret = $self->{autotidy};
1938 84 100       289 if ( @_ ) {
1939 40         101 $self->{autotidy} = shift;
1940             }
1941              
1942 84         256 return $ret;
1943             }
1944              
1945              
1946             =head2 $mech->grep_inputs( \%properties )
1947              
1948             Returns a list of all the input controls in the
1949             current form whose properties match all of the regexes in C<$properties>.
1950             The controls returned are all descended from HTML::Form::Input.
1951              
1952             If C<$properties> is undef or empty then all inputs will be
1953             returned.
1954              
1955             If there is no current page, there is no form on the current
1956             page, or there are no submit controls in the current form
1957             then the return will be an empty list.
1958              
1959             # Get all text controls whose names begin with "customer".
1960             my @customer_text_inputs =
1961             $mech->grep_inputs( {
1962             type => qr/^(text|textarea)$/,
1963             name => qr/^customer/
1964             }
1965             );
1966              
1967             =cut
1968              
1969             sub grep_inputs {
1970 6     6 1 13 my $self = shift;
1971 6         9 my $properties = shift;
1972              
1973 6         11 my @found;
1974              
1975 6         66 my $form = $self->current_form();
1976 6 50       7359 if ( $form ) {
1977 6         20 my @inputs = $form->inputs();
1978 6         53 @found = _grep_hashes( \@inputs, $properties );
1979             }
1980              
1981 6         23 return @found;
1982             }
1983              
1984              
1985             =head2 $mech->grep_submits( \%properties )
1986              
1987             grep_submits() does the same thing as grep_inputs() except that
1988             it only returns controls that are submit controls, ignoring
1989             other types of input controls like text and checkboxes.
1990              
1991             =cut
1992              
1993             sub grep_submits {
1994 0     0 1 0 my $self = shift;
1995 0   0     0 my $properties = shift || {};
1996              
1997 0         0 $properties->{type} = qr/^(?:submit|image)$/; # submits only
1998 0         0 my @found = $self->grep_inputs( $properties );
1999              
2000 0         0 return @found;
2001             }
2002              
2003             # Search an array of hashrefs, returning a list of the incoming
2004             # hashrefs that match *all* the pattern in $patterns.
2005             sub _grep_hashes {
2006 6     6   10 my $hashes = shift;
2007 6   50     17 my $patterns = shift || {};
2008              
2009 6         11 my @found;
2010              
2011 6 50       9 if ( ! %{$patterns} ) {
  6         18  
2012             # Nothing to match on, so return them all.
2013 0         0 @found = @{$hashes};
  0         0  
2014             }
2015             else {
2016 6         10 foreach my $hash ( @{$hashes} ) {
  6         29  
2017              
2018             # Check every pattern for a match on the current hash.
2019 23         36 my $matches_everything = 1;
2020 23         39 foreach my $pattern_key ( keys %{$patterns} ) {
  23         57  
2021 26 100 66     189 $matches_everything = 0 unless exists $hash->{$pattern_key} && $hash->{$pattern_key} =~ $patterns->{$pattern_key};
2022 26 100       67 last if !$matches_everything;
2023             }
2024              
2025 23 100       60 push @found, $hash if $matches_everything;
2026             }
2027             }
2028              
2029 6         33 return @found;
2030             }
2031              
2032              
2033             =head2 $mech->stuff_inputs( [\%options] )
2034              
2035             Finds all free-text input fields (text, textarea, and password) in the
2036             current form and fills them to their maximum length in hopes of finding
2037             application code that can't handle it. Fields with no maximum length
2038             and all textarea fields are set to 66000 bytes, which will often be
2039             enough to overflow the data's eventual receptacle.
2040              
2041             There is no return value.
2042              
2043             If there is no current form then nothing is done.
2044              
2045             The hashref $options can contain the following keys:
2046              
2047             =over
2048              
2049             =item * ignore
2050              
2051             hash value is arrayref of field names to not touch, e.g.:
2052              
2053             $mech->stuff_inputs( {
2054             ignore => [qw( specialfield1 specialfield2 )],
2055             } );
2056              
2057             =item * fill
2058              
2059             hash value is default string to use when stuffing fields. Copies
2060             of the string are repeated up to the max length of each field. E.g.:
2061              
2062             $mech->stuff_inputs( {
2063             fill => '@' # stuff all fields with something easy to recognize
2064             } );
2065              
2066             =item * specs
2067              
2068             hash value is arrayref of hashrefs with which you can pass detailed
2069             instructions about how to stuff a given field. E.g.:
2070              
2071             $mech->stuff_inputs( {
2072             specs=>{
2073             # Some fields are datatype-constrained. It's most common to
2074             # want the field stuffed with valid data.
2075             widget_quantity => { fill=>'9' },
2076             notes => { maxlength=>2000 },
2077             }
2078             } );
2079              
2080             The specs allowed are I (use this fill for the field rather than
2081             the default) and I (use this as the field's maxlength instead
2082             of any maxlength specified in the HTML).
2083              
2084             =back
2085              
2086             =cut
2087              
2088             sub stuff_inputs {
2089 7     7 1 7379 my $self = shift;
2090              
2091 7   100     30 my $options = shift || {};
2092 7         35 assert_isa( $options, 'HASH' );
2093 7         81 assert_in( $_, ['ignore', 'fill', 'specs'] ) foreach ( keys %{$options} );
  7         45  
2094              
2095             # Set up the fill we'll use unless a field overrides it.
2096 7         213 my $default_fill = '@';
2097 7 100 66     31 if ( defined $options->{fill} && length($options->{fill}) > 0 ) {
2098 3         6 $default_fill = $options->{fill};
2099             }
2100              
2101             # Fields in the form to not stuff
2102 7         12 my $ignore = {};
2103 7 100       20 if ( exists $options->{ignore} ) {
2104 1         4 assert_isa( $options->{ignore}, 'ARRAY' );
2105 1         9 $ignore = { map {($_, 1)} @{$options->{ignore}} };
  1         5  
  1         3  
2106             }
2107              
2108 7         13 my $specs = {};
2109 7 100       21 if ( exists $options->{specs} ) {
2110 2         8 assert_isa( $options->{specs}, 'HASH' );
2111 2         19 $specs = $options->{specs};
2112 2         9 foreach my $field_name ( keys %{$specs} ) {
  2         12  
2113 4         88 assert_isa( $specs->{$field_name}, 'HASH' );
2114 4         30 assert_in( $_, ['fill', 'maxlength'] ) foreach ( keys %{$specs->{$field_name}} );
  4         21  
2115             }
2116             }
2117              
2118 7         109 my @inputs = $self->find_all_inputs( type_regex => qr/^(text|textarea|password)$/ );
2119              
2120 7         918 foreach my $field ( @inputs ) {
2121 42 50       331 next if $field->readonly();
2122 42 50       311 next if $field->disabled(); # TODO: HTML::Form::TextInput allows setting disabled--allow it here?
2123              
2124 42         294 my $name = $field->name();
2125              
2126             # Skip if it's one of the fields to ignore.
2127 42 100       296 next if exists $ignore->{ $name };
2128              
2129             # Fields with no maxlength will get this many characters.
2130 41         58 my $maxlength = 66000;
2131              
2132             # maxlength from the HTML
2133 41 100       77 if ( $field->type ne 'textarea' ) {
2134 34 100       143 if ( exists $field->{maxlength} ) {
2135 27         39 $maxlength = $field->{maxlength};
2136             # TODO: what to do about maxlength==0 ? non-numeric? less than 0 ?
2137             }
2138             }
2139              
2140 41         76 my $fill = $default_fill;
2141              
2142 41 100       80 if ( exists $specs->{$name} ) {
2143             # Process the per-field info.
2144              
2145 4 50 66     40 if ( exists $specs->{$name}->{fill} && defined $specs->{$name}->{fill} && length($specs->{$name}->{fill}) > 0 ) {
      66        
2146 3         8 $fill = $specs->{$name}->{fill};
2147             }
2148              
2149             # maxlength override from specs
2150 4 100 66     27 if ( exists $specs->{$name}->{maxlength} && defined $specs->{$name}->{maxlength} ) {
2151 2         6 $maxlength = $specs->{$name}->{maxlength};
2152             # TODO: what to do about maxlength==0 ? non-numeric? less than 0?
2153             }
2154             }
2155              
2156             # stuff it
2157 41 100       90 if ( ($maxlength % length($fill)) == 0 ) {
2158 38         473 $field->value( $fill x ($maxlength/length($fill)) );
2159             }
2160             else {
2161 3         24 $field->value( substr( $fill x int(($maxlength + length($fill) - 1)/length($fill)), 0, $maxlength ) );
2162             }
2163             } # for @inputs
2164              
2165 7         83 return;
2166             }
2167              
2168              
2169             =head2 $mech->followable_links()
2170              
2171             Returns a list of links that Mech can follow. This is only http and
2172             https links.
2173              
2174             =cut
2175              
2176             sub followable_links {
2177 9     9 1 41 my $self = shift;
2178              
2179 9         146 return $self->find_all_links( url_abs_regex => qr{^(?:https?|file)://} );
2180             }
2181              
2182              
2183             =head2 $mech->lacks_uncapped_inputs( [$comment] )
2184              
2185             Executes a test to make sure that the current form content has no
2186             text input fields that lack the C attribute, and that each
2187             C value is a positive integer. The test fails if the current
2188             form has such a field, and succeeds otherwise.
2189              
2190             Checks that all text input fields in the current form specify a maximum
2191             input length. Fields for which the concept of input length is irrelevant,
2192             and controls that HTML does not allow to be capped (e.g. textarea)
2193             are ignored.
2194              
2195             The return is true if the test succeeded, false otherwise.
2196              
2197             =cut
2198              
2199             sub lacks_uncapped_inputs {
2200 2     2 1 4068 my $self = shift;
2201 2         4 my $comment = shift;
2202              
2203 2 50       8 $comment = 'All text inputs should have maxlength attributes' unless defined($comment);
2204              
2205 2         5 local $Test::Builder::Level = $Test::Builder::Level + 1;
2206              
2207 2         4 my @uncapped;
2208              
2209 2         22 my @inputs = $self->grep_inputs( { type => qr/^(?:text|password)$/ } );
2210 2         23 foreach my $field ( @inputs ) {
2211 9 50       85 next if $field->readonly();
2212 9 100       87 next if $field->disabled();
2213              
2214 7 100       60 if ( not defined($field->{maxlength}) ) {
2215 1         24 push( @uncapped, $field->name . ' has no maxlength attribute' );
2216 1         20 next;
2217             }
2218              
2219 6         15 my $val = $field->{maxlength};
2220 6 100 66     83 if ( ($val !~ /^\s*\d+\s*$/) || ($val+0 <= 0) ) {
2221 4         19 push( @uncapped, $field->name . qq{ has an invalid maxlength attribute of "$val"} );
2222             }
2223             }
2224              
2225 2         25 my $ok = $TB->ok( @uncapped == 0, $comment );
2226 2         1690 $TB->diag( $_ ) for @uncapped;
2227              
2228 2         1284 return $ok;
2229             }
2230              
2231             =head2 $mech->check_all_images_ok( [%criterium ], [$comment] )
2232              
2233             Executes a test to make sure all images in the page can be downloaded. It
2234             does this by running C requests on them. The current page content stays the same.
2235              
2236             The test fails if any image cannot be found, but reports all of the ones that were not found.
2237              
2238             For a definition of I, see L<< Cin WWW::Mechanize|WWW::Mechanize/$mech->images >>.
2239              
2240             The optional C<%criterium> argument can be passed in before the C<$comment> and will be used to define
2241             which images should be considered. This is useful to filter out specific paths.
2242              
2243             $mech->check_all_images_ok( url_regex => qr{^/}, 'All absolute images should exist');
2244             $mech->check_all_images_ok( url_regex => qr{\.(?:gif|jpg)$}, 'All gif and jpg images should exist');
2245             $mech->check_all_images_ok(
2246             url_regex => qr{^((?!\Qhttps://googleads.g.doubleclick.net/\E).)*$},
2247             'All images should exist, but Ignore the ones from Doubleclick'
2248             );
2249              
2250             For a full list of possible arguments see L<< Cin WWW::Mechanize|WWW::Mechanize/$mech->find_all_images >>.
2251              
2252             The return is true if the test succeeded, false otherwise.
2253              
2254             =cut
2255              
2256             sub check_all_images_ok {
2257 0     0 1 0 my $self = shift;
2258 0         0 my @args = @_;
2259              
2260 0         0 my $comment;
2261 0 0       0 if ( @args % 2 ) {
2262 0         0 $comment = pop @args;
2263             }
2264              
2265 0 0       0 $comment = 'All images in the page should exist' unless defined($comment);
2266              
2267 0         0 require HTTP::Request::Common;
2268              
2269 0         0 my @not_ok;
2270 0         0 foreach my $img ( map { $_->URI } $self->find_all_images(@args) ) {
  0         0  
2271 0         0 my $abs = $img->abs;
2272              
2273 0         0 state $head_cache; # Cache images we've already checked between calls.
2274 0 0       0 if ( !$head_cache->{$abs}++ ) {
2275             # WWW::Mechanize->_make_request makes a raw LWP::UserAgent request that does
2276             # not show up in our history and does not mess with our current content.
2277 0         0 my $res = $self->_make_request( HTTP::Request::Common::HEAD($abs) );
2278 0 0       0 if ( not $res->is_success ) {
2279 0         0 push( @not_ok, $img . ' returned code ' . $res->code );
2280             }
2281             }
2282             }
2283              
2284 0         0 my $ok = $TB->ok( @not_ok == 0, $comment );
2285 0         0 $TB->diag($_) for @not_ok;
2286              
2287 0         0 return $ok;
2288             }
2289              
2290              
2291             sub _diag_url {
2292 3     3   56 my $self = shift;
2293              
2294 3         109 my $uri = $self->uri;
2295              
2296 3 50       164 return $uri ? $uri->as_string : 'Unable to determine URL';
2297             }
2298              
2299              
2300             =head1 TODO
2301              
2302             Other ideas for features are at https://github.com/petdance/test-www-mechanize
2303              
2304             =head1 AUTHOR
2305              
2306             Andy Lester, C<< >>
2307              
2308             =head1 BUGS
2309              
2310             Please report any bugs or feature requests to
2311             .
2312              
2313             =head1 SUPPORT
2314              
2315             You can find documentation for this module with the perldoc command.
2316              
2317             perldoc Test::WWW::Mechanize
2318              
2319             You can also look for information at:
2320              
2321             =over 4
2322              
2323             =item * Bug tracker
2324              
2325             L
2326              
2327             =item * CPAN Ratings
2328              
2329             L
2330              
2331             =item * Search CPAN
2332              
2333             L
2334              
2335             =back
2336              
2337             =head1 ACKNOWLEDGEMENTS
2338              
2339             Thanks to
2340             Julien Fiegehenn,
2341             @marderh,
2342             Eric A. Zarko,
2343             @moznion,
2344             Robert Stone,
2345             @tynovsky,
2346             Jerry Gay,
2347             Jonathan "Duke" Leto,
2348             Philip G. Potter,
2349             Niko Tyni,
2350             Greg Sheard,
2351             Michael Schwern,
2352             Mark Blackman,
2353             Mike O'Regan,
2354             Shawn Sorichetti,
2355             Chris Dolan,
2356             Matt Trout,
2357             MATSUNO Tokuhiro,
2358             and Pete Krawczyk for patches.
2359              
2360             =head1 COPYRIGHT & LICENSE
2361              
2362             Copyright 2004-2022 Andy Lester.
2363              
2364             This library is free software; you can redistribute it and/or modify it
2365             under the terms of the Artistic License version 2.0.
2366              
2367             =cut
2368              
2369             1; # End of Test::WWW::Mechanize