Pregunta

I'm working on my first Perl script here and trying to be as efficient as possible by re-using code instead of having the same code over and over. I've tried a couple different things already to no avail.

I'm also trying to avoid having to just import the set of commands from an external file, but it's looking like the only viable option right now. So I'm seeking advice here.

Here is the gist of the script (taking advantage of Selenium):

#!/usr/bin/perl
use strict;
use warnings;

use Getopt::Long;
use WWW::Selenium;

my @changepass;
my $addsub     = '';
my $add2sub    = '';

GetOptions (
    "changepass|cp=s{3}" => \@changepass,
    "addsub|as=s" => \$addsub,
    "add2sub|a2s=s" => \$add2sub,
    "help|h" => \&do_help
) or die(&do_help);


sub login {
  my $sel = WWW::Selenium->new(
      host => "localhost",
      port => 4444,
      browser => "*googlechrome",
      browser_url => "example.com",
  );
  $sel->start;
  $sel->open("/login.php");
  $sel->wait_for_page_to_load("5000");
  $sel->type("id=loginSection-username", "username");
  $sel->type("id=loginSection-password", "password");
  $sel->click("name=send");
  $sel->wait_for_page_to_load("30000");
  sleep(2);

  return $sel;
}


sub do_changepass {
  my $email = $changepass[0];
  my $oldpass = $changepass[1];
  my $newpass = $changepass[2];
  my ($sel) = @_;
  $sel->click("css=#my-webspaces-container .more > a");
  $sel->wait_for_page_to_load("30000");
  ...MORE COMMANDS...
}


if (@changepass) {
  print "Changing password...\n";
  my $sel = do_changepass();
  print "Finished!\n";
}

So I'd like to be able to reuse certain blocks of code such as the my $sel variable. But I do realize it's an object. If I could somehow just store it as plain text and then import it into the function. (not via an external file, if possible.)

¿Fue útil?

Solución 2

To get working what you're trying to accomplish you have to take care about the scope of the $sel Object:

...

if (@commands){
    my $sel = login();
    do_changepass($sel);
}

sub login {
    my $sel = WWW::Selenium->new( host => "localhost",
                                  port => 4444,
                                  browser => "*googlechrome",
                                  browser_url => "example.com",
                                );
    $sel->start;
    $sel->open("/login.php");
    $sel->wait_for_page_to_load("5000");
    $sel->type("id=loginSection-username", "username");
    $sel->type("id=loginSection-password", "password");
    $sel->click("name=send");
    $sel->wait_for_page_to_load("30000");
    sleep(2);

    return $sel;
}

sub do_changepass {
    my $email = $changepass[0]; my $oldpass = $changepass[1]; my $newpass = $changepass[2];
    my ($sel) = @_;
    $sel->click("css=#my-webspaces-container .more > a");
    $sel->wait_for_page_to_load("30000");
    ...MORE COMMANDS...
}
...

my creates the variable with a lexical scope, so it will be gone after you leave the current block (and there are no references to it neither).

or you can use the login-subroutine directly in the do_changepass

...
    sub do_changepass {
        my $email = $changepass[0]; my $oldpass = $changepass[1]; my $newpass = $changepass[2];
        my ($sel) = login(); # <--- login used here!!!
        $sel->click("css=#my-webspaces-container .more > a");
        $sel->wait_for_page_to_load("30000");
        ...MORE COMMANDS...
    }
...

Otros consejos

Note that you should never put prototypes on Perl subroutines (the () after the subroutine identifier). You must also always add use strict and use warnings to the top of every program. (use warnings is preferable to -w in the shebang line.)

You can return the $sel object that you have created in one subroutine and use it further in another. It's not clear what sort of thing you want to do as your two subroutines appear to be identical apart from a comment at the end of the second, but this may help

#!/usr/bin/perl
use strict;
use warnings;

use WWW::Selenium;

sub login {
  my $sel = WWW::Selenium->new(
      host => "localhost",
      port => 4444,
      browser => "*googlechrome",
      browser_url => "example.com",
  );
  $sel->start;
  $sel->open("/login.php");
  $sel->wait_for_page_to_load("5000");
  $sel->type("id=username", "usernamehere");
  $sel->type("id=password", "passwordhere");
  $sel->click("name=send");
  $sel->wait_for_page_to_load("30000");
  sleep(2);

  return $sel;
}

sub some_function {
  my ($sel) = @_;
  # ....MORE COMMANDS HERE....
}

my $sel = login();
some_function($sel);

Update

What you have wrong with your code is that you never call login. If you look at my example above, login is called to do the login and return the value of $sel, which is then passed to some_function.

Your code should look like this

if (@changepass) {
  print "Changing password...\n";
  my $sel = login();
  do_changepass($sel);
  print "Finished!\n";
}

I also suggest that you avoid calling login from within each subroutine. It is best called from the same code that calls do_changepass etc. as above.

In my experience, one of the best things you can do to avoid duplication as well as increase maintainability is to introduce abstraction.

In order to reuse your "selenium" instance throughout the script here is what I do:

In my test script:

my $driver = Custom::WebApp::setup_selenium( undef, undef, \%desired_capabilities );

In my WebApp.pm:

sub setup_selenium {

    my $self         = shift;
    my $browser      = shift;
    my $capabilities = shift;
    my $driver;
    my %desired_capabilities;

    if ($capabilities) {
        %desired_capabilities = %$capabilities;
    }

    # Start selenium with capabilities if passed in from test script
    unless ( keys %desired_capabilities == 0 ) {

        $driver = eval {
            Selenium::Remote::Driver->new(%desired_capabilities);
        };
        return $@ if $@;    # Return with error if capability not matched
    }

    # Or just start it with default settings
    else {
        $driver = eval {
            Selenium::Remote::Driver->new( browser_name => $browser,
                                           proxy => { proxyType => 'system' } );
        };
        return $@ if $@;    # Return with error if capability not matched
    }
    return $driver;

}

The driver that is returned by setup_selenium can then be used throughout the script until you call $driver->quit;

If you notice, the above solution is using Selenium::Remote::Driver binding but you can easily replace WWW::Selenium if you like (although I do recommend S:R:D mainly because it's actively maintained on Git and CPAN).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top