๐ฏ Importance of Design Pattern
The importance of employing design patterns in test automation cannot be overstated! It serves as a blueprint for organizing interaction with the user interface (UI) elements of web pages in a structured and reusable manner. ๐ญ
๐ก What is a Design Pattern? A proven solution to common problems in software design that provides a template for how to solve problems in various situations
๐ Why Design Patterns Matter
Design patterns provide several critical benefits:
- ๐ง Enhanced Maintainability - Centralized UI changes management
- ๐ Improved Readability - Cleaner, more efficient code
- ๐ Reduced Code Duplication - Reusable components
- ๐๏ธ Better Structure - Organized and scalable architecture
- ๐ก๏ธ Increased Robustness - More reliable test automation
By abstracting the UI structure away from the test scripts, Design Patterns enable testers to write cleaner, more efficient code. Changes to the UI can be managed in a centralized manner, minimizing the impact on tests and improving the robustness of the automation suite.
โก Result: More scalable, maintainable, and reliable test automation strategies that align with software development best practices
๐ค POM (Page Object Model) vs Functional Helpers
Both Page Object Model (POM) and Functional Helpers are popular design patterns used to enhance test automation frameworks. Let's explore the key differences:
๐๏ธ Page Object Model (POM)
Aspect | Description | Benefits |
---|---|---|
๐๏ธ Structure | Organizes web UI elements into objects corresponding to pages/components | Clear page-based organization |
๐ง Maintenance | Centralizes UI changes, ideal for frequently changing applications | Easy to update and maintain |
๐ Readability | Abstracts UI specifics into methods, making tests read like user stories | Highly readable test scripts |
โป๏ธ Reusability | High reusability across different tests for same page/component | Maximum code reuse |
๐ Learning Curve | Steeper due to separate page object layer design | Requires architectural planning |
โ๏ธ Functional Helpers
Aspect | Description | Benefits |
---|---|---|
๐๏ธ Structure | Uses functions for common tasks without strict page binding | Flexible function-based approach |
๐ง Maintenance | Straightforward for small projects, challenging for large suites | Simple for small-scale projects |
๐ Readability | Abstracts UI specifics into functions for better readability | Good readability with functions |
โป๏ธ Reusability | Moderate reusability, may need adjustments across contexts | Limited cross-context reuse |
๐ Learning Curve | Lower initial setup, more intuitive for simple projects | Quick to get started |
๐ฏ Which Should You Choose?
๐ก Decision Factors:
- Project Scale: Large/complex โ POM, Small/simple โ Functional Helpers
- Team Experience: Experienced โ POM, Beginners โ Functional Helpers
- UI Complexity: Complex/changing โ POM, Static/simple โ Functional Helpers
- Long-term Maintenance: Long-term โ POM, Short-term โ Functional Helpers
Decision taken: For this series, we'll implement POM as it's more popular and provides better scalability for real-world applications.
๐ ๏ธ POM Setup
Since POM Design Pattern is more popular and scalable, we will implement it in our project. There are several different implementations, but I'll show you the two most effective approaches.
Step 1: Create Folder Structure
Create a logical folder structure in your project's root directory:
project-root/
โโโ pages/
โ โโโ clientSite/
โ โโโ HomePage.ts
โ โโโ NavPage.ts
โ โโโ ArticlePage.ts
โโโ tests/
โโโ playwright.config.ts
๐๏ธ Why This Structure?: This gives you flexibility to extend with Admin Panel or other application sections later
Step 2: Create Page Object Files
Create and implement page objects for all pages of the application. We'll create page objects for:
- ๐ Home Page - Main landing page functionality
- ๐งญ Nav Page - Navigation bar (present on every page, but defined once)
- ๐ Article Page - Article creation and management
Step 3: Create Page Object Classes
๐ Complete Implementation: The three page objects are fully implemented in the GitHub repository
Let's examine the Article Page as our primary example:
import { Page, Locator, expect } from '@playwright/test';
/**
* This is the page object for Article Page functionality.
* @export
* @class ArticlePage
* @typedef {ArticlePage}
*/
export class ArticlePage {
constructor(private page: Page) {}
get articleTitleInput(): Locator {
return this.page.getByRole('textbox', {
name: 'Article Title',
});
}
get articleDescriptionInput(): Locator {
return this.page.getByRole('textbox', {
name: "What's this article about?",
});
}
get articleBodyInput(): Locator {
return this.page.getByRole('textbox', {
name: 'Write your article (in',
});
}
get articleTagInput(): Locator {
return this.page.getByRole('textbox', {
name: 'Enter tags',
});
}
get publishArticleButton(): Locator {
return this.page.getByRole('button', {
name: 'Publish Article',
});
}
get publishErrorMessage(): Locator {
return this.page.getByText("title can't be blank");
}
get editArticleButton(): Locator {
return this.page.getByRole('link', { name: '๏ฟ Edit Article' }).first();
}
get deleteArticleButton(): Locator {
return this.page
.getByRole('button', { name: '๏ Delete Article' })
.first();
}
/**
* Navigates to the edit article page by clicking the edit button.
* Waits for the page to reach a network idle state after navigation.
* @returns {Promise<void>}
*/
async navigateToEditArticlePage(): Promise<void> {
await this.editArticleButton.click();
await this.page.waitForResponse(
(response) =>
response.url().includes('/api/articles/') &&
response.request().method() === 'GET'
);
}
/**
* Publishes an article with the given details.
* @param {string} title - The title of the article.
* @param {string} description - A brief description of the article.
* @param {string} body - The main content of the article.
* @param {string} [tags] - Optional tags for the article.
* @returns {Promise<void>}
*/
async publishArticle(
title: string,
description: string,
body: string,
tags?: string
): Promise<void> {
await this.articleTitleInput.fill(title);
await this.articleDescriptionInput.fill(description);
await this.articleBodyInput.fill(body);
if (tags) {
await this.articleTagInput.fill(tags);
}
await this.publishArticleButton.click();
await this.page.waitForResponse(
(response) =>
response.url().includes('/api/articles/') &&
response.request().method() === 'GET'
);
await expect(
this.page.getByRole('heading', { name: title })
).toBeVisible();
}
/**
* Edits an existing article with the given details.
* @param {string} title - The new title of the article.
* @param {string} description - The new description of the article.
* @param {string} body - The new content of the article.
* @param {string} [tags] - Optional new tags for the article.
* @returns {Promise<void>}
*/
async editArticle(
title: string,
description: string,
body: string,
tags?: string
): Promise<void> {
await this.articleTitleInput.fill(title);
await this.articleDescriptionInput.fill(description);
await this.articleBodyInput.fill(body);
if (tags) {
await this.articleTagInput.fill(tags);
}
await this.publishArticleButton.click();
await this.page.waitForResponse(
(response) =>
response.url().includes('/api/articles/') &&
response.request().method() === 'GET'
);
await expect(
this.page.getByRole('heading', { name: title })
).toBeVisible();
}
/**
* Deletes the currently selected article.
* @returns {Promise<void>}
*/
async deleteArticle(): Promise<void> {
await this.deleteArticleButton.click();
await expect(this.page.getByText('Global Feed')).toBeVisible();
}
}
It is debatable if using only methods leads to easier implementation. My opinion is to stick with get functions and use them into the methods.
๐ฏ What's Next?
In the next article we will dive into implementing POM (Page Object Model) as Fixture and creating Auth User Session.
๐ฌ Community: Please feel free to initiate discussions on this topic, as every contribution has the potential to drive further refinement.
โจ Ready to supercharge your testing skills? Let's continue this journey together!
๐๐ป Thank you for reading! Building robust, scalable automation frameworks is a journey best taken together. If you found this article helpful, consider joining a growing community of QA professionals ๐ who are passionate about mastering modern testing.
Join the community and get the latest articles and tips by signing up for the newsletter.
Top comments (0)