Search code examples
javascriptconstructore2e-testingplaywright

How to implement Page Object Models using context browser in Playwright in typescript?


This current implementation is working very well for certain classes

export class PlaywrightDevPage {
  readonly page: Page;

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

  async goto() {
    await this.page.goto('https://playwright.dev');
  }
}

test('test using page with page object model', async ({ page }) => {
  const devPage = new PlaywrightDevPage(page);

  await devPage.goto();
});

But I want to implement a new context in the Page Object Models, but the browser is closed when the object is created.

export class AppPage {
  readonly browser: Browser;

  constructor(browser: Browser) {
    this.browser = browser;
  }

  async goto() {
    // Remove storage state to simplify the sample,
    // the main problem is with browser.newContext: Browser has been closed
    // const context = await this.browser.newContext({
    //   storageState: './auth.json',
    // });
    const context = await this.browser.newContext();
    const page = await context.newPage();

    await page.goto('https://playwright.dev');
  }
}

  test('test using context with page object model', async ({ browser }) => {
  const devPage = new AppPage(browser);

  devPage.goto();
});

It is throwing the following error:

browser.newContext: Browser has been closed

What is the way to implement context in Page Object Models?

Live sample in stackblitz that is showing the error.


Solution

  • In your top example,

    test('test sample', async ({page}) => {
      devPage = await new PlaywrightDevPage(page);
      devPage.goto();
    });
    

    should be

    test('test sample', async ({page}) => {
      const devPage = new PlaywrightDevPage(page);
      await devPage.goto();
    });
    

    Your constructor isn't async, so there's no promise to await. However, devPage.goto() is async and should be awaited.

    In your second example, you're passing a browser as a page. That's the wrong object. You can destructure the browser in the test block callback and use that if you want:

    test('test sample', async ({browser}) => {
      const devPage = new AppPage(browser);
      await devPage.goto();
    });
    

    Modify the class to keep track of context and page so you can use them in other functions and eventually close them (and ensure this.page exists when you run this.page.goto):

    export class AppPage {
      readonly browser: Browser;
    
      constructor(browser: Browser) {
        this.browser = browser;
      }
    
      async goto() {
        this.context = await this.browser.newContext({
          storageState: './auth.json',
        });
        this.page = await this.context.newPage();
        await this.page.goto('https://playwright.dev');
      }
    }
    

    As a possible design improvement, it might make sense to create a separate function that handles creating context and the page. It's a little odd that goto is responsible for these things, breaking the single-responsibility principle.