Monday, September 24, 2012

Extend Selenium::Remote::Driver to support applicationName capability

Why would you need applicationName support incorporated into your automated testing framework? Well if for no other reason of having more granularity when defining the environment in which your automated web application tests will execute in. It is my opinion that we have to be in full control (to the largest extent possible) of the test environment. And this type of control begins when we are defining the environment.

The module that we will be making changes to is Driver.pm (available from CPAN). It is an implementation of the driver that Selenium provides (in Perl) to the Selenium stand alone server (which is required to use the Perl language bindings to Selenium).

NOTE: I recommend the use your Perl package manager to download and install Selenium::Remote::Driver plus its dependencies and familiarizing yourself with it before going through the below changes.

So how do you add this support to the already great Perl language bindings to the Selenium driver? Below is how I did it:

Changes to Driver.pm to support applicationName capability:

1. Add application_name property to the new method (see code below)
2. Add applicationName (see code below) to new_session %args hash

Basically in order to support applicationName property you need to update the new and new_session methods respectively as outlined below: (note code is a fragment used for reference). The code below is found around line 173 of the Driver.pm module; the newly inserted line is number 12, depicted by >>>> <<<< in both.
1:  sub new {  
2:    my ( $class, %args ) = @_;  
3:    my $ress = new Selenium::Remote::Commands;  
4:    
5:    # Set the defaults if user doesn't send any  
6:    my $self = {  
7:      remote_server_addr => delete $args{remote_server_addr} || 'localhost',  
8:      browser_name    => delete $args{browser_name}    || 'firefox',  
9:      platform      => delete $args{platform}      || 'ANY',  
10:      port        => delete $args{port}        || '4444',  
11:      version      => delete $args{version}      || '',  
12:   >>>>> application_name  => delete $args{application_name}  || undef, <<<<<<  
13:      session_id     => undef,  
14:      remote_conn    => undef,  
15:      commands      => $ress,  
16:      auto_close     => 1, # by default we will close remote session on DESTROY  
17:      pid        => $$,  
18:    };  

1:  sub new_session {  
2:    my ($self, $extra_capabilities) = @_;  
3:    $extra_capabilities ||= {};  
4:    my $args = {  
5:      'desiredCapabilities' => {  
6:        'browserName'    => $self->{browser_name},  
7:        'platform'     => $self->{platform},  
8:        'javascriptEnabled' => $self->{javascript},  
9:        'version'      => $self->{version},  
10:        'acceptSslCerts'  => $self->{accept_ssl_certs},  
11:        'proxy'       => $self->{proxy},  
12:    >>>>>> 'applicationName'  => $self->{application_name},  <<<<<<  
13:        %$extra_capabilities,  
14:      },  
15:    };  
After the above modifications you can, in your test scripts, add the applicationName property to your desired capabilities hash. Below is an example:
1:    my %desired_capabilities = ( remote_server_addr => $grid_server,  
2:                   browser_name => $browser,  
3:                   platform => $os_platform,  
4:                   port => $port,  
5:                   application_name => $app_name,  
6:                   proxy => {  
7:                     proxyType => 'system'  
8:                   });  
9:    
10:    my $driver = Selenium::Remote::Driver->new(%desired_capabilities);  
Benefits of running with the above:
You can now define specifically which Browser, OS, Platform to run your test against. For example lets say you want to run a test against Firefox on Windows 7 x64; with the current implementation there is no way to force the grid to select a speficic platform.

With this change you can define an environment (application) when the node is being registered with the hub:
 java -jar selenium-server-standalone-2.25.0.jar -role node -hub http://localhost:4445/wd/hub -browser browserName=firefox,maxInstances=3,platform=VISTA,applicationName=WIN7x64FFX -port 5556  

The above command registers a node with the Firefox application running on the node using the specified applicationName=WIN7x64FFX. When test scripts request this specific environment (via the applicationName property) they will be sent to this node for test execution.

If you do not feel like doing all this yourself then feel free to download the file Driver.pm from my fork on github of the Selenium Perl bindings by clicking the following link: https://github.com/freddyvega/Selenium-Remote-Driver/blob/support-application-name-capability/lib/Selenium/Remote/Driver.pm

Saturday, September 15, 2012

Another method for running Selenium tests in parallel using Perl

Here is another quick tutorial on how to implement a parallel test runner (for Selenium or any other type of test).

First the code:
1:  #!C:/Perl64/bin/perl  
2:  use strict;  
3:  use warnings;  
4:  use File::Find::Rule;  
5:    
6:  my $start_dir = shift || 'C:\Tests';  
7:  my $count = 0;  
8:    
9:  my @subdirs = File::Find::Rule->directory->in($start_dir);  
10:    
11:  my @files = File::Find::Rule->file()  
12:                ->name( '*.pl' ) # Only get perl files  
13:                ->in( $subdirs[0] );  
14:  foreach my $test (@files) {  
15:    system(1, $test);  
16:    print "Executing Test: " . $test . " Sequence #: " . $count . "\n";  
17:    print "My Father Is: " . $$ . "\n"; # process that started me  
18:    $count++;  
19:  }  
20:  exit(0);  
The magic here is being performed by line 15. Using the perl function system in this special form we can force it to not wait for the process it is launching before continuing, effectively allowing us to spawn multiple tests at the [almost] same time.

From the Perl docs:
system(1, @args)
spawns an external process and immediately returns its process designator, without waiting for it to terminate.
The above script will output the following:
 Executing Test: C:\Temp\test_template.pl Sequence #: 0  
 My Father Is: 8692  
 Executing Test: C:\Temp\test_template2.pl Sequence #: 1  
 My Father Is: 8692  
 Executing Test: C:\Temp\test_template3.pl Sequence #: 2  
 My Father Is: 8692  
 Executing Test: C:\Temp\test_template4.pl Sequence #: 3  
 My Father Is: 8692  
The below image shows how, in my debugger, the test runner has stopped but the 4 spawned processes are still running (theoretically running automated tests) as indicated in my Windows Task Manager by the running perl.exe processes.



That's all it takes to launch multiple automated tests at the same time, using Perl's system function.

Thursday, September 13, 2012

Running Selenium tests in parallel using Perl

Some are of the opinion that you should not try to start to experiment with parallelization of Selenium tests until you have a need for it. I do not share that opinion. I think you should dive into parallelization of your tests as soon as you have enough tests to run in parallel (e.g. TWO! :))

So what does it take to run tests in parallel? Well all you need is a parallel test runner. Fortunately for us, Perl is the Swiss Army knife of the internet (and automation as well). For our implementation we'll be using one module and perl's fork().

The first module is available here: File::Find::Rule
The second comes with your Perl distribution

We'll be using File::Find::Rule to parse directories and create lists (arrays) of files in the process; and for managing our multiple tests running in parallel fork().

1:  #!C:/Perl64/bin/perl  
2:  use strict;  
3:  use warnings;  
4:  use File::Find::Rule;  
5:    
6:  my $start_dir = shift || 'C:\Tests\';  
7:  my $count = 0;  
8:    
9:  my @subdirs = File::Find::Rule->directory->in($start_dir);  
10:    
11:  my @files = File::Find::Rule->file()  
12:                ->name( '*.pl' ) # Get only perl files  
13:                ->in( $subdirs[0] );  
14:    
15:  foreach my $test (@files) {  
16:    my $pid = fork;  
17:    die "fork failed" unless defined $pid;  
18:      
19:    if ($pid == 0) { # We Got a Child  
20:      print "Executing Test: " . $test . " Sequence #: " . $count . "\n";  
21:      my $exit_code = system($test);  
22:      print "Done Executing Test: " . $test . " Sequence #: " . $count . "\n";  
23:      print "Exit Code: " . ($exit_code >> 8) . "\n";  
24:      exit ($exit_code >> 8);  
25:    }  
26:    $count++;    
27:  }  
28:  exit(0);  

If you run the above script with two .pl files in the C:\Temp\ directory you get the following output:

 Executing Test: C:\Temp\test1.pl Sequence #: 0  
 Executing Test: C:\Temp\test2.pl Sequence #: 1  
 Done Executing Test: C:\Temp\test2.pl Sequence #: 1  
 Exit Code: 2  
 Done Executing Test: C:\Temp\test1.pl Sequence #: 0  
 Exit Code: 1  

This is what the above script is doing:
1. You pass in a directory or it uses C:\Tests\ as the default
2. Create a list of subdirectories
3. Go into each directory and get a list of files
4. Fork each one of those files (making sure they exit)

NOTE: Number 4 above is important! Unless you like zombies, that is!
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.