{"id":10041,"date":"2025-09-27T12:40:00","date_gmt":"2025-09-27T12:40:00","guid":{"rendered":"https:\/\/blog.testgrid.io\/?p=10041"},"modified":"2025-10-08T16:28:14","modified_gmt":"2025-10-08T16:28:14","slug":"playwright-page-object-model","status":"publish","type":"post","link":"https:\/\/testgrid.io\/blog\/playwright-page-object-model\/","title":{"rendered":"Playwright POM Tutorial: How to Build Maintainable Tests with Page Objects"},"content":{"rendered":"\n<p>Test automation eases the test execution in the software development lifecycle but it comes with its own set of challenges. One of the most important aspects of writing effective automation suites is their maintainability. To be able to easily scale the frameworks it is very essential to write clear, modular and segregated scripts to minimise inter dependence. If the scripts are written without any format it can lead to a lot of overhead in their maintenance. This is where the Page Object Model comes to the rescue. In this article we will be discussing the Page Object Model and how we can leverage its benefits in Playwright tests.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What is a Page Object Model?<\/h2>\n\n\n\n<p>Page Object Model, or POM is an automation pattern that helps to create reusable components by encapsulating elements of a web page along with their interactions in a single class. These classes are a representation of each web page of the application and serve as reusable components.<\/p>\n\n\n\n<p>Each page object class contains the implementation details to facilitate interaction with the user interface.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What are the benefits of using Page Object Model?<\/h2>\n\n\n\n<p>Page Object Model comes with several benefits for creation of robust and scalable frameworks. Some of the advantages are discussed below-<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Improves Test Code Maintainability\n<ul class=\"wp-block-list\">\n<li>The test code becomes cleaner and easier to maintain by encapsulating page interactions.<\/li>\n\n\n\n<li>If the UI is updated anywhere in the future, it is easy to update only the page class, reducing the need to make huge changes in the framework.<\/li>\n\n\n\n<li>Whenever new tests are added, only page object methods are required without the need to work on element locators again.<\/li>\n\n\n\n<li>Segregation between test code and the UI interaction code is promoted.<br><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Promotes Reusability\n<ul class=\"wp-block-list\">\n<li>Using page object classes, we can share the common actions across multiple tests.<\/li>\n\n\n\n<li>Since page objects encapsulate reusable logic, code duplicity is reduced.<\/li>\n\n\n\n<li>For example, a login page can be used for multiple tests using the login method.<br><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Hides UI Implementation Details\n<ul class=\"wp-block-list\">\n<li>Test methods or test scripts work with methods like login(), search(), etc. directly without the need to expose the code within these methods.<\/li>\n\n\n\n<li>Test scripts do not directly interact with the UI elements, hence protecting the scripts from the effect of UI changes.<br><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Robust Tests, Less Vulnerable to UI Changes\n<ul class=\"wp-block-list\">\n<li>The test scripts are less brittle to UI changes, if any UI element is updated, only the page object class needs to be updated.<\/li>\n\n\n\n<li>As compared to updating many locators spread across the script, it is very easy to maintain the scripts using POM.<br><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Clean Separation of Test Code and Page Interactions\n<ul class=\"wp-block-list\">\n<li>The test cases logic is focused on validations rather than UI details.<\/li>\n\n\n\n<li>The test actions are translated to UI interactions using the page object.<\/li>\n\n\n\n<li>Separation makes code modular and easier to maintain.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">What are the disadvantages related to Page Object Model?<\/h2>\n\n\n\n<p>While the Page Object Model provides a lot of benefits, there are some potential drawbacks that one should consider.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Complexity due to additional classes\n<ul class=\"wp-block-list\">\n<li>Other than maintaining the tests, additional page object classes are introduced.<\/li>\n\n\n\n<li>More files and classes are created in the project structure.<\/li>\n\n\n\n<li>New members might take some time to understand the page object structure.<br><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Page Object needs to sync with UI\n<ul class=\"wp-block-list\">\n<li>If there is any update in the UI, the page object needs to be updated to accommodate the changes.<\/li>\n\n\n\n<li>If any additional feature is added to the web page, corresponding page objects might need addition of new methods to handle the same.<\/li>\n\n\n\n<li>Using page objects will not eliminate the need of maintenance, any change in the flow, locators will still require maintenance of the page objects.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Abstraction layer is added\n<ul class=\"wp-block-list\">\n<li>Although abstraction helps hide UI implementation details, it can become difficult to access or verify elements at times.<\/li>\n\n\n\n<li>It can sometimes lead to difficulty in debugging issues.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<p>Now that we understand what page object model is and how it can be helpful to us, let us see how we can implement it in our <a href=\"https:\/\/testgrid.io\/blog\/playwright-testing\/\" data-type=\"link\" data-id=\"https:\/\/testgrid.io\/blog\/playwright-testing\/\">Playwright Tests<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Implementing Page Object Model for Playwright Tests<\/h2>\n\n\n\n<p>To begin writing Playwright tests, ensure that you have downloaded and installed <a href=\"https:\/\/code.visualstudio.com\/\" target=\"_blank\" rel=\"noopener\">Visual Studio Code<\/a>. Also, download and install <a href=\"https:\/\/nodejs.org\/en\/download\" target=\"_blank\" rel=\"noopener\">Node JS<\/a> in your system.<\/p>\n\n\n\n<p>Let us now look through the steps to creating the structure of our Playwright Page Object Model project.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Create a new directory for the project. I have created a directory <strong><em>PlaywrightPOM <\/em><\/strong>in my system.<\/li>\n\n\n\n<li>Next, we open the directory in Visual Studio Code(VS Code)we created in Step#1. Click on <em>File &gt; Open &gt; Select the directory name you have created(PlaywrightPOM)<\/em>.<br>You will see it in the VS Code explorer as shown below-<\/li>\n<\/ol>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" width=\"474\" height=\"322\" src=\"https:\/\/testgrid.io\/blog\/wp-content\/uploads\/2023\/10\/playwright-install.png\" alt=\"Playwright POM directory\" class=\"wp-image-10042\" loading=\"lazy\" title=\"\"><\/figure>\n\n\n\n<p>3.We next install playwright, by clicking on <em>Terminal &gt; New Terminal<\/em>. In the terminal write the below command:<\/p>\n\n\n\n<pre class=\"wp-block-code has-white-color has-black-background-color has-text-color has-background\"><code>npm init playwright@latest\n\n<\/code><\/pre>\n\n\n\n<p>This command will prompt you to answer certain questions, select them as per your requirement.<\/p>\n\n\n\n<p>Once the above command is executed, you will see that files and folders have been added to the project as shown in the snapshot below-<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" width=\"961\" height=\"694\" src=\"https:\/\/testgrid.io\/blog\/wp-content\/uploads\/2023\/10\/image4-1.png\" alt=\"\" class=\"wp-image-10043\" loading=\"lazy\" title=\"\"><\/figure>\n\n\n\n<p>4. Next, we will create two more folders viz, pages and utilities in the project directory. The pages folder will hold the page objects of our page object model. The Utilities folder on the other hand will contain common utilities(if any) for the project.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" width=\"278\" height=\"307\" src=\"https:\/\/testgrid.io\/blog\/wp-content\/uploads\/2023\/10\/image3-2.png\" alt=\"\" class=\"wp-image-10045\" loading=\"lazy\" title=\"\"><\/figure>\n\n\n\n<p>5. Before we start writing the scripts we will install the browsers by entering the below command in the terminal:<\/p>\n\n\n\n<pre class=\"wp-block-code has-white-color has-black-background-color has-text-color has-background\"><code>npx playwright install<\/code><\/pre>\n\n\n\n<p>6.We are now all set with our project structure and we will now start writing our tests, but before that let us see the use case steps that we will be using to implement the POM for Playwright Test.<\/p>\n\n\n\n<p>        a. Navigate to testgrid.io.<\/p>\n\n\n\n<p>        b. Click on the Get Started button.<\/p>\n\n\n\n<p>        c. Enter details in the Sign Up Form.<\/p>\n\n\n\n<p><em><strong>Note that you can automate your own use case by following the approach mentioned below.<\/strong><\/em><\/p>\n\n\n\n<p>7. To achieve thi,s we will be using Typescript. Let us first create one file inside the pages folder and name it home.page.ts. In this fil,e we will write our code to locate the elements on the page and perform actions on them:<\/p>\n\n\n\n<p><br><strong><em>home.page.ts<\/em><\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code has-white-color has-black-background-color has-text-color has-background\"><code>import { expect, Locator, Page } from '@playwright\/test';\nexport class TestGridHomePage{\nreadonly url=\"https:\/\/testgrid.io\/\";\nreadonly page: Page;\nreadonly getStartedButton: Locator;\n\n\nconstructor(page: Page){\nthis.page = page;\nthis.getStartedButton = page.locator('text=Get started');\n}\n\n\nasync goTo(){\nawait this.page.goto(this.url);\n}\n\n\nasync getStarted(){\nawait this.getStartedButton.click();\n}\n\n}\n<\/code><\/pre>\n\n\n\n<p>Code Walkthrough<\/p>\n\n\n\n<pre class=\"wp-block-code has-white-color has-black-background-color has-text-color has-background\"><code>import { expect, Locator, Page } from '@playwright\/test';<\/code><\/pre>\n\n\n\n<p>Imports Playwright test library classes that will be used in the page object<\/p>\n\n\n\n<pre class=\"wp-block-code has-white-color has-black-background-color has-text-color has-background\"><code>export class TestGridHomePage{<\/code><\/pre>\n\n\n\n<p>Defines the TestGridHomePage class that will contain the page object logic<\/p>\n\n\n\n<pre class=\"wp-block-code has-white-color has-black-background-color has-text-color has-background\"><code>readonly url=\"https:\/\/testgrid.io\/\";\nreadonly page: Page;\nreadonly getStartedButton: Locator;<\/code><\/pre>\n\n\n\n<p>Read only fields are declared where-<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong><em>Url<\/em><\/strong>, is used to declare the TestGrid base URL.<\/li>\n\n\n\n<li>&nbsp;<strong><em>Page<\/em><\/strong>, field of type Page that will reference the Playwright Page object.<\/li>\n\n\n\n<li><strong><em>getStartedButton<\/em><\/strong>, locator field to store the Get Started button locator.<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code has-white-color has-black-background-color has-text-color has-background\"><code>constructor(page: Page){\nthis.page = page;\nthis.getStartedButton = page.locator('text=Get started');\n}\n<\/code><\/pre>\n\n\n\n<p>Constructor is created to accept the Page object and initialise the page and locator fields.<\/p>\n\n\n\n<pre class=\"wp-block-code has-white-color has-black-background-color has-text-color has-background\"><code>async goTo(){\nawait this.page.goto(this.url);\n}\n<\/code><\/pre>\n\n\n\n<p>goTo() method is used to navigate to the base url.<\/p>\n\n\n\n<pre class=\"wp-block-code has-white-color has-black-background-color has-text-color has-background\"><code>async getStarted(){\nawait this.getStartedButton.click();\n}\n<\/code><\/pre>\n\n\n\n<p>getStarted() method is used to click on the Get Started button.<\/p>\n\n\n\n<p>We will create another page class, viz, register..page.ts that will be used to hold the elements of the Sign Up page that we see after clicking on the Get Started button. The code for the same will be:<\/p>\n\n\n\n<p><strong><em>register.page.ts<\/em><\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code has-white-color has-black-background-color has-text-color has-background\"><code>import { expect, Locator, Page } from '@playwright\/test';\nexport class RegistrationPage{\nreadonly page: Page;\nreadonly fullName: Locator;\nreadonly email: Locator;\nreadonly mobile: Locator;\nreadonly password: Locator;\nreadonly signUpButton: Locator;\n\n\nconstructor(page: Page){\nthis.page = page;\nthis.fullName = page.locator('id=full_name');\nthis.email = page.locator('id=business_email');\nthis.mobile = page.locator('id=mobile_number');\nthis.password = page.locator('id=password');\nthis.signUpButton = page.locator('id=signin-button');\n}\n\n\nasync fillForm(){\nawait this.fullName.fill(\"Your Name\");\nawait this.email.fill(\"Your email id\");\nawait this.mobile.fill(\"Your mobile number\");\nawait this.password.fill(\"Your choice password\");\nawait this.signUpButton.click();\n}\n\n\n}\n\n<\/code><\/pre>\n\n\n\n<p>Code Walkthrough<\/p>\n\n\n\n<pre class=\"wp-block-code has-white-color has-black-background-color has-text-color has-background\"><code>readonly page: Page;\nreadonly fullName: Locator;\nreadonly email: Locator;\nreadonly mobile: Locator;\nreadonly password: Locator;\nreadonly signUpButton: Locator;<\/code><\/pre>\n\n\n\n<p>Read only fields are declared for the page, and the sign up form elements.<\/p>\n\n\n\n<pre class=\"wp-block-code has-white-color has-black-background-color has-text-color has-background\"><code>constructor(page: Page){\nthis.page = page;\nthis.fullName = page.locator('id=full_name');\nthis.email = page.locator('id=business_email');\nthis.mobile = page.locator('id=mobile_number');\nthis.password = page.locator('id=password');\nthis.signUpButton = page.locator('id=signin-button');\n}\n<\/code><\/pre>\n\n\n\n<p>A constructor is created to initialise the page and its elements using the locators.<\/p>\n\n\n\n<pre class=\"wp-block-code has-white-color has-black-background-color has-text-color has-background\"><code>async fillForm(){\nawait this.fullName.fill(\"Your Name\");\nawait this.email.fill(\"Your email id\");\nawait this.mobile.fill(\"Your mobile number\");\nawait this.password.fill(\"Your choice password\");\nawait this.signUpButton.click();\n}\n<\/code><\/pre>\n\n\n\n<p>The fillForm() method is used to fill in the details to the sign up form. You can add more lines of code to the method to handle specific scenarios in your case.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>8. The next step is to write the test file to execute our test case. We create home.test.ts file and write the below code in it:<\/p>\n\n\n\n<pre class=\"wp-block-code has-white-color has-black-background-color has-text-color has-background\"><code>import{test,expect} from '@playwright\/test';\nimport { TestGridHomePage } from \"..\/pages\/home.page\";\nimport { RegistrationPage } from '..\/pages\/register.page';\ntest('TestGrid Get Started Flow',async ({page}) =&gt; {\nconst homePage = new TestGridHomePage(page);\nconst regPage = new RegistrationPage(page);\nawait homePage.goTo();\nawait homePage.getStarted();\nconst delay = ms =&gt; new Promise(resolve =&gt; setTimeout(resolve, ms));\nawait delay(10000);\nawait regPage.fillForm();\n})\n<\/code><\/pre>\n\n\n\n<p>Code Walkthrough<\/p>\n\n\n\n<pre class=\"wp-block-code has-white-color has-black-background-color has-text-color has-background\"><code>import { TestGridHomePage } from \"..\/pages\/home.page\";\nimport { RegistrationPage } from '..\/pages\/register.page';<\/code><\/pre>\n\n\n\n<p>We import the required pages in the test.<\/p>\n\n\n\n<pre class=\"wp-block-code has-white-color has-black-background-color has-text-color has-background\"><code>const homePage = new TestGridHomePage(page);\nconst regPage = new RegistrationPage(page);<\/code><\/pre>\n\n\n\n<p>References to the pages of the POM are created using which we will refer the methods of the page classes.<\/p>\n\n\n\n<pre class=\"wp-block-code has-white-color has-black-background-color has-text-color has-background\"><code>await homePage.goTo();\nawait homePage.getStarted();<\/code><\/pre>\n\n\n\n<p>The goTo() and the getStarted() methods of the home page are called to navigate to the url and click on the Get Started Button respectively.<\/p>\n\n\n\n<pre class=\"wp-block-code has-white-color has-black-background-color has-text-color has-background\"><code>const delay = ms =&gt; new Promise(resolve =&gt; setTimeout(resolve, ms));\nawait delay(5000);<\/code><\/pre>\n\n\n\n<p>We are using promise to set a timeout to handle network lags here. You may use other ways to handle timeout scenarios in your project as per your requirement.<\/p>\n\n\n\n<pre class=\"wp-block-code has-white-color has-black-background-color has-text-color has-background\"><code>await regPage.fillForm();<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Finally, we call the fillForm() method to fill up the details in the sign up form using the registration page object.<\/li>\n<\/ul>\n\n\n\n<p>Once these files are created, your project directory would look like below-<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" width=\"259\" height=\"435\" src=\"https:\/\/testgrid.io\/blog\/wp-content\/uploads\/2023\/10\/image6-2.png\" alt=\"\" class=\"wp-image-10044\" loading=\"lazy\" title=\"\"><\/figure>\n\n\n\n<p>9. You can now execute this test file using the command below:<\/p>\n\n\n\n<pre class=\"wp-block-code has-white-color has-black-background-color has-text-color has-background\"><code>npx playwright test home.test.ts \u2013headed<\/code><\/pre>\n\n\n\n<p>By default, playwright executes in headless mode, hence use \u2013headed to execute the test in headed mode. Once this command is executed, you will see that your test is executed and the results are displayed in the console as below-<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" width=\"382\" height=\"111\" src=\"https:\/\/testgrid.io\/blog\/wp-content\/uploads\/2023\/10\/image1-3.png\" alt=\"\" class=\"wp-image-10047\" loading=\"lazy\" title=\"\"><\/figure>\n\n\n\n<p>Additionally, you can look at the generated report to look at the results of execution:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" width=\"1134\" height=\"672\" src=\"https:\/\/testgrid.io\/blog\/wp-content\/uploads\/2023\/10\/image2-2.png\" alt=\"\" class=\"wp-image-10048\" loading=\"lazy\" title=\"\"><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Conclusion<\/h3>\n\n\n\n<p>To summarise the Page Object Model, we can conclude on the following points:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Page Object Model, POM is a design pattern that helps to create a robust and maintainable automation framework.<\/li>\n\n\n\n<li>Due to the ease of handling the scripts and segregating the test logic from the UI interactions, POM helps in creating efficient and scalable frameworks.<\/li>\n\n\n\n<li>Although POM makes the tests more readable and maintainable, it might sometimes create problems in debugging issues.<\/li>\n\n\n\n<li>POM eases the process of updating the page objects in case of changes in the UI or the flow, by just having the need to update the page object classes with minimal need to touch the test scripts.<\/li>\n\n\n\n<li>POM when implemented in a clear and concise manner can help in creating modular frameworks, reusing page objects wherever necessary.<\/li>\n<\/ul>\n\n\n\n<p><br><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Test automation eases the test execution in the software development lifecycle but it comes with its own set of challenges. One of the most important aspects of writing effective automation suites is their maintainability. To be able to easily scale the frameworks it is very essential to write clear, modular and segregated scripts to minimise [&hellip;]<\/p>\n","protected":false},"author":29,"featured_media":10050,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"footnotes":""},"categories":[209],"tags":[],"class_list":["post-10041","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-test-automation"],"acf":[],"images":{"medium":"https:\/\/testgrid.io\/blog\/wp-content\/uploads\/2023\/10\/playwright-POM.jpg","large":"https:\/\/testgrid.io\/blog\/wp-content\/uploads\/2023\/10\/playwright-POM.jpg"},"_links":{"self":[{"href":"https:\/\/testgrid.io\/blog\/wp-json\/wp\/v2\/posts\/10041","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/testgrid.io\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/testgrid.io\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/testgrid.io\/blog\/wp-json\/wp\/v2\/users\/29"}],"replies":[{"embeddable":true,"href":"https:\/\/testgrid.io\/blog\/wp-json\/wp\/v2\/comments?post=10041"}],"version-history":[{"count":6,"href":"https:\/\/testgrid.io\/blog\/wp-json\/wp\/v2\/posts\/10041\/revisions"}],"predecessor-version":[{"id":15355,"href":"https:\/\/testgrid.io\/blog\/wp-json\/wp\/v2\/posts\/10041\/revisions\/15355"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/testgrid.io\/blog\/wp-json\/wp\/v2\/media\/10050"}],"wp:attachment":[{"href":"https:\/\/testgrid.io\/blog\/wp-json\/wp\/v2\/media?parent=10041"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/testgrid.io\/blog\/wp-json\/wp\/v2\/categories?post=10041"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/testgrid.io\/blog\/wp-json\/wp\/v2\/tags?post=10041"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}