[譯]利用Protractor的控制流做異步測試

原文地址: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.

Protractor 一個用來做AngularJS 應用e2e測試的框架,它使用真實的瀏覽器,就像一個真正的用戶一樣。它以NodeJSWebDriverJS為基礎并且可以利用Angular的內部部件知悉Angular完成處理和更新綁定狀態的時間。

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 和 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(), and click(),都會被安排進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。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容