This thread is trying to answer question "What are the benefits of using POMs as fixtures, and how can fixtures be organized in multiple files? What is the difference between using POM pages as fixtures and regular POM with hooks?"
Hi, the @playwright/test
runner is encouraging/enforcing test isolation, so each test (even within the same spec) get their own browser context and page (window/tab), with fresh cookies, local storage, etc.
Since each test gets its own page
object, you cannot create your POMs somewhere at the top. You have to do that in the tests themselves. That can get a bit noisy. So people define the POMs as fixtures and 'inject' them into the test.
I think when the test deals with various pages, this can quickly become noisy by itself, like
// Use all those pages.
});
Instead you could create a WebshopPages class, exposing a property for each of those pages, needed for a webshop scenario.
await pages.catalog.find('clean code');
await pages.catalog.openResult(1);
await pages.product.buy();
await pages.checkout.asReturningCustomer();
await pages.login.authenticate();
await pages.checkout.confirmPurchase();
});
To be honest I'm still a bit 'searching' how to design the tests cleanly, with little noise.
The other thing is when I have serial tests in which I call API's, doing assertations, etc. I have issues with the context, pages etc. I have tests in which I call the pages in the tests and they work out of the box, when I do the same tests but have the pages as fixtures. I need to describe the tests and have all the tests stored inside the describe.
Well. I use test.describe.configure({ mode: 'serial' });
to run all the tests in the spec file one after another.
Anything that is different between the 2 tests is that in the 1st I call the pages in the tests (default POM) and in the 2nd I call the pages as fixtures. I have basePages fixture where I make fixture for every page.
This is the first spec that works (in this one I call the pages in the tests)
test.describe.configure({ mode: 'serial' });
let page: Page;
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
});
test.afterAll(async () => {
await page.close();
});
test('Signup', async () => {
const registration = new signupPage(page);
await registration.goToSignupUrl();
await registration.enterEmail(loginData.adminEmail);
await registration.enterPassword(loginData.password);
await registration.clickSignupButton();
await expect(page).toHaveURL(/.*home/);
});
test('Simulate form submission', async ({ request }) => {
const userID = await getUserID(page);
await simulateFormSubmission(request, userID);
await page.reload();
});
test('Add user info', async () => {
const addInfo = new addInfoPage(page);
await addInfo.clickAddInfoButton();
await addInfo.enterName(userData.name);
await addInfo.enterNumber(userData.number);
await addInfo.enterBirthdate(userData.birthdate);
await addInfo.clickAddUserInfo();
});
And this is the second spec where I use the pages as fixtures in the tests
test.describe.configure({ mode: 'serial' });
let page: Page;
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
});
test.afterAll(async () => {
await page.close();
});
test('Signup', async ({ registration }) => {
await registration.goToSignupUrl();
await registration.enterEmail(loginData.adminEmail);
await registration.enterPassword(loginData.password);
await registration.clickSignupButton();
await expect(page).toHaveURL(/.*home/);
});
test('Simulate form submission', async ({ request }) => {
const userID = await getUserID(page);
await simulateFormSubmission(request, userID);
await page.reload();
});
test('Add user info', async ({ addInfo }) => {
await addInfo.clickAddInfoButton();
await addInfo.enterName(userData.name);
await addInfo.enterNumber(userData.number);
await addInfo.enterBirthdate(userData.birthdate);
await addInfo.clickAddUserInfo();
});
Hi ah now I understand what you meant. This is how test fixtures work. Each test will run in an isolated browser context and page. I believe using a shared context and page from the beforeAll is discouraged.
If you have a sequence of steps, instead of making them separate tests, you could make them a single test consisting of multiple test.step calls. https://playwright.dev/docs/api/class-test#test-step
On the other hand, if the only dependency would be authentication (I don't think it is in your case), and you don't want to log in for each test, then you could use storageState: https://playwright.dev/docs/auth
Thank you so much! I used the test.step and it works like a charm 🙂
But still I don't like the idea of adding all the pages in the basePage file because in time the file will just grow larger. I was thinking about what you've mentioned in the previous message about
test('buy an item', async ({ webshopPages : pages }) => {
await pages.catalog.find('clean code');
await pages.catalog.openResult(1);
await pages.product.buy();
await pages.checkout.asReturningCustomer();
await pages.login.authenticate();
await pages.checkout.confirmPurchase();
});
Is there any way to group pages in the basePage so I can import them as one in the tests? I don't understand the implementation in your example
Would it be possible to create new fixture file myPagesFixture.ts
and add the pages there
myPagesFixture.ts
import { test as pagesTest } from '@playwright/test';
import { signupPage } from '../pages/signupPage'
import { addInfoPage } from '../pages/addInfoPage'
type pages = {
registration: signupPage;
addInfo: addInfoPage;
}
export const test = pagesTest.extend<pages>({
registration: async ({ page }, use) => {
await use(new signupPage(page))
},
addInfo: async ({ page }, use) => {
await use(new addInfoPage(page))
}
})
and then use the basePage.ts
file only for importing the other fixtures from different files.
This will later allow us to import test
in all spec files only from basePage.ts.
basePage.ts
import { test as baseTest } from '@playwright/test';
import { test as pagesTest } from './myPagesFixture'
export const test = baseTest.extend({
...pagesTest
// Or something like this?
});
export { expect, Page, BrowserContext } from '@playwright/test';
What do you think about this approach?
I believe using a shared context and page from the beforeAll is discouraged
Yep, I am trying to grasp my head around each test is independent. From GUI where you have to get from point A to point D in test, it seems that steps would be repeated a lot if each test was iindependent.
Found this discussion and solved the latest problem I had with splitting the fixtures in multiple files https://github.com/microsoft/playwright/issues/22867
the framework i've built for our team is an abstracted POM npm package flow. For example:
To reduce duplicated code use across different product teams I have an npm package called "login-page-modules". This package contains a src folder with class files that build out the playwright POM structure and locators including methods specific to the page that can be used by anyone that installs the package. So a simple login test using this structure would look like this in code:
import { LoginPage } from '@login-page-modules';
import { test, expect } from '@playwright/test';
test.describe('SSO LoginPage tests', async () => {
// not using authenticated state
test.use({ storageState: undefined });
let loginPage: LoginPage;
test.beforeEach(async ({ page }) => {
loginPage = new LoginPage(page);
await loginPage.goto(testEnv);
});
test('login', async ({ page }) => {
// method in LoginPage has expects to check that login is successful otherwise it returns errors.
await loginPage.login(username, password);
});
});
So basically anyone that needs to login via our base login page can pull in this npm package and utilize the POM structure for their stuff.
@refactoreric What do you think of that approach of splitting the fixtures into multiple files and use the basePage just for exposing them to the tests?
I kinda like the approach of using regular POM with hooks and create fixtures that I'll pass to the hooks for env. set up and tear down. Not use the POM pages as fixtures because kinda complicates things 🙃
Rayrun is a community for QA engineers. I am constantly looking for new ways to add value to people learning Playwright and other browser automation frameworks. If you have feedback, email [email protected].