Dealt with and dismissed commitments in a personalized Jasmine Matcher

The Tale:

A unique jasmine matcher has been crafted by us, with the ability to perform 2 key tasks:

  • hover over a specified element
  • verify the presence of a tooltip displaying the expected text

Execution:

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

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

            return {
                pass: tooltipPage.tooltip.getText().then(function(actualTooltip) {
                    return jasmine.matchersUtil.equals(actualTooltip, expectedTooltip);
                }),
                message: "Missing expected tooltip '" + expectedTooltip + "'."
            };
        }
    };
},

Moreover, the tooltipPage serves as a separately defined Page Object:

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

module.exports = new Tooltip();

This utility has proven to be handy for us, upholding the DRY principle in maintaining our test codebase clear and comprehensible:

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

The Challenge and Inquiry:

Presently, I am aiming to enhance the matcher's functionality to handle two distinct scenarios separately:

  • absence of any tooltip displayed upon hovering (essentially, when the browser.wait() promise is rejected)
  • presence of a tooltip that doesn't match the expected one

How could the matcher be refined to address these issues individually and report distinct errors?

Evaluating Solutions Attempted:

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

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

            return browser.wait(EC.visibilityOf(tooltipPage.tooltip), 5000, "The tooltip is still invisible.").then(function () {
                return {
                    pass: tooltipPage.tooltip.getText().then(function(actualTooltip) {
                        return jasmine.matchersUtil.equals(actualTooltip, expectedTooltip);
                    }),
                    message: "Element lacks the expected tooltip '" + expectedTooltip + "'."
                };
            }, function () {
                return {
                    pass: false,
                    message: "No tooltip appeared upon hovering over the element"
                }
            });
        }
    };
},

An attempt was made to explicitly handle the browser.wait() operation and distinguish between the "success" and "error" outcomes. However, this led to a Jasmine Spec timeout and a lengthy red error message on the console:

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

It appears challenging to return a promise from the "compare" function.

Answer №1

According to jasminewd2 (An adapter for Jasmine-to-WebDriverJS. Utilized by Protractor), the following code snippet explains:

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

If there is an async function or a promise that needs resolution in a custom matcher/expectation, it must be wrapped with the result.pass value so that protractor waits for the promise to resolve.

In cases where a jasmine spec timeout error occurs due to protractor not recognizing a promise that needs resolving before an operation, one can either pass the async function directly in the expect statement or to the pass value within the result object. Below is an example code snippet demonstrating this concept:

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 Occurred"
              }
      }
  };
},

One limitation of the above code is the inability to create a custom error message. To address this, explicitly returning the result object allows for customization of error messages as needed. Here's an illustration:

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: In absence of the message property within the result object, protractor will automatically generate a generic error message containing the promise object information (Evidenced by a lengthy message starting with - { ptor_: ... }).

Hopefully, this explanation proves helpful.

Answer №2

While recalling what I've read previously, it seems that jasmine 2 may not support the type of matcher you are attempting to use (with an async function inside), and returning promises. I will make an effort to locate the source and provide an update here. Additionally, performing mouse actions within the matcher is not recommended as it deviates from the intended purpose of matchers.

Therefore, my suggestion is as follows: For cleaner code, consider exporting the following into a function and invoking it.

var checkToolTipVisibility = function(elm, expectedTooltip) {
    browser.actions().mouseMove(elm).perform();
    browser.wait(EC.visibilityOf(tooltipPage.tooltip), 5000, "Tooltip is still not visible."); // Optionally handle timeout or other conditions here...
    expect(tooltipPage.tooltip.getText()).toEqual(expectedTooltip);
}

checkToolTipVisibility(page.fromDateInput, "After"); // Example usage

This approach offers a straightforward and neat solution without the need for custom matchers. It aligns with the conventions of Jasmine (avoiding async functions in matchers). Personally, I typically store such functions in a 'utils.js' file and require it as necessary in my code.

I hope this explanation proves helpful, and I will continue searching for the original source of information supporting my initial statement!

Answer №3

Having trouble getting this code snippet to function properly within the Karma/Angular 2+ environment. I resorted to utilizing Jasmine's global fail method directly inside the promise:

const _global: any = (typeof window === 'undefined' ? global : window);

const customMatchers: jasmine.CustomMatcherFactories = {
  toPassA11y: () => {
    return {
      compare: (el: any): any => {
        const axe = require('axe-core');
        const result: any = {
          message: '',
          pass: true
        };

        axe.run((error: Error, results: any) => {
          if (error) throw error;
          if (results.violations.length > 0) {
            _global.fail('Expected element to pass accessibility checks.');
          }
        });

        return result;
      }
    };
  }
};

_global.beforeEach(() => {
  jasmine.addMatchers(customMatchers);
});

In the test suite:

describe('Home component', () => {
  it('should verify accessibility compliance', async(() => {
    expect(document).toPassA11y();
  }));
});

Answer №4

After studying @Girish Sortur's excellent answer on Stack Overflow, I have curated the complete code for a matcher that now successfully handles both scenarios of missing tooltips and varying tooltip text:

checkTooltip: function() {
    return {
        validate: function(element, expectedTooltip) {
            var tooltipComponent = requirePO("tooltip"),
                output = {};

            // hover over the element
            browser.actions().mouseMove(element).perform();

            // wait for the tooltip to display and manage possible errors
            output.pass = browser.wait(EC.visibilityOf(tooltipComponent.tooltip), 5000).then(function () {
                return tooltipComponent.tooltip.getText().then(function(actualTooltip) {
                    output.message = "Expected tooltip: '" + expectedTooltip + "'. Actual tooltip: '" + actualTooltip + "'.";
                    return jasmine.matchersUtil.equals(actualTooltip, expectedTooltip);
                })
            }, function () {
                output.message = "No tooltip appeared upon hovering over the element";
                return false;
            });
            return output;
        }
    };
},

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Using a straightforward approach with v-model in vue.js on an href element instead of a select

