Introduction
Once you've implemented the Page Object Model (POM), you'll quickly notice another problem.
Many of your page classes contain the same code: opening URLs, waiting for elements, clicking buttons, typing into text boxes, taking screenshots, waiting for loaders to disappear, reading toast messages, scrolling to elements, and handling dialogs.
If every Page Object repeats these methods, your framework becomes difficult to maintain. Professional automation frameworks solve this by introducing a Base Page. A Base Page is a parent class that contains all the common functionality shared across multiple pages. Every Page Object extends this class and automatically inherits those reusable methods.
In this tutorial, you'll learn why a Base Page is important, how to design one in Playwright, what methods belong in it, what should never go inside it, and how enterprise teams structure their automation frameworks.
What is a Base Page?
A Base Page is a reusable parent class that contains common functionality used by multiple pages. Instead of writing the same methods repeatedly, every Page Object inherits them. Think of it as the foundation of your automation framework.
BasePage
│
┌─────────────┼─────────────┐
│ │ │
LoginPage DashboardPage ProductsPage
│ │ │
└─────────────┼─────────────┘
Shared Common MethodsThis keeps your framework clean, consistent, and easier to maintain.
Why Do You Need a Base Page?
Imagine you have 80 Page Objects, 2,500 automated tests, and 12 automation engineers. Every page needs methods like navigate, wait, screenshot, scroll, and verify page title. Without a Base Page, these methods would be duplicated dozens of times. With a Base Page, they're written once and reused everywhere.
Project Structure
A clean enterprise framework might look like this:
playwright-framework/
pages/
│
├── BasePage.ts
├── LoginPage.ts
├── DashboardPage.ts
├── ProductsPage.ts
├── CheckoutPage.ts
utils/
fixtures/
tests/
api/
data/Every page inherits from BasePage.
Creating the Base Page
Create pages/BasePage.ts:
import { Page, Locator } from '@playwright/test';
export class BasePage {
constructor(protected page: Page) {}
}Every child page will now have access to the Playwright page object.
Navigation Method
Instead of writing await page.goto('/login'); everywhere, create a reusable navigate method.
async navigate(url: string) {
await this.page.goto(url);
}Usage:
await loginPage.navigate('/login');Simple and reusable.
Get Current URL
async currentUrl() {
return this.page.url();
}Usage:
const url = await loginPage.currentUrl();Get Page Title
async title() {
return await this.page.title();
}Useful for validations and debugging.
Refresh Page
async refresh() {
await this.page.reload();
}Go Back
async back() {
await this.page.goBack();
}Go Forward
async forward() {
await this.page.goForward();
}Take Screenshot
Screenshots are extremely useful when debugging failed tests.
async screenshot(name: string) {
await this.page.screenshot({
path: `screenshots/${name}.png`
});
}Usage:
await dashboardPage.screenshot('Dashboard');Wait for Page Load
Instead of repeating await page.waitForLoadState('networkidle'); create a reusable method.
async waitForPageLoad() {
await this.page.waitForLoadState('networkidle');
}Scroll to Element
async scroll(locator: Locator) {
await locator.scrollIntoViewIfNeeded();
}Many applications require scrolling before interacting with elements.
JavaScript Click
Occasionally, standard clicks don't work due to overlays or custom UI components.
async jsClick(locator: Locator) {
await locator.evaluate((element) => {
(element as HTMLElement).click();
});
}Use this sparingly and only when necessary.
Highlight an Element
Helpful for debugging.
async highlight(locator: Locator) {
await locator.evaluate((element) => {
(element as HTMLElement).style.border =
'3px solid red';
});
}Many automation engineers enable this during local debugging.
Wait for Loader
Enterprise applications often display loading spinners.
async waitForLoader(loader: Locator) {
await loader.waitFor({
state: 'hidden'
});
}Now every page can use the same waiting strategy.
Read Toast Messages
Many applications display success notifications.
async getToastMessage(toast: Locator) {
return await toast.textContent();
}Reusable across every page.
Handling Dialogs
async acceptDialog() {
this.page.on('dialog', async dialog => {
await dialog.accept();
});
}Similarly:
async dismissDialog() {
this.page.on('dialog', async dialog => {
await dialog.dismiss();
});
}Waiting for URL
async waitForUrl(url: string) {
await this.page.waitForURL(url);
}Useful after navigation.
Common Keyboard Actions
async pressEnter() {
await this.page.keyboard.press('Enter');
}Download Handling
Many enterprise applications export reports.
const downloadPromise = this.page.waitForEvent('download');Wrapping download logic in the Base Page avoids repeating the same implementation.
Extending BasePage
Now your Login Page becomes:
import { BasePage } from './BasePage';
export class LoginPage extends BasePage {
async login(username: string, password: string) {
await this.page.getByLabel('Username')
.fill(username);
await this.page.getByLabel('Password')
.fill(password);
await this.page.getByRole('button', {
name: 'Login'
}).click();
}
}Notice that LoginPage automatically inherits every Base Page method.
Combining Base Page with Locator Helpers
If you've created custom locator wrapper functions, your page becomes even cleaner. Instead of this.page.getByRole('button', { name: 'Login' }); you can simply write this.locator.button('Login');. This keeps locator strategies centralized and consistent across the framework.
import { BasePage } from './BasePage';
import { LocatorHelper } from '../utils/LocatorHelper';
export class LoginPage extends BasePage {
private locator = new LocatorHelper(this.page);
async login(username: string, password: string) {
await this.locator.textbox('Username').fill(username);
await this.locator.textbox('Password').fill(password);
await this.locator.button('Login').click();
}
}What Should NOT Go Into Base Page?
A common mistake is turning the Base Page into a God Class. Avoid placing page-specific business logic here. For example, login(), checkout(), addProduct(), and searchProduct() do not belong in the Base Page. These methods belong in their respective Page Objects. The Base Page should only contain generic functionality shared across many pages.
Best Practices
- Keep the Base Page focused on reusable utilities.
- Use inheritance only for truly common behavior.
- Prefer Playwright's built-in APIs before creating custom wrappers.
- Keep methods small and easy to understand.
- Combine the Base Page with the Page Object Model and Fixtures.
- Document shared methods so new team members can discover them easily.
Common Mistakes
- Making the Base Page hundreds of lines long.
- Adding business workflows to the Base Page.
- Duplicating helper methods in child pages.
- Ignoring Playwright's built-in waiting mechanisms.
- Mixing API utilities into UI Base Pages.
Enterprise Framework Example
A mature Playwright framework often looks like this:
pages/
BasePage.ts
LoginPage.ts
DashboardPage.ts
ProductsPage.ts
OrdersPage.ts
CheckoutPage.ts
AdminPage.ts
ProfilePage.tsEvery page inherits navigation, waiting, screenshots, scrolling, and shared UI utilities from BasePage, while each child class contains only the functionality unique to that page.
Frequently Asked Questions
Is a Base Page mandatory?
No. Small projects can work without one. However, for medium and large automation frameworks, a Base Page greatly improves consistency and reduces code duplication.
Should every Page Object extend BasePage?
Usually yes, provided the Base Page contains only generic UI functionality. If a page has unique behavior that doesn't benefit from inheritance, composition is another valid design choice.
Can I have multiple Base Pages?
Absolutely. Many enterprise frameworks create specialized base classes such as BasePage, AuthenticatedPage, AdminBasePage, and ModalBasePage. Each serves a different purpose while keeping inheritance focused.
Should assertions go in the Base Page?
No. The Base Page should provide reusable utilities. Assertions generally belong in your test files or dedicated assertion/helper classes.
Conclusion
A well-designed Base Page is one of the cornerstones of a scalable Playwright automation framework. It eliminates duplication, promotes consistency, and allows Page Objects to focus on business actions instead of low-level browser interactions.
The key is balance: keep the Base Page lightweight, reusable, and framework-oriented. Combine it with the Page Object Model, custom locator helpers, fixtures, and environment configuration, and you'll have a solid foundation that can support thousands of automated tests.
In the next tutorial, we'll explore the Playwright Component Object Model (COM), where you'll learn how to model reusable UI components like headers, navigation menus, sidebars, modals, and data tables.
Related Tutorials
- Playwright Page Object Model (POM)
- Custom Locator Helper Functions
- Playwright Project Structure Explained
- Playwright Fixtures Explained
- Playwright Component Object Model
- Enterprise Playwright Framework Architecture
- Playwright Best Practices
- Playwright Design Patterns
- Playwright Interview Questions
- Playwright Utility Classes
Keywords: Playwright Base Page, Playwright BasePage, Base Page Design, Playwright framework architecture, Playwright Page Object Model, Playwright reusable methods, Playwright automation framework, Playwright TypeScript, enterprise Playwright framework, Playwright best practices.
Get Playwright tutorials in your inbox
Weekly tips, real-world examples, and framework patterns – no spam, unsubscribe anytime.
Playwright Framework Series
View all →- 1Getting Started with Playwright: Installation, Setup, and Your First Test
- 2Playwright Locators: The Complete Guide with Real-World Examples
- 3Playwright Actions: Complete Guide to Click, Fill, Hover, Keyboard, Mouse & File Upload
- 4Playwright Assertions: The Complete Guide with Real-World Examples
- 5Playwright Auto Waiting: The Complete Guide with Real-World Examples
- 6Playwright Fixtures: The Complete Guide with Real-World Examples
- 7Playwright Browser Context & Multiple Tabs: The Complete Guide with Real-World Examples
- 8Playwright Authentication & Session Management: Complete Guide with Enterprise Examples
- 9Playwright Network Interception & API Mocking: Complete Guide with Real-World Examples
- 10Playwright Page Object Model (POM): The Complete Guide with Enterprise Examples
- 11Page Object Model with Playwright: A Practical Guide
- 12How to Build a Robust Professional Playwright Framework from Scratch (Step-by-Step)
- 13Building an Enterprise Playwright Framework from Scratch
API Testing Series
View all →- 1Playwright API Testing: The Complete Guide with Real-World Examples
- 2Part 1 : Playwright API Testing Tutorial: Build Enterprise-Level API Automation Framework
- 3Part 2: Playwright API Testing Tutorial: Setting Up Project & Sending Your First API Request
- 4Part 3: Mastering CRUD Operations in Playwright API Testing
- 5Part 4: Authentication in Playwright API Testing (Bearer Token, JWT, API Key & Reusable Fixtures)
- 6Part 5: Building an Enterprise-Level Playwright API Automation Framework
- 7Part 6: API Models, Schema Validation & Test Data Management in Playwright
- 8Part 7: Custom Fixtures, Hooks, Logging & Parallel Execution in Playwright API Testing
- 9Part 8: Building a Production-Ready Playwright API Framework (Environment Management, CI/CD, Reporting & Best Practices)
- 10Part 9: Advanced Playwright API Testing Techniques for Enterprise Automation
- 11Part 10: Building a Complete Enterprise Playwright API Automation Framework (Final Part)
- 12API Testing with Playwright: Request Context, Auth, and Assertions