-
Notifications
You must be signed in to change notification settings - Fork 7
Components
A Component is a JavaScript file containing a hash whose enclosing curly braces will be automatically added and containing entries that can be of two types: elements or actions.
Component files have to end with Component.js
.
It is good practice to name your components after the frontend components they model:
SearchBarComponent.js
,TreeSelectorComponent.js
…
A component element maps a testing-space name to a DOM element and, as such, contains all needed information to retrieve one in a web page.
The simplest way to target a DOM element is through a CSS selector. If you want to retrieve an element through a CSS selector, simply associate the element name to its selector:
// example/DuckDuckGo/SearchBarComponent.js
field: '[name=q]',
submitButton: '#search_button_homepage'
Here, two elements have been defined: field
and submitButton
, both through CSS selectors. When these elements are accessed in a scenario, the selected element will be retrieved from the currently-active webpage. If they are not present, actions on them will fail (unless of course you were testing for their absence).
To use another location strategy than CSS, simply map the component element name to a single-key hash mapping the locator type to its selector.
For example, the two definitions above could as well have been written:
// example/DuckDuckGo/SearchBarComponent.js
field: { name: 'q' },
submitButton: { id: 'search_button_homepage' }
All WebDriver locator types are supported, and are also aliased to shorter versions:
xpath
-
partial link text
(aliased toa
) -
link text
(aliased tolinkText
) id
name
-
class name
(aliased toclass
) -
tag name
(aliased totag
) -
css selector
(aliased tocss
)
Straight strings are actually simply expanded to
{ css: <value> }
.
If you are faced with a complex (or poorly automatically) generated DOM, your best option is probably to use XPath.
A component action maps a testing-space name to a JavaScript function, usually combining simple interaction steps on elements to model a user behavior.
It is usually good practice to group elements at the top of a component declaration, add an empty line and then group actions.
Some actions are generated magically on the declared elements. All generated actions are asynchronous and return Q
promises.
All elements have a magic set<ElementName>
associated action generated, which takes the keystroke sequence to send to that element as a parameter.
// SearchBarComponent.js
field: 'input[name=q]'
// in `1 - SearchScenario.js`
description: 'Search for something',
steps: [
SearchBarComponent.setField('something') // automatically generated
// sends the 's-o-m-e-t-h-i-n-g' keystroke sequence
// after having focused the `field` element
]
Setters either reject their returned promise if the element on which they are applied can't be found, or fulfill it with the element on which they are applied.
You won't use the return value unless in a custom action.
Some element naming conventions trigger the generation of associated clicking actions:
// SearchBarComponent.js
submitButton: '#search_button_homepage'
// in `1 - SearchScenario.js`
description: 'Search for something',
steps: [
SearchBarComponent.submit() // automatically generated
// clicks the `submitButton` element
]
An element named <elementName><typeSuffix>
will automatically augment its declaring component with an <elementName>
action if <typeSuffix>
is one of:
- "Link"
- "Button"
- "Checkbox"
- "Option"
- "Radio"
Such actions return a promise which will either be fulfilled, or rejected if the element could not be found or clicked.
Custom actions are functions you write in the component description. They usually return a promise whose fulfillment state will be used to consider the step as passed or failed. They can also be synchronous, in which case they should throw to fail as a step.
// ClockComponent.js
getCurrentHour: function() {
return this.result.then(function(resultElement) {
return resultElement.text();
}).then(function(text) {
return text.split(':')[0]; // get the hour only
});
}
As you can see, actions can access elements in the same component as they are declared by using the this
keyword.
An important thing to note is that, in this case, the element accessor returns a promise for the element, not the element straight away. You must chain it with then
to a function that will be executed once the element is available, and which will be passed a reference to the wd
object representing it, with all wd
's API available. If the element is not found on the page, the promise will be rejected with an error describing the reason why the element could not be accessed.
Magic actions are also available for you to call on this
component:
// example/DuckDuckGo/SearchBarComponent.js
field: '[name=q]',
submitButton: '#search_button_homepage',
searchFor: function searchFor(term) {
return this.setField(term)()
.then(this.submit());
}
These actions also return promises.
Look out for the double call: the first component action in a chain in other component actions have to be called twice (once with their actual parameters, another without any param).
This limitation is due to the wrapping that is injected for scenarios. It is known and will be fixed in an upcoming release. You can track progress in issue #99.
Another important thing is that your function may get called several times in a row if it gets rejected, until the timeout is exhausted.
This is what allows Watai to be very resilient against varying load times, but it means you should (of course) avoid global mutable state.