# -*- Mode: perl; indent-tabs-mode: nil -*- package QA::Util; use strict; use Data::Dumper; use HTTP::Cookies; use Test::More; use Test::WWW::Selenium; use WWW::Selenium::Util qw(server_is_running); use base qw(Exporter); @QA::Util::EXPORT = qw( trim url_quote random_string pause _ok log_in logout file_bug_in_product go_to_admin edit_product add_product open_advanced_search_page set_parameters delete_bugs delete_flag_types get_selenium get_rpc_clients WAIT_TIME CHROME_MODE ); # How long we wait for pages to load. use constant WAIT_TIME => 120000; use constant CONF_FILE => "../config/selenium_test.conf"; use constant CHROME_MODE => 1; ##################### # Utility Functions # ##################### sub _ok($$) { ok($_[0], $_[1]) or pause(1); } sub pause { my $i = $_[0] || 0; my $c; do { $c = [caller($i++)] } while ($c->[0] eq 'Test::WWW::Selenium' || $c->[0] eq 'WWW::Selenium' || $c->[0] eq 'QA::Util'); print STDERR "***PAUSED*** at ".$c->[1].":".$c->[2].". Press Enter to continue\n"; getc; } sub random_string { my $size = shift || 30; # default to 30 chars if nothing specified return join("", map{ ('0'..'9','a'..'z','A'..'Z')[rand 62] } (1..$size)); } # Remove consecutive as well as leading and trailing whitespaces. sub trim { my ($str) = @_; if ($str) { $str =~ s/[\r\n\t\s]+/ /g; $str =~ s/^\s+//g; $str =~ s/\s+$//g; } return $str; } # This originally came from CGI.pm, by Lincoln D. Stein sub url_quote { my ($toencode) = (@_); $toencode =~ s/([^a-zA-Z0-9_\-.])/uc sprintf("%%%02x",ord($1))/eg; return $toencode; } ################### # Setup Functions # ################### sub get_config { # read the test configuration file my $conf_file = CONF_FILE; my $config = do($conf_file) or die "can't read configuration '$conf_file': $!$@"; } sub get_selenium { my $chrome_mode = shift; my $config = get_config(); if (!server_is_running) { die "Selenium Server isn't running!"; } my $sel = Test::WWW::Selenium->new( host => $config->{host}, port => $config->{port}, browser => $chrome_mode ? $config->{experimental_browser_launcher} : $config->{browser}, browser_url => $config->{browser_url}, error_callback => \&pause, ); return ($sel, $config); } sub get_xmlrpc_client { my $config = get_config(); my $xmlrpc_url = $config->{browser_url} . "/" . $config->{bugzilla_installation} . "/xmlrpc.cgi"; require QA::RPC::XMLRPC; # A temporary cookie jar that isn't saved after the script closes. my $cookie_jar = new HTTP::Cookies(); my $rpc = new QA::RPC::XMLRPC(proxy => $xmlrpc_url); $rpc->transport->cookie_jar($cookie_jar); return ($rpc, $config); } sub get_jsonrpc_client { require QA::RPC::JSONRPC; # A temporary cookie jar that isn't saved after the script closes. my $cookie_jar = new HTTP::Cookies(); my $rpc = new QA::RPC::JSONRPC(); $rpc->transport->cookie_jar($cookie_jar); # If we don't set a long timeout, then the Bug.add_comment test # where we add a too-large comment fails. $rpc->transport->timeout(180); $rpc->version('1.0'); return $rpc; } sub get_rpc_clients { my ($xmlrpc, $config) = get_xmlrpc_client(); my $jsonrpc = get_jsonrpc_client(); return ($xmlrpc, $jsonrpc, $config); } ################################ # Helpers for Selenium Scripts # ################################ # Go to the home/login page and log in. sub log_in { my ($sel, $config, $user) = @_; $sel->open_ok("/$config->{bugzilla_installation}/", undef, "Go to the login page"); $sel->type_ok("Bugzilla_login_top", $config->{"${user}_user_login"}, "Enter $user login name"); $sel->type_ok("Bugzilla_password_top", $config->{"${user}_user_passwd"}, "Enter $user password"); $sel->click_ok("log_in_top", undef, "Submit credentials"); $sel->wait_for_page_to_load(WAIT_TIME); $sel->title_is("Bugzilla Main Page", "User is logged in"); } # Log out. Will fail if you are not logged in. sub logout { my $sel = shift; $sel->click_ok("link=Log out", undef, "Logout"); $sel->wait_for_page_to_load_ok(WAIT_TIME); $sel->title_is("Logged Out"); } # Display the bug form to enter a bug in the given product. sub file_bug_in_product { my ($sel, $product, $classification, $component) = @_; $classification ||= "All"; $sel->click_ok("link=New", undef, "Go create a new bug"); $sel->wait_for_page_to_load(WAIT_TIME); my $title = $sel->get_title(); if ($title eq "Select Classification") { ok(1, "More than one enterable classification available. Display them in a list"); $sel->click_ok("link=$classification", undef, "Choose $classification"); $sel->wait_for_page_to_load(WAIT_TIME); $title = $sel->get_title(); } if ($title eq "Enter Bug") { ok(1, "Display the list of enterable products"); $sel->click_ok("link=$product", undef, "Choose $product"); $sel->wait_for_page_to_load(WAIT_TIME); } else { ok(1, "Only one product available in $classification. Skipping the 'Choose product' page.") } $sel->title_is("Enter Bug: $product", "Display form to enter bug data"); $sel->select("component", $component ? "label=$component" : "index=0"); } # Go to admin.cgi. sub go_to_admin { my $sel = shift; $sel->click_ok("link=Administration", undef, "Go to the Admin page"); $sel->wait_for_page_to_load(WAIT_TIME); $sel->title_like(qr/^Administer your installation/, "Display admin.cgi"); } # Go to editproducts.cgi and display the given product. sub edit_product { my ($sel, $product, $classification) = @_; $classification ||= "Unclassified"; go_to_admin($sel); $sel->click_ok("link=Products", undef, "Go to the Products page"); $sel->wait_for_page_to_load(WAIT_TIME); my $title = $sel->get_title(); if ($title eq "Select Classification") { ok(1, "More than one enterable classification available. Display them in a list"); $sel->click_ok("link=$classification", undef, "Choose $classification"); $sel->wait_for_page_to_load(WAIT_TIME); } else { $sel->title_is("Select product", "Display the list of enterable products"); } $sel->click_ok("link=$product", undef, "Choose $product"); $sel->wait_for_page_to_load(WAIT_TIME); $sel->title_is("Edit Product '$product'", "Display properties of $product"); } sub add_product { my ($sel, $classification) = @_; $classification ||= "Unclassified"; go_to_admin($sel); $sel->click_ok("link=Products", undef, "Go to the Products page"); $sel->wait_for_page_to_load(WAIT_TIME); my $title = $sel->get_title(); if ($title eq "Select Classification") { ok(1, "More than one enterable classification available. Display them in a list"); $sel->click_ok("//a[contains(\@href, 'editproducts.cgi?action=add&classification=$classification')]", undef, "Add product to $classification"); } else { $sel->title_is("Select product", "Display the list of enterable products"); $sel->click_ok("link=Add", undef, "Add a new product"); } $sel->wait_for_page_to_load(WAIT_TIME); $sel->title_is("Add Product", "Display the new product form"); } sub open_advanced_search_page { my $sel = shift; $sel->click_ok("link=Search"); $sel->wait_for_page_to_load(WAIT_TIME); my $title = $sel->get_title(); if ($title eq "Simple Search") { ok(1, "Display the simple search form"); $sel->click_ok("link=Advanced Search"); $sel->wait_for_page_to_load(WAIT_TIME); } $sel->title_is("Search for bugs", "Display the Advanced search form"); } # $params is a hashref of the form: # {section1 => { param1 => {type => '(text|select)', value => 'foo'}, # param2 => {type => '(text|select)', value => 'bar'}, # param3 => undef }, # section2 => { param4 => ...}, # } # section1, section2, ... is the name of the section # param1, param2, ... is the name of the parameter (which must belong to the given section) # type => 'text' is for text fields # type => 'select' is for drop-down select fields # undef is for radio buttons (in which case the parameter must be the ID of the radio button) # value => 'foo' is the value of the parameter (either text or label) sub set_parameters { my ($sel, $params) = @_; go_to_admin($sel); $sel->click_ok("link=Parameters", undef, "Go to the Config Parameters page"); $sel->wait_for_page_to_load(WAIT_TIME); $sel->title_is("Configuration: Required Settings"); my $last_section = "Required Settings"; foreach my $section (keys %$params) { if ($section ne $last_section) { $sel->click_ok("link=$section"); $sel->wait_for_page_to_load_ok(WAIT_TIME); $sel->title_is("Configuration: $section"); $last_section = $section; } my $param_list = $params->{$section}; foreach my $param (keys %$param_list) { my $data = $param_list->{$param}; if (defined $data) { my $type = $data->{type}; my $value = $data->{value}; if ($type eq 'text') { $sel->type_ok($param, $value); } elsif ($type eq 'select') { $sel->select_ok($param, "label=$value"); } else { ok(0, "Unknown parameter type: $type"); } } else { # If the value is undefined, then the param name is # the ID of the radio button. $sel->click_ok($param); } } $sel->click_ok('//input[@type="submit" and @value="Save Changes"]', undef, "Save Changes"); $sel->wait_for_page_to_load_ok(WAIT_TIME); $sel->title_is("Parameters Updated"); } } sub delete_bugs { my ($sel, $config, $bugs) = @_; # Enable allowbugdeletion set_parameters($sel, { "Administrative Policies" => {"allowbugdeletion-on" => undef } }); edit_product($sel, 'TestProduct', 'Unclassified'); $sel->click_ok("link=Edit components:"); $sel->wait_for_page_to_load_ok(WAIT_TIME); $sel->title_is("Select component of product 'TestProduct'"); my $text = trim($sel->get_text("bugzilla-body")); if ($text !~ /KillerComponent/) { # Create KillerComponent $sel->click_ok("link=Add"); $sel->wait_for_page_to_load_ok(WAIT_TIME); $sel->type_ok("component", "KillerComponent"); $sel->type_ok("description", "Component killing bugs"); $sel->type_ok("initialowner", $config->{unprivileged_user_login}); $sel->type_ok("initialqacontact", $config->{unprivileged_user_login}); $sel->click_ok("create"); $sel->wait_for_page_to_load_ok(WAIT_TIME); } # Move bugs into KillerComponent for (@$bugs) { $sel->type_ok("quicksearch_top", $_); $sel->click_ok("find_top"); $sel->wait_for_page_to_load_ok(WAIT_TIME); $sel->title_like(qr/^Bug $_/); $sel->select_ok("product", "label=TestProduct"); $sel->select_ok("component", "label=KillerComponent"); $sel->click_ok("commit"); $sel->wait_for_page_to_load_ok(WAIT_TIME); if ($sel->get_title() =~ /Verify/) { $sel->click_ok("change_product"); $sel->wait_for_page_to_load_ok(WAIT_TIME); } $sel->title_is("Bug $_ processed"); } # Delete KillerComponent and all bugs in it edit_product($sel, 'TestProduct', 'Unclassified'); $sel->click_ok("link=Edit components:"); $sel->wait_for_page_to_load_ok(WAIT_TIME); $sel->title_is("Select component of product 'TestProduct'"); $sel->click_ok("//a[contains(\@href, 'editcomponents.cgi?action=del&product=TestProduct&component=KillerComponent')]"); $sel->wait_for_page_to_load_ok(WAIT_TIME); $sel->title_is("Delete component 'KillerComponent' from 'TestProduct' product"); $sel->click_ok("delete"); $sel->wait_for_page_to_load_ok(WAIT_TIME); $sel->title_is("Component Deleted"); } sub delete_flag_types { my ($sel, $config, $regexp) = @_; go_to_admin($sel); $sel->click_ok("link=Flags"); $sel->wait_for_page_to_load_ok(WAIT_TIME); $sel->title_is("Administer Flag Types"); for ('flag_types_bugs', 'flag_types_attachments') { my $flagtype_count = $sel->get_xpath_count("//table[\@id='$_']/tbody/tr"); for (my $i = 2; $i <= $flagtype_count; $i++) { my $flag_name = trim($sel->get_text("//table[\@id='$_']/tbody/tr[$i]/td[1]")); if ($flag_name =~ /$regexp/) { $sel->click_ok("//table[\@id='$_']/tbody/tr[$i]//a[contains(\@href,'action=confirmdelete')]"); $sel->wait_for_page_to_load_ok(WAIT_TIME); $sel->title_is("Confirm Deletion of Flag Type '$flag_name'"); $sel->click_ok("link=Yes, delete"); $sel->wait_for_page_to_load_ok(WAIT_TIME); $sel->title_is("Flag Type '$flag_name' Deleted"); my $msg = trim($sel->get_text("message")); _ok($msg eq "The flag type $flag_name has been deleted.", "Flag type $flag_name deleted"); $flagtype_count = $sel->get_xpath_count("//table[\@id='$_']/tbody/tr"); $i--; } } } } 1; __END__