我们正在使用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"
    });
});

问题:

fork 新浏览器后,如何使用相同的Page Object字段和函数,但如何将其应用于新实例化的浏览器(本例中为browser2)?

换句话说,这里所有的element()调用都将应用于browser,但需要应用于browser2。我们如何切换上下文?

想法:
  • 这里一种可能的方法是在element = browser2.element的上下文中临时加入redefine the global browser2 。这种方法的问题在于,我们在页面对象函数中也有browser.wait()调用。这意味着还应该设置browser = browser2。在这种情况下,我们需要记住temp变量中的browser全局对象,并在切换回主browser上下文后将其还原。
  • 另一种可能的方法是将浏览器实例传递给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();
    

    10-05 23:02
    查看更多