File Coverage

blib/lib/WWW/Webrobot.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             package WWW::Webrobot;
2 28     28   1226 use strict;
  28         52  
  28         1083  
3 28     28   152 use warnings;
  28         58  
  28         1353  
4              
5             # Author: Stefan Trcek
6             # Copyright(c) 2004-2006 ABAS Software AG
7              
8             *VERSION = \'0.81';
9              
10 28     28   151 use Carp;
  28         53  
  28         2934  
11 28     28   15618 use WWW::Webrobot::Properties;
  28         68  
  28         907  
12 28     28   19534 use WWW::Webrobot::SymbolTable;
  28         80  
  28         789  
13 28     28   22290 use WWW::Webrobot::XML2Tree;
  0            
  0            
14             use WWW::Webrobot::TestplanRunner;
15             use WWW::Webrobot::Global;
16             use WWW::Webrobot::AssertDefault;
17             use WWW::Webrobot::XHtml;
18              
19              
20             my %arg_default = (
21             data => {},
22             option => {},
23             assert => WWW::Webrobot::AssertDefault -> new(),
24             description => '',
25             useragent => '',
26             http_header => {},
27             define => {},
28             is_recursive => 0,
29             fail_str => '',
30             fail => -1,
31             );
32              
33             =head1 NAME
34              
35             WWW::Webrobot - Run Testplans
36              
37             =head1 SYNOPSIS
38              
39             use WWW::Webrobot;
40             WWW::Webrobot -> new($cfg) -> run($test_plan);
41              
42             configures Webrobot with $cfg, reads a testplan and executes this plan
43              
44             =head1 DESCRIPTION
45              
46             Runs a testplan according to a configuration.
47              
48             =head1 METHODS
49              
50             =over
51              
52             =item $wr = WWW::Webrobot -> new( $cfg_name, $cmd_param );
53              
54             Construct an object.
55              
56             $cfg_name
57             SCLAR: config string
58             REF : Name of the config file
59             $cmd_param
60             ??? to be documented
61              
62             Example:
63             $wr = WWW::Webrobot->new(\"configfile.cfg");
64             $wr = WWW::Webrobot->new(<
65             names=first=value
66             names=second=another value
67             EOF
68              
69             =cut
70              
71             sub new {
72             my $class = shift;
73             my $self = bless({}, ref($class) || $class);
74             my ($cfg_name, $cmd_param) = @_;
75             $self->cfg($cfg_name, $cmd_param) if defined $cfg_name;
76             return $self;
77             }
78              
79              
80             =item $wr -> cfg();
81              
82             Get the config data.
83              
84             =item $wr -> cfg($cfg_name, $cmd_properties);
85              
86             Read in the config data from a file named $cfg.
87             Add all properties in $cmd_properties.
88             $cmd_properties is a ref to a list of key/value pairs.
89              
90             Example:
91             $cmd_properties = [[key0, value0], [key1, value1], ...];
92              
93             Note:
94             Currently $cfg_name may also be a (internal) hash.
95             It is needed for webrobot-load but is declared deprecated.
96              
97             =cut
98              
99             sub cfg {
100             my ($self, $cfg, $cmd_param) = @_;
101             confess("config data: hash no more allowed")
102             if (ref $cfg eq "HASH"); # formerly allowed, check for unclean updates
103             $self->{cfg} = __PACKAGE__->read_configuration($cfg, $cmd_param) if defined $cfg;
104             return $self->{cfg};
105             }
106              
107              
108             =item $wr -> run($test_plan);
109              
110             =over
111              
112             =item $test_plan
113              
114             Read in the testplan from a file $test_plan and run it.
115             If $test_plan is SCALAR it is taken as a string,
116             if $test_plan is a reference it is taken as a file name.
117              
118             Example:
119             $wr->run(\"xml_file.xml");
120             $wr->run(<
121            
122            
123            
124            
125            
126            
127            
128             EOF
129              
130             =back
131              
132              
133             =cut
134              
135             sub run {
136             my $self = shift;
137             my ($test_plan_name, $child_id) = @_;
138             $child_id ||= 1;
139             #my $cfg = $self -> cfg() or die "Missing config definition";
140              
141             $test_plan_name = $test_plan_name || $self->cfg->{testplan} or
142             die "No testplan defined!";
143             WWW::Webrobot::Global->plan_name(ref $test_plan_name ? $$test_plan_name : "__IN_MEMORY__");
144              
145             my $sym_tbl = WWW::Webrobot::SymbolTable->new();
146             foreach (@{$self->cfg->{names}}) {
147             my ($key, $value) = @$_;
148             $sym_tbl -> define_symbol($key, $sym_tbl->evaluate($value));
149             }
150             $sym_tbl -> define_symbol("_id", $child_id);
151             my $test_plan = __PACKAGE__->read_testplan($test_plan_name, $sym_tbl);
152              
153             $sym_tbl = WWW::Webrobot::SymbolTable->new();
154             return WWW::Webrobot::TestplanRunner -> new() -> run($test_plan, $self->cfg, $sym_tbl);
155             }
156              
157             sub read_testplan {
158             my ($pkg, $test_plan_name, $sym_tbl) = @_;
159              
160             my $parser = WWW::Webrobot::XML2Tree->new();
161             my $tree =
162             (! ref $test_plan_name) ? $parser -> parse($test_plan_name) :
163             (ref $test_plan_name eq 'SCALAR') ? $parser -> parsefile($$test_plan_name) :
164             undef;
165              
166             # expand all properties
167             $sym_tbl->evaluate($tree);
168              
169             # convert test plan tree to internal data structure
170             my $test_plan = xml2testplan($tree, $sym_tbl);
171              
172             # check and normalize 'test_plan'
173             die "Can't read file $test_plan_name, err=$?, msg=$@" if $@;
174             ref($test_plan) or die "No valid testplan!";
175             foreach (@$test_plan) {
176             $_ = {%arg_default, %$_};
177             }
178             return $test_plan;
179             }
180              
181             sub assert {
182             my ($cond, $text) = @_;
183             croak "$text" if !$cond;
184             }
185              
186             sub xml2testplan {
187             my ($tree, $sym_tbl) = @_;
188             my $plan = xml2plan($tree, $sym_tbl);
189             return $plan;
190             }
191              
192             sub xml2plan {
193             my ($tree, $sym_tbl) = @_;
194             my $attributes = $tree->[0];
195             my $tag = $tree->[1];
196             my $content = $tree->[2];
197             assert($tag eq "plan", " expected");
198             my $plan = xml2planlist($content, $sym_tbl);
199             return $plan;
200             }
201              
202             sub xml2planlist {
203             my ($tree, $sym_tbl) = @_;
204              
205             my $plan = [];
206             my $attributes = $tree->[0];
207             for (my $i = 1; $i < @$tree; $i += 2) {
208             my $tag = $tree->[$i];
209             my $content = $tree->[$i+1];
210             SWITCH: foreach ($tag) {
211             ! $_ and do { last }; # skip white space, obsolete?
212             /^plan$/ and do {
213             my $plan_attributes = $content->[0];
214             my $action = $plan_attributes->{action};
215             assert(!defined $action || $action eq "shuffle",
216             "action='$action' not allowed, expected [shuffle]");
217             my $sub_plan = xml2planlist($content, $sym_tbl);
218             fisher_yates_shuffle($sub_plan) if $action eq "shuffle";
219             push @$plan, @$sub_plan;
220             last;
221             };
222             /^request$/ and do {
223             assert(ref $content eq 'ARRAY', "Test plan request expected");
224             push @$plan, request2entry($content);
225             last;
226             };
227             /^include$/ and do {
228             my $attr = $content->[0];
229             my $fname = $attr->{file};
230             my @list = @$content[1 .. @$content-1];
231             my $parm = get_data(\@list);
232             $sym_tbl->push_scope();
233             foreach (keys %$parm) {
234             $sym_tbl->define_symbol($_, $parm->{$_});
235             }
236             my $iplan = __PACKAGE__->read_testplan(\$fname, $sym_tbl);
237             push @$plan, @$iplan;
238             $sym_tbl->pop_scope();
239             last;
240             };
241             /^cookies$/ and do {
242             for ($content->[0]->{value} || "") {
243             assert(m/^on$/i || m/^off$/i || m/^clear$/i || m/^clear_temporary$/i,,
244             "found '$_', expected one of [on, off, clear, clear_temporary]");
245             push @$plan, {method => "COOKIES", url => "$_"};
246             }
247             last;
248             };
249             /^referrer$/ and do {
250             for ($content->[0]->{value} || "") {
251             assert(m/^on$/i || m/^off$/i || m/^clear$/i,
252             "found '$_', expected 'on', 'off, 'clear'");
253             push @$plan, {method => "REFERRER", url => "$_"};
254             }
255             last;
256             };
257             /^config$/ and do {
258             my @mode = ();
259             push @mode, ["filename", $content->[0]->{filename} || ""] if $content->[0]->{filename};
260             push @mode, ["script" , $content->[0]->{script } || ""] if $content->[0]->{script};
261             my $cfg = config2entry($content);
262             push @$plan, {method => "CONFIG", property => $cfg->{property}, _mode => \@mode, url => ""};
263             last;
264             };
265             /^sleep$/ and do {
266             push @$plan, {method => "SLEEP", url => $content->[0]->{value} || 1};
267             last;
268             };
269             /^global-assertion$/ and do {
270             my @assert = @$content[1 .. @$content-1];
271             my $mode_src = $content->[0]->{mode} || "";
272             my $mode = $mode_src || "add";
273             assert($mode eq "new" || $mode eq "add", ": found attribute mode='$mode_src', expected 'new', 'add'");
274             push @$plan, {method => "GLOBAL-ASSERTION", url => "", mode => $mode, global_assert_xml => \@assert};
275             last;
276             };
277             assert(0, "found <$tag>, expected , , , , , , , ");
278             }
279             }
280             return $plan;
281             }
282              
283              
284             sub config2entry { # copied from request2entry, may be subject to be joined
285             my ($tree) = @_;
286              
287             my %entry = ();
288              
289             my $attributes = $tree->[0];
290             for (my $i = 1; $i < @$tree; $i += 2) {
291             my $tag = $tree->[$i];
292             my $content = $tree->[$i+1];
293              
294             next if !$tag; # skip white space
295             my $attr = $content->[0];
296             # ??? obsolete iff CDATA->value
297             my @list = @$content[1 .. @$content-1];
298             if (@list > 1 && ! $list[0] && ! exists $attr->{value}) {
299             $attr->{value} = $list[1];
300             }
301             SWITCH: foreach ($tag) {
302             /^property$/ and do {
303             foreach (qw/value/) {
304             if ($attr->{$_}) {
305             push @{$entry{property}}, [$_, $attr->{name}, $attr->{$_}];
306             last;
307             }
308             }
309             last;
310             };
311             assert(0, "found <$tag>, expected ");
312             }
313             }
314             return \%entry;
315             }
316              
317             sub request2entry {
318             my ($tree) = @_;
319              
320             my %entry = ();
321              
322             my $attributes = $tree->[0];
323             for (my $i = 1; $i < @$tree; $i += 2) {
324             my $tag = $tree->[$i];
325             my $content = $tree->[$i+1];
326              
327             next if !$tag; # skip white space
328             my $attr = $content->[0];
329             # ??? obsolete iff CDATA->value
330             my @list = @$content[1 .. @$content-1];
331             if (@list > 1 && ! $list[0] && ! exists $attr->{value}) {
332             $attr->{value} = $list[1];
333             }
334             SWITCH: foreach ($tag) {
335             /^method$/ and do {
336             $entry{method} = trim($attr->{value}) || "GET";
337             last;
338             };
339             /^url$/ and do {
340             $entry{url} = trim($attr->{value}) || die "URL required";
341             last;
342             };
343             /^description$/ and do {
344             $entry{description} = trim($attr->{value});
345             last;
346             };
347             /^useragent$/ and do {
348             $entry{useragent} = trim($attr->{value});
349             last;
350             };
351             /^http-header$/ and do {
352             $entry{http_header}->{$attr->{name} || ""} = trim($attr->{value});
353             last;
354             };
355             /^data$/ and do {
356             $entry{data} = get_data(\@list);
357             last;
358             };
359             /^assert$/ and do {
360             $entry{assert_xml} = \@list;
361             last;
362             };
363             /^recurse$/ and do {
364             $entry{recurse_xml} = \@list;
365             last;
366             };
367             /^property$/ and do {
368             foreach (qw/value regex xpath header status random/) {
369             if ($attr->{$_}) {
370             push @{$entry{property}}, [$_, $attr->{name}, $attr->{$_}];
371             last;
372             }
373             }
374             last;
375             };
376             assert(0, "found <$tag>, expected , , , , , , , ");
377             }
378             }
379             return \%entry;
380             }
381              
382             sub get_data {
383             my ($list) = @_;
384             my %entry = ();
385              
386             for (my $i = 0; $i < @$list; $i += 2) {
387             my $tag = $list->[$i];
388             my $content = $list->[$i+1];
389              
390             next if !$tag; # skip white space
391             assert($tag eq 'parm', " expected");
392             my $attr = $content->[0];
393             my $lhs = $attr->{name};
394             my $rhs = (defined $attr->{value}) ? $attr->{value} : ($content->[1] ? "" : trim($content->[2]));
395             $entry{$lhs} = $rhs;
396             }
397             return \%entry;
398             }
399              
400             sub trim {
401             my ($str) = @_;
402             return "" if !defined $str;
403             $str =~ s/^\s+//s;
404             $str =~ s/\s+$//s;
405             return $str;
406             }
407              
408              
409             # static
410             # shuffle an array randomly inplace
411             sub fisher_yates_shuffle {
412             my ($array) = @_; # $array is a reference to an array
413             my $last = @$array;
414             while ($last--) {
415             my $k = int rand ($last+1);
416             @$array[$last, $k] = @$array[$k, $last];
417             }
418             }
419              
420              
421             # static
422             sub read_configuration {
423             my ($package, $cfg_name, $cmd_param) = @_;
424             die "Missing config definition" if !$cfg_name;
425              
426             # read config file in 'properties' format
427             my $config = WWW::Webrobot::Properties->new(
428             listmode => [qw(names auth_basic output http_header proxy no_proxy mail.Attach)],
429             key_value => [qw(names http_header proxy)],
430             multi_value => [qw(auth_basic mail.Attach)],
431             structurize => [qw(load mail)],
432             );
433             my $cfg = $config->load($cfg_name, $cmd_param);
434              
435             # adjust property 'output' to internal data structure
436             $cfg->{output} = [ $cfg->{output} ] if ref($cfg->{output}) ne "ARRAY";
437             my $output = $cfg->{output};
438             foreach (@$output) {
439             my ($class, $rest) = split /\s+/, $_, 2;
440             eval "require $class;";
441             die "Can't find class='$class', $@" if $@;
442             $rest ||= "";
443             my @parm = eval("( $rest )");
444             die "Invalid parameter list: $@" if $@;
445             $_ = $class -> new(@parm);
446             }
447              
448             # adjust property 'auth_basic' to internal data structure
449             my %intern_realm = ();
450             foreach (@{$cfg->{auth_basic}}) {
451             my ($id, $login, $passwd) = @$_;
452             $intern_realm{$id} = [$login, $passwd];
453             }
454             $cfg->{auth_basic} = \%intern_realm;
455              
456             # adjust 'http_header'
457             $cfg->{http_header} = array2hash($cfg->{http_header});
458              
459             # adjust 'proxy'
460             $cfg->{proxy} = array2hash($cfg->{proxy});
461              
462             # adjust 'names'
463             #$cfg->{names} = array2hash($cfg->{names});
464              
465             # normalize 'load'
466             $cfg->{load}->{number_of_clients} ||= 1 if defined $cfg->{load};
467              
468             return $cfg;
469             }
470              
471              
472             sub array2hash {
473             my ($http_header) = @_;
474             my %hash = ();
475             foreach (@$http_header) {
476             my ($key, $value) = @$_;
477             $hash{$key} = $value;
478             }
479             return \%hash;
480             }
481              
482             =back
483              
484             =head1 SEE ALSO
485              
486             L
487              
488             L
489              
490             =cut
491              
492             1;