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.

4 Answers 4

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? Commented Apr 26, 2021 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
    Commented Apr 26, 2021 at 13:26
  • I also edited my answer to point out another mistake in exporting and importing that SearchPage object.
    – pavelsaman
    Commented Apr 26, 2021 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
    Commented Apr 26, 2021 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
    Commented Apr 27, 2021 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 :) Commented May 4, 2021 at 9:50
0

But you don't need to pass page in every function of a class. Instead you can pass the page object from test class while creating the object of the class.

const searchPage = new SearchPage(page)

This will work.

0

Introduction:

In the realm of test automation, the Page Object Model (POM) is a widely adopted design pattern for enhancing code maintainability, readability, and reusability. When combined with a powerful automation tool like playwright, it forms a robust framework that empowers you to write efficient and scalable automated tests.

Understanding the Page Object Model (POM):

The Page Object Model is a design pattern that encourages the separation of test code from the UI details of a web application. It achieves this by representing each web page as a separate class, encapsulating the page’s elements and interaction methods. This abstraction layer shields test scripts from UI changes, promoting easier maintenance and reducing code duplication.

Implementing POM into Playwright:

  1. Create Page Object: Define a separate class for each page or component of your application. These classes should encapsulate the page’s elements and interactions using Playwright’s APIs.

(LoginPageObjectModel) enter image description here

  1. Write Test Scripts: Develop your test scripts by utilizing the page object created in the previous step. Test scripts should focus on describing test scenarios and invoking methods from page objects to interact with the application under test.

(loginPageTest) enter image description here

  1. Run Tests: Execute your tests using Playwright's test runner or any other testing framework integrated with Playwright. Monitor test results and iterate on your tests as needed to ensure the reliability of your automated testing suite.

Conclusion:

Implementing the Page Object Model in the Playwright framework offers numerous benefits for automated testing projects. By organizing test code into reusable page objects, testers can create more maintainable and scalable test suites while reducing the impact of UI changes on their tests. Combined with Playwright's powerful automation capabilities, POM provides a robust foundation for achieving efficient and reliable test automation. Embrace the Page Object Model in your Playwright projects to streamline your testing efforts and deliver high-quality web applications with confidence.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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