view release on metacpan or search on metacpan
See IMITATING OTHER BROWSERS a discussion of these types of techniques.
These changes are not foolproof, but it is 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.
alert_text
Returns the message shown in a currently displayed modal message box
alive
This method returns true or false depending on if the Firefox process
is still running.
application_type
returns the application type for the Marionette protocol. Should be
'gecko'.
arch
- [https://lraj22.github.io/browserfeatcl/](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](#imitating-other-browsers) a discussion of these types of techniques. These changes are not foolproof, but it is interesting to see what can be done with modern browsers. All this behaviour should be regarded as extre...
## alert\_text
Returns the message shown in a currently displayed modal message box
## alive
This method returns true or false depending on if the Firefox process is still running.
## 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.
lib/Firefox/Marionette.pm view on Meta::CPAN
}
}
return $port || _DEFAULT_PORT();
}
sub _reconnected {
my ($self) = @_;
return $self->{_reconnected};
}
sub _check_reconnecting_firefox_process_is_alive {
my ( $self, $pid ) = @_;
if ( $OSNAME eq 'MSWin32' ) {
if (
Win32::Process::Open(
my $process, $pid, _WIN32_PROCESS_INHERIT_FLAGS()
)
)
{
$self->{_win32_firefox_process} = $process;
return $pid;
lib/Firefox/Marionette.pm view on Meta::CPAN
$local_name_regex = qr/${local_name_regex}\w+/smx;
return $local_name_regex;
}
sub _get_local_reconnect_pid {
my ($self) = @_;
my $temp_directory = File::Spec->tmpdir();
my $temp_handle = DirHandle->new($temp_directory)
or Firefox::Marionette::Exception->throw(
"Failed to open directory '$temp_directory':$EXTENDED_OS_ERROR");
my $alive_pid;
my $local_name_regex = $self->_get_local_name_regex();
TEMP_DIR_LISTING: while ( my $tainted_entry = $temp_handle->read() ) {
next if ( $tainted_entry eq File::Spec->curdir() );
next if ( $tainted_entry eq File::Spec->updir() );
if ( $tainted_entry =~ /^($local_name_regex)$/smx ) {
my ($untainted_entry) = ($1);
my $possible_root_directory =
File::Spec->catfile( $temp_directory, $untainted_entry );
my $local_proxy = $self->_read_possible_proxy_path(
lib/Firefox/Marionette.pm view on Meta::CPAN
}
}
elsif ( $self->_binary() ) {
next TEMP_DIR_LISTING;
}
if ( ( defined $local_proxy->{firefox} )
&& ( $local_proxy->{firefox}->{pid} ) )
{
if (
my $check_pid =
$self->_check_reconnecting_firefox_process_is_alive(
$local_proxy->{firefox}->{pid}
)
)
{
$alive_pid = $check_pid;
}
else {
next TEMP_DIR_LISTING;
}
}
else {
next TEMP_DIR_LISTING;
}
if ( ( defined $local_proxy->{xvfb} )
&& ( defined $local_proxy->{xvfb}->{pid} )
lib/Firefox/Marionette.pm view on Meta::CPAN
$self->{_xvfb_pid} = $local_proxy->{xvfb}->{pid};
}
$self->{_initial_version} = $local_proxy->{firefox}->{version};
$self->{_root_directory} = $possible_root_directory;
$self->_setup_profile();
}
}
closedir $temp_handle
or Firefox::Marionette::Exception->throw(
"Failed to close directory '$temp_directory':$EXTENDED_OS_ERROR");
return $alive_pid;
}
sub _setup_profile {
my ($self) = @_;
if ( $self->{profile_name} ) {
$self->{_profile_directory} =
Firefox::Marionette::Profile->directory( $self->{profile_name} );
$self->{profile_path} =
File::Spec->catfile( $self->{_profile_directory}, 'prefs.js' );
}
lib/Firefox/Marionette.pm view on Meta::CPAN
push @other_processes, $process;
}
}
$self->{_other_win32_ssh_processes} = \@other_processes;
return;
}
sub _remote_process_running {
my ( $self, $remote_pid ) = @_;
my $now = time;
if ( ( defined $self->{last_remote_alive_status} )
&& ( $self->{last_remote_kill_time} >= $now ) )
{
return $self->{last_remote_alive_status};
}
$self->{last_remote_kill_time} = $now;
my $remote_uname = $self->_remote_uname();
if ( !defined $remote_uname ) {
return;
}
elsif ( $remote_uname eq 'MSWin32' ) {
return $self->_win32_remote_process_running($remote_pid);
}
else {
return $self->_generic_remote_process_running($remote_pid);
}
}
sub _win32_remote_process_running {
my ( $self, $remote_pid ) = @_;
my $binary = 'tasklist';
my @arguments = ( '/FI', q["PID eq ] . $remote_pid . q["] );
$self->{last_remote_alive_status} = 0;
foreach my $line ( split /\r?\n/smx, $self->execute( $binary, @arguments ) )
{
if ( $line =~ /^firefox[.]exe[ ]+(\d+)[ ]/smx ) {
if ( $1 == $remote_pid ) {
$self->{last_remote_alive_status} = 1;
}
}
}
return $self->{last_remote_alive_status};
}
sub _generic_remote_process_running {
my ( $self, $remote_pid ) = @_;
my $result = $self->_execute_via_ssh(
{ return_exit_status => 1 },
(
$self->_remote_uname() eq 'cygwin'
? ( '/bin/kill', '-W' )
: ('kill')
),
'-0',
$remote_pid
);
if ( $result == 0 ) {
$self->{last_remote_alive_status} = 1;
}
else {
$self->{last_remote_alive_status} = 0;
}
return $self->{last_remote_alive_status};
}
sub alive {
my ($self) = @_;
if ( $self->_adb() ) {
my $parameters;
my $binary = q[adb];
my @arguments =
( qw(-s), $self->_adb_serial(), qw(shell am stack list) );
my $handle =
$self->_get_local_handle_for_generic_command_output( $parameters,
$binary, @arguments );
my $quoted_package_name = quotemeta $self->_adb_package_name();
lib/Firefox/Marionette.pm view on Meta::CPAN
return $connected;
}
sub _setup_local_connection_to_firefox {
my ( $self, @arguments ) = @_;
my $host = _DEFAULT_HOST();
my $port;
my $socket;
my $sock_addr;
my $connected;
while ( ( !$connected ) && ( $self->alive() ) ) {
if ( $self->_adb() ) {
Firefox::Marionette::Exception->throw(
'TODO: Cannot connect to android yet. Patches welcome');
}
$socket = undef;
socket $socket,
$self->_using_unix_sockets_for_ssh_connection()
? Socket::PF_UNIX()
: Socket::PF_INET(), Socket::SOCK_STREAM(), 0
or Firefox::Marionette::Exception->throw(
lib/Firefox/Marionette.pm view on Meta::CPAN
$port ||= $self->_get_marionette_port_or_undef();
next if ( !defined $port );
$sock_addr ||= $self->_get_sock_addr( $host, $port );
next if ( !defined $sock_addr );
$connected =
$self->_network_connection_and_initial_read_from_marionette( $socket,
$sock_addr );
}
$self->_reap();
if ( ( $self->alive() ) && ($socket) ) {
}
else {
my $error_message =
$self->error_message()
? $self->error_message()
: q[Firefox was not launched];
Firefox::Marionette::Exception->throw($error_message);
}
return $socket;
}
lib/Firefox/Marionette.pm view on Meta::CPAN
my $message_id = $self->_new_message_id();
$self->_send_request(
[ _COMMAND(), $message_id, $self->_command('WebDriver:GetTitle') ] );
my $response = $self->_get_response($message_id);
return $self->_response_result_value($response);
}
sub quit {
my ( $self, $flags ) = @_;
my $ssh_local_directory = $self->ssh_local_directory();
if ( !$self->alive() ) {
my $socket = delete $self->{_socket};
if ($socket) {
close $socket
or Firefox::Marionette::Exception->throw(
"Failed to close socket to firefox:$EXTENDED_OS_ERROR");
}
$self->_terminate_xvfb();
}
elsif ( $self->_socket() ) {
eval {
lib/Firefox/Marionette.pm view on Meta::CPAN
my $encoder = JSON->new()->convert_blessed()->ascii();
if ( $self->debug() ) {
$encoder->canonical(1);
}
my $json = $encoder->encode($object);
my $length = length $json;
if ( $self->debug() ) {
warn ">> $length:$json\n";
}
my $result;
if ( $self->alive() ) {
$result = syswrite $self->_socket(), "$length:$json";
}
if ( !defined $result ) {
my $socket_error = $EXTENDED_OS_ERROR;
if ( $self->alive() ) {
Firefox::Marionette::Exception->throw(
"Failed to send request to firefox:$socket_error");
}
else {
my $error_message =
$self->error_message() ? $self->error_message() : q[];
Firefox::Marionette::Exception->throw($error_message);
}
}
return;
}
sub _handle_socket_read_failure {
my ($self) = @_;
my $socket_error = $EXTENDED_OS_ERROR;
if ( $self->alive() ) {
Firefox::Marionette::Exception->throw(
"Failed to read size of response from socket to firefox:$socket_error"
);
}
else {
my $error_message =
$self->error_message() ? $self->error_message() : q[];
Firefox::Marionette::Exception->throw($error_message);
}
return;
}
sub _read_from_socket {
my ($self) = @_;
my $number_of_bytes_in_response;
my $initial_buffer;
while ( ( !defined $number_of_bytes_in_response ) && ( $self->alive() ) ) {
my $number_of_bytes;
my $octet;
if ( $self->{_initial_octet_read_from_marionette_socket} ) {
$octet = delete $self->{_initial_octet_read_from_marionette_socket};
$number_of_bytes = length $octet;
}
else {
$number_of_bytes = sysread $self->_socket(), $octet, 1;
}
if ( defined $number_of_bytes ) {
lib/Firefox/Marionette.pm view on Meta::CPAN
($number_of_bytes_in_response) = ($1);
}
}
if ( !defined $self->{_initial_packet_size} ) {
$self->{_initial_packet_size} = $number_of_bytes_in_response;
}
my $number_of_bytes_already_read = 0;
my $json = q[];
while (( defined $number_of_bytes_in_response )
&& ( $number_of_bytes_already_read < $number_of_bytes_in_response )
&& ( $self->alive() ) )
{
my $number_of_bytes_read = sysread $self->_socket(), my $buffer,
$number_of_bytes_in_response - $number_of_bytes_already_read;
if ( defined $number_of_bytes_read ) {
$json .= $buffer;
$number_of_bytes_already_read += $number_of_bytes_read;
}
else {
my $socket_error = $EXTENDED_OS_ERROR;
if ( $self->alive() ) {
Firefox::Marionette::Exception->throw(
"Failed to read response from socket to firefox:$socket_error"
);
}
else {
my $error_message =
$self->error_message() ? $self->error_message() : q[];
Firefox::Marionette::Exception->throw($error_message);
}
}
lib/Firefox/Marionette.pm view on Meta::CPAN
if ( ( $self->debug() ) && ( defined $number_of_bytes_in_response ) ) {
warn "<< $number_of_bytes_in_response:$json\n";
}
return $self->_decode_json($json);
}
sub _decode_json {
my ( $self, $json ) = @_;
my $parameters;
eval { $parameters = JSON::decode_json($json); } or do {
if ( $self->alive() ) {
if ($EVAL_ERROR) {
chomp $EVAL_ERROR;
die "$EVAL_ERROR\n";
}
}
else {
my $error_message =
$self->error_message() ? $self->error_message() : q[];
Firefox::Marionette::Exception->throw($error_message);
}
lib/Firefox/Marionette.pm view on Meta::CPAN
=back
Importantly, this will break L<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 L<IMITATING OTHER BROWSERS|/IMITATING-OTHER-BROWSERS> a discussion of these types of techniques. These changes are not foolproof, but it is interesting to see what can be done with modern browsers. All this behaviour should be regarded as extre...
=head2 alert_text
Returns the message shown in a currently displayed modal message box
=head2 alive
This method returns true or false depending on if the Firefox process is still running.
=head2 application_type
returns the application type for the Marionette protocol. Should be 'gecko'.
=head2 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.
t/01-marionette.t view on Meta::CPAN
}
sub empty_port {
socket my $socket, Socket::PF_INET(), Socket::SOCK_STREAM(), 0 or die "Failed to create a socket:$!";
bind $socket, Socket::sockaddr_in( 0, Socket::INADDR_LOOPBACK() ) or die "Failed to bind socket:$!";
my $port = ( Socket::sockaddr_in( getsockname $socket ) )[0];
close $socket or die "Failed to close random socket:$!";
return $port;
}
sub process_alive {
my ($pid) = @_;
if ($^O eq 'MSWin32') {
if (Win32::Process::Open(my $process, $pid, 0)) {
$process->GetExitCode( my $exit_code );
if ( $exit_code == Win32::Process::STILL_ACTIVE() ) {
return 1;
}
} else {
return 0;
}
t/01-marionette.t view on Meta::CPAN
ok($timeouts->script() == $script_timeout, "\$timeouts->script() is $script_timeout");
ok($timeouts->implicit() == $implicit_timeout, "\$timeouts->implicit() is $implicit_timeout");
if ($major_version >= $min_stealth_version) {
TODO: {
local $TODO = "Some installations of firefox can default to webdriver being off"; # such as http://www.cpantesters.org/cpan/report/a0532bce-c32c-11ee-ae2f-883f6e8775ea (FreeBSD 14.0-STABLE) (BuildID 20240123011445)
my $webdriver = $firefox->script('return navigator.webdriver');
ok($webdriver, "navigator.webdriver returns true:" . (defined $webdriver ? $webdriver : q[undef]));
}
}
ok(!defined $firefox->child_error(), "Firefox does not have a value for child_error");
ok($firefox->alive(), "Firefox is still alive");
ok(not($firefox->script('window.open("about:blank", "_blank");')), "Opening new window to about:blank via 'window.open' script");
ok($firefox->close_current_window_handle(), "Closed new tab/window");
SKIP: {
if ($major_version < 55) {
skip("Deleting and re-creating sessions can hang firefox for old versions", 1);
}
ok($firefox->delete_session()->new_session(), "\$firefox->delete_session()->new_session() has cleared the old session and created a new session");
}
my $child_error = $firefox->quit();
if ($child_error != 0) {
diag("Firefox exited with a \$? of $child_error");
}
ok($child_error =~ /^\d+$/, "Firefox has closed with an integer exit status of " . $child_error);
if ($major_version < 50) {
$correct_exit_status = $child_error;
}
ok($firefox->child_error() == $child_error, "Firefox returns $child_error for the child error, matching the return value of quit():$child_error:" . $firefox->child_error());
ok(!$firefox->alive(), "Firefox is not still alive");
}
if ((!defined $major_version) || ($major_version < 40)) {
$profile->set_value('security.tls.version.max', 3);
}
$profile->set_value('browser.newtabpage.activity-stream.feeds.favicon', 'true');
$profile->set_value('browser.shell.shortcutFavicons', 'true');
$profile->set_value('browser.newtabpage.enabled', 'true');
$profile->set_value('browser.pagethumbnails.capturing_disabled', 'false', 0);
$profile->set_value('startup.homepage_welcome_url', 'false', 0);
t/01-marionette.t view on Meta::CPAN
}
if ($skip_message) {
skip($skip_message, 8);
}
ok($firefox, "Firefox has started in Marionette mode with as survivable");
my $capabilities = $firefox->capabilities();
ok((ref $capabilities) eq 'Firefox::Marionette::Capabilities', "\$firefox->capabilities() returns a Firefox::Marionette::Capabilities object");
my $firefox_pid = $capabilities->moz_process_id();
ok($firefox_pid, "Firefox process has a process id of $firefox_pid");
if (!$ENV{FIREFOX_HOST}) {
ok(process_alive($firefox_pid), "Can contact firefox process ($firefox_pid)");
}
$firefox = undef;
if (!$ENV{FIREFOX_HOST}) {
ok(process_alive($firefox_pid), "Can contact firefox process ($firefox_pid)");
}
($skip_message, $firefox) = start_firefox(0, debug => 1, reconnect => 1);
ok($firefox, "Firefox has reconnected in Marionette mode");
$capabilities = $firefox->capabilities();
ok($firefox_pid == $capabilities->moz_process_id(), "Firefox has the same process id");
$firefox = undef;
if (!$ENV{FIREFOX_HOST}) {
ok(!process_alive($firefox_pid), "Cannot contact firefox process ($firefox_pid)");
}
if ($ENV{FIREFOX_HOST}) {
if ($ENV{FIREFOX_BINARY}) {
skip("No profile testing when the FIREFOX_BINARY override is used", 6);
}
if (!$ENV{RELEASE_TESTING}) {
skip("No profile testing except for RELEASE_TESTING", 6);
}
if (($ENV{WATERFOX}) || ($ENV{WATERFOX_VIA_FIREFOX})) {
skip("No profile testing when any WATERFOX override is used", 6);
t/01-marionette.t view on Meta::CPAN
ok($firefox_pid, "Firefox process has a process id of $firefox_pid when using a profile_name");
my $child_error = $firefox->quit();
if ($child_error != 0) {
diag("Firefox exited with a \$? of $child_error");
}
ok($child_error =~ /^\d+$/, "Firefox has closed with an integer exit status of " . $child_error);
if ($major_version < 50) {
$correct_exit_status = $child_error;
}
ok($firefox->child_error() == $child_error, "Firefox returns $child_error for the child error, matching the return value of quit():$child_error:" . $firefox->child_error());
ok(!$firefox->alive(), "Firefox is not still alive");
} else {
if ($ENV{FIREFOX_BINARY}) {
skip("No profile testing when the FIREFOX_BINARY override is used", 6);
}
if (!$ENV{RELEASE_TESTING}) {
skip("No profile testing except for RELEASE_TESTING", 6);
}
if (($ENV{WATERFOX}) || ($ENV{WATERFOX_VIA_FIREFOX})) {
skip("No profile testing when any WATERFOX override is used", 6);
}
t/01-marionette.t view on Meta::CPAN
$at_least_one_success = 1;
}
if ($skip_message) {
skip($skip_message, 8);
}
ok($firefox, "Firefox has started in Marionette mode with as survivable with a profile_name and har");
my $capabilities = $firefox->capabilities();
ok((ref $capabilities) eq 'Firefox::Marionette::Capabilities', "\$firefox->capabilities() returns a Firefox::Marionette::Capabilities object");
my $firefox_pid = $capabilities->moz_process_id();
ok($firefox_pid, "Firefox process has a process id of $firefox_pid when using a profile_name");
ok(process_alive($firefox_pid), "Can contact firefox process ($firefox_pid) when using a profile_name");
$firefox = undef;
ok(process_alive($firefox_pid), "Can contact firefox process ($firefox_pid) when using a profile_name");
($skip_message, $firefox) = start_firefox(0, debug => 1, reconnect => 1, profile_name => $name);
ok($firefox, "Firefox has reconnected in Marionette mode when using a profile_name");
ok($firefox_pid == $capabilities->moz_process_id(), "Firefox has the same process id when using a profile_name");
$firefox = undef;
ok(!process_alive($firefox_pid), "Cannot contact firefox process ($firefox_pid)");
}
}
if ($^O eq 'MSWin32') {
} elsif ($ENV{RELEASE_TESTING}) {
eval {
$ca_cert_handle = File::Temp->new( TEMPLATE => File::Spec->catfile( File::Spec->tmpdir(), 'firefox_test_ca_cert_XXXXXXXXXXX')) or Firefox::Marionette::Exception->throw( "Failed to open temporary file for writing:$!");
fcntl $ca_cert_handle, Fcntl::F_SETFD(), 0 or Carp::croak("Can't clear close-on-exec flag on temporary file:$!");
$ca_private_key_handle = File::Temp->new( TEMPLATE => File::Spec->catfile( File::Spec->tmpdir(), 'firefox_test_ca_private_XXXXXXXXXXX')) or Firefox::Marionette::Exception->throw( "Failed to open temporary file for writing:$!");
system {'openssl'} 'openssl', 'genrsa', '-out' => $ca_private_key_handle->filename(), 4096 and Carp::croak("Failed to generate a private key:$!");
t/01-marionette.t view on Meta::CPAN
} elsif ($^O eq 'cygwin') {
SKIP: {
skip("Not testing dead firefox processes for cygwin", 2);
}
TODO: {
local $TODO = $correct_exit_status == 0 ? q[] : $capabilities->browser_version() . " is not exiting cleanly";
ok($firefox->quit() == $correct_exit_status, "Firefox has closed with an exit status of $correct_exit_status:" . $firefox->child_error());
}
} else {
my $xvfb_pid = $firefox->xvfb_pid();
while($firefox->alive()) {
diag("Killing PID " . $capabilities->moz_process_id() . " with a signal " . $signals_by_name{TERM});
sleep 1;
kill $signals_by_name{TERM}, $capabilities->moz_process_id();
sleep 1;
}
eval { $firefox->go('https://metacpan.org') };
chomp $@;
ok($@ =~ /Firefox[ ]killed[ ]by[ ]a[ ]TERM[ ]signal/smx, "Exception is thrown when a command is issued to a dead firefox process:$@");
eval { $firefox->go('https://metacpan.org') };
chomp $@;
t/author/bulk_test.pl view on Meta::CPAN
}
return 1;
}
sub _win32_path {
my ($unix_path) = @_;
my $windows_path = join q[\\], split /[\/]/smx, $unix_path;
return $windows_path;
}
sub _check_parent_alive {
if (!kill 0, $parent_pid) {
die "Parent ($parent_pid) is no longer running. Terminating\n";
}
}
sub _sleep_until_shutdown {
my ($server) = @_;
while (_virsh_node_running($server)) {
_virsh_shutdown($server);
_log_stderr($server, "Waiting for $server->{name} to shutdown");
t/author/bulk_test.pl view on Meta::CPAN
print {*STDERR} _prefix($server) . "$message\n" or die "Failed to print to STDERR:$EXTENDED_OS_ERROR";
}
sub _log_stdout {
my ($server, $message) = @_;
print _prefix($server) . "$message\n" or die "Failed to print to STDOUT:$EXTENDED_OS_ERROR";
}
sub _contents {
my ($server, $parameters, $command, @arguments) = @_;
_check_parent_alive();
my @lines;
my $return_result;
my $handle = FileHandle->new();
if (my $pid = $handle->open(q[-|])) {
my $alarm_method;
my $alarm_killed;
if ($parameters->{alarm_after}) {
_log_stderr($server, "Alarm is $parameters->{alarm_after} seconds");
alarm $parameters->{alarm_after};
$alarm_method = sub {
t/author/bulk_test.pl view on Meta::CPAN
_log_stderr($server, "Killed local process $pid after $parameters->{alarm_after} seconds at " . localtime);
$alarm_killed = 1;
};
}
local $SIG{ALRM} = $alarm_method;
COMMAND: while(my $line = <$handle>) {
$line =~ s/\r?\n$//smx;
$line =~ s/\e\[(K|\d+;1H|\??25[lh]|2J|[mHG]|23X|17X)//smxg;
$line =~ s/\e\]0;//smxg;
$line =~ s/\x7//smxg;
_check_parent_alive();
_log_stdout($server, $line);
push @lines, $line;
if ($alarm_killed) {
last COMMAND;
}
}
if (!$alarm_killed) {
my $result = close $handle;
if ($result == 1) {
$return_result = 0;
t/test_daemons.pm view on Meta::CPAN
http {
client_body_temp_path $temp_directories{client_body_temp};
proxy_temp_path $temp_directories{proxy_temp};
fastcgi_temp_path $temp_directories{fastcgi_temp};
uwsgi_temp_path $temp_directories{uwsgi_temp};
scgi_temp_path $temp_directories{scgi_temp};
access_log logs/access.log;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 4096;
types {
text/html html;
text/javascript js;
text/css css;
application/json json;
}
default_type text/plain;