WebDriver2
WebDriver level 2 bindings implementing
W3C's specification.
Current implementation status is
documented below.
Usage
Using a driver directly
To use a driver directly for driver-level endpoint commands, request a WD2::Component::Driver
with Provider.get-driver: $browser, :$port. The test class
will need to specify the browser and port upon instantiation:
use WD2;
use WD2::Component::Driver;
my WD2::Component::Driver:D $driver =
Provider.get-driver: 'chrome', port => 9515;
Most commands are Session or Element endpoints, though:
use WD2::Component::Session;
my WD2::Component::Session:D $session =
$driver.new-session: %optional-capabilities-options;
$session.navigate-to: $url-as-Str; # can be file path or web address
If no capabilities are given, the minimum, empty default will be supplied: { capabilities => { } }. Please see specification(s) for capability availability and format.
Some Element endpoints:
use WD2::Component::Element;
use WD2::Locators;
my WD2::Component::Element:D $element =
$session.find-element: By.id: 'identifier';
$element.click;
In addition to locating Elements by ID, the standard locators are available:
$element = $session.find-element: By.tag: 'input';
$element = $session.find-element: By.css: 'body > div.head';
# also By.link-text, By.partial-link-text, By.xpath
See below for status.
When finished:
$session.delete;
Convenience Methods and Routines, Test Template
Methods
Some Session and Element convenience methods have been provided that are not part of the WebDriver2 specification. They are listed below at the end of the implementation status section.
Wait Routines
Since waiting for a condition to be true before moving to the next step is useful, several routines that poll state have also been provided:
# relevant imports and declarations completed above
use WD2::Wait::Common :ALL;
my &wait-present = present $session, $locator, duration => 5, interval => 1/10, :soft;
# do something...
# then wait for 5 seconds, polling every .1 second,
# for an element to appear; don't throw an exception if it doesn't
&wait-present();
my WD2::Component::Element $element =
$session.find-element: By.id: 'gets-removed';
my &wait-stale = stale $element;
# do something...
# then wait for the element to be removed using default values;
# throw Timeout exception if it isn't
&wait-stale();
my WD2::Component::Element $updatable =
$session.find-element: By.id: 'updatable';
my WD2::Component::Element $input =
$session.find-element: By.id: 'text-input';
my WD2::Component::Element $updater =
$session.find-element: By.tag: 'button';
my &wait-updated = text-to-be $updatable, 'new text';
$input.send-keys: 'new text';
$updater.click;
&wait-updated();
List with implementation status given below the endpoints table.
WD2::Test::Template
Test classes can implement the WD2::Test::Template role to avoid some boilerplate.
The example test included with the distribution is explained here.
From xt/lib/Example.rakumod:
use WD2::Test::Template;
class Example does WD2::Test::Template {
my IO::Path:D $html-file =
$*PROGRAM.parent.sibling( 'content' ).add: 'test.html';
has Str:D $.name = 'example';
has Str:D $.description = 'example test description';
has Int:D $.plan = 1;
# included so that when run as part of the test suite,
# it will skip testing if the user has not requested
# driver testing. This Should Be Omitted for normal
# user tests.
method init {
if %*ENV<DRIVER_TESTING> {
self.WD2::Test::Template::init;
} else {
self.diag: 'DRIVER_TESTING was not set';
self.pass: 'DRIVER_TESTING was not set';
self.done-testing;
exit;
}
}
method test {
$!session.navigate-to: 'file://' ~ $html-file.absolute;
self.is: 'title', 'test', $!session.title;
}
}
Implementing classes need to provide a test name and description and override the test method. The self.is: ... is provided by the WD2::Test::Adapter role via WD2::Test::Template (no additional import necessary). That and the related methods delegate to the Test routines but will run a handler if a test fails. The default handler takes a screenshot when called (see below for details and how to suppress this behavior). Note that the order of arguments are the reverse of the Test routines. This leaves the final position available for calculating the actual value.
Once such a class is written, it can be used in a test script.
From xt/05-test-template/example.rakutest:
use lib <lib xt/lib>;
use WD2::Test::Template;
use Example;
constant &MAIN = driver-test Example;
The script is just a wrapper that runs the test class. The driver-test sub is from the
WD2::Test::Template compunit (hence the first import). It takes the test class and
returns a sub suitable for use as a MAIN. The options provided are:
| Str $browser? | if none is provided and there is a browser file in the CWD,
its value will be read and used. otherwise the test will fail |
| Str:D :$host = '127.0.0.1' | |
| Int:D :$port = 9515 | 9515 was the default for chromedriver and edgedriver. it will likely need to be supplied when
using firefox or safari, or if chromedriver or edgedriver is using a random port. the port can
also be set when starting the driver (as opposed to or
in addition to the script) |
| IO::Path(Str:D) :$test-root = 'xt'.IO | currently unused |
| Int:D :$close-delay = 3 | if set negative, the session will be left open when the script completes or if there is
a fatal exception (except failed Session creation - in which case there will be no session to leave open).
If the session is left open, the session-id will be given on STDOUT so that it can
be used to close the session gracefully later. E.g., by using the provided
bin/close-session.raku script:
close-session --host=127.0.0.1 --port=9515 <browser>(required) <session-id>(required) |
| Bool:D :$no-auto-ss = False | By default, in addition to screenshots Tests explicitly request, screenshots are taken
anytime there is a failure (if using the provided WD2::Test::Adapter methods) or exception.
set to suppress this behavior |
| Str:D :$debug(:$debug-level) = 'WARN' | valid values: OFF, ERR, WARN, Info, trace, extra. debugging output has not been
incorporated, yet |
In addition, any extra named arguments will be passed to the test class, so that instance variables can be built
from them.
TODO
- cover all implemented endpoints with unit tests
- add Rakudoc
- browser support
- implement the rest of the endpoints
Feedback
Suggestions, design recommendations, and feature requests
welcome.
Implementation Status
- "" - Planned
- NYI - will throw exception
- I - Implemented
- ✓ - Implemented and tested
| | Windows | Windows / Linux | MacOS | |
|---|
| new session | I | ✓ (W), I (L) | | | $driver.new-session: { capabilities => { ... } } |
| delete session | I | ✓ (W), I (L) | | | $session.delete |
| status | I | I | | | $driver.status |
| get timeouts | I | I | | | $session.get-timeouts |
| set timeouts | I | I | | | $session.set-timeouts: Int $script, Int $page-load, Int $implicit |
| navigate to | I | ✓ (W), I (L) | | | $session.navigate-to: Str $url |
| get current url | I | I | | | $session.current-url |
| back | I | I | | | $session.back |
| forward | I | I | | | $session.forward |
| refresh | I | I | | | $session.refresh |
| get title | I | ✓ (W), I (L) | | | $session.title |
| get window handle | I | I | | | $session.get-window-handle |
| close window | I | I | | | $session.close-window |
| switch to window | I | I | | | $session.switch-to-window: $handle |
| get window handles | I | I | | | $session.window-handles |
| new window | I | I | | | $session.new-window: Str $type? where <tab window>.any |
| switch to frame | I | ✓ (W), I (L) | | | $session.switch-to: Int $frame-id
$frame-element.switch-to |
| switch to parent frame | I | I | | | $session.switch-to-parent-frame |
| get window rect | | | | | $session.get-window-rect |
| set window rect | I | I | | | $session.set-window-rect: Int $width, Int $height, Int $x, Int $y |
| maximize window | I | I | | | $session.maximize-window |
| minimize window | I | I | | | $session.minimize-window |
| fullscreen window | I | I | | | $session.fullscreen-window |
| get active element | I | I | | | $session.active-element |
| get element shadow root | I | I | | | $element.shadow-root |
| find element | I | ✓ (W), I (L) | | | $session.find-element: By $locator |
| find elements | I | I | | | $session.find-elements: By $locator |
| find element from element | I | I | | | $element.find-element: By $locator |
| find elements from element | I | I | | | $element.find-elements: By $locator |
| find element from shadow root | I | I | | | $shadow-root.find-element: By $locator |
| find elements from shadow root | I | I | | | $shadow-root.find-elements: By $locator |
| is element selected | I | I | | | $element.is-element-selected |
| get element attribute | I | ✓ (W), I (L) | | | $element.attribute: Str $name |
| get element property | I | I | | | $element.property: Str $name |
| get element css value | I | I | | | $element.css-value: Str $css-prop |
| get element text | I | I | | | $element.text |
| get element tag name | I | I | | | $element.tag-name |
| get element rect | I | I | | | $element.rect |
| is element enabled | I | I | | | $element.is-enabled |
| get computed role | I | I | | | $element.computed-role |
| get computed label | I | I | | | $element.computed-label |
| element click | I | I | | | $element.click |
| element clear | I | I | | | $element.clear |
| element send keys | I | I | | | $element.send-keys: Str $text |
| get page source | I | I | | | $session.page-source |
| execute script | I | I | | | $session.execute-script: Str $scr, @args |
| execute async script | I | I | | | $session.execute-async-script: Str $scr, @args |
| get all cookies | I | I | | | $session.get-all-cookies |
| get named cookie | I | I | | | $session.get-named-cookie: Str $name |
| add cookie | I | I | | | $session.add-cookie: %cookie-speckeys: name*
value*
path
domain
secure
httpOnly
expiry
sameSite* required |
| delete cookie | I | I | | | $session.delete-cookie: Str $name |
| delete all cookies | I | I | | | $session.delete-all-cookies |
| perform actions | NYI | NYI | NYI | NYI | $session.perform-actions |
| release actions | NYI | NYI | NYI | NYI | $session.release-actions |
| dismiss alert | I | I | | | $session.dismiss-alert |
| accept alert | I | ✓ (W), I (L) | | | $session.accept-alert |
| get alert text | I | ✓ (W), I (L) | | | $session.alert-text |
| send alert text | I | I | | | $session.send-alert-text: Str $text |
| take screenshot | I | I | | | $session.take-screenshot |
| take element screenshot | I | I | | | $element.take-element-screenshot |
| print page | I | I | | | $session.print-page |
| is-displayed ( optional endpoint ) | I | I | | | $element.is-displayed |
| present ( convenience method - not spec'd ) | I | I | | | $session.present: By $locator; $element.present: By $locator |
| id ( convenience method - not spec'd ) | I | I | | | $element.id |
| top ( convenience method - not spec'd ) | I | I | | | $session.top |
| switch-to ( convenience method - not spec'd ) | I | I | | | $frame-element.switch-to |
| select ( convenience method - not spec'd ) | I | I | | | $select-element.select: Str $option-text |
| selected-option ( convenience method - not spec'd ) | I | I | | | $select-element.selected-option |
| selected-value ( convenience method - not spec'd ) | I | I | | | $select-element.selected-value |
Locator Status
- "" - Planned
- I - Implemented
- ✓ - Passing
| locator | status |
|---|
| By.id | ✓ |
| By.tag | ✓ |
| By.css | ✓ |
| By.xpath | ✓ |
| By.link-text | ✓ |
| By.partial-link-text | ✓ |
Wait Status
- "" - Planned
- I - Implemented
- ✓ - Passing
| routine | status | import with | notes |
|---|
| base-wait | ✓ | use WD2::Wait :base | base wait routine. can be used to wait for arbitrary conditions |
| basic-op | I | use WD2::Wait :base | basic wait - use operation's return value directly for truthiness |
| basic-true | I | use WD2::Wait :basic | wait for identically True value |
| basic-so-true | I | use WD2::Wait :basic | wait for truthiness |
| basic-to-true | I | use WD2::Wait :basic | wait for falsiness and alter return value to be the opposite Bool value |
| basic-eq | I | use WD2::Wait :basic | wait for a specific value using eq |
| basic-equals | I | use WD2::Wait :basic | wait for a specific value using == |
| basic-accepts | I | use WD2::Wait :basic | wait for a specific value using ~~ |
| throwable | I | use WD2::Wait :throw | return (non-Falure) Exception, otherwise, the return value. used to build waits but is not one itself |
| expect-throw | I | use WD2::Wait :throw | $throwable-return.isa: Exception ?? False !! $result but True. used to build waits but is not one itself |
| expect-throw-type | I | use WD2::Wait :throw | rethrow wrong type; return the type if it was expected; otherwise, return Error-Code (falsy). used to build waits but is not one itself |
| no-throw | I | use WD2::Wait :throw | $throwable-return.isa: Exception ?? $throwable-return but False !! $throwable-return. used to build waits but is not one itself |
| no-throw-type | I | use WD2::Wait :throw | rethrow anything unexpected; return the expected type but False. used to build waits but is not one itself |
| present | ✓ | use WD2::Wait::Common :presence | |
| absent | ✓ | use WD2::Wait::Common :presence | |
| stale | ✓ | use WD2::Wait::Common :presence | |
| displayed | ✓ | use WD2::Wait::Common :presence | |
| hidden | ✓ | use WD2::Wait::Common :presence | |
| value-not-empty | I | use WD2::Wait::Common :value | |
| value-to-eq | I | use WD2::Wait::Common :value | |
| value-to-be | I | use WD2::Wait::Common :value | |
| text-to-be | I | use WD2::Wait::Common :value | |
| title-to-be | I | use WD2::Wait::Common :value | |