Beim Aufbau eines Frameworks für Automatisierungstests besteht einer der wichtigsten Aspekte darin, zu bestimmen, wie Tests so konzipiert werden können, dass die Ausführungslogik unabhängig von der Implementierung verteilt werden kann.
Da Projekte schnell mit neuen Funktionalitäten wachsen und Änderungen an bestehenden Funktionen beinhalten, stehen QA-Automatisierungsingenieure vor folgenden Aufgaben:
- Erstellung hochwertiger neuer Tests
- Unterstützung und Refactoring bestehender Tests mit minimalen Änderungen an Code und Struktur
Um diese Aufgaben zu erfüllen, verwenden wir das Page Object Model (POM).
In diesem Artikel sprechen wir über die Bedeutung des Page Object Model-Musters in der Selenium-Automatisierung. Der Artikel untersucht verschiedene Methoden der Musterimplementierung, einschließlich des Standardansatzes und der Verwendung der Page Factory-Klasse. Er geht auf die wichtigsten Vorteile von POM ein und bietet eine vergleichende Analyse zwischen POM und Page Factory.
Was ist das Page Object Model (POM)?
Das Page Object Model (POM) ist ein Entwurfsmuster, das sich bei Automatisierungstests großer Beliebtheit erfreut, da es die Testwartung verbessert und die Codeduplikation reduziert. Die wichtigsten Spezifikationen dieses Musters sind:
- Erstellung einer eigenen Klasse im Projekt, die der entsprechenden Webseite der Anwendung entspricht.
- Die erstellte Seitenklasse enthält eine Deklaration der entsprechenden Webelemente, die sich auf dieser Seite befinden, zusammen mit einer Liste von Methoden, die mit diesen Elementen interagieren
Als weitere nützliche Möglichkeiten zur Verbesserung des Musters können die folgenden definiert werden:
- Erstellung einer übergeordneten Klasse, die den gesamten gemeinsamen Inhalt der Seitenklassen enthält. Es ist eine gute Praxis, eine solche Klasse als abstrakt zu definieren, um die Möglichkeit zu vermeiden, Objekte von nicht existierenden Webseiten in der Anwendung zu erstellen
- Erstellung einer separaten Klasse für Aktionen mit Elementen – wie die Eingabe von Daten, das Anklicken von Elementen und das Scrollen zu Elementen
Um die Effektivität des POM-Ansatzes besser zu verstehen, sehen wir uns ein Beispiel für einen typischen automatisierten Test an, bei dem das Page Object Model nicht verwendet wird.
Hier ist ein Codeschnipsel ohne POM (einfacher 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();
}
}
Der obige Ansatz hat mehrere Nachteile, unter anderem:
- Der Web-Treiber wird direkt in der Testklasse initialisiert
- Fehlende saubere Trennung zwischen Testcode und seitenbezogenem Code (Locators)
- Bei komplexeren Tests steigt die Anzahl der benötigten Elemente und Aktionen deutlich an, was zu Komplikationen bei der Lesbarkeit des Codes führt
- Wenn die Webelemente in mehreren Tests verwendet werden, müssen sie für jeden einzelnen Test separat deklariert werden.
- Wenn Element-Locators geändert werden, müssen Aktualisierungen an allen Stellen im Projekt vorgenommen werden, an denen diese Elemente verwendet werden.
Implementierung des Page Object Model
Wenden wir nun das POM-Muster auf den oben beschriebenen Test an und beobachten wir den Unterschied in der Implementierung. Die POM-Implementierung umfasst die folgenden Schritte:
- Erstellung von separaten Klassen, die den Webseiten der Anwendung entsprechen (in unserem Fall wird es 2 Klassen geben – LoginPage und ProductsPage)
- Deklaration von Web-Elementen in beiden Seitenklassen mit By (abstrakte Klasse in Selenium)
- Vorbereitung von Methoden für die Interaktion mit Elementen in jeder Klasse
- Erstellung einer übergeordneten Klasse für Tests
Schauen Sie sich die Beispielprojektstruktur mit POM an:
Die Klasse LoginPage definiert die auf der Login-Seite vorhandenen Web-Elemente, die Methoden zur Interaktion mit ihnen, das Webdriver-Objekt und einen Konstruktor.
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");
}
}
}
Die Klasse ProductsPage definiert die auf der Seite Products vorhandenen Webelemente sowie die Methoden für die Interaktion mit ihnen, das Webdriver-Objekt und den Konstruktor.
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 ist die übergeordnete Klasse für Testklassen. Diese Klasse bietet die folgenden Aktionen:
- Vorbereitungsmethoden für das Erstellen einer Treibersitzung (vor jedem Testbeginn) und das Schließen der Sitzung (nach Beendigung des Testlaufs)
- Erstellung und Initialisierung von Seitenklassenobjekten
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();
}
}
Die Testklasse beschreibt nur die Logik der Testausführung (ohne Treiberinitialisierung, Suche nach Webelementen und Aktionen mit ihnen).
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();
}
}
Vorteile der Verwendung von POM
Nach dem Vergleich von Testimplementierungen mit POM und ohne POM können wir die folgenden Vorteile von POM feststellen:
- Wiederverwendbarkeit des Codes. Wir können Variablen und Methoden der Seitenklasse in allen Tests verwenden, ohne die Webelemente neu definieren zu müssen
- Einfache Testwartung und Refaktorierung
- Lesbarkeit des Codes. Dies wird erreicht, indem der Seitencode vom Testcode getrennt wird
Was ist Page Factory?
Eine weitere Variante für die Implementierung des Page Object Models ist die Verwendung der Page Factory-Klasse, die von Selenium WebDriver bereitgestellt wird. Die wichtigsten Spezifikationen der Page Factory sind:
- Verwendung der @FindBy-Annotation zum Auffinden und Deklarieren von Elementen (mit verschiedenen Locator-Strategien)
- Verwendung der statischen Methode initElements() zur Initialisierung von Elementen der aktuellen Webseite, die mit der @FindBy-Annotation deklariert wurden
- Unterstützung des Konzepts der “lazy initialization” (unter Verwendung der Klasse AjaxElementLocatorFactory) zur Identifizierung von Webelementen nur dann, wenn sie in beliebigen Operationen und Aktionen verwendet werden
Implementierung der Page Factory
Als Grundlage nehmen wir das zuvor erstellte Projekt und aktualisieren es mit Page Factory. Die Projektstruktur, die Test- und Parent-Klassen bleiben unverändert. Die Änderungen werden nur in den Page-Klassen vorgenommen.
Die folgenden Änderungen wurden vorgenommen:
- Deklarierte Webelemente unter Verwendung der @FindBy-Annotation und der WebElement-Schnittstelle
- Initialisierung von Webelementen im Klassenkonstruktor mit der Methode initElements() der Klasse PageFactory
- Aktualisierte Methoden, die mit Elementen interagieren
Für 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");
}
}
Für ProdukteSeite:
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 vs. Page Factory
Lassen Sie uns die Hauptunterschiede zwischen POM und Page Factory zusammenfassen und definieren:
Es ist ein Entwurfsmuster
Es handelt sich um eine von Selenium WebDriver bereitgestellte Klasse für die POM-Implementierung
Für die Suche nach Webelementen wird die Klasse By verwendet
Für die Suche nach Webelementen wird die @FindBy-Annotation verwendet
Jedes Objekt der Seitenklasse muss einzeln initialisiert werden
Alle Seitenelemente werden mit der statischen Methode initElements() initialisiert
Bietet keine faule Initialisierung
Bietet faule Initialisierung
Fazit
Bevor man ein Framework für Automatisierungstests einrichtet, muss man sich überlegen, wie man die Geschäftslogik der Tests effektiv vom Code für ihre Implementierung trennt. Dieser Ansatz macht die Tests lesbarer, wiederverwendbar und leichter zu pflegen.
Um diese Ziele zu erreichen, können Sie das Page Object Model (POM) verwenden. Dieses Muster kann auf standardmäßige Weise oder über die Klasse Page Factory implementiert werden.
Meiner Meinung nach hat die POM-Implementierung mit Page Factory mehr Vorteile als das typische POM. Einige dieser Vorteile sind die Möglichkeit, alle Webelemente auf einmal zu initialisieren, die Unterstützung des Lazy-Load-Konzepts und eine genauere Elementdeklaration mit der @FindBy-Annotation.