原文地址:Asynchronous Testing with Protractor’s ControlFlow
首先,如果你也在用Angular并且也用Protractor做测试的话,这篇文章也许可以帮到你。:)
水平有限,部分翻译不妥的地方请留言指正,谢谢!
Asynchronous Testing with Protractor’s ControlFlow
Protractor is an end-to-end testing framework for AngularJS applications that uses a real browser, just as a real user would. It is built on top of NodeJS and WebDriverJS and taps into the internals of Angular to know when Angular is done processing and updating bindings.
The joy of Protractor and WebDriverJS is that we can write our tests in a synchronous style, and still have the added benefits of asynchronous code.Protractor 一个用来做AngularJS 应用e2e测试的框架,它使用真实的浏览器,就像一个真正的用户一样。它以NodeJS 和 WebDriverJS为基础并且可以利用Angular的内部部件知悉Angular完成处理和更新绑定状态的时间。
使用Protractor 和 WebDriverJS的乐趣在于我们可以用同步的样式去写测试,同时享受到异步代码带来的好处。
We are currently using Protractor for testing a Rails and Angular application that we’re developing here in the Detroit office. We were faced with a problem: “How do we write our own functions which are asynchronous but appear in the same synchronous style that Protractor tests are written in?” This is especially handy for performing REST requests for seeding data into the test database using something like the rails Hangar gem.
我们目前正在用Protractor测试一个用Rails和Angular开发的应用。 现在我们有一个问题是:我们怎样才能把我们自己写的异步方法变成像Protractor的那样的同步风格呢?这种风格在进行把数据上传到测试数据库的REST请求时会尤其顺手比如使用Rails的Hangar库。
The ControlFlow
This required diving into the internals of how Protractor and WebDriverJS handle asynchrony. WebDriverJS uses an object called theControlFlow, which coordinates the scheduling and execution of commands operating on a queue.
首先我们需要知道在Protractor和WebDriverJS内部是如何处理异步的。WebDriverJS 使用一个叫做ControlFlow的对象,这个对象的功能是协调队列当中各种命令的安排和调度。
Any time an asynchronous command is invoked, it’s put in the queue where it will wait for all previous commands to complete prior to execution. The ControlFlow allows us to write code like this:
当一个异步的命令被触发,它将会被放进这个队列中并且等待前面的命令都完成以后才会被触发。ControlFlow 允许我们的代码可以像下面这样写:
driver.get(“http://www.google.com”);
driver.findElement(webdriver.By.name('q')).sendKeys('webdriver');
driver.findElement(webdriver.By.name('btnG')).click();
driver.getTitle().then(function(title) { console.log(title); });
That really performs in a synchronous manner such as this:
这样写的代码会同步执行,像下面的写法一样:
driver.get(“http://www.google.com”).then(function () {
return driver.findElement(webdriver.By.name('q'));
}).then(function (q) {
return q.sendKeys('webdriver');
}).then(function() {
return driver.findElement(webdriver.By.name('btnG'));
}).then(function (btnG) {
return btnG.click();
}).then(function () {
return driver.getTitle();
}).then(function (title) {
console.log(title);
});
Behind the scenes of WebDriver, each call that interacts with the browser, such as get()
, findElement()
, sendKeys()
, and click()
, is being scheduled and pushed onto the WebDriver ControlFlow, allowing us to not have to worry about using .then()
on the resulting promises (unless we specifically want the result returned by the call).
WebDriver背后的原理是,每一次和浏览器交互的命令,像
get()
,findElement()
,sendKeys()
, andclick()
,都会被安排进WebDriver 的ControlFlow队列中,这样做就使我们不用在结果promise上使用.then()
(除非我们希望得到每次命令的返回值))。
Using the WebDriverJS Promises
The ControlFlow queues functions based on promises. The following code exhibits how to take a callback based function and wrap it into WebDriver’s Promise API. We will be using the Restler NodeJS library.
ControlFlow 的队列方法是建立在promises之上的。接下来的例子将会展示怎样把一个基于回调的方法包装进WebDriver的Promise API。我们会还是用Restler NodeJS 库。
To create a WebDriver/Protractor deferred using their promise API:
用WebDriver/Protractor的promise API创建一个延迟对象。
deferred = protractor.promise.defer()
Then fullfill or reject the promise based on Restler’s events and return the deferred promise:
完成和拒绝的状态取决于Restler的事件,最后返回延迟对象的promise。
restler.postJson(url, requestData, options).once('success', function() {
return deferred.fulfill();
}).once('error', function() {
return deferred.reject();
}).once('fail', function() {
return deferred.reject();
});
deferred.promise;
Pushing the Promise Onto the ControlFlow
To solve our problem of invoking a synchronous REST request, we have to interact with WebDriver’s ControlFlow.
为了解决我们想要进行一个同步的REST请求的问题,我们必须用到WebDriver到ControlFlow.
To get the instance of the ControlFlow that is shared with protractor:
得到ControlFlow的实例。
var flow = browser.controlFlow()
Then we can use the ControlFlow’s execute()
function, assuming we created a based on the above code that wraps restler’s callbacks and returns a WebDriverJS promise:
然后我们就可以使用 ControlFlow的
execute()
方法,假设我们创建一个方法包装的restler的回调并且返回一个WebDriverJS promise对象。
flow.execute(restFunction)
Chaining Promises and Getting Results
The good news is that ControlFlow’s execute()
function returns a new promise that will be fulfilled when our original promise is executed by the scheduler. We can use use the all()
function, which returns a new promise once all of the control flow’s promises are fulfilled:
好消息是 ControlFlow的
execute()
方法会返回一个新的promise,而且当我们的原始的promise被调度程序触发的时候这个新的promise会触发完成状态。我们可以使用all()
方法,它可以当所有的control flow promises完成的时候返回一个新的promise。
var allPromise = protractor.promise.all(flow.execute(restFunction1), flow.execute(restFunction2))
When allPromise is fulfilled, it will return an array of the results of all the promises passed to all()
as arguments.
当所有的promise都完成了,它将会返回一个包含所有promise结果的数组。
Ideally the implementation of your helper functions would abstract the intermediary promises that are being passed to the ControlFlow and purely operate based upon the promises returned from the ControlFlow’s execute function.
理想情况下你的helper方法可以抽象那些传递给ControlFlow的中介promises,只单纯的处理那些ControlFlow的execute方法返回的promises。
网友评论