123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375 |
- package Selenium::Firefox;
- use strict;
- use warnings;
- # ABSTRACT: Use FirefoxDriver without a Selenium server
- use Moo;
- use Carp;
- use Selenium::Firefox::Binary qw/firefox_path/;
- use Selenium::CanStartBinary::FindBinary
- qw/coerce_simple_binary coerce_firefox_binary/;
- extends 'Selenium::Remote::Driver';
- =head1 SYNOPSIS
- # These two are the same, and will only work with Firefox 48+
- my $driver = Selenium::Firefox->new;
- $driver = Selenium::Firefox->new( marionette_enabled => 1 );
- #Do stuff...
- $driver->shutdown_binary;
- # For Firefox 47 and older, disable marionette:
- $driver = Selenium::Firefox->new( marionette_enabled => 0 );
- $driver->shutdown_binary;
- =for Pod::Coverage has_binary
- =for Pod::Coverage has_firefox_binary
- =head1 DESCRIPTION
- B<Breaking Changes:> There are breaking changes in v1.0+ of this
- module if you're using it to start FF47; please see L</"BREAKING
- CHANGES">. You can ignore this if you're using v1.0+ of this module to
- start FF48.
- This class allows you to use the FirefoxDriver without needing the JRE
- or a selenium server running. Unlike starting up an instance of
- S::R::D, do not pass the C<remote_server_addr> and C<port> arguments,
- and we will search for the Firefox executable in your C<$PATH>. We'll
- try to start the binary, connect to it, and shut it down at the end of
- the test.
- If the Firefox application is not found in the expected places, we'll
- fall back to the default L<Selenium::Remote::Driver> behavior of
- assuming defaults of 127.0.0.1:4444 after waiting a few seconds.
- If you specify a remote server address, or a port, our assumption is
- that you are doing standard S::R::D behavior and we will not attempt
- any binary startup.
- If you're curious whether your Selenium::Firefox instance is using a
- separate Firefox binary, or through the selenium server, you can check
- the value of the C<binary_mode> attr after instantiation.
- =head1 BREAKING CHANGES
- In version v1.0+ and newer, the default behavior is to enable
- marionette & geckodriver mode. This means that an existing script that
- works with v0.2701 and Firefox v47 will require modification if you
- upgrade Selenium::Firefox to v1.0+. That is,
- # v0.2701 of Selenium::Firefox works with FF47 like such; this will not
- # work for FF47 after upgrade:
- my $fx47_old = Selenium::Firefox->new;
- ...
- $fx47_old->shutdown_binary;
- # v1.0 of Selenium::Firefox works with FF47 like this
- my $fx47_new = Selenium::Firefox->new( marionette_enabled => 0);
- ...
- $fx47_new->shutdown_binary;
- We default to assuming FF48 and geckodriver mode because all
- forthcoming versions of the Firefox browser will be using the
- geckodriver architecture, and also because that's consistent with the
- rest of the driver setups, which all have separate driver binaries
- apart from the browser itself. This means that:
- # v0.2701 of Selenium::Firefox cannot start up FF48 at all
- # v1.0+ of Selenium::Firefox works with FF48+ like this:
- my $fx48 = Selenium::Firefox->new;
- As with the other drivers, Selenium::Firefox in marionette/geckodriver
- mode requires a C<geckodriver> executable in the path or provided
- during startup, and it will also attempt to find the path to your
- Firefox browser. During testing, we found that it was necessary for us
- to pass the Firefox browser file path to the C<geckodriver> executable
- during start up, or else C<geckodriver> would have trouble finding
- Firefox.
- =cut
- has '+browser_name' => (
- is => 'ro',
- default => sub { 'firefox' }
- );
- =attr binary
- Optional: specify the path to the C<geckodriver> binary - this is NOT
- the path to the Firefox browser. To specify the path to your Firefox
- browser binary, see the L</firefox_binary> attr.
- For Firefox 48 and greater, this is the path to your C<geckodriver>
- executable. If you don't specify anything, we'll search for
- C<geckodriver> in your C<$PATH>.
- For Firefox 47 and older, this attribute does not apply, because the
- older FF browsers do not use the separate driver binary startup.
- =cut
- has 'binary' => (
- is => 'lazy',
- coerce => \&coerce_simple_binary,
- default => sub { 'geckodriver' },
- predicate => 1
- );
- =attr binary_port
- Optional: specify the port that we should bind to. If you don't
- specify anything, we'll default to the driver's default port. Since
- there's no a priori guarantee that this will be an open port, this is
- _not_ necessarily the port that we end up using - if the port here is
- already bound, we'll search above it until we find an open one.
- See L<Selenium::CanStartBinary/port> for more details, and
- L<Selenium::Remote::Driver/port> after instantiation to see what the
- actual port turned out to be.
- =cut
- has 'binary_port' => (
- is => 'lazy',
- default => sub { 9090 }
- );
- =attr firefox_profile
- Optional: Pass in an instance of L<Selenium::Firefox::Profile>
- pre-configured as you please. The preferences you specify will be
- merged with the ones necessary for setting up webdriver, and as a
- result some options may be overwritten or ignored.
- my $profile = Selenium::Firefox::Profile->new;
- my $firefox = Selenium::Firefox->new(
- firefox_profile => $profile
- );
- =cut
- has '_binary_args' => (
- is => 'lazy',
- builder => sub {
- my ($self) = @_;
- if ( $self->marionette_enabled ) {
- my $args =
- ' --port '
- . $self->port
- . ' --marionette-port '
- . $self->marionette_port
- . ' --binary "'
- . $self->firefox_binary . '"';
- return $args;
- }
- else {
- return ' -no-remote';
- }
- }
- );
- has '+wd_context_prefix' => (
- is => 'ro',
- default => sub {
- my ($self) = @_;
- if ( $self->marionette_enabled ) {
- return '';
- }
- else {
- return '/hub';
- }
- }
- );
- =attr marionette_binary_port
- Optional: specify the port that we should bind marionette to. If you don't
- specify anything, we'll default to the marionette's default port. Since
- there's no a priori guarantee that this will be an open port, this is
- _not_ necessarily the port that we end up using - if the port here is
- already bound, we'll search above it until we find an open one.
- Selenium::Firefox->new(
- marionette_enabled => 1,
- marionette_binary_port => 12345,
- );
- Attempting to specify a C<marionette_binary_port> in conjunction with
- setting C<marionette_enabled> does not make sense and will most likely
- not do anything useful.
- =cut
- has 'marionette_binary_port' => (
- is => 'lazy',
- default => sub { 2828 }
- );
- =attr marionette_enabled
- Optional: specify whether
- L<marionette|https://developer.mozilla.org/en-US/docs/Mozilla/QA/Marionette>
- should be enabled or not. By default, marionette is enabled, which
- assumes you are running with Firefox 48 or newer. To use this module to
- start Firefox 47 or older, you must pass C<< marionette_enabled => 0 >>.
- my $ff48 = Selenium::Firefox->new( marionette_enabled => 1 ); # defaults to 1
- my $ff47 = Selenium::Firefox->new( marionette_enabled => 0 );
- =cut
- has 'marionette_enabled' => (
- is => 'lazy',
- default => 1
- );
- =attr firefox_binary
- Optional: specify the path to the Firefox browser executable. Although
- we will attempt to locate this in your C<$PATH>, you may specify it
- explicitly here. Note that path here must point to a file that exists
- and is executable, or we will croak.
- For Firefox 48 and newer, this will be passed to C<geckodriver> such
- that it will attempt to start up the Firefox at the specified path. If
- you do not specify anything, we will look for the Firefox browser on
- our own in the normal places, but if the browser cannot be found,
- we'll probably C<die> during instantiation.
- For Firefox 47 and older, this browser path should be the file that we
- directly start up.
- =cut
- has 'firefox_binary' => (
- is => 'lazy',
- coerce => \&coerce_firefox_binary,
- predicate => 1,
- builder => 'firefox_path'
- );
- has '_execute_script_suffix' => (
- is => 'lazy',
- default => 'Gecko'
- );
- =head2 get_context
- Description:
- Firefox extension: Retrieve browser's scope (chrome or content).
- Chrome is a privileged scope where you can access things like the
- Firefox UI itself. Content scope is where things like webpages live.
- Output:
- STRING - context {CHROME|CONTENT}
- Usage:
- print $firefox_driver->get_context();
- =cut
- sub get_context {
- my $self = shift;
- if ( $self->_is_old_ff ) {
- return 0;
- }
- my $res = { 'command' => 'getContext' };
- return $self->_execute_command($res);
- }
- =head2 set_context
- Description:
- Firefox extension: Set browser's scope (chrome or content).
- Chrome is a privileged scope where you can access things like the
- Firefox UI itself. Content scope is where things like webpages live.
- Input:
- Required:
- <STRING> - context {CHROME|CONTENT}
- Usage:
- $firefox_driver->set_context( $context );
- Output:
- BOOLEAN - success or failure
- =cut
- sub set_context {
- my ( $self, $context ) = @_;
- if ( $self->_is_old_ff ) {
- return 0;
- }
- if ( not defined $context ) {
- croak "Expecting context";
- }
- if ( $context !~ m/chrome|content/i ) {
- croak "Expecting context value: chrome or content";
- }
- my $res = { 'command' => 'setContext' };
- return $self->_execute_command( $res, { context => $context } );
- }
- with 'Selenium::CanStartBinary';
- =attr custom_args
- Optional: specify any additional command line arguments you'd like
- invoked during the binary startup. See
- L<Selenium::CanStartBinary/custom_args> for more information.
- For Firefox 48 and newer, these arguments will be passed to
- geckodriver during start up.
- For Firefox 47 and older, these arguments will be passed to the
- Firefox browser during start up.
- =attr startup_timeout
- Optional: specify how long to wait for the binary to start itself and
- listen on its port. The default duration is arbitrarily 10 seconds. It
- accepts an integer number of seconds to wait: the following will wait
- up to 20 seconds:
- Selenium::Firefox->new( startup_timeout => 20 );
- See L<Selenium::CanStartBinary/startup_timeout> for more information.
- =method shutdown_binary
- Call this method instead of L<Selenium::Remote::Driver/quit> to ensure
- that the binary executable is also closed, instead of simply closing
- the browser itself. If the browser is still around, it will call
- C<quit> for you. After that, it will try to shutdown the browser
- binary by making a GET to /shutdown and on Windows, it will attempt to
- do a C<taskkill> on the binary CMD window.
- $self->shutdown_binary;
- It doesn't take any arguments, and it doesn't return anything.
- We do our best to call this when the C<$driver> option goes out of
- scope, but if that happens during global destruction, there's nothing
- we can do.
- =attr fixed_ports
- Optional: Throw instead of searching for additional ports; see
- L<Selenium::CanStartBinary/fixed_ports> for more info.
- =cut
- 1;
|