Profile.pm 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. package Selenium::Remote::Driver::Firefox::Profile;
  2. # ABSTRACT: Use custom profiles with Selenium::Remote::Driver
  3. use strict;
  4. use warnings;
  5. use Archive::Zip qw( :ERROR_CODES );
  6. use Archive::Extract;
  7. use Carp qw(croak);
  8. use Cwd qw(abs_path);
  9. use File::Copy qw(copy);
  10. use File::Temp;
  11. use MIME::Base64;
  12. use Scalar::Util qw(blessed looks_like_number);
  13. sub new {
  14. my $class = shift;
  15. # TODO: add handling for a pre-existing profile folder passed into
  16. # the constructor
  17. my $self = {
  18. profile_dir => File::Temp->newdir(),
  19. user_prefs => {},
  20. extensions => []
  21. };
  22. bless $self, $class or die "Can't bless $class: $!";
  23. return $self;
  24. }
  25. sub set_preference {
  26. my ($self, %prefs) = @_;
  27. foreach (keys %prefs) {
  28. my $value = $prefs{$_};
  29. my $clean_value = '';
  30. if (blessed($value) and $value->isa("JSON::Boolean")) {
  31. $clean_value = $value ? "true" : "false";
  32. }
  33. elsif ($value =~ /^(['"]).*\1$/ or looks_like_number($value)) {
  34. $clean_value = $value;
  35. }
  36. else {
  37. $clean_value = '"' . $value . '"';
  38. }
  39. $self->{user_prefs}->{$_} = $clean_value;
  40. }
  41. }
  42. sub set_preferences {
  43. my ($self, %prefs) = @_;
  44. $self->set_preference(%prefs);
  45. }
  46. sub get_preference {
  47. my ($self, $pref) = @_;
  48. return $self->{user_prefs}->{$pref};
  49. }
  50. sub add_extension {
  51. my ($self, $xpi) = @_;
  52. my $xpi_abs_path = abs_path($xpi);
  53. croak "$xpi_abs_path: extensions must be in .xpi format" unless $xpi_abs_path =~ /\.xpi$/;
  54. push (@{$self->{extensions}}, $xpi_abs_path);
  55. }
  56. sub path {
  57. my $self = shift;
  58. return $self->{profile_dir};
  59. }
  60. sub _encode {
  61. my $self = shift;
  62. # The remote webdriver accepts the Firefox profile as a base64
  63. # encoded zip file
  64. $self->_layout_on_disk();
  65. my $zip = Archive::Zip->new();
  66. my $dir_member = $zip->addTree( $self->{profile_dir} );
  67. my $string = "";
  68. open (my $fh, ">", \$string);
  69. binmode($fh);
  70. unless ( $zip->writeToFileHandle($fh) == AZ_OK ) {
  71. die 'write error';
  72. }
  73. return encode_base64($string);
  74. }
  75. sub _layout_on_disk {
  76. my $self = shift;
  77. $self->_write_preferences();
  78. $self->_install_extensions();
  79. return $self->{profile_dir};
  80. }
  81. sub _write_preferences {
  82. my $self = shift;
  83. my $userjs = $self->{profile_dir} . "/user.js";
  84. open (my $fh, ">>", $userjs)
  85. or croak "Cannot open $userjs for writing preferences: $!";
  86. foreach (keys %{$self->{user_prefs}}) {
  87. print $fh 'user_pref("' . $_ . '", ' . $self->get_preference($_) . ');' . "\n";
  88. }
  89. close ($fh);
  90. }
  91. sub _install_extensions {
  92. my $self = shift;
  93. my $extension_dir = $self->{profile_dir} . "/extensions/";
  94. mkdir $extension_dir unless -d $extension_dir;
  95. # TODO: handle extensions that need to be unpacked
  96. foreach (@{$self->{extensions}}) {
  97. # For Firefox to recognize the extension, we have to put the
  98. # .xpi in the /extensions/ folder and change the filename to
  99. # its id, which is found in the install.rdf in the root of the
  100. # zip.
  101. my $ae = Archive::Extract->new( archive => $_,
  102. type => "zip");
  103. my $tempDir = File::Temp->newdir();
  104. $ae->extract( to => $tempDir );
  105. my $install = $ae->extract_path();
  106. $install .= '/install.rdf';
  107. open (my $fh, "<", $install)
  108. or croak "No install.rdf inside $_: $!";
  109. my (@file) = <$fh>;
  110. close ($fh);
  111. my @name = grep { chomp; $_ =~ /<em:id>[^{]/ } @file;
  112. $name[0] =~ s/.*<em:id>(.*)<\/em:id>.*/$1/;
  113. my $xpi_dest = $extension_dir . $name[0] . ".xpi";
  114. copy($_, $xpi_dest)
  115. or croak "Error copying $_ to $xpi_dest : $!";
  116. }
  117. }
  118. 1;
  119. __END__
  120. =head1 SYNPOSIS
  121. use Selenium::Remote::Driver;
  122. use Selenium::Remote::Driver::Firefox::Profile;
  123. my $profile = Selenium::Remote::Driver::Firefox::Profile->new();
  124. $profile->set_preference(
  125. "browser.startup.homepage" => "http://www.google.com"
  126. );
  127. $profile->add_extension('t/www/redisplay.xpi');
  128. my $driver = Selenium::Remote::Driver->new(
  129. extra_capabilities => {
  130. firefox_profile => $profile
  131. });
  132. $driver->get("http://www.google.com");
  133. print $driver->get_title();
  134. =head1 DESCRIPTION
  135. You can use this module to create a custom Firefox Profile for your
  136. Selenium tests. Currently, you can set browser preferences and add
  137. extensions to the profile before passing it in the constructor for a
  138. new Selenium::Remote::Driver.