line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Test::WWW::Mechanize::Catalyst; |
2
|
|
|
|
|
|
|
|
3
|
7
|
|
|
7
|
|
144336
|
use Moose; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
use Carp qw/croak/; |
6
|
|
|
|
|
|
|
require Catalyst::Test; # Do not call import |
7
|
|
|
|
|
|
|
use Class::Load qw(load_class is_class_loaded); |
8
|
|
|
|
|
|
|
use Encode qw(); |
9
|
|
|
|
|
|
|
use HTML::Entities; |
10
|
|
|
|
|
|
|
use Test::WWW::Mechanize; |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
extends 'Test::WWW::Mechanize', 'Moose::Object'; |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
#use namespace::clean -except => 'meta'; |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
our $VERSION = '0.60'; |
17
|
|
|
|
|
|
|
our $APP_CLASS; |
18
|
|
|
|
|
|
|
my $Test = Test::Builder->new(); |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
has catalyst_app => ( |
21
|
|
|
|
|
|
|
is => 'ro', |
22
|
|
|
|
|
|
|
predicate => 'has_catalyst_app', |
23
|
|
|
|
|
|
|
); |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
has allow_external => ( |
26
|
|
|
|
|
|
|
is => 'rw', |
27
|
|
|
|
|
|
|
isa => 'Bool', |
28
|
|
|
|
|
|
|
default => 0 |
29
|
|
|
|
|
|
|
); |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
has host => ( |
32
|
|
|
|
|
|
|
is => 'rw', |
33
|
|
|
|
|
|
|
isa => 'Str', |
34
|
|
|
|
|
|
|
clearer => 'clear_host', |
35
|
|
|
|
|
|
|
predicate => 'has_host', |
36
|
|
|
|
|
|
|
); |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
sub new { |
39
|
|
|
|
|
|
|
my $class = shift; |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
my $args = ref $_[0] ? $_[0] : { @_ }; |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
# Dont let LWP complain about options for our attributes |
44
|
|
|
|
|
|
|
my %attr_options = map { |
45
|
|
|
|
|
|
|
my $n = $_->init_arg; |
46
|
|
|
|
|
|
|
defined $n && exists $args->{$n} |
47
|
|
|
|
|
|
|
? ( $n => delete $args->{$n} ) |
48
|
|
|
|
|
|
|
: ( ); |
49
|
|
|
|
|
|
|
} $class->meta->get_all_attributes; |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
my $obj = $class->SUPER::new(%$args); |
52
|
|
|
|
|
|
|
my $self = $class->meta->new_object( |
53
|
|
|
|
|
|
|
__INSTANCE__ => $obj, |
54
|
|
|
|
|
|
|
($APP_CLASS ? (catalyst_app => $APP_CLASS) : () ), |
55
|
|
|
|
|
|
|
%attr_options |
56
|
|
|
|
|
|
|
); |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
$self->BUILDALL; |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
return $self; |
62
|
|
|
|
|
|
|
} |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
sub BUILD { |
65
|
|
|
|
|
|
|
my ($self) = @_; |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
unless ($ENV{CATALYST_SERVER}) { |
68
|
|
|
|
|
|
|
croak "catalyst_app attribute is required unless CATALYST_SERVER env variable is set" |
69
|
|
|
|
|
|
|
unless $self->has_catalyst_app; |
70
|
|
|
|
|
|
|
load_class($self->catalyst_app) |
71
|
|
|
|
|
|
|
unless (is_class_loaded($self->catalyst_app)); |
72
|
|
|
|
|
|
|
} |
73
|
|
|
|
|
|
|
} |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
sub _make_request { |
76
|
|
|
|
|
|
|
my ( $self, $request, $arg, $size, $previous) = @_; |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
my $response = $self->_do_catalyst_request($request); |
79
|
|
|
|
|
|
|
$response->header( 'Content-Base', $response->request->uri ) |
80
|
|
|
|
|
|
|
unless $response->header('Content-Base'); |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
$self->cookie_jar->extract_cookies($response) if $self->cookie_jar; |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
# fail tests under the Catalyst debug screen |
85
|
|
|
|
|
|
|
if ( !$self->{catalyst_debug} |
86
|
|
|
|
|
|
|
&& $response->code == 500 |
87
|
|
|
|
|
|
|
&& $response->content =~ /on Catalyst \d+\.\d+/ ) |
88
|
|
|
|
|
|
|
{ |
89
|
|
|
|
|
|
|
my ($error) |
90
|
|
|
|
|
|
|
= ( $response->content =~ /<code class="error">(.*?)<\/code>/s ); |
91
|
|
|
|
|
|
|
$error ||= "unknown error"; |
92
|
|
|
|
|
|
|
decode_entities($error); |
93
|
|
|
|
|
|
|
$Test->diag("Catalyst error screen: $error"); |
94
|
|
|
|
|
|
|
$response->content(''); |
95
|
|
|
|
|
|
|
$response->content_type(''); |
96
|
|
|
|
|
|
|
} |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
# NOTE: cargo-culted redirect checking from LWP::UserAgent: |
99
|
|
|
|
|
|
|
$response->previous($previous) if $previous; |
100
|
|
|
|
|
|
|
my $redirects = defined $response->redirects ? $response->redirects : 0; |
101
|
|
|
|
|
|
|
if ($redirects > 0 and $redirects >= $self->max_redirect) { |
102
|
|
|
|
|
|
|
return $self->_redirect_loop_detected($response); |
103
|
|
|
|
|
|
|
} |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
# check if that was a redirect |
106
|
|
|
|
|
|
|
if ( $response->header('Location') |
107
|
|
|
|
|
|
|
&& $response->is_redirect |
108
|
|
|
|
|
|
|
&& $self->redirect_ok( $request, $response ) ) |
109
|
|
|
|
|
|
|
{ |
110
|
|
|
|
|
|
|
return $self->_redirect_loop_detected($response) if $self->max_redirect <= 0; |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
# TODO: this should probably create the request by cloning the original |
113
|
|
|
|
|
|
|
# request and modifying it as LWP::UserAgent::request does. But for now... |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
# *where* do they want us to redirect to? |
116
|
|
|
|
|
|
|
my $location = $response->header('Location'); |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
# no-one *should* be returning non-absolute URLs, but if they |
119
|
|
|
|
|
|
|
# are then we'd better cope with it. Let's create a new URI, using |
120
|
|
|
|
|
|
|
# our request as the base. |
121
|
|
|
|
|
|
|
my $uri = URI->new_abs( $location, $request->uri )->as_string; |
122
|
|
|
|
|
|
|
my $referral = HTTP::Request->new( GET => $uri ); |
123
|
|
|
|
|
|
|
return $self->request( $referral, $arg, $size, $response ); |
124
|
|
|
|
|
|
|
} else { |
125
|
|
|
|
|
|
|
$response->{_raw_content} = $response->content; |
126
|
|
|
|
|
|
|
} |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
return $response; |
129
|
|
|
|
|
|
|
} |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
sub _redirect_loop_detected { |
132
|
|
|
|
|
|
|
my ( $self, $response ) = @_; |
133
|
|
|
|
|
|
|
$response->header("Client-Warning" => |
134
|
|
|
|
|
|
|
"Redirect loop detected (max_redirect = " . $self->max_redirect . ")"); |
135
|
|
|
|
|
|
|
$response->{_raw_content} = $response->content; |
136
|
|
|
|
|
|
|
return $response; |
137
|
|
|
|
|
|
|
} |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
sub _set_host_header { |
140
|
|
|
|
|
|
|
my ( $self, $request ) = @_; |
141
|
|
|
|
|
|
|
# If there's no Host header, set one. |
142
|
|
|
|
|
|
|
unless ($request->header('Host')) { |
143
|
|
|
|
|
|
|
my $host = $self->has_host |
144
|
|
|
|
|
|
|
? $self->host |
145
|
|
|
|
|
|
|
: $request->uri->host; |
146
|
|
|
|
|
|
|
$host .= ':'.$request->uri->_port if $request->uri->_port; |
147
|
|
|
|
|
|
|
$request->header('Host', $host); |
148
|
|
|
|
|
|
|
} |
149
|
|
|
|
|
|
|
} |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
sub _do_catalyst_request { |
152
|
|
|
|
|
|
|
my ($self, $request) = @_; |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
my $uri = $request->uri; |
155
|
|
|
|
|
|
|
$uri->scheme('http') unless defined $uri->scheme; |
156
|
|
|
|
|
|
|
$uri->host('localhost') unless defined $uri->host; |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
$request = $self->prepare_request($request); |
159
|
|
|
|
|
|
|
$self->cookie_jar->add_cookie_header($request) if $self->cookie_jar; |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
# Woe betide anyone who unsets CATALYST_SERVER |
162
|
|
|
|
|
|
|
return $self->_do_remote_request($request) |
163
|
|
|
|
|
|
|
if $ENV{CATALYST_SERVER}; |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
$self->_set_host_header($request); |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
my $res = $self->_check_external_request($request); |
168
|
|
|
|
|
|
|
return $res if $res; |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
my @creds = $self->get_basic_credentials( "Basic", $uri ); |
171
|
|
|
|
|
|
|
$request->authorization_basic( @creds ) if @creds; |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
require Catalyst; |
174
|
|
|
|
|
|
|
my $response = $Catalyst::VERSION >= 5.89000 ? |
175
|
|
|
|
|
|
|
Catalyst::Test::_local_request($self->{catalyst_app}, $request) : |
176
|
|
|
|
|
|
|
Catalyst::Test::local_request($self->{catalyst_app}, $request); |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
# LWP would normally do this, but we don't get down that far. |
180
|
|
|
|
|
|
|
$response->request($request); |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
return $response |
183
|
|
|
|
|
|
|
} |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
sub _check_external_request { |
186
|
|
|
|
|
|
|
my ($self, $request) = @_; |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
# If there's no host then definitley not an external request. |
189
|
|
|
|
|
|
|
$request->uri->can('host_port') or return; |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
if ( $self->allow_external && $request->uri->host_port ne 'localhost:80' ) { |
192
|
|
|
|
|
|
|
return $self->SUPER::_make_request($request); |
193
|
|
|
|
|
|
|
} |
194
|
|
|
|
|
|
|
return undef; |
195
|
|
|
|
|
|
|
} |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
sub _do_remote_request { |
198
|
|
|
|
|
|
|
my ($self, $request) = @_; |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
my $res = $self->_check_external_request($request); |
201
|
|
|
|
|
|
|
return $res if $res; |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
my $server = URI->new( $ENV{CATALYST_SERVER} ); |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
if ( $server->path =~ m|^(.+)?/$| ) { |
206
|
|
|
|
|
|
|
my $path = $1; |
207
|
|
|
|
|
|
|
$server->path("$path") if $path; # need to be quoted |
208
|
|
|
|
|
|
|
} |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
# the request path needs to be sanitised if $server is using a |
211
|
|
|
|
|
|
|
# non-root path due to potential overlap between request path and |
212
|
|
|
|
|
|
|
# response path. |
213
|
|
|
|
|
|
|
if ($server->path) { |
214
|
|
|
|
|
|
|
# If request path is '/', we have to add a trailing slash to the |
215
|
|
|
|
|
|
|
# final request URI |
216
|
|
|
|
|
|
|
my $add_trailing = $request->uri->path eq '/'; |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
my @sp = split '/', $server->path; |
219
|
|
|
|
|
|
|
my @rp = split '/', $request->uri->path; |
220
|
|
|
|
|
|
|
shift @sp;shift @rp; # leading / |
221
|
|
|
|
|
|
|
if (@rp) { |
222
|
|
|
|
|
|
|
foreach my $sp (@sp) { |
223
|
|
|
|
|
|
|
$sp eq $rp[0] ? shift @rp : last |
224
|
|
|
|
|
|
|
} |
225
|
|
|
|
|
|
|
} |
226
|
|
|
|
|
|
|
$request->uri->path(join '/', @rp); |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
if ( $add_trailing ) { |
229
|
|
|
|
|
|
|
$request->uri->path( $request->uri->path . '/' ); |
230
|
|
|
|
|
|
|
} |
231
|
|
|
|
|
|
|
} |
232
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
$request->uri->scheme( $server->scheme ); |
234
|
|
|
|
|
|
|
$request->uri->host( $server->host ); |
235
|
|
|
|
|
|
|
$request->uri->port( $server->port ); |
236
|
|
|
|
|
|
|
$request->uri->path( $server->path . $request->uri->path ); |
237
|
|
|
|
|
|
|
$self->_set_host_header($request); |
238
|
|
|
|
|
|
|
return $self->SUPER::_make_request($request); |
239
|
|
|
|
|
|
|
} |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
sub import { |
242
|
|
|
|
|
|
|
my ($class, $app) = @_; |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
if (defined $app) { |
245
|
|
|
|
|
|
|
load_class($app) |
246
|
|
|
|
|
|
|
unless (is_class_loaded($app)); |
247
|
|
|
|
|
|
|
$APP_CLASS = $app; |
248
|
|
|
|
|
|
|
} |
249
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
} |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
1; |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
__END__ |
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
=head1 NAME |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
Test::WWW::Mechanize::Catalyst - Test::WWW::Mechanize for Catalyst |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
=head1 SYNOPSIS |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
# We're in a t/*.t test script... |
264
|
|
|
|
|
|
|
use Test::WWW::Mechanize::Catalyst; |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
# To test a Catalyst application named 'Catty': |
267
|
|
|
|
|
|
|
my $mech = Test::WWW::Mechanize::Catalyst->new(catalyst_app => 'Catty'); |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
$mech->get_ok("/"); # no hostname needed |
270
|
|
|
|
|
|
|
is($mech->ct, "text/html"); |
271
|
|
|
|
|
|
|
$mech->title_is("Root", "On the root page"); |
272
|
|
|
|
|
|
|
$mech->content_contains("This is the root page", "Correct content"); |
273
|
|
|
|
|
|
|
$mech->follow_link_ok({text => 'Hello'}, "Click on Hello"); |
274
|
|
|
|
|
|
|
# ... and all other Test::WWW::Mechanize methods |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
# White label site testing |
277
|
|
|
|
|
|
|
$mech->host("foo.com"); |
278
|
|
|
|
|
|
|
$mech->get_ok("/"); |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
=head1 DESCRIPTION |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
L<Catalyst> is an elegant MVC Web Application Framework. |
283
|
|
|
|
|
|
|
L<Test::WWW::Mechanize> is a subclass of L<WWW::Mechanize> that incorporates |
284
|
|
|
|
|
|
|
features for web application testing. The L<Test::WWW::Mechanize::Catalyst> |
285
|
|
|
|
|
|
|
module meshes the two to allow easy testing of L<Catalyst> applications without |
286
|
|
|
|
|
|
|
needing to start up a web server. |
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
Testing web applications has always been a bit tricky, normally |
289
|
|
|
|
|
|
|
requiring starting a web server for your application and making real HTTP |
290
|
|
|
|
|
|
|
requests to it. This module allows you to test L<Catalyst> web |
291
|
|
|
|
|
|
|
applications but does not require a server or issue HTTP |
292
|
|
|
|
|
|
|
requests. Instead, it passes the HTTP request object directly to |
293
|
|
|
|
|
|
|
L<Catalyst>. Thus you do not need to use a real hostname: |
294
|
|
|
|
|
|
|
"http://localhost/" will do. However, this is optional. The following |
295
|
|
|
|
|
|
|
two lines of code do exactly the same thing: |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
$mech->get_ok('/action'); |
298
|
|
|
|
|
|
|
$mech->get_ok('http://localhost/action'); |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
Links which do not begin with / or are not for localhost can be handled |
301
|
|
|
|
|
|
|
as normal Web requests - this is handy if you have an external |
302
|
|
|
|
|
|
|
single sign-on system. You must set allow_external to true for this: |
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
$mech->allow_external(1); |
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
You can also test a remote server by setting the environment variable |
307
|
|
|
|
|
|
|
CATALYST_SERVER; for example: |
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
$ CATALYST_SERVER=http://example.com/myapp prove -l t |
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
will run the same tests on the application running at |
312
|
|
|
|
|
|
|
http://example.com/myapp regardless of whether or not you specify |
313
|
|
|
|
|
|
|
http:://localhost for Test::WWW::Mechanize::Catalyst. |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
Furthermore, if you set CATALYST_SERVER, the server will be regarded |
316
|
|
|
|
|
|
|
as a remote server even if your links point to localhost. Thus, you |
317
|
|
|
|
|
|
|
can use Test::WWW::Mechanize::Catalyst to test your live webserver |
318
|
|
|
|
|
|
|
running on your local machine, if you need to test aspects of your |
319
|
|
|
|
|
|
|
deployment environment (for example, configuration options in an |
320
|
|
|
|
|
|
|
http.conf file) instead of just the Catalyst request handling. |
321
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
This makes testing fast and easy. L<Test::WWW::Mechanize> provides |
323
|
|
|
|
|
|
|
functions for common web testing scenarios. For example: |
324
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
$mech->get_ok( $page ); |
326
|
|
|
|
|
|
|
$mech->title_is( "Invoice Status", "Make sure we're on the invoice page" ); |
327
|
|
|
|
|
|
|
$mech->content_contains( "Andy Lester", "My name somewhere" ); |
328
|
|
|
|
|
|
|
$mech->content_like( qr/(cpan|perl)\.org/, "Link to perl.org or CPAN" ); |
329
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
This module supports cookies automatically. |
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
To use this module you must pass it the name of the application. See |
333
|
|
|
|
|
|
|
the SYNOPSIS above. |
334
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
Note that Catalyst has a special development feature: the debug |
336
|
|
|
|
|
|
|
screen. By default this module will treat responses which are the |
337
|
|
|
|
|
|
|
debug screen as failures. If you actually want to test debug screens, |
338
|
|
|
|
|
|
|
please use: |
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
$mech->{catalyst_debug} = 1; |
341
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
An alternative to this module is L<Catalyst::Test>. |
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
=head1 CONSTRUCTOR |
345
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
=head2 new |
347
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
Behaves like, and calls, L<WWW::Mechanize>'s C<new> method. Any params |
349
|
|
|
|
|
|
|
passed in get passed to WWW::Mechanize's constructor. Note that we |
350
|
|
|
|
|
|
|
need to pass the name of the Catalyst application to the "use": |
351
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
use Test::WWW::Mechanize::Catalyst 'Catty'; |
353
|
|
|
|
|
|
|
my $mech = Test::WWW::Mechanize::Catalyst->new; |
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
=head1 METHODS |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
=head2 allow_external |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
Links which do not begin with / or are not for localhost can be handled |
360
|
|
|
|
|
|
|
as normal Web requests - this is handy if you have an external |
361
|
|
|
|
|
|
|
single sign-on system. You must set allow_external to true for this: |
362
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
$mech->allow_external(1); |
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
head2 catalyst_app |
366
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
The name of the Catalyst app which we are testing against. Read-only. |
368
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
=head2 host |
370
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
The host value to set the "Host:" HTTP header to, if none is present already in |
372
|
|
|
|
|
|
|
the request. If not set (default) then Catalyst::Test will set this to |
373
|
|
|
|
|
|
|
localhost:80 |
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
=head2 clear_host |
376
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
Unset the host attribute. |
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
=head2 has_host |
380
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
Do we have a value set for the host attribute |
382
|
|
|
|
|
|
|
|
383
|
|
|
|
|
|
|
=head2 $mech->get_ok($url, [ \%LWP_options ,] $desc) |
384
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
A wrapper around WWW::Mechanize's get(), with similar options, except the |
386
|
|
|
|
|
|
|
second argument needs to be a hash reference, not a hash. Returns true or |
387
|
|
|
|
|
|
|
false. |
388
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
=head2 $mech->title_is( $str [, $desc ] ) |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
Tells if the title of the page is the given string. |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
$mech->title_is( "Invoice Summary" ); |
394
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
=head2 $mech->title_like( $regex [, $desc ] ) |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
Tells if the title of the page matches the given regex. |
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
$mech->title_like( qr/Invoices for (.+)/ |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
=head2 $mech->title_unlike( $regex [, $desc ] ) |
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
Tells if the title of the page does NOT match the given regex. |
404
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
$mech->title_unlike( qr/Invoices for (.+)/ |
406
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
=head2 $mech->content_is( $str [, $desc ] ) |
408
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
Tells if the content of the page matches the given string. |
410
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
=head2 $mech->content_contains( $str [, $desc ] ) |
412
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
Tells if the content of the page contains I<$str>. |
414
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
=head2 $mech->content_lacks( $str [, $desc ] ) |
416
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
Tells if the content of the page lacks I<$str>. |
418
|
|
|
|
|
|
|
|
419
|
|
|
|
|
|
|
=head2 $mech->content_like( $regex [, $desc ] ) |
420
|
|
|
|
|
|
|
|
421
|
|
|
|
|
|
|
Tells if the content of the page matches I<$regex>. |
422
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
=head2 $mech->content_unlike( $regex [, $desc ] ) |
424
|
|
|
|
|
|
|
|
425
|
|
|
|
|
|
|
Tells if the content of the page does NOT match I<$regex>. |
426
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
=head2 $mech->page_links_ok( [ $desc ] ) |
428
|
|
|
|
|
|
|
|
429
|
|
|
|
|
|
|
Follow all links on the current page and test for HTTP status 200 |
430
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
$mech->page_links_ok('Check all links'); |
432
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
=head2 $mech->page_links_content_like( $regex,[ $desc ] ) |
434
|
|
|
|
|
|
|
|
435
|
|
|
|
|
|
|
Follow all links on the current page and test their contents for I<$regex>. |
436
|
|
|
|
|
|
|
|
437
|
|
|
|
|
|
|
$mech->page_links_content_like( qr/foo/, |
438
|
|
|
|
|
|
|
'Check all links contain "foo"' ); |
439
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
=head2 $mech->page_links_content_unlike( $regex,[ $desc ] ) |
441
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
Follow all links on the current page and test their contents do not |
443
|
|
|
|
|
|
|
contain the specified regex. |
444
|
|
|
|
|
|
|
|
445
|
|
|
|
|
|
|
$mech->page_links_content_unlike(qr/Restricted/, |
446
|
|
|
|
|
|
|
'Check all links do not contain Restricted'); |
447
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
=head2 $mech->links_ok( $links [, $desc ] ) |
449
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
Check the current page for specified links and test for HTTP status |
451
|
|
|
|
|
|
|
200. The links may be specified as a reference to an array containing |
452
|
|
|
|
|
|
|
L<WWW::Mechanize::Link> objects, an array of URLs, or a scalar URL |
453
|
|
|
|
|
|
|
name. |
454
|
|
|
|
|
|
|
|
455
|
|
|
|
|
|
|
my @links = $mech->find_all_links( url_regex => qr/cnn\.com$/ ); |
456
|
|
|
|
|
|
|
$mech->links_ok( \@links, 'Check all links for cnn.com' ); |
457
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
my @links = qw( index.html search.html about.html ); |
459
|
|
|
|
|
|
|
$mech->links_ok( \@links, 'Check main links' ); |
460
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
$mech->links_ok( 'index.html', 'Check link to index' ); |
462
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
=head2 $mech->link_status_is( $links, $status [, $desc ] ) |
464
|
|
|
|
|
|
|
|
465
|
|
|
|
|
|
|
Check the current page for specified links and test for HTTP status |
466
|
|
|
|
|
|
|
passed. The links may be specified as a reference to an array |
467
|
|
|
|
|
|
|
containing L<WWW::Mechanize::Link> objects, an array of URLs, or a |
468
|
|
|
|
|
|
|
scalar URL name. |
469
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
my @links = $mech->links(); |
471
|
|
|
|
|
|
|
$mech->link_status_is( \@links, 403, |
472
|
|
|
|
|
|
|
'Check all links are restricted' ); |
473
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
=head2 $mech->link_status_isnt( $links, $status [, $desc ] ) |
475
|
|
|
|
|
|
|
|
476
|
|
|
|
|
|
|
Check the current page for specified links and test for HTTP status |
477
|
|
|
|
|
|
|
passed. The links may be specified as a reference to an array |
478
|
|
|
|
|
|
|
containing L<WWW::Mechanize::Link> objects, an array of URLs, or a |
479
|
|
|
|
|
|
|
scalar URL name. |
480
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
my @links = $mech->links(); |
482
|
|
|
|
|
|
|
$mech->link_status_isnt( \@links, 404, |
483
|
|
|
|
|
|
|
'Check all links are not 404' ); |
484
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
=head2 $mech->link_content_like( $links, $regex [, $desc ] ) |
486
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
Check the current page for specified links and test the content of |
488
|
|
|
|
|
|
|
each against I<$regex>. The links may be specified as a reference to |
489
|
|
|
|
|
|
|
an array containing L<WWW::Mechanize::Link> objects, an array of URLs, |
490
|
|
|
|
|
|
|
or a scalar URL name. |
491
|
|
|
|
|
|
|
|
492
|
|
|
|
|
|
|
my @links = $mech->links(); |
493
|
|
|
|
|
|
|
$mech->link_content_like( \@links, qr/Restricted/, |
494
|
|
|
|
|
|
|
'Check all links are restricted' ); |
495
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
=head2 $mech->link_content_unlike( $links, $regex [, $desc ] ) |
497
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
Check the current page for specified links and test that the content of each |
499
|
|
|
|
|
|
|
does not match I<$regex>. The links may be specified as a reference to |
500
|
|
|
|
|
|
|
an array containing L<WWW::Mechanize::Link> objects, an array of URLs, |
501
|
|
|
|
|
|
|
or a scalar URL name. |
502
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
my @links = $mech->links(); |
504
|
|
|
|
|
|
|
$mech->link_content_like( \@links, qr/Restricted/, |
505
|
|
|
|
|
|
|
'Check all links are restricted' ); |
506
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
=head2 follow_link_ok( \%parms [, $comment] ) |
508
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
Makes a C<follow_link()> call and executes tests on the results. |
510
|
|
|
|
|
|
|
The link must be found, and then followed successfully. Otherwise, |
511
|
|
|
|
|
|
|
this test fails. |
512
|
|
|
|
|
|
|
|
513
|
|
|
|
|
|
|
I<%parms> is a hashref containing the params to pass to C<follow_link()>. |
514
|
|
|
|
|
|
|
Note that the params to C<follow_link()> are a hash whereas the parms to |
515
|
|
|
|
|
|
|
this function are a hashref. You have to call this function like: |
516
|
|
|
|
|
|
|
|
517
|
|
|
|
|
|
|
$agent->follow_link_ok( {n=>3}, "looking for 3rd link" ); |
518
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
As with other test functions, C<$comment> is optional. If it is supplied |
520
|
|
|
|
|
|
|
then it will display when running the test harness in verbose mode. |
521
|
|
|
|
|
|
|
|
522
|
|
|
|
|
|
|
Returns true value if the specified link was found and followed |
523
|
|
|
|
|
|
|
successfully. The HTTP::Response object returned by follow_link() |
524
|
|
|
|
|
|
|
is not available. |
525
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
=head1 CAVEATS |
527
|
|
|
|
|
|
|
|
528
|
|
|
|
|
|
|
=head2 External Redirects and allow_external |
529
|
|
|
|
|
|
|
|
530
|
|
|
|
|
|
|
If you use non-fully qualified urls in your test scripts (i.e. anything without |
531
|
|
|
|
|
|
|
a host, such as C<< ->get_ok( "/foo") >> ) and your app redirects to an |
532
|
|
|
|
|
|
|
external URL, expect to be bitten once you come back to your application's urls |
533
|
|
|
|
|
|
|
(it will try to request them on the remote server). This is due to a limitation |
534
|
|
|
|
|
|
|
in WWW::Mechanize. |
535
|
|
|
|
|
|
|
|
536
|
|
|
|
|
|
|
One workaround for this is that if you are expecting to redirect to an external |
537
|
|
|
|
|
|
|
site, clone the TWMC object and use the cloned object for the external |
538
|
|
|
|
|
|
|
redirect. |
539
|
|
|
|
|
|
|
|
540
|
|
|
|
|
|
|
|
541
|
|
|
|
|
|
|
=head1 SEE ALSO |
542
|
|
|
|
|
|
|
|
543
|
|
|
|
|
|
|
Related modules which may be of interest: L<Catalyst>, |
544
|
|
|
|
|
|
|
L<Test::WWW::Mechanize>, L<WWW::Mechanize>. |
545
|
|
|
|
|
|
|
|
546
|
|
|
|
|
|
|
=head1 AUTHOR |
547
|
|
|
|
|
|
|
|
548
|
|
|
|
|
|
|
Ash Berlin C<< <ash@cpan.org> >> (current maintainer) |
549
|
|
|
|
|
|
|
|
550
|
|
|
|
|
|
|
Original Author: Leon Brocard, C<< <acme@astray.com> >> |
551
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
=head1 COPYRIGHT |
553
|
|
|
|
|
|
|
|
554
|
|
|
|
|
|
|
Copyright (C) 2005-9, Leon Brocard |
555
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
=head1 LICENSE |
557
|
|
|
|
|
|
|
|
558
|
|
|
|
|
|
|
This module is free software; you can redistribute it or modify it |
559
|
|
|
|
|
|
|
under the same terms as Perl itself. |
560
|
|
|
|
|
|
|
|