Fuente
QA Co-pilot | ️ Битва підходів: Junior vs. QA Architect (Page Object Model — правиль...
25 Vistas/Alcance
2026-05-11 12:29
Mensaje №290
⚔️ Битва підходів: Junior vs. QA Architect (Page Object Model — правильна архітектура)Сьогодні розбираємо тему, яку всі "знають", але мало хто робить правильно — Page Object Model. Різниця між Junior і Architect тут не в тому, чи використовувати POM. А в тому, що саме ховати всередині. ☕️❌ Підхід Junior-автоматизатора (POM як "папка для локаторів")// pages/LoginPage.tsexport class LoginPage { readonly page: Page; constructor(page: Page) { this.page = page; } async fillEmail(email: string) { await this.page.locator('#email').fill(email); } async fillPassword(password: string) { await this.page.locator('#password').fill(password); } async clickSubmit() { await this.page.locator('#submit').click(); }}// tests/login.spec.tstest('Логін з валідними даними', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.fillEmail('[email protected]'); await loginPage.fillPassword('qwerty123'); await loginPage.clickSubmit(); await expect(page.locator('.dashboard-title')).toBeVisible();});
Чому це антипатерн:
По-перше: Page Object перетворився на тонку обгортку над локаторами — і нічого більше. Вся логіка взаємодії досі живе в тесті. По-друге, тест знає забагато: він знає послідовність дій, знає що перевіряти, знає як влаштована сторінка. Якщо завтра #submit стане [data-testid="login-btn"] — йдеш шукати всі місця в тестах руками. По-третє: такий POM не дає жодної ізоляції — це просто перейменовані page.locator() виклики.
✅ Підхід QA Architect (POM як сервісний шар)// pages/LoginPage.tsexport class LoginPage { private readonly emailInput = this.page.getByLabel('Email'); private readonly passwordInput = this.page.getByLabel('Password'); private readonly submitButton = this.page.getByRole('button', { name: 'Sign in' }); private readonly errorMessage = this.page.getByTestId('auth-error'); constructor(private readonly page: Page) {} // Публічний API сторінки — одна дія, один метод async login(email: string, password: string): Promise<void> { await this.emailInput.fill(email); await this.passwordInput.fill(password); await this.submitButton.click(); } async expectErrorMessage(text: string): Promise<void> { await expect(this.errorMessage).toHaveText(text); }}// pages/DashboardPage.tsexport class DashboardPage { private readonly title = this.page.getByRole('heading', { name: 'Dashboard' }); constructor(private readonly page: Page) {} async expectLoaded(): Promise<void> { await expect(this.title).toBeVisible(); }}// tests/login.spec.tstest('Логін з валідними даними', async ({ page }) => { const loginPage = new LoginPage(page); const dashboardPage = new DashboardPage(page); await loginPage.login('[email protected]', 'qwerty123'); await dashboardPage.expectLoaded();});test('Логін з невалідним паролем', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.login('[email protected]', 'wrongpass'); await loginPage.expectErrorMessage('Invalid credentials');});
Чому це шедевр:Тест читається як специфікація, а не як інструкція для браузера. Page Object інкапсулює всю логіку взаємодії — тест не знає жодного локатора. Локатори побудовані на семантиці (getByRole, getByLabel) — стійкі до рефакторингу верстки. Assertions теж живуть у Page Object — тест перевіряє поведінку, а не деталі реалізації. Зміна UI = правка в одному файлі, а не хірургія по всьому репозиторію.
Золоте правило: Page Object — це не папка для локаторів. Це публічний API вашої сторінки. Якщо ваш тест досі знає про #submit або .dashboard-title — у вас не POM, у вас ілюзія абстракції.А який рівень POM у вас на проєкті?🔥 — Повна інкапсуляція: тести не знають жодного локатора, тільки методи👀 — Десь між: локатори в POM є, але логіка частково тече в тести🤬 — POM є у назві папки, але по суті — просто locator() обгорнутий в клас