Is there a way to utilize an href-link instead of using <select> to switch languages with vue.i18n? <a @click="$i18n.locale = 'en'">EN</a> <a @click="$i18n.locale = 'da'">DA</a> ...

Problem with the property `className` not adding correctly on `$scope.eventSource`

Currently, I am utilizing the AngularJS directive to integrate the Arshaw FullCalendar. However, I am encountering an issue where the className specified in $scope.eventSource is not appearing on the event object. Here is a snippet of my code: $scope.even ...

Using multiple header tags in HTML

I have been tasked with developing a webpage featuring a prominent header at the top of the page. This header will include the navigation bar, logo, and website title. As the user begins to scroll, I want the header to transform into a compact version that ...

Retrieving ng-model using ng-change in AngularJS

Here is an example of the HTML code I am currently working with: <select ng-model="country" ng-options="c.name for c in countries" ng-change="filterByCountry"></select> This HTML snippet is being populated by the following object containing a ...

Monitoring changes in session storage with AngularJS

In the sessionStorga, I have stored data from various controllers using a library called https://github.com/fredricrylander/angular-webstorage. The data is being successfully stored and is accessible within the current session. However, I am encountering a ...

Click Event for Basic CSS Dropdown Menu

My goal is to develop a straightforward feature where a dropdown menu appears below a specific text field once it is clicked. $(".val").children("div").on("click", function() { alert("CLICK"); }); .container { display: inline-block; } .val { d ...

Post a message utilizing javascript

Can a client-side tweet be generated using JavaScript, a text box, and a submit button? This involves entering the tweet text into the textbox, clicking the button, and then tweeting it with an authenticated account all within the client's browser. ...

Using JavaScript's document.write method to display HTML code, along with a variable retrieved from a Flash application

Can I ask two questions at once? Firstly, how can I achieve the following? document.write(' <div id="jwplayer"> <center> <div id='mediaplayer'></div> <script type="text/javascript"> jwplayer('mediapl ...

Encountering an "Unspecified Reference Error" while attempting to retrieve data from an API in your

I've been attempting to utilize a mock API from in order to fetch data for my Next.js application. However, I am consistently encountering an undefined reference error, despite following the code snippet provided in the official Next.js documentation ...

Attempting to iterate over the array of objects in order to discover a match

I am currently working with an object structure that resembles the one shown in the image linked below. My goal is to compare the inner object properties (such as massing type id) with existing IDs. If there is a match, I want to retrieve the name of that ...

What is the best way to manage a session using JavaScript?

Currently developing a website that initially hides all elements except for the password box upon loading. Once the user enters the correct password, all webpage elements are revealed. Seeking a solution to keep the elements visible on reload by utilizing ...

What is the best way to extract the text inside a div element based on the input value in a

I attempted to extract the innerText of a div along with the input values within it. For instance: <div> My name is Shubham, I work for <input type="text"/> for the last 5 years.</div> My objective is to retrieve all the text ...

PhoneGap and jQuery prove ineffective in fetching json results from Twitter

I've been working on a project to fetch the most recent 50 tweets with a specific hash tag. The project is built for mobile devices using PhoneGap (0.9.6) and jQuery (1.6.1). Below is my code: function retrieveTweets(hash, numOfResults) { var uri ...

JavaScript and AJAX: Dynamically Create URLs

I don't have any experience with web development, so could you please explain in detail? If my questions are unclear, feel free to ask for more information. Imagine I have a webpage with the following URL: www.mycompany.com/category1 This page has ...

Steps for displaying an image link on an aspx webpage

Is it possible to have the full-size image open on http//example.com/image.aspx when the thumbnail is clicked, instead of http//example.com/images/image.jpeg? I am looking for a solution that does not require creating individual pages for each image or edi ...

The "main" entry for ts-node is not valid when running ts-node-dev

Recently, I embarked on a TypeScript project using yarn where I executed the following commands: yarn init -y yarn add typescript -D yarn tsc --init yarn add ts-node-dev -D Subsequently, I crafted a script titled dev that triggers tsnd src/index.ts, howev ...

Unleashing the power of real-time communication with XMPP using AngularJS

I am currently working on transitioning the basic XMPP setup using Strophe and JavaScript to AngularJS. .controller('loginCtrl', function(xmppAuth) { xmppAuth.auth(login, password); }) and in service: .service('xmppAuth', f ...

Difficulty encountered when consolidating intricate data attributes into a single array

I have a task to tackle in the code snippet below. My goal is to collect all the data in the age attributes and store them in a single array, formatting it as shown here: output = [48, 14, 139, 49, 15, 135, 51, 15, 140, 49, 15, 135, 51, 15, 140, 52, 16, ...

When using the transition mode "out-in" in Vue, the returned ref element may be undefined

Encountering an issue where when mode="out-in" is used in a <transition mode="out-in">, a ref'd element returns undefined during the `updated` lifecycle. Strangely, it works fine without the `mode="out-in"` attribute. Any suggestions on how to ...

The resetFields() function fails to execute

While utilizing Vue3 with Element Plus to create a form, I encountered an issue with the resetFields() method not working as expected. The form is unable to automatically refresh itself. In the child component (Edit.vue): <template> <el-dialo ...