Protractor – 實戰篇 – Page Objects

 

Protractor 是一款 end-to-end test framework. 什麼是 end-to-end test 呢?! 簡單的說它可以模擬 user 在 browser 上操作的所有行為(近乎 100%), 進而測試頁面 flow 是否順暢? JavaScript 各 module 是否運作正常? 如果寫得好的話, 取代傳統的人工測試使之進化到全自動化測試也不再是件遙不可及的夢想。

 

Protractor 可以搭配的 behavior driven development(BDD) framework 分別有 Jasmine(default)、Mocha 以及 Cucumber, 我這裡所撰寫的 testing code 是 base 在 Cucumber 上, 因此以下將介紹如何設定 Protractor 以及 Chumber.

 

 

本篇所要介紹的是如何利用 Cucumber 來撰寫相關的 test code. 我先帶大家粗淺的了解一下什麼是 Cucumber. 

 

Cucumber 是一套「 Behaviour Driven Development」framework, 也就是俗稱的 BDD, 摘錄自好友 joey 的說法, BDD 定義如下:

 

 

BDD 的全名為 Behavior-driven development ,在 2003 年由 Dan North 所命名,用來作為 TDD 的輔助。BDD 是透過 DSL ( Domain-specific language )來描述系統的行為。透過屬於該 Domain 的表達方式,來描述系統的 Feature 與使用者的 Scenario ,並且依據這些 Scenario 產生對應的 code flow template ,接著可結合 Unit Test 的 3A 原則 ( Arrange, Act, Assert ),來驗證系統功能是否有滿足這些 Scenario 。

 

source: https://msdn.microsoft.com/zh-tw/library/dn308251.aspx

 

 

它所帶來的直接效益在於能讓使用者、測試人員與開發人員, 可以用一樣的方式來描述與了解需求,並且降低將人話轉換成程式碼的成本。這聽起來實在很棒, 這亦是我們選擇搭配 Cucumber 的主要原因之一

 

在繼續進行之前, 我們必須要先了解 Cucumber 所支援的檔案類型, 基本上可以歸納為以下三纇

 

  • Features:

用來描述需求的檔案, 一個 feature 是由 N 個 scenario 所構成, 而每個 scenario 為 Given / Then / When 三種 type 組成的 step 的集合體, 藉由自然語言的闡述, 讓任何一位觀看的 user 都可以很快且清楚的進入狀況, 了解需求為何? 達成的路徑為何? 以及所需要看到的結果為何?! 其檔案的副檔名為 .feature

 

 

Feature: google index

    As a user of google

    I could do some search

 

Scenario: 搜尋 “google”, 第一筆資料顯示正確

    Given I visit “google”

    When I search “google”

    Then search result must have more than “1” record

    And first record title must be “Google”

 

example: https://github.com/meistudioli/protractorEx/blob/master/cucumber/google/google.feature#L25

 

 

如同 scenario 所描述的, 當我前往 google, 然後輸入關鍵字 “google” 進行搜尋後, 我應該可以搜到東西, 且第一筆資料的 title 一定是 “Google”. 是不是很淺顯易懂呢?! 通常 Given / When / Then 可以幫助我們來加強自然語言的描述, 它們通常使用的時機為:

  • Given : 闡述一個或者多個狀態的達成, 以上面為例即表示 – 我已經在 google 的首頁
  • When : 闡述一個或者是多個動作被驅動的契機, 以上面為例即表示 – 當我搜尋關鍵字 “google”
  • Then : 闡述期待看到的現像, 以上面為立即表示 – 搜尋結果是正確的

 

Scenario 的撰寫請儘量維持其獨立性, 減少前後 dependence, 如此才方便日後作拔插處理, 試想有相關 dependence scenario, 一旦前後相依關係被移除, 那麼是必會造成 test fail, 對於後面接手的 developer 也會無所適從!!

 

Cucumber 為了維持某種程度的 reuse 關係, 會儘量希望我們讓 step 可以被 reuse, 減少不必要的 step definition 撰寫, 這確實不錯, 本來工程師就應該要發揮 code 的最大效益, 並且降低重複性過高的 code,  不過千萬不要為了 reuse 而讓你的 step definition 過於複雜, 試想一個萬能的 step 背後所帶來的直接風險有可能便是一堆 if else 或者 switch case, 無疑的這會增加 maintain 的成本, 我們希望可以寫出好維護且持續性的 code, 而非一次性用完就扔的 test code

 

 

  • Step Definitions:

