AI-val generálva: POM – Page Object Model

Becsült olvasási idő:
6 perc

Mire jó az AI? Hát például arra, hogy ha írni szeretnék egy témáról, de lusta vagyok, az egyébként is Google segítsével összeszedett információkat rendszerbe szedni, akkor relatív gyorsan kézhez kapok egy bejegyzést.

A Page Object Model (POM) a szoftvertesztelés-automatizálás egyik legnépszerűbb és leghatékonyabb tervezési mintája (Design Pattern). A lényege, hogy az alkalmazás egyes weboldalait (vagy oldalrészeit) különálló objektumokként képezi le a kód szintjén.

Miért érdemes használni?

  • Karbantarthatóság: Ha megváltozik egy gomb ID-ja vagy egy mező elhelyezkedése, elegendő azt egyetlen helyen (a Page Object osztályban) átírni, a teszteseteket nem kell bántani.
  • Kód újrafelhasználhatósága: A gyakori műveletek (pl. bejelentkezés, keresés) egyszer vannak megírva, és bármelyik tesztből meghívhatók.
  • Olvashatóság: A tesztkód tiszta, átlátható és üzleti logikát követő lesz, rejtve a techniai megvalósítást (pl. DOM-lekérdezéseket).

Minta megvalósítás (Python + Selenium)

Az alábbiakban egy klasszikus bejelentkezési folyamat példáján keresztül látható a POM működése.

A Page Object osztály (login_page.py)

Ez az osztály tartalmazza az oldal elemeinek azonosítóit (lokátorait) és a végezhető műveleteket.

Python

from selenium.webdriver.common.by import By

class LoginPage:
    def __init__(self, driver):
        self.driver = driver
        
        # Lokátorok (különválasztva a logikától)
        self._username_input = (By.ID, "username")
        self._password_input = (By.ID, "password")
        self._login_button = (By.CSS_SELECTOR, "button[type='submit']")
        self._error_message = (By.CLASS_NAME, "flash-error")

    # Akciók / Műveletek
    def enter_username(self, username: str):
        self.driver.find_element(*self._username_input).send_keys(username)

    def enter_password(self, password: str):
        self.driver.find_element(*self._password_input).send_keys(password)

    def click_login(self):
        self.driver.find_element(*self._login_button).click()

    # Összetett folyamat (Láncolt / Magasabb szintű akció)
    def login(self, username: str, password: str):
        self.enter_username(username)
        self.enter_password(password)
        self.click_login()

    def get_error_message(self) -> str:
        return self.driver.find_element(*self._error_message).text

A Teszteset (test_login.py)

A tesztkód csak a Page Object által nyújtott metódusokat használja, így végtelenül egyszerű és olvasható.

Python

import pytest
from selenium import webdriver
from login_page import LoginPage

@pytest.fixture
def driver():
    driver = webdriver.Chrome()
    driver.get("https://example.com/login")
    yield driver
    driver.quit()

def test_successful_login(driver):
    login_page = LoginPage(driver)
    
    # Bejelentkezés végrehajtása
    login_page.login("valid_user", "correct_password")
    
    # Ellenőrzés (Assertion)
    assert driver.current_url == "https://example.com/dashboard"

def test_failed_login(driver):
    login_page = LoginPage(driver)
    
    login_page.login("invalid_user", "wrong_password")
    
    # Hibaüzenet ellenőrzése
    assert "Érvénytelen adatok" in login_page.get_error_message()

Jó tanácsok a gyakorlati alkalmazáshoz

  • Ne tegyél assertion-öket a Page Object-be: A assert kifejezések a tesztfájlban maradjanak, a Page Object csak adatot adjon vissza vagy állapotot módosítson.
  • Page Factory használata: Bizonyos keretrendszerekben (pl. Java + Selenium) a @FindBy annotációkkal még tisztább szerkezet érhető el.
  • Lazy loading: Az elemeket csak akkor kérd le a DOM-ból, amikor ténylegesen szükség van rájuk (például dinamikus felületek esetén).

A Page Object Model (POM) megvalósítása a modernebb eszközökkel (mint a Playwright vagy a Selenium Java-alapú változata) még letisztultabbá válik, mivel ezek a keretrendszerek beépített architektúrával támogatják a megbízhatóbb elemkezelést.

