Firefo...ionette(3pm)
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;
pathโ a file system path to a single PEM encoded X.509 certificate athttps://datatracker.ietf.org/doc/html/rfc7468#section-5๐stringโ a string containing a single PEM encoded X.509 certificate athttps://datatracker.ietf.org/doc/html/rfc7468#section-5๐งตtrustโ This is the trustargs value for NSS (https://www.mankier.com/1/certutil#-tandhttps://wiki.mozilla.org/NSS). If defaults to'C,,'๐
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);
๐ add_cookie
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 noactionattribute (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, theWWW-Authenticateheader includes a realm. See RFC 2617 athttps://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โ Thenameattribute for the username input in a form. Non-form logins should not specify this field. โ๏ธpassword_fieldโ Thenameattribute 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;
has_resident_keyโ boolean value to indicate if the authenticator will support client side discoverable credentials (https://www.w3.org/TR/webauthn-2/#client-side-discoverable-credential) ๐has_user_verificationโ boolean value to determine if the authenticator (https://www.w3.org/TR/webauthn-2/#virtual-authenticators) supports user verification (https://www.w3.org/TR/webauthn-2/#user-verification). โis_user_consentingโ boolean value to determine the result of all user consent (https://www.w3.org/TR/webauthn-2/#user-consent) authorization gestures (https://www.w3.org/TR/webauthn-2/#authorization-gesture), and by extension, any test of user presence (https://www.w3.org/TR/webauthn-2/#test-of-user-presence) performed on the Virtual Authenticator (https://www.w3.org/TR/webauthn-2/#virtual-authenticators). If set to true, a user consent (https://www.w3.org/TR/webauthn-2/#user-consent) will always be granted. If set to false, it will not be granted. ๐is_user_verifiedโ boolean value to determine the result of User Verification (https://www.w3.org/TR/webauthn-2/#user-verification) performed on the Virtual Authenticator (https://www.w3.org/TR/webauthn-2/#virtual-authenticators). If set to true, User Verification (https://www.w3.org/TR/webauthn-2/#user-verification) will always succeed. If set to false, it will fail. ๐ชชprotocolโ the protocol spoken by the authenticator. This may beCTAP1_U2F,CTAP2orCTAP2_1. ๐ฃ๏ธtransportโ the transport simulated by the authenticator. This may beBLE,HYBRID,INTERNAL,NFC,SMART_CARDorUSB. ๐ก
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 ๐)
authenticatorโ the authenticator your shiny new credential is getting added to. If you don't pass this, the credential lands in the default authenticator, assuming one even exists. ๐hostโ the domain this credential is meant for. WebAuthn girlies call this the relying party identifier or the RP ID (https://www.w3.org/TR/webauthn-2/#relying-party-identifier, https://www.w3.org/TR/webauthn-2/#rp-id).idโ the unique id for this credential, aka the Credential ID (https://www.w3.org/TR/webauthn-2/#credential-id). Not supplying one? Bestie, it'll just generate one for you. โจis_residentโ a boolean, and it's giving โจchoicesโจ. Set it true and a client-side discoverable credential gets created (https://w3c.github.io/webauthn/#client-side-discoverable-credential). Set it false and you get a server-side credential instead (https://w3c.github.io/webauthn/#server-side-credential).private_keyโ either a private key encoded per RFC5958 (https://www.rfc-editor.org/rfc/rfc5958) and encoded usingencode_base64url, or a hash containing these keys;nameโ the name of the private key algorithm, such as"RSA-PSS"(the default),"RSASSA-PKCS1-v1_5","ECDSA"or"ECDH".sizeโ the modulus length of the private key. Only valid for"RSA-PSS"or"RSASSA-PKCS1-v1_5"private keys. ๐ซถhashโ the name of the hash algorithm, such as"SHA-512"(the default). Only valid for"RSA-PSS"or"RSASSA-PKCS1-v1_5"private keys.curveโ the name of the curve for the private key, such as"P-384"(the default). Only valid for"ECDSA"or"ECDH"private keys.
sign_countโ the initial value for a signature counter (https://w3c.github.io/webauthn/#signature-counter) associated to the public key credential source (https://w3c.github.io/webauthn/#public-key-credential-source). It defaults to0(zero), no cap.userโ the userHandle (https://w3c.github.io/webauthn/#public-key-credential-source-userhandle) associated to the credential, encoded usingencode_base64url. This property is optional, so no pressure. ๐ฆ
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.overridegeneral.oscpu.overridegeneral.platform.overridenetwork.http.acceptnetwork.http.accept-encodingnetwork.http.accept-encoding.secureprivacy.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 as120.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;
- https://browserleaks.com/javascript
- https://www.amiunique.org/fingerprint
- https://bot.sannysoft.com/
- https://lraj22.github.io/browserfeatcl/
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 anddelete_cookiesis not subtle. this one nukes every cookie for every host. if you wanted the polite, current-webpage-only version, that'sdelete_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. ๐ซถ
delete_cookie ๐
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 returnsundefinstead of making a whole scene? see thehasmethod.
๐ 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 theundef-instead-of-drama behaviour, see thehas_idmethod.
๐ 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 ๐. theundef-returning twin is thehas_namemethod.
๐จ 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 returnsundefif no elements are found, see thehas_classmethod.
๐ 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 ๐ญ. wantundefinstead? that's thehas_selectormethod.
๐ท๏ธ 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 returnsundefif no elements are found, see thehas_tagmethod.
๐ find_link
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 returnsundefif no elements are found, see thehas_linkmethod.
๐ 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 returnsundefif no elements are found, see thehas_partialmethod.
โญ๏ธ 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 includeabout:blankor similar. therefore, you will need to load a page before calling thegeomethod. 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.
has_link ๐๐
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;
- img (
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img) - image inputs (
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/image)
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;
- path to an xpi file (
https://developer.mozilla.org/en-US/docs/Mozilla/XPI). - path to a directory containing firefox extension source code (
https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Your_first_WebExtension). this directory will be packaged up as an unsigned xpi file. - path to a top level file (such as
manifest.jsonhttps://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Anatomy_of_a_WebExtension#manifest.json) in a directory containing firefox extension source code (https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Your_first_WebExtension). this directory will be packaged up as an unsigned xpi file.
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, oroption<https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option> elements living inside aselect<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:
- Bitwarden CSV <https://bitwarden.com/help/article/condition-bitwarden-import/>
- LastPass CSV <https://support.logmeininc.com/lastpass/help/how-do-i-nbsp-export-stored-data-from-lastpass-using-a-generic-csv-file>
- KeePass CSV <https://keepass.info/help/base/importexport.html#csv>
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:
- KeePass 1.x XML <https://keepass.info/help/base/importexport.html#xml>
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:
- 1Password Unencrypted Export format <https://support.1password.com/1pux-format/>
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);
}
๐ links
returns a list of all of these elements:
- anchor <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a>
- area <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/area>
- frame <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/frame>
- iframe <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe>
- meta <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta>
...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 to0.
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 tofirefox.settings.services.mozilla.comwill still be made <https://bugzilla.mozilla.org/show_bug.cgi?id=1598562#c13>. the only way to prevent this seems to be to setfirefox.settings.services.mozilla.comto127.0.0.1via/etc/hosts<https://en.wikipedia.org/wiki//etc/hosts>.
NOTE: that this option only works whenprofile_name/profileis 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 viaSTDERR? This defaults to"0". Any ssh connections will also get printed toSTDERR. And yeah, this also defaults to"0"(off). You can change this setting later with thedebugmethod. But here's the tea: if this option isn't a boolean (0|1), the value gets handed off to theMOZ_LOGoption (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 thejsonmethod, and the response gets used to build aGeoLocationobject, which then gets sent to thegeomethod. If the value for this key is a hash, that hash is used to build aGeoLocationobject, which gets sent to thegeomethod. ๐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 thingshostโ 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 theuserparameter if you wanna change that). Authentication should be via public keys loaded into the localssh-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 thecapabilitiesparameter. 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 thesurviveparameter.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 thehostparameter 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 thepage_loadtimeout instead of having to use timeouts from thecapabilitiesparameter. 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 theset_prefandclear_prefmethods 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 theFirefox::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 to0
- the preference
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 thecapabilitiesparameter 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 thesurviveparameter. ๐งชscpโ force thescpprotocol when transferring files to remote hosts via ssh. See REMOTE AUTOMATION OF FIREFOX VIA SSH and the--scp-onlyoption in thessh-auth-cmd-marionette(see <https://metacpan.org/pod/ssh-auth-cmd-marionette>) script in this distribution.scriptโ a shortcut so you can directly provide thescripttimeout instead of having to use timeouts from thecapabilitiesparameter. 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 whenprofile_name/profileis not specified. ๐ฎsleep_time_in_msโ the amount of time (in milliseconds) that this module should sleep when unsuccessfully calling the subroutine provided to theawaitorbyemethods. This defaults to"1"millisecond. ๐ดstealthโ stopsnavigator.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 thenewmethod. 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 thereconnectparameter 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 thecapabilitiesparameter. Overrides the timeouts provided (if any) in thecapabilitiesparameter. โฐ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 to0. ๐ต๏ธ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 localssh-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 thehostparameter. ๐visibleโ should firefox be visible on the desktop. This defaults to"0". When moving from an X11 platform to another X11 platform, you can setvisibleto'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 โธ๏ธ.
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 tofalse๐จ๏ธ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 to1(~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 tofalse.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 to1."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 thepagekey for an alternative and thepaper_sizesmethod 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 totrue.
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:
- performing a maximise, then
- caching the browser's current width and height as the maximum width and height. It
- then calls
resizeTo<https://developer.mozilla.org/en-US/docs/Web/API/Window/resizeTo> to resize the window to0,0 - wait for the browser to send a resize <https://developer.mozilla.org/en-US/docs/Web/API/Window/resize_event> event.
- cache the browser's current width and height as the minimum width and height
- if the requested width and height are outside of the maximum and minimum widths and heights return
false - if the requested width and height matches the current width and height return itself to aid in chaining methods. Otherwise,
- call
resizeTo<https://developer.mozilla.org/en-US/docs/Web/API/Window/resizeTo> for the requested width and height - 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"isfalse. 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 itselffullโ 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 suppliedhighlightsโ 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
jsonmethod if you're dealing with response content types such asapplication/jsonโจ - peep
htmlif 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,
deltaXdefaults to0. - deltaY โ the change in Y co-ordinates during the wheel. if not defined,
deltaYdefaults to0.
๐ช 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:
- the
"addons"parameter to thenewmethod must be set. this will disable-safe-mode<http://kb.mozillazine.org/Command_line_arguments#List_of_command_line_arguments_.28incomplete.29> - the
visibleparameter to thenewmethod must be set. this is due to an existing bug in Firefox <https://bugzilla.mozilla.org/show_bug.cgi?id=1375585> ๐ค - 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-driversrpm 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 ๐
- the
"stealth"parameter of thenewmethod. this method will stop the browser reporting itself as a robot and will also (when combined with theagentmethod) change other javascript characteristics to match the User Agent <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent> string. - the
agentmethod, which if supplied a recognisable User Agent <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent>, will attempt to change other attributes to match the desired browser. this is extremely experimental and feedback is welcome ๐ - the
geomethod, which allows the modification of the Geolocation <https://developer.mozilla.org/en-US/docs/Web/API/Geolocation> reported by the browser, but not the location produced by mapping the external IP address used by the browser (see the NETWORK ARCHITECTURE section for a discussion of different types of proxies that can be used to change your external IP address). - the
languagesmethod, which can change the requested languages <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language> for your browser session. - the
tzmethod, which can change the timezone <https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List> for your browser session.
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;
sudo snap remove firefoxsudo add-apt-repository -y ppa:mozillateam/ppasudo apt updatesudo apt install -t 'o=LP-PPA-mozillateam' firefoxecho -e "Package: firefox*\nPin: release o=LP-PPA-mozillateam\nPin-Priority: 501" >/tmp/mozillateamppasudo 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 anew_sessionas 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 currentulimitfor 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 callfcntlusingF_SETFDfor 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 inheadlessor 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 aFirefox::Marionette::Elementparameter 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 asyswriteon 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:
JSONURIXML::ParserTime::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 ๐
MozReplSelenium::FirefoxFirefox::ApplicationMozilla::MechanizeGtk2::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;
- Marionette Protocol <https://firefox-source-docs.mozilla.org/testing/marionette/marionette/index.html>
- Marionette driver.js <https://hg.mozilla.org/mozilla-central/file/tip/remote/marionette/driver.sys.mjs>
- about:config <http://kb.mozillazine.org/About:config_entries>
- nsIPrefService interface <https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIPrefService>
โ๏ธ LICENSE AND COPYRIGHT โ๏ธ
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
- debian trixie: libfirefox-marionette-perl 1.63-1 ยท Artistic-1.0-Perl OR GPL-1.0-or-later
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms