Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Resolved and rejected promises in a custom Jasmine Matcher

The Story:

We've developed a custom jasmine matcher that does 2 main things:

  • mouse over a given element
  • check that there is a tooltip shown with a desired text

Implementation:

toHaveTooltip: function() {
    return {
        compare: function(elm, expectedTooltip) {
            var tooltipPage = requirePO("tooltip");

            browser.actions().mouseMove(elm).perform();
            browser.wait(EC.visibilityOf(tooltipPage.tooltip), 5000, "Tooltip is still not visible.");

            return {
                pass: tooltipPage.tooltip.getText().then(function(actualTooltip) {
                    return jasmine.matchersUtil.equals(actualTooltip, expectedTooltip);
                }),
                message: "Element does not have the tooltip '" + expectedTooltip + "'."
            };
        }
    };
},

where tooltipPage is a Page Object defined separately:

var Tooltip = function () {
    this.tooltip = element(by.css(".tooltip"));
};

module.exports = new Tooltip();

The usage is quite convenient for us and really helps to follow the DRY principle keeping our test code base clean and readable:

expect(page.fromDateInput).toHaveTooltip("After");

The Problem and the Question:

Now, what I'm trying to do is to have the matcher handle 2 use cases separately:

  • there is no tooltip on mouse over shown at all (which is, basically, the browser.wait() rejected promise)
  • there is a tooltip, but not the desired one

How can I improve the matcher to be able to handle these two problems separately and report different errors?

What I've tried:

toHaveTooltip: function() {
    return {
        compare: function(elm, expectedTooltip) {
            var tooltipPage = requirePO("tooltip");

            browser.actions().mouseMove(elm).perform();

            return browser.wait(EC.visibilityOf(tooltipPage.tooltip), 5000, "Tooltip is still not visible.").then(function () {
                return {
                    pass: tooltipPage.tooltip.getText().then(function(actualTooltip) {
                        return jasmine.matchersUtil.equals(actualTooltip, expectedTooltip);
                    }),
                    message: "Element does not have the tooltip '" + expectedTooltip + "'."
                };
            }, function () {
                return {
                    pass: false,
                    message: "No tooltip shown on mouse over the element"
                }
            });
        }
    };
},

Here I've tried to resolve browser.wait() explicitly and handle the "success" and "error" cases separately. This resulted into a Jasmine Spec timeout and a huge "red" text on the console:

Expected ({ ptor_: ({ setFileDetector: Function, ...
5 minutes scrolling here
... InnerHtml: Function, getId: Function, getRawId: Function }) to have tooltip 'After'.

I'm afraid I cannot return a promise from the "compare" function.

like image 391
alecxe Avatar asked Oct 24 '15 00:10

alecxe


1 Answers

As per jasminewd2 (An adapter for Jasmine-to-WebDriverJS. Used by Protractor) code -

An expectation resolves any promises given for actual and expected values, as well as the pass property of the result object.

So if at all there is an async function or a promise that needs to be resolved in a custom matcher/expectation then it needs to be wrapped to the result.pass value, so that protractor waits for the promise to be resolved.

In the question, a jasmine spec timeout error is encountered because protractor couldn't understand that there is a promise that needs to be resolved before performing that particular operation. In order to resolve it, either pass the async function in the expect statement directly or pass it to the pass value of the result object. Here's the code for it -

toHaveTooltip: function() {
  return {
      compare: function(elm, expectedTooltip) {
          var tooltipPage = requirePO("tooltip");

          browser.actions().mouseMove(elm).perform();

              return {
                  pass: browser.wait(EC.visibilityOf(tooltipPage.tooltip), 5000, "Tooltip is still not visible.").then(function () {
                            tooltipPage.tooltip.getText().then(function(actualTooltip) {
                                return jasmine.matchersUtil.equals(actualTooltip, expectedTooltip);
                            }),
                        }, function () {
                            return false;
                        }),
                  message: "Error Occured"
              }
      }
  };
},

However, the problem with the above code is that a custom error message cannot be crafted. To resolve it, the best method I could find was to return the result object explicitly, so that an error message can be assigned to it as required. Here's an example -

var result = {};
result.pass = browser.wait(EC.visibilityOf(tooltipPage.tooltip), 5000, "Tooltip is still not visible.").then(function () {
                  tooltipPage.tooltip.getText().then(function(actualTooltip) {
                      result.message = "Element does not have the tooltip '" + expectedTooltip + "'.";
                      return jasmine.matchersUtil.equals(actualTooltip, expectedTooltip);
                  }),
              }, function () {
                  result.message = "No tooltip shown on mouse over the element";
                  return false;
              });
return result;

Note: If there is no message property in the result object, then protractor will try to create a generic error message itself and it will contain the promise object (A lengthy message starting with - { ptor_: ... }) as shown in the question.

Hope it helps.

like image 189
giri-sh Avatar answered Oct 27 '22 00:10

giri-sh