1 package Support::Integration;
4 use Exporter qw(import);
7 our @EXPORT = qw(make_request form_from_html);
9 package MockDispatcher {
10 sub end_request { die "END_OF_MOCK_REQUEST" }
15 $::dispatcher = bless { }, "MockDispatcher";
19 my ($controller, $action, %form_vars) = @_;
21 my ($out, $err, @ret);
24 local $SIG{__WARN__} = sub {
25 # ignore spurious warnings, TAP::Harness calls this warnings enabled
28 open(my $out_fh, '>', \$out) or die;
29 open(my $err_fh, '>', \$err) or die;
31 local *STDOUT = $out_fh;
32 local *STDERR = $err_fh;
34 local $::form = Form->new;
35 $::form->{$_} = $form_vars{$_} for keys %form_vars;
39 if ($controller =~ /^[a-z]/) {
40 $::form->{script} = $controller.'.pl'; # usually set by dispatcher, needed for checks in update_exchangerate
41 local $ENV{REQUEST_URI} = "http://localhost/$controller.pl"; # needed for Form::redirect_header
42 require "bin/mozilla/$controller.pl";
44 @ret = &{ "::$action" }();
46 require "SL/Controller/$controller.pm";
48 @ret = "SL::Controller::$controller"->new->_run_action($action);
51 } or do { my $err = $@;
52 die unless $err =~ /^END_OF_MOCK_REQUEST/;
56 return ($out, $err, @ret);
60 my ($html, $form_selector) = @_;
61 $form_selector //= '#form';
63 my $q = HTML::Query->new(text => $html);
66 for my $input ($q->query("$form_selector input")->get_elements()) {
67 next if !$input->attr('name') || $input->attr('disabled');
68 $form{ $input->attr('name') } = $input->attr('value') // "";
70 for my $select ($q->query("$form_selector select")->get_elements()) {
71 my $name = $select->attr('name');
72 my ($selected_option) = (
73 grep({ $_->tag eq 'option' && $_->attr('selected') } $select->content_list),
74 grep({ $_->tag eq 'option' } $select->content_list)
77 $form{ $name } = $selected_option->attr('value') // $selected_option->as_text
92 Support::Integration - helper for simple frontend integration tests
97 use Support::Integration qw(:all);
99 # before making any requests, setup mock dispatcher
100 Support::Integration::setup();
101 Support::TestSetup::login();
103 # then do a simple request: ar.pl?action=add&type=invoice
104 # returns the generated outputs and return values
105 # exceptions are bubbled through
106 my ($stdout, $strderr, @ret) = make_request('ar', 'add', type => 'invoice');
108 # generate form contents from html for the given form selector (defaults to '#form')
109 my %form = form_from_html($stdout, '#form');
111 # add values the user would enter
112 $form{partnumber} = "Part 1";
114 # there is no javascript emulation so you need to set ids like a picker would
115 $form{customer_id} = 14392;
117 # and do request again
118 ($stdout, $stderr, @ret) = make_request('ar', 'update', %form);
120 # also works with new controllers
121 ($stdout, $stderr, @ret) = make_request('Part', 'new', %form);
125 This is intended as a simple way of testing user centric journeys spanning
128 See synopsis for the intended usage. C<make_request> emulates most of the
129 usual dispatching and routing and simply returns the html output of the action.
130 C<form_from_html> extracts what would be submitted from a <form> element in the
139 Reigsters a mock dispatcher global so that C<$::dispatcher->end_request> works.
140 Needs to be called before using L</make_request>
142 =item * make_request CONTROLLER, ACTION, [ FORM_KEY => FORM_VALUE, .... ]
144 Emulates a request. Supported are both old-style and new-style controllers.
145 Since the routing code does not distinguish between verbs (GET/POST), this doesn't either.
147 Returns stdout, stderr and the return values of the action.
149 Exceptions are bubbled as is, with the exception of the graceful end_request.
151 =item * form_from_html HTML_STRING, [ FORM_SELECTOR ]
153 Parses the given html string into a dom, finds all inputs
154 and selects within the given form selector
155 and extracts their key values into a return hash.
157 If ommited, form selector defaults to C<#form>.
159 See L</CAVEATS> for limitations.
167 =item * No authentication
169 This is not intended to emulate the login process. It is expected that
170 C<Support::TestSetup::login()> is called for any Requests that require user access.
172 =item * No Javascript emulation
174 This means: no pickers, no auto format, no auto update on change.
175 No dynamic content like added rows or autocompletion.
177 =item * Form extraction is stupid
179 This simply gets all non-disabled inputs and selects within a form selector.
180 The order of elements is strictly DOM order, which is important for checkbox_for_submit
181 and ParseFilter serialization.
183 =item * No network round-trips
185 Requests will be emulated within the same process without a network roundtrip.
186 This is not for full black box system tests.
188 =item * Warnings are suppressed
190 Test::Harness will sometimes enable warnings for all code, which then pollutes
191 stderr. so warnings are suppressed for make_request.
197 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>