Bij het bouwen van een framework voor automatiseringstests is een van de cruciale aspecten om te bepalen hoe tests ontworpen moeten worden op een manier die de distributie van uitvoeringslogica onafhankelijk van de implementatie toestaat.
Naarmate projecten snel groeien met nieuwe functionaliteiten en veranderingen in bestaande functies met zich meebrengen, staan QA-automatiseringstechnici voor de volgende taken:
- Het creëren van nieuwe tests van hoge kwaliteit
- Het ondersteunen en refactoren van bestaande tests met minimale wijzigingen in code en structuur
Om deze taken te volbrengen, gebruiken we het Page Object Model (POM).
In dit artikel bespreken we het belang van het Page Object Model-patroon in Selenium-automatisering. Het artikel onderzoekt verschillende implementatiemethoden van het patroon, waaronder de standaardaanpak en het gebruik van de Page Factory-klasse. Het duikt in de belangrijkste voordelen van POM en biedt een vergelijkende analyse tussen POM en Page Factory.
Wat is Page Object Model (POM)?
Page Object Model (POM) is een ontwerppatroon dat populair is geworden in automatiseringstests voor het verbeteren van het onderhoud van tests en het verminderen van code-duplicatie. De belangrijkste specificaties van dit patroon zijn:
- Het aanmaken van een aparte klasse in het project die overeenkomt met de betreffende webpagina van de applicatie
- De aangemaakte paginaklasse bevat een declaratie van de bijbehorende web-elementen die zich op deze pagina bevinden, samen met een lijst van methoden die met deze elementen interageren
Als aanvullende nuttige mogelijkheden voor het verbeteren van het patroon kunnen de volgende worden gedefinieerd:
- Het aanmaken van een bovenliggende klasse die alle gemeenschappelijke inhoud voor paginaklassen bevat. Het is goede praktijk om zo’n klasse abstract te definiëren om de mogelijkheid van het aanmaken van objecten van niet-bestaande webpagina’s in de applicatie te voorkomen
- Het voorbereiden van een aparte klasse voor acties met elementen — zoals het invoeren van gegevens, klikken op elementen en scrollen naar elementen
Om de effectiviteit van de POM-aanpak beter te begrijpen, laten we een voorbeeld bekijken van een typische geautomatiseerde test zonder het Page Object Model te gebruiken.
Hier is een codefragment zonder POM (eenvoudige login-test):
public class LoginWithOutPOMTest {
WebDriver webDriver;
@Before
public void setUp() {
try{
WebDriverManager.chromedriver().setup();
webDriver = new ChromeDriver();
webDriver.manage().window().maximize();
webDriver.manage().timeouts().implicitlyWait(Duration.ofSeconds(20));
}catch (Exception e){
Assert.fail("Can not create driver session");
}
}
@Test
public void validLogin() {
webDriver.get("https://www.saucedemo.com");
webDriver.findElement(By.id("user-name")).clear();
webDriver.findElement(By.id("user-name")).sendKeys("standard_user");
webDriver.findElement(By.id("password")).clear();
webDriver.findElement(By.id("password")).sendKeys("secret_sauce");
webDriver.findElement(By.id("login-button")).click();
String text = webDriver.findElement(
By.cssSelector("div.header_secondary_container span.title")).getText();
Assert.assertTrue("Login was not successful", text.contains("Products"));
}
@After
public void tearDown() {
webDriver.quit();
}
}
De bovenstaande aanpak heeft verschillende nadelen, waaronder:
- De web driver is direct in de testklasse geïnitialiseerd
- Gebrek aan een duidelijke scheiding tussen de testcode en paginaspecifieke code (locators)
- Bij complexere tests zal het aantal vereiste elementen en acties aanzienlijk toenemen, wat leidt tot complicaties in de leesbaarheid van de code
- Als de web-elementen in meerdere tests worden gebruikt, moeten ze afzonderlijk voor elke individuele test worden gedeclareerd
- Als de element-locators worden gewijzigd, moeten updates worden toegepast op alle plaatsen in het project waar deze elementen worden gebruikt
Implementatie van Page Object Model
Laten we nu het POM-patroon toepassen op de hierboven beschreven test en het verschil in implementatie observeren. De POM-implementatie omvat de volgende stappen:
- Het aanmaken van aparte klassen die overeenkomen met webpagina’s van de applicatie (in ons geval zullen er 2 klassen zijn — LoginPage en ProductsPage)
- Declaratie van web-elementen in beide paginaklassen met behulp van By (abstracte klasse in Selenium)
- Voorbereiding van methoden voor interactie met elementen in elke klasse
- Het aanmaken van een bovenliggende klasse voor tests
Bekijk de voorbeeldprojectstructuur met POM:
De LoginPage-klasse definieert de web-elementen op de Inlogpagina, methoden voor interactie met deze elementen, het webdriver-object en een constructor.
public class LoginPage {
WebDriver webDriver;
/**
* create page constructor
*/
public LoginPage(WebDriver webDriver) {
this.webDriver = webDriver;
}
/**
* define web elements for Login page
*/
By userNameField = By.id("user-name");
By passWordField = By.id("password");
By loginBtn = By.id("login-button");
// method for entering username
public void enterUserName(String userName) {
webDriver.findElement(userNameField).clear();
webDriver.findElement(userNameField).sendKeys(userName);
}
// method for entering password
public void enterPassWord(String passWord) {
webDriver.findElement(passWordField).clear();
webDriver.findElement(passWordField).sendKeys(passWord);
}
// method for clicking on Login button
public void clickOnLogin() {
webDriver.findElement(loginBtn).click();
}
// method for opening login page
public void openLoginPage() {
try{
webDriver.get("https://www.saucedemo.com/");
}catch (Exception e){
Assert.fail("Impossible to open Login page");
}
}
}
De ProductsPage-klasse definieert de web-elementen op de Productenpagina, samen met methoden voor interactie met deze elementen, het webdriver-object en de constructor.
public class ProductsPage {
WebDriver webDriver;
/**
* create page constructor
*/
public ProductsPage(WebDriver webDriver) {
this.webDriver = webDriver;
}
/**
* define web elements for Products page
*/
By productsTitle = By.cssSelector("div.header_secondary_container span.title");
public void checkProductsPageOpened() {
String title = webDriver.findElement(productsTitle).getText();
Assert.assertTrue("Products page was not opened", title.contains("Products"));
}
}
ParentTest is de bovenliggende klasse voor testklassen. Deze klasse biedt de volgende acties:
- Voorbereiding van methoden voor het maken van een driver-sessie (vóór elke test) en het sluiten ervan (na afloop van de test)
- Aanmaken en initialiseren van objecten van paginaklassen
public class ParentTest {
WebDriver webDriver;
protected LoginPage loginPage;
protected ProductsPage productsPage;
@Before
public void setUp() {
try{
WebDriverManager.chromedriver().setup();
webDriver = new ChromeDriver();
webDriver.manage().window().maximize();
webDriver.manage().timeouts().implicitlyWait(Duration.ofSeconds(20));
loginPage = new LoginPage(webDriver);
productsPage = new ProductsPage(webDriver);
}catch (Exception e){
Assert.fail("Can not create driver session");
}
}
@After
public void tearDown() {
webDriver.quit();
}
}
De Testklasse beschrijft alleen de logica van de testuitvoering (zonder driver-initialisatie, het vinden van web-elementen en acties daarmee).
public class LoginWithPOMTest extends ParentTest {
String username = "standard_user";
String password = "secret_sauce";
@Test
public void validLogin() {
loginPage.openLoginPage();
loginPage.enterUserName(username);
loginPage.enterPassWord(password);
loginPage.clickOnLogin();
productsPage.checkProductsPageOpened();
}
}
Voordelen van het gebruik van POM
Na het vergelijken van testimplementaties met POM en zonder POM, kunnen we de volgende POM-voordelen identificeren:
- Herbruikbaarheid van code. We kunnen variabelen en methoden van de paginaklasse in alle tests gebruiken zonder de web-elementen opnieuw te definiëren
- Gemakkelijk onderhoud en refactoring van tests
- Leesbaarheid van code. Dit wordt bereikt door de paginacode te scheiden van de testcode
Wat is Page Factory?
Een andere variant voor de implementatie van het Page Object Model is het gebruik van de Page Factory-klasse die door Selenium WebDriver wordt geleverd. De belangrijkste specificaties van de Page Factory zijn:
- Gebruik van de @FindBy-annotatie voor het lokaliseren en declareren van elementen (met verschillende locator-strategieën)
- Gebruik van de statische methode initElements() om elementen van de huidige webpagina te initialiseren die met de @FindBy-annotatie zijn gedeclareerd
- Ondersteuning van het lazy initialisatie-concept (met behulp van de AjaxElementLocatorFactory-klasse) voor het identificeren van web-elementen alleen wanneer ze worden gebruikt in enige bewerkingen en acties
Implementatie van Page Factory
Als basis nemen we het eerder gemaakte project en werken we het bij met Page Factory. De projectstructuur, Test- en Parent-klassen blijven ongewijzigd. De wijzigingen worden alleen doorgevoerd in de paginaklassen.
De volgende wijzigingen zijn doorgevoerd:
- Web-elementen gedeclareerd met behulp van de @FindBy-annotatie en WebElement-interface
- Web-elementen geïnitialiseerd in de klasse-constructor met behulp van de methode initElements() van de PageFactory-klasse
- Methoden die met elementen interageren bijgewerkt
Voor LoginPage:
public class LoginPage {
WebDriver webDriver;
public LoginPage(WebDriver webDriver) {
this.webDriver = webDriver;
PageFactory.initElements(webDriver, this);
}
/**
* define web elements for Login page using @FindBy annotation
*/
@FindBy(id = "user-name")
WebElement userNameField;
@FindBy(id = "password")
WebElement passWordField;
@FindBy(id = "login-button")
WebElement loginBtn;
// method for entering username
public void enterUserName(String userName) {
userNameField.clear();
userNameField.sendKeys(userName);
}
// method for entering password
public void enterPassWord(String passWord) {
passWordField.clear();
passWordField.sendKeys(passWord);
}
// method for clicking on Login button
public void clickOnLogin() {
loginBtn.click();
}
// method for opening login page
public void openLoginPage() {
try {
webDriver.get("https://www.saucedemo.com/");
} catch (Exception e) {
Assert.fail("Impossible to open Login page");
}
}
For ProductsPage:
public class ProductsPage {
WebDriver webDriver;
/**
* create page constructor
*/
public ProductsPage(WebDriver webDriver) {
this.webDriver = webDriver;
PageFactory.initElements(webDriver, this);
}
/**
* define web elements for Products page using @FindBy annotation
*/
@FindBy(css = "div.header_secondary_container span.title")
WebElement productsTitle;
public void checkProductsPageOpened() {
String title = productsTitle.getText();
Assert.assertTrue("Products page was not opened", title.contains("Products"));
}
}
Page Object Model versus Page Factory
Laten we de belangrijkste verschillen tussen POM en Page Factory samenvatten en definiëren:
Het is een ontwerppatroon
Het is een klasse geleverd door Selenium WebDriver voor POM-implementatie
Voor het vinden van web-elementen wordt de By-klasse gebruikt
Voor het vinden van web-elementen wordt de @FindBy-annotatie gebruikt
Elk page-object moet individueel worden geïnitialiseerd
Alle page-elementen worden geïnitialiseerd met de statische methode initElements()
Biedt geen ‘lazy initialization’
Biedt ‘lazy initialization’
Conclusie
Voordat u een framework opzet voor automatiseringstesten, moet u nadenken over de effectieve manier om de testlogica te scheiden van de code voor de implementatie ervan. Deze aanpak maakt tests leesbaarder, herbruikbaarder en gemakkelijker te onderhouden.
Om deze doelen te bereiken, kunt u gebruikmaken van het Page Object Model (POM). Dit patroon kan op een standaardmanier worden geïmplementeerd of via de Page Factory-klasse.
Naar mijn mening heeft de implementatie van POM met Page Factory meer voordelen vergeleken met de standaard POM. Enkele van deze voordelen zijn de mogelijkheid om alle web-elementen in één keer te initialiseren, ondersteuning voor het ‘lazy load’-concept en een nauwkeurigere elementdeclaratie met de @FindBy-annotatie.
Ontdek hoe we Kazidomi hebben geholpen om de levering van functies te versnellen met testautomatisering en de #1 startup in België te worden