🏗 ساخت چارچوب نمایشنامه نویسی گام به گام – الگوی طراحی تنظیمات

🎯 اهمیت الگوی طراحی
اهمیت استفاده از الگوهای طراحی در اتوماسیون تست قابل تحمل نیست! این به عنوان یک خدمت می کند طرح برای سازماندهی تعامل با استفاده از عناصر رابط کاربری (UI) صفحات وب به روشی ساختاری و قابل استفاده مجدد. 🎭
💡 الگوی طراحی چیست؟ یک راه حل اثبات شده برای مشکلات متداول در طراحی نرم افزار که الگویی برای چگونگی حل مشکلات در موقعیت های مختلف فراهم می کند
🚀 چرا الگوهای طراحی اهمیت دارند
الگوهای طراحی چندین مزیت مهم را ارائه می دهد:
- 🔧 قابلیت حفظ افزایش یافته – مدیریت متمرکز UI مدیریت
- 📖 خوانایی بهبود یافته – پاک کننده ، کد کارآمدتر
- 🔄 کاهش تکثیر کد – اجزای قابل استفاده مجدد
- 🏗 ساختار بهتر – معماری سازمان یافته و مقیاس پذیر
- 🛡 استحکام افزایش یافته است – اتوماسیون تست قابل اطمینان تر
با انتزاع ساختار UI به دور از اسکریپت های آزمون ، الگوهای طراحی آزمایش کنندگان را قادر می سازد تا کد تمیزتر و کارآمدتری بنویسند. تغییرات در UI را می توان به روشی متمرکز مدیریت کرد و تأثیر آن را بر تست ها و بهبود استحکام مجموعه اتوماسیون به حداقل می رساند.
⚡ نتیجه: استراتژی های اتوماسیون تست مقیاس پذیر تر ، قابل حفظ و قابل اعتماد تر که با بهترین شیوه های توسعه نرم افزار هماهنگ است
🤔 POM (مدل شیء صفحه) در مقابل یاران کاربردی
هر دو مدل شیء صفحه (POM) وت یاران کاربردی الگوهای طراحی محبوب برای تقویت چارچوب های اتوماسیون تست استفاده می شود. بیایید تفاوت های کلیدی را بررسی کنیم:
🏛 مدل شیء صفحه (POM)
جنبه | شرح | فواید |
---|---|---|
🏗 ساختار | عناصر UI وب را در اشیاء مربوط به صفحات/مؤلفه ها سازماندهی می کند | سازمان مبتنی بر صفحه |
🔧 نگهداری | تغییرات UI را متمرکز می کند ، ایده آل برای تغییر برنامه های کاربردی | به روزرسانی و نگهداری آسان است |
📖 قابلیت خوانایی | ویژگی های UI چکیده در روش ها ، و تست ها را مانند داستانهای کاربر خوانده می شود | اسکریپت های تست بسیار قابل خواندن |
♻ قابلیت استفاده مجدد | قابلیت استفاده مجدد بالا در تست های مختلف برای همان صفحه/مؤلفه | حداکثر استفاده مجدد از کد |
📚 منحنی یادگیری | به دلیل طراحی لایه شیء صفحه جداگانه تندتر | نیاز به برنامه ریزی معماری دارد |
⚙ یاران کاربردی
جنبه | شرح | فواید |
---|---|---|
🏗 ساختار | از توابع برای کارهای مشترک بدون اتصال دقیق صفحه استفاده می کند | رویکرد مبتنی بر عملکرد انعطاف پذیر |
🔧 نگهداری | ساده برای پروژه های کوچک ، به چالش کشیده برای سوئیت های بزرگ | ساده برای پروژه های در مقیاس کوچک |
📖 قابلیت خوانایی | برای خوانایی بهتر ، مشخصات UI را در توابع انتزاع می کند | خوانایی خوب با توابع |
♻ قابلیت استفاده مجدد | قابلیت استفاده مجدد متوسط ، ممکن است نیاز به تعدیل در متن ها داشته باشد | استفاده مجدد از متن متقاطع محدود |
📚 منحنی یادگیری | تنظیم اولیه پایین ، شهودی تر برای پروژه های ساده | سریع شروع به کار |
🎯 کدام یک را انتخاب کنید؟
💡 عوامل تصمیم گیری:
- مقیاس پروژه: بزرگ/پیچیده → POM ، یاران کوچک/ساده → کاربردی
- تجربه تیمی: با تجربه → POM ، مبتدیان → یاران کاربردی
- پیچیدگی UI: پیچیده/در حال تغییر → POM ، استاتیک/ساده → یاران کاربردی
- نگهداری طولانی مدت: بلند مدت → POM ، کوتاه مدت یا یاران کاربردی
تصمیم گرفته شده: برای این سری ، ما پیاده سازی خواهیم کرد پوم از آنجا که محبوب تر است و مقیاس پذیری بهتری را برای برنامه های دنیای واقعی فراهم می کند.
🛠 تنظیم POM
پس از الگوی طراحی POM محبوب تر و مقیاس پذیر تر است ، ما آن را در پروژه خود پیاده سازی خواهیم کرد. چندین اجرای مختلف وجود دارد ، اما من به شما نشان می دهم دو رویکرد مؤثربشر
مرحله 1: ساختار پوشه را ایجاد کنید
یک ساختار پوشه منطقی در فهرست اصلی پروژه خود ایجاد کنید:
project-root/
├── pages/
│ └── clientSite/
│ ├── HomePage.ts
│ ├── NavPage.ts
│ └── ArticlePage.ts
├── tests/
└── playwright.config.ts
🏗 چرا این ساختار؟: این به شما انعطاف پذیری می دهد تا بعداً با پانل مدیر یا سایر بخش های کاربردی گسترش دهید
مرحله 2: پرونده های شیء را ایجاد کنید
اشیاء صفحه را برای کلیه صفحات برنامه ایجاد و پیاده سازی کنید. ما اشیاء صفحه را برای:
- 🏠 صفحه اصلی – عملکرد اصلی صفحه فرود
- 🧭 صفحه ناو – نوار ناوبری (در هر صفحه موجود است ، اما یک بار تعریف شده است)
- 📄 صفحه مقاله – ایجاد و مدیریت مقاله
مرحله 3: کلاسهای شیء را ایجاد کنید
📚 اجرای کامل: اشیاء سه صفحه به طور کامل در مخزن GitHub پیاده سازی شده اند
بیایید بررسی کنیم صفحه مقاله به عنوان مثال اصلی ما:
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() {return this.page.getByRole('textbox', {
name: 'Article Title',
});}
get articleDescriptionInput() {return this.page.getByRole('textbox', {
name: "What's this article about?",
});}
get articleBodyInput() {return this.page.getByRole('textbox', {
name: 'Write your article (in',
});}
get articleTagInput() {return this.page.getByRole('textbox', {
name: 'Enter tags',
});}
get publishArticleButton() {return this.page.getByRole('button', {
name: 'Publish Article',
});}
get publishErrorMessage() {return this.page.getByText("title can't be blank");}
get editArticleButton() {return this.page
.getByRole('link', { name: ' Edit Article' })
.first();}
get deleteArticleButton() {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}
*/
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} - Optional tags for the article.
* @returns {Promise}
*/
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} - Optional new tags for the article.
* @returns {Promise}
*/
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}
*/
async deleteArticle(): Promise<void> {
await this.deleteArticleButton.click();
await expect(this.page.getByText('Global Feed')).toBeVisible();
}
}
امکان تعریف فقط روشها (بدون تنظیم توابع GET) وجود دارد. این رویکرد شباهت های زیادی با یاران کاربردی طراحی طرح. در اینجا مثالی آورده شده است که چگونه به نظر می رسد:
import { Page, 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) {}
/**
* 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}
*/
async navigateToEditArticlePage(): Promise<void> {
await this.page.getByRole('link', { name: 'Edit Article' }).first().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} - Optional tags for the article.
* @returns {Promise}
*/
async publishArticle(
title: string,
description: string,
body: string,
tags?: string
): Promise<void> {
await this.page.getByRole('textbox', {
name: 'Article Title',
}).fill(title);
await this.page.getByRole('textbox', {
name: "What's this article about?",
}).fill(description);
await this.page.getByRole('textbox', {
name: 'Write your article (in',
}).fill(body);
if (tags) {
await this.page.getByRole('textbox', {
name: 'Enter tags',
}).fill(tags);
}
await this.page.getByRole('button', {
name: 'Publish Article',
}).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} - Optional new tags for the article.
* @returns {Promise}
*/
async editArticle(
title: string,
description: string,
body: string,
tags?: string
): Promise<void> {
await this.page.getByRole('textbox', {
name: 'Article Title',
}).fill(title);
await this.page.getByRole('textbox', {
name: "What's this article about?",
}).fill(description);
await this.page.getByRole('textbox', {
name: 'Write your article (in',
}).fill(body);
if (tags) {
await this.page.getByRole('textbox', {
name: 'Enter tags',
}).fill(tags);
}
await this.page.getByRole('button', {
name: 'Publish Article',
}).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}
*/
async deleteArticle(): Promise<void> {
await this.page.getByRole('button', { name: 'Delete Article' }).first().click();
await expect(this.page.getByText('Global Feed')).toBeVisible();
}
}
اگر تنها استفاده از روشها منجر به اجرای آسان تر شود ، قابل بحث است. نظر من این است که با توابع GET بچسبید و از آنها در روش ها استفاده کنید.
🎯 بعدی چیست؟
در مقاله بعدی ما به اجرای آن شیرجه می زنیم POM (مدل شیء صفحه) به عنوان فیکسچر و ایجاد جلسه کاربربشر
💬 جامعه: لطفاً برای شروع بحث و گفتگو در مورد این موضوع احساس راحتی کنید ، زیرا هر سهم این امکان را دارد که پالایش بیشتری را انجام دهد.
✨ آماده استفاده از مهارت های تست خود هستید؟ بیایید این سفر را با هم ادامه دهیم!