Skip to content
This repository has been archived by the owner on Apr 23, 2020. It is now read-only.

Scenarios

Matti Schneider edited this page Oct 7, 2015 · 2 revisions

Scenarios reference

A Scenario is a JavaScript file containing a hash whose enclosing curly braces will be automatically added and containing two keys: description and scenario.

File naming

Scenario files have to start with a numerical (positive integer) index, and end with Scenario.js.

Prefix indices are needed for Watai to determine dependencies. Currently, there is no support for complex dependencies, so your test suite has to be a linear scenario. To make that scenario explicit, scenario filenames have to be prefixed with their index.

Indices don't have to be continuous, i.e. you may have scenarios 1, 2, 3 and 5.

Best practice

Separate the names of your scenarios from their prefixes with a dash, such as 1 - LoginScenario.js, 2 - LogoutScenario.js.

You can quickly exclude a scenario from being evaluated by renaming it to something like (pending) 4 - FixTheWorldScenario.js.

If you just want to ignore some scenarios for a run, see the [[ignore option|Configuration#wiki-ignore]].

Description

The description of a scenario is a String. It is a human-readable description of the expected behavior the scenario is testing.

Steps

A scenario is made of an array of steps, stored in the steps key. You can define as many steps in a scenario as you want.

Action step

Any Component action (functions defined in components) may be referenced as a step.

These functions have to be called, just like you would any JavaScript code.

Example

// SearchBarComponent.js
field:        'input[name=q]',
submitButton: '#search_button_homepage',
searchFor: function searchFor(term) {
    return this.setField(term)()
               .then(this.submit());
}
// in `1 - SearchScenario.js`
description: 'Search for something',
steps: [
    SearchBarComponent.searchFor('something')
]

This is strictly equivalent to:

// SearchBarComponent.js
field:        'input[name=q]',
submitButton: '#search_button_homepage'
// in `1 - SearchScenario.js`
description: 'Search for something',
steps: [
    SearchBarComponent.setField('something'),
    SearchBarComponent.submit()
]

The difference is that in the first case, the component offers an action and hides its implementation details, making the scenario higher level.

The choice of granularity and repartition between component actions and scenario steps is up to you, but it should follow the same rules as any object model: semantics win. In our example, a search bar component's main use is to allow searching, not to enter some text in a field, so searching is probably the action your users will want to be doing with it, and thus that's what you'll be testing.

Even though it looks like you're calling the action directly, it is just syntactic sugar through a partial application. The actions are actually wrapped, and their return value is a promise-returning function of arity 0, ready to be chained with other steps. The reason for this trick is that the WebDriver context is not yet available when scenarios are parsed. Components prepare your actions so that you may call them in scenarios, but calling them here only binds the actual action to the given arguments.

State description step

A state description is a JavaScript object listing component elements and their corresponding expected value.

steps: [
    {   // this hash is a state description step
        'ZeroClickComponent.header': 'Meanings of ' + lookupTerm,
        'ZeroClickComponent.header': true
    }
]

Any hash in a scenario will be interpreted as a state description. Each key/value pair is called a state descriptor.

Keys

State descriptors have element selectors as keys. That is, a String that selects an element inside an existing component.

// in MyComponent.js
myField: '#field',
// …

The corresponding state descriptor key is 'MyComponent.myField'.

Remember that keys have to be strings and, as they contain dots, will need to be quoted for the JavaScript parser to allow them.

Values

Values of state descriptors may be one of the following:

  • A String against which the textual content (or the value attribute for inputs) will be matched (exact match).
    {
        'ZeroClickComponent.header': 'Meanings of ' + lookupTerm
    }
  • A RegExp against which the textual content (or the value attribute for inputs) will be matched.
    {
        'ZeroClickComponent.header': /Meanings of/,  // match the textual content with a static regexp…
        'ZeroClickComponent.header': new RegExp(lookupTerm + '$')  // …or with a dynamic one
    }
  • A Boolean defining mandatory presence (true) or mandatory absence (false) from the user-visible DOM.
    {
        'ZeroClickComponent.header': true  // prove that the element exists
    }
  • A Function that may implement any logic and throw to reject the match. The function will be called with a reference to the element referenced by its key, as a wd object, on which all of wd's API is made available. Node's assert default library is also injected, so you can use it instead of throwing yourself.

A custom function may also return a promise, whose fulfilment state will be honored.

    {
        // providing a good name (i.e. prefixed with `is`, `has`…) for the function will allow for better reports
        'ZeroClickComponent.header': function isAlwaysValid(header) {
            assert.ok(true, 'This is always valid');
        },
        'SearchBarComponent.field': function hasNoAutocompletion(searchField) {
            return searchField.getAttribute('autocomplete')  // note that if you're using a promise, you should **always** `return` from it
                    .then(function(attribute) {  // a promise-returning matcher will be evaluated based on whether it was resolved or rejected
                        assert.equal(attribute, 'off', 'Expected autocompletion to be off');
                    });
        }
    }

You can see all of these descriptors in their full executable context in the DuckDuckGo advanced matchers example.

Options

State descriptions may define the timeout option to specify how long to wait at most for a match in a specific state assertion, overriding the config-given timeout for the suite.

// in `1 - CorrectionScenario.js`
description: 'Text typed in a field should be corrected asynchronously',
steps: [
    TextInputComponent.type('somethign'),
    TextInputComponent.correct(),
    {
        timeout: 20000, // correction could be very long
        'TextInputComponent.textarea': 'something'
    }
]

Custom function

You can also reference any custom function, either defined inline or referenced from a Fixture file, as a scenario step.

Such a function can either be synchronous, or return a promise for async execution.

// ClockScenario.js
// ...
    ClockComponent.lookup(clockLookupTown),
    function hasExpectedTimeDiff() {
        return ClockComponent.getCurrentHour()()
                          .then(function(hour) {
                            assert.equal(hour, previousHour - 1);
                          });
    }
// ...

By contrast with action steps, custom functions must not be called directly.

Clone this wiki locally