vitalets / playwright-bdd

BDD testing with Playwright runner
https://vitalets.github.io/playwright-bdd/
MIT License
305 stars 37 forks source link

Question: How to share page instance among multiple steps #234

Open gitToSantosh opened 3 days ago

gitToSantosh commented 3 days ago

Problem

For example I have 2 step definitions, first one opens a new tab/page and second step interacts with the new page, there are further steps which will interact with the new page

When( 'I click on X button under "Some" section', async ( { page1, context } ) => {
    const [ newPage ] = await Promise.all( [
        context.waitForEvent( 'page', { timeout: 120000 } ),
        page1.clickButton()
    ] );
} );
When( 'I do some stuff in other page', async ( { page2, } ) => {
    page2.doSomething();
} );

And In my fixtures I have fixtures for both pages, but this creates page instances at the beginning of the test, and when I want to interact elements of Page2, the test gets stuck with no output at all

type Fixtures = {
    page1: Page1;
    page2: Page2;
};
export const test = base.extend<Fixtures>( {

    page1: async ( { page, world }, use ) => {
        const page1 = new Page1( page, world );
        await use( page1 );
    },
    page2: async ( { page, world }, use ) => {
        const page2 = new Page2( page, world );
        await use( page2 );
    }
} );
vitalets commented 3 days ago

There can be different approaches. In case of sharing page instances between steps you can use context.pages() method to get all pages and filter required one.

If there are fixtures that are initialized with Playwright's default page, you can make these fixtures to be able to hot-switch the page like myFixture.page = newPage. For example, in POM:

class AdminPage {
  page: Page;
  greeting: Locator;

  constructor(page: Page) {
    this.page = page;
    this.greeting = page.locator('#greeting'); // <- you should not store such locators if you want to hot-switch the page.
  }
}

The in the step you can switch fixtures to the new page:

When( 'I click on X button under "Some" section', async ( { page1, context } ) => {
    const [ newPage ] = await Promise.all( [
        context.waitForEvent( 'page', { timeout: 120000 } ),
        page1.clickButton()
    ] );
        adminPage.page = newPage;
} );

But here we loose the ability to interact with old page via that fixture.

I think the better way is to use writable fixture as cross step context and put there all shared data, like word in cucumber js:

export const test = base.extend<Fixtures>( {
  world: async ({}, use ) => {
    await use({ newPage: null });
  },

And then in steps:

When( 'I click on X button under "Some" section', async ( { page1, context, world } ) => {
    const [ newPage ] = await Promise.all( [
        context.waitForEvent( 'page', { timeout: 120000 } ),
        page1.clickButton()
    ] );
        world.newPage = newPage; // <- store new page for subsequent steps
} );