| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Test::WWW::Declare; |
|
2
|
14
|
|
|
14
|
|
77
|
use warnings; |
|
|
14
|
|
|
|
|
29
|
|
|
|
14
|
|
|
|
|
455
|
|
|
3
|
14
|
|
|
14
|
|
81
|
use strict; |
|
|
14
|
|
|
|
|
27
|
|
|
|
14
|
|
|
|
|
471
|
|
|
4
|
14
|
|
|
14
|
|
76
|
use base 'Test::More'; |
|
|
14
|
|
|
|
|
29
|
|
|
|
14
|
|
|
|
|
17011
|
|
|
5
|
14
|
|
|
14
|
|
130738
|
use Test::WWW::Mechanize; |
|
|
14
|
|
|
|
|
3218586
|
|
|
|
14
|
|
|
|
|
690
|
|
|
6
|
14
|
|
|
14
|
|
175
|
use Test::Builder; |
|
|
14
|
|
|
|
|
28
|
|
|
|
14
|
|
|
|
|
34226
|
|
|
7
|
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
our $VERSION = '0.02'; |
|
9
|
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
our @EXPORT = qw(flow run get session check mech match follow_link content |
|
11
|
|
|
|
|
|
|
should shouldnt click href button fill form SKIP _twd_dummy |
|
12
|
|
|
|
|
|
|
title equal caselessly contain matches equals contains |
|
13
|
|
|
|
|
|
|
never always lack lacks url uri); |
|
14
|
|
|
|
|
|
|
our $BUILDER = Test::Builder->new(); |
|
15
|
|
|
|
|
|
|
our $WWW_MECHANIZE; |
|
16
|
|
|
|
|
|
|
our $IN_FLOW; |
|
17
|
|
|
|
|
|
|
our %mechs; |
|
18
|
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
=begin private |
|
20
|
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
=head2 import_extra |
|
22
|
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
Called by L's C code when L is first |
|
24
|
|
|
|
|
|
|
C |
|
25
|
|
|
|
|
|
|
C |
|
26
|
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
=end private |
|
28
|
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
=cut |
|
30
|
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
sub import_extra { |
|
32
|
14
|
|
|
14
|
1
|
4885
|
Test::More->export_to_level(2); |
|
33
|
|
|
|
|
|
|
} |
|
34
|
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
=head1 NAME |
|
36
|
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
Test::WWW::Declare - declarative testing for your web app |
|
38
|
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
40
|
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
use Test::WWW::Declare tests => 3; |
|
42
|
|
|
|
|
|
|
use Your::Web::App::Test; |
|
43
|
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
Your::Web::App::Test->start_server; |
|
45
|
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
session 'testuser' => run { |
|
47
|
|
|
|
|
|
|
flow 'log in and out' => check { |
|
48
|
|
|
|
|
|
|
flow 'log in' => check { |
|
49
|
|
|
|
|
|
|
get 'http://localhost/'; |
|
50
|
|
|
|
|
|
|
fill form 'login' => { |
|
51
|
|
|
|
|
|
|
username => 'testuser', |
|
52
|
|
|
|
|
|
|
password => 'drowssap', |
|
53
|
|
|
|
|
|
|
}; |
|
54
|
|
|
|
|
|
|
content should contain 'log out'; |
|
55
|
|
|
|
|
|
|
}; |
|
56
|
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
flow 'log out' => check { |
|
58
|
|
|
|
|
|
|
get 'http://localhost/'; |
|
59
|
|
|
|
|
|
|
click href 'log out'; |
|
60
|
|
|
|
|
|
|
}; |
|
61
|
|
|
|
|
|
|
}; |
|
62
|
|
|
|
|
|
|
}; |
|
63
|
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
65
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
Often in web apps, tests are very dependent on the state set up by previous |
|
67
|
|
|
|
|
|
|
tests. If one test fails (e.g. "follow the link to the admin page") then it's |
|
68
|
|
|
|
|
|
|
likely there will be many more failures. This module aims to alleviate this |
|
69
|
|
|
|
|
|
|
problem, as well as provide a nicer interface to L. |
|
70
|
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
The central idea is that of "flow". Each flow is a sequence of commands ("fill |
|
72
|
|
|
|
|
|
|
in this form") and assertions ("content should contain 'testuser'"). If any of |
|
73
|
|
|
|
|
|
|
these commands or assertions fail then the flow is aborted. Only that one |
|
74
|
|
|
|
|
|
|
failure is reported to the test harness and user. Flows may also contain other |
|
75
|
|
|
|
|
|
|
flows. If an inner flow fails, then the outer flow fails as well. |
|
76
|
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
=head1 FLOWS AND SESSIONS |
|
78
|
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
=head2 session NAME => run { CODE } |
|
80
|
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
Sessions are a way of associating a set of flows with a L |
|
82
|
|
|
|
|
|
|
instance. A session is mostly equivalent with a user interacting with your web |
|
83
|
|
|
|
|
|
|
app. |
|
84
|
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
Within a session, every command (C, C, etc) is operating on |
|
86
|
|
|
|
|
|
|
that session's L instance. You may have multiple sessions in |
|
87
|
|
|
|
|
|
|
one test file. Two sessions with the same name are in fact the same session. |
|
88
|
|
|
|
|
|
|
This lets you write code like the following, simplified slightly: |
|
89
|
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
session 'first user' => run { |
|
91
|
|
|
|
|
|
|
get "$URL/give?task=1&victim=other"; |
|
92
|
|
|
|
|
|
|
session 'other user' => run { |
|
93
|
|
|
|
|
|
|
get "$URL/tasks"; |
|
94
|
|
|
|
|
|
|
content should match qr/task 1/; |
|
95
|
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
# this is the same session/mech as the outermost 'first user' |
|
97
|
|
|
|
|
|
|
session 'first user' => run { |
|
98
|
|
|
|
|
|
|
get "$URL/tasks"; |
|
99
|
|
|
|
|
|
|
content shouldnt match qr/task 1/; |
|
100
|
|
|
|
|
|
|
}; |
|
101
|
|
|
|
|
|
|
}; |
|
102
|
|
|
|
|
|
|
}; |
|
103
|
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
=head2 flow NAME => check { CODE } |
|
105
|
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
A flow encompasses a single test. As described above, each flow is a sequence |
|
107
|
|
|
|
|
|
|
of commands, assertions, and other flows. If any of the components of a flow |
|
108
|
|
|
|
|
|
|
fail, the rest of the flow is aborted and one or more test failures are |
|
109
|
|
|
|
|
|
|
reported to the test harness. |
|
110
|
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
=head1 COMMANDS |
|
112
|
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
=head2 get URL |
|
114
|
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
=head2 click button |
|
116
|
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
=head2 click href |
|
118
|
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
=head2 follow_link |
|
120
|
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
=head2 fill form NAME => {FIELD1 => VALUE1, FIELD2 => VALUE2} |
|
122
|
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
=head1 ASSERTIONS |
|
124
|
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
Every assertion has two parts: a subject and a verb. |
|
126
|
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
=head2 SUBJECTS |
|
128
|
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
=head3 content |
|
130
|
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
=head3 title |
|
132
|
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
=head3 url |
|
134
|
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
=head2 VERBS |
|
136
|
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
=head3 should(nt) (caselessly) match REGEX |
|
138
|
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
=head3 should(nt) (caselessly) contain STRING |
|
140
|
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
=head3 should(nt) (caselessly) lack STRING |
|
142
|
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
=head3 should(nt) (caselessly) equal STRING |
|
144
|
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
=cut |
|
146
|
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
# DSLey functions |
|
148
|
0
|
|
|
0
|
1
|
0
|
sub to($) { return $_[0] } |
|
149
|
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
sub _args { |
|
151
|
74
|
|
|
74
|
|
117
|
my $args = shift; |
|
152
|
74
|
100
|
|
|
|
330
|
return $args if ref($args) eq 'HASH'; |
|
153
|
37
|
|
|
|
|
139
|
return {expected => $args}; |
|
154
|
|
|
|
|
|
|
} |
|
155
|
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
sub should ($) { |
|
157
|
29
|
|
|
29
|
1
|
81
|
return _args(shift); |
|
158
|
|
|
|
|
|
|
} |
|
159
|
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
sub shouldnt ($) { |
|
161
|
4
|
|
|
4
|
0
|
9
|
my $args = _args(shift); |
|
162
|
4
|
|
|
|
|
8
|
$args->{negative} = 1; |
|
163
|
4
|
|
|
|
|
15
|
return $args; |
|
164
|
|
|
|
|
|
|
} |
|
165
|
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
sub match ($) { |
|
167
|
11
|
|
|
11
|
1
|
246
|
my $args = _args(shift); |
|
168
|
11
|
|
|
|
|
43
|
$args->{match} = 'regex'; |
|
169
|
11
|
|
|
|
|
48
|
return $args; |
|
170
|
|
|
|
|
|
|
} |
|
171
|
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
sub equal ($) { |
|
173
|
18
|
|
|
18
|
1
|
268
|
my $args = _args(shift); |
|
174
|
18
|
|
|
|
|
48
|
$args->{match} = 'equality'; |
|
175
|
18
|
|
|
|
|
73
|
return $args; |
|
176
|
|
|
|
|
|
|
} |
|
177
|
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
sub contain ($) { |
|
179
|
7
|
|
|
7
|
1
|
15
|
my $args = _args(shift); |
|
180
|
7
|
|
|
|
|
214
|
$args->{match} = 'index'; |
|
181
|
7
|
|
|
|
|
26
|
return $args; |
|
182
|
|
|
|
|
|
|
} |
|
183
|
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
sub lack ($) { |
|
185
|
1
|
|
|
1
|
1
|
5
|
my $args = _args(shift); |
|
186
|
1
|
|
|
|
|
2
|
$args->{match} = 'index'; |
|
187
|
1
|
|
|
|
|
2
|
$args->{negative} = 1; |
|
188
|
1
|
|
|
|
|
4
|
return $args; |
|
189
|
|
|
|
|
|
|
} |
|
190
|
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
sub caselessly ($) { |
|
192
|
4
|
|
|
4
|
0
|
10
|
my $args = _args(shift); |
|
193
|
4
|
|
|
|
|
8
|
$args->{case_insensitive} = 1; |
|
194
|
4
|
|
|
|
|
861
|
return $args; |
|
195
|
|
|
|
|
|
|
} |
|
196
|
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
sub check (&) { |
|
198
|
29
|
|
|
29
|
1
|
188
|
my $coderef = shift; |
|
199
|
|
|
|
|
|
|
|
|
200
|
29
|
|
|
|
|
153
|
return $coderef; |
|
201
|
|
|
|
|
|
|
} |
|
202
|
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
sub run (&) { |
|
204
|
17
|
|
|
17
|
1
|
3637
|
my $coderef = shift; |
|
205
|
|
|
|
|
|
|
|
|
206
|
17
|
|
|
|
|
213
|
return $coderef; |
|
207
|
|
|
|
|
|
|
} |
|
208
|
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
# alternates (e.g. "foo matches bar" instead of "foo should match bar") |
|
210
|
3
|
|
|
3
|
0
|
8
|
sub contains ($) { contain $_[0] } |
|
211
|
2
|
|
|
2
|
0
|
9
|
sub equals ($) { equal $_[0] } |
|
212
|
1
|
|
|
1
|
0
|
46
|
sub matches ($) { match $_[0] } |
|
213
|
0
|
|
|
0
|
0
|
0
|
sub lacks ($) { lack $_[0] } |
|
214
|
|
|
|
|
|
|
|
|
215
|
1
|
|
|
1
|
0
|
4
|
sub always ($) { should $_[0] } |
|
216
|
1
|
|
|
1
|
0
|
5
|
sub never ($) { shouldnt $_[0] } |
|
217
|
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
# Mech interactions |
|
219
|
|
|
|
|
|
|
sub mech(;$) { |
|
220
|
93
|
|
|
93
|
0
|
196
|
my $name = shift; |
|
221
|
93
|
100
|
|
|
|
977
|
return defined $name ? $mechs{$name} : $WWW_MECHANIZE; |
|
222
|
|
|
|
|
|
|
} |
|
223
|
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
sub get { |
|
225
|
18
|
|
|
18
|
1
|
257
|
my $url = shift; |
|
226
|
|
|
|
|
|
|
|
|
227
|
18
|
|
|
|
|
78
|
mech()->get($url); |
|
228
|
18
|
100
|
|
|
|
1833171
|
if (!$IN_FLOW) |
|
229
|
|
|
|
|
|
|
{ |
|
230
|
1
|
|
|
|
|
5
|
$BUILDER->ok(mech->success, "navigated to $url"); |
|
231
|
|
|
|
|
|
|
} |
|
232
|
|
|
|
|
|
|
|
|
233
|
18
|
50
|
|
|
|
350
|
return if mech->success; |
|
234
|
|
|
|
|
|
|
|
|
235
|
0
|
0
|
|
|
|
0
|
Carp::croak mech->status |
|
236
|
|
|
|
|
|
|
. (mech->response ? ' - ' . mech->response->message : '') |
|
237
|
|
|
|
|
|
|
} |
|
238
|
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
sub href ($) { |
|
240
|
11
|
|
|
11
|
1
|
251
|
return (shift, 'href'); |
|
241
|
|
|
|
|
|
|
} |
|
242
|
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
sub button ($) { |
|
244
|
2
|
|
|
2
|
1
|
30
|
return (shift, 'button'); |
|
245
|
|
|
|
|
|
|
} |
|
246
|
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
sub click { |
|
248
|
13
|
|
|
13
|
1
|
32
|
my $link = shift; |
|
249
|
13
|
|
|
|
|
24
|
my $type = shift; |
|
250
|
|
|
|
|
|
|
|
|
251
|
13
|
100
|
|
|
|
54
|
if ($type eq 'button') { |
|
252
|
2
|
|
|
|
|
5
|
my $ok = mech()->click_button(value => $link); |
|
253
|
2
|
50
|
|
|
|
23512
|
$ok = $ok->is_success if $ok; |
|
254
|
2
|
50
|
|
|
|
24
|
my $verb = ref($link) eq 'Regexp' ? "matching " : ""; |
|
255
|
2
|
50
|
|
|
|
8
|
$BUILDER->ok($ok, "Clicked button $verb$link") if !$IN_FLOW; |
|
256
|
2
|
|
|
|
|
10
|
return $ok; |
|
257
|
|
|
|
|
|
|
} |
|
258
|
|
|
|
|
|
|
else { |
|
259
|
11
|
100
|
|
|
|
59
|
if (ref $link ne 'Regexp') { |
|
260
|
1
|
|
|
|
|
338
|
Carp::croak "click doesn't know what to do with a link type of " |
|
261
|
|
|
|
|
|
|
. ref($link); |
|
262
|
|
|
|
|
|
|
} |
|
263
|
10
|
|
|
|
|
16
|
my $ok; |
|
264
|
10
|
|
|
|
|
33
|
my $response = mech()->follow_link(text_regex => $link); |
|
265
|
10
|
100
|
100
|
|
|
144308
|
$ok = 1 if $response && $response->is_success; |
|
266
|
10
|
100
|
|
|
|
162
|
$BUILDER->ok($ok, "Clicked link matching $link") if !$IN_FLOW; |
|
267
|
10
|
100
|
|
|
|
623
|
Carp::croak($response ? $response->as_string : "No link matching $link found") if !$ok; |
|
|
|
100
|
|
|
|
|
|
|
268
|
8
|
|
|
|
|
43
|
return $ok; |
|
269
|
|
|
|
|
|
|
} |
|
270
|
|
|
|
|
|
|
} |
|
271
|
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
sub follow_link { |
|
273
|
0
|
|
|
0
|
1
|
0
|
my $ret = mech()->follow_link(@_); |
|
274
|
|
|
|
|
|
|
|
|
275
|
0
|
0
|
|
|
|
0
|
if (!$ret) { |
|
276
|
0
|
|
|
|
|
0
|
Carp::croak "follow_link couldn't find a link matching " |
|
277
|
|
|
|
|
|
|
. "(" . join(', ', @_) . ")"; |
|
278
|
|
|
|
|
|
|
} |
|
279
|
|
|
|
|
|
|
} |
|
280
|
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
sub content ($) { |
|
282
|
10
|
|
|
10
|
1
|
32
|
_magic_match({got => mech()->content, name => "Content", %{shift @_}}); |
|
|
10
|
|
|
|
|
455
|
|
|
283
|
|
|
|
|
|
|
} |
|
284
|
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
sub title ($) { |
|
286
|
26
|
|
|
26
|
1
|
92
|
my $title = mech()->title; |
|
287
|
26
|
|
|
|
|
6027
|
_magic_match({got => $title, name => "Title '$title'", %{shift @_}}); |
|
|
26
|
|
|
|
|
210
|
|
|
288
|
|
|
|
|
|
|
} |
|
289
|
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
sub url ($) { |
|
291
|
1
|
|
|
1
|
1
|
4
|
my $url = mech()->uri; |
|
292
|
1
|
|
|
|
|
28
|
_magic_match({got => $url, name => "URL '$url'", %{shift @_}}); |
|
|
1
|
|
|
|
|
11
|
|
|
293
|
|
|
|
|
|
|
} |
|
294
|
|
|
|
|
|
|
*uri = \&url; |
|
295
|
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
# yes, there's a little too much logic in here. that's why it's magic |
|
297
|
|
|
|
|
|
|
sub _magic_match { |
|
298
|
37
|
|
|
37
|
|
74
|
my $orig = shift @_; |
|
299
|
37
|
|
|
|
|
200
|
my %args = %$orig; |
|
300
|
37
|
|
|
|
|
81
|
my $match; |
|
301
|
|
|
|
|
|
|
my @output; |
|
302
|
|
|
|
|
|
|
|
|
303
|
37
|
|
100
|
|
|
319
|
$args{negative} ||= 0; |
|
304
|
|
|
|
|
|
|
|
|
305
|
37
|
|
|
|
|
72
|
push @output, $args{name}; |
|
306
|
37
|
100
|
|
|
|
118
|
push @output, $args{negative} ? () |
|
307
|
|
|
|
|
|
|
: "does not"; |
|
308
|
|
|
|
|
|
|
|
|
309
|
37
|
100
|
|
|
|
161
|
if ($args{match} eq 'equality') { |
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
310
|
18
|
100
|
|
|
|
56
|
if ($args{case_insensitive}) { |
|
311
|
2
|
|
|
|
|
6
|
push @output, "caselessly"; |
|
312
|
2
|
|
|
|
|
6
|
$args{got} = lc $args{got}; |
|
313
|
2
|
|
|
|
|
6
|
$args{expected} = lc $args{expected}; |
|
314
|
|
|
|
|
|
|
} |
|
315
|
|
|
|
|
|
|
|
|
316
|
18
|
100
|
|
|
|
54
|
push @output, $args{negative} ? "equals" |
|
317
|
|
|
|
|
|
|
: "equal"; |
|
318
|
18
|
|
|
|
|
40
|
push @output, $orig->{expected}; |
|
319
|
|
|
|
|
|
|
|
|
320
|
18
|
|
|
|
|
53
|
$match = $args{got} eq $args{expected}; |
|
321
|
|
|
|
|
|
|
} |
|
322
|
|
|
|
|
|
|
elsif ($args{match} eq 'index') { |
|
323
|
8
|
100
|
|
|
|
26
|
if ($args{case_insensitive}) { |
|
324
|
2
|
|
|
|
|
6
|
push @output, "caselessly"; |
|
325
|
2
|
|
|
|
|
5
|
$args{got} = lc $args{got}; |
|
326
|
2
|
|
|
|
|
7
|
$args{expected} = lc $args{expected}; |
|
327
|
|
|
|
|
|
|
} |
|
328
|
|
|
|
|
|
|
|
|
329
|
8
|
100
|
|
|
|
22
|
push @output, $args{negative} ? "contains" |
|
330
|
|
|
|
|
|
|
: "contain"; |
|
331
|
8
|
|
|
|
|
48
|
push @output, $orig->{expected}; |
|
332
|
|
|
|
|
|
|
|
|
333
|
8
|
|
|
|
|
22
|
$match = index($args{got}, $args{expected}) >= 0; |
|
334
|
|
|
|
|
|
|
} |
|
335
|
|
|
|
|
|
|
elsif ($args{match} eq 'regex') { |
|
336
|
11
|
50
|
|
|
|
47
|
if ($args{case_insensitive}) { |
|
337
|
0
|
|
|
|
|
0
|
push @output, "caselessly"; |
|
338
|
0
|
|
|
|
|
0
|
push @output, $args{expected}; |
|
339
|
0
|
|
|
|
|
0
|
$args{expected} = "(?i:$args{expected})"; |
|
340
|
|
|
|
|
|
|
} |
|
341
|
|
|
|
|
|
|
|
|
342
|
11
|
100
|
|
|
|
49
|
push @output, $args{negative} ? "matches" |
|
343
|
|
|
|
|
|
|
: "match"; |
|
344
|
11
|
|
|
|
|
30
|
push @output, $orig->{expected}; |
|
345
|
|
|
|
|
|
|
|
|
346
|
11
|
|
|
|
|
127
|
$match = $args{got} =~ $args{expected}; |
|
347
|
|
|
|
|
|
|
} |
|
348
|
|
|
|
|
|
|
else { |
|
349
|
0
|
|
|
|
|
0
|
Carp::croak "No \$args{match} (yes this error needs to be fixed)"; |
|
350
|
|
|
|
|
|
|
} |
|
351
|
|
|
|
|
|
|
|
|
352
|
37
|
100
|
|
|
|
140
|
my $ok = ($match ? 1 : 0) ^ $args{negative}; |
|
353
|
37
|
100
|
|
|
|
104
|
if (!$IN_FLOW) { |
|
354
|
2
|
|
|
|
|
18
|
$BUILDER->ok($ok, join(' ', @output)); |
|
355
|
2
|
|
|
|
|
323
|
return $ok; |
|
356
|
|
|
|
|
|
|
} |
|
357
|
|
|
|
|
|
|
|
|
358
|
35
|
50
|
|
|
|
189
|
return 1 if $ok; |
|
359
|
0
|
|
|
|
|
0
|
Carp::croak join(' ', @output); |
|
360
|
|
|
|
|
|
|
} |
|
361
|
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
sub form ($$) { |
|
363
|
3
|
|
|
3
|
1
|
81
|
my $form_name = shift; |
|
364
|
3
|
|
|
|
|
7
|
my $data = shift; |
|
365
|
|
|
|
|
|
|
|
|
366
|
3
|
|
|
|
|
12
|
my $form = mech()->form_name($form_name); |
|
367
|
|
|
|
|
|
|
|
|
368
|
3
|
100
|
|
|
|
21205
|
if (!defined($form)) { |
|
369
|
1
|
|
|
|
|
240
|
Carp::croak "There is no form named '$form_name'"; |
|
370
|
|
|
|
|
|
|
} |
|
371
|
|
|
|
|
|
|
|
|
372
|
2
|
|
|
|
|
13
|
return $data; |
|
373
|
|
|
|
|
|
|
} |
|
374
|
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
sub fill { |
|
376
|
2
|
|
|
2
|
1
|
6
|
my $data = shift; |
|
377
|
|
|
|
|
|
|
|
|
378
|
2
|
50
|
|
|
|
11
|
Carp::croak "fill expects a hashref" if ref($data) ne 'HASH'; |
|
379
|
|
|
|
|
|
|
|
|
380
|
2
|
|
|
|
|
7
|
mech()->set_fields(%{$data}); |
|
|
2
|
|
|
|
|
20
|
|
|
381
|
|
|
|
|
|
|
} |
|
382
|
|
|
|
|
|
|
|
|
383
|
|
|
|
|
|
|
# the meat of the module |
|
384
|
|
|
|
|
|
|
sub SKIP ($) { |
|
385
|
2
|
|
|
2
|
1
|
45
|
my $reason = shift; |
|
386
|
|
|
|
|
|
|
|
|
387
|
2
|
|
|
|
|
555
|
Carp::croak "SKIP: $reason"; |
|
388
|
|
|
|
|
|
|
} |
|
389
|
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
sub flow ($$) { |
|
391
|
29
|
|
|
29
|
1
|
61
|
my $name = shift; |
|
392
|
29
|
|
|
|
|
56
|
my $coderef = shift; |
|
393
|
|
|
|
|
|
|
|
|
394
|
29
|
|
|
|
|
66
|
eval { local $IN_FLOW = 1; $coderef->() }; |
|
|
29
|
|
|
|
|
51
|
|
|
|
29
|
|
|
|
|
82
|
|
|
395
|
|
|
|
|
|
|
|
|
396
|
29
|
100
|
|
|
|
1823
|
if ($@ =~ /^SKIP: (.*)$/) { |
|
|
|
100
|
|
|
|
|
|
|
397
|
2
|
|
|
|
|
7
|
my $reason = $1; |
|
398
|
2
|
|
|
|
|
28
|
$BUILDER->skip($reason); |
|
399
|
|
|
|
|
|
|
} |
|
400
|
|
|
|
|
|
|
elsif ($@) { |
|
401
|
5
|
100
|
|
|
|
19
|
if ($IN_FLOW) { |
|
402
|
1
|
50
|
|
|
|
13
|
if ($@ =~ /^Flow '/) |
|
403
|
|
|
|
|
|
|
{ |
|
404
|
0
|
|
|
|
|
0
|
die $@; |
|
405
|
|
|
|
|
|
|
} |
|
406
|
1
|
|
|
|
|
7
|
die "Flow '$name' failed: $@"; |
|
407
|
|
|
|
|
|
|
} |
|
408
|
|
|
|
|
|
|
|
|
409
|
4
|
|
|
|
|
49
|
$BUILDER->ok(0, $name); |
|
410
|
|
|
|
|
|
|
|
|
411
|
4
|
100
|
|
|
|
773
|
if ($@ =~ /^Flow '/) { |
|
412
|
1
|
|
|
|
|
11
|
$BUILDER->diag($@); |
|
413
|
|
|
|
|
|
|
} |
|
414
|
|
|
|
|
|
|
else { |
|
415
|
3
|
|
|
|
|
34
|
$BUILDER->diag("Flow '$name' failed: $@"); |
|
416
|
|
|
|
|
|
|
} |
|
417
|
|
|
|
|
|
|
} |
|
418
|
|
|
|
|
|
|
else { |
|
419
|
22
|
|
|
|
|
263
|
$BUILDER->ok(1, $name); |
|
420
|
|
|
|
|
|
|
} |
|
421
|
|
|
|
|
|
|
} |
|
422
|
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
sub session ($$) { |
|
424
|
17
|
|
|
17
|
1
|
44
|
my $title = shift; |
|
425
|
17
|
|
|
|
|
37
|
my $coderef = shift; |
|
426
|
|
|
|
|
|
|
|
|
427
|
17
|
|
66
|
|
|
456
|
$mechs{$title} ||= Test::WWW::Mechanize->new(quiet => 1); |
|
428
|
17
|
|
|
|
|
341628
|
local $WWW_MECHANIZE = $mechs{$title}; |
|
429
|
|
|
|
|
|
|
|
|
430
|
17
|
|
|
|
|
2076
|
$coderef->(); |
|
431
|
|
|
|
|
|
|
|
|
432
|
17
|
50
|
|
|
|
2938
|
if ($@ =~ /^SKIP: (.*)$/) { |
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
433
|
0
|
|
|
|
|
0
|
my $reason = $1; |
|
434
|
0
|
|
|
|
|
0
|
$BUILDER->skip($reason); |
|
435
|
|
|
|
|
|
|
} |
|
436
|
|
|
|
|
|
|
elsif ($@ =~ /^Flow '/) { |
|
437
|
|
|
|
|
|
|
# flow already displayed the error |
|
438
|
|
|
|
|
|
|
} |
|
439
|
|
|
|
|
|
|
elsif ($@) { |
|
440
|
1
|
|
|
|
|
6
|
$BUILDER->diag($@); |
|
441
|
|
|
|
|
|
|
} |
|
442
|
|
|
|
|
|
|
} |
|
443
|
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
sub dump($) { |
|
445
|
0
|
|
|
0
|
0
|
0
|
my $file = shift; |
|
446
|
0
|
|
|
|
|
0
|
mech->save_content($file); |
|
447
|
|
|
|
|
|
|
} |
|
448
|
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
# used only for testing that we got T:W:D's goods |
|
450
|
1
|
|
|
1
|
|
2027
|
sub _twd_dummy { "XYZZY" } |
|
451
|
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
=head1 SUBCLASSING |
|
453
|
|
|
|
|
|
|
|
|
454
|
|
|
|
|
|
|
One of the goals of this module is to let you subclass it to provide extra |
|
455
|
|
|
|
|
|
|
features, such as automatically logging in a user each time a session is |
|
456
|
|
|
|
|
|
|
created. |
|
457
|
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
=head1 CAVEATS |
|
459
|
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
If you fail any tests, then the actual number of tests run may be fewer than |
|
461
|
|
|
|
|
|
|
you have in your file. This is because when a flow fails, it immediately aborts |
|
462
|
|
|
|
|
|
|
the rest of its body (which may include other flows). So if you're setting the |
|
463
|
|
|
|
|
|
|
number of tests based on how many ran, make sure that all tests passed. |
|
464
|
|
|
|
|
|
|
|
|
465
|
|
|
|
|
|
|
=head1 BUGS |
|
466
|
|
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
Hopefully few. We'd like to know about any of them. Please report them to |
|
468
|
|
|
|
|
|
|
C. |
|
469
|
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
=head1 SEE ALSO |
|
471
|
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
L, L. |
|
473
|
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
=head1 MAINTAINER |
|
475
|
|
|
|
|
|
|
|
|
476
|
|
|
|
|
|
|
Shawn M Moore C<< >> |
|
477
|
|
|
|
|
|
|
|
|
478
|
|
|
|
|
|
|
=head1 ORIGINAL AUTHOR |
|
479
|
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
Jesse Vincent C<< >> |
|
481
|
|
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
=head1 COPYRIGHT |
|
483
|
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
Copyright 2007-2008 Best Practical Solutions, LLC |
|
485
|
|
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. |
|
487
|
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
=cut |
|
489
|
|
|
|
|
|
|
|
|
490
|
|
|
|
|
|
|
1; |
|
491
|
|
|
|
|
|
|
|