Page Objects Pattern on WebDriverIO

Page Objects Pattern – 為了解決什麼問題?

傳統我們實作測試碼方式,是以測試案例 (Test Case) 為主體。一個測試案例包含了前置條件、步驟到檢驗結果,我們就循序實作出來。直觀易懂撰寫又快速的背後,你會發現一旦底層有所變動 (WebDriverIO API) 或 UI 調整,勢必都是很大的功夫要重新檢視所有相關測試碼並作出相應的調整。所以 Page Objects Pattern 就是來解決這個問題!

Within your web app’s UI there are areas that your tests interact with. A Page Object simply models these as objects within the test code. This reduces the amount of duplicated code and means that if the UI changes, the fix need only be applied in one place.

— 以上節錄自官方文件 Page Objects 的目的

簡單來說就是降低重複程式碼,當 UI 變動時,只需要修改一個地方。

Page Objects 模式,我們會定義兩個類別:

  • page.js

定義這個專案下的通用服務,一般我們會將底層 API 至於此做一層封裝,然後各頁面的 page 類別再繼承該 page.js 做提供的方法覆寫。

  • XXX.page.js

以每個頁面為一個主體,定義它所提供的服務,你會定義這個 Page class 的屬性與方法:

屬性:頁面上一個元素
方法:提供的服務

page-object
圖片來自: Martin Fowler – PageObject

接下來我們延續上個章節,來將我們的 登入登出測試碼 做一個有物件導向 (OO)的撰寫方式吧!

 

目錄結構

.
├── ...
└── test
    ├── pageobjects
    └── specs

 

test/pageobjects/page.js

function Page() {
  this.title = 'My Page';
}

Page.prototype.open = function (path) {
  browser.url(path);
};
Page.prototype.pause = function (milliseconds) {
  browser.pause(milliseconds);
};

module.exports = new Page();

 

test/pageobjects/login.page.js

var Page = require('./page');
var LoginPage = Object.create(Page, {

  // 定義元素
  email: {
    get: function () {
      return browser.element('input[name=email]');
    }
  },
  password: {
    get: function () {
      return browser.element('input[name=password]');
    }
  },
  signIn: {
    get: function () {
      return browser.element('button[type=submit]');
    }
  },
  signOut: {
    get: function () {
      return browser.element('[title="Sign Out"]');
    }
  },
  alertDanger: {
    get: function () {
      return browser.element('[data-alert-type=danger]');
    }
  },
  alertInfo: {
    get: function () {
      return browser.element('[data-alert-type=info]');
    }
  },

  // override 方法
  open: {
    value: function () {
      Page.open.call(this, 'http://demo.keystonejs.com/keystone/signin');
    }
  },
  pause: {
    value: function () {
      Page.pause.call(this, 3000);
    }
  },

  // 自訂方法
  signInClick: {
    value: function () {
      this.signIn.click();
    }
  },
  signOutClick: {
    value: function () {
      this.signOut.click();
    }
  },
  waitAlertDangerIsExist: {
    value: function () {
      this.alertDanger.waitForExist();
    }
  },
  waitAlertInfoIsExist: {
    value: function () {
      this.alertInfo.waitForExist();
    }
  },
  waitSignOutIsExist: {
    value: function () {
      this.signOut.waitForExist(7000);
    }
  },



});

module.exports = LoginPage;

 

test/specs/login.test.js

var expect = require('chai').expect;
var LoginPage = require('../pageobjects/login.page');

describe('第一個前端測試程式', function () {

  beforeEach(function() {
    LoginPage.pause();
  });

  it('登入失敗', function () {
    LoginPage.open();
    // 輸入帳號
    LoginPage.email.setValue('demo@keystonejs.com');
    // 輸入錯誤密碼
    LoginPage.password.setValue('1234');
    // 按送出按鈕
    LoginPage.signInClick();
    // 檢查是否出現警告訊息
    LoginPage.waitAlertDangerIsExist();
    // 警告訊息的文字內容,是否如預期
    expect(LoginPage.alertDanger.getText()).to.contain('The email and password you entered are not valid.');
  });

  it('登入成功', function() {
    // 輸入帳號
    LoginPage.email.setValue('demo@keystonejs.com');
    // 輸入正確密碼
    LoginPage.password.setValue('demo');
    // 按送出按鈕
    LoginPage.signInClick();
    // 檢查是否存在登出連結
    LoginPage.waitSignOutIsExist();
  });

  it('登出', function() {
    // 點選登出
    LoginPage.signOutClick();
    // 檢查是否出現登出成功的訊息
    LoginPage.waitAlertInfoIsExist();
    expect(LoginPage.alertInfo.getText()).to.contain('You have been signed out.');
  });
});

 

你會看到,我們將每個頁面轉成 page objects,將服務分門別類,這樣的結構會清晰許多。我們若要調整到底層 API 或 UI 結構,僅會動到 page objects (page.js or login.page.js),甚少機會動到 specs 的測試碼 (login.test.js)。這裡注意一個部分,在 page objects 底下是不包含驗證的。

如此的架構規劃,相信會更有維護性的保障。

 


參考資料:

https://kkboxsqa.wordpress.com/2014/04/28/page-objects-design-pattern-in-automation-testing/

發表迴響

你的電子郵件位址並不會被公開。 必要欄位標記為 *

這個網站採用 Akismet 服務減少垃圾留言。進一步瞭解 Akismet 如何處理網站訪客的留言資料