Hi, I would like to know how can you implement POM design pattern with playwright.
Thanks.
Hi @tomern, you can use the page object in Playwright and wrap it in a Page Object Model. For example:
class CheckoutPage {
constructor(page) {
this.page = page;
}
async clickSubmit() {
return this.page.click('text=Submit');
}
}
Feel free to reopen if you have a follow-up question.
Arjun, thanks for your response!
How do I use functions declared in one page object in another page object?
Hi @randomactions, the best approach would depend on your use-case. Couple of approaches to consider:
Thanks, Arjun!
@randomactions I would strongly recommend against usage of inheritance - that may lead to circular dependencies.
The better approach would be a set of helper methods combined with Page Element pattern.
E.g.
// Page element class
class ConfirmDialog {
constructor(page) {
this.page = page;
}
async confirm() {
return this.page.click('text=Confirm')
}
}
// Page object class
class CheckoutPage {
constructor(page) {
this.page = page;
this.confirmDialog = new ConfirmDialog(page);
}
async clickSubmit() {
return this.page.click('text=Submit');
}
}
//test
const checkoutPage = new CheckoutPage(page);
await checkoutPage.clickSubmit();
await checkoutPage.confirmDialog.confirm();
@tymfear thank you!
import {Page, BrowserContext} from 'playwright';
export default class FacebookUtilities {
page: Page;
context: BrowserContext;
constructor(page: Page, context: BrowserContext) {
this.page = page;
this.context = context;
}
TxtBxLogin = `xpath=//input[@id='email']`;
TxtBxPassword = `xpath=//input[@id='pass']`;
BtnLogin = `xpath=//button[@id='loginbutton']`;
BtnProfileCancel = `xpath=//button[@id='u_0_0']`;
BtnProfileContinue = `xpath=//button[@name='__CONFIRM__']`;
BtnGroupsNotNow = `xpath=//button[@name='__SKIP__']`;
BtnGroupsOk = `xpath=//button[@name='__CONFIRM__']`;
LinkChooseWhatToAllow = `xpath=//a[@id='u_0_19']`;
//Login
login = async (email: string, password: string) => {
await this.page.fill(this.TxtBxLogin, email);
await this.page.fill(this.TxtBxPassword, password);
await this.page.click(this.BtnLogin);
await this.page.waitForNavigation({waitUntil: `domcontentloaded`});
};
//Profile Page
profilePopupClickCancel = async () => {
await this.page.waitForSelector(this.BtnProfileCancel);
await this.page.click(this.BtnProfileCancel);
await this.page.waitForNavigation({waitUntil: `domcontentloaded`});
};
profilePopupClickContinue = async () => {
await this.page.waitForSelector(this.BtnProfileContinue);
await this.page.click(this.BtnProfileContinue);
await this.page.waitForNavigation({waitUntil: `domcontentloaded`});
};
//Groups Page
groupsPageClickOnNotNow = async () => {
await this.page.waitForSelector(this.BtnGroupsNotNow);
await this.page.click(this.BtnGroupsNotNow);
await this.page.waitForNavigation({waitUntil: `domcontentloaded`});
};
groupsPageClickOnOk = async () => {
await this.page.waitForSelector(this.BtnGroupsOk);
await this.page.click(this.BtnGroupsOk);
await this.page.waitForNavigation({waitUntil: `domcontentloaded`});
};
This is how I have implemented. This would expose the selectors too just in case if you want to assert them in a page.
Thank you for all the answers!
How would this all look like with TypeScript? Did anyone tried doing it in TS?
I ended up here after a random google search, but thought I would add my 2 cents for anyone else that comes across this, in case it helps.
I made my own page object framework a while back in Python, and intend to port it over to JS/TS when I get a good amount of time and figure out a nice, idiomatic way to do it in those languages (I used descriptors in Python, and JS has a heavy focus on async stuff, so it doesn't translate perfectly).
The term "Page Object Model" was, historically, very overloaded and misused, and led a ton of people to less-than-effective and complex implementations. The "Model" part of that is also meant to indicate that the term represents a design pattern, so an individual implementation would _adhere to/follow_ that model, and the implementation would just be a "page object". To help clear this up, new terminology was established (beyond just the model vs object thing), and now, "page component objects" are an explicit thing in the "Page Object Model".
My framework is neither a model, nor an object (although it does have objects, and adheres to the Page Object Model), i.e. it is a framework that you can use to build individual page component objects and page objects around. The terminology can help a lot when trying to figure something out.
These components are basically just objects, where each one:
(Keep in mind, that neither the page object nor the component objects should have any idea that they're running in a test. The goal is to separate concerns, not conflate them)
This is called "object composition", and helps _immensely_ by compartmentalizing logic, making things reusable, and avoiding circular dependencies, like @tymfear mentioned.
You could make each component/page ad hoc, having all the code contained therein, with no inheritance, but I strongly recommend against it. Instead, if you can't find an already existing framework, I recommend making your own. Mine took me a while to make, but that's only because I wanted to be super fancy with it. But really, something just as effective, albeit less fancy shouldn't take much time. I'm thinking something like this:
class Page {
constructor(driver) {
this.driver = driver;
}
async waitUntil(callback, timeout) {
let endTime = new Date()).getTime() + timeout;
let result = await callback(this).catch( err => false);
while (!result && new Date()).getTime() < endTime){
new Promise(resolve => setTimeout(resolve, 500));
result = await callback(this).catch( err => false);
}
if (result) {
return result;
}
throw new TimeoutError();
}
}
class PageComponent {
constructor(driver, parent) {
this.driver = driver;
this.parent = parent;
}
async getElement() {
return await this.driver.findElement(this._locator);
}
async sendKeys(keys) {
await this.getElement().sendKeys(keys);
}
async waitUntil(callback, timeout) {
let endTime = new Date()).getTime() + timeout;
let result = await callback(this).catch( err => false);
while (!result && new Date()).getTime() < endTime){
new Promise(resolve => setTimeout(resolve, 500));
result = await callback(this).catch( err => false);
}
if (result) {
return result;
}
throw new TimeoutError();
}
}
and then you could have:
class UsernameField extends PageComponent {
_locator = By.CSS("#username");
}
class PasswordField extends PageComponent {
_locator = By.CSS("#username");
}
class LoginForm extends PageComponent {
_locator = By.CSS("form#login");
constructor(driver, parent) {
super(driver, parent);
this.username = new UsernameField(this.driver, this);
this.password = new PasswordField(this.driver, this);
}
async fillOut(user) {
await this.username.sendKeys(user.username);
await this.password.sendKeys(user.password);
}
await submit() {
await this.getElement().submit();
}
}
class LoginPage extends Page {
constructor(driver) {
super(driver);
this.loginForm = new LoginForm(driver, this);
// make responsible for knowing how to wait for the DOM to finish updating when the page is first loaded
this.waitUntil(page => await page.loginForm.getElement().isDisplayed());
}
async login(user) {
await this.loginForm.fillOut(user);
await this.loginForm.submit();
}
}
I have no idea if that'll work as is, but hopefully that gets the idea across.
With this approachh, Playwright, Puppeteer, Selenium, whatever; it doesn't really matter. The browser-driving agent is just that: an interface that your page objects can use to tell the browser what to do/get. In regards to implementing page objects, the difference between the agents is like the difference between a brick and a rock when it comes to hammering a nail; it really just affects the particulars of how exactly you smack the nail with it.
In this case, it's just a few pieces of the framework that would need to be adjusted depending on the API of the particular API you're using. My examples used my rough recollection of Selenium's JS API, but as an example for getting the associated element, for Playwright, instead of return await this.driver.findElement(this._locator), it would be return await this.page.$(this._locator) (the locator would also need to change for Playwright's needs, but you get the idea).
Hopefully this helps!
hi all,
I would to use page object model and using playwright docs I added (just an example):
class LoginPage {
constructor(page) {
this.page = page
}
async doSmth() {
await this.page.waitForSelector('selector')
}
}
module.exports = { LoginPage }
and in a test file I want to import LoginPage once and use it across several tests (using it , mocha runner).
but what only works for me is doing in each test/hook:
const loginPage = new LoginPage(page)
await loginPage.doSmth()
@arjunattam maybe you could advice smth.
thanks in advance
@akapitoha if you're looking to run multiple assertions against a page when it's in a particular state, without having any of those assertions prevent any of the others from running should they fail, then you can bundle all your its together in a describe block, and use a before to set everything up, instead of a beforeEach. Then you can have as many assertions as you want by putting them in their own its. But be careful not to make any state changing calls in those its, as that can make your tests problematic as they'll be dependent on executing in a very specific order, and won't be able to be run on their own.
Thanks @akapitoha. @SalmonMode's suggestion is excellent, and I would recommend this approach for Mocha.
We are experimenting with a different approach in playwright-test with which you can provide your tests with a loginPage. This is called fixtures -- full syntax here. We are collecting feedback on playwright-test and working towards making it stable.
// In fixtures.ts
builder.loginPage.init(async ({page}, runTest) => {
// get built-in page and wrap with POM
const loginPage = new LoginPage(page);
// pass this to your test
runTest(loginPage);
});
// In your test
it('should login', async ({ loginPage }) => {
// use loginPage in your test function
});
@SalmonMode @arjunattam thanks a lot for your quick replies.
guess I already try to implement suggestion with describe block (if I understood it correctly), like
const { LibraryPage } = require("../pages/libraryPage.js")
for (const browserType of ["chromium", "webkit"]) {
describe("login", async () => {
let browser
let page
const loginPage = new LoginPage(page)
before(async () => {
browser = await playwright[browserType].launch({ headless: true })
page = await browser.newPage()
await page.setDefaultTimeout(15000)
await page.setDefaultNavigationTimeout(30000)
})
it(`(${browserType}): test 1`, async () => {
await loginPage.navigate()
})
it(`(${browserType}): test 2`, async () => {
await loginPage.navigate()
})
})
}
in both tests I try to call a function from PO, but get TypeError: Cannot read property 'goto' of undefined
(goto is used in navigate()
I'm guessing loginPage.navigate() is a state changing call, so I wouldn't use it in the it. I'd have to see the rest of the code to know what's fully going on though.
it just opens a login page
class LoginPage {
constructor(page) {
this.page = page
}
async navigate() {
await this.page.goto("login_page_url")
}
}
module.exports = { LoginPage }
Ohhh, I see what's going on. It's undefined, because you passed in undefined when you first made the page object. My bad.
Try instantiating your page object in the before right after you assign a value to page.
yeah, works now across all its after adding loginPage = new LoginPage(page) in before hook. thank you so much!