Firefo...ionette(3pm)

๐Ÿ“ฆ
version 1.63 ยท 3 versions
, in debian trixie

okay bestie, sit down, hydrate, because we're about to automate a whole browser ๐Ÿ’… This is the manpage for Firefox::Marionette (you'll also catch it filed under the name Firefo...ionette(3pm), which is just the header being extra and truncating itself). It's long, so this is part 1 of 15 โ€” buckle up, no sign-off, we're going all the way ๐Ÿซถ

โœจ NAME โœจ

Firefox::Marionette โ€” Automate the Firefox browser with the Marionette protocol ๐ŸŒธ


๐Ÿ’ซ VERSION ๐Ÿ’ซ

Version 1.63 โ€” that's the one, that's the vibe ๐ŸŽ€


๐Ÿฆ‹ SYNOPSIS ๐Ÿฆ‹

okay so here's literally the whole point of this module in nine-ish lines:

use Firefox::Marionette();
use v5.10;

my $firefox = Firefox::Marionette->new()->go('https://metacpan.org/');

say $firefox->find_tag('title')->property('innerHTML'); # same as $firefox->title();

say $firefox->html();

$firefox->find_class('page-content')->find_id('metacpan_search-input')->type('Test::More');

say "Height of page-content div is " . $firefox->find_class('page-content')->css('height');

my $file_handle = $firefox->selfie();

$firefox->await(sub { $firefox->find_class('autocomplete-suggestion'); })->click();

$firefox->find_partial('Download')->click();

likeโ€ฆ chainable browser control?? we're obsessed ๐Ÿ˜ญ


๐ŸŒˆ DESCRIPTION ๐ŸŒˆ

This is a client module to automate the Mozilla Firefox browser via the Marionette protocol โ€” the spec lives at https://developer.mozilla.org/en-US/docs/Mozilla/QA/Marionette/Protocol. That's the whole tea โ˜•


๐Ÿ‘‘ CONSTANTS ๐Ÿ‘‘

๐ŸŒŸ BCD_PATH

returns the local path used for storing the browser compatibility data for the agent method when the "stealth" parameter is supplied to the new method. This database is built by the build-bcd-for-firefox binary at https://metacpan.org/pod/build-bcd-for-firefox ๐Ÿ’พ


๐ŸŽ€ SUBROUTINES/METHODS ๐ŸŽ€

๐Ÿ’– accept_alert

accepts a currently displayed modal message box. click yes, basically ๐Ÿ’…

๐Ÿ’– accept_connections

Enables or disables accepting new socket connections. By calling this method with false the server will not accept any further connections, but existing connections will not be forcibly closed. Use true to re-enable accepting connections ๐Ÿ”Œ

Please note that when closing the connection via the client you can end up in a non-recoverable state if it hasn't been enabled before. that's the danger zone, bestie ๐Ÿ’€

๐Ÿ’– active_element

returns the active element of the current browsing context's document element, if the document element is non-null. no element, no answer, that's just how it is โœจ

๐Ÿ’– add_bookmark

accepts a bookmark as a parameter and adds the specified bookmark to the Firefox places database ๐Ÿ“Œ

use Firefox::Marionette();

my $bookmark = Firefox::Marionette::Bookmark->new(
                 url   => 'https://metacpan.org',
                 title => 'This is MetaCPAN!'
                         );
my $firefox = Firefox::Marionette->new()->add_bookmark($bookmark);

This method returns itself to aid in chaining methods. chain chain chain ๐Ÿ”—

๐Ÿ’– add_certificate

accepts a hash as a parameter and adds the specified certificate to the Firefox database with the supplied or default trust. Allowed keys are below;

This method returns itself to aid in chaining methods.

use Firefox::Marionette();

my $pem_encoded_string = <<'_PEM_';
-----BEGIN CERTIFICATE-----
MII..
-----END CERTIFICATE-----
_PEM_
my $firefox = Firefox::Marionette->new()->add_certificate(string => $pem_encoded_string);

accepts a single cookie object as the first parameter and adds it to the current cookie jar ๐Ÿช This method returns itself to aid in chaining methods.

This method throws an exception if you try to add a cookie for a different domain than the current document (https://developer.mozilla.org/en-US/docs/Web/WebDriver/Errors/InvalidCookieDomain). wrong domain? denied, periodt ๐Ÿšซ

๐Ÿ’– add_header

accepts a hash of HTTP headers to include in every future HTTP Request ๐Ÿ“จ

use Firefox::Marionette();
use UUID();

my $firefox = Firefox::Marionette->new();
my $uuid = UUID::uuid();
$firefox->add_header( 'Track-my-automated-tests' => $uuid );
$firefox->go('https://metacpan.org/');

these headers are added to any existing headers. To clear headers, see the delete_header method ๐Ÿ‘€

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new()->delete_header( 'Accept' )->add_header( 'Accept' => 'text/perl' )->go('https://metacpan.org/');

will only send out an Accept header (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept) that looks like "Accept: text/perl" ๐Ÿ’Œ

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new()->add_header( 'Accept' => 'text/perl' )->go('https://metacpan.org/');

by itself, will send out an Accept header (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept) that may resemble "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8,text/perl". This method returns itself to aid in chaining methods. (note the may, bestie โ€” it's giving "approximately" ๐Ÿ™ˆ)

๐Ÿ’– add_login

accepts a hash of the following keys;

  • host โ€” The scheme + hostname of the page where the login applies, for example 'https://www.example.org' ๐Ÿ 
  • user โ€” The username for the login. ๐Ÿ‘ค
  • password โ€” The password for the login. ๐Ÿ”‘
  • origin โ€” The scheme + hostname that the form-based login was submitted to (https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-action). Forms with no action attribute (https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-action) default to submitting to the URL of the page containing the login form, so that is stored here. This field should be omitted (it will be set to undef) for http auth type authentications and "" means to match against any form action. ๐Ÿ“ฎ
  • realm โ€” The HTTP Realm for which the login was requested. When an HTTP server sends a 401 result, the WWW-Authenticate header includes a realm. See RFC 2617 at https://datatracker.ietf.org/doc/html/rfc2617. If the realm is not specified, or it was blank, the hostname is used instead. For HTML form logins, this field should not be specified. ๐Ÿ›ก๏ธ
  • user_field โ€” The name attribute for the username input in a form. Non-form logins should not specify this field. โœ๏ธ
  • password_field โ€” The name attribute for the password input in a form. Non-form logins should not specify this field. ๐Ÿคซ

or a Firefox::Marionette::Login object as the first parameter and adds the login to the Firefox login database ๐Ÿ—„๏ธ

use Firefox::Marionette();
use UUID();

my $firefox = Firefox::Marionette->new();

# for http auth logins

my $http_auth_login = Firefox::Marionette::Login->new(host => 'https://pause.perl.org', user => 'AUSER', password => 'qwerty', realm => 'PAUSE');
$firefox->add_login($http_auth_login);
$firefox->go('https://pause.perl.org/pause/authenquery')->accept_alert(); # this goes to the page and submits the http auth popup

# for form based login

my $form_login = Firefox::Marionette::Login(host => 'https://github.com', user => 'me2@example.org', password => 'uiop[]', user_field => 'login', password_field => 'password');
$firefox->add_login($form_login);

# or just directly

$firefox->add_login(host => 'https://github.com', user => 'me2@example.org', password => 'uiop[]', user_field => 'login', password_field => 'password');
Note for HTTP Authentication, the realm (https://datatracker.ietf.org/doc/html/rfc2617#section-2) must perfectly match the correct realm (https://datatracker.ietf.org/doc/html/rfc2617#section-2) supplied by the server. like, character for character, no chill, no mercy ๐Ÿ˜ค

This method returns itself to aid in chaining methods.

๐Ÿ’– add_site_header

accepts a host name and a hash of HTTP headers to include in every future HTTP Request that is being sent to that particular host ๐ŸŒ

use Firefox::Marionette();
use UUID();

my $firefox = Firefox::Marionette->new();
my $uuid = UUID::uuid();
$firefox->add_site_header( 'metacpan.org', 'Track-my-automated-tests' => $uuid );
$firefox->go('https://metacpan.org/');

these headers are added to any existing headers going to the metacpan.org site, but no other site. To clear site headers, see the delete_site_header method ๐Ÿงน

๐Ÿ’– add_webauthn_authenticator

accepts a hash of the following keys;

It returns the newly created authenticator ๐ŸŽ‰

use Firefox::Marionette();
use Crypt::URandom();

my $user_name = MIME::Base64::encode_base64( Crypt::URandom::urandom( 10 ), q[] ) . q[@example.com];
my $firefox = Firefox::Marionette->new( webauthn => 0 );
my $authenticator = $firefox->add_webauthn_authenticator( transport => Firefox::Marionette::WebAuthn::Authenticator::INTERNAL(), protocol => Firefox::Marionette::WebAuthn::Authenticator::CTAP2() );
$firefox->go('https://webauthn.io');
$firefox->find_id('input-email')->type($user_name);
$firefox->find_id('register-button')->click();
$firefox->await(sub { sleep 1; $firefox->find_class('alert-success'); });
$firefox->find_id('login-button')->click();
$firefox->await(sub { sleep 1; $firefox->find_class('hero confetti'); });

๐Ÿ’– add_webauthn_credential

accepts a hash of the following keys; ๐Ÿซถ

(โ€ฆand that's exactly where part 1 runs out of page โ€” the keys are coming in the next part, no spoilers bestie, stay tuned ๐Ÿ’œ)

It returns the newly created credential. And like, obviously if the credential was just created it probably isn't much good by itself. But you can use it to recreate a credential, so long as you know all the parameters. iykyk.

use Firefox::Marionette();
use Crypt::URandom();

my $user_name = MIME::Base64::encode_base64( Crypt::URandom::urandom( 10 ), q[] ) . q[@example.com];
my $firefox = Firefox::Marionette->new();
$firefox->go('https://webauthn.io');
$firefox->find_id('input-email')->type($user_name);
$firefox->find_id('register-button')->click();
$firefox->await(sub { sleep 1; $firefox->find_class('alert-success'); });
$firefox->find_id('login-button')->click();
$firefox->await(sub { sleep 1; $firefox->find_class('hero confetti'); });
foreach my $credential ($firefox->webauthn_credentials()) {
    $firefox->delete_webauthn_credential($credential);

# ... time passes ...

    $firefox->add_webauthn_credential(
              id            => $credential->id(),
              host          => $credential->host(),
              user          => $credential->user(),
              private_key   => $credential->private_key(),
              is_resident   => $credential->is_resident(),
              sign_count    => $credential->sign_count(),
                          );
}
$firefox->go('about:blank');
$firefox->clear_cache(Firefox::Marionette::Cache::CLEAR_COOKIES());
$firefox->go('https://webauthn.io');
$firefox->find_id('input-email')->type($user_name);
$firefox->find_id('login-button')->click();
$firefox->await(sub { sleep 1; $firefox->find_class('hero confetti'); });

addons

returns whether pre-existing addons (extensions/themes) are allowed to run. For Firefox versions less than 55 this will be true, because -safe-mode (http://kb.mozillazine.org/Command_line_arguments#List_of_command_line_arguments_.28incomplete.29) cannot be automated. ๐Ÿ˜ญ

agent

accepts an optional value for the User-Agent header (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent) and sets it using the profile preferences, plus by inserting javascript into the current page. It returns the current value, something like 'Mozilla/5.0 (<system-information>) <platform> (<platform-details>) <extensions>'. This value gets retrieved with navigator.userAgent (https://developer.mozilla.org/en-US/docs/Web/API/Navigator/userAgent).

You can use this method to set a user agent string like so;

use Firefox::Marionette();
use strict;

# useragents.me should only be queried once a month or less.
# these UA strings should be cached locally.

my %user_agent_strings = map { $_->{ua} => $_->{pct} } @{$firefox->json("https://www.useragents.me/api")->{data}};
my ($user_agent) = reverse sort { $user_agent_strings{$a} <=> $user_agent_strings{$b} } keys %user_agent_strings;

my $firefox = Firefox::Marionette->new();
$firefox->agent($user_agent); # agent is now the most popular agent from useragents.me

If the user agent string you pass in looks like a Chrome (https://www.google.com/chrome/), Edge (https://microsoft.com/edge) or Safari (https://www.apple.com/safari/) user agent string, then this method will also try and change other profile preferences so they match the new agent string. Those parameters are;

  • general.appversion.override
  • general.oscpu.override
  • general.platform.override
  • network.http.accept
  • network.http.accept-encoding
  • network.http.accept-encoding.secure
  • privacy.donottrackheader.enabled

On top of that, this method will accept a hash of values as parameters too. When you hand it a hash, it alters specific parts of the normal Firefox User Agent. Those hash parameters are;

  • os โ€” the operating system you want, and the known values are "linux", "win32", "darwin", "freebsd", "netbsd", "openbsd" and "dragonfly" ๐Ÿ‰
  • version โ€” a specific version of firefox, such as 120.
  • arch โ€” a specific version of the architecture, such as "x86_64" or "aarch64" or "s390x".
  • increment โ€” a specific offset from the actual version of firefox, such as -5

You can use these parameters to set a user agent string like so;

use Firefox::Marionette();
use strict;

my $firefox = Firefox::Marionette->new();
$firefox->agent(os => 'freebsd', version => 118);

# user agent is now equal to
# Mozilla/5.0 (X11; FreeBSD amd64; rv:109.0) Gecko/20100101 Firefox/118.0

$firefox->agent(os => 'linux', arch => 's390x', version => 115);
# user agent is now equal to
# Mozilla/5.0 (X11; Linux s390x; rv:109.0) Gecko/20100101 Firefox/115.0

If the "stealth" parameter was supplied to the new method, it will also attempt to create known specific javascript functions to imitate the required browser. And if the database built by build-bcd-for-firefox (https://metacpan.org/pod/build-bcd-for-firefox) is accessible, then it will also attempt to delete/provide dummy implementations for the corresponding javascript attributes (https://github.com/mdn/browser-compat-data) for the desired browser. These websites have been super useful for testing these ideas;

Importantly, this will break feature detection (https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Feature_detection) for any website that relies on it. ๐Ÿ’€

See IMITATING OTHER BROWSERS for a discussion of these types of techniques. These changes are not foolproof, but honestly it's interesting to see what can be done with modern browsers. All this behaviour should be regarded as extremely experimental and subject to change. Feedback welcome, fr fr. ๐Ÿซถ

alert_text

Returns the message shown in a currently displayed modal message box ๐Ÿ‘‘

alive

This method returns true or false depending on whether the Firefox process is still running. Vibe check on your browser. ๐Ÿ’–

application_type

returns the application type for the Marionette protocol. Should be 'gecko'.

arch

returns the architecture of the machine running firefox. Should be something like 'x86_64' or 'arm'. This is only intended for test suite support, bestie.

aria_label

accepts an element as the parameter. It returns the ARIA label (https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-label) for the element. accessibility icon behavior โœจ

aria_role

accepts an element as the parameter. It returns the ARIA role (https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles) for the element.

async_script

accepts a scalar containing a javascript function that gets executed in the browser. This method returns itself so you can chain methods like a main character. ๐Ÿ‘‘

The executing javascript is subject to the script timeout, which by default is 30 seconds. Don't be late. โฐ

attribute

accepts an element as the first parameter and a scalar attribute name as the second parameter. It returns the initial value of the attribute with that name. This method returns the initial content from the HTML source code, whereas the property method returns the current content. Two different girls, two different jobs. ๐Ÿ’…

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new()->go('https://metacpan.org/');
my $element = $firefox->find_id('metacpan_search-input');
!defined $element->attribute('value') or die "attribute is defined but did not exist in the html source!";
$element->type('Test::More');
!defined $element->attribute('value') or die "attribute has changed but only the property should have changed!";

await

accepts a subroutine reference as a parameter and then executes the subroutine. If a not found exception gets thrown, this method will sleep for sleep_time_in_ms milliseconds and then execute the subroutine again. When the subroutine executes successfully, it returns whatever the subroutine returns. iconic retry energy. ๐Ÿ”

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new(sleep_time_in_ms => 5)->go('https://metacpan.org/');

$firefox->find_id('metacpan_search-input')->type('Test::More');

$firefox->await(sub { $firefox->find_class('autocomplete-suggestion'); })->click();

back ๐ŸŒธ

bestie this one is literally the undo button. it causes the browser to traverse one step backward in the joint history of the current browsing context. the browser will wait for the one step backward to complete or the session's page_load duration to elapse before returning โ€” and that's 5 minutes by default, no cap. this method returns itself to aid in chaining methods. ๐Ÿซถ


debug ๐Ÿ›

accepts a boolean and returns the current value of the debug setting. this allows the dynamic setting of debug. yes queen, you can flip it at runtime. ๐Ÿ’…


default_binary_name ๐Ÿ”ฅ

just returns the string 'firefox'. only of interest when sub-classing. that's it. that's the whole method. short king energy. ๐Ÿ‘‘


download ๐Ÿ“

accepts a URI and an optional timeout in seconds (the default is 5 minutes) as parameters and downloads the URI in the background and returns a handle to the downloaded file.

use Firefox::Marionette();
use v5.10;

my $firefox = Firefox::Marionette->new();

my $handle = $firefox->download('https://raw.githubusercontent.com/david-dick/firefox-marionette/master/t/data/keepassxs.csv');

foreach my $line (<$handle>) {
  print $line;
}

background downloading so you can keep vibing while it cooks. โœจ


bookmarks ๐ŸŽ€

accepts either a scalar or a hash as a parameter. the scalar may by the title of a bookmark or the URL of the bookmark. the hash may have the following keys;

  • title โ€” the title of the bookmark.
  • url โ€” the url of the bookmark.

returns a list of all Firefox::Marionette::Bookmark objects that match the supplied parameters (if any).

use Firefox::Marionette();
use v5.10;

my $firefox = Firefox::Marionette->new();

foreach my $bookmark ($firefox->bookmarks(title => 'This is MetaCPAN!')) {
  say "Bookmark found";
}

# OR

foreach my $bookmark ($firefox->bookmarks()) {
  say "Bookmark found with URL " . $bookmark->url();
}

# OR

foreach my $bookmark ($firefox->bookmarks('https://metacpan.org')) {
  say "Bookmark found";
}

three ways to find your faves, pick your fighter. ๐ŸŒˆ


browser_version ๐Ÿฆ‹

this method returns the current version of firefox. iconic information honestly.


bye ๐Ÿ’€

accepts a subroutine reference as a parameter and then executes the subroutine. if the subroutine executes successfully, this method will sleep for sleep_time_in_ms milliseconds and then execute the subroutine again. when a not found exception is thrown, this method will return itself to aid in chaining methods.

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new()->go('https://metacpan.org/');

$firefox->find_id('metacpan_search-input')->type('Test::More');

$firefox->await(sub { $firefox->find_class('autocomplete-suggestion'); })->click();

$firefox->bye(sub { $firefox->find_name('metacpan_search-input') })->await(sub { $firefox->interactive() && $firefox->find_partial('Download') })->click();

it's giving "keep trying until it's gone", and the naming is unhinged but we love her for it. ๐Ÿ™ˆ


cache_keys โญ

returns the set of all cache keys from Firefox::Marionette::Cache.

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new();
foreach my $key_name ($firefox->cache_keys()) {
  my $key_value = $firefox->check_cache_key($key_name);
  if (Firefox::Marionette::Cache->$key_name() != $key_value) {
    warn "This module this the value of $key_name is " . Firefox::Marionette::Cache->$key_name();
    warn "Firefox thinks the value of   $key_name is $key_value";
  }
}

capabilities ๐Ÿ‘‘

returns the capabilities of the current firefox binary. you can retrieve timeouts or a proxy with this method. lowkey the receipts of what your firefox can do.


certificate_as_pem ๐ŸŽ€

accepts a certificate stored in the Firefox database as a parameter and returns a PEM encoded X.509 certificate <https://datatracker.ietf.org/doc/html/rfc7468#section-5> as a string.

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new();

# Generating a ca-bundle.crt to STDOUT from the current firefox instance

foreach my $certificate (sort { $a->display_name() cmp $b->display_name } $firefox->certificates()) {
    if ($certificate->is_ca_cert()) {
        print '# ' . $certificate->display_name() . "\n" . $firefox->certificate_as_pem($certificate) . "\n";
    }
}

the ca-bundle-for-firefox <https://metacpan.org/pod/ca-bundle-for-firefox> command that is provided as part of this distribution does this. so you don't even have to write it yourself, bestie. ๐Ÿ’–


certificates ๐Ÿงš

returns a list of all known certificates in the Firefox database.

use Firefox::Marionette();
use v5.10;

# Sometimes firefox can neglect old certificates.  See https://bugzilla.mozilla.org/show_bug.cgi?id=1710716

my $firefox = Firefox::Marionette->new();
foreach my $certificate (grep { $_->is_ca_cert() && $_->not_valid_after() < time } $firefox->certificates()) {
  say "The " . $certificate->display_name() " . certificate has expired and should be removed";
  print 'PEM Encoded Certificate ' . "\n" . $firefox->certificate_as_pem($certificate) . "\n";
}

this method returns itself to aid in chaining methods.

yes that bugzilla link is literally in there, firefox really does neglect old certificates sometimes. not our fault. ๐Ÿซ 

check_cache_key ๐Ÿ”

accepts a cache_key as a parameter.

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new();
foreach my $key_name ($firefox->cache_keys()) {
  my $key_value = $firefox->check_cache_key($key_name);
  if (Firefox::Marionette::Cache->$key_name() != $key_value) {
    warn "This module this the value of $key_name is " . Firefox::Marionette::Cache->$key_name();
    warn "Firefox thinks the value of   $key_name is $key_value";
  }
}

this method returns the cache_key's actual value from firefox as a number. this may differ from the current value of the key from Firefox::Marionette::Cache as these values have changed as firefox has evolved. so if the numbers don't match, that's not a you problem, that's a firefox-grew-up problem. ๐Ÿ˜ญ


child_error ๐Ÿผ

this method returns the $? (CHILD_ERROR) for the Firefox process, or undefined if the process has not yet exited. undefined means "still alive, be patient bestie".


chrome ๐Ÿ’…

changes the scope of subsequent commands to chrome context. this allows things like interacting with firefox menu's and buttons outside of the browser window.

use Firefox::Marionette();
use v5.10;

my $firefox = Firefox::Marionette->new()->chrome();
$firefox->script(...); # running script in chrome context
$firefox->content();

see the context method for an alternative methods for changing the context. chrome context is the behind the scenes main character moment, iykyk. โœจ


chrome_window_handle ๐ŸชŸ

returns a server-assigned identifier for the current chrome window that uniquely identifies it within this Marionette instance. this can be used to switch to this window at a later point. this corresponds to a window that may itself contain tabs. this method is replaced by window_handle and appropriate context calls for Firefox 94 and after <https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/94#webdriver_conformance_marionette>.

deprecated-ish territory: for Firefox 94 and after, use window_handle plus the right context calls instead. ๐Ÿ’€

chrome_window_handles ๐ŸชŸ๐ŸชŸ

returns identifiers for each open chrome window for tests interested in managing a set of chrome windows and tabs separately. this method is replaced by window_handles and appropriate context calls for Firefox 94 and after <https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/94#webdriver_conformance_marionette>.

same deal, bestie โ€” Firefox 94 and after wants window_handles + context calls. ๐ŸŒธ

clear ๐Ÿงน

accepts a element as the first parameter and clears any user supplied input. bye bye, typed text.


clear_cache ๐Ÿ—‘๏ธ

accepts a single flag parameter, which can be an ORed set of keys from Firefox::Marionette::Cache and clears the appropriate sections of the cache. if no flags parameter is supplied, the default is CLEAR_ALL. note that this method, unlike delete_cookies will actually delete all cookies for all hosts, not just the current webpage.

use Firefox::Marionette();
use Firefox::Marionette::Cache qw(:all);

my $firefox = Firefox::Marionette->new()->go('https://do.lots.of.evil/')->clear_cache(); # default clear all

$firefox->go('https://cookies.r.us')->clear_cache(CLEAR_COOKIES());

this method returns itself to aid in chaining methods.

ok so real talk: the difference between this and delete_cookies is not subtle. this one nukes every cookie for every host. if you wanted the polite, current-webpage-only version, that's delete_cookies. periodt. ๐Ÿ”ฅ

clear_pref ๐ŸŒฑ

accepts a preference <http://kb.mozillazine.org/About:config> name and restores it to the original value. see the get_pref and set_pref methods to get a preference value and to set to it to a particular value. this method returns itself to aid in chaining methods.

use Firefox::Marionette();
my $firefox = Firefox::Marionette->new();

$firefox->clear_pref('browser.search.defaultenginename');

factory reset for one single pref, obsessed. ๐ŸŽ€


click ๐Ÿ‘†

accepts a element as the first parameter and sends a 'click' to it. the browser will wait for any page load to complete or the session's page_load duration to elapse before returning, which, by default is 5 minutes. the click method is also used to choose an option in a select dropdown.

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new(visible => 1)->go('https://ebay.com');
my $select = $firefox->find_tag('select');
foreach my $option ($select->find_tag('option')) {
    if ($option->property('value') == 58058) { # Computers/Tablets & Networking
        $option->click();
    }
}

yes bestie, dropdowns count. one method, two jobs, ate. ๐Ÿ’…


close_current_chrome_window_handle ๐Ÿšช

closes the current chrome window (that is the entire window, not just the tabs). it returns a list of still available chrome window handles. you will need to switch_to_window to use another window.

heads up: that's the whole window going bye bye, not one little tab. don't come for me when your tabs are gone. ๐Ÿ˜ญ

close_current_window_handle ๐Ÿšชโœจ

closes the current window/tab. it returns a list of still available window/tab handles. the gentler sibling of the method above.


content ๐ŸŒˆ

changes the scope of subsequent commands to browsing context. this is the default for when firefox starts and restricts commands to operating in the browser window only.

use Firefox::Marionette();
use v5.10;

my $firefox = Firefox::Marionette->new()->chrome();
$firefox->script(...); # running script in chrome context
$firefox->content();

see the context method for an alternative methods for changing the context. content context = normal browser window hours, the default vibe. ๐Ÿฆ‹


context ๐ŸŽญ

accepts a string as the first parameter, which may be either 'content' or 'chrome'. it returns the context type that is Marionette's current target for browsing context scoped commands.

use Firefox::Marionette();
use v5.10;

my $firefox = Firefox::Marionette->new();
if ($firefox->context() eq 'content') {
   say "I knew that was going to happen";
}
my $old_context = $firefox->context('chrome');
$firefox->script(...); # running script in chrome context
$firefox->context($old_context);

see the content and chrome methods for alternative methods for changing the context. save your old context and restore it after, that's just good manners fr fr. ๐Ÿ’œ


cookies ๐Ÿช

returns the contents of the cookie jar in scalar or list context.

use Firefox::Marionette();
use v5.10;
and that's where the original page cuts off for this part, bestie โ€” the cookies example keeps going in the next bit. don't worry, we're not done. ๐Ÿซถ
my $firefox = Firefox::Marionette->new()->go('https://github.com');
foreach my $cookie ($firefox->cookies()) {
    if (defined $cookie->same_site()) {
        say "Cookie " . $cookie->name() . " has a SameSite of " . $cookie->same_site();
    } else {
        warn "Cookie " . $cookie->name() . " does not have the SameSite attribute defined";
    }
}

like, that's the whole vibe โ€” some cookies have a SameSite and some are just out here living their truth with no attribute at all. ๐Ÿ’…


css ๐ŸŽ€

takes an element as the first parameter and a scalar CSS property name as the second parameter. it hands you back the value of the computed style for that property. no cap.

use Firefox::Marionette();
use v5.10;

my $firefox = Firefox::Marionette->new()->go('https://metacpan.org/');
say $firefox->find_id('metacpan_search-input')->css('height');

current_chrome_window_handle ๐Ÿฆ‹

go look at chrome_window_handle. bestie, it's the same thing. โœจ

delete_bookmark ๐Ÿ”ฅ

accepts a bookmark as a parameter and deletes that bookmark from the Firefox database. yeet. ๐Ÿ—‘๏ธ

use Firefox::Marionette();
use v5.10;

my $firefox = Firefox::Marionette->new();
foreach my $bookmark (reverse $firefox->bookmarks()) {
  if ($bookmark->parent_guid() ne Firefox::Marionette::Bookmark::ROOT()) {
    $firefox->delete_bookmark($bookmark);
  }
}
say "Bookmarks? We don't need no stinking bookmarks!";

this method returns itself to aid in chaining methods. ๐Ÿซถ

delete_certificate ๐Ÿ’€

accepts a certificate stored in the Firefox database as a parameter and deletes/distrusts the certificate from the Firefox database. it's giving "who even are you" energy, but to a CA. ๐Ÿ˜ญ

use Firefox::Marionette();
use v5.10;

my $firefox = Firefox::Marionette->new();
foreach my $certificate ($firefox->certificates()) {
    if ($certificate->is_ca_cert()) {
        $firefox->delete_certificate($certificate);
    } else {
        say "This " . $certificate->display_name() " certificate is NOT a certificate authority, therefore it is not being deleted";
    }
}
say "Good luck visiting a HTTPS website!";

this method returns itself to aid in chaining methods. ๐Ÿซถ

deletes a single cookie by name. accepts a scalar containing the cookie name as a parameter. this method returns itself to aid in chaining methods. bye girl, we barely knew you. ๐Ÿ‘‹

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new()->go('https://github.com');
foreach my $cookie ($firefox->cookies()) {
    warn "Cookie " . $cookie->name() . " is being deleted";
    $firefox->delete_cookie($cookie->name());
}
foreach my $cookie ($firefox->cookies()) {
    die "Should be no cookies here now";
}

delete_cookies ๐Ÿ‘‘

here be cookie monsters! ๐Ÿช๐Ÿ‘พ note that this method will only delete cookies for the current site. see clear_cache for an alternative. this method returns itself to aid in chaining methods.

delete_header โœจ

accepts a list of HTTP header names to delete from future HTTP Requests.

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new();
$firefox->delete_header( 'User-Agent', 'Accept', 'Accept-Encoding' );

will remove the User-Agent (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent), Accept (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept) and Accept-Encoding (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding) headers from all future requests. ๐Ÿ‘‹โœจ

this method returns itself to aid in chaining methods. ๐Ÿซถ

delete_login ๐ŸŒธ

accepts a login as a parameter.

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new();
foreach my $login ($firefox->logins()) {
    if ($login->user() eq 'me@example.org') {
        $firefox->delete_login($login);
    }
}

will remove the logins with the username matching 'me@example.org'. super targeted, super slay. ๐ŸŽฏ

this method returns itself to aid in chaining methods. ๐Ÿซถ

delete_logins ๐Ÿงš

this method empties the password database. like, the whole thing. it's gone. ๐Ÿ’€

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new();
$firefox->delete_logins();

this method returns itself to aid in chaining methods. ๐Ÿซถ

delete_session ๐ŸŒˆ

deletes the current WebDriver session. periodt. โœ‚๏ธ

delete_site_header ๐Ÿฆ‹

accepts a host name and a list of HTTP headers names to delete from future HTTP Requests.

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new();
$firefox->delete_header( 'metacpan.org', 'User-Agent', 'Accept', 'Accept-Encoding' );

will remove the User-Agent (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent), Accept (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept) and Accept-Encoding (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding) headers from all future requests to metacpan.org. very "we don't do that here, specifically on this one site" of you. ๐Ÿ’…

this method returns itself to aid in chaining methods. ๐Ÿซถ

delete_webauthn_all_credentials ๐Ÿ”

this method accepts an optional authenticator, in which case it will delete all credentials from this authenticator. if no parameter is supplied, the default authenticator will have all credentials deleted.

my $firefox = Firefox::Marionette->new();
my $authenticator = $firefox->add_webauthn_authenticator( transport => Firefox::Marionette::WebAuthn::Authenticator::INTERNAL(), protocol => Firefox::Marionette::WebAuthn::Authenticator::CTAP2() );
$firefox->delete_webauthn_all_credentials($authenticator);
$firefox->delete_webauthn_all_credentials();

delete_webauthn_authenticator ๐ŸŽ€

this method accepts an optional authenticator, in which case it will delete this authenticator from the current Firefox instance. if no parameter is supplied, the default authenticator will be deleted.

my $firefox = Firefox::Marionette->new();
my $authenticator = $firefox->add_webauthn_authenticator( transport => Firefox::Marionette::WebAuthn::Authenticator::INTERNAL(), protocol => Firefox::Marionette::WebAuthn::Authenticator::CTAP2() );
$firefox->delete_webauthn_authenticator($authenticator);
$firefox->delete_webauthn_authenticator();

delete_webauthn_credential โญ

this method accepts either a credential and an authenticator, in which case it will remove the credential from the supplied authenticator or

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new();
my $authenticator = $firefox->add_webauthn_authenticator( transport => Firefox::Marionette::WebAuthn::Authenticator::INTERNAL(), protocol => Firefox::Marionette::WebAuthn::Authenticator::CTAP2() );
foreach my $credential ($firefox->webauthn_credentials($authenticator)) {
    $firefox->delete_webauthn_credential($credential, $authenticator);
}

just a credential, in which case it will remove the credential from the default authenticator.

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new();
...
foreach my $credential ($firefox->webauthn_credentials()) {
    $firefox->delete_webauthn_credential($credential);
}

this method returns itself to aid in chaining methods. ๐Ÿซถ

developer ๐Ÿ’œ

returns true if the current version of firefox is a developer edition (https://www.mozilla.org/en-US/firefox/developer/) (does the minor version number end with an 'b\d+'?) version. iykyk. ๐Ÿ”

dismiss_alert ๐Ÿ™ˆ

dismisses a currently displayed modal message box. not today, popup. ๐Ÿšซ

displays ๐Ÿ–ฅ๏ธโœจ

accepts an optional regex to filter against the usage for the display and returns a list of all the known displays (https://en.wikipedia.org/wiki/List_of_common_resolutions) as a Firefox::Marionette::Display. it's giving resolution wikipedia rabbit hole, and we're obsessed. ๐ŸŒ€

use Firefox::Marionette();
use Encode();
use v5.10;

my $firefox = Firefox::Marionette->new( visible => 1, kiosk => 1 )->go('http://metacpan.org');;
my $element = $firefox->find_id('metacpan_search-input');
foreach my $display ($firefox->displays(qr/iphone/smxi)) {
    say 'Can Firefox resize for "' . Encode::encode('UTF-8', $display->usage(), 1) . '"?';
    if ($firefox->resize($display->width(), $display->height())) {
        say 'Now displaying with a Pixel aspect ratio of ' . $display->par();
        say 'Now displaying with a Storage aspect ratio of ' . $display->sar();
        say 'Now displaying with a Display aspect ratio of ' . $display->dar();
    } else {
        say 'Apparently NOT!';
    }
}

downloaded ๐Ÿ“ฅ๐Ÿ’–

accepts a filesystem path and returns a matching filehandle. this is trivial for locally running firefox, but sufficiently complex to justify the method for a remote firefox running over ssh. like, ssh said "not me doing the easy version". ๐Ÿ˜ญ

use Firefox::Marionette();
use v5.10;

my $firefox = Firefox::Marionette->new( host => '10.1.2.3' )->go('https://metacpan.org/');

$firefox->find_class('page-content')->find_id('metacpan_search-input')->type('Test::More');

$firefox->await(sub { $firefox->find_class('autocomplete-suggestion'); })->click();

$firefox->find_partial('Download')->click();

while(!$firefox->downloads()) { sleep 1 }

foreach my $path ($firefox->downloads()) {

    my $handle = $firefox->downloaded($path);

    # do something with downloaded file handle

}

downloading โณ๐Ÿฆ‹

returns true if any files in downloads end in ".part". yes bestie, the .part is the "still cooking" flag. ๐Ÿณ

use Firefox::Marionette();
use v5.10;

my $firefox = Firefox::Marionette->new()->go('https://metacpan.org/');

$firefox->find_class('page-content')->find_id('metacpan_search-input')->type('Test::More');

$firefox->await(sub { $firefox->find_class('autocomplete-suggestion'); })->click();

$firefox->find_partial('Download')->click();

while(!$firefox->downloads()) { sleep 1 }

while($firefox->downloading()) { sleep 1 }

foreach my $path ($firefox->downloads()) {
    say $path;
}

downloads ๐Ÿ“‚๐ŸŒˆ

returns a list of file paths (including partial downloads) of downloads during this Firefox session.

use Firefox::Marionette();
use v5.10;

my $firefox = Firefox::Marionette->new()->go('https://metacpan.org/');

$firefox->find_class('page-content')->find_id('metacpan_search-input')->type('Test::More');

$firefox->await(sub { $firefox->find_class('autocomplete-suggestion'); })->click();

$firefox->find_partial('Download')->click();

while(!$firefox->downloads()) { sleep 1 }

foreach my $path ($firefox->downloads()) {
    say $path;
}

error_message ๐Ÿ˜ญ๐Ÿ’€

this method returns a human readable error message describing how the Firefox process exited (assuming it started okay). on Win32 platforms this information is restricted to exit code. so windows girlies get the short version. ๐ŸชŸ

execute โšก๐Ÿ‘‘

this utility method executes a command with arguments and returns STDOUT as a chomped string. it is a simple method only intended for the Firefox::Marionette::* modules. don't go using it for your whole life, it's not that kind of method. ๐Ÿ’…

๐ŸŽ€ fill_login

okay so this one goes snooping through the Password Manager (https://support.mozilla.org/en-US/kb/password-manager-remember-delete-edit-logins) looking for a login that fits any form on the current page. and bestie, the match has to be exact: the form must match the host, the action attribute, AND the user and password field names. all of it. no vibes-based matching here ๐Ÿ’…

use Firefox::Marionette();
use IO::Prompt();

my $firefox = Firefox::Marionette->new();

my $firefox = Firefox::Marionette->new();

my $url = 'https://github.com';

my $user = 'me@example.org';

my $password = IO::Prompt::prompt(-echo => q[*], "Please enter the password for the $user account when logging into $url:");

$firefox->add_login(host => $url, user => $user, password => 'qwerty', user_field => 'login', password_field => 'password');

$firefox->go("$url/login");

$firefox->fill_login();

๐Ÿ” find

takes an xpath expression (https://en.wikipedia.org/wiki/XPath) as the first parameter and hands you back the first element that matches it. slay.

this method is subject to the implicit timeout, which by default is 0 seconds. lowkey important, don't forget it.

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new()->go('https://metacpan.org/');

$firefox->find('//input[@id="metacpan_search-input"]')->type('Test::More');

# OR in list context

foreach my $element ($firefox->find('//input[@id="metacpan_search-input"]')) {
    $element->type('Test::More');
}
if no elements are found, a not found exception gets thrown ๐Ÿ˜ญ. want the version that just returns undef instead of making a whole scene? see the has method.

๐Ÿ†” find_id

takes an id (https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/id) as the first parameter and returns the first element with a matching 'id' property.

this method is subject to the implicit timeout, which by default is 0 seconds.

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new()->go('https://metacpan.org/');

$firefox->find_id('metacpan_search-input')->type('Test::More');

# OR in list context

foreach my $element ($firefox->find_id('metacpan_search-input')) {
    $element->type('Test::More');
}
no elements? not found exception, thrown, periodt. for the undef-instead-of-drama behaviour, see the has_id method.

๐Ÿ“› find_name

this one returns the first element with a matching 'name' property. cute and simple fr fr.

this method is subject to the implicit timeout, which by default is 0 seconds.

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new()->go('https://metacpan.org/');
$firefox->find_name('q')->type('Test::More');

# OR in list context

foreach my $element ($firefox->find_name('q')) {
    $element->type('Test::More');
}
nothing matched โ†’ not found exception ๐Ÿ’€. the undef-returning twin is the has_name method.

๐ŸŽจ find_class

accepts a class name (https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/class) as the first parameter and returns the first element with a matching 'class' property.

this method is subject to the implicit timeout, which by default is 0 seconds.

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new()->go('https://metacpan.org/');
$firefox->find_class('form-control home-metacpan_search-input')->type('Test::More');

# OR in list context

foreach my $element ($firefox->find_class('form-control home-metacpan_search-input')) {
    $element->type('Test::More');
}
if no elements are found, a not found exception will be thrown. for the same functionality that returns undef if no elements are found, see the has_class method.

๐ŸŒˆ find_selector

accepts a CSS Selector (https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) as the first parameter and returns the first element that matches that selector. it's giving CSS girlie energy โœจ

this method is subject to the implicit timeout, which by default is 0 seconds.

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new()->go('https://metacpan.org/');
$firefox->find_selector('input.home-metacpan_search-input')->type('Test::More');

# OR in list context

foreach my $element ($firefox->find_selector('input.home-metacpan_search-input')) {
    $element->type('Test::More');
}
no matches โ†’ not found exception, bestie ๐Ÿ˜ญ. want undef instead? that's the has_selector method.

๐Ÿท๏ธ find_tag

accepts a tag name (https://developer.mozilla.org/en-US/docs/Web/API/Element/tagName) as the first parameter and returns the first element with this tag name.

this method is subject to the implicit timeout, which by default is 0 seconds.

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new()->go('https://metacpan.org/');
my $element = $firefox->find_tag('input');

# OR in list context

foreach my $element ($firefox->find_tag('input')) {
    # do something
}
if no elements are found, a not found exception will be thrown. for the same functionality that returns undef if no elements are found, see the has_tag method.

accepts a text string as the first parameter and returns the first link element that has a matching link text.

this method is subject to the implicit timeout, which by default is 0 seconds.

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new()->go('https://metacpan.org/');
$firefox->find_link('API')->click();

# OR in list context

foreach my $element ($firefox->find_link('API')) {
    $element->click();
}
if no elements are found, a not found exception will be thrown. for the same functionality that returns undef if no elements are found, see the has_link method.

๐Ÿ”Ž find_partial

accepts a text string as the first parameter and returns the first link element that has a partially matching link text. yes, partial. it ate. ๐Ÿ‘‘

this method is subject to the implicit timeout, which by default is 0 seconds.

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new()->go('https://metacpan.org/');
$firefox->find_partial('AP')->click();

# OR in list context

foreach my $element ($firefox->find_partial('AP')) {
    $element->click();
}
if no elements are found, a not found exception will be thrown. for the same functionality that returns undef if no elements are found, see the has_partial method.

โญ๏ธ forward

makes the browser go one step forward in the joint history of the current browsing context. it'll wait for that one step forward to finish, or until the session's page_load duration elapses before returning โ€” which, by default, is 5 minutes. this method returns itself so you can keep chaining methods ๐Ÿซถ

๐Ÿ–ฅ๏ธ full_screen

full screens the firefox window. that's it, that's the whole method. this method returns itself to aid in chaining methods ๐Ÿ’…

๐Ÿ“ geo

accepts an optional geo location object, or the parameters for a geo location object, turns on the Geolocation API (https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API), and returns the current value returned by calling the javascript getCurrentPosition (https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition) method. this one gets its own deep dive in the GEO LOCATION section. if the current location can't be figured out, this method returns undef.

NOTE: firefox will only allow Geolocation (https://developer.mozilla.org/en-US/docs/Web/API/Geolocation) calls to be made from secure contexts (https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts) and, bizarrely, this does not include about:blank or similar. therefore, you will need to load a page before calling the geo method. no page, no location, bestie ๐ŸŒธ
use Firefox::Marionette();

my $firefox = Firefox::Marionette->new( proxy => 'https://this.is.another.location:3128', geo => 1 );

# Get geolocation for this.is.another.location (via proxy)

$firefox->geo($firefox->json('https://freeipapi.com/api/json/'));

# now google maps will show us in this.is.another.location

$firefox->go('https://maps.google.com/');

if (my $geo = $firefox->geo()) {
    warn "Apparently, we're now at " . join q[, ], $geo->latitude(), $geo->longitude();
} else {
    warn "This computer is not allowing geolocation";
}

# OR the quicker setup (run this with perl -C)

warn "Apparently, we're now at " . Firefox::Marionette->new( proxy => 'https://this.is.another.location:3128', geo => 'https://freeipapi.com/api/json/' )->go('https://maps.google.com/')->geo();
NOTE: currently this call sets the location to be exactly what is specified. it will also attempt to modify the current timezone (if available in the geo location parameter) to match the specified timezone. this function should be considered experimental. feedback welcome ๐Ÿซถ

in particular, the ipgeolocation API (https://ipgeolocation.io/documentation/ip-geolocation-api.html) is the only API that currently provides geolocation data and matching timezone data in one API call. if anyone finds/develops another similar API, the author would be delighted to include support for it in this module. no cap ๐Ÿ’–

๐Ÿš€ go

navigates the current browsing context to the given URI and waits for the document to load, or the session's page_load duration to elapse, before returning โ€” which, by default, is 5 minutes.

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new();
$firefox->go('https://metacpan.org/'); # will only return when metacpan.org is FULLY loaded (including all images / js / css)

to make the go method return quicker, you need to set the page load strategy capability to an appropriate value, such as below;

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new( capabilities => Firefox::Marionette::Capabilities->new( page_load_strategy => 'eager' ));
$firefox->go('https://metacpan.org/'); # will return once the main document has been loaded and parsed, but BEFORE sub-resources (images/stylesheets/frames) have been loaded.

when going directly to a URL that needs to be downloaded, please see BUGS AND LIMITATIONS for a necessary workaround, and the download method for an alternative. โœจ

this method returns itself to aid in chaining methods.

โš™๏ธ get_pref

accepts a preference (http://kb.mozillazine.org/About:config) name. see the set_pref and clear_pref methods to set a preference value and to restore it to it's original value. this method returns the current value of the preference ๐Ÿ‘‘

so like, here's get_pref doing its thing irl:

use Firefox::Marionette();
my $firefox = Firefox::Marionette->new();

warn "Your browser's default search engine is set to " . $firefox->get_pref('browser.search.defaultenginename');

har ๐Ÿ•ธ๏ธโœจ

bestie. this one hands you back a hashref that is the http archive (https://en.wikipedia.org/wiki/HAR_(file_format)) of your whole session. it's giving โœจreceiptsโœจ.

โฐ heads up: this method answers to the script timeout, which by default is 30 seconds. and fr fr, it can also just hang (all the way until that script timeout) if the original devtools window got closed. not a vibe, but that's the deal.

the hashref is built to be swallowed whole by the Archive::Har module. ๐Ÿซถ

use Firefox::Marionette();
use Archive::Har();
use v5.10;

my $firefox = Firefox::Marionette->new(visible => 1, debug => 1, har => 1);

$firefox->go("http://metacpan.org/");

$firefox->find('//input[@id="metacpan_search-input"]')->type('Test::More');
$firefox->await(sub { $firefox->find_class('autocomplete-suggestion'); })->click();

my $har = Archive::Har->new();
$har->hashref($firefox->har());

foreach my $entry ($har->entries()) {
    say $entry->request()->url() . " spent " . $entry->timings()->connect() . " ms establishing a TCP connection";
}

has ๐Ÿ”๐Ÿ’–

takes an xpath expression (https://en.wikipedia.org/wiki/XPath) as the first parameter and gives you back the first element that matches it. no cap.

this method is subject to the implicit timeout, which by default is 0 seconds.

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new()->go('https://metacpan.org/');

if (my $element = $firefox->has('//input[@id="metacpan_search-input"]')) {
    $element->type('Test::More');
}

if nothing turns up, this method returns undef. ๐Ÿ™ˆ if you want the same energy but with a not found exception thrown at you instead, go peep the find method.

has_id ๐Ÿ†”โœจ

takes an id (https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/id) as the first parameter and returns the first element whose 'id' property matches. slay.

this method is subject to the implicit timeout, which by default is 0 seconds.

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new()->go('https://metacpan.org/');

if (my $element = $firefox->has_id('metacpan_search-input')) {
    $element->type('Test::More');
}

no elements found? you get undef. if you'd rather have a not found exception thrown, that's the find_id method's whole job.

has_name ๐Ÿท๏ธ๐ŸŒธ

this method returns the first element with a matching 'name' property.

this method is subject to the implicit timeout, which by default is 0 seconds.

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new()->go('https://metacpan.org/');
if (my $element = $firefox->has_name('q')) {
    $element->type('Test::More');
}

nothing matched? undef. want the exception-throwing twin instead? that's find_name, bestie.

has_class ๐ŸŽ€๐Ÿ’…

takes a class name (https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/class) as the first parameter and returns the first element with a matching 'class' property.

this method is subject to the implicit timeout, which by default is 0 seconds.

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new()->go('https://metacpan.org/');
if (my $element = $firefox->has_class('form-control home-metacpan_search-input')) {
    $element->type('Test::More');
}

zero matches = undef. for the same thing but with a not found exception, see find_class.

has_selector ๐Ÿงšโœจ

takes a CSS Selector (https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) as the first parameter and returns the first element that matches that selector. iconic behavior.

this method is subject to the implicit timeout, which by default is 0 seconds.

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new()->go('https://metacpan.org/');
if (my $element = $firefox->has_selector('input.home-metacpan_search-input')) {
    $element->type('Test::More');
}

no elements found โ†’ undef. the not-found-exception version is find_selector.

has_tag ๐Ÿท๏ธ๐Ÿ”ฅ

takes a tag name (https://developer.mozilla.org/en-US/docs/Web/API/Element/tagName) as the first parameter and returns the first element with that tag name.

this method is subject to the implicit timeout, which by default is 0 seconds.

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new()->go('https://metacpan.org/');
if (my $element = $firefox->has_tag('input')) {
    # do something
}

if no elements are found, this method will return undef. for the same functionality that throws a not found exception, see the find_tag method.

takes a text string as the first parameter and returns the first link element with a matching link text.

this method is subject to the implicit timeout, which by default is 0 seconds.

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new()->go('https://metacpan.org/');
if (my $element = $firefox->has_link('API')) {
    $element->click();
}

if no elements are found, this method will return undef. for the same functionality that throws a not found exception, see the find_link method.

has_partial ๐Ÿ“๐Ÿ”Ž

takes a text string as the first parameter and returns the first link element whose link text partially matches. yes, partial โ€” we love a flexible queen.

this method is subject to the implicit timeout, which by default is 0 seconds.

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new()->go('https://metacpan.org/');
if (my $element = $firefox->find_partial('AP')) {
    $element->click();
}

if no elements are found, this method will return undef. for the same functionality that throws a not found exception, see the find_partial method.

html ๐Ÿ“„๐ŸŒˆ

returns the page source of the content document. lowkey important: this page source can be wrapped in html that firefox provides. so if that's not what you want, go look at the json method for an alternative when you're dealing with response content types like application/json, and strip for an alternative when you're dealing with other non-html content types like text/plain.

use Firefox::Marionette();
use v5.10;

say Firefox::Marionette->new()->go('https://metacpan.org/')->html();

import_bookmarks ๐Ÿ”–๐Ÿ‘‘

takes a filesystem path to a bookmarks file as its parameter and imports all the bookmarks in that file. and it's not picky โ€” it can deal with backups from Firefox (https://support.mozilla.org/en-US/kb/export-firefox-bookmarks-to-backup-or-transfer), Chrome (https://support.google.com/chrome/answer/96816?hl=en) or Edge. versatile icon ๐Ÿ’…

use Firefox::Marionette();
use v5.10;

my $firefox = Firefox::Marionette->new()->import_bookmarks('/path/to/bookmarks_file.html');

this method returns itself so you can keep chaining methods like the main character you are.

images ๐Ÿ–ผ๏ธโœจ

returns a list of all of the following elements;

as Firefox::Marionette::Image objects. cute, right?

this method is subject to the implicit timeout, which by default is 0 seconds.

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new()->go('https://metacpan.org/');
if (my $link = $firefox->images()) {
    say "Found a image with width " . $image->width() . "px and height " . $image->height() . "px from " . $image->URL();
}

if no elements are found, this method will return undef.

install ๐Ÿงฉ๐Ÿ”ฅ

takes the following as the first parameter;

and an optional true/false second parameter to say whether the xpi file should be a temporary extension (https://extensionworkshop.com/documentation/develop/temporary-installation-in-firefox/) โ€” meaning it only lives for the existence of this browser instance.

โš ๏ธ real talk: unsigned xpi files may only be loaded temporarily (https://wiki.mozilla.org/Add-ons/Extension_Signing), except for nightly firefox installations (https://www.mozilla.org/en-US/firefox/channel/desktop/#nightly). read that twice, bestie.

it returns the GUID for the addon, which you can then use as a parameter to the uninstall method. ๐Ÿ‘‘

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new();

my $extension_id = $firefox->install('/full/path/to/gnu_terry_pratchett-0.4-an+fx.xpi');

# OR downloading and installing source code

system { 'git' } 'git', 'clone', 'https://github.com/kkapsner/CanvasBlocker.git';

if ($firefox->nightly()) {

    $extension_id = $firefox->install('./CanvasBlocker'); # permanent install for unsigned packages in nightly firefox

} else {

    $extension_id = $firefox->install('./CanvasBlocker', 1); # temp install for normal firefox

}

interactive โณ๐Ÿฆ‹

returns true if "document.readyState === "interactive"" or if loaded is true. that's the whole vibe check.

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new()->go('https://metacpan.org/');
$firefox->find_id('metacpan_search-input')->type('Type::More');
$firefox->await(sub { $firefox->find_class('autocomplete-suggestion'); })->click();
while(!$firefox->interactive()) {
    # redirecting to Test::More page
}

is_displayed ๐Ÿ‘€โœจ

takes an element as the first parameter. this method returns true or false depending on if the element is displayed (https://firefox-source-docs.mozilla.org/testing/marionette/internals/interaction.html#interaction.isElementDisplayed).

is_enabled โœ…๐Ÿ’–

takes an element as the first parameter. this method returns true or false depending on if the element is enabled (https://w3c.github.io/webdriver/#is-element-enabled).

๐ŸŽ€ is_selected

hands you an element as the first param, and it's like... is this thing selected or not? returns true or false, no in-between, based on the WebDriver definition of is element selected <https://w3c.github.io/webdriver/#dfn-is-element-selected>.

heads up bestie: this one only makes sense for checkbox <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox> or radio <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/radio> inputs, or option <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option> elements living inside a select <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select> dropdown. anything else and it's giving confused energy.

๐Ÿ” is_trusted

takes a certificate as the first param, returns true or false depending on whether that certificate is a trusted CA certificate in the current profile. so iconic.

use Firefox::Marionette();
use v5.10;

my $firefox = Firefox::Marionette->new( profile_name => 'default' );
foreach my $certificate ($firefox->certificates()) {
    if (($certificate->is_ca_cert()) && ($firefox->is_trusted($certificate))) {
        say $certificate->display_name() . " is a trusted CA cert in the current profile";
    }
}

๐ŸŒธ json

returns a JSON object parsed straight from the page source of the content document. it's a lil convenience wrapper around the strip method, lowkey doing the most for you ๐Ÿ’…

use Firefox::Marionette();
use v5.10;

say Firefox::Marionette->new()->go('https://fastapi.metacpan.org/v1/download_url/Firefox::Marionette")->json()->{version};

and wait, there's more โ€” this method can also take a URI as a param, go grab that URI via the firefox fetch call <https://developer.mozilla.org/en-US/docs/Web/API/fetch>, and transform the body into JSON via firefox <https://developer.mozilla.org/en-US/docs/Web/API/Response/json_static>. no cap.

use Firefox::Marionette();
use v5.10;

say Firefox::Marionette->new()->json('https://freeipapi.com/api/json/')->{ipAddress};

โŒจ๏ธ key_down

takes a param describing a key and returns an action you can use in the perform method that corresponds to that key being pressed down. keyboard queen behaviour.

use Firefox::Marionette();
use Firefox::Marionette::Keys qw(:all);

my $firefox = Firefox::Marionette->new();

$firefox->chrome()->perform(
                             $firefox->key_down(CONTROL()),
                             $firefox->key_down('l'),
                           )->release()->content();

โŒจ๏ธ key_up

takes a param describing a key and returns an action you can use in the perform method that corresponds to that key being released. matching set with key_down, obvi โœจ

use Firefox::Marionette();
use Firefox::Marionette::Keys qw(:all);

my $firefox = Firefox::Marionette->new();

$firefox->chrome()->perform(
                             $firefox->key_down(CONTROL()),
                             $firefox->key_down('l'),
                             $firefox->pause(20),
                             $firefox->key_up('l'),
                             $firefox->key_up(CONTROL())
                           )->content();

๐Ÿ—ฃ๏ธ languages

takes an optional list of values for the Accept-Language <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language> header and sets them using the profile preferences. then it hands you back the current values as a list, like ('en-US', 'en'). multilingual main character energy fr fr ๐ŸŒ


โœ… loaded

returns true if "document.readyState === "complete"" โ€” that's the vibe check for "is the page done yet".

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new()->go('https://metacpan.org/');
$firefox->find_id('metacpan_search-input')->type('Type::More');
$firefox->await(sub { $firefox->find_class('autocomplete-suggestion'); })->click();
while(!$firefox->loaded()) {
    # redirecting to Test::More page
}

๐Ÿ”‘ logins

returns a list of all the Firefox::Marionette::Login objects available. all of them. the whole stash ๐Ÿซถ

use Firefox::Marionette();
use v5.10;

my $firefox = Firefox::Marionette->new();
foreach my $login ($firefox->logins()) {
   say "Found login for " . $login->host() . " and user " . $login->user();
}

๐Ÿ“„ logins_from_csv

takes a filehandle as a param, then reads that filehandle for exported logins as CSV. it's known to work with these formats:

returns a list of Firefox::Marionette::Login objects.

use Firefox::Marionette();
use FileHandle();

my $handle = FileHandle->new('/path/to/last_pass.csv');
my $firefox = Firefox::Marionette->new();
foreach my $login (Firefox::Marionette->logins_from_csv($handle)) {
    $firefox->add_login($login);
}

๐Ÿ“œ logins_from_xml

takes a filehandle as a param, then reads that filehandle for exported logins as XML. it's known to work with these formats:

returns a list of Firefox::Marionette::Login objects.

use Firefox::Marionette();
use FileHandle();

my $handle = FileHandle->new('/path/to/keepass1.xml');
my $firefox = Firefox::Marionette->new();
foreach my $login (Firefox::Marionette->logins_from_csv($handle)) {
    $firefox->add_login($login);
}

๐Ÿ—œ๏ธ logins_from_zip

takes a filehandle as a param, then reads that filehandle for exported logins as a zip file. it's known to work with these formats:

returns a list of Firefox::Marionette::Login objects.

use Firefox::Marionette();
use FileHandle();

my $handle = FileHandle->new('/path/to/1Passwordv8.1pux');
my $firefox = Firefox::Marionette->new();
foreach my $login (Firefox::Marionette->logins_from_zip($handle)) {
    $firefox->add_login($login);
}

returns a list of all of these elements:

...as Firefox::Marionette::Link objects. iconic little collection honestly.

this method is subject to the implicit timeout, which by default is 0 seconds. so don't come crying if it bails early ๐Ÿ’€
use Firefox::Marionette();

my $firefox = Firefox::Marionette->new()->go('https://metacpan.org/');
if (my $link = $firefox->links()) {
    if ($link->tag() eq 'a') {
        warn "Found a hyperlink to " . $link->URL();
    }
}

if no elements are found, this method returns undef. it's giving nothing, literally.


๐ŸŽ macos_binary_paths

returns a list of filesystem paths that this module will check for binaries it can automate when running on MacOS <https://en.wikipedia.org/wiki/MacOS>. only of interest when you're sub-classing, so if that's not you, carry on bestie.


๐Ÿ›ฐ๏ธ marionette_protocol

returns the version for the Marionette protocol. the current most recent version is '3'. slay.


๐Ÿ–ฅ๏ธ maximise

maximises the firefox window. returns itself so you can chain methods like the icon you are. big window, big energy โœจ


๐Ÿ“ฆ mime_types

returns a list of MIME types that will be downloaded by firefox and made available from the downloads method.

use Firefox::Marionette();
use v5.10;

my $firefox = Firefox::Marionette->new(mime_types => [ 'application/pkcs10' ])

foreach my $mime_type ($firefox->mime_types()) {
    say $mime_type;
}

๐Ÿค minimise

minimises the firefox window. returns itself so you can keep chaining. tiny window, still main character ๐Ÿฆ‹


๐Ÿ–ฑ๏ธ mouse_down

takes a param describing which mouse button this method should apply to (left, middle or right) and returns an action for use in the perform method that corresponds to a mouse button being pressed down.


๐Ÿ–ฑ๏ธ mouse_move

takes an element param, or a ( x => 0, y => 0 ) type hash manually describing exactly where to move the mouse to, and returns an action for use in the perform method that corresponds to such a mouse movement โ€” either to the specified co-ordinates or to the middle of the supplied element param. other params you can pass are listed below;

  • origin - the origin of the <x => 0, y => 0> co-ordinates. should be either "viewport", "pointer" or an element.
  • duration - number of milliseconds over which to distribute the move. if not defined, the duration defaults to 0.

this method returns itself to aid in chaining methods.


๐Ÿ–ฑ๏ธ mouse_up

takes a param describing which mouse button this method should apply to (left, middle or right) and returns an action for use in the perform method that corresponds to a mouse button being released.


๐ŸŒŸ new

takes an optional hash as a param. allowed keys are below;

  • addons โ€” should any firefox extensions and themes be available in this session? defaults to "0".
  • binary โ€” use the specified path to the Firefox <https://firefox.org/> binary, rather than the default path.
  • capabilities โ€” use the supplied capabilities object, for example to set whether the browser should accept insecure certs or whether the browser should use a proxy.
  • chatty โ€” Firefox is extremely chatty on the network, including checking for the latest malware/phishing sites, updates to firefox/etc. this option is therefore off ("0") by default, but it can be switched on ("1") if required. even with chatty switched off, connections to firefox.settings.services.mozilla.com will still be made <https://bugzilla.mozilla.org/show_bug.cgi?id=1598562#c13>. the only way to prevent this seems to be to set firefox.settings.services.mozilla.com to 127.0.0.1 via /etc/hosts <https://en.wikipedia.org/wiki//etc/hosts>.
NOTE: that this option only works when profile_name/profile is not specified. fr fr, don't skip this one.
  • console โ€” show the browser console <https://developer.mozilla.org/en-US/docs/Tools/Browser_Console/> when the browser is launched. this defaults to "0" (off). see CONSOLE LOGGING for a discussion of how to send log messages to the console.
  • debug โ€” bestie, should firefox's debug be available via STDERR? This defaults to "0". Any ssh connections will also get printed to STDERR. And yeah, this also defaults to "0" (off). You can change this setting later with the debug method. But here's the tea: if this option isn't a boolean (0|1), the value gets handed off to the MOZ_LOG option (see <https://firefox-source-docs.mozilla.org/networking/http/logging.html>) on the command line of the firefox binary so you can unlock extra levels of debug. ๐Ÿ’…
  • developer โ€” only allow a developer edition (see <https://www.mozilla.org/en-US/firefox/developer/>) to be launched. This defaults to "0" (off). ๐Ÿง‘โ€๐Ÿ’ป
  • devtools โ€” begin the session with the devtools (see <https://developer.mozilla.org/en-US/docs/Tools>) window opened in a separate window. โœจ
  • geo โ€” set up the browser preferences (see <http://kb.mozillazine.org/About:config>) so the Geolocation API (see <https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API>) actually works. Here's the vibe: if the value for this key is a URI object or a string beginning with '^(?:data|http)', this object will be retrieved using the json method, and the response gets used to build a GeoLocation object, which then gets sent to the geo method. If the value for this key is a hash, that hash is used to build a GeoLocation object, which gets sent to the geo method. ๐Ÿ“
  • height โ€” set the height (see <http://kb.mozillazine.org/Command_line_arguments#List_of_command_line_arguments_.28incomplete.29>) of the initial firefox window.
  • har โ€” begin the session with the devtools (see <https://developer.mozilla.org/en-US/docs/Tools>) window opened in a separate window. The HAR Export Trigger addon (see <https://addons.mozilla.org/en-US/firefox/addon/har-export-trigger/>) will be loaded into the new session automatically, which means that -safe-mode (see <http://kb.mozillazine.org/Command_line_arguments#List_of_command_line_arguments_.28incomplete.29>) will not be activated for this session AND this functionality will only be available for Firefox 61+. โš ๏ธ heads up on both those things
  • host โ€” use ssh (see <https://man.openbsd.org/ssh.1>) to create and automate firefox on the specified host. Go read REMOTE AUTOMATION OF FIREFOX VIA SSH and NETWORK ARCHITECTURE for the full lore. The user will default to the current user name (peep the user parameter if you wanna change that). Authentication should be via public keys loaded into the local ssh-agent (see <https://man.openbsd.org/ssh-agent>). ๐Ÿ”‘
  • implicit โ€” a shortcut so you can directly provide the implicit timeout instead of having to use timeouts from the capabilities parameter. Overrides all longer ways. ๐Ÿ˜Œ
  • index โ€” a parameter that lets you specify a specific firefox instance to survive and reconnect to. It literally does nothing else at the moment, bestie. See the survive parameter.
  • kiosk โ€” start the browser in kiosk mode (see <https://support.mozilla.org/en-US/kb/firefox-enterprise-kiosk-mode>). ๐Ÿ–ฅ๏ธ
  • mime_types โ€” any MIME types that Firefox will encounter during this session. MIME types that are not specified will result in a hung browser (the File Download popup will appear). so specify them, fr fr ๐Ÿ˜ญ
  • nightly โ€” only allow a nightly release (see <https://www.mozilla.org/en-US/firefox/channel/desktop/#nightly>) to be launched. This defaults to "0" (off). ๐ŸŒ™
  • port โ€” if the host parameter is also set, use ssh (see <https://man.openbsd.org/ssh.1>) to create and automate firefox via the specified port. See REMOTE AUTOMATION OF FIREFOX VIA SSH and NETWORK ARCHITECTURE.
  • page_load โ€” a shortcut so you can directly provide the page_load timeout instead of having to use timeouts from the capabilities parameter. Overrides all longer ways. ๐Ÿ’จ
  • profile โ€” create a new profile based on the supplied profile. NOTE: firefox ignores any changes made to the profile on the disk while it's running โ€” instead, use the set_pref and clear_pref methods to make changes while firefox is running. ๐Ÿ‘‘
  • profile_name โ€” pick a specific existing profile to automate, rather than creating a new profile. Firefox (see <https://firefox.com>) refuses to allow more than one instance of a profile to run at the same time. Profile names can be obtained by using the Firefox::Marionette::Profile::names() method. The following conditions are required to use existing profiles:
    • the preference "security.webauth.webauthn_enable_softtoken" must be set to "true" in the profile OR
    • the "webauth" parameter to this method must be set to 0

NOTE: firefox ignores any changes made to the profile on the disk while it's running โ€” instead, use the set_pref and clear_pref methods to make changes while firefox is running.

  • proxy โ€” this is a shortcut method for setting a proxy using the capabilities parameter above. It accepts a proxy URL, with the following allowable schemes: 'http' and 'https'. It also allows a reference to a list of proxy URLs, which will function as a list of proxies that Firefox will try in left to right order (see <https://developer.mozilla.org/en-US/docs/Web/HTTP/Proxy_servers_and_tunneling/Proxy_Auto-Configuration_PAC_file#description>) until a working proxy is found. See REMOTE AUTOMATION OF FIREFOX VIA SSH, NETWORK ARCHITECTURE and SETTING UP SOCKS SERVERS USING SSH. ๐ŸŒ
  • reconnect โ€” an experimental parameter to allow a reconnection to firefox that a connection has been discontinued. See the survive parameter. ๐Ÿงช
  • scp โ€” force the scp protocol when transferring files to remote hosts via ssh. See REMOTE AUTOMATION OF FIREFOX VIA SSH and the --scp-only option in the ssh-auth-cmd-marionette (see <https://metacpan.org/pod/ssh-auth-cmd-marionette>) script in this distribution.
  • script โ€” a shortcut so you can directly provide the script timeout instead of having to use timeouts from the capabilities parameter. Overrides all longer ways. โšก
  • seer โ€” this option is switched off "0" by default. When you switch it on "1", it will activate the various speculative and pre-fetch options for firefox. NOTE: this option only works when profile_name/profile is not specified. ๐Ÿ”ฎ
  • sleep_time_in_ms โ€” the amount of time (in milliseconds) that this module should sleep when unsuccessfully calling the subroutine provided to the await or bye methods. This defaults to "1" millisecond. ๐Ÿ˜ด
  • stealth โ€” stops navigator.webdriver (see <https://developer.mozilla.org/en-US/docs/Web/API/Navigator/webdriver>) from being accessible by the current web page. This is achieved by loading an extension (see <https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions>), which will automatically switch on the "addons" parameter for the new method. This is extremely experimental. See IMITATING OTHER BROWSERS for a discussion. ๐Ÿฅท
  • survive โ€” if this is set to a true value, firefox will not automatically exit when the object goes out of scope. See the reconnect parameter for an experimental technique for reconnecting. ๐Ÿซถ
  • trust โ€” give a path to a root certificate (see <https://en.wikipedia.org/wiki/Root_certificate>) encoded as a PEM encoded X.509 certificate (see <https://datatracker.ietf.org/doc/html/rfc7468#section-5>) that will be trusted for this session. ๐Ÿ“œ
  • timeouts โ€” a shortcut so you can directly provide a timeout object instead of having to use timeouts from the capabilities parameter. Overrides the timeouts provided (if any) in the capabilities parameter. โฐ
  • trackable โ€” if this is set, profile preferences will be set to make it harder to be tracked by the browser's fingerprint (see <https://en.wikipedia.org/wiki/Device_fingerprint#Browser_fingerprint>) across browser restarts. This is on by default, but may be switched off by setting it to 0. ๐Ÿ•ต๏ธ
  • user โ€” if the "host" parameter is also set, use ssh (see <https://man.openbsd.org/ssh.1>) to create and automate firefox with the specified user. See REMOTE AUTOMATION OF FIREFOX VIA SSH and NETWORK ARCHITECTURE. The user will default to the current user name. Authentication should be via public keys loaded into the local ssh-agent (see <https://man.openbsd.org/ssh-agent>). ๐Ÿ”
  • via โ€” specifies a proxy jump box (see <https://man.openbsd.org/ssh_config#ProxyJump>) to be used to connect to a remote host. See the host parameter. ๐Ÿš€
  • visible โ€” should firefox be visible on the desktop. This defaults to "0". When moving from an X11 platform to another X11 platform, you can set visible to 'local' to enable X11 forwarding (see <https://man.openbsd.org/ssh#X>). See X11 FORWARDING WITH FIREFOX. ๐Ÿ‘€
  • waterfox โ€” only allow a binary that looks like a waterfox version (see <https://www.waterfox.net/>) to be launched. ๐ŸฆŠ
  • webauthn โ€” a boolean parameter to determine whether or not to add a webauthn authenticator after the connection is established. The default is to add a webauthn authenticator for Firefox after version 118. ๐Ÿ”‘
  • width โ€” set the width (see <http://kb.mozillazine.org/Command_line_arguments#List_of_command_line_arguments_.28incomplete.29>) of the initial firefox window.

This method returns a new "Firefox::Marionette" object, connected to an instance of firefox (see <https://firefox.com>). In a non MacOS/Win32/Cygwin environment, if it's necessary (no DISPLAY variable can be found and the visible parameter to the new method has been set to true) and possible (Xvfb can be executed successfully), this method will also automatically start an Xvfb instance (see <https://en.wikipedia.org/wiki/Xvfb>). โœจ

use Firefox::Marionette();

my $remote_darwin_firefox = Firefox::Marionette->new(
                 debug => 'timestamp,nsHttp:1',
                 host => '10.1.2.3',
                 trust => '/path/to/root_ca.pem',
                 binary => '/Applications/Firefox.app/Contents/MacOS/firefox'
                                                    ); # start a temporary profile for a remote firefox and load a new CA into the temp profile
...

foreach my $profile_name (Firefox::Marionette::Profile->names()) {
    my $firefox_with_existing_profile = Firefox::Marionette->new( profile_name => $profile_name, visible => 1 );
    ...
}

๐ŸชŸ new_window ๐ŸชŸ

accepts an optional hash as the parameter. Allowed keys are below ๐Ÿ‘‡

  • focus โ€” a boolean field representing if the new window be opened in the foreground (focused) or background (not focused). Defaults to false.
  • private โ€” a boolean field representing if the new window should be a private window. Defaults to false. ๐Ÿ•ถ๏ธ
  • type โ€” the type of the brand new window, bestie. It can be one of 'tab' or 'window'. Defaults to 'tab'.

Returns the window handle for the new window, and you're gonna want it for switching, fr.

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new();

my $window_handle = $firefox->new_window(type => 'tab');

$firefox->switch_to_window($window_handle);

new_session

creates a whole new WebDriver session โœจ. The way this works: it's on you, the caller, to do the necessary checks on the requested capabilities so they're WebDriver conforming โ€” no cap. The WebDriver service that Marionette offers does not match or negotiate capabilities beyond type and bounds checks. That's it, that's the deal ๐Ÿ’….

nightly

returns true if the current version of firefox is a nightly release (https://www.mozilla.org/en-US/firefox/channel/desktop/#nightly) โ€” and how does it know? It checks if the minor version number ends with an 'a1'. Iconic behaviour honestly ๐ŸŒ™.

paper_sizes

returns a list of all the recognised names for paper sizes, like A4 or LEGAL. Very stationery-core of it ๐ŸŽ€.

pause

accepts a parameter in milliseconds and returns a corresponding action for the perform method that will cause a pause in the chain of actions you handed to perform. Little breather moment โธ๏ธ.

pdf

accepts an optional hash as the first parameter with the following allowed keys, and bestie, there are a lot of them:

  • landscape โ€” Paper orientation. Boolean value. Defaults to false ๐Ÿ–จ๏ธ
  • margin โ€” A hash describing the margins. The hash may have the following optional keys: 'top', 'left', 'right' and 'bottom'. All these keys are in cm and default to 1 (~0.4 inches).
  • page โ€” A hash describing the page. The hash may have the following keys: 'height' and 'width'. Both keys are in cm and default to US letter size. See the 'size' key.
  • page_ranges โ€” A list of the pages to print. Available for Firefox 96 (https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/96#webdriver_conformance_marionette) and after.
  • print_background โ€” Print background graphics. Boolean value. Defaults to false.
  • raw โ€” rather than a file handle containing the PDF, the binary PDF will be returned. Straight to the veins, no wrapper ๐Ÿ˜ค.
  • scale โ€” Scale of the webpage rendering. Defaults to 1. "shrink_to_fit" should be disabled to make "scale" work. (Yes, that's a real gotcha, noted.)
  • size โ€” The desired size (width and height) of the pdf, specified by name. See the page key for an alternative and the paper_sizes method for a list of accepted page size names.
  • shrink_to_fit โ€” Whether or not to override page size as defined by CSS. Boolean value. Defaults to true.

returns a File::Temp object containing a PDF encoded version of the current page for printing. Obsessed ๐Ÿซถ.

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new()->go('https://metacpan.org/');
my $handle = $firefox->pdf();
foreach my $paper_size ($firefox->paper_sizes()) {
        $handle = $firefox->pdf(size => $paper_size, landscape => 1, margin => { top => 0.5, left => 1.5 });
        ...
        print $firefox->pdf(page => { width => 21, height => 27 }, raw => 1);
        ...
}

percentage_visible

accepts an element as the first parameter and returns the percentage of that element that is currently visible in the viewport (https://developer.mozilla.org/en-US/docs/Glossary/Viewport) ๐Ÿ‘€. How does it pull that off? It determines the co-ordinates of the DOMRect (https://developer.mozilla.org/en-US/docs/Web/API/DOMRect) with a getBoundingClientRect (https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect) call, and then uses elementsFromPoint (https://developer.mozilla.org/en-US/docs/Web/API/Document/elementsFromPoint) and getComputedStyle (https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle) calls to work out how much of the DOMRect (https://developer.mozilla.org/en-US/docs/Web/API/DOMRect) is visible to the user. The getComputedStyle (https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle) call is there to determine the state of the visibility (https://developer.mozilla.org/en-US/docs/Web/CSS/visibility) and display (https://developer.mozilla.org/en-US/docs/Web/CSS/display) attributes. So many calls, so much slay ๐Ÿฆ‹.

use Firefox::Marionette();
use Encode();
use v5.10;

my $firefox = Firefox::Marionette->new( visible => 1, kiosk => 1 )->go('http://metacpan.org');;
my $element = $firefox->find_id('metacpan_search-input');
my $totally_viewable_percentage = $firefox->percentage_visible($element); # search box is slightly hidden by different effects
foreach my $display ($firefox->displays()) {
    if ($firefox->resize($display->width(), $display->height())) {
        if ($firefox->percentage_visible($element) < $totally_viewable_percentage) {
           say 'Search box stops being fully viewable with ' . Encode::encode('UTF-8', $display->usage());
           last;
        }
    }
}

perform

accepts a list of actions (see mouse_up, mouse_down, mouse_move, pause, key_down and key_up) and performs these actions in sequence ๐Ÿ”ฅ. This gives you fine control over interactions โ€” including sending right clicks to the browser and sending Control, Alt and other special keys, main character keyboard energy. The release method will complete outstanding actions (such as mouse_up or key_up actions).

use Firefox::Marionette();
use Firefox::Marionette::Keys qw(:all);
use Firefox::Marionette::Buttons qw(:all);

my $firefox = Firefox::Marionette->new();

$firefox->chrome()->perform(
                             $firefox->key_down(CONTROL()),
                             $firefox->key_down('l'),
                             $firefox->key_up('l'),
                             $firefox->key_up(CONTROL())
                           )->content();

$firefox->go('https://metacpan.org');
my $help_button = $firefox->find_class('btn search-btn help-btn');
$firefox->perform(
                              $firefox->mouse_move($help_button),
                              $firefox->mouse_down(RIGHT_BUTTON()),
                              $firefox->pause(4),
                              $firefox->mouse_up(RIGHT_BUTTON()),
            );

See the release method for an alternative to manually specifying all the mouse_up and key_up methods.

profile_directory

returns the profile directory used by the current instance of firefox ๐Ÿ—‚๏ธ. This is mainly intended for debugging firefox. And listen: Firefox is not designed to cope with these files being altered while firefox is running. So don't go poking around in there mid-session, ngl ๐Ÿ™ˆ.

property

accepts an element as the first parameter and a scalar attribute name as the second parameter. It returns the current value of the property with the supplied name. Heads up though โ€” this method will return the current content, whereas the attribute method will return the initial content from the HTML source code. Different vibes, different methods ๐Ÿ’œ.

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new()->go('https://metacpan.org/');
my $element = $firefox->find_id('metacpan_search-input');
$element->property('value') eq '' or die "Initial property should be the empty string";
$element->type('Test::More');
$element->property('value') eq 'Test::More' or die "This property should have changed!";

# OR getting the innerHTML property

my $title = $firefox->find_tag('title')->property('innerHTML'); # same as $firefox->title();

pwd_mgr_lock

Accepts a new primary password (https://support.mozilla.org/en-US/kb/use-primary-password-protect-stored-logins) and locks the Password Manager (https://support.mozilla.org/en-US/kb/password-manager-remember-delete-edit-logins) with it ๐Ÿ”’. Locking it down, periodt.

use Firefox::Marionette();
use IO::Prompt();

my $firefox = Firefox::Marionette->new();
my $password = IO::Prompt::prompt(-echo => q[*], "Please enter the password for the Firefox Password Manager:");
$firefox->pwd_mgr_lock($password);
$firefox->pwd_mgr_logout();
# now no-one can access the Password Manager Database without the value in $password

This method returns itself to aid in chaining methods. Cute of it โœจ.

pwd_mgr_login

Accepts the primary password (https://support.mozilla.org/en-US/kb/use-primary-password-protect-stored-logins) and allows the user to access the Password Manager (https://support.mozilla.org/en-US/kb/password-manager-remember-delete-edit-logins). Let me in, let me innn ๐Ÿซถ.

use Firefox::Marionette();
use IO::Prompt();

my $firefox = Firefox::Marionette->new( profile_name => 'default' );
my $password = IO::Prompt::prompt(-echo => q[*], "Please enter the password for the Firefox Password Manager:");
$firefox->pwd_mgr_login($password);
...
# access the Password Database.
...
$firefox->pwd_mgr_logout();
...
# no longer able to access the Password Database.

This method returns itself to aid in chaining methods.

pwd_mgr_logout

Logs the user out of being able to access the Password Manager (https://support.mozilla.org/en-US/kb/password-manager-remember-delete-edit-logins). Bye bye access ๐Ÿ‘‹.

use Firefox::Marionette();
use IO::Prompt();

my $firefox = Firefox::Marionette->new( profile_name => 'default' );
my $password = IO::Prompt::prompt(-echo => q[*], "Please enter the password for the Firefox Password Manager:");
$firefox->pwd_mgr_login($password);
...
# access the Password Database.
...
$firefox->pwd_mgr_logout();
...
# no longer able to access the Password Database.

This method returns itself to aid in chaining methods.

pwd_mgr_needs_login

returns true or false if the Password Manager (https://support.mozilla.org/en-US/kb/password-manager-remember-delete-edit-logins) has been locked and needs a primary password (https://support.mozilla.org/en-US/kb/use-primary-password-protect-stored-logins) to access it. Vibe check on the password situation ๐ŸŽ€.

use Firefox::Marionette();
use IO::Prompt();

my $firefox = Firefox::Marionette->new( profile_name => 'default' );
if ($firefox->pwd_mgr_needs_login()) {
  my $password = IO::Prompt::prompt(-echo => q[*], "Please enter the password for the Firefox Password Manager:");
  $firefox->pwd_mgr_login($password);
}

quit

Marionette will stop accepting new connections before ending the current session, and finally attempting to quit the application ๐Ÿšช. This method returns the $? (CHILD_ERROR) value for the Firefox process. Real numbers, no cap.

rect

accepts a element as the first parameter and returns the current position and size of the element ๐Ÿ“.

refresh

refreshes the current page ๐Ÿ”„. The browser will wait for the page to completely refresh or the session's page_load duration to elapse before returning โ€” which, by default, is 5 minutes. Yes, five whole minutes, so maybe don't hold your breath bestie ๐Ÿ˜ญ. This method returns itself to aid in chaining methods.

release ๐Ÿ’…

okay so release completes any outstanding actions issued by the perform method. that's it, that's the job. it's the little โœจ and they were both like โœจโœจ.

use Firefox::Marionette();
use Firefox::Marionette::Keys qw(:all);
use Firefox::Marionette::Buttons qw(:all);

my $firefox = Firefox::Marionette->new();

$firefox->chrome()->perform(
                             $firefox->key_down(CONTROL()),
                             $firefox->key_down('l'),
                           )->release()->content();

$firefox->go('https://metacpan.org');
my $help_button = $firefox->find_class('btn search-btn help-btn');
$firefox->perform(
                              $firefox->mouse_move($help_button),
                              $firefox->mouse_down(RIGHT_BUTTON()),
                              $firefox->pause(4),
                )->release();

resize ๐Ÿฆ‹

takes width and height as a list, and then it attempts to resize the entire browser to match those parameters. and here's the thing bestie โ€” because window managers are chaotic little gremlins, this function has to manually calculate what the maximum and minimum sizes of the display actually are. like it's doing math nobody asked it to do. it goes like this:

  1. performing a maximise, then
  2. caching the browser's current width and height as the maximum width and height. It
  3. then calls resizeTo <https://developer.mozilla.org/en-US/docs/Web/API/Window/resizeTo> to resize the window to 0,0
  4. wait for the browser to send a resize <https://developer.mozilla.org/en-US/docs/Web/API/Window/resize_event> event.
  5. cache the browser's current width and height as the minimum width and height
  6. if the requested width and height are outside of the maximum and minimum widths and heights return false
  7. if the requested width and height matches the current width and height return itself to aid in chaining methods. Otherwise,
  8. call resizeTo <https://developer.mozilla.org/en-US/docs/Web/API/Window/resizeTo> for the requested width and height
  9. wait for the resize <https://developer.mozilla.org/en-US/docs/Web/API/Window/resize_event> event

this method returns itself to aid in chaining methods if the method succeeds, otherwise it returns false. no in-between, it's giving binary ๐Ÿ’€

use Firefox::Marionette();
use Encode();
use v5.10;

my $firefox = Firefox::Marionette->new( visible => 1, kiosk => 1 )->go('http://metacpan.org');;
if ($firefox->resize(1024, 768)) {
    say 'We are showing an XGA display';
} else {
   say 'Resize failed to work';
}

resolve ๐ŸŒˆ

takes a hostname as an argument and resolves it to a list of matching IP addresses. it can also take an optional hash with extra keys, and those are described over in Firefox::Marionette::DNS. slay.

use Firefox::Marionette();
use v5.10;

my $ssh_server = 'remote.example.org';
my $firefox = Firefox::Marionette->new( host => $ssh_server );
my $hostname = 'metacpan.org';
foreach my $ip_address ($firefox->resolve($hostname)) {
   say "$hostname resolves to $ip_address at $ssh_server";
}
$firefox = Firefox::Marionette->new();
foreach my $ip_address ($firefox->resolve($hostname, flags => Firefox::Marionette::DNS::RESOLVE_REFRESH_CACHE() | Firefox::Marionette::DNS::RESOLVE_BYPASS_CACHE(), type => Firefox::Marionette::DNS::RESOLVE_TYPE_DEFAULT())) {
   say "$hostname resolves to $ip_address;
}

resolve_override ๐Ÿงš

takes a hostname and an IP address as parameters, and then forces the browser to override any future DNS requests for the supplied hostname. it's gaslighting the browser, respectfully.

use Firefox::Marionette();
use v5.10;

my $firefox = Firefox::Marionette->new();
my $hostname = 'metacpan.org';
my $ip_address = '127.0.0.1';
foreach my $result ($firefox->resolve_override($hostname, $ip_address)->resolve($hostname)) {
   if ($result eq $ip_address) {
     warn "local metacpan time?";
   } else {
     die "This should not happen";
   }
}
$firefox->go('https://metacpan.org'); # this tries to contact a webserver on 127.0.0.1

this method returns itself to aid in chaining methods. ๐Ÿซถ


restart ๐Ÿ”„

restarts the browser. after the restart, capabilities should be restored. the same profile settings should be applied, but the current state of the browser (such as the uri) will be reset (like after a normal browser restart). this method is primarily intended for use by the update method. Not sure if this is useful by itself. (their words, not mine ๐Ÿ˜ญ)

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new();

$firefox->restart(); # but why?

this method returns itself to aid in chaining methods. โœจ


root_directory ๐Ÿ“

this is the root directory for the current instance of firefox. the directory may exist on a remote server. For debugging purposes only.


screen_orientation ๐Ÿ“ฑ

returns the current browser orientation. this will be one of the valid primary orientation values 'portrait-primary', 'landscape-primary', 'portrait-secondary', or 'landscape-secondary'. this method is only currently available on Android (Fennec). iykyk.


script ๐ŸŽ€

takes a scalar containing a javascript function body that gets executed in the browser, plus an optional hash as a second parameter. allowed keys are below bestie ๐Ÿ‘‡

  • args โ€” The reference to a list is the arguments passed to the function body.
  • filename โ€” Filename of the client's program where this script is evaluated.
  • line โ€” Line in the client's program where this script is evaluated.
  • new โ€” Forces the script to be evaluated in a fresh sandbox. Note that if it is undefined, the script will normally be evaluated in a fresh sandbox.
  • sandbox โ€” Name of the sandbox to evaluate the script in. The sandbox is cached for later re-use on the same window <https://developer.mozilla.org/en-US/docs/Web/API/Window> object if "new" is false. If he parameter is undefined, the script is evaluated in a mutable sandbox. If the parameter is "system", it will be evaluated in a sandbox with elevated system privileges, equivalent to chrome space.
  • timeout โ€” A timeout to override the default script timeout, which, by default is 30 seconds.

Returns the result of the javascript function. when a parameter is an element (such as being returned from a find type operation), the script method will automatically translate that into a javascript object. Likewise, when the result being returned in a script method is an element <https://dom.spec.whatwg.org/#concept-element> it will be automatically translated into a perl object. the circle of life fr fr ๐Ÿฆ‹

use Firefox::Marionette();
use v5.10;

my $firefox = Firefox::Marionette->new()->go('https://metacpan.org/');

if (my $element = $firefox->script('return document.getElementsByName("metacpan_search-input")[0];')) {
    say "Lucky find is a " . $element->tag_name() . " element";
}

my $search_input = $firefox->find_id('metacpan_search-input');

$firefox->script('arguments[0].style.backgroundColor = "red"', args => [ $search_input ]); # turn the search input box red

the executing javascript is subject to the script timeout, which, by default is 30 seconds. โฐ


selfie ๐Ÿคณ

returns a File::Temp object containing a lossless PNG image screenshot. if an element is passed as a parameter, the screenshot will be restricted to the element.

if an element is not passed as a parameter and the current context is 'chrome', a screenshot of the current viewport will be returned.

if an element is not passed as a parameter and the current context is 'content', a screenshot of the current frame will be returned.

the parameters after the element parameter are taken to be an optional hash with the following allowed keys;

  • hash โ€” return a SHA256 hex encoded digest of the PNG image rather than the image itself
  • full โ€” take a screenshot of the whole document unless the first element parameter has been supplied.
  • raw โ€” rather than a file handle containing the screenshot, the binary PNG image will be returned.
  • scroll โ€” scroll to the element supplied
  • highlights โ€” a reference to a list containing elements to draw a highlight around. Not available in Firefox 70 <https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/70#WebDriver_conformance_Marionette> onwards.

scroll ๐ŸŒธ

takes an element as the first parameter and scrolls <https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView> to it. the optional second parameter is the same as for the scrollInfoView <https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView> method.

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new(visible => 1)->go('https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView');
my $link = $firefox->find_id('content')->find_link('Examples');
$firefox->scroll($link);
$firefox->scroll($link, 1);
$firefox->scroll($link, { behavior => 'smooth', block => 'center' });
$firefox->scroll($link, { block => 'end', inline => 'nearest' });

send_alert_text ๐Ÿ’ฌ

sends keys to the input field of a currently displayed modal message box. that's the whole thing, short and sweet, we love a concise queen ๐Ÿ‘‘


set_javascript โšก

takes a parameter for the the profile preference value of javascript.enabled <https://support.mozilla.org/en-US/kb/javascript-settings-for-interactive-web-pages#w_for-advanced-users>. this method returns itself to aid in chaining methods.


set_pref ๐ŸŽ›๏ธ

takes a preference <http://kb.mozillazine.org/About:config> name and the new value to set it to. see the get_pref and clear_pref methods to get a preference value and to restore it to it's original value. this method returns itself to aid in chaining methods.

use Firefox::Marionette();
my $firefox = Firefox::Marionette->new();
...
$firefox->set_pref('browser.search.defaultenginename', 'DuckDuckGo');

shadow_root ๐ŸŒ‘

takes an element as a parameter and returns it's ShadowRoot <https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot> as a shadow root object or throws an exception. so it's either a vibe or a crash, no third option ๐Ÿ’€

use Firefox::Marionette();
use Cwd();

my $firefox = Firefox::Marionette->new()->go('file://' . Cwd::cwd() . '/t/data/elements.html');

$firefox->find_class('add')->click();
my $custom_square = $firefox->find_tag('custom-square');
my $shadow_root = $firefox->shadow_root($custom_square);

foreach my $element (@{$firefox->script('return arguments[0].children', args => [ $shadow_root ])}) {
    warn $element->tag_name();
}

see the FINDING ELEMENTS IN A SHADOW DOM section for how to delve into a shadow DOM <https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM>. โœจ

๐ŸŒŸ shadowy ๐ŸŒŸ

okay bestie, so shadowy takes an element as a parameter and gives you back true if that element has a ShadowRoot (that's the one at <https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot>), or false if it doesn't. that's it, that's the vibe check ๐Ÿ’…

use Firefox::Marionette();
use Cwd();

my $firefox = Firefox::Marionette->new()->go('file://' . Cwd::cwd() . '/t/data/elements.html');
$firefox->find_class('add')->click();
my $custom_square = $firefox->find_tag('custom-square');
if ($firefox->shadowy($custom_square)) {
    my $shadow_root = $firefox->find_tag('custom-square')->shadow_root();
    warn $firefox->script('return arguments[0].innerHTML', args => [ $shadow_root ]);
    ...
}

fr fr, the whole point of this one is checking whether shadow_root can even be called on this element without it throwing an exception at you. it's giving "look before you leap" ๐Ÿฆ‹


๐Ÿ˜ด sleep_time_in_ms ๐Ÿ˜ด

this one accepts a new time to sleep (in the await or bye methods) and returns the previous time back to you. the default is "1" millisecond โ€” yes, one, tiny, iconic.

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new(sleep_time_in_ms => 5); # setting default time to 5 milliseconds

my $old_time_in_ms = $firefox->sleep_time_in_ms(8); # setting default time to 8 milliseconds, returning 5 (milliseconds)

๐Ÿ”Œ ssh_local_directory ๐Ÿ”Œ

returns the path to the local directory for the ssh connection (if any). For debugging purposes only. no cap, that's the whole thing ๐Ÿซถ


๐ŸŽ€ strip ๐ŸŽ€

returns the page source of the content document, but after an attempt has been made to remove the typical firefox html wrappers that show up around non-html content types like text/plain and application/json.

  • peep the json method if you're dealing with response content types such as application/json โœจ
  • peep html if you're dealing with html content types ๐ŸŒธ

it's a convenience method that literally just wraps the html method. periodt.

use Firefox::Marionette();
use JSON();
use v5.10;

say JSON::decode_json(Firefox::Marionette->new()->go("https://fastapi.metacpan.org/v1/download_url/Firefox::Marionette")->strip())->{version};
โš ๏ธ heads up bestie: this method assumes the bytes it gets from the html method are UTF-8 encoded and translates accordingly โ€” and if they're not UTF-8 encoded, it will throw an exception at you in the process ๐Ÿ˜ญ

๐ŸชŸ switch_to_frame ๐ŸชŸ

accepts a frame as a parameter and switches to it within the current window. simple, clean, slay.


โฌ†๏ธ switch_to_parent_frame โฌ†๏ธ

sets the current browsing context for future commands to the parent of the current browsing context. it's giving "go up a level" ๐Ÿ’œ


๐Ÿšช switch_to_window ๐Ÿšช

accepts a window handle (either the result of window_handles or a window name) as a parameter and switches focus to this window. main character energy only ๐Ÿ‘‘

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new();
$firefox->version
my $original_window = $firefox->window_handle();
$firefox->new_window( type => 'tab' );
$firefox->new_window( type => 'window' );
$firefox->switch_to_window($original_window);
$firefox->go('https://metacpan.org');

๐Ÿท๏ธ tag_name ๐Ÿท๏ธ

accepts a Firefox::Marionette::Element object as the first parameter and returns the relevant tag name. like, for example 'a' (the one at <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a>) or 'input' (the one at <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input>). iconic โœจ


๐Ÿ’ฌ text ๐Ÿ’ฌ

accepts a element as the first parameter and returns the text that is contained by that element (if any). yes, "if any" โ€” sometimes there's just nothing there and that's fine ๐Ÿ“


โณ timeouts โณ

returns the current timeouts for page loading, searching, and scripts. that's the trio, bestie ๐Ÿ’…


๐ŸŒ tz ๐ŸŒ

accepts a Olson TZ identifier (full list right here: <https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List>) as the first parameter.

and get this โ€” this method returns itself so you can keep chaining methods like a queen ๐ŸŽ€


๐Ÿ“› title ๐Ÿ“›

returns the current title of the window โ€” y'know, the one at <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title>. it's in the tab, it's the vibe ๐ŸŒˆ


โŒจ๏ธ type โŒจ๏ธ

accepts an element as the first parameter and a string as the second parameter. it sends that string to the specified element in the current page โ€” classic filling-out-a-text-box behaviour, you know the drill.

this method returns itself to aid in chaining methods. go off, chain away ๐Ÿซถ


๐Ÿ–ฅ๏ธ uname ๐Ÿ–ฅ๏ธ

returns the $^O ($OSNAME) compatible string describing the platform where firefox is running. cross-platform realness โญ


๐Ÿ”„ update ๐Ÿ”„

queries the Update Services and applies any available updates. it restarts the browser if necessary to complete the update.

โš ๏ธ this function is experimental and currently has not been successfully tested on Win32 or MacOS. so like, be aware, bestie ๐Ÿ˜ญ
use Firefox::Marionette();
use v5.10;

my $firefox = Firefox::Marionette->new();

my $update = $firefox->update();

while($update->successful()) {
    $update = $firefox->update();
}

say "Updated to " . $update->display_version() . " - Build ID " . $update->build_id();

$firefox->quit();

returns a status object that contains useful information about any updates that occurred. very demure, very informative ๐Ÿ’…


๐Ÿ—‘๏ธ uninstall ๐Ÿ—‘๏ธ

accepts the GUID for the addon to uninstall. that GUID is returned from the install method. this method returns itself to aid in chaining methods โœจ

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new();

my $extension_id = $firefox->install('/full/path/to/gnu_terry_pratchett-0.4-an+fx.xpi');

# do something

$firefox->uninstall($extension_id); # not recommended to uninstall this extension IRL.

(and no, genuinely, don't uninstall that extension IRL. the manpage said it, not me ๐Ÿข)


๐Ÿ”— uri ๐Ÿ”—

returns the current URI of the current top level browsing context for Desktop. it is equivalent to the javascript "document.location.href" โ€” literally the same thing, no notes ๐Ÿ”ฅ


๐Ÿ” webauthn_authenticator ๐Ÿ”

returns the default WebAuthn authenticator that was created when the new method was called. security queen behaviour ๐Ÿ‘‘


๐Ÿชช webauthn_credentials ๐Ÿชช

this method accepts an optional authenticator โ€” and if you give it one, it returns all the credentials attached to that authenticator. if you supply no parameter at all, you get the credentials from the default authenticator instead. easy ๐Ÿ’œ

use Firefox::Marionette();
use v5.10;

my $firefox = Firefox::Marionette->new();
foreach my $credential ($firefox->webauthn_credentials()) {
   say "Credential host is " . $credential->host();
}

# OR

my $authenticator = $firefox->add_webauthn_authenticator( transport => Firefox::Marionette::WebAuthn::Authenticator::INTERNAL(), protocol => Firefox::Marionette::WebAuthn::Authenticator::CTAP2() );
foreach my $credential ($firefox->webauthn_credentials($authenticator)) {
   say "Credential host is " . $credential->host();
}

โœ… webauthn_set_user_verified โœ…

this method accepts a boolean for the is_user_verified field, plus an optional authenticator (otherwise the default authenticator gets used). it sets the is_user_verified field to the supplied boolean value. boom, done ๐ŸŽ€

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new();
$firefox->webauthn_set_user_verified(1);

๐Ÿ–ฑ๏ธ wheel ๐Ÿ–ฑ๏ธ

accepts an element parameter, or a ( x => 0, y => 0 ) type hash manually describing exactly where to move the mouse from, and returns an action for use in the perform method that corresponds with such a wheel action โ€” either to the specified co-ordinates or to the middle of the supplied element parameter.

other parameters you can pass are listed below bestie ๐Ÿ‘‡

  • origin โ€” the origin of the ( x => 0, y => 0 ) co-ordinates. should be either "viewport", "pointer" or an element.
  • duration โ€” number of milliseconds over which to distribute the move. if not defined, the duration defaults to 0.
  • deltaX โ€” the change in X co-ordinates during the wheel. if not defined, deltaX defaults to 0.
  • deltaY โ€” the change in Y co-ordinates during the wheel. if not defined, deltaY defaults to 0.

๐ŸชŸ win32_organisation ๐ŸชŸ

accepts a parameter of a Win32 product name and returns the matching organisation. Only of interest when sub-classing.


๐Ÿ“‹ win32_product_names ๐Ÿ“‹

returns a hash of known Windows product names (such as 'Mozilla Firefox') with priority orders. the lower the priority, the earlier this module will check for the existence of that product. Only of interest when sub-classing. iykyk ๐Ÿ’…


๐ŸชŸ window_handle ๐ŸชŸ

returns the current window's handle. on desktop this typically corresponds to the currently selected tab. it returns an opaque server-assigned identifier for this window that uniquely identifies it within this Marionette instance โ€” so you can use it to switch back to this window at a later point.

this is the same as the window object in Javascript (the one at <https://developer.mozilla.org/en-US/docs/Web/API/Window>) ๐ŸŒˆ

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new();
my $original_window = $firefox->window_handle();
my $javascript_window = $firefox->script('return window'); # only works for Firefox 121 and later
if ($javascript_window ne $original_window) {
    die "That was unexpected!!! What happened?";
}

๐Ÿ—‚๏ธ window_handles ๐Ÿ—‚๏ธ

returns a list of top-level browsing contexts. on desktop this typically corresponds to the set of open tabs for browser windows, or the window itself for non-browser chrome windows.

each window handle is assigned by the server and is guaranteed unique โ€” however, the return array does not have a specified ordering. so don't come for me about the order bestie ๐Ÿ˜ญ

use Firefox::Marionette();
use 5.010;

my $firefox = Firefox::Marionette->new();
my $original_window = $firefox->window_handle();
$firefox->new_window( type => 'tab' );
$firefox->new_window( type => 'window' );
say "There are " . $firefox->window_handles() . " tabs open in total";
say "Across " . $firefox->chrome()->window_handles()->content() . " chrome windows";

๐Ÿ“ window_rect ๐Ÿ“

accepts an optional position and size as a parameter, sets the current browser window to that position and size, and returns the previous position, size and state of the browser window.

if you pass no parameter at all, it just returns the current position, size and state of the browser window. glow up your window, king ๐Ÿ’…


๐ŸชŸ window_type ๐ŸชŸ

returns the current window's type. this should be 'navigator:browser'. that's the whole thing, no cap โœจ


๐Ÿ”ข xvfb_pid ๐Ÿ”ข

returns the pid of the xvfb process, if it exists. ๐ŸŽ€


๐Ÿ–ผ๏ธ xvfb_display ๐Ÿ–ผ๏ธ

returns the value for the DISPLAY environment variable if one has been generated for the xvfb environment. ๐ŸŒธ


๐Ÿ”‘ xvfb_xauthority ๐Ÿ”‘

returns the value for the XAUTHORITY environment variable if one has been generated for the xvfb environment. ๐Ÿ’œ

โœจ NETWORK ARCHITECTURE โœจ

okay bestie, this is the part where things get actually iconic ๐Ÿ’… this module lets you build a whole complicated network architecture โ€” SSH proxies, HTTP proxies, the works. it's giving spy movie but make it Perl.

my $firefox = Firefox::Marionette->new(
                host  => 'Firefox.runs.here'
                via   => 'SSH.Jump.Box',
                trust => '/path/to/ca-for-squid-proxy-server.crt',
                proxy => 'https://Squid.Proxy.Server:3128'
                   )->go('https://Target.Web.Site');

and that lil snippet produces this exact effect, where each ascii box is its own separate network node:

     ---------          ----------         -----------
     | Perl  |  SSH     | SSH    |  SSH    | Firefox |
     | runs  |--------->| Jump   |-------->| runs    |
     | here  |          | Box    |         | here    |
     ---------          ----------         -----------
                                                |
     ----------          ----------             |
     | Target |  HTTPS   | Squid  |    TLS      |
     | Web    |<---------| Proxy  |<-------------
     | Site   |          | Server |
     ----------          ----------

likeโ€ฆ the hops?? the little arrows?? obsessed ๐Ÿ˜ญ


also the proxy parameter can hold multiple proxies, you just pass a reference to a list:

my $firefox = Firefox::Marionette->new(
                host  => 'Firefox.runs.here'
                trust => '/path/to/ca-for-squid-proxy-server.crt',
                proxy => [ 'https://Squid1.Proxy.Server:3128', 'https://Squid2.Proxy.Server:3128' ]
                   )->go('https://Target.Web.Site');

when firefox gets a whole list of proxies, it uses the first one that works. and ngl it also does a basic lil form of proxy failover โ€” which may involve a failed network request before it fails over to the next proxy. so yeah, one sad little network request might flop before it hops to the next one. it's fine. it's fine!! ๐Ÿ™ˆ

in the diagram below, Squid1.Proxy.Server is the first proxy in the list and gets used exclusively, unless it's unavailable โ€” in which case Squid2.Proxy.Server steps up like the main character it is ๐Ÿ‘‘

                                          ----------
                                     TLS  | Squid1 |
                                   ------>| Proxy  |-----
                                   |      | Server |    |
     ---------      -----------    |      ----------    |       -----------
     | Perl  | SSH  | Firefox |    |                    | HTTPS | Target  |
     | runs  |----->| runs    |    |                    ------->| Web     |
     | here  |      | here    |    |                    |       | Site    |
     ---------      -----------    |      ----------    |       -----------
                                   | TLS  | Squid2 |    |
                                   ------>| Proxy  |-----
                                          | Server |
                                          ----------
๐Ÿ’– head on over to the REMOTE AUTOMATION OF FIREFOX VIA SSH section for more options.
๐ŸŒธ and check SETTING UP SOCKS SERVERS USING SSH if you want easy proxying via ssh <https://man.openbsd.org/ssh>
๐Ÿฆ‹ and the GEO LOCATION section shows you how to combine all this with the right browser settings for your end point.

๐Ÿ” AUTOMATING THE FIREFOX PASSWORD MANAGER ๐Ÿ”

okay so this is lowkey the coolest part: you can log into a website without ever directly handling usernames and passwords. like, your hands never touch the credentials. iconic behaviour, no notes. ๐Ÿ’…

the Password Manager can be preloaded with the right passwords and locked, like this:

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new( profile_name => 'locked' ); # using a pre-built profile called 'locked'
if ($firefox->pwd_mgr_needs_login()) {
    my $new_password = IO::Prompt::prompt(-echo => q[*], 'Enter the password for the locked profile:');
    $firefox->pwd_mgr_login($password);
} else {
    my $new_password = IO::Prompt::prompt(-echo => q[*], 'Enter the new password for the locked profile:');
    $firefox->pwd_mgr_lock($password);
}
...
$firefox->pwd_mgr_logout();

and the usernames + passwords โ€” for both HTTP Authentication popups and HTML Form based logins โ€” can be added, viewed, and deleted. slay:

use WebService::HIBP();

my $hibp = WebService::HIBP->new();

$firefox->add_login(host => 'https://github.com', user => 'me@example.org', password => 'qwerty', user_field => 'login', password_field => 'password');
$firefox->add_login(host => 'https://pause.perl.org', user => 'AUSER', password => 'qwerty', realm => 'PAUSE');
...
foreach my $login ($firefox->logins()) {
    if ($hibp->password($login->password())) { # does NOT send the password to the HIBP webservice
        warn "HIBP reports that your password for the " . $login->user() . " account at " . $login->host() . " has been found in a data breach";
        $firefox->delete_login($login); # how could this possibly help?
    }
}

and you can use them to fill in login prompts without ever explicitly knowing the account details. the audacity. the elegance. ๐ŸŽ€

$firefox->go('https://pause.perl.org/pause/authenquery')->accept_alert(); # this goes to the page and submits the http auth popup

$firefox->go('https://github.com/login')->fill_login(); # fill the login and password fields without needing to see them

๐ŸŒ GEO LOCATION ๐ŸŒ

the firefox Geolocation API <https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API> is available to you โ€” you just supply the "geo" parameter to the new method, and then call the geo method (from a secure context <https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts>, bestie, don't forget that part).

the geo method accepts a bunch of specific latitude and longitude parameters as a list, like:

$firefox->geo(latitude => -37.82896, longitude => 144.9811);

OR

$firefox->geo(lat => -37.82896, long => 144.9811);

OR

$firefox->geo(lat => -37.82896, lng => 144.9811);

OR

$firefox->geo(lat => -37.82896, lon => 144.9811);

or you can pass it all in as a reference, like:

$firefox->geo({ latitude => -37.82896, longitude => 144.9811 });

and the way that you get a variety of parameter names plus the ability to pass parameters in as a reference means it can deal with all sorts of geo location websites, such as:

$firefox->geo($firefox->json('https://freeipapi.com/api/json/')); # get geo location from current IP address

$firefox->geo($firefox->json('https://geocode.maps.co/search?street=101+Collins+St&city=Melbourne&state=VIC&postalcode=3000&country=AU&format=json')->[0]); # get geo location of street address

$firefox->geo($firefox->json('http://api.positionstack.com/v1/forward?access_key=' . $access_key . '&query=101+Collins+St,Melbourne,VIC+3000')->{data}->[0]); # get geo location of street address using api key

$firefox->geo($firefox->json('https://api.ipgeolocation.io/ipgeo?apiKey=' . $api_key)); # get geo location from current IP address

$firefox->geo($firefox->json('http://api.ipstack.com/142.250.70.206?access_key=' . $api_key)); # get geo location from specific IP address (http access only for free)

ngl these sites were active at the time this documentation was written, but fr fr their main job is just to illustrate how flexible the geo and json methods are at handing the desired location over to the Geolocation API <https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API>. they're the vibe, not the promise ๐Ÿ’–

and like, as mentioned in the geo method documentation, the ipgeolocation API <https://ipgeolocation.io/documentation/ip-geolocation-api.html> is the only API that's currently providing geolocation data and matching timezone data in one single API call. no cap. so if you use that url, the tz method gets automatically called to set the timezone to the matching timezone for the geographic location. one call, two gifts. ๐Ÿซถ


๐Ÿ“ CONSOLE LOGGING ๐Ÿ“

sending debug to the console can be quite confusing in firefox, because some techniques just will not work in chrome context. ๐Ÿ˜ญ so here's an example that's genuinely super useful:

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new( visible => 1, devtools => 1, console => 1, devtools => 1 );

$firefox->script( q[console.log("This goes to devtools b/c it's being generated in content mode")]);

$firefox->chrome()->script( q[console.log("Sent out on standard error for Firefox 136 and later")]);

likeโ€ฆ the way that content mode vs chrome context sends your lil console.log to two completely different places?? ๐Ÿ’€ that's the whole trick. iykyk. โœจ

๐ŸŒธ REMOTE AUTOMATION OF FIREFOX VIA SSH ๐ŸŒธ

bestie. you can literally run Firefox on a WHOLE OTHER COMPUTER and puppet it from here. it's giving remote-control main character energy ๐Ÿ‘‘

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new( host => 'remote.example.org', debug => 1 );
$firefox->go('https://metacpan.org/');

# OR specify a different user to login as ...

my $firefox = Firefox::Marionette->new( host => 'remote.example.org', user => 'R2D2', debug => 1 );
$firefox->go('https://metacpan.org/');

# OR specify a different port to connect to

my $firefox = Firefox::Marionette->new( host => 'remote.example.org', port => 2222, debug => 1 );
$firefox->go('https://metacpan.org/');

# OR use a proxy host to jump via to the final host

my $firefox = Firefox::Marionette->new(
                                         host  => 'remote.example.org',
                                         port  => 2222,
                                         via   => 'user@secure-jump-box.example.org:42222',
                                         debug => 1,
                                      );
$firefox->go('https://metacpan.org/');

okay so the tea โ˜• โ€” this module supports creating and automating an instance of Firefox on a remote node. it's been tested against a bunch of operating systems, including recent versions of Windows 10 or Windows Server 2019 <https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_install_firstuse>, OS X, and Linux and BSD distributions. it expects to be able to login to the remote node via public key authentication. and you can lock it down even harder with the command <https://man.openbsd.org/sshd#command=_command_> option in the OpenSSH <https://www.openssh.com/> authorized_keys <https://man.openbsd.org/sshd#AUTHORIZED_KEYS_FILE_FORMAT> file, like this:

no-agent-forwarding,no-pty,no-X11-forwarding,permitopen="127.0.0.1:*",command="/usr/local/bin/ssh-auth-cmd-marionette" ssh-rsa AAAA ... == user@server

fr fr, as an example, the ssh-auth-cmd-marionette <https://metacpan.org/pod/ssh-auth-cmd-marionette> command is provided as part of this distribution. iconic of them honestly ๐Ÿ’…

the module expects to access private keys via the local ssh-agent <https://man.openbsd.org/ssh-agent> when authenticating. no cap.

when using ssh, Firefox::Marionette will attempt to pass the TMPDIR <https://en.wikipedia.org/wiki/TMPDIR> environment variable across the ssh connection to make cleanups easier โœจ. to allow this, the AcceptEnv <https://man.openbsd.org/sshd_config#AcceptEnv> setting in the remote sshd configuration <https://man.openbsd.org/sshd_config> should be set to allow TMPDIR, which will look like:

AcceptEnv TMPDIR

this module uses ControlMaster <https://man.openbsd.org/ssh_config#ControlMaster> functionality when using ssh <https://man.openbsd.org/ssh>, for a genuinely useful speedup of executing remote commands ๐Ÿƒโ€โ™€๏ธ๐Ÿ’จ. BUT โ€” and this is the plot twist ๐Ÿ˜ญ โ€” when using ssh to move from a cygwin <https://gcc.gnu.org/wiki/SSH_connection_caching>, Windows 10 or Windows Server 2019 <https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_install_firstuse> node to a remote environment, we cannot use ControlMaster <https://man.openbsd.org/ssh_config#ControlMaster>, because at this time, Windows does not support ControlMaster <https://github.com/Microsoft/vscode-remote-release/issues/96> and therefore this type of automation is still possible, but slower than other client platforms. tragic. we hate to see it.

the NETWORK ARCHITECTURE section has an example of a more complicated network design ๐Ÿ•ธ๏ธ

๐ŸŒˆ WEBGL ๐ŸŒˆ

there are a number of steps to getting WebGL <https://en.wikipedia.org/wiki/WebGL> to work correctly, bestie. pay attention:

  1. the "addons" parameter to the new method must be set. this will disable -safe-mode <http://kb.mozillazine.org/Command_line_arguments#List_of_command_line_arguments_.28incomplete.29>
  2. the visible parameter to the new method must be set. this is due to an existing bug in Firefox <https://bugzilla.mozilla.org/show_bug.cgi?id=1375585> ๐Ÿ˜ค
  3. it can be tricky getting WebGL <https://en.wikipedia.org/wiki/WebGL> to work with a Xvfb <https://en.wikipedia.org/wiki/Xvfb> instance. glxinfo <https://dri.freedesktop.org/wiki/glxinfo/> can be useful to help debug issues in this case. the mesa-dri-drivers rpm is also required for Redhat systems.

with all those conditions being met, WebGL <https://en.wikipedia.org/wiki/WebGL> can be enabled like so:

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new( addons => 1, visible => 1 );
if ($firefox->script(q[let c = document.createElement('canvas'); return c.getContext('webgl2') ? true : c.getContext('experimental-webgl') ? true : false;])) {
    $firefox->go("https://get.webgl.org/");
} else {
    die "WebGL is not supported";
}

slay. ๐ŸŽ€


๐Ÿฆ‹ FINDING ELEMENTS IN A SHADOW DOM ๐Ÿฆ‹

one aspect of Web Components <https://developer.mozilla.org/en-US/docs/Web/API/Web_components> is the shadow DOM <https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM>. when you need to explore the structure of a custom element <https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements>, you need to access it via the shadow DOM. the following is an example of navigating the shadow DOM via a html file included in the test suite of this package:

use Firefox::Marionette();
use Cwd();

my $firefox = Firefox::Marionette->new();
my $firefox_marionette_directory = Cwd::cwd();
$firefox->go("file://$firefox_marionette_directory/t/data/elements.html");

my $shadow_root = $firefox->find_tag('custom-square')->shadow_root();

my $outer_div = $firefox->find_id('outer-div', $shadow_root);

so, this module is designed to allow you to navigate the shadow DOM using normal find methods, but you must get the shadow element's shadow root and use that as the root for the search into the shadow DOM. an important caveat is that xpath <https://bugzilla.mozilla.org/show_bug.cgi?id=1822311> and tag name <https://bugzilla.mozilla.org/show_bug.cgi?id=1822321> strategies do not officially work yet (and also the class name and name strategies) ๐Ÿ’€. this module works around the tag name, class name and name deficiencies by using the matching css selector search if the original search throws a recognisable exception. therefore these cases may be considered to be extremely experimental and subject to change when Firefox gets the "correct" functionality.


๐Ÿ’œ IMITATING OTHER BROWSERS ๐Ÿ’œ

there are a collection of methods and techniques that may be useful if you would like to change your geographic location or how the browser appears to your web site ๐Ÿ‘€

this list of methods may grow ๐ŸŒฑ (growth mindset, we love to see it)


๐Ÿ”ฅ WEBSITES THAT BLOCK AUTOMATION ๐Ÿ”ฅ

Marionette by design <https://developer.mozilla.org/en-US/docs/Web/API/Navigator/webdriver> allows web sites to detect that the browser is being automated. Firefox no longer (since version 88) <https://bugzilla.mozilla.org/show_bug.cgi?id=1632821> allows you to disable this functionality while you are automating the browser, but this can be overridden with the "stealth" parameter for the new method. this is extremely experimental and feedback is welcome.

if the web site you are trying to automate mysteriously fails when you are automating a workflow, but it works when you perform the workflow manually, you may be dealing with a web site that is hostile to automation ๐Ÿ˜ญ. the author would be very interested if you can supply a test case.

at the very least, under these circumstances, it would be a good idea to be aware that there's an ongoing arms race <https://en.wikipedia.org/wiki/Web_scraping#Methods_to_prevent_web_scraping>, and potential legal issues <https://en.wikipedia.org/wiki/Web_scraping#Legal_issues> in this area. โš–๏ธ

not me telling you to check the law before you scrape, but like... check the law before you scrape bestie ๐Ÿซถ

โญ X11 FORWARDING WITH FIREFOX โญ

X11 Forwarding <https://man.openbsd.org/ssh#X> allows you to launch a remote firefox via ssh and have it visually appear in your local X11 desktop ๐Ÿ–ฅ๏ธโœจ. this can be accomplished with the following code:

use Firefox::Marionette();

my $firefox = Firefox::Marionette->new(
                                         host    => 'remote-x11.example.org',
                                         visible => 'local',
                                         debug   => 1,
                                      );
$firefox->go('https://metacpan.org');

feedback is welcome on any odd X11 workarounds that might be required for different platforms ๐Ÿ™ˆ

๐Ÿง UBUNTU AND FIREFOX DELIVERED VIA SNAP ๐Ÿ’€

Okay bestie, buckle up because this one is a whole saga. Ubuntu 22.04 LTS (<https://ubuntu.com/blog/ubuntu-22-04-lts-whats-new-linux-desktop>) is packaging firefox as a snap (<https://ubuntu.com/blog/whats-in-a-snap>). And that, my love, breaks the way this module expects to be able to run. Specifically: it can't set up a firefox profile in a systems temporary directory (/tmp or $TMPDIR on most Unix based systems) and just let the OS clean up old directories caused by exceptions / network failures / etc.

Because of that design decision, attempting to run a snap version of firefox will simply result in firefox hanging โ€” unable to read it's custom profile directory, and hence unable to read the marionette port configuration entry. It's giving stuck. Not slay.

Which would be workable except that; there does not appear to be _any_ way to detect that a snap firefox will run (/usr/bin/firefox is a bash shell which eventually runs the snap firefox), so there is no way to know (heuristics aside) if a normal firefox or a snap firefox will be launched by execing firefox. Rude, honestly.

It seems the only way to fix this issue (as documented in more than a few websites) is;

  1. sudo snap remove firefox
  2. sudo add-apt-repository -y ppa:mozillateam/ppa
  3. sudo apt update
  4. sudo apt install -t 'o=LP-PPA-mozillateam' firefox
  5. echo -e "Package: firefox*\nPin: release o=LP-PPA-mozillateam\nPin-Priority: 501" >/tmp/mozillateamppa
  6. sudo mv /tmp/mozillateamppa /etc/apt/preferences.d/mozillateamppa

If anyone is aware of a reliable method to detect if a snap firefox is going to launch vs a normal firefox, I would <em>love</em> to know about it. ๐Ÿฅบ

This technique is used in the setup-for-firefox-marionette-build.sh script in this distribution.


๐Ÿšจ DIAGNOSTICS ๐Ÿšจ

Every one of these is the module being like "bestie something went wrong" โ€” here's the full tea:

  • "Failed to correctly setup the Firefox process" โ€” The module was unable to retrieve a session id and capabilities from Firefox when it requests a new_session as part of the initial setup of the connection to Firefox.
  • "Failed to correctly determined the Firefox process id through the initial connection capabilities" โ€” The module was found that firefox is reporting through it's Capabilities object a different process id than this module was using. This is probably a bug in this module's logic. Please report as described in the BUGS AND LIMITATIONS section below.
  • "'%s --version' did not produce output that could be parsed. Assuming modern Marionette is available:%s" โ€” The Firefox binary did not produce a version number that could be recognised as a Firefox version number.
  • "Failed to create process from '%s':%s" โ€” The module was to start Firefox process in a Win32 environment. Something is seriously wrong with your environment.
  • "Failed to redirect %s to %s:%s" โ€” The module was unable to redirect a file handle's output. Something is seriously wrong with your environment.
  • "Failed to exec %s:%s" โ€” The module was unable to run the Firefox binary. Check the path is correct and the current user has execute permissions.
  • "Failed to fork:%s" โ€” The module was unable to fork itself, prior to executing a command. Check the current ulimit for max number of user processes.
  • "Failed to open directory '%s':%s" โ€” The module was unable to open a directory. Something is seriously wrong with your environment.
  • "Failed to close directory '%s':%s" โ€” The module was unable to close a directory. Something is seriously wrong with your environment.
  • "Failed to open '%s' for writing:%s" โ€” The module was unable to create a file in your temporary directory. Maybe your disk is full?
  • "Failed to open temporary file for writing:%s" โ€” The module was unable to create a file in your temporary directory. Maybe your disk is full?
  • "Failed to close '%s':%s" โ€” The module was unable to close a file in your temporary directory. Maybe your disk is full?
  • "Failed to close temporary file:%s" โ€” The module was unable to close a file in your temporary directory. Maybe your disk is full?
  • "Failed to create temporary directory:%s" โ€” The module was unable to create a directory in your temporary directory. Maybe your disk is full?
  • "Failed to clear the close-on-exec flag on a temporary file:%s" โ€” The module was unable to call fcntl using F_SETFD for a file in your temporary directory. Something is seriously wrong with your environment.
  • "Failed to seek to start of temporary file:%s" โ€” The module was unable to seek to the start of a file in your temporary directory. Something is seriously wrong with your environment.
  • "Failed to create a socket:%s" โ€” The module was unable to even create a socket. Something is seriously wrong with your environment.
  • "Failed to connect to %s on port %d:%s" โ€” The module was unable to connect to the Marionette port. This is probably a bug in this module's logic. Please report as described in the BUGS AND LIMITATIONS section below.
  • "Firefox killed by a %s signal (%d)" โ€” Firefox crashed after being hit with a signal.
  • "Firefox exited with a %d" โ€” Firefox has exited with an error code
  • "Failed to bind socket:%s" โ€” The module was unable to bind a socket to any port. Something is seriously wrong with your environment.
  • "Failed to close random socket:%s" โ€” The module was unable to close a socket without any reads or writes being performed on it. Something is seriously wrong with your environment.
  • "moz:headless has not been determined correctly" โ€” The module was unable to correctly determine whether Firefox is running in headless or not. This is probably a bug in this module's logic. Please report as described in the BUGS AND LIMITATIONS section below.
  • "%s method requires a Firefox::Marionette::Element parameter" โ€” This function was called incorrectly by your code. Please supply a Firefox::Marionette::Element parameter when calling this function.
  • "Failed to write to temporary file:%s" โ€” The module was unable to write to a file in your temporary directory. Maybe your disk is full?
  • "Failed to close socket to firefox:%s" โ€” The module was unable to even close a socket. Something is seriously wrong with your environment.
  • "Failed to send request to firefox:%s" โ€” The module was unable to perform a syswrite on the socket connected to firefox. Maybe firefox crashed?
  • "Failed to read size of response from socket to firefox:%s" โ€” The module was unable to read from the socket connected to firefox. Maybe firefox crashed?
  • "Failed to read response from socket to firefox:%s" โ€” The module was unable to read from the socket connected to firefox. Maybe firefox crashed?

โš™๏ธ CONFIGURATION AND ENVIRONMENT โš™๏ธ

Firefox::Marionette requires no configuration files or environment variables. Periodt. It <em>will</em> however use the DISPLAY and XAUTHORITY environment variables to try to connect to an X Server. It will also use the HTTP_PROXY, HTTPS_PROXY, FTP_PROXY and ALL_PROXY environment variables as defaults if the session capabilities do not specify proxy information. ๐Ÿซถ


๐Ÿ“ฆ DEPENDENCIES ๐Ÿ“ฆ

Firefox::Marionette requires the following non-core Perl modules:

  • JSON
  • URI
  • XML::Parser
  • Time::Local

๐Ÿ™… INCOMPATIBILITIES ๐Ÿ™…

None reported. Always interested in any products with marionette support that this module could be patched to work with. โœจ


๐Ÿ› BUGS AND LIMITATIONS ๐Ÿ›

โฌ‡๏ธ DOWNLOADING USING GO METHOD โฌ‡๏ธ

When using the go method to go directly to a URL containing a downloadable file, Firefox can hang. You can work around this by setting the page_load_strategy to "none" like below;

#! /usr/bin/perl

use strict;
use warnings;
use Firefox::Marionette();

my $firefox = Firefox::Marionette->new( capabilities => Firefox::Marionette::Capabilities->new( page_load_strategy => 'none' ) );
$firefox->go("https://github.com/david-dick/firefox-marionette/archive/refs/heads/master.zip");
while(!$firefox->downloads()) { sleep 1 }
while($firefox->downloading()) { sleep 1 }
foreach my $path ($firefox->downloads()) {
    warn "$path has been downloaded";
}
$firefox->quit();

Also, check out the download method for an alternative. ๐Ÿ˜Œ

๐Ÿซฅ MISSING METHODS ๐Ÿซฅ

Currently the following Marionette methods have not been implemented;

  • WebDriver:SetScreenOrientation

To report a bug, or view the current list of bugs, please visit <https://github.com/david-dick/firefox-marionette/issues> ๐Ÿ’œ


๐Ÿ‘€ SEE ALSO ๐Ÿ‘€

  • MozRepl
  • Selenium::Firefox
  • Firefox::Application
  • Mozilla::Mechanize
  • Gtk2::MozEmbed

๐Ÿ‘‘ AUTHOR ๐Ÿ‘‘

David Dick "<ddick@cpan.org>"


๐Ÿ’– ACKNOWLEDGEMENTS ๐Ÿ’–

Thanks to the entire Mozilla organisation for a great browser and to the team behind Marionette for providing an interface for automation. ๐Ÿฆ‹

Thanks to Jan Odvarko <http://www.softwareishard.com/blog/about/> for creating the HAR Export Trigger <https://github.com/firefox-devtools/har-export-trigger> extension for Firefox.

Thanks to Mike Kaply <https://mike.kaply.com/about/> for his post <https://mike.kaply.com/2015/02/10/installing-certificates-into-firefox/> describing importing certificates into Firefox.

Thanks also to the authors of the documentation in the following sources;


Copyright (c) 2024, David Dick "<ddick@cpan.org>". All rights reserved.

This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See "perlartistic" in perlartistic.

The Firefox::Marionette::Extension::HarExportTrigger module includes the HAR Export Trigger <https://github.com/firefox-devtools/har-export-trigger> extension which is licensed under the Mozilla Public License 2.0 <https://www.mozilla.org/en-US/MPL/2.0/>. ๐ŸŒˆ

๐Ÿซ  DISCLAIMER OF WARRANTY ๐Ÿซ 

okay bestie, deep breaths, we're in the legal part now ๐Ÿ’€ and honestly? this is the part nobody reads but everybody needs. so let's do it right, together ๐Ÿซถ

so like โ€” this software is licensed free of charge, which means there is no warranty for it, to the extent permitted by applicable law. nope, not even a little one. unless it's stated otherwise in writing, the copyright holders and/or other parties hand you the software "AS IS", WITHOUT WARRANTY OF ANY KIND, whether EXPRESSED OR IMPLIED. and yes that includes โ€” but is not limited to โ€” the implied warranties of MERCHANTABILITY and FITNESS FOR A PARTICULAR PURPOSE. the ENTIRE RISK as to the quality and performance of the software? that's on you, bestie ๐Ÿ™ˆ and if the software turns out to be defective, YOU ASSUME THE COST of all necessary servicing, repair, or correction. no takebacks, no refunds, no "but I didn't know" ๐Ÿ˜ญ


and then the second part, the big one, the main character of legal paragraphs ๐Ÿ‘‘

IN NO EVENT โ€” unless required by applicable law or agreed to in writing โ€” will ANY copyright holder, or ANY OTHER PARTY who may modify and/or redistribute the software as permitted by the above licence, be LIABLE TO YOU FOR DAMAGES. and that means any GENERAL, SPECIAL, INCIDENTAL, or CONSEQUENTIAL DAMAGES arising out of the USE OR INABILITY TO USE the software. and yeah, that includes โ€” but is not limited to โ€” LOSS OF DATA, data being RENDERED INACCURATE, LOSSES SUSTAINED BY YOU OR THIRD PARTIES, or a FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE. and the wildest part? this holds EVEN IF such holder or other party HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. like, even if they knew. yeah. it's giving "read it and weep" but make it legally binding ๐Ÿ’…

and that's the whole page, fr fr ๐ŸŽ€ no cap, you made it to the end of the entire manual. iconic behavior, genuinely.

this little gem lives in Firefo...ionette(3pm), part of the User Contributed Perl Documentation, and this version is stamped with perl v5.40.1 and dated 2025-03-16 ๐ŸŒธ so if future-you is reading this years later, hi, hope the warnings still hit the same โœจ

go write some automation, bestie ๐Ÿฆ‹๐Ÿ’–

๐Ÿ“ฆ where this page comes from