Sencha Ext JS 7.7 is Here – Discover What’s New and Exciting – LEARN MORE

Sencha Test Futures API for End-to-End Testing

June 13, 2017 111 Views
Show

We designed and built Sencha Test to provide developers and test automation engineers with a comprehensive unit and end-to-end testing solution that helps avoid common testing challenges and pitfalls. We know that automating black box web application tests is incredibly complicated. You have to manage many factors including access to elements on the page, availability of components to perform an action, cross-browser verification, and the need to wait for pages to render.

In this article, we’ll discuss a very important feature of Sencha Test, the Futures API, which allows automation engineers to write tests targeting JavaScript components rather than depending on DOM elements – using concise JavaScript that is not cluttered with fragile code that tries to work around browser and rendering issues.

Futures

To make interactive tests expressive and maintainable, Sencha Test provides a family of classes under the ST.future.* namespace, collectively called “futures”. Futures are objects, created in test code, that provide a concise syntax to describe a test sequence.

The name is derived from the fact that these classes enable test writers to easily interact with page elements even on highly dynamic pages that are constructed by application code. Futures objects will wait for the target element to become available on the page before executing the associated testing code.

Futures can act on HTML elements, of course, and Sencha Test provides Component Futures that can act on Ext JS components at higher levels of abstraction than HTML elements. Component futures typically provide special methods for working with the component, making it much easier to test component interactions.

Note that futures are implemented as classes. This means that there is a class hierarchy, which allows for methods and properties to be shared among groups of related classes. You can derive your own classes if you need to create a customized class.

To see a simple example of futures, the following code uses an ST.future.Element that is returned by the ST.element() factory method:

   it('should change text on click', function () {
        ST.element('button#foo').
            click(10, 10).
            textLike(/^Save/).
            and(function (el) {
                expect(el.hasCls('somecls')).toBe(true);
            });
    });

The ST.element() method accepts a locator (a generalized string that locates an element or component). The returned ST.future.Element instance provides the methods we’re using above: click(), textLike() and, and(). Note how the instance supports method chaining and a fluent-style interface.

It’s tempting to think of this test as a sequence of function calls that synchronously interact with the element, but it’s important to remember that these method calls don’t actually perform their operations immediately. Instead, the futures instance is creating a series of actions that will occur in the specified order “when they can.”

The methods available to futures classes can be described in several broad classes: locators, actions, states, inspections, and waits.

Locators

The first step in our example test uses ST.element() to create the future element instance. While that is the first job of this method, its second and equally important job is to schedule a request to locate the desired DOM element. The requested target is passed as a locator string to the futures factory method, and identifies a DOM element or Ext JS component using one of the supported locator syntaxes.

Under the covers, ST.element() stores the provided locator and starts waiting for that element to be added to the DOM and made visible.

The task of locating the element will not start until the test function returns control to the browser.

Component futures may support additional locator methods, such as the row(), column(), and grid() methods of the ST.futures.Grid class. These locator methods typically select other targets relative to the currently-selected target, enabling tests to navigate through related components on the page in complex tests.

Actions

The purpose of locating a particular component or element is typically to perform some sort of action on it, and Action methods serve exactly that purpose. They range from simple actions such as clicking on a DOM element to more complex interactions with Ext JS components such as revealing a particular row in a grid or collapsing/expanding a panel.

The second step in our example test is to click() on the selected element using some (optional) element-relative coordinates. When the click() method is called, it adds a click event to the schedule. Many of the ST.future.Element methods are also action methods, and they all work in a similar manner: they schedule an action that will follow the previously scheduled actions, which will act on the element located by the future instance.

Action methods have verbs for names such as “click”.

States

In addition to selecting elements and triggering actions on them, tests often need to wait for those actions to have an effect and then either perform more actions or verify a specific ending state. States methods provide a way for tests to wait for targeted elements to enter a desired state before additional steps are performed.

For example, the third step in our test is the textLike() method. This schedules a wait for the textContent of the element to match the given regular expression. This group of methods is concerned with describing a state and injecting a delay in the schedule until that desired state is achieved. Some state methods require polling to detect the state transition while others can listen for events to detect the change. In any case, this optimization detail is something Sencha Test handles and is not a concern for the test author.

State methods have nouns or descriptions for names such as “collapsed” or “textLike”.

Inspections

Inspections are tester-provided functions typically used to verify that the operations performed during test execution have shown that the application correctly implements the desired behavior. These functions are typically used to inspect the element, its current state, and/or other aspects of the application.

In the case of our example test, we end with a call to the and() method. This method schedules the provided function to be called after the previous steps complete. Namely, after the textContent matches the regular expression. The function calls Jasmine’s expect() method to verify that our target element has the class “somecls”. This is how we let Jasmine know whether the test passes.

Our function receives one argument: the located ST.Element. The and() method allows an optional second argument, a done function that works the same way as an asynchronous Jasmine test. If the function declares the second argument, the done function will be passed and must be called.

Custom Waits

Sometimes a wait condition needs to be expressed in code. In many cases, the optional second argument allowed by the and() function can be used for this purpose. For example:

   it('should change text on click', function () {
        ST.element('button#foo').
            click(10, 10).
            textLike(/^Save/).
            and(function (el, done) {
                // wait for condition
                done();
            });
    });

In other cases, the test must simply poll for the proper state. Futures provide a wait() method to handle this:

   it('should change text on click', function () {
        ST.element('button#foo').
            click(10, 10).
            textLike(/^Save/).
            wait(function (el) {
                return el.hasCls('somecls'); // return true-like when done
            });
    });

In general, it’s best to use the and() approach because it avoids polling, but the right choice will depend more on the situation at hand.

Component Example

The previous example deals with a DOM element. To see how a test might interact with an Ext JS component, consider a new example test:

   it('should change cell text on click', function () {
        ST.grid('grid#foo').
            row(42).
                cell('firstName').
                reveal().
                click(10, 10).
                textLike(/^Hello$/).
                and(function (cell) {
                    expect(cell.el.hasCls('somecls')).toBe(true);
                });
    });

In this example, the first two calls after ST.grid() are locator methods: row() and cell(). There are various ways to describe the desired row and cell. In this case, we’re using the method that takes the id of the associated record (42) and the column id (“firstName”).

Once we’ve called a row() method, the chain of method calls will operate on that row future. We can “climb” back up to the grid by calling the row’s grid() method as shown here:

       ST.grid('grid#foo').
            row(42).
                reveal().  // scroll the row into view
                click(10, 10).
            grid().row(999).  // pick a different row
                reveal().
                click(10, 10);

This approach works in a similar way between rows and cells:

       ST.grid('grid#foo').
            row(42).
                cell('firstName').  // column id
                    reveal().  // scroll cell into view
                    click(10, 10).
                row().   // return to row 42
                cell('lastName').
                    reveal().
                    click(10, 10).
            grid().row(999).
                reveal().
                click(10, 10);

In the example below, we’re using the xtype value “assetgrid” as a locator to the ST.grid() API to locate the proper grid within the page. We use the rowWith() and cellWith() locators to find the “quantity” cell in the record having the name field equal to “Statesman 10-piece conference table and chairs”. We then use the textLike() state method to verify that the “quantity” is “4”.

ST.grid('assetgrid')
        .rowWith('name', 'Statesman 10-piece conference table and chairs')
        .cellWith('dataIndex', 'quantity')
        .textLike('4');

Note that we don’t need to explicitly call Jasmine’s expect() method in an inspection function as described earlier. We can simply use our state method to select the cell with the desired value. If that cell can’t be found, our test will correctly be marked as a failed test.

WebDriver Integration

Under the hood, Sencha Test leverages WebDriver to control and interact with the application running in the browser. Scenarios that involve navigating across different pages can be controlled via the Futures API, just like navigating through elements on a single page. The wide range of APIs allows test authors to perform operations on a plain HTML page or directly on an Ext JS component spanning multiple pages. Sencha Test abstracts the use of WebDriver, meaning that test authors can focus on writing tests rather than explicitly invoking and interacting with WebDriver.

Conclusion

The Futures API in Sencha Test is a unique and very powerful approach to test automation. A simple yet powerful API can help test authors accomplish many necessary testing actions such as locating elements or components on the page, waiting for those elements to become available, performing operations on elements, and validating the expected outcome. The Futures API is flexible and accommodates many locator strategies, and it eliminates the need for explicit wait methods due to its awareness of the component rendering mechanics of Ext JS. For more information, please check out the complete list of Futures API properties and methods.

coming soon

Something Awesome Is

COMING SOON!