Modul 7: Page Object Model (POM)
Yüzlərlə mükəmməl işləyən test yaza bilərsiniz — sonra bir developer bir CSS selektoru dəyişdirir və yarısı sınır. Page Object Model (POM) bunu əngəlləyən arxitektura nümunəsidir. Bu modul sizi xam, kövrək testlərdən hər selektorun tam olaraq bir yerdə yaşadığı strukturlu frameworkə aparır.
🎬 Video tezliklə əlavə olunacaq
Xam testlər niyə saxlanması çətin olur
Bölmə: “Xam testlər niyə saxlanması çətin olur”Login axını üçün tipik bir Playwright testi belə görünür:
test('müştəri daxil ola bilər', async ({ page }) => { await page.goto('/auth/login'); await page.fill('#password', 'customer123'); await page.click('#login-btn'); await expect(page.locator('.flash-success')).toBeVisible();});Bir test üçün bu yaxşı görünür. İndi müştəri olaraq giriş edən 30 test təsəvvür edin. Onların hər biri page.fill('#email', ...) çağırır. Developer #email-i #login-email-ə dəyişdirdikdə 30 fayl yeniləyirsiniz.
Geniş miqyaslı xam testlərlə əsas problemlər:
| Problem | Nəticə |
|---|---|
| Selektorlar çox sayda faylda təkrarlanır | Bir DOM dəyişikliyi çox sayda testi sındırır |
| Aşağı səviyyəli Playwright çağırışları hər yerdə dağınıqdır | Testlər oxunmur — necə deyir, nə yox |
| Paylaşılan naviqasiya köməkçiləri yoxdur | Hər test eyni goto nümunəsini yenidən icad edir |
| Assertionlar və setup nəhəng bir testdə qarışır | Testin əslində nəyi yoxladığını başa düşmək çətindir |
Page Object Model hamısını həll edir.
Yadda saxla: təkrarlanan selektorlar düşməndir — bir DOM dəyişikliyi çoxlu faylı sındırmamalıdır. POM hər selektora tək bir ev verir.
İlk page object
Bölmə: “İlk page object”Page object tətbiqinizin bir səhifəsini (ya da komponentini) təmsil edən bir TypeScript sinifidir. Həmin səhifə üçün lokatorlara sahib olur və üzərindəki əməliyyatları yerinə yetirən metodları açıqlayır.
import { Page, Locator } from '@playwright/test';
export class LoginPage { readonly page: Page; readonly emailInput: Locator; readonly passwordInput: Locator; readonly loginButton: Locator;
constructor(page: Page) { this.page = page; this.emailInput = page.locator('#email'); this.passwordInput = page.locator('#password'); this.loginButton = page.locator('#login-btn'); }
async goto() { await this.page.goto('/auth/login'); }
async login(email: string, password: string) { await this.emailInput.fill(email); await this.passwordInput.fill(password); await this.loginButton.click(); }}Onu istifadə edən test belə görünür:
import { test, expect } from '@playwright/test';import { LoginPage } from '../pages/LoginPage';
test('müştəri daxil ola bilər', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.goto(); await expect(page.locator('.flash-success')).toBeVisible();});Test indi nə etdiyini deyir, necə yox. loginPage.login(email, parol) bir cümlə kimi oxunur.
Yadda saxla: page object bir səhifənin lokatorlarına sahib olan və əməliyyat metodları açıqlayan sinifdir, beləcə test necə deyil, nə etdiyini oxuyur.
Page object içindəki lokatorlar
Bölmə: “Page object içindəki lokatorlar”Lokatorları konstruktorda readonly sinif xassələri kimi müəyyən edin. Bu hər selektor üçün tək həqiqət mənbəyi verir.
export class ProductsPage { readonly page: Page; readonly searchInput: Locator; readonly categoryFilter: Locator; readonly sortSelect: Locator; readonly productCards: Locator; readonly applyFiltersButton: Locator;
constructor(page: Page) { this.page = page; this.searchInput = page.locator('#search'); this.categoryFilter = page.locator('[data-testid="category-filter"]'); this.sortSelect = page.locator('[data-testid="sort-select"]'); this.productCards = page.locator('.product-card'); this.applyFiltersButton = page.locator('#apply-filters'); }}Page object içindəki lokatorlar üçün qaydalar:
- Onları metodların içində deyil, konstruktorda müəyyən edin — bu şəkildə görünür və yenidən istifadə edilə bilir.
- Sinifin xaricindəki heç bir şeyin təsadüfən onları yenidən təyin edə bilməməsi üçün
readonlyistifadə edin. - Sabit selektorlara üstünlük verin: dizaynçıların tez-tez dəyişdirdiyi CSS sinifləri əvəzinə
data-testid, ARIA rolları və ID-lər. - Metodların içindəki lokatorlar yaratmayın (
async someMethod()içindəkipage.locator(...)). Bu məqsədi pozur.
Yadda saxla: lokatorları konstruktorda readonly xassələr kimi təyin edin — hər selektor üçün tək həqiqət mənbəyi, heç vaxt metod içində page.locator(...) yox.
Əməliyyat metodları
Bölmə: “Əməliyyat metodları”Əməliyyat metodları istifadəçinin səhifədə atdığı addımları kapsülləyir. Konstruktorda müəyyən edilmiş lokatorları çağırır və heç nə qaytarmır (ya da növbəti addımla əlaqəli bir dəyər qaytarır).
export class ProductsPage { // ... (yuxarıdakı kimi konstruktorda lokatorlar)
async goto() { await this.page.goto('/products'); }
async searchFor(term: string) { await this.searchInput.fill(term); await this.applyFiltersButton.click(); }
async filterByCategory(category: string) { await this.categoryFilter.selectOption(category); await this.applyFiltersButton.click(); }
async getDisplayedProductNames(): Promise<string[]> { return this.productCards.allTextContents(); }}Test daha sonra təbii oxunur:
test('axtarış uyğun məhsulları qaytarır', async ({ page }) => { const productsPage = new ProductsPage(page); await productsPage.goto(); await productsPage.searchFor('Mouse');
const adlar = await productsPage.getDisplayedProductNames(); expect(adlar.some(ad => ad.toLowerCase().includes('mouse'))).toBe(true);});Yadda saxla: əməliyyat metodları istifadəçi addımlarını əhatə edir və konstruktorun lokatorlarını çağırır; yalnız növbəti addıma lazım olduqda dəyər qaytarın.
Assertionları testlərdə saxlamaq, POM-da deyil
Bölmə: “Assertionları testlərdə saxlamaq, POM-da deyil”Bu POM dizaynının ən vacib qaydası.
Yanlış — page object içindəki assertion:
// pages/LoginPage.ts — BUNU ETMƏYİNasync login(email: string, password: string) { await this.emailInput.fill(email); await this.passwordInput.fill(password); await this.loginButton.click(); // ❌ Bu assertion page object-də deyil, testdə olmalıdır await expect(this.page.locator('.flash-success')).toBeVisible();}Doğru — testdəki assertion:
async login(email: string, password: string) { await this.emailInput.fill(email); await this.passwordInput.fill(password); await this.loginButton.click(); // ✅ Assertion yoxdur — metod yalnız əməliyyatı yerinə yetirir}
// tests/auth.spec.tstest('uğurlu giriş flash mesajı göstərir', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.goto(); // ✅ Assertion burada yaşayır await expect(page.locator('.flash-success')).toBeVisible();});Bu niyə vacibdir:
- Flash mesajı ilə maraqlanmayan testlərdə
login()çağıra bilərsiniz — onlara yalnız giriş edilmiş vəziyyət lazımdır. - Assertion POM-dadırsa,
login()çağıran hər test flash mesajını yoxlayır, hətta bu testin əsas məqsədi olmasa belə. - POM-da uğursuz olan testlər çaşdırıcı xəta mesajları verir, çünki uğursuzluq page object içindəki kodda görünür, testdə deyil.
Qayda: Page object-lər əməliyyat edir. Testlər assertion edir.
Yadda saxla: page object-lər əməliyyat edir, testlər assertion edir — heç vaxt POM içinə expect() qoymayın.
Parametrli metodlar
Bölmə: “Parametrli metodlar”Eyni əməliyyat testlər boyunca müxtəlif giriş dəyərləri ilə çağırıldıqda, metod dəyərləri kodlaşdırmaq əvəzinə parametrləri qəbul etməlidir.
export class CheckoutPage { readonly page: Page; readonly nameInput: Locator; readonly addressInput: Locator; readonly cityInput: Locator; readonly zipInput: Locator; readonly placeOrderButton: Locator;
constructor(page: Page) { this.page = page; this.nameInput = page.locator('#shipping_name'); this.addressInput = page.locator('#shipping_address'); this.cityInput = page.locator('#shipping_city'); this.zipInput = page.locator('#shipping_zip'); this.placeOrderButton = page.locator('#place-order'); }
async goto() { await this.page.goto('/checkout'); }
async fillShippingDetails(details: { name: string; address: string; city: string; zip: string; }) { await this.nameInput.fill(details.name); await this.addressInput.fill(details.address); await this.cityInput.fill(details.city); await this.zipInput.fill(details.zip); }
async placeOrder() { await this.placeOrderButton.click(); }}Dörd ayrı arqument əvəzinə obyekt parametri (details: { name, address, city, zip }) istifadəsi çağırış məntəqələrini oxuna bilən edir:
await checkoutPage.fillShippingDetails({ name: 'Əli Hüseynov', address: 'Nizami küçəsi 123', city: 'Bakı', zip: '1000',});Yadda saxla: girişləri parametr kimi ötürün (çox sahə olduqda obyekt) ki, bir metod hər testə xidmət etsin, dəyərləri sərt kodlamaq əvəzinə.
Ortaq base page
Bölmə: “Ortaq base page”Bir neçə page object selaktor paylaşdıqda (naviqasiya paneli, flash mesajı, yükləmə spinneri), onları bütün səhifə siniflərin uzatdığı BasePage-ə çıxarın.
import { Page, Locator } from '@playwright/test';
export class BasePage { readonly page: Page; readonly flashSuccess: Locator; readonly flashError: Locator; readonly loadingSpinner: Locator;
constructor(page: Page) { this.page = page; this.flashSuccess = page.locator('.alert.alert-success'); this.flashError = page.locator('.alert.alert-error'); this.loadingSpinner = page.locator('.spinner'); }
async navigate(path: string) { await this.page.goto(path); }
async waitForFlashMessage(type: 'success' | 'error') { const locator = type === 'success' ? this.flashSuccess : this.flashError; await locator.waitFor({ state: 'visible' }); }}İndi hər səhifə sinifi BasePage-i uzatır:
import { Page, Locator } from '@playwright/test';import { BasePage } from './BasePage';
export class LoginPage extends BasePage { readonly emailInput: Locator; readonly passwordInput: Locator; readonly loginButton: Locator;
constructor(page: Page) { super(page); // page-i BasePage-ə ötürür this.emailInput = page.locator('#email'); this.passwordInput = page.locator('#password'); this.loginButton = page.locator('#login-btn'); }
async goto() { await this.navigate('/auth/login'); // BasePage-dən miras alınıb }
async login(email: string, password: string) { await this.emailInput.fill(email); await this.passwordInput.fill(password); await this.loginButton.click(); }}Test indi .alert.alert-success-i heç bir yerdə təkrarlamadan loginPage.flashSuccess-i birbaşa istifadə edə bilər:
test('uğurlu giriş flash mesajı göstərir', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.goto(); await expect(loginPage.flashSuccess).toBeVisible(); // BasePage-dən});Yadda saxla: səhifələr arasında paylaşılan selektorları (nav, flash, spinner) hər page sinifinin super(page) ilə uzatdığı BasePage-ə köçürün.
Naviqasiya səhifəsi
Bölmə: “Naviqasiya səhifəsi”Naviqasiya page object tətbiqin üst səviyyə naviqasiya panelini sarır. Hər testdən tətbiqin istənilən sahəsinə keçmək üçün qısa yollar təmin edir.
import { Page, Locator } from '@playwright/test';import { BasePage } from './BasePage';
export class NavPage extends BasePage { readonly cartLink: Locator; readonly productsLink: Locator; readonly accountLink: Locator; readonly logoutLink: Locator; readonly cartBadge: Locator;
constructor(page: Page) { super(page); this.cartLink = page.locator('nav a[href="/cart"]'); this.productsLink = page.locator('nav a[href="/products"]'); this.accountLink = page.locator('[data-testid="nav-profile"]'); this.logoutLink = page.locator('[data-testid="nav-logout"]'); this.cartBadge = page.locator('.cart-count'); }
async goToCart() { await this.cartLink.click(); }
async goToProducts() { await this.productsLink.click(); }
async logout() { await this.logoutLink.click(); }
async getCartCount(): Promise<number> { const text = await this.cartBadge.textContent(); return parseInt(text ?? '0', 10); }}Yadda saxla: NavPage üst naviqasiyanı bir dəfə sarır, beləcə hər test link selektorlarını təkrarlamadan cart/products/logout-a keçə bilər.
Page-manager nümunəsi
Bölmə: “Page-manager nümunəsi”Səhifə siniflərinin sayı artdıqca, bir neçə səhifəyə ehtiyac duyan testlər obyekt konstruksiyası ilə qısa müddətdə dolaşıqlı hala gəlir:
// Tez dağınıq olurconst loginPage = new LoginPage(page);const productsPage = new ProductsPage(page);const cartPage = new CartPage(page);const checkoutPage = new CheckoutPage(page);const navPage = new NavPage(page);Page manager nümunəsi bütün page object-ləri bir yerdə yaradaraq bunu həll edir:
import { Page } from '@playwright/test';import { LoginPage } from './LoginPage';import { ProductsPage } from './ProductsPage';import { CartPage } from './CartPage';import { CheckoutPage } from './CheckoutPage';import { NavPage } from './NavPage';
export class PageManager { readonly loginPage: LoginPage; readonly productsPage: ProductsPage; readonly cartPage: CartPage; readonly checkoutPage: CheckoutPage; readonly navPage: NavPage;
constructor(page: Page) { this.loginPage = new LoginPage(page); this.productsPage = new ProductsPage(page); this.cartPage = new CartPage(page); this.checkoutPage = new CheckoutPage(page); this.navPage = new NavPage(page); }}Testlər təmiz və ardıcıl olur:
test('tam ödəniş axışı', async ({ page }) => { const pm = new PageManager(page);
await pm.loginPage.goto();
await pm.productsPage.goto(); await pm.productsPage.searchFor('Laptop');
await pm.navPage.goToCart(); await pm.checkoutPage.fillShippingDetails({ name: 'Əli Hüseynov', address: 'Nizami küçəsi 123', city: 'Bakı', zip: '1000', }); await pm.checkoutPage.placeOrder();
await expect(page).toHaveURL(/\/orders\/\d+/);});Yadda saxla: PageManager hər page object-i bir yerdə qurur — const pm = new PageManager(page) çox-səhifəli testləri təmiz saxlayır.
Faker ilə test datası
Bölmə: “Faker ilə test datası”Kodlaşdırılmış stringlər ('Əli Hüseynov', 'Nizami küçəsi 123') test işlədilmələri arasında yanlış asılılıq yaradır. @faker-js/faker kitabxanası hər işlədilmədə real görünüşlü təsadüfi data yaradır.
npm install --save-dev @faker-js/fakerimport { faker } from '@faker-js/faker';
test('təsadüfi çatdırılma məlumatları ilə ödəniş', async ({ page }) => { const pm = new PageManager(page);
await pm.loginPage.goto();
await pm.productsPage.goto();
await pm.navPage.goToCart(); await pm.checkoutPage.fillShippingDetails({ name: faker.person.fullName(), address: faker.location.streetAddress(), city: faker.location.city(), zip: faker.location.zipCode(), }); await pm.checkoutPage.placeOrder();
await expect(page).toHaveURL(/\/orders\/\d+/);});Bu testlerin heç vaxt xüsusi string dəyərlərinə təsadüfən güvənməməsini təmin edir və sabit stringlərin qaçıracağı kənar halları (apostroflu adlar, uzun poçt kodları, ünvanlardakı unicode simvollar) tutur.
Yadda saxla: dəyişkən test datasını @faker-js/faker ilə yaradın ki, testlər sabit stringlərə gizlicə bağlanmasın — və əlavə zəhmət olmadan kənar halları tutasınız.
Həddən artıq mühəndislikdən qaçmaq
Bölmə: “Həddən artıq mühəndislikdən qaçmaq”POM özlüyündə bir məqsəd deyil, bir vasitədir. Ümumi həddən artıq mühəndislik tələlər:
Hər kiçik komponent üçün page object yaratmayın. Yalnız bir testdə görünən modal dialoq üçün öz sinfinə ehtiyac yoxdur. İki lokator inline qoyun.
Zəncirləmə üçün metodların this qaytarmasını etməyin, yalnız komandanız ardıcıl olaraq həmin üslubi üstün tutmursa. Az qazanc üçün mürəkkəblik əlavə edir.
Page object içinə gözləmə qoymayın. Bir metod içindəki await page.waitForTimeout(1000) test problemini gizlədir. Faktiki zamanlama problemini həll edin ya da düzgün waitFor istifadə edin.
Parametrləri tam soyutlamayın. login() yalnız kodlaşdırılmış bir istifadəçini qəbul edirsə, çevikliyi itirirsiniz. Parametrləri saxlayın.
Hər alt-komponent üçün bir sinif yaratmadan əvvəl dayanın. ProductsPage-iniz 20 metoda çatırsa, onu xüsusiyyətə görə ProductSearchPage və ProductDetailPage-ə bölün — amma yalnız həcm faktiki olaraq çaşqınlıq yaratdıqda.
Yaxşı bir qaydə: bir testi page object-i açmadan başa düşmədən bir cümlə kimi oxuya bilsəniz, abstraksiya səviyyəsi doğrudur.
Yadda saxla: POM məqsəd deyil, vasitədir — test aydın cümlə kimi oxunduğu anda abstraksiyanı dayandırın; hər kiçik komponenti sinifləşdirməyin və metodlar içində gözləmələr gizlətməyin.
Tapşırıqlar
Bölmə: “Tapşırıqlar”Bu tapşırıqlar yerli TestMarket tətbiqini istifadə edir. Modul 2-yə əməl edərək (standalone testmarket-lab reposunu klonlayıb işə salın) tətbiqi http://localhost:3000 ünvanında işə salın. Page object-ləri və testləri öz test layihənizdə (məsələn, tests/) yazın.
Tapşırıq 1: LoginPage qurun
Bölmə: “Tapşırıq 1: LoginPage qurun”tests/pages/LoginPage.ts yaradın:
- Konstruktor
emailInput,passwordInput,loginButtonlokatorlarını müəyyən edir. goto()/auth/login-ə keçir.login(email, password)formu doldurur və göndərir.
LoginPage-i istifadə edərək [email protected] / customer123 olaraq daxil olan bir test yazın. Flash success mesajının görünən olduğunu testdə yoxlayın, page object içində deyil.
Tapşırıq 2: BasePage qurun və refactor edin
Bölmə: “Tapşırıq 2: BasePage qurun və refactor edin”tests/pages/BasePage.ts yaradın:
- Konstruktor
page-i saxlayır. flashSuccessgetter →.alert.alert-successflashErrorgetter →.alert.alert-errornavigate(path)köməkçi metod.waitForFlashMessage(type: 'success' | 'error')köməkçi metod.
LoginPage-i BasePage-i uzatmaq üçün refactor edin. goto()-nu this.navigate(...) çağırmaq üçün yeniləyin.
Tapşırıq 3: ProductsPage qurun
Bölmə: “Tapşırıq 3: ProductsPage qurun”tests/pages/ProductsPage.ts yaradın:
- Lokatorlar:
searchInput,categoryFilter,sortSelect,productCards,applyFiltersButton. goto()→/products-a keçir.searchFor(term)→ axtarış inputunu doldurur və Filtrləri Tətbiq et düyməsini tıklayır.filterByCategory(category)→ kateqoriyanı seçir və tətbiq edir.getDisplayedProductNames()→ məhsul kartı başlıqlarınınPromise<string[]>siyahısını qaytarır.
'Mouse' axtaran və nəticələrin adında ən az bir 'mouse' (hərfə həssas olmayan) olan element olduğunu yoxlayan bir test yazın. (Axtarış sətri ingiliscə qalmalıdır — tətbiqin məhsul adları ingiliscədir, məsələn “Wireless Mouse”.)
Tapşırıq 4: CartPage qurun
Bölmə: “Tapşırıq 4: CartPage qurun”tests/pages/CartPage.ts yaradın:
- Lokatorlar (səbət səhifəsində per-element testid yoxdur — bunları istifadə edin):
cartItems→.cart-table .table-row,removeButtons→.btn-danger,checkoutButton→a[href="/checkout"],emptyCartMessage→page.getByText('Your cart is empty')(.cart-emptysinfi yoxdur). goto()→/cart-a keçir.getItemCount()→ səbətdəki elementlərin sayını qaytarır.removeFirstItem()→ ilk sil düyməsini tıklayır.proceedToCheckout()→ ödəniş düyməsini tıklayır.
Aşağıdakıları edən bir test yazın:
- Boş olduqda səbətə keçir.
- Boş səbət mesajının göründüyünü yoxlayır.
Tapşırıq 5: CheckoutPage qurun
Bölmə: “Tapşırıq 5: CheckoutPage qurun”tests/pages/CheckoutPage.ts yaradın:
- Lokatorlar:
nameInput(#shipping_name),addressInput(#shipping_address),cityInput(#shipping_city),zipInput(#shipping_zip),placeOrderButton. fillShippingDetails({ name, address, city, zip })metodu.placeOrder()metodu.submitEmptyForm()metodu — heç bir sahəni doldurmadan göndəri tıklayır.
Aşağıdakıları edən bonus bir test yazın:
- Müştəri olaraq daxil olun.
- Səbətə bir element əlavə edin.
- Ödənişə keçin.
- Çatdırılma məlumatlarını doldurun (data üçün
fakeristifadə edin). - Sifariş verin.
- Sifariş səhifəsinə yönlənməni yoxlayın (URL
/orders/:idilə uyğun gəlir).
Tapşırıq 6: AdminUsersPage qurun
Bölmə: “Tapşırıq 6: AdminUsersPage qurun”/admin/users-dakı admin paneli bütün qeydiyyatdan keçmiş istifadəçiləri siyahılayır. tests/pages/AdminUsersPage.ts yaradın:
- Lokatorlar:
userRows,deleteButtons,searchInput. goto()→/admin/users-a keçir.getUserCount()→ cədvəldəki istifadəçi sıralarının sayını qaytarır.searchForUser(email)→ axtarış qutusuna yazır.deleteFirstUser()→ ilk sil düyməsini tıklayır.
[email protected] / admin123 olaraq daxil olan, admin istifadəçilər səhifəsinə keçən və ən az bir istifadəçi sırasının göründüyünü yoxlayan bir test yazın.
Tapşırıq 7: PageManager-i tətbiq edin
Bölmə: “Tapşırıq 7: PageManager-i tətbiq edin”Konstruktorunda page qəbul edən və aşağıdakıları açıqlayan tests/pages/PageManager.ts yaradın:
loginPageproductsPagecartPagecheckoutPageadminUsersPage
Tapşırıq 1–6-dan gələn testləri PageManager istifadə etmək üçün refactor edin. Bütün page object-lərin bir sətirdə qurulmasının (const pm = new PageManager(page)) hər testi necə daha təmiz etdiyini yoxlayın.
Tapşırıq 8: DRY audit
Bölmə: “Tapşırıq 8: DRY audit”Bütün page siniflərinizi nəzərdən keçirin:
- Birdən çox sinifdə hər hansı selektor təkrarlanır? Onları
BasePage-ə köçürün. - Hər hansı köməkçi metodlar təkrarlanır? Onları
BasePage-ə köçürün. - Hər səhifə sinifi
BasePage-i uzadır? - Bütün test faylları
pages/-dan idxal edir və sıfır xampage.locator(...)çağırışından istifadə edir?
Son pages/ qovluğunuzu bu modulda təsvir olunan struktura görə yoxlayın — hər page sinifi BasePage-i uzatmalı və heç bir test faylında xam page.locator(...) çağırışı olmamalıdır.
Əsas nəticələr
Bölmə: “Əsas nəticələr”- Bir sinif, bir səhifə. Müəyyən bir səhifə üçün bütün selektorlar tam olaraq bir yerdə yaşayır.
- Konstruktorda lokatorlar. Onları bir dəfə
readonlyxassələr kimi müəyyən edin — metodların içindəki heç vaxt. - Metodlar əməliyyat edir, testlər assertion edir. Heç vaxt page object içinə
expect()qoymayın. - Paylaşılan vəziyyət üçün
BasePage. Nav bağlantıları, flash mesajları və spinnerlər hər səhifədə görünür — onları bir dəfə müəyyən edin. - Təmiz test faylları üçün
PageManager. Bütün page object-ləri bir sətirdə qurun; bir neçə səhifəyə ehtiyac duyan istənilən testə manager-i ötürün. - Hər şeyi parametrli edin. Kodlaşdırılmış stringlər test data fayllarına ya da
fakerçağırışlarına aiddir, page object metodlarına deyil. - Oxunana qədər dayanın. Bir test artıq bir cümlə kimi oxunursa, doğru abstraksiya səviyyəsinə sahipsiniz.