Selenium-Remote-Driver
view release on metacpan or search on metacpan
lib/Selenium/Firefox/Profile.pm view on Meta::CPAN
package Selenium::Firefox::Profile;
$Selenium::Firefox::Profile::VERSION = '1.50';
# ABSTRACT: Use custom profiles with Selenium::Remote::Driver
# TODO: convert this to Moo!
use strict;
use warnings;
use Archive::Zip qw( :ERROR_CODES );
use Carp qw(croak);
use Cwd qw(abs_path);
use File::Copy qw(copy);
use File::Temp;
use File::Basename qw(dirname);
use IO::Uncompress::Unzip 2.030 qw($UnzipError);
use JSON qw(decode_json);
use MIME::Base64;
use Scalar::Util qw(blessed looks_like_number);
use XML::Simple;
sub new {
my $class = shift;
my %args = @_;
my $profile_dir;
if ( $args{profile_dir} && -d $args{profile_dir} ) {
$profile_dir = $args{profile_dir};
}
else {
$profile_dir = File::Temp->newdir();
}
# TODO: accept user prefs, boolean prefs, and extensions in
# constructor
my $self = {
profile_dir => $profile_dir,
user_prefs => {},
extensions => []
};
bless $self, $class or die "Can't bless $class: $!";
return $self;
}
sub set_preference {
my ( $self, %prefs ) = @_;
foreach ( keys %prefs ) {
my $value = $prefs{$_};
my $clean_value = '';
if ( JSON::is_bool($value) ) {
$self->set_boolean_preference( $_, $value );
next;
}
elsif ( $value =~ /^(['"]).*\1$/ or looks_like_number($value) ) {
# plain integers: 0, 1, 32768, or integers wrapped in strings:
# "0", "1", "20140204". in either case, there's nothing for us
# to do.
$clean_value = $value;
}
else {
# otherwise it's hopefully a string that we'll need to
# quote on our own
$clean_value = '"' . $value . '"';
}
$self->{user_prefs}->{$_} = $clean_value;
}
}
sub set_boolean_preference {
my ( $self, %prefs ) = @_;
foreach ( keys %prefs ) {
my $value = $prefs{$_};
$self->{user_prefs}->{$_} = $value ? 'true' : 'false';
}
}
sub get_preference {
my ( $self, $pref ) = @_;
return $self->{user_prefs}->{$pref};
}
sub add_extension {
my ( $self, $xpi ) = @_;
croak 'File not found: ' . $xpi unless -e $xpi;
my $xpi_abs_path = abs_path($xpi);
croak '$xpi_abs_path: extensions must be in .xpi format'
unless $xpi_abs_path =~ /\.xpi$/;
push( @{ $self->{extensions} }, $xpi_abs_path );
}
sub add_webdriver {
my ( $self, $port, $is_marionette ) = @_;
my $current_user_prefs = $self->{user_prefs};
$self->set_preference(
# having the user prefs here allows them to overwrite the
# mutable loaded prefs
%{$current_user_prefs},
# but the frozen ones cannot be overwritten
'webdriver_firefox_port' => $port
);
if ( !$is_marionette ) {
$self->_add_webdriver_xpi;
}
return $self;
}
sub _add_webdriver_xpi {
my ($self) = @_;
my $this_dir = dirname( abs_path(__FILE__) );
my $webdriver_extension = $this_dir . '/webdriver.xpi';
$self->add_extension($webdriver_extension);
}
sub add_marionette {
my ( $self, $port ) = @_;
return if !$port;
$self->set_preference( 'marionette.defaultPrefs.port', $port );
}
sub _encode {
my $self = shift;
# The remote webdriver accepts the Firefox profile as a base64
# encoded zip file
$self->_layout_on_disk();
my $zip = Archive::Zip->new();
$zip->addTree( $self->{profile_dir} );
my $string = "";
open( my $fh, ">", \$string );
binmode($fh);
unless ( $zip->writeToFileHandle($fh) == AZ_OK ) {
die 'write error';
}
return encode_base64( $string, '' );
}
sub _layout_on_disk {
my $self = shift;
$self->_write_preferences();
$self->_install_extensions();
return $self->{profile_dir};
}
sub _write_preferences {
my $self = shift;
my $userjs = $self->{profile_dir} . "/user.js";
open( my $fh, ">>", $userjs )
or die "Cannot open $userjs for writing preferences: $!";
foreach ( keys %{ $self->{user_prefs} } ) {
print $fh 'user_pref("'
. $_ . '", '
. $self->get_preference($_) . ');' . "\n";
}
close($fh);
}
sub _install_extensions {
my $self = shift;
my $extension_dir = $self->{profile_dir} . "/extensions/";
mkdir $extension_dir unless -d $extension_dir;
# TODO: handle extensions that need to be unpacked
foreach my $xpi ( @{ $self->{extensions} } ) {
# For Firefox to recognize the extension, we have to put the
# .xpi in the /extensions/ folder and change the filename to
# its id, which is found in the install.rdf in the root of the
# zip.
my $rdf_string = $self->_extract_install_rdf($xpi);
my $rdf = XMLin($rdf_string);
my $name = $rdf->{Description}->{'em:id'};
my $xpi_dest = $extension_dir . $name . ".xpi";
copy( $xpi, $xpi_dest )
or croak "Error copying $_ to $xpi_dest : $!";
}
}
sub _extract_install_rdf {
my ( $self, $xpi ) = @_;
my $unzipped = IO::Uncompress::Unzip->new($xpi)
or die "Cannot unzip $xpi: $UnzipError";
my $install_rdf = '';
while ( $unzipped->nextStream ) {
my $filename = $unzipped->getHeaderInfo->{Name};
if ( $filename eq 'install.rdf' ) {
my $buffer;
while ( ( my $status = $unzipped->read($buffer) ) > 0 ) {
$install_rdf .= $buffer;
}
return $install_rdf;
}
}
croak
'Invalid Firefox extension: could not find install.rdf in the .XPI at: '
. $xpi;
}
1;
__END__
=pod
=encoding UTF-8
( run in 0.940 second using v1.01-cache-2.11-cpan-8f98c5d2c55 )