Multiple browsers and the Page Object pattern

后端 未结 2 650
别那么骄傲
别那么骄傲 2020-12-31 07:31

We are using the Page Object pattern to organize our internal AngularJS application tests.

Here is an example page object we have:

var Logi         


        
相关标签:
2条回答
  • 2020-12-31 08:27

    Look at my solution. I simplified example, but we are using this approach in current project. My app has pages for both user permissions types, and i need to do some complex actions same time in both browsers. I hope this might show you some new, better way!

    "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();
    
    0 讨论(0)
  • 2020-12-31 08:31

    Maybe you could write few functions to make the the browser registration/start/switch smoother. (Basically it is your first option with some support.)

    For example:

    var browserRegistry = [];
    
    function openNewBrowser(){
      if(typeof browserRegistry[0] == 'undefined'){
        browseRegistry[0] = {
          browser: browser,
          element: element,
          $: $,
          $$: $$,
          ... whatever else you need.
        }
      }
      var tmp = browser.forkNewDriverInstance();
      var id = browserRegistry.length;
      browseRegistry[id] = {
          browser: tmp,
          element: tmp.element,
          $: tmp.$,
          $$: tmp.$$,
          ... whatever else you need.
      }
      switchToBrowserContext(id);
      return id;
    }
    function switchToBrowserContext(id){
      browser=browseRegistry[id].browser;
      element=browseRegistry[id].element;
      $=browseRegistry[id].$;
      $$=browseRegistry[id].$$;
    }
    

    And you use it this way in your example:

    describe("Login functionality", function () {
        var scope = {};
    
        beforeEach(function () {
            browser.get("/#login");
            scope.page1 = new LoginPage();
            openNewBrowser();
            browser.get("/#login");
            scope.page2 = new LoginPage();
        });
    
        it("should warn there is an opened session", function () {
            scope.page1.username.clear();
            scope.page1.username.sendKeys(login);
            scope.page1.password.sendKeys(password);
            scope.page1.loginButton.click();
    
            scope.page2.username.clear();
            scope.page2.username.sendKeys(login);
            scope.page2.password.sendKeys(password);
            scope.page2.loginButton.click();    
        });
    }); 
    

    So you can leave your page objects as they are.

    To be honest I think your second approach is cleaner... Using global variables can bite back later. But if you don't want to change your POs, this can also work.

    (I did not test it... sorry for the likely typos/errors.) (You can place the support functions to your protractor conf's onprepare section for example.)

    0 讨论(0)
提交回复
热议问题