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
|
|
|
|
|
|
|
|