在自定义的Jasmine匹配器中解决和拒绝承诺

Resolved and rejected promises in a custom Jasmine Matcher

本文关键字:解决 拒绝 承诺 自定义 Jasmine      更新时间:2023-09-26

故事:

我们开发了一个定制的jasmine匹配器,它主要做两件事:

  • 鼠标悬停在给定元素上
  • 检查是否有带有所需文本的工具提示
实现:

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 + "'."
            };
        }
    };
},

其中tooltipPage是单独定义的Page Object:

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

这种用法对我们来说非常方便,并且确实有助于遵循DRY原则,保持我们的测试代码库干净可读:

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

问题与疑问:

现在,我要做的是让匹配器分别处理两个用例:

  • 根本没有显示鼠标上方的工具提示(这基本上是browser.wait()被拒绝的承诺)
  • 有一个工具提示,但不是想要的那个

如何改进匹配器,使其能够分别处理这两个问题并报告不同的错误?

我试过了:

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

在这里,我试图明确地解决browser.wait(),并分别处理"成功"answers"错误"的情况。这导致了Jasmine Spec超时和控制台上一个巨大的"红色"文本:

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

我恐怕不能从"compare"函数返回一个承诺。

根据jasminewd2(一个茉莉到webdriverjs的适配器。(由Protractor使用)代码-

期望解析任何对实际值和期望值给出的承诺,以及result对象的pass属性。

因此,如果有一个async函数或承诺需要在自定义匹配器/期望中解决,那么它需要被包装到result.pass值,以便量角器等待承诺被解决。

在这个问题中,jasmine spec timeout错误是由于protractor无法理解在执行特定操作之前需要解决一个promise。为了解决这个问题,可以直接在expect语句中传递async函数,或者将其传递给result对象的pass值。下面是它的代码-

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

然而,上面代码的问题是不能制作自定义错误消息。要解决这个问题,我能找到的最好方法是显式地返回result对象,这样就可以根据需要为它分配错误消息。这里有一个例子-

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;

注意:如果result对象中没有message属性,那么protractor将尝试自己创建一个通用的错误消息,它将包含promise对象(一个以- { ptor_: ... }开头的冗长消息),如问题所示。

希望能有所帮助。

嗯,我记得在某处读到jasmine 2不支持你正在尝试做的匹配器类型(内部有async函数),并返回承诺…我将尝试在这里找到源代码和更新。此外,你不应该在匹配器内部做鼠标动作,那不是匹配器的重点。

所以基本上我所说和建议的是:如果您想要一个干净的代码,请将以下代码导出到一个函数中并调用它。

var checkToolTipVisibility (elm, expectedTooltip) {
    browser.actions().mouseMove(elm).perform();
    browser.wait(EC.visibilityOf(tooltipPage.tooltip), 5000, "Tooltip is still not visible.");//optional then here if you want to fail with a timeout or something...
    expect(tooltipPage.tooltip.getText()).toEqual(expectedTooltip);
}
checkToolTipVisibility(page.fromDateInput, "After");//usage

我认为这是一个非常干净和简单的解决方案,它不需要任何自定义匹配器,它是做事情的茉莉方式(不是匹配器中的异步函数),这就是我在我的代码中使用的方式,除了那些函数位于utils.js文件中,我需要时。

希望我有帮助,我将继续寻找我的第一个声明的来源!

由于某些原因,我无法让这个在Karma/Angular 2+中工作。我最终在承诺本身内调用Jasmine的全局fail方法:

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);
});

在规范中:

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

基于@Girish Sortur的完美答案,这里是匹配器的完整代码,它现在可以完美地分别处理缺失的工具提示和不同的工具提示文本情况:

toHaveTooltip: function() {
    return {
        compare: function(elm, expectedTooltip) {
            var tooltipPage = requirePO("tooltip"),
                result = {};
            // mouse over the element
            browser.actions().mouseMove(elm).perform();
            // wait for tooltip to appear and handle errors
            result.pass = browser.wait(EC.visibilityOf(tooltipPage.tooltip), 5000).then(function () {
                return tooltipPage.tooltip.getText().then(function(actualTooltip) {
                    result.message = "Expected tooltip: '" + expectedTooltip + "'. Actual tooltip: '" + actualTooltip + "'.";
                    return jasmine.matchersUtil.equals(actualTooltip, expectedTooltip);
                })
            }, function () {
                result.message = "No tooltip shown on mouse over the element";
                return false;
            });
            return result;
        }
    };
},