Profile.pm 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  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(looks_like_number);
  13. =head1 DESCRIPTION
  14. You can use this module to create a custom Firefox Profile for your
  15. Selenium tests. Currently, you can set browser preferences and add
  16. extensions to the profile before passing it in the constructor for a
  17. new Selenium::Remote::Driver.
  18. =head1 SYNPOSIS
  19. use Selenium::Remote::Driver;
  20. use Selenium::Remote::Driver::Firefox::Profile;
  21. my $profile = Selenium::Remote::Driver::Firefox::Profile->new;
  22. $profile->set_preference(
  23. 'browser.startup.homepage' => 'http://www.google.com',
  24. 'browser.cache.disk.capacity' => 358400
  25. );
  26. $profile->set_boolean_preference(
  27. 'browser.shell.checkDefaultBrowser' => 0
  28. );
  29. $profile->add_extension('t/www/redisplay.xpi');
  30. my $driver = Selenium::Remote::Driver->new(
  31. 'firefox_profile' => $profile
  32. );
  33. $driver->get('http://www.google.com');
  34. print $driver->get_title();
  35. =cut
  36. sub new {
  37. my $class = shift;
  38. # TODO: add handling for a pre-existing profile folder passed into
  39. # the constructor
  40. # TODO: accept user prefs, boolean prefs, and extensions in
  41. # constructor
  42. my $self = {
  43. profile_dir => File::Temp->newdir(),
  44. user_prefs => {},
  45. extensions => []
  46. };
  47. bless $self, $class or die "Can't bless $class: $!";
  48. return $self;
  49. }
  50. =method set_preference
  51. Set string and integer preferences on the profile object. You can set
  52. multiple preferences at once. If you need to set a boolean preference,
  53. see C<set_boolean_preference()>.
  54. $profile->set_preference("quoted.integer.pref" => '"20140314220517"');
  55. # user_pref("quoted.integer.pref", "20140314220517");
  56. $profile->set_preference("plain.integer.pref" => 9005);
  57. # user_pref("plain.integer.pref", 9005);
  58. $profile->set_preference("string.pref" => "sample string value");
  59. # user_pref("string.pref", "sample string value");
  60. =cut
  61. sub set_preference {
  62. my ($self, %prefs) = @_;
  63. foreach (keys %prefs) {
  64. my $value = $prefs{$_};
  65. my $clean_value = '';
  66. if (blessed($value) and $value->isa("JSON::Boolean")) {
  67. $clean_value = $value ? "true" : "false";
  68. }
  69. elsif ($value =~ /^(['"]).*\1$/ or looks_like_number($value)) {
  70. $clean_value = $value;
  71. }
  72. else {
  73. $clean_value = '"' . $value . '"';
  74. }
  75. $self->{user_prefs}->{$_} = $clean_value;
  76. }
  77. }
  78. sub set_preferences {
  79. my ($self, %prefs) = @_;
  80. $self->set_preference(%prefs);
  81. }
  82. =method get_preference
  83. Retrieve the computed value of a preference. Strings will be double
  84. quoted and boolean values will be single quoted as "true" or "false"
  85. accordingly.
  86. $profile->set_boolean_preference("true.pref" => 1);
  87. print $profile->get_preference("true.pref") # true
  88. $profile->set_preference("string.pref" => "an extra set of quotes");
  89. print $profile->get_preference("string.pref") # "an extra set of quotes"
  90. =cut
  91. sub get_preference {
  92. my ($self, $pref) = @_;
  93. return $self->{user_prefs}->{$pref};
  94. }
  95. =method add_extension
  96. Add an existing C<.xpi> to the profile by providing its path. This
  97. only works with packaged C<.xpi> files, not plain/un-packed extension
  98. directories.
  99. $profile->add_extension('t/www/redisplay.xpi');
  100. =cut
  101. sub add_extension {
  102. my ($self, $xpi) = @_;
  103. my $xpi_abs_path = abs_path($xpi);
  104. croak "$xpi_abs_path: extensions must be in .xpi format" unless $xpi_abs_path =~ /\.xpi$/;
  105. push (@{$self->{extensions}}, $xpi_abs_path);
  106. }
  107. sub path {
  108. my $self = shift;
  109. return $self->{profile_dir};
  110. }
  111. sub _encode {
  112. my $self = shift;
  113. # The remote webdriver accepts the Firefox profile as a base64
  114. # encoded zip file
  115. $self->_layout_on_disk();
  116. my $zip = Archive::Zip->new();
  117. my $dir_member = $zip->addTree( $self->{profile_dir} );
  118. my $string = "";
  119. open (my $fh, ">", \$string);
  120. binmode($fh);
  121. unless ( $zip->writeToFileHandle($fh) == AZ_OK ) {
  122. die 'write error';
  123. }
  124. return encode_base64($string);
  125. }
  126. sub _layout_on_disk {
  127. my $self = shift;
  128. $self->_write_preferences();
  129. $self->_install_extensions();
  130. return $self->{profile_dir};
  131. }
  132. sub _write_preferences {
  133. my $self = shift;
  134. my $userjs = $self->{profile_dir} . "/user.js";
  135. open (my $fh, ">>", $userjs)
  136. or croak "Cannot open $userjs for writing preferences: $!";
  137. foreach (keys %{$self->{user_prefs}}) {
  138. print $fh 'user_pref("' . $_ . '", ' . $self->get_preference($_) . ');' . "\n";
  139. }
  140. close ($fh);
  141. }
  142. sub _install_extensions {
  143. my $self = shift;
  144. my $extension_dir = $self->{profile_dir} . "/extensions/";
  145. mkdir $extension_dir unless -d $extension_dir;
  146. # TODO: handle extensions that need to be unpacked
  147. foreach (@{$self->{extensions}}) {
  148. # For Firefox to recognize the extension, we have to put the
  149. # .xpi in the /extensions/ folder and change the filename to
  150. # its id, which is found in the install.rdf in the root of the
  151. # zip.
  152. my $ae = Archive::Extract->new( archive => $_,
  153. type => "zip");
  154. my $tempDir = File::Temp->newdir();
  155. $ae->extract( to => $tempDir );
  156. my $install = $ae->extract_path();
  157. $install .= '/install.rdf';
  158. open (my $fh, "<", $install)
  159. or croak "No install.rdf inside $_: $!";
  160. my (@file) = <$fh>;
  161. close ($fh);
  162. my @name = grep { chomp; $_ =~ /<em:id>[^{]/ } @file;
  163. $name[0] =~ s/.*<em:id>(.*)<\/em:id>.*/$1/;
  164. my $xpi_dest = $extension_dir . $name[0] . ".xpi";
  165. copy($_, $xpi_dest)
  166. or croak "Error copying $_ to $xpi_dest : $!";
  167. }
  168. }
  169. 1;
  170. __END__
  171. =head1 SEE ALSO
  172. https://developer.mozilla.org/en-US/docs/Mozilla/Preferences/A_brief_guide_to_Mozilla_preferences