Advanced Concepts

In this section you will learn some advanced Minium concepts that can help you dealing with some of the problems that may arise when creating end-to-end tests for web applications.

Waiting Presets

Sometimes, it is necessary to wait that some element is displayed on the page or not. For instance, a spinning wheel is often displayed to indicate that the application is doing something in background, and therefore you should wait until it disappears.

In Minium Mail sample app, it shows a spinning wheel after you perform some operation.

Let's try to delete an email item and then compose another one:

browser.get("http://minium.vilt.io/sample-app/");

var mailItemCheckbox = $(":checkbox");
var removeBtn = $("#remove-action");
var composeBtn = $("#compose");

mailItemCheckbox.click();
removeBtn.click();
composeBtn.click();

If you run that script all at once (select it all and press Ctrl + Enter), you'll notice it will fail when trying to click the Compose button. The reason is that the spinning wheel is being displayed and it "blocks" elements behind the backdrop form being interacted with. So, we need to wait for that spinning wheel to disappear before we can click the Compose button. We can do that with the .waitForUnexistence() method:

browser.get("http://minium.vilt.io/sample-app/");

var loading = $(".loading").withCss("display", "block");
var mailItemCheckbox = $(":checkbox");
var removeBtn = $("#remove-action");
var composeBtn = $("#compose");

mailItemCheckbox.click();
removeBtn.click();
loading.waitForUnexistence();
composeBtn.click();

The interaction loading.waitForUnexistence() will wait at most for a specified amount of time (by default, 5 seconds) that the element doesn't exist. After that time, it will fail, otherwise, as soon the element disappears, it will proceed.

However, it is possible that the spinning wheel takes more than 5 seconds to disappear. Remember that, in real world applications, the spinning wheel is normally associated with time-consuming operations that involve AJAX requests.

In those situations where we know that it will probably take more time, we need to ensure it will wait using a different timeout.

Minium Mail lets us configure the loading time and that way we can simulate a time-consuming operation. The following code will change the loading time to be 8 seconds:

browser.get("http://minium.vilt.io/sample-app/");

var configBtn = $("#configure");
var loadingTimeFld = $("#loading-time-seconds");
var saveBtn = $("#config-save");

configBtn.click();
loadingTimeFld.fill("8");
saveBtn.click();

Note that the loading time gets reset every time we refresh the page, so we won't reload the page using the browser.get(...) method. If you do, you need to change the loading time again.

If we now try to run the same interaction code as we were running before:

mailItemCheckbox.click();
removeBtn.click();
loading.waitForUnexistence();
composeBtn.click();

it will fail with a TimeoutException. That's because loading.waitForUnexistence() timeout is 5 seconds and now the spinning wheel is displayed for 8 seconds.

To fix it, we need to use waiting presets, which are basically labelled timeouts and polling intervals.

The following code creates two waiting presets, fast and slow:

// we need to load a module, we'll talk about this later
var timeUnits = require("minium/timeunits");

// browser configuration
browser.configure()
  .waitingPreset("fast")
    .timeout(1, timeUnits.SECONDS)
  .done()
  .waitingPreset("slow")
    .timeout(10, timeUnits.SECONDS)
    .interval(1, timeUnits.SECONDS)
  .done();

If we now use slow waiting preset when calling .waitForUnexistence(), it will now wait at most 10 seconds instead of 5 seconds, and that way it will work:

mailItemCheckbox.click();
removeBtn.click();
loading.waitForUnexistence("slow");
composeBtn.click();

Note: There is a special waiting preset, immediate, that doesn't wait at all. Besides, you can use .checkForUnexistence() / .checkForExistence() to determine if any matching element exists or not without failing:

loading.checkForUnexistence("immediate") // returns true or false immediately

Interaction Listeners

Having to explicitly wait every time a spinning wheel is displayed only adds complexity to the code. Besides, when we explain someone how to use some functionality in a site, we never tell them they need to wait until the spinning wheel disappears, because it is already assumed. The same way, we want Minium to implicitly wait every time it sees a spinning wheel.

Interaction listeners are specially handy for these kind of situations, and also for error handling, like retrying some interaction when it fails. They intercept all interaction calls, and can perform additional logic before, after or when they fail.

ensureExistence / ensureUnexistence

These interaction listeners only allow the interaction to be performed after existence / unexistence or certain elements. In our case, we want to ensure that no spinning wheel is displayed in the page:

var loading = $(".loading").withCss("display", "block");

var loadingUnexistenceListener = minium.interactionListeners
  .ensureUnexistence(loading)
  .withWaitingPreset("slow");

// browser configuration
browser.configure()
  .interactionListeners()
    .add(loadingUnexistenceListener)
  .done();

We can now run the same code that removes and email and then starts composing a new mail without having to call loading.waitForUnexistence():

browser.get("http://minium.vilt.io/sample-app/");

var mailItemCheckbox = $(":checkbox");
var removeBtn = $("#remove-action");
var composeBtn = $("#compose");

mailItemCheckbox.click();
removeBtn.click();
composeBtn.click();

Error handling

There are situations where some exception can be thrown. For instance, if during some interaction an alert window is displayed, an UnhandledAlertException is thrown. Of course, we can explicitly handle alert windows with $(":root").alert().accept(), for instance, but if we cannot predict when the alert window will be displayed, we may need to handle it once it occurs:

