2

Recently I have been asking questions regarding Playwright and so far the community has been very helpful. I hope that my question and answers provided will be in use for people who are just starting with Playwright.

I have been working on the framework set up in Playwright. Framework consists of Mocha, Chai and uses Node.js. While reading https://playwright.dev/ I came a cross a section about using Page Objects approach. However the example which was posted is different from the ones which I am used and currently would like to implement in my solution. I am new to Playwright and Node.js so maybe someone who has more experience or maybe already worked with Page Object Pattern in Playwright will help. I am placing the code which was an example in the documentation and below the code my way of how I would like to have. I am open for any suggestions/improvements/comments from your side.

Example in documentation -> https://playwright.dev/docs/pom

-- Page Object Class set up--

// models/Search.js
class SearchPage {
  constructor(page) {
    this.page = page;
  }
  async navigate() {
    await this.page.goto('https://bing.com');
  }
  async search(text) {
    await this.page.fill('[aria-label="Enter your search term"]', text);
    await this.page.press('[aria-label="Enter your search term"]', 'Enter');
  }
}
module.exports = { SearchPage };

-- Test implementation --

// search.spec.js
const { SearchPage } = require('./models/Search');

// In the test
const page = await browser.newPage();
const searchPage = new SearchPage(page);
await searchPage.navigate();
await searchPage.search('search query');

-- My (if possible) implementation of the Page Object Class --

// models/Search.js

    class SearchPage {
      constructor(page) {
        this.page = page;
      }
      
// Separate URL with Locators
        
    const bingUrl = "https://bing.com";
    const searchField = '[aria-label="Enter your search term"]';
      
      async navigate() {
        await this.page.goto(bingUrl);
      }
      async search(text) {
        await this.page.fill(searchField, text);
        await this.page.press(searchField, 'Enter');
      }
    }
    module.exports = new SearchPage();

-- My (if possible) implementation of the test --

// search.spec.js
    const { SearchPage } = require('./models/Search');
    
    // In the test
    await SearchPage.navigate();
    await SearchPage.search('search query');

If the solution is good, then I am planning on adding new automated test which will for sure reuse the existing page objects. A part from the mentioned questions above, if my solution is good then is it possible to use it with other future tests? From what I heard, when you export modules as new instance of classes you can run into cashed issues. If that so then what would be the correct approach? Also if you see any better suggestions on how I can implement Page Object Patter in Node.js let me know.

Thanks in advance and as always or help is welcomed.

2

Have you tried to run it?

constructor(page) {
    this.page = page;
}

and later:

module.exports = new SearchPage();

What about that missing page? this.page in the object will be undefined, so none of the methods will actually work and you will get some sort of exception at runtime.

A solution could be to send page to every method:

async search(page, text) {
    await page.fill(searchField, text);
    await page.press(searchField, 'Enter');
}

and in test:

await SearchPage.search(page, 'search query');

EDIT:

Another problem seems to be in that export/import-require. If you do this:

module.exports = new SearchPage();

and later:

const { SearchPage } = require('./models/Search');

SearchPage will end up undefined.

Perhaps you wanted to do:

const searchPage = require('./models/Search');

that is without destructuring.

Or:

const { navigate } = require('./models/Search');

that is getting concrete properties out of the exported object. But then you'd need to change the methods to arrow functions:

navigate = async () => {
    // something
}

in order to be able to use:

const { navigate } = require('./models/Search');
navigate();
6
  • 1
    I did run it as you said after posting and ran into mentioned runtime errors... For the reference I was trying to get similar set up as it is showed for the Webdriver.IO -> webdriver.io/docs/pageobjects. Maybe I should try sticking to their set up? If it goes for the "pages" I could use that but lets say I have a test that will require more than one Page Object class, wont it get messy and confusing? Apr 26 at 13:10
  • 1
    @Automation_Padawan: Both approaches work in the end, so to me it seems more like a code-style-preference kind of question. That said, I wouldn't try to spend too much time on this and just pick one approach and stick to it throughout the project (consistency is important). Personally, having to type one more line (const searchPage = new SearchPage(page);) doesn't seem too messy to me. More the opposite, because the Webdriver.IO example works with LoginPage.open() style, but the name's first capital letter suggests it's a class, not an instance. That could lead to confusion.
    – pavelsaman
    Apr 26 at 13:26
  • I also edited my answer to point out another mistake in exporting and importing that SearchPage object.
    – pavelsaman
    Apr 26 at 14:47
  • 1
    Good answer. It's helpful to know that Page Object Model is a design pattern -- how you organize your code -- so it's not dependent on a particular tool. Also, using ES6 "export default" in the page object class would be another option.
    – Lee Jensen
    Apr 26 at 16:51
  • @LeeJensen: yes, that's also possible to use esm modules and export default or even just export and then destructuring if I need to import just some properties. It's probably a bit more elegant, although I think it's worth learning commonjs modules as well. JS is good and bad in that it has many ways to do the same thing. I recommend learning JS before learning concrete frameworks like Playwright.
    – pavelsaman
    Apr 27 at 8:41
0

You want to export a class:

module.exports =  { SearchPage };

, not an object of that class:

module.exports = new SearchPage();

If you export a class, you can instantiate new objects as required however if you just export an created object, then you cannot create more objects out of it, if required.

1
  • indeed, I tried at first and run into issue while trying to create new page objects inside the test, so at the moment the best working way for me is to export the whole class and create instances of it if needed. As pavelsaman replied before it is more of a coding style rather than actual issue. I now know it after I have spent more time on this matter :) May 4 at 9:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.