完成 feature 後, 理所當然的 test code 不可能就這麼憑空生出, 這時候就需要 Step Definition 來定義每一個 step 所需要完成的事情, 其副檔名為 .js, 而 step 中所用的語法可以使用 Protractor API 來加速我們進行開發.

 

 

var google = function() {

    var Given = When = Then = this.defineStep;

 

    When(/^I search “([^”]*)”$/, function(key, callback) {

            var world = this;

            this.stand.goSearch(key).then(

                    function(google) {

                          //do nothing

                    }

            ).then(callback, callback);

    });

 

    ……

    ……

    ……

 

};

 

module.exports = google;

 

example: https://github.com/meistudioli/protractorEx/blob/master/cucumber/google/step_google.js

 

 

如上所述, 這便是和 When I search “google” 相呼應的 step definition, 它主要是由 Regular Expression 來作 mapping, 我們可以藉由挖空某些地方來當作變數, 進而傳遞到 function 裡面來作使用, 比方說上例的 ([^”]*),  即是我們所挖空的變數

 

這些被挖空的變數它會以在句子中出現的順序, 依序被被放置在 function 的 arguments 序列中, 比方說上例的 key 即表示該挖空的變數

 

為了讓每個 step 以及 scenario 可以正常的串接起來, 這裡需要特別注意 callback() 的使用時機, 唯有透過 callback() 才能有效的告知 Cucumber 繼續往後 go, 而 callback 它一定是 arguments 最後一個參數, 即 arguments[arguments.length]

 

 

Cucumber 特別設置了「World」來讓同一 scenario 的 step 間有共同的變數可以作傳遞, 它在每一個 scenario 被執行時會被 new 起來, 且會被消滅於 scenario 結束後, 所以它是無法跨 scenario 來使用. 至於要怎麼使用它呢?! 其實很簡單, 只要在 step definition 中直接呼叫「this」, 便可以對它作 set / get 的動作了喔!

 

 

this.id = ‘mei’;

this.sex = ‘M’;

 

 

如果想要作到跨 scenario 的變數傳遞的話, 可以使用 Protractor 所提供的全域變數 – params

 

 

browser.params.id = ‘mei’;

browser.params.sex = ‘M’;

 

 

另一件需要注意的是, 由於 Protractor 提供 API 大部分為 promise based, 所以在撰寫 step definition 時, 亦需要遵照這樣的準則下去設計, 讓 method 間是 then able 的

 

 

  • Hooks:

hooks 被賦予了 scenario 被執行時用來 準備 || 清理 現場的重要使命. 它和 step definition 的寫法是一樣的, 通常呈現會有兩種類型

 

 

var beforeHooks = function() {

    this.Before(function(callback) {

            browser.clearMockModules();

            browser.manage().deleteAllCookies();

            callback();

    });

};

 

module.exports = beforeHooks;

 

example: https://github.com/meistudioli/protractorEx/blob/master/cucumber/common/beforeHooks.js

 

 

這類型的 hook 由於 Cucumber 已經包裝過, 所以可以直接用 this.Before 來作宣告, 上例所要闡述的便是在每個 scenario 被執行前, 先進行 mockmodule 以及 cookie 的清除, 亦即執行還原現場的動作

 

除此之外, 這類型的 hook 其實可以搭配 tag 一起使用, 因此便可以作到針對某些符合 tag 集合的 scenario 或者是 feature 才能被執行的 hook

 

 

var beforeHooks = function() {

    this.Before(“@foo”, “@bar,@baz”, function(callback) {

            browser.clearMockModules();

            browser.manage().deleteAllCookies();

            callback();

    });

};

 

module.exports = beforeHooks;

 

 

以上例來說明, 僅有在 tag 符合 (@foo && @bar) 或是 (@foo && @baz) 這兩種 pattern 的 feature 或是 scenario 才會被執行

 

tags: https://github.com/cucumber/cucumber/wiki/Tags

 

 

另一種 hook 則需要透過「註冊」來達到宣告的效果

 

 

var afterHooks = function() {

    this.registerHandler(‘AfterFeatures’, function (event, callback) {

            // clean up!

            // Be careful, there is no World instance available on `this` here

            // because all scenarios are done and World instances are long gone.

            callback();   

    });

};

 

module.exports = afterHooks;

 

