Good Practices

In this sections you will learn some good practices that will help you improve the quality of your Minium tests.

One Elements expression, one Interaction

Most of UI testing and automation code should be as simple as identifying elements and perform interactions with those elements. For that reason, Minium code should be as straighforward as possible:

An example of what Minium code should look like:

// elements expressions
var usernameFld = $(":text").withName("username");
var passwordFld = $(":password").withName("password");
var loginBtn =  $(":submit");

// interactions
usernameFld.fill("minium");
passwordFld.fill("some strong password");
loginBtn.click();

Avoid control flow statements

Control flow statements like if-then-else should be an exception, not the rule, and so, try to aoid using them.

As an example, suppose you want to uncheck all checkboxes in a form. A possible way of doing it could be:

var checkboxes = $("#my-form :checkbox");
for (var checkbox in Iterator(checkboxes)) {
  if (checkbox.is(":checked")) checkbox.click();
}

However, it is possible to do the same without the if statement. The right way would be:

var checkboxes = $("#my-form :checkbox").filter(":checked");
for (var checkbox in Iterator(checkboxes)) {
  checkboxes.click();
}

Avoid explicit waits

Let's consider filling a field when some loading operation is running. If we want to ensure that we don't fill that field until the loading operation completes, one possible solution would be:

// elements
var field = $("#somefield");
var loading = $(".loading");
// interactions
loading.waitForUnexistence();
field.fill("Minium can do better than this");

However, we can simplify that code by just considering a different expression for field: if the expression only evaluates into #somefield element when .loading element doesn't exist, we can avoid having an explicit wait:

// elements
var field = $("#somefield").unless(".loading");
// interactions
field.fill("Minium can!");

Avoid getting values from elements

Minium provides some jquery methods for accessing values from WebElements, like '.text()', '.attr(name)', etc. However, these methods will always evaluate immediatelly, which can be a problem, because Minium cannot ensure their evaluation occurs when it actually evaluates into a non-empty set. For that reason, it should be avoided.

Instead, filters are provided to restrict elements based on a specific value:

// gets element by text
$("span").withText("Minium can!");

// gets element by style value
$("div").withCss("visibility", "visible");

// gets element by name
$(":text").withName("somename");

In case you really need to get some value, consider chaining the method call with a .waitForExistence():

var elemText = $("label").waitForExistence().text();
// or getting a style value
var backgroundColor = $("label").waitForExistence().css("background-color");

Avoid iterating using .size()

Let's say we want to remove all items from a shopping cart, and for that we need to iterate through all the corresponding remove buttons and click on it. We could consider the following code for that:

var shoppingCart = $("#shopping-cart");
var removeBtns = shoppingCart.find(".items button").withValue("Remove");
var size = removeBtns.size();

for (var i = 0; i < size; i++) {
  // only clicks the first matching element
  removeBtn.click();
}

However, as explained before, getting values from elements should be avoided, and so a better alternative is to replace the iteration with a while loop where we check for the existence of removeBtns (don't forget that each iteration will remove the first element, therefore next time removeBtns evaluates it will return one element less):

var shoppingCart = $("#shopping-cart");
var removeBtns = shoppingCart.find(".items button").withValue("Remove");

while (removeBtns.checkForExistence()) {
  // only clicks the first matching element
  removeBtns.click();
}

Yet, that code is not perfect yet: when no more items exist in the shopping cart, Minium will wait until a timeout at removeBtns.checkForExistence() and only then it will return false, breaking the while loop.

On the other hand, if we try to check immediatelly for the existence of removeBtns (with removeBtns.checkForExistence("immediate")), that could cause some problems. Let's, for instance, make things even harder: consider that every time we remove an element of the shopping cart, the shopping cart is refreshed dynamically (an element .loading will be displayed).

In that case, removing an item would mean that the next iteration using removeBtns.checkForExistence("immediate") would not occur, because removeBtns would not exist until the shopping cart completed refreshing.

So, how do we avoid breaking the loop too soon and avoid waiting the timeout period at the end? The solution is the following:

var shoppingCart = $("#shopping-cart").unless(".loading");
var removeBtns = shoppingCart.find(".items button").withValue("Remove");

while (shoppingCart.waitForExistence().then(removeBtns).checkForExistence("immediate")) {
  // only clicks the first matching element each iteration
  removeBtns.click();
}

So what did we change?

Avoid calling browser methods outside any of Cucumber methods

Avoid at all cost calling browser methods outside any of cucumber methods (World, When, Then, etc.). That can afect tests in multiple browsers, as it will force all browsers to open on startup instead of opening the browser when it is needed (that is, when tests for that specific browser start to run). This can cause selenium timeouts because of browsers being opened too long without any activity. Instead, consider initializing browser inside of World:

World(function () {
  var loadingElem = $(".loading");
  browser.configure()
    .interactionListeners()
      .clear()
      .add(minium.interactionListeners.ensureUnexistence(loadingElem));
});