typescriptplaywrightplaywright-test

How to initialize Page Object Models in Playwright without beforeEach


I'm working with Playwright and TypeScript and have noticed that Page Object Models (POMs) always require a page parameter for initialization.

Since the page object is only available from the beforeEach block onwards, I'm forced to use this pattern:

test.describe('describe name', () => {
  let potentialSnippetPage: PotentialSnippetPage

  test.beforeEach(async ({ page }) => {
    potentialSnippetPage = new PotentialSnippetPage(page);
  });

  test('test title', async () => {
    await potentialSnippetPage.someMethod();
  });
});

This approach feels cumbersome because I have to declare the POM variable with let first, then initialize it properly in the beforeEach block with the actual page object.

Is there a way to handle POM initialization in Playwright without beforeEach and let?

I'm looking for patterns that would reduce the repetitive setup while maintaining type safety.


Solution

  • This precise case is covered in the docs, using fixtures:

    import { test as base } from '@playwright/test';
    import { PotentialSnippetPage } from './potential-snippet-page';
    
    const test = base.extend<{ potentialSnippetPage: PotentialSnippetPage }>({
      potentialSnippetPage: async ({ page }, use) => {
        const potentialSnippetPage = new PotentialSnippetPage(page);
        await potentialSnippetPage.goto();
        await use(potentialSnippetPage);
      },
    });
    
    test.describe('describe name', () => {
      test('test title', async ({ potentialSnippetPage }) => {
        await potentialSnippetPage.someMethod();
      });
    });
    

    Where const test = ... can be exported/imported and hidden away in another file, if desired.

    That said, the let/beforeEach pattern isn't that bad, IMO. It's a ubiquitous idiom in the Jest world, which Playwright was modeled after.


    Related questions: