package Test::WWW::Selenium::More; use Carp; use Moose; use Test::WWW::Selenium; use namespace::autoclean; our $VERSION = '0.10'; # VERSION has host => ( is => 'rw', isa => 'Str', builder => '_host' ); has port => ( is => 'rw', isa => 'Int', builder => '_port' ); has browser => ( is => 'rw', isa => 'Str', builder => '_browser' ); has browser_url => ( is => 'rw', isa => 'Str', builder => '_browser_url', required => 1, lazy => 1 ); has autostop => ( is => 'rw', isa => 'Bool', builder => '_autostop' ); has slow => ( is => 'rw', isa => 'Int', builder => '_slow' ); has _stash => ( is => 'rw', isa => 'HashRef', default => sub { {} } ); sub _host { $ENV{SELENIUM_HOST} || 'localhost' } sub _port { $ENV{SELENIUM_PORT} || 4444 } sub _browser { $ENV{SELENIUM_BROWSER} || '*chrome' } sub _autostop { $ENV{SELENIUM_AUTOSTOP} || 1 } sub _slow { $ENV{SELENIUM_SLOW} || 0 } sub _browser_url { $ENV{SELENIUM_BROWSER_URL} || confess 'browser_url is required'; } # Delegation. This effectively wraps Test::WWW::Selenium. # TODO I think this is kinda slow. Or maybe Moose is always slow? has selenium => ( is => 'ro', isa => 'Test::WWW::Selenium', handles => sub { map { chomp; $_ => $_ } ; }, builder => '_selenium', lazy => 1, # to ensure the builder works right ); sub _selenium { my $self = shift; my $selenium = Test::WWW::Selenium->new( port => $self->port, host => $self->host, browser => $self->browser, browser_url => $self->browser_url, autostop => $self->autostop, ); $selenium->open('/'); $selenium->delete_all_visible_cookies; return $selenium; } has _timeout => ( is => 'rw', isa => 'Int', default => 30000, ); # Override some of the Test::WWW::Selenium methods so that they return $self. # This enables us to chain commands. around qr/ ( _ok$ | # methods that end in _ok or _is$ | # methods that end in _is or _isnt$ | # methods that end in _isnt or _like$ # methods that end in _like ) /ix => sub { my $orig = shift; my $self = shift; $self->$orig(@_); sleep $self->slow; return $self; }; # Selenium has a bug where it (sometimes?) doesn't really wait for the page to # load unless you pass an argument to this sub. Also return $self so that we # can have chained commands. around wait_for_page_to_load_ok => sub { my $orig = shift; my $self = shift; if ( scalar @_ == 0 ) { $self->$orig( $self->_timeout ); } else { $self->$orig(@_); } return $self; }; # Selenium has a bug where it (sometimes?) doesn't really wait for the page to # load unless you pass an argument to this sub. But don't return $self. We # want to preserve return values from non test methods so that people can get # return values when they really want them. around wait_for_page_to_load => sub { my $orig = shift; my $self = shift; if ( scalar @_ == 0 ) { return $self->$orig( $self->_timeout ); } else { return $self->$orig(@_); } }; __PACKAGE__->meta->make_immutable; sub load_data { my ( $self, $file ) = @_; die $! unless ( my @test_data = do $file ); die $@ if $@; return do $file; } sub _wait_with_message { my ($self) = @_; my $t = $self->_timeout; my $msg = "waited ${t} milliseconds but page never loaded"; $self->wait_for_page_to_load or Test::Most::diag($msg); return $self; } sub follow_link_ok { my ( $self, $locator, $text ) = @_; my $test_description = "follow_link, ${locator} "; $test_description .= $text if defined $text; my $return_value = $self->click($locator) && $self->_wait_with_message(); Test::Most::ok( $return_value, $test_description ); return $self; } sub fill_form_ok { my ( $self, $form ) = @_; if ( $form->{input} ) { foreach my $locator ( keys %{ $form->{input} } ) { $self->type_ok( $locator, $form->{input}->{$locator} ); } } if ( $form->{select} ) { foreach my $locator ( keys %{ $form->{select} } ) { $self->select_ok( $locator, $form->{select}->{$locator} ); } } return $self; } sub submit_form_ok { my ( $self, $form ) = @_; die "submit or click keys are required" unless ( $form->{click} || $form->{submit} ); $self->fill_form_ok($form); $self->follow_link_ok( $form->{click} ) if $form->{click}; if ( $form->{submit} ) { $self->submit_ok( $form->{submit} ) && $self->_wait_with_message(); } return $self; } sub wait_for_jquery_ok { my ($self) = @_; $self->wait_for_condition_ok( 'selenium.browserbot.getCurrentWindow().jQuery.active == 0', $self->_timeout, # 'wait_for_jquery_to_load, ' . $self->_timeout # ); return $self; } sub jquery_select_ok { my $self = shift; $self->select_ok(@_) && $self->wait_for_jquery_ok; return $self; } sub select_and_page_load_ok { my $self = shift; $self->select_ok(@_) && $self->wait_for_page_to_load_ok; return $self; } sub jquery_click_ok { my $self = shift; $self->click_ok(@_) && $self->wait_for_jquery_ok; return $self; } sub stash_text { my $self = shift; my $locator = shift; my $key = shift; $self->_stash->{$key} = $self->get_text( $locator, @_ ); return $self; } sub stash_location { my $self = shift; my $key = shift; $self->_stash->{$key} = $self->get_location(@_); return $self; } sub from_stash { my $self = shift; my $key = shift; return $self->_stash->{$key}; } sub stash { my $self = shift; my $value = shift; my $key = shift; $self->_stash->{$key} = $value; return $self; } sub note { my ( $self, $msg ) = @_; Test::Most::note($msg); return $self; } sub is_text_not_present_ok { my ( $self, $text, $name ) = @_; $name //= "is_text_not_present, $text"; Test::Most::ok( !$self->is_text_present($text), $name ); return $self; } sub note_line { my ( $self, $msg ) = @_; my $length = length($msg) || 80; $self->note( "_" x $length ); $self->note( " " x $length ); $self->note($msg) if $msg; $self->note( "_" x $length ); $self->note( " " x $length ); return $self; } sub download_file_ok { my $self = shift or die; my $locator = shift or die; my $url = $self->get_attribute( $locator . '@href' ); my $return_value = $self->get_eval( qq{1 + 1}, $self->_timeout, # qq{downloading $url, } . $self->_timeout ); # Test::Most::is( $return_value, 2, qq{Download host is reachable: $return_value} ) or return $self; $self->run_script( 'function seleniumDownloadFileOk() { var xmlhttp=new XMLHttpRequest(); xmlhttp.open("GET","' . $url . '",false); xmlhttp.send(""); return xmlhttp.status; }', $self->_timeout, # '0 downloading $url, ' . $self->_timeout # ); my $status = $self->get_eval( 'selenium.browserbot.getCurrentWindow().seleniumDownloadFileOk()', $self->_timeout, # "downloading ${url}" . $self->_timeout ); # Test::Most::is( $status, 200, qq{Download status: $status} ); return $self; } sub change_speed { my $self = shift or die; my $seconds = shift || 0; $self->slow($seconds); return $self; } 1; # ABSTRACT: More tools for Selenium testing # I used this command to get the list of functions in WWW::Selenium: # grep '=item $sel-' /usr/local/lib/perl/5.10.0/WWW/Selenium.pm # Then I used vim to get rid of the extra stuff. Then I used vim to add the # additional functions from Test::WWW::Selenium. =pod =encoding utf-8 =head1 NAME Test::WWW::Selenium::More - More tools for Selenium testing =head1 SYNOPSIS use Test::WWW::Selenium::More; Test::WWW::Selenium::More->new() ->note('this is a test. this is only a test.') ->open_ok("/") ->is_text_present_ok("Welcome to the internet") ->download_file_ok('link=Download my file'); =head1 DESCRIPTION This module provides method chaining and some useful tools for Selenium testing. If you are new to this module or Selenium testing in general, see the L. This library extends L. Method chaining is available for all Test::WWW::Selenium::More methods and all Test::WWW::Selenium methods whose names end in _ok, _is, _isnt or _like. =head1 ATTRIBUTES =head2 host The hostname or ip address of the Selenium server. Defaults to 'localhost'. =head2 port The port of the Selenium server. Defaults to '4444'. =head2 browser The browser to run tests against on the Selenium server. Defaults to '*chrome'. =head2 browser_url The Selenium server runs tests against this website. =head2 autostop When $selenium goes out of scope the browser will be automatically shut down if this attribute is set to true. Otherwise stop() must be called explicitly. Defaults to 1. =head2 slow The number of seconds to sleep after each call to any Test::WWW::Selenium method. This is useful for slowing down tests if you are watching them run in a browser. Defaults to 0. =head2 stash A HashRef of saved values. This behaves similar to the Catalyst stash. =head1 METHODS =head2 load_data( $file ) Returns a data structure from $file. $file must have valid Perl syntax. This method is for use with data driven tests. It helps you to separate your Perl code from your test data. Here is an example of what the contents of $file could look like: ( { name => 'Name of my test', link => 'Click me', text => 'Ponies', }, { name => 'Test downloading stuff', link => 'Download', text => 'Download worked', }, ... ); =head2 follow_link_ok( $locator, $test_description ) Returns $self. This method performs a click_ok on the page element specified by $locator and then does a wait_for_page_to_load(). $test_description is optional. It will be set to something appropriate if you don't set it. =head2 fill_form_ok( $form ) Returns $self. $form must be a hashref that looks like this: $selenium->fill_form_ok( { select => { locator => value, ... }, input => { locator => value, ... }, } ); The 'select' key indicates dropdown menus that will be selected. The 'input' key indicates text input, hidden input, checkboxes, and radio buttons. The form is not submitted. =head2 submit_form_ok( $form ) Returns $self. $form must be a hashref that looks like this: $selenium->submit_form_ok( { select => { locator => value, ... }, input => { locator => value, ... }, click => 'locator', # or 'submit' instead of 'click' } ); $form is simply passed to fill_form_ok(). Afterwards submit_form_ok() looks for 2 keys to process: 'click' and 'submit'. The form is submitted via a click on page element indicated by 'locator' when 'click' is used. The form is submitted without a click when 'submit' is used. This is useful for forms without submit buttons. =head2 wait_for_jquery_ok() Returns $self. This blocks until jQuery.active to return false. =head2 jquery_select_ok($locator, $menu_option) Returns $self. $locator should point to a dropdown menu on the page. This method will select the $menu_option from the dropdown. Then it will call wait_for_jquery(). =head2 select_and_page_load_ok($locator, $menu_option) Returns $self. $locator should point to a dropdown menu on the page. This method will select the $menu_option from the dropdown. Then it will call wait_for_page_to_load(). =head2 jquery_click_ok($locator, $menu_option) Returns $self. Click whatever is located at $locator. Then wait for jquery to finish by calling wait_for_jquery(). =head2 stash_text( $locator => $key ) Returns $self. Retrieves the value of $locator from selenium and stores it in the stash under the name $key. =head2 stash_location( $key ) Returns $self. Retrieves the location from selenium and stores it in the stash under the name $key. =head2 from_stash( $key ) Returns a value from the stash. =head2 stash( $value => $key ) Returns $self. Saves $value to the stash under the name $key. =head2 note( $msg ) Returns $self. calls Test::Most::note($msg); =head2 is_text_not_present_ok( $text ) Returns $self. The opposite of is_text_present_ok(). =head2 note_line( $msg ) Outputs an underlined message, useful for dividing up test output. If no message specified, then just prints the separator line. =head2 download_file_ok($locator) Parses the href attribute from a link on the current page. Downloads that url via javascript's XMLHttpRequest. Checks that response status is 200. =head2 change_speed($seconds) This just updates the slow() attribute. The only difference is that it returns $self so that you can do method chaining. =head1 ENVIRONMENT VARIABLES The following is a list of environment variables that affect the behavior of this library. Each corresponds to an attribute (see the ATTRIBUTES section). =head2 SELENIUM_HOST =head2 SELENIUM_PORT =head2 SELENIUM_BROWSER =head2 SELENIUM_BROWSER_URL =head2 SELENIUM_TIMEOUT =head2 SELENIUM_AUTOSTOP =head2 SELENIUM_SLOW =head1 AUTHOR Eric Johnson =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2012 by Eric Johnson. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut __DATA__ get_absolute_location absolute_location_is absolute_location_isnt absolute_location_like absolute_location_unlike get_alert alert_is alert_isnt alert_like alert_unlike get_all_buttons all_buttons_is all_buttons_isnt all_buttons_like all_buttons_unlike get_all_fields all_fields_is all_fields_isnt all_fields_like all_fields_unlike get_all_links all_links_is all_links_isnt all_links_like all_links_unlike get_all_window_ids all_window_ids_is all_window_ids_isnt all_window_ids_like all_window_ids_unlike get_all_window_names all_window_names_is all_window_names_isnt all_window_names_like all_window_names_unlike get_all_window_titles all_window_titles_is all_window_titles_isnt all_window_titles_like all_window_titles_unlike get_attribute attribute_is attribute_isnt attribute_like attribute_unlike get_attribute_from_all_windows attribute_from_all_windows_is attribute_from_all_windows_isnt attribute_from_all_windows_like attribute_from_all_windows_unlike get_body_text body_text_is body_text_isnt body_text_like body_text_unlike get_checked checked_is checked_isnt checked_like checked_unlike get_confirmation confirmation_is confirmation_isnt confirmation_like confirmation_unlike get_cookie cookie_is cookie_isnt cookie_like cookie_unlike get_cookie_by_name cookie_by_name_is cookie_by_name_isnt cookie_by_name_like cookie_by_name_unlike get_cursor_position cursor_position_is cursor_position_isnt cursor_position_like cursor_position_unlike get_element_height element_height_is element_height_isnt element_height_like element_height_unlike get_element_index element_index_is element_index_isnt element_index_like element_index_unlike get_element_position_left element_position_left_is element_position_left_isnt element_position_left_like element_position_left_unlike get_element_position_top element_position_top_is element_position_top_isnt element_position_top_like element_position_top_unlike get_element_width element_width_is element_width_isnt element_width_like element_width_unlike get_eval eval_is eval_isnt eval_like eval_unlike get_expression expression_is expression_isnt expression_like expression_unlike get_html_source html_source_is html_source_isnt html_source_like html_source_unlike get_location location_is location_isnt location_like location_unlike get_mouse_speed mouse_speed_is mouse_speed_isnt mouse_speed_like mouse_speed_unlike get_prompt prompt_is prompt_isnt prompt_like prompt_unlike get_select_options select_options_is select_options_isnt select_options_like select_options_unlike get_selected_id selected_id_is selected_id_isnt selected_id_like selected_id_unlike get_selected_ids selected_ids_is selected_ids_isnt selected_ids_like selected_ids_unlike get_selected_index selected_index_is selected_index_isnt selected_index_like selected_index_unlike get_selected_indexes selected_indexes_is selected_indexes_isnt selected_indexes_like selected_indexes_unlike get_selected_label selected_label_is selected_label_isnt selected_label_like selected_label_unlike get_selected_labels selected_labels_is selected_labels_isnt selected_labels_like selected_labels_unlike get_selected_options selected_options_is selected_options_isnt selected_options_like selected_options_unlike get_selected_value selected_value_is selected_value_isnt selected_value_like selected_value_unlike get_selected_values selected_values_is selected_values_isnt selected_values_like selected_values_unlike get_speed speed_is speed_isnt speed_like speed_unlike get_table table_is table_isnt table_like table_unlike get_text text_is text_isnt text_like text_unlike get_title title_is title_isnt title_like title_unlike get_value value_is value_isnt value_like value_unlike get_whether_this_frame_match_frame_expression whether_this_frame_match_frame_expression_is whether_this_frame_match_frame_expression_isnt whether_this_frame_match_frame_expression_like whether_this_frame_match_frame_expression_unlike get_whether_this_window_match_window_expression whether_this_window_match_window_expression_is whether_this_window_match_window_expression_isnt whether_this_window_match_window_expression_like whether_this_window_match_window_expression_unlike get_xpath_count xpath_count_is xpath_count_isnt xpath_count_like xpath_count_unlike add_location_strategy add_location_strategy_ok add_script add_script_ok add_selection add_selection_ok allow_native_xpath allow_native_xpath_ok alt_key_down alt_key_down_ok alt_key_up alt_key_up_ok answer_on_next_prompt answer_on_next_prompt_ok assign_id assign_id_ok attach_file attach_file_ok capture_entire_page_screenshot capture_entire_page_screenshot_ok capture_entire_page_screenshot_to_string capture_entire_page_screenshot_to_string_ok capture_screenshot capture_screenshot_ok capture_screenshot_to_string capture_screenshot_to_string_ok check check_ok choose_cancel_on_next_confirmation choose_cancel_on_next_confirmation_ok choose_ok_on_next_confirmation choose_ok_on_next_confirmation_ok click click_ok click_at click_at_ok close close_ok context_menu context_menu_ok context_menu_at context_menu_at_ok control_key_down control_key_down_ok control_key_up control_key_up_ok create_cookie create_cookie_ok delete_all_visible_cookies delete_all_visible_cookies_ok delete_cookie delete_cookie_ok deselect_pop_up deselect_pop_up_ok double_click double_click_ok double_click_at double_click_at_ok drag_and_drop drag_and_drop_ok drag_and_drop_to_object drag_and_drop_to_object_ok dragdrop dragdrop_ok fire_event fire_event_ok focus focus_ok go_back go_back_ok highlight highlight_ok ignore_attributes_without_value ignore_attributes_without_value_ok is_alert_present is_alert_present_ok is_checked is_checked_ok is_confirmation_present is_confirmation_present_ok is_cookie_present is_cookie_present_ok is_editable is_editable_ok is_element_present is_element_present_ok is_location is_location_ok is_ordered is_ordered_ok is_prompt_present is_prompt_present_ok is_selected is_selected_ok is_something_selected is_something_selected_ok is_text_present is_text_present_ok is_visible is_visible_ok key_down key_down_ok key_down_native key_down_native_ok key_press key_press_ok key_press_native key_press_native_ok key_up key_up_ok key_up_native key_up_native_ok meta_key_down meta_key_down_ok meta_key_up meta_key_up_ok mouse_down mouse_down_ok mouse_down_at mouse_down_at_ok mouse_down_right mouse_down_right_ok mouse_down_right_at mouse_down_right_at_ok mouse_move mouse_move_ok mouse_move_at mouse_move_at_ok mouse_out mouse_out_ok mouse_over mouse_over_ok mouse_up mouse_up_ok mouse_up_at mouse_up_at_ok mouse_up_right mouse_up_right_ok mouse_up_right_at mouse_up_right_at_ok open open_ok open_window open_window_ok pause pause_ok refresh refresh_ok remove_all_selections remove_all_selections_ok remove_script remove_script_ok remove_selection remove_selection_ok retrieve_last_remote_control_logs retrieve_last_remote_control_logs_ok rollup rollup_ok run_script run_script_ok select select_ok select_frame select_frame_ok select_pop_up select_pop_up_ok select_window select_window_ok set_browser_log_level set_browser_log_level_ok set_context set_context_ok set_cursor_position set_cursor_position_ok set_mouse_speed set_mouse_speed_ok set_speed set_speed_ok set_timeout set_timeout_ok shift_key_down shift_key_down_ok shift_key_up shift_key_up_ok shut_down_selenium_server shut_down_selenium_server_ok submit submit_ok type type_ok type_keys type_keys_ok uncheck uncheck_ok use_xpath_library use_xpath_library_ok wait_for_condition wait_for_condition_ok wait_for_element_present wait_for_element_present_ok wait_for_frame_to_load wait_for_frame_to_load_ok wait_for_page_to_load wait_for_page_to_load_ok wait_for_pop_up wait_for_pop_up_ok wait_for_text_present wait_for_text_present_ok window_focus window_focus_ok window_maximize window_maximize_ok stop