Rand Stats

WebDriver2

zef:zjmarlow

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 Duration:D $duration = Duration.new: 5;
	my Duration:D $interval = Duration.new: 1/10;
	my &wait-present = present $session, $locator, :$duration, :$interval, :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
	my WD2::Component::Element $element = wait-present;
	
	$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;
	
	use WD2::Locators;
	use WD2::Wait::Common :presence;
	
	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;
			
			my Duration:D $duration = Duration.new: 5;
			my Duration:D $interval = Duration.new: 1/10;
			my By:D $locator = By.id: 'DNE';
			my &wait-present = present $!session, $locator, :$duration, :$interval, :soft;
			
			self.lives-ok: 'lives but False for element that DNE', {
				my WD2::Component::Element $element = wait-present;
				self.nok: 'no element found', $element;
			}
		}
	}

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 (template role is the only 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:

optionnotes
Str $browser?if none is provided and there is a browser file in the test root (default ./xt), its value will be read and used. otherwise the test will fail
Str:D :$host = '127.0.0.1'
Int:D :$port = 95159515 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'.IOdetermines where to look for the browser file, which is used when the browser is not specified on the command line
Int:D :$close-delay = 3if 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 = FalseBy 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

Feedback

Suggestions, design recommendations, and feature requests welcome.

Safari Quirks

Implementation Status

 WindowsWindows / LinuxMacOS 
endpointedgechromefirefoxsafarimethod
new session✓✓✓✓$driver.new-session: { capabilities => { ... } }
delete session✓✓✓✓$session.delete
statusIIII$driver.status
get timeoutsIIII$session.get-timeouts
set timeoutsIIII$session.set-timeouts: Int $script, Int $page-load, Int $implicit
navigate to✓✓✓✓$session.navigate-to: Str $url
get current urlIIII$session.current-url
backIIII$session.back
forwardIIII$session.forward
refreshIIII$session.refresh
get title✓✓✓✓$session.title
get window handleIIII$session.get-window-handle
close windowIIII$session.close-window
switch to windowIIII$session.switch-to-window: $handle
get window handlesIIII$session.window-handles
new windowIIII$session.new-window: Str $type? where <tab window>.any
switch to frame✓✓✓I$session.switch-to: Int $frame-id $frame-element.switch-to
switch to parent frameIIII$session.switch-to-parent-frame
get window rectIIII$session.get-window-rect
set window rectIIII$session.set-window-rect: Int $width, Int $height, Int $x, Int $y
maximize windowIIII$session.maximize-window
minimize windowIIII$session.minimize-window
fullscreen windowIIII$session.fullscreen-window
get active elementIIII$session.active-element
get element shadow rootIIII$element.shadow-root
find element✓✓✓✓$session.find-element: By $locator
find elements✓✓✓✓$session.find-elements: By $locator
find element from element✓✓✓✓$element.find-element: By $locator
find elements from element✓✓✓✓$element.find-elements: By $locator
find element from shadow rootIIII$shadow-root.find-element: By $locator
find elements from shadow rootIIII$shadow-root.find-elements: By $locator
is element selectedIIII$element.is-element-selected
get element attribute✓✓✓✓$element.attribute: Str $name
get element property✓✓✓✓$element.property: Str $name
get element css valueIIII$element.css-value: Str $css-prop
get element text✓✓✓✓$element.text
get element tag name✓✓✓✓$element.tag-name
get element rect✓✓✓I$element.rect
is element enabled✓✓✓I$element.is-enabled
get computed roleIIII$element.computed-role
get computed labelIIII$element.computed-label
element click✓✓✓✓$element.click
element clear✓✓✓✓$element.clear
element send keys✓✓✓✓$element.send-keys: Str $text
get page sourceIIII$session.page-source
execute scriptIIII$session.execute-script: Str $scr, @args
execute async scriptIIII$session.execute-async-script: Str $scr, @args
get all cookiesIIII$session.get-all-cookies
get named cookieIIII$session.get-named-cookie: Str $name
add cookieIIII$session.add-cookie: %cookie-spec

keys:

name* value* path domain secure httpOnly expiry sameSite

* required

delete cookieIIII$session.delete-cookie: Str $name
delete all cookiesIIII$session.delete-all-cookies
perform actionsIIII$session.perform-actions
release actionsIIII$session.release-actions
dismiss alertIIII$session.dismiss-alert
accept alert✓✓✓✓$session.accept-alert
get alert text✓✓✓✓$session.alert-text
send alert textIIII$session.send-alert-text: Str $text
take screenshotIIII$session.take-screenshot
take element screenshotIIII$element.take-element-screenshot
print pageIIII$session.print-page
is-displayed ( optional endpoint )IIIX$element.is-displayed
present ( convenience method - not spec'd )IIII$session.present: By $locator; $element.present: By $locator
id ( convenience method - not spec'd )IIII$element.id
top ( convenience method - not spec'd )IIII$session.top
switch-to ( convenience method - not spec'd )IIII$frame-element.switch-to
select ( convenience method - not spec'd )IIII$select-element.select: Str $option-text
selected-option ( convenience method - not spec'd )IIII$select-element.selected-option
selected-value ( convenience method - not spec'd )IIII$select-element.selected-value

Locator Status

locatorstatus
By.id✓
By.tag✓
By.css✓
By.xpath✓
By.link-text✓
By.partial-link-text✓

Wait Status

routinestatusimport withnotes
base-wait✓use WD2::Wait :basebase wait routine. can be used to wait for arbitrary conditions
basic-opIuse WD2::Wait :basebasic wait - use operation's return value directly for truthiness
basic-trueIuse WD2::Wait :basicwait for identically True value
basic-so-trueIuse WD2::Wait :basicwait for truthiness
basic-to-trueIuse WD2::Wait :basicwait for falsiness and alter return value to be the opposite Bool value
basic-eqIuse WD2::Wait :basicwait for a specific value using eq
basic-equalsIuse WD2::Wait :basicwait for a specific value using ==
basic-acceptsIuse WD2::Wait :basicwait for a specific value using ~~
throwableIuse WD2::Wait :throwreturn (non-Falure) Exception, otherwise, the return value. used to build waits but is not one itself
expect-throwIuse WD2::Wait :throw$throwable-return.isa: Exception ?? False !! $result but True. used to build waits but is not one itself
expect-throw-typeIuse WD2::Wait :throwrethrow wrong type; return the type if it was expected; otherwise, return Error-Code (falsy). used to build waits but is not one itself
no-throwIuse WD2::Wait :throw$throwable-return.isa: Exception ?? $throwable-return but False !! $throwable-return. used to build waits but is not one itself
no-throw-typeIuse WD2::Wait :throwrethrow 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-emptyIuse WD2::Wait::Common :value
value-to-eqIuse WD2::Wait::Common :value
value-to-beIuse WD2::Wait::Common :value
text-to-beIuse WD2::Wait::Common :value
title-to-beIuse WD2::Wait::Common :value
alert✓use WD2::Wait::Common :alertreturn is alert text