For DOM based testing most of the functions from Sahi tests can be used (please note that Sakuli only implements the open source APIs).
The main difference between Sakuli v1 and Sakuli v2 is the usage of
Promises in the action
API, meaning that you have to await
a click for example.
On the other hand, element selectors remain synchronized functions but will not do the actual DOM fetching anymore.
While an expression like var $e=_link('Sakuli')
did an actual DOM-access in Sakuli v1.x, it returns a kind of abstract
query for an element now. So, action can fetch this element whenever it is required.
A detailed list of all available functions can be found in the Legacy API interface,
Sakuli uses the concept of reusable queries rather than directly working on an element-object (like in Selenium).
Sakuli offers an expressive set of accessors like _div
, _textbox
or _table
. These accessors will not return an
actual element or any reference to it. Rather it will create a query. This query can then be used in various
Actions like _click
, _highlight
or _isVisible
. This concept could be compared with
Locators in Selenium.
This architecture gives us two nice benefits:
Most accessors are defined in the same way: They are functions that take anAccessorIdentifier as a first parameter and a variadic list of Relations:
_NAME(identifier, ...relations): SahiElementQuery
The accessor adds a static Locator to the returned query. Since
a query object consists of a locator, an identifier and a list of relations, we will eventually get an entire query object. The locator basically is a CSS element selector which you would expect from the accessor name - so _div
for example adds By.css('div')
, _textbox
adds By.css('input[type="text"], input:not([type])')
and so on.
HTML-Attribute | Accessor Function |
---|---|
<[HTML - tag] class='[ class name ]'></[HTML - tag]> |
_byClassName |
<[HTML - tag] id='[ id name ]'></[HTML - tag]> |
_byID |
Since Sakuli encapsulates the creation (through accessors) and the application (e.g. through actions) of a query, a user will rarely get in touch with these objects directly. Nevertheless, it is good to understand how Sakuli works with queries. Let us consider this example:
await _click(_button('Sign In'));
The following will happen under the hood:
_button
creates a query with a locator to a button element and with 'Sign In'
as an identifier and an empty list of relations
This query is passed to the _click
action. This action does the following things:
The identifer is another relict from Sahi that can be one of the following types:
Type | Effect |
---|---|
number |
The identifier is considered as index. Sakuli picks the element at this index (zero-based) in step 2.3 |
RegExp |
Tests this RegExp against the following attributes of each element in the list at step 2.1: [aria-describedby] , [name] , [id] , className , innerText , value , src |
string |
The string is normalized and wrapped into a RegExp, therefore the same logic as for RegExp is applied |
Since we mostly apply the logic of Sahi comparisons against the class attribute are pretty dumb. While the attribute value is semantically a space separated list of class names. It is just handled as a usual string in Sahi (and therefore also in Sakuli so far).
Actions usually invoke a Selenium action sequence with an activated bridge mode to cover compatibility to most webdriver implementations. An action accepts a ElementQuery or a WebElement and tries to perform the action on this element several times. This approach reduces the count of StaleElementReferenceErrors dramatically, especially when a query is used.
Beside the fact that actions work asynchronously now, they behave like in Sahi. One exception is the _eval
method, which accepts a string now containing some JavaScript code, which is performed on the website by the webdriver implementation (see executeAsyncScript
method of
Seleniums Thenablewebdriver).
const windowOuterHeight = await _eval(`return window.outerHeight`)
These methods are useful to get deeper access to elements and element-attributes:
const [x,y] = await _position(_image('funny-cat-image.png'));
or let you perform checks (e.g. if an element exists).
if(await _exists(_div('cookie-banner'))) {
await _click(_button('I agree'))
}
List of fetch functions:
List of relations:
List of assertion functions:
Since Sakuli uses Seleniums webdriver it also provides various ways to access the functionality of this backend.
It is recommended to use Sakulis built-in functionalities rather than work with the driver instances or any WebElement directly. At the moment, Sakuli is built upon Selenium. Nevertheless, a switch to other technologies in the future is possible. Downwards compatibility is only possible for Sakulis built-in functionalities. Direct use of webdriver instance methods is not supported.
Sakuli test scripts provide a globally accessible object of the current WebDriver instance which can be used to invoke its native methods directly. This might be useful for switching between frames:
await driver.switchTo().frame(1);
await _click(_div('element-in-frame-1'));
await driver.switchTo().defaultContent();
The Fetch API provides the _fetch
function which returns the native
WebElement
instance from Seleniums webdriver for a query:
const webElement = await _fetch(_image('funny-cat-image.png'));
const {width, height} = await webElement.getRect();