example: https://github.com/meistudioli/protractorEx/blob/master/cucumber/common/afterHooks.js

 

 

至於什麼時候需要用什麼樣的 hook 則需要查找官方詳細的說明了

 

Hooks: https://github.com/cucumber/cucumber/wiki/Hooks

 

不管是哪樣的 hook, 有一點千萬不能疏忽的就是 – callback, 它如同 step definition 一樣, 是需要 excute callback 才能讓整體 test 持續不間斷的 go 下去喔! 一旦忘記執行 callback, 則可以看到用來 run test 的 browser 就會傻傻 hang 在那裏不動喔!

 

 

以上, 便是 Cucumber 相關檔案的基本用途介紹, 看到這裡不知道您是否對 Cucumber 的了解更上一層呢?! 接下來我們將會針對 step definition 作叫為詳細的介紹囉!!

 

 

——————————————————————————————————————–

 

 

筆者已經把相關的 test code 放在 git 上, 以下範例均會以 git 上的 code 作說明, 所以請先點擊下方連結, 開起相關的 feature 和 step definition

 

feature: https://github.com/meistudioli/protractorEx/blob/master/cucumber/googleNG/googleNG.feature

step definition: https://github.com/meistudioli/protractorEx/blob/master/cucumber/googleNG/step_googleNG.js

 

我們先看 tag: @NG0001 這個 feature

 

 

@NG0001 @E2E

Scenario: 頁面 header 是否存在

    Given I go to “google”

    Then header should exist

 

 

它所要驗的便是觀看 google 首頁的 header 是否存在, 由於它裡面有兩個 step, 所以理所當然的, 就會有兩個 step definituion

 

 

