简体   繁体   中英

Selenium/WebdriverJs/Protractor promise chaining with page objects

I am currently implementing the page object pattern in protractor/selenium.

Since every method in protractor returns a promise, for consitency the methods in my page object should also return promises.

Further more my page objects may have functions which return another page object or a custom of a page object (like LeftNavigation, MainContent). Instead of returning the page object itself, the page object should be returned within a promise. Currently I do not really undestand how to do that.

Additionally I would like chain my method calls without using the .then(..) method. For WebElements it is possible to call further functions without calling the .then(..) method, eg

browser.driver.findElement(By.css('#someid')).findElement(By.css('#somebutton')).click();

I would also like achieve this with the page object pattern:

let pagePromise = AdminBaseBage.get(); // returns a Promise<AdminBasePage>
let mContent = page.mainContent;// should return a Promise<MainContent>
let titlePromise = mContent.getModuleTitle(); // returns a Promise<string>

or even better

AdminBaseBage.get().mainContent.getModuleTitle();

Below an extract of my PageObjects with some questions here:

AdminBasePage.js

var LeftNavigation = require('../../pageobject/LeftNavigation.js');
var MainContent = require('../../pageobject/MainContent.js');

class AdminBasePage {

    constructor() {
        this._leftNavigation = new LeftNavigation();
        this._mainContent = new MainContent();
    }

    /**
     * @returns {Promise<AdminBasePage>}
     */
    static getPage() {
        return browser.driver.get("index.php").then(function() {
            return new AdminBasePage();
        });
    }

    /**
     * @returns <LoginPage>
     */
    logout() {
        this.leftNavigation.logout();
        return new LoginPage(); //also here I would like to return a promise.
    }

    /**
     * @returns {LeftNavigation}
     */
    get leftNavigation() {
        //Instead of return the object directly, I would like to return a promise here. 
        //But how?
        return this._leftNavigation;
    };

    /**
     * @returns {MainContent}
     */
    get mainContent() {
        //Instead of return the object directly, I would like to return a promise here. 
        //But how?
        return this._mainContent;
    };
}

module.exports = AdminBasePage;

MainContent.js

class MainContent {

    constructor() {

        /** @type {WebElementPromise} */
        this._element_mainContent = this.webDriver.findElement(By.css('#maincontent'));

    }


    /**
     * Gets the title of the main content
     *
     * @returns {webdriver.promise.Promise<string>}
     */
    getMainContentTitle() {
        return this._element_mainContent
                   .findElement(By.id('moduleTitle'))
                   .getText();
    }

}

/** @type {MainContent} */
module.exports = MainContent;

Can you give any advice? I hope it is somehow clear what I am trying to explain :-)

Regards

You shouldn't try to make a PageObject a Promise. A PageObject is supposed to be a method/property factory and thus shouldn't be a constraint in the execution flow. I would keep it simple by returning an element with a property rather than trying to locate all the elements in a constructor:

describe('Suite', function() {

    it('should module title be ...', function() {
        let pageAdmin = AdminBaseBage.get();
        let mContent = pageAdmin.mainContent;
        let titlePromise = mContent.getModuleTitle();
        expect(titlePromise).toEqual('module title');
    });

});


class MainContent {

    constructor() {

    }

    get element_module_title() { return element(By.css('#maincontent #moduleTitle')); }

    /**
     * Gets the title of the main content
     *
     * @returns {webdriver.promise.Promise<string>}
     */
    getModuleTitle() {
        return this.element_module_title.getText();
    }

}

thanks for your input.

You are right that page objects should not constraint the execution flow. I will forget about trying to make a promise here :-). I also extracted the element initialization in the constrcturos to getter methods.

My main page object now consists of several page elements (LeftNavigation, TopMenu etc.) which I have created. Each of these page elements now access the WebElements they need (only via the getter methods).

class AdminBasePage extends BasePage {

    constructor{
        super();

        /** @type {LeftNavigation} */
        this._leftNavigation = new LeftNavigation();

        /** @type {TopMenu} */
        this._topMenu = new TopMenu();

        /** @type {PathNavi} */
        this._pathNavi = new PathNavi();

        /** @type {ContentTopBar} */
        this._contentTopBar = new ContentTopBar();

    /** @type MainContent*/
        this._mainContent = new MainContent()
    }

   /**
    * @returns {Promise<AdminBasePage>}
    */
    static getPage() {
        return browser.driver.get("index.php").then(function() {
            return new AdminBasePage();
        });
    }

    ....getters + other methods follow

}

My test now looks as followed:

describe('module_checklist', function () {
     it('Check number of elements in list', function () {
        let page = AdminBasePage.getPage();//returns Promise<AdminBage>

        // check number of list rows
        page.then(function (templateListPage) {
                return templateListPage.mainContent.getArrListRows();//returns Promise<ListRow[]>
            })
            .then(function (arrRows) {
                expect(arrRows.length).toEqual(2);
            });


        //check total number in pagination
        page.then(function (templateListPage) {
            expect(templateListPage.mainContent.getPagination().getIntPaginationTotalNumber()).toEqual(2);
        });
    });
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM