我们正在使用 Page Object模式 来组织内部AngularJS应用程序测试。
这是我们拥有的示例页面对象:
var LoginPage = function () { this.username = element(by.id("username")); this.password = element(by.id("password")); this.loginButton = element(by.id("submit")); } module.exports = LoginPage;
在单浏览器测试中,非常清楚如何使用它:
var LoginPage = require("./../po/login.po.js"); describe("Login functionality", function () { var scope = {}; beforeEach(function () { browser.get("/#login"); scope.page = new LoginPage(); }); it("should successfully log in a user", function () { scope.page.username.clear(); scope.page.username.sendKeys(login); scope.page.password.sendKeys(password); scope.page.loginButton.click(); // assert we are logged in }); });
但是,当涉及到实例化多个浏览器并且需要在单个测试中在它们之间进行切换的测试时,变得不清楚如何在多个浏览器中使用同一页面对象:
describe("Login functionality", function () { var scope = {}; beforeEach(function () { browser.get("/#login"); scope.page = new LoginPage(); }); it("should warn there is an opened session", function () { scope.page.username.clear(); scope.page.username.sendKeys(login); scope.page.password.sendKeys(password); scope.page.loginButton.click(); // assert we are logged in // fire up a different browser and log in var browser2 = browser.forkNewDriverInstance(); // the problem is here - scope.page.username.clear() would be applied to the main "browser" }); });
问题:
分叉新浏览器后,如何使用相同的Page Object字段和函数,但将其应用于新实例化的浏览器(browser2在这种情况下)?
browser2
换句话说,element()此处的所有调用都将应用于browser,但需要应用于browser2。我们如何切换上下文?
element()
browser
想法:
一种可能的方法是在的情况下临时[重新定义全局element=browser2.element``browser2。这种方法的问题在于,browser.wait()在页面对象函数内部也有调用。这意味着也browser = browser2应该设置。在这种情况下,我们需要记住browser临时变量中的全局对象,并在切换回主browser上下文后将其还原。
element=browser2.element``browser2
browser.wait()
browser = browser2
另一种可能的方法是将浏览器实例传递给page对象,例如:
var LoginPage = function (browserInstance) { browser = browserInstance ? browserInstance : browser; var element = browser.element; // ...
}
但这可能需要更改我们拥有的每个页面对象。
希望问题清楚-让我知道是否需要澄清。
看我的解决方案。我简化了示例,但是我们在当前项目中使用了这种方法。我的应用程序具有两种用户权限类型的页面,并且我需要在两种浏览器中同时执行一些复杂的操作。我希望这可以向您展示一些新的更好的方法!
"use strict"; //In config, you should declare global browser roles. I only have 2 roles - so i make 2 global instances //Somewhere in onPrepare() function global.admin = browser; admin.admin = true; global.guest = browser.forkNewDriverInstance(); guest.guest = true; //Notice that default browser will be 'admin' example: // let someElement = $('someElement'); // this will be tried to be found in admin browser. class BasePage { //Other shared logic also can be added here. constructor (browser = admin) { //Simplified example this._browser = browser } } class HomePage extends BasePage { //You will not directly create this object. Instead you should use .getPageFor(browser) constructor(browser) { super(browser); this.rightToolbar = ToolbarFragment.getFragmentFor(this._browser); this.chat = ChatFragment.getFragmentFor(this._browser); this.someOtherNiceButton = this._browser.$('button.menu'); } //This function relies on params that we have patched for browser instances in onPrepare(); static getPageFor(browser) { if (browser.guest) return new GuestHomePage(browser); else if (browser.admin) return new AdminHomePage(browser); } openProfileMenu() { let menu = ProfileMenuFragment.getFragmentFor(this._browser); this.someOtherNiceButton.click(); return menu; } } class GuestHomePage extends RoomPage { constructor(browser) { super(browser); } //Some feature that is only available for guest login() { // will be 'guest' browser in this case. this._browser.$('input.login').sendKeys('sdkfj'); //blabla this._browser.$('input.pass').sendKeys('2345'); //blabla this._browser.$('button.login').click(); } } class AdminHomePage extends RoomPage { constructor(browser) { super(browser); } acceptGuest() { let acceptGuestButton = this._browser.$('.request-admission .control-btn.admit-user'); this._browser.wait(EC.elementToBeClickable(acceptGuestButton), 10000, 'Admin should be able to see and click accept guest button. ' + 'Make sure that guest is currently trying to connect to the page'); acceptGuestButton.click(); //Calling browser directly since we need to do complex action. Just example. guest.wait(EC.visibilityOf(guest.$('.central-content')), 10000, 'Guest should be dropped to the page'); } } //Then in your tests let guestHomePage = HomePage.getPageFor(guest); guestHomePage.login(); let adminHomePage = HomePage.getPageFor(admin); adminHomePage.acceptGuest(); adminHomePage.openProfileMenu(); guestHomePage.openProfileMenu();