【问题标题】:Resolved and rejected promises in a custom Jasmine Matcher在自定义 Jasmine Matcher 中解决和拒绝的承诺
【发布时间】:2016-01-23 15:06:45
【问题描述】:

故事:

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

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

实施:

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 个用例:

  • 鼠标悬停时根本没有显示任何工具提示(基本上就是 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() 并分别处理“成功”和“错误”情况。这导致 Jasmine Spec 超时并在控制台上显示巨大的“红色”文本:

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

恐怕我无法从“比较”函数返回承诺。

【问题讨论】:

  • 嗨,请问您使用的是什么版本的茉莉花? 1.x 还是 2.x?
  • @vrachlin 当然,茉莉花 2。谢谢。

标签: javascript selenium jasmine promise protractor


【解决方案1】:

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

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

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 文件中。

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

【讨论】:

  • “好吧,我记得在某处读到 jasmine 2 不支持您尝试执行的匹配器类型(内部带有异步函数),并返回承诺..” 是的,但量角器带来了包jasminewd2,它允许 Jasmine 拥有与 Selenium 产生的承诺类型一起工作的匹配器,所以我认为你没有最终决定权。除此之外,我同意匹配器应该避免改变页面的状态。
  • 您的第一句话显然是不正确的。如果是这样,那么任何人都无法将getText() 函数传递给expect 语句。 Protractor 通过 jasminewd2(webdriverjs 和 jasmine 之间的桥梁)使用 jasmine 库,该声明的证据就在那里。 Here's more to it
  • @GirishSortur 我并不是说您不能将承诺作为第一个参数传递,而是第二个参数 expect(promise).toSomething(notPromise)...
  • @vrachlin,这也是不正确的。你可以在期望语句的任何地方传递 promise。 jasminewd2 文档有这句话 - An expectation resolves any promises given for actual and expected values, as well as the pass property of the result object。所以你可以在两个参数中传递承诺。
【解决方案2】:

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

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

因此,如果有一个异步函数或一个需要在自定义匹配器/期望中解析的承诺,那么它需要被包装到 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"
              }
      }
  };
},

但是,上述代码的问题是无法制作自定义错误消息。为了解决它,我能找到的最好方法是显式返回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 属性,那么量角器将尝试自己创建一个通用错误消息,并且它将包含promise 对象(A以 - { ptor_: ... }) 开头的冗长消息 如问题所示。

希望对你有帮助。

【讨论】:

  • 非常感谢您的解释 - 我已经对您的上一个代码 sn-ps 应用了一些小修复(在单独的答案中发布),现在它运行良好!
【解决方案3】:

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

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

【讨论】:

    【解决方案4】:

    由于某种原因,我无法让它在 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();
      }));
    });
    

    【讨论】:

    • 这里要记住的是,这种行为只会允许“真实”的期望。换句话说,你不能在你的调用前加上.not.toPassA11y(),因为返回的结果总是“真”,而且你在事后手动失败了测试。
    猜你喜欢
    • 2019-08-06
    • 2023-03-26
    • 2014-08-10
    • 2013-06-22
    • 1970-01-01
    • 2016-10-20
    • 1970-01-01
    • 2020-03-07
    • 2016-01-16
    相关资源
    最近更新 更多