// always accepts window alerts
var unhandledAlertListener = minium.interactionListeners
  .onUnhandledAlert()
  .accept();

// browser configuration
browser.configure()
  .interactionListeners()
    .add(unhandledAlertListener)
  .done();

Another kind of errors that can happen is the StaleElementReferenceException, which basically happens when we try to interact with an element that no longer exists in the page (for instance, if the page was refreshed, which forces that element to be destroyed in the browser).

In case you start getting these kind of errors, you can register the following interaction listener:

// always accepts window alerts
var staleElementReferenceListener = minium.interactionListeners
  .onStaleElementReference()
  .thenRetry();

// browser configuration
browser.configure()
  .interactionListeners()
    .add(staleElementReferenceListener)
  .done();

Timeout handling

It is also possible to handle timeout exceptions in a very advanced way. The following example shows an interaction listener that, when a timeout occurs, is triggered, and then checks if an loading element exists in the page. If it exists, it will wait for its unexistence with a provided waiting preset and then it will retry the interaction:

var timeoutListener = minium.interactionListeners
  .onTimeout()
  .when(loading)
  .waitForUnexistence(loading)
  .withWaitingPreset("slow")
  .thenRetry();

browser.configure()
  .interactionListeners()
    .add(timeoutListener);

Base Expressions

The concept behing the Base Elements expression is that it should represent the root elements of the UI that can be interacted with.

For instance, when Modal elements are displayed (for instance, a bootstrap modal dialog) we want base to evaluate to that modal element, therefore excluding all elements that are behind the backdrop element.

Then, by using base as the root of our elements expressions, we can get some assurance that we are getting the right elements instead of getting elements that are not interactable (or should not be interactable) at that point.

To demonstrate how useful this pattern can be, let's do a simple exercise: we'll start composing an email and then we'll try to click the first button it finds:

browser.get("http://minium.vilt.io/sample-app/");

// this will open the New message modal dialog
$("#compose").click();

// let's try to click the first available button
$("button").click();

If we try to evaluate that code, it will fail. That's because $("button").click() will try to click the first matching button (which is the Compose button), and that one is under the modal backdrop and for that reason, it is not accessible for interactions.

You can try to evaluate $("button") and you'll see that lots of buttons in the page will highlight, both the ones that are in the modal dialog and the ones in the main page, that are not accessible due to the modal backdrop:

Buttons

So, let's consider the following base expression:

base = $(":root").unless(".modal-backdrop").add(".modal-dialog");

Let's try to explain what it does:

So, basically that expression evaluates into the root element or into an opened modal dialog, but never both at the same time. If we try to evaluate base when a modal dialog is opened:

Base expression evaluation when modal dialog is being displayed

So, if we use base as our "root" for finding elements in the page, we can now restrict them to accessible ones.

Try to evaluate the following expression with the modal dialog open now:

base.find("button")

You'll see that only buttons inside the modal dialog were highlighted:

Buttons with base expression

And now the following code will evaluate successfully:

browser.get("http://minium.vilt.io/sample-app/");

var base = $(":root").unless(".modal-backdrop").add(".modal-dialog");

// this will open the New message modal dialog
base.find("#compose").click();

// let's try to click the first available button
base.find("button").click();

Modules with step definitions

You can also define Step definitions at modules to reuse step definitions by several projects.

For this example we can create a module with step definitions to fill the form of the email. Create a new file modules/forms-steps.js and paste the following code there:

function formsSteps() {
  this.When(/^I submit the form$/, function() {
    var btn = base.find("submit");
    btn.click();
  });

  this.When(/^I fill the field "(.*?)" with "(.*?)"$/, function(field, value) {
    var fld = base.find("input, textarea").withLabel(field);
    fld.fill(value);
  });
}

if (typeof module !== 'undefined') module.exports = formsSteps;

After creating your module, you can copy the module with step definitions to other projects and reuse the same step definitions. To use the step definitions, simply call the module this way:

var mc = require("minium/cucumber");
mc.loadDefinitions("forms-steps");

So in your Features, you can now use the the steps definitions.

When I fill the field "Recipients" with "Rui Figueira"
And I fill the field "Subject" with "Minium Test"
And I fill the field "Message" with "My new Message"
And I submit the form

Create external modules (JAR file)

If you intend to create an external module from your project, open the pom.xml (located at the project root folder) and add the following code there:

<build>
    <resources>
        <resource>
            <directory>src/test/resources</directory>
            <includes>
                <include>modules/**/*</include>
            </includes>
        </resource>
    </resources>
</build>

Finally, run the following command at the project root folder in order to install it into the local Maven repository:

mvn clean install

Now, you can load this file at other projects in order to reuse the modules code.

Load external modules

Minium allow you to import modules, in order to reuse code from other projects.

To load a module, just add the corresponding Maven dependency to the pom.xml file of the projects where you want to use it. For example, the modules from project example:my-modules:1.0 can be imported like this:

<dependencies>
    <dependency>
        <groupId>example</groupId>
        <artifactId>my-modules</artifactId>
        <version>1.0</version>
    </dependency>
</dependencies>

The dependencies of a project are automatically loaded when it is opened. To update them while a project is opened, go to Project > Dependencies and click on Update.

Now, use the snippets above to import the modules and/or to import the step definitions.