Tuesday, July 5, 2011

Automation System, Explained - Part 1 "The Classes"

A reader and fellow engineer suggested we start the "break down" of the automation system we introduced here last week by explaining the Selenium commands abstraction process. I will also explain the rationale behind our choosing a paged object design pattern for the implementation. The website used in the example for this exercise, somedatesite.com is fictitious and did not exist when I published this.

Problem:

  • Dynamic sites where element id's change often, especially the signup and login pages.
  • Automated tests failures usually alert the QA team of element changes
  • Element name updates have to be made to all of the scripts in the suite
  • Test engineers, at this company, were directed to concentrate only on test logic and were spending much time learning the Selenium-RC API.

Our Solution:

It is worth noting that we did spend a good amount of time understanding how our client's customer's used the site. Had we not done this, the resulting system would have most likely failed. Our proposed system has the following advantages:
  • Abstraction of Selenium commands
  • Test engineers will have access to and interface with exposed methods
  • Test scripts will be unaffected by element name changes. Other than failing tests, the logic of the test script will not need to be changed.
  • Whenever element names change, only the specific methods in the class that are affected need to be updated.
  • Learning curve for new test engineers is decreased because there is no Selenium to learn; well not right away.
somedatesite.com has a login page where users access the members only area.

Login form:
<form method="POST" action="main.php" id="login_form">
 <table>
  <tbody><tr>
   <td>email:</td>
    <td><input style="type="text" name="email" value="EmailOrUsername"></td>
     </tr>
       <tr>
        <td>password:</td>
         <td align="left"><input type="password" name="password" value=""> </td>
       </tr>
       <tr>
         <td><input name="remember_me" type="checkbox"></td>
          <td> remember me</td>
        </tr>
     <tr>
     <td>
      <input type="submit" value="sign in" name="login_submit" id="login_submit">
     </tr>
    </tbody>
   </table>
</form>

Login method:
sub login {
    my $self = shift;
    my $print_heading = shift;

    if ( $print_heading == 1 )  {

      write_log(4, "\nusing browser: $self->{browser}\n", $self->{log_file});
      write_log(4, "using email: $self->{email}\n", $self->{log_file});
      write_log(4, "using pword: $self->{password}\n", $self->{log_file});
      write_log(4, "using gender: $self->{gender}\n\n", $self->{log_file});
    }
    # Open the home page
    $self->{sel}->open_ok("/", $self->{browser});

    # Inject 1000 ms in between selenium calls
    $self->{sel}->set_speed("2000");

    # Enter email address and Password then Click Sign In button
    $self->{sel}->type_ok("email", $self->{email});

    $self->{sel}->type_ok("login_password", $self->{password});
    $self->{sel}->click_ok("login_submit");

    # Set time between selenium calls back to 1000 ms
    $self->{sel}->set_speed("1000");
}

New object method:
sub new {

    my $class = shift;
    my $self = bless { @_ }, $class;

    return $self;
}

As you can see from the login method above there is a lot going on there for a simple login function. This is a very simple login page, by the way.

In order to use the login method above we first have to instantiate an object from the class. For this our somedatesite.com class includes a new method that returns to us an object instantiated with all of the test data that will be used in the test execution.

In the test script:
Only test logic will be included in the test script. So for the example above:


use SomeWebSite;

my $swb_obj = SomeWebSite->new(
                               gender => $gender,
                               browser => $browser,
                               test_site => $test_site,
                               email => $email,
                               password => $pword 
                              );
$swb_obj->login(1);

undef $swb_obj;

exit;

In the above example the test engineer has effectively interfaced with a web site using Selenium without using a single selenium command; directly :). Writing all of those selenium commands every time you write a test that includes login into the page is cumbersome as well. Granted you have to at least tell the test how to use selenium or even to start it! We have a method for that as well.

Setup Selenium method:

    sub setup_selenium {

    my $self = shift;
    
    my $sel = Test::WWW::Selenium->new( host => "localhost",
                                        port => 4444,
                                        browser => $_[0],
                                        browser_url => $_[1] );
    return $sel;
}

In order to be able to use the same selenium instance throughout the test, we create a selenium object and include it as a value when creating the page object.

Lets update the example above to include the selenium initialization, changes highlighted in yellow:


use SomeWebSite;

my $sel =SomeWebSite::setup_selenium(undef, $browser, $test_site);

my $swb_obj = SomeWebSite->new(
                               gender => $gender,
                               browser => $browser,
                               test_site => $test_site,
                               email => $email,
                               password => $pword,
                               sel => $sel 
                              );

$swb_obj->login(1);

undef $swb_obj;

exit;


Now once you log in, you might want to check some text on the page. The check_text method will do just that.

Check text method:

sub check_text {


    my $self = shift;
    my $text = shift;
    my $mode = shift;


    given ($mode) {
        when "regexpi" { $self->{sel}->is_text_present_ok("regexpi:$text"); }
        when "glob" { $self->{sel}->is_text_present_ok("glob:$text"); }
        when "exact" { $self->{sel}->is_text_present_ok("exact:$text"); }
        default { Custom::GPSubs::write_log(4, "Search MODE was not supplied\n", $self->{logfile}) }
    }
}

Lets continue modifying the script above and check for the text "i am here", "you are there", "we are nowhere", changes highlighted in blue:


use SomeWebSite;

my $sel =SomeWebSite::setup_selenium(undef, $browser, $test_site);

my $swb_obj = SomeWebSite->new(
                               gender => $gender,
                               browser => $browser,
                               test_site => $test_site,
                               email => $email,
                               password => $pword,
                               sel => $sel 
                               );

$swb_obj->login(1);

my @check_text = "i am here", "you are there", "we are nowhere";

foreach my $text ( @check_text ) {

         $swb_obj->check_text($text, "regexpi");
}

undef $swb_obj;

exit;

In the example above, we first create an object with the new method. We then call the different methods to "do work" for us. In the example above selenium knows what to use for email, password and browser because when we created the object we defined all of these parameters (email, password, etc), and it uses it to login to the page. At the end of this sub execution the page under test will be left at the screen immediately after the login screen (i.e. the users Home Screen).

We can now do things like check what page we are on, using another method, get_current_page_url and once verified we are on the correct page, do some text verification using the check_text method.

As you can probably decipher by now, pretty much any "function" that needs to be done by a user, which will normally include a few selenium commands, can be abstracted in a class and expose human readable methods to test engineers in charge of writing automated regression tests. This is, by the way, where we recommend you use the most automation, in your regression test strategy.

In the next post in this series I will outline the system components and talk about the challenges faced and how we over came them.

We encourage feedback and dialogue so feel free to ask any questions or leave any comments. The lines are open... :)

No comments:

Post a Comment

Creative Commons License
VGP-Miami Web and Mobile Automation Blog by Alfred Vega is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.