Home > Blockchain >  Selenium Perl handle shadow DOM
Selenium Perl handle shadow DOM

Time:09-20

There are plenty examples using Selenium Python to handle shadow DOM. I'd like to do the same in Perl.

Perl's Selenium::Remote::Driver doesn't have shadow DOM support, but I should be able to do it through JavaScript. I got my inspiration from accessing-shadow-dom-tree-with-selenium.

The following is my code in Perl

#!/usr/bin/env perl

use Selenium::Chrome;

my $driver = Selenium::Chrome->new (
   startup_timeout => 60, 
   custom_args => "--log-path=/tmp/selenium_chromedriver",
   logfile => "/tmp/selenium_chromedriver2", 
   debug_on => 1,
   extra_capabilities => {
      'goog:chromeOptions' => {
         args => [
            '--no-sandbox',
            '--disable-dev-shm-usage', 
            '--window-size=1260,720',
            '--user-data-dir=/tmp/selenium_chrome',
         ],  
      },  
   },  
);

$driver->get("chrome-search://local-ntp/local-ntp.html"); # chrome new tab
my $shadow_host = $driver->find_element("html/body/ntp-app", "xpath");
my $shadow_root = $driver->execute_script('return arguments[0].shadowRoot', $shadow_host);

for my $e ( @{$shadow_root->find_elements(':host > *', 'css')} ) { 
   # error: Can't call method "find_elements" on unblessed reference
   print "found\n";
}

$driver->shutdown_binary();

But I got error: Can't call method "find_elements" on unblessed reference.

How can I overcome this error?

Thank you for any help.

  • My environment is: ubuntu 18, Perl 5.26, Selenium:Chrome 1.46, Chrome 99, chromedriver 99.
  • The same mechanism is tested working with Python 3.8.5.
  • Why I am not using Python? because the server in my work place only has Perl, no Python 3.

CodePudding user response:

the following code works

#!/usr/bin/env perl

use Selenium::Chrome;

my $driver = Selenium::Chrome->new (
   startup_timeout => 60,
   custom_args => "--log-path=/tmp/selenium_chromedriver",
   logfile => "/tmp/selenium_chromedriver2",
   debug_on => 1,
   extra_capabilities => {
      'goog:chromeOptions' => {
         args => [
            '--no-sandbox',
            '--disable-dev-shm-usage',
            '--window-size=1260,720',
            '--user-data-dir=/tmp/selenium_chrome',
         ],
      },
   },
);

$driver->get("chrome-search://local-ntp/local-ntp.html"); # chrome new tab
my $shadow_host = $driver->find_element("html/body/ntp-app", "xpath");

package MyShadow {
   sub new {
      my ($class, %attrs) = @_;
      my $shadow_root = $attrs{driver}->execute_script('return arguments[0].shadowRoot', $attrs{shadow_host});
      return undef if ! $shadow_root;
      $attrs{shadow_root} = $shadow_root;
      bless \%attrs, $class;
   }
   sub find_element {
      my ($self, $target, $scheme) = @_;
      die "scheme=$scheme is not supported. Only css is supported" if $scheme ne 'css';
      return $self->{driver}->execute_script(
                 "return arguments[0].querySelector(arguments[1])",
                 $self->{shadow_root},
                 $target
             );
   }
   sub find_elements {
      my ($self, $target, $scheme) = @_;
      die "scheme=$scheme is not supported. Only css is supported" if $scheme ne 'css';
      return $self->{driver}->execute_script(
                "return arguments[0].querySelectorAll(arguments[1])",
                $self->{shadow_root},
                $target
      );
   }
};

my $shadow_driver = MyShadow->new(driver=>$driver, shadow_host=>$shadow_host);
if ($shadow_driver) {
   for my $e ( @{$shadow_driver->find_elements(':host > *', 'css')} ) {
      print "found\n";
   }
}

$driver->shutdown_binary();

Key points:

  • For Selenium, no matter Python or Perl, they are wrappers to javascript. As long as you get correct javascript, you can do whatever you want.

  • For Shadow driver, all you need to implement is the find_element() and find_elements().

  • I only implemented 'css', no 'xpath', because that's what Python does as of 2022/09/19.

  • Related