Az alábbiakban mindkét megközelítésre találsz egy-egy komplett kódmintát.

Playwright + TypeScript (Ajánlott / Modern megközelítés)

A Playwright eleve magában foglalja az automatikus várakozást (Auto-waiting), így nincs szükség explicit wait-ek kézi kezelésére.

A Page Object osztály (pages/LoginPage.ts)

TypeScript

import { Page, Locator } from '@playwright/test';

export class LoginPage {
  readonly page: Page;
  readonly usernameInput: Locator;
  readonly passwordInput: Locator;
  readonly loginButton: Locator;
  readonly errorMessage: Locator;

  constructor(page: Page) {
    self.page = page;
    // A Playwright-ban a lokátorok deklaratívak, nem kérdezik le azonnal a DOM-ot
    this.usernameInput = page.locator('#username');
    this.passwordInput = page.locator('#password');
    this.loginButton = page.locator('button[type="submit"]');
    this.errorMessage = page.locator('.flash-error');
  }

  async goto() {
    await this.page.goto('https://example.com/login');
  }

  async login(username: string, password: string) {
    await this.usernameInput.fill(username);
    await this.passwordInput.fill(password);
    await this.loginButton.click();
  }

  async getErrorMessage(): Promise<string> {
    return await this.errorMessage.textContent() || '';
  }
}

A Tesztfájl (tests/login.spec.ts)

TypeScript

import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';

test.describe('Bejelentkezés tesztek', () => {
  let loginPage: LoginPage;

  test.beforeEach(async ({ page }) => {
    loginPage = new LoginPage(page);
    await loginPage.goto();
  });

  test('Sikeres bejelentkezés', async ({ page }) => {
    await loginPage.login('valid_user', 'correct_password');
    
    // Ellenőrzés Playwright assert-tel
    await expect(page).toHaveURL('https://example.com/dashboard');
  });

  test('Hibás bejelentkezés', async () => {
    await loginPage.login('invalid_user', 'wrong_password');
    
    await expect(loginPage.errorMessage).toBeVisible();
    await expect(loginPage.errorMessage).toContainText('Érvénytelen adatok');
  });
});

Java + Selenium (Klasszikus keretrendszer)

Java környezetben a PageFactory és a @FindBy annotációk használata a legelterjedtebb POM minta.

A Page Object osztály (LoginPage.java)

Java

package pages;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;

public class LoginPage {
    private WebDriver driver;

    // Lokátorok deklarálása annotációkkal
    @FindBy(id = "username")
    private WebElement usernameInput;

    @FindBy(id = "password")
    private WebElement passwordInput;

    @FindBy(css = "button[type='submit']")
    private WebElement loginButton;

    @FindBy(className = "flash-error")
    private WebElement errorMessage;

    // Konstruktor: inicializálja a PageFactory elemeit
    public LoginPage(WebDriver driver) {
        this.driver = driver;
        PageFactory.initElements(driver, this);
    }

    public void enterUsername(String username) {
        usernameInput.clear();
        usernameInput.sendKeys(username);
    }

    public void enterPassword(String password) {
        passwordInput.clear();
        passwordInput.sendKeys(password);
    }

    public void clickLogin() {
        loginButton.click();
    }

    public void login(String username, String password) {
        enterUsername(username);
        enterPassword(password);
        clickLogin();
    }

    public String getErrorMessageText() {
        return errorMessage.getText();
    }
}

A Tesztosztály (JUnit 5 használatával) (LoginTest.java)

Java

package tests;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import pages.LoginPage;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class LoginTest {
    private WebDriver driver;
    private LoginPage loginPage;

    @BeforeEach
    public void setUp() {
        driver = new ChromeDriver();
        driver.manage().window().maximize();
        driver.get("https://example.com/login");
        loginPage = new LoginPage(driver);
    }

    @Test
    public void testSuccessfulLogin() {
        loginPage.login("valid_user", "correct_password");
        assertEquals("https://example.com/dashboard", driver.getCurrentUrl());
    }

    @Test
    public void testFailedLogin() {
        loginPage.login("invalid_user", "wrong_password");
        assertTrue(loginPage.getErrorMessageText().contains("Érvénytelen adatok"));
    }

    @AfterEach
    public void tearDown() {
        if (driver != null) {
            driver.quit();
        }
    }
}

Főbb különbségek a két megközelítés között

SzempontPlaywright (TypeScript)Selenium (Java)
LokátorokA Locator objektumok deklaratívak (auto-wait)A @FindBy elemek lekezeléséhez várakozás (WebDriverWait) kellhet
PárhuzamosításAlapból támogatott a tesztfutztatóbanTestNG vagy JUnit szálkezelést igényel
InicializálásEgyszerű objektumkonstruktorPageFactory.initElements() hívás szükséges

A Playwright Custom Fixture funkciója segítségével a Page Object osztályok példányosítását teljesen kiszervezheted a tesztekből. Ezzel elkerülhető, hogy minden egyes tesztfájlban manuálisan kelljen megadni a const loginPage = new LoginPage(page) sorokat.

A tesztek így közvetlenül paraméterként kérhetik el a szükséges oldalobjektumokat, az infrastruktúra pedig automatikusan példányosítja és előkészíti azokat.

A Page Object osztályok (pages/)

A megszokott módon hozzuk létre az oldalklasszokat.

TypeScript

// pages/LoginPage.ts
import { Page, Locator } from '@playwright/test';

export class LoginPage {
  readonly page: Page;
  readonly usernameInput: Locator;

  constructor(page: Page) {
    this.page = page;
    this.usernameInput = page.locator('#username');
  }

  async goto() {
    await this.page.goto('/login');
  }
}

// pages/DashboardPage.ts
import { Page, Locator } from '@playwright/test';

export class DashboardPage {
  readonly page: Page;
  readonly userMenu: Locator;

  constructor(page: Page) {
    this.page = page;
    this.userMenu = page.locator('#user-menu');
  }
}

A Custom Fixture létrehozása (fixtures/baseTest.ts)

Itt terjesztjük ki a Playwright alapértelmezett test objektumát, megadva a saját Page Object fixture-jeinket.

TypeScript

import { test as base } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
import { DashboardPage } from '../pages/DashboardPage';

// 1. Típusdefiníció a fixture-ökhöz
type MyFixtures = {
  loginPage: LoginPage;
  dashboardPage: DashboardPage;
};

// 2. A 'base.extend' használatával regisztráljuk a saját fixture-jeinket
export const test = base.extend<MyFixtures>({
  // Automatikusan példányosítja a LoginPage-t és igény esetén megnyitja az oldalt
  loginPage: async ({ page }, use) => {
    const loginPage = new LoginPage(page);
    await loginPage.goto(); // Akár előkészítő lépéseket is tehetünk ide
    await use(loginPage);   // Átadja az objektumot a tesztnek
  },

  // Példányosítja a DashboardPage-t
  dashboardPage: async ({ page }, use) => {
    const dashboardPage = new DashboardPage(page);
    await use(dashboardPage);
  },
});

// Az 'expect'-et is exportáljuk a kényelmes importálásért
export { expect } from '@playwright/test';

A Fixture-ök használata a tesztekben (tests/login.spec.ts)

A @playwright/test helyett a saját fixtures/baseTest fájlunkból importáljuk a test objektumot. A tesztfüggvény paramétereként megadott nevek alapján a Playwright automatikusan beadagolja a megfelelő példányokat.

TypeScript

// Figyelem: A saját kiterjesztett test objektumunkat importáljuk!
import { test, expect } from '../fixtures/baseTest';

test('Sikeres bejelentkezés custom fixture-rel', async ({ loginPage, dashboardPage }) => {
  // A loginPage.goto() már lefutott a fixture-ben!
  await loginPage.usernameInput.fill('standard_user');
  
  // A dashboardPage is azonnal használható, nem kellett 'new'-val példányosítani
  await expect(dashboardPage.userMenu).toBeVisible();
});

Miért ez a legnépszerűbb Playwright architektúra?

  • Nulla boilerplate kód: Nem kell beforeEach blokkokban példányosítani az osztályokat.
  • Lazy loading (Lusta betöltés): Csak azok a Page Object-ek példányosulnak, amiket a teszt fejlécében ({ loginPage, dashboardPage }) kifejezetten elkérsz.
  • Beépített tear-down: A fixture-ben a use() hívás utáni kódrészlet automatikusan lefut a teszt végén (takarításra, kijelentkezésre ideális).
Szerző:
Kategória:
, ,
Bejegyzés dátuma:

Hozzászólások

Vélemény, hozzászólás?