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.