Given(/I go to “([^”]*)”/, function(key, callback) {

    var url;

 

    switch (key) {

        case ‘google’:

            url = ‘https://www.google.com.tw/‘;

            break;

    }//end switch

 

    browser.get(url).then(callback, callback);

};

 

git: https://github.com/meistudioli/protractorEx/blob/master/cucumber/googleNG/step_googleNG.js#L6

 

簡單講述一下上面 code 作了哪些事情, 這個 step 主要主要要作的就是前往我們指定的網頁 – google。

 

為了讓這個 step 可以被 reuse, 我們雙引號裡面的東西挖出來當作變數, 然後以這個 key 去換取相關的 url, 最後再透過 Protractor API – browser.get, 讓 browser 前往這個網址, 最後這個 then 的用法可能第一次接觸的朋友會感到新奇, 為什麼要寫兩次 callback 呢?! 這是因為 then 這個 method 可以吃兩個參數, 分別是「成功」或者「失敗」要作的 function, 我們這裡兩個參數都帶入 callback, 最主要的用意就是使其不管成功或者失敗都要往下一 step 或者 scenario 走, 不要「死」在這裡

 

 

Then(/header should exist/, function(callback) {

    $(‘#mngb’).isPresent().then(

        function(flag) {

               expect(flag, ‘header missing’).to.be.true;

        }

    ).then(callback, callback);

};

 

git: https://github.com/meistudioli/protractorEx/blob/master/cucumber/googleNG/step_googleNG.js#L118

 

這個 step definition 直接 mapping 到 step – header should exist, 相關的執行步驟如下:

 

  1. 我們透過 Protractor API – $ 來選取頁面上的 element, 這裡對 front end 非常友善的就是 – 我們可以直接使用 CSS selector 來作 element 的選取
  2. 由於選取完的 element 是 then able, 所以直接在其後面掛上 isPresent 這個方法, 它會回傳 true || false 來告知我們這個 element 是否存在於當前  DOM 中
  3. 我們可以用 then 去把這個 flag 接出來, 最後再用 Chai 所強化 expect 來觀看結果是否正確, expect 中的第二個參數會在驗證失敗後被 echo 於畫面中方便我們 debug

 

一個簡單的 test code 就此完成, 是不是覺得很輕鬆呢?! 接下來我們來增加一點點難度

 

 

@NG0004 @E2E

Scenario: 搜尋 “google”, 第一筆資料顯示正確

    Given I go to “google”

    When I search “google” from google

    Then search result should have more than “1” record

 

 

由於第一個 step 我們已經定義過了, 所以便可以不用再次進行撰寫, 直接看下面兩個 step

 

 

When(/^I search “(.*)” from google$/, function(key, callback) {

    $(‘#lst-ib’).clear().sendKeys(key).then(

        function() {

            $(‘#hplogo’).then(

                function(e) {

                    e.click();

                },

                function() {

                    //err catch

                }

            );

        }

    ).then(

        function() {

            return $(‘input[name=”btnK”]’).isDisplayed().then(

                function(flag) {

return $(flag ? ‘input[name=”btnK”]’ : ‘button[name=”btnG”]’);

                },

                function() {

                    return $(‘input[name=”btnK”]’);

                }

            );

        }

    ).then(

        function(submit) {

            browser.getCurrentUrl().then(

                function(url) {

                    submit.click().then(

                        function() {

                            browser.wait(

                                function() {

                                    return browser.getCurrentUrl().then(

                                        function(cUrl) {

                                            return cUrl != url;

                                        }

                                    );

                                }

                            , 5000);

                        }

                    );

                }

            );

        }

    ).then(

        function() {

            browser.wait(

                function() {

                    return $(‘#resultStats’).isPresent().then(

                        function(flag) {

                            return flag;  

                        }   

                    );

                }

            , 5000);

        }

    ).then(callback, callback);

};

 

 

git: https://github.com/meistudioli/protractorEx/blob/master/cucumber/googleNG/step_googleNG.js#L58

 

 

天啊?! 希望這堆 code 不要把你嚇跑了, 其實我也不想寫這麼多orz, 主要是因為 google search 其實是 SPA 架構 (Single Page Application), 所以需要作的檢查就會非常的多, 按 A 出現 B, 按 B 出現 C 之類的相依關係

 

上面這些 code 基本上作的, 便是在頁面 input 輸入關鍵字 – “google”, 然後點擊 – google 搜尋進行搜尋, 簡單的動作為什麼要寫這麼多test code 呢? 相關的步驟如下

 

  1. 找到相關的 input, 對其進行清除並且輸入我們要搜尋的關鍵字
  2. 由於有可能出現 autoComplete 的 layer 遮住「google 搜尋」按鈕, 所以要先在其它地方點一下讓該 layer 消失
  3. 由於他是 SPA 架構, 所以我們要先找出目前的「google 搜尋」在哪裡
  4. 點擊「google 搜尋」按鈕進行搜尋, 驗證的方法就是拿當前網址和點擊後的網址比對, 若不同則表已進行搜尋
  5. 等待搜尋結果資訊出現, 若出現了, 則錶已經完成搜尋這個動作了

 

以上便是該 step definition 所完成的動作

 

 

Then(/search result should have more than “(d+)” record/, function(amount, callback) {

    var request = Number(amount);

    $(‘#resultStats’).getInnerHtml().then(

        function(html) {

            html = html.replace(/,/g, ”).replace(/約有 (d+) 項結果.*/, ‘$1’);

            return Number(html);

        },

        function() {

            return 0;

        }

    ).then(

        function(amount) {

                expect(amount, ‘search result error’).to.be.at.least(request);

        }

    ).then(callback, callback);

};

 

git: https://github.com/meistudioli/protractorEx/blob/master/cucumber/googleNG/step_googleNG.js#L142

 

接下來這個 step 便是要驗證搜尋結果的筆數是否有大於 1 筆, 所以我們使用了 Then 並且把雙引號裡面的數字抽出來當作變數, 接下來的步驟如下:

 

  1. 找到搜尋資訊這個 element, 並且藉由掛上 getInnerHtml 這個 method 把他的內容取出來
  2. 透過 then 把結果取出, 然後透過 Regular Expression 把我們要的數量取出來作比對
  3. 透過 Chai – expect, 來驗證取出來的數量是否大於我們的需求, 若為真則表示驗證成功, 反之, 則表示失敗

 

看了以上兩個 scenario 後, 對於整體 test code 的規劃相信已經有些雛形了吧! 這便是基本的 Cucumber 搭配後的 test code, 整理一下其優勢的地方不外有

 

  • 藉由 feature 與 scenario 的描述, 我們可以輕易的看出「需」與「求」, 除了有效降有時效性文件產出, 更是大大降低了與 PM 之間的溝通成本, 也讓 developer 可以很輕易的看出設計方向, 避免走偏
  • 由於 step defintion 的關係, 我們可以 reues 這些 step, 降低撰寫太多相同性質的 test code, 相同的句子亦藉由變數的替換, 大大增加了可以使用的契機, 發揮出最大效益

 

雖然說上述的優點已經很威了, 不過這當中其實存在著更大的改進空間, 只要把這缺乏的拼圖補完, 就可以讓整體獲得倍率顯著成長喔! 我們先來看看上面的 code 有什麼樣的弱點

 

  • 「頁面結構」散落在不同的 step definition, 一旦日後頁面有作異動, 那麼就會需要花費很多心力去把相關的地方找出來並作調整, 當然頁面調整不會只有一次, 也就是說這將會是無止盡的煉獄
  • 「頁面的行為」散落在不同的 step definition, 一旦日後頁面 flow 有調整, 那慘了, 如圖上述一般, 要把相關的行為都找出來, 把缺的或者是新增的行為補上去, 天啊?! 無止盡的地獄就算了, 還要世代都遭到如此的罷凌, 所以通常都會走向砍掉重練的地步, 這其實不是我們所樂見的

 

這就是為什麼我們要導入 「Page Object」 最主要的地方, 我們可以藉由 Page Object, 將頁面的結構, 頁面的行為通通收納到 Page Object 中, 一個頁面就是一個 Page Object, 他可以是首頁, 他可以是商品介紹頁, 更可以是結帳流程相關頁面, step definition 的操作就是對 Page Object 作溝通所堆疊而成, 今天頁面結構調整, 頁面 flow 有所異動, developer 只要對 Page Object 作調整, 便可以迅速的得到 response, 惱人的 grep 掰掰, 理不清頁行為掰掰, 今後我們又是一位快快樂樂的 developer 了^^

 

我們先來看一下範例程式的頁面結構

 

git: https://github.com/meistudioli/protractorEx

 

├─── cucumber

├─── egg

├─── lib

│         ├── components 

│         └── pages (https://github.com/meistudioli/protractorEx/tree/master/lib/pages)

├─── dragonKeeper.js

├─── google.conf.js

└─── suiteQueue.js

 

我們 Page Object 存放在 lib / pages, 點擊上方的連結, 我們可以看到該 folder 有兩個檔案, 分別是

 

 

google.js

pageObject.js (https://github.com/meistudioli/protractorEx/blob/master/lib/pages/pageObject.js)

 

 

其中 pageObject.js 是 Super Class, 所有的 PageObject 都是繼承自這個 Super Class, 它包含了一些常用的頁面行為, 比方說

 

  • element 選取器: one / all 
  • go: 賦予前往該 page 的 method, url 皆定義在 Page Object 的 data.url
  • login: user login 
  • wait series: waitUntilDisplay / waitUntilPresent / waitUntilUrlChange
  • io: for xmlhttprequest

 

其中有個比較特殊的地方, 那就是 components

 

git: https://github.com/meistudioli/protractorEx/blob/master/lib/pages/pageObject.js#L11

 

一個服務裡面不同頁面其實會有相同的 module 是 reuse 的, 就好比 header 和 footer 是每個頁面都會有的, 於是我們可以把這些 module 再抽出成 components, 形成這個頁面的 「肉」, 也避免了不同 Page Object 作了重工

 

 

 

另一個檔案 google.js 便是繼承 Super Class 的 Page Object, 我直接以該頁面為 Page Object 命名, 這樣方便我們一眼就可以看出這個檔案是哪一個頁面, 讓我們打開 google.js 一窺其結構

 

google.js https://github.com/meistudioli/protractorEx/blob/master/lib/pages/google.js

 

我們可以看到它除了繼承了 Super Class 外, 也有些屬於自身特有的屬性, 甚至是頁面方法, 這些都是實體頁面的抽像層, 如果頁面行為和結構補的齊全得話, 我們可以便可以很輕易的一窺這個 pagae 俱備了什麼樣的能力喔! 宛若 JOJO 冒險野郎中 – 岸邊露畔老師的替身能力 – 天堂之門 XD

 

 

google.selector 是我們存放頁面結構的地方, 我們藉由 key / value 的搭配, 巧妙的讓 step 中的自然語言換成了頁面上的相關 element, 得到彼此間的平衡與協調

 

git: https://github.com/meistudioli/protractorEx/blob/master/lib/pages/google.js#L10

 

 

想當然爾, 如果從 Super Class 中所繼承過來的 method 不堪用得話, 我們異可以作 rewrite 的動作, 從狂野中獲得了優雅, 兼具動與靜的完美

 

git: https://github.com/meistudioli/protractorEx/blob/master/lib/pages/google.js#L27

 

 

到這裡應該可以體會 Page Object 利害的地方了吧! 那麼我們剩下的就是什麼時候要去作 new 他的動作呢?

 

基本上, 我把他綁在一個 step 中

 

step:

 

Given I visit “google”

 

 

step definition:

 

Given(/I visit “([^”]*)”/, function(type, callback) {

    var stand, param, type = type.replace(/s/g, ”);

    switch(true) {

        case (/^google/i.test(type)):

            stand = require(__base + constants.PO.google);

            break;

    }//end switch

 

    stand = new stand(param);

    this.stand = stand;

 

    stand.go().then(

        function() {

            callback();

        },

        function() {

            callback();

        }

    );

};

 

git: https://github.com/meistudioli/protractorEx/blob/master/cucumber/common/commonStepDefinition.js#L18

 

 

該 step 如同 step 的描述 – 我去訪問 google 這個 page, 先藉由變數的挖空, 增加這個 step 的可用性, 既然要去 google 這個 page, 自然的就必須要先把這個 Page Object 給 require 進來並且將之活性化 – new, new 起來後的 Page Object, 我用 stand (替身) 把他接起來

 

stand = new stand(param);

 

然後使用 Cucumber 的 world, 讓它在下一個 step definition 也可以被使用, 不需要重複作 new 的動作

 

 this.stand = stand;

 

最後在執行每個 Page Object 都有的方法 – go, 就可以順利前往該頁面了

 

另外一個 new Page Object 的時機點呢?! 我會把它歸類到複合式連鎖頁面, 比方說「購物車頁面」點了結帳的按鈕就會前往到「結帳頁面」, 這時候我會在按鈕點擊後的動作把「結帳頁面」new 起來再往回帶, 徹底發揮出 promise than able 的特性, 讓測試如同行雲流水般不間斷。

 

 

 

所以囉! 我們再來看看套用了 Page Object 的 step definition 會變成什麼樣子??

 

scenario:

 

@C0001 @E2E

Scenario: 頁面 header 是否存在

    Given I visit “google”

    Then header must exist

 

git: https://github.com/meistudioli/protractorEx/blob/master/cucumber/google/google.feature#L6

 

 

step definition:

 

Then(/^header must exist$/, function(callback) {

     var header = this.stand.header.one(‘header’);

     this.stand.isExist(header).then(

        function(flag) {

             expect(flag, ‘header missing’).to.be.true;

        }

    );

});

 

git: https://github.com/meistudioli/protractorEx/blob/master/cucumber/common/commonStepDefinition.js#L61

 

 

 

在 step definition 中已經不再有任何的 element selector 了, 所以日後頁面有所異動, 我們只要到 Page Object 的 selector 屬性去調整即可, 大大減少了不必要的 replace 工

 

再來比對一下上面另一個 step 

 

When I search “google”

 

git: https://github.com/meistudioli/protractorEx/blob/master/cucumber/google/google.feature#L27

 

 

step definition:

 

When(/^I search “([^”]*)”$/, function(key, callback) {

    this.stand.goSearch(key).then(

        function(google) {

            //do nothing

        }

    ).then(callback, callback);

});

 

https://github.com/meistudioli/protractorEx/blob/master/cucumber/google/step_google.js#L6

 

 

由於整個行為已經被歸納到 Page Object, 我們見到的就只剩下優雅而以

 

最後再來比較期待值的 step

 

Then search result must have more than “1” record

 

git: https://github.com/meistudioli/protractorEx/blob/master/cucumber/google/google.feature#L28

 

 

 

step definition:

 

Then(/search result must have more than “(d+)” record/, function(amount, callback) {

    var request = Number(amount);

    this.stand.getSearchResultAmount().then(

        function(amount) {

            expect(amount, ‘search result error’).to.be.at.least(request);

        }

    ).then(callback, callback);

});

 

git: https://github.com/meistudioli/protractorEx/blob/master/cucumber/google/step_google.js#L28

 

 

 

 

以上便是導入 Page Object 後相關變化, 勝敗在此已經不需要再用任何言語表示了, 可以被 reuse 且好 maintain 的 test code 才是王者, 歡迎大家一同進入 Page Object 的領域吧 ^^

 

 

※ Reference:

 

 

 

 

 

※ 延伸閱讀:

轉載文章 – Protractor – 實戰篇 – Page Objects

mei

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *