File Coverage

blib/lib/Test/WWW/Mechanize.pm
Criterion Covered Total %
statement 605 802 75.4
branch 169 278 60.7
condition 44 88 50.0
subroutine 72 91 79.1
pod 61 61 100.0
total 951 1320 72.0


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