3

我将ChaiJS与我的Casper-Chai插件一起使用,但我不确定如何解决遇到的特定问题。

我希望能够编写测试,例如:

expect(casper).selector("#waldo").to.be.visible;

这非常简单,调用如下

utils.addChainableMethod(chai.Assertion.prototype, 'selector', 
  selectorMethod, selectorChainableMethod);

utils.addMethod(chai.Assertion.prototype, 'visible', visibleMethod);

*Method引用是对执行相应测试或可链接调用的函数的引用。

我的问题是让例如“选择器”修改链中的后代的最佳方式是什么。想到两个选择:

  1. 用于utils.flag(chai, 'object')将其更改为选择器;或者

  2. 用例如创建一个新标志utils.flag(chai, 'casper-selector')

当调用“可见”时,它可以读取相应的标志。修改“对象”似乎有用的地方是稍后调用例如“长度”时。但是,我有点担心更改“对象”的意外副作用。

我可能还想修改对象,用于“长度”测试,沿着链,例如:

// there can be only one!
expect(casper).selector("#waldo").length(1)

// but that one has 4 classes
expect(casper).selector("#waldo").class.to.have.length(4)

感谢任何想法和输入。

----编辑----

好的,这就是让 Casper-Chai 扎根的概念挑战,这需要对 Casper 是什么以及为什么 Casper-Chai 应该是 Chai 插件而不仅仅是现有 Casper API 的替代品进行一些描述。Casper 是 PhantomJS 无头 Web 浏览器的包装器,因此 Casper 有效地运行两个不同的虚拟机:“控制器”和无头 Web 浏览器。

控制器中没有 DOM 或“文档”或“窗口”对象;Controller 在这方面很像 Node.js,尽管它使用 WebKit javascript 解析器。同时,PhantomJS 启动了一个无头 Web 浏览器。然后控制器可以通过 PhantomJS/Casper API 与无头浏览器进行通信。控制器可以告诉无头浏览器要加载哪些页面,运行什么 javascript(就像在控制台中输入 javascript),甚至可以模拟键盘输入和鼠标点击等事件。无头浏览器具有完整的 DOM 和 javascript 堆栈:它是在 WebKit 中加载的网页。您可以捕获WebKit 将呈现的屏幕截图。

Casper-Chai 在 Controller 中运行。在控制器中使用 Mocha + Chai 创建的测试旨在评估无头浏览器的状态。尽管我们可以将状态从浏览器复制到控制器并在复制的状态上运行测试,但我对该设计的有限实验揭示了设计固有的问题(即效率、竞争条件、性能和潜在的副作用)。问题是浏览器状态是动态的、复杂的并且可能很笨重。

所以以 John 的例子来说,expect(casper.find("#waldo")).to.be.visible是行不通的,因为没有 DOM,除非 Casper 返回的对象进行了某种惰性评估/调解。即使我们序列化并复制了 HTML 元素,Controller 中也没有 CSS。然后,即使有 CSS for #waldo,也无法像 Web 浏览器一样测试层次结构。我们将不得不复制大部分 DOM 和所有 CSS,然后复制一个 Web 浏览器来测试是否#waldo可见 - 对于每个单独的测试。Casper-Chai 旨在通过在浏览器中运行测试来避免这个问题。

只是为了一点额外的照明,一个简单的比较是获得匹配选择器的元素数量。可以写expect(casper.evaluate(function () {return __utils__.findAll('.my_class')}).to.have.length(4), wherecasper.evaluate在无头浏览器中运行给定的函数,并返回与选择器匹配的 DOM 元素列表作为字符串;您可以将其__utils__视为 Casper 的 jQuery 版本。或者,可以写expect(casper).selector('.my_class').to.have.length(4)whereselector成为“对象”,它有一个.length调用 'casper.evaluate(function () { return utils.findAll('.my_class').length`。仅返回整数长度。对于少量测试,要么工作正常,但对于大量测试,此性能特征变得有影响力(在这里,以这种简单的形式,并且在更复杂的情况下可能在更大程度上影响)。

当然可以写expect(casper.evaluate(function () { __utils__.findAll('.my_class').length }).equal(4),但如果要写这样的测试,为什么还要麻烦 BDD/Chai?它消除了 Chai 提供的可读性优势。

另外值得注意的是,Controller中可能有多个Casper实例,对应多个PhantomJS页面。把它们想象成诡异的标签。

因此,鉴于 Domenic 的回答是修改“对象”标志是解决问题的适当方法,这似乎是最实用的方法 - 根据上述描述进行任何思考。

我希望上面描述了为什么 Casper-Chai 应该是一个插件,而不仅仅是 Casper 的 API 扩展。我还将由 Casper 的作者运行它,看看他是否有任何意见。

这可能不是一段完美的关系,但我希望 Casper 和 Chai 可以轻松相处。:)

4

2 回答 2

2

困难源于 casper 有一个高度程序化的 API,有 和 之类的Casper#click(String selector)方法Casper#fetchText(String selector)。为了自然地适应 chai,需要一个面向对象的 API,例如Casper#find(String selector)(返回一个CasperSelection对象)CasperSelection#click()、 ...CasperSelection#text()等。

因此,我建议您使用or方法扩展 casper 对象本身,该方法返回一个您可以基于该对象的断言。然后您将不需要更改对象标志。findselector

expect(casper.find("#waldo")).to.be.visible;
expect(casper.find("#waldo")).to.have.length(1)
expect(casper.find("#waldo").class).to.have.length(4)
于 2012-12-22T16:08:41.583 回答
1

我大多同意@John。您正在对其他对象执行您的期望,所以说

expect(casper).select("#waldo").to.have.length(1)

很奇怪。你不期待关于 的任何事情casper,你期待关于 的事情casper.find("#waldo")。还要考虑should语法:

casper.should.select("#waldo").have.length(1)
// vs.
casper.find("#waldo").should.have.length(1)

也就是说,如果你对这种 API 死心塌地,这正是object标志的用途。Chai 甚至这样做,做出如下断言

myObj.should.have.property("foo").that.equals("bar")

运作良好:

https://github.com/chaijs/chai/blob/49a465517331308695c3d8262cdad42c3ac591ef/lib/chai/core/assertions.js#L773

于 2012-12-23T00:01:28.053 回答