自定义 Jasmine Matcher 中已解决和已拒绝的承诺

Resolved and rejected promises in a custom Jasmine Matcher

故事:

我们开发了一个自定义的 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是单独定义的页面对象:

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

module.exports = new Tooltip();

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

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

问题与问题:

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

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

我试过的:

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() 并分别处理 "success" 和 "error" 情况。这导致 Jasmine 规范超时和控制台上的巨大 "red" 文本:

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

恐怕我无法return "compare" 函数的承诺。

好吧,我记得在某处读到 jasmine 2 不支持您尝试执行的匹配器类型(内部带有异步函数),并返回 promises..我将尝试找到源代码并在此处更新。此外,您不应该在匹配器内部执行鼠标操作,这不是匹配器的重点。

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

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

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

希望我帮到你了,我会继续寻找我第一个声明的来源!

根据 jasminewd2 (Jasmine-to-WebDriverJS 的适配器。量角器使用) code -

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

因此,如果有一个异步函数或一个 promise 需要在自定义 matcher/expectation 中解决,那么它需要包装到 result.pass 值,以便量角器等待要解决的承诺。

在问题中,遇到了 jasmine spec timeout 错误,因为量角器无法理解在执行该特定操作之前需要解决的承诺。为了解决它,要么直接在 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"
              }
      }
  };
},

但是,上述代码的问题在于无法制作自定义错误消息。要解决它,我能找到的最好方法是显式 return 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会尝试创建一个通用的错误信息本身,它将包含承诺对象 (以 - { ptor_: ... } 开头的冗长消息),如问题中所示。

希望对您有所帮助。

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

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

出于某种原因,我无法在 Karma/Angular 2+ 中使用它。我最终在 promise 本身中调用了 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();
  }));
});