Lors de la construction d’un framework de tests d’automatisation, l’un des aspects cruciaux est de déterminer comment concevoir les tests de manière à permettre la distribution de la logique d’exécution indépendamment de l’implémentation.
À mesure que les projets évoluent rapidement avec de nouvelles fonctionnalités et impliquent des modifications des fonctionnalités existantes, les ingénieurs QA en automatisation font face aux tâches suivantes :
- Créer de nouveaux tests de haute qualité
- Soutenir et refactoriser les tests existants avec un minimum de modifications du code et de la structure
Pour accomplir ces tâches, nous utilisons le Page Object Model (POM).
Dans cet article, nous allons parler de l’importance du pattern Page Object Model dans l’automatisation Selenium. L’article explore différentes méthodes d’implémentation du pattern, notamment l’approche standard et l’utilisation de la classe Page Factory. Il examine les principaux avantages du POM et propose une analyse comparative entre POM et Page Factory.
Qu’est-ce que le Page Object Model (POM) ?
Le Page Object Model (POM) est un pattern de conception qui a gagné en popularité dans les tests d’automatisation pour améliorer la maintenance des tests et réduire la duplication de code. Les principales caractéristiques de ce pattern sont :
- Création d’une classe séparée dans le projet qui correspond à la page web appropriée de l’application
- La classe de page créée inclut une déclaration des éléments web correspondants situés sur cette page, ainsi qu’une liste de méthodes qui interagissent avec ces éléments
En tant que possibilités supplémentaires utiles pour améliorer le pattern, les éléments suivants peuvent être définis :
- Création d’une classe parente qui inclut tout le contenu commun pour les classes de pages. Il est recommandé de définir une telle classe comme abstraite pour éviter la possibilité de créer des objets de pages web inexistantes dans l’application
- Préparation d’une classe séparée pour les actions avec les éléments — comme la saisie de données, le clic sur des éléments et le défilement vers des éléments
Pour mieux comprendre l’efficacité de l’approche POM, examinons un exemple de test automatisé classique sans utiliser le Page Object Model.
Voici un extrait de code sans POM (test de connexion simple) :
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();
}
}
L’approche ci-dessus présente plusieurs inconvénients, notamment :
- Le pilote web a été initialisé directement dans la classe de test
- Absence d’une séparation nette entre le code de test et le code spécifique à la page (localisateurs)
- Avec des tests plus complexes, le nombre d’éléments et d’actions requis augmentera considérablement, entraînant des complications de lisibilité du code
- Si les éléments web sont utilisés dans plusieurs tests, ils doivent être déclarés séparément pour chaque test individuel
- Si les localisateurs d’éléments sont modifiés, les mises à jour doivent être appliquées à tous les endroits du projet où ces éléments sont utilisés
Implémentation du Page Object Model
Maintenant, appliquons le pattern POM au test décrit ci-dessus et observons la différence dans l’implémentation. L’implémentation POM comprendra les étapes suivantes :
- Création de classes séparées correspondant aux pages web de l’application (dans notre cas, il y aura 2 classes — LoginPage et ProductsPage)
- Déclaration des éléments web dans les deux classes de pages en utilisant By (classe abstraite dans Selenium)
- Préparation de méthodes pour l’interaction avec les éléments dans chaque classe
- Création d’une classe parente pour les tests
Voici un exemple de structure de projet avec POM :
La classe LoginPage définit les éléments web présents sur la page de connexion, les méthodes pour interagir avec eux, l’objet webdriver et un constructeur.
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");
}
}
}
La classe ProductsPage définit les éléments web présents sur la page Produits, ainsi que les méthodes pour interagir avec eux, l’objet webdriver et le constructeur.
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 est la classe parente pour les classes de test. Cette classe fournit les actions suivantes :
- Préparation de méthodes pour la création d’une session de pilote (avant chaque début de test) et sa fermeture (après la fin de l’exécution du test)
- Création et initialisation des objets des classes de pages
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();
}
}
La classe Test décrit uniquement la logique d’exécution des tests (sans initialisation du pilote, recherche d’éléments web et actions avec eux).
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();
}
}
Avantages de l’Utilisation du POM
Après avoir comparé les implémentations de tests avec et sans POM, nous pouvons identifier les avantages suivants du POM :
- Réutilisabilité du code. Nous pouvons utiliser les variables et méthodes de la classe de page dans tous les tests sans redéfinir les éléments web
- Facilité de maintenance et de refactorisation des tests
- Lisibilité du code. Ceci est obtenu en séparant le code de page du code de test
Qu’est-ce que Page Factory ?
Une autre variante pour l’implémentation du Page Object Model consiste à utiliser la classe Page Factory fournie par Selenium WebDriver. Les principales caractéristiques de Page Factory sont :
- Utilisation de l’annotation @FindBy pour localiser et déclarer les éléments (avec différentes stratégies de localisation)
- Utilisation de la méthode statique initElements() pour initialiser les éléments de la page web courante qui ont été déclarés avec l’annotation @FindBy
- Prise en charge du concept d’initialisation différée (en utilisant la classe AjaxElementLocatorFactory) pour identifier les éléments web uniquement lorsqu’ils sont utilisés dans des opérations et des actions
Implémentation de Page Factory
Comme base, nous allons reprendre le projet précédemment créé et le mettre à jour en utilisant Page Factory. La structure du projet, les classes Test et Parent resteront inchangées. Les modifications seront uniquement apportées dans les classes de pages.
Les modifications suivantes ont été effectuées :
- Déclaration des éléments web en utilisant l’annotation @FindBy et l’interface WebElement
- Initialisation des éléments web dans le constructeur de la classe en utilisant la méthode initElements() de la classe PageFactory
- Mise à jour des méthodes qui interagissent avec les éléments
Pour 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 vs Page Factory
Résumons et définissons les principales différences entre POM et Page Factory :
It is a design pattern
It is a class provided by Selenium WebDriver for POM implementation
For finding web elements, the By class is used
For finding web elements, the @FindBy annotation is used
Each page class object must be initialized individually
All page elements are initialized using the static method initElements()
Does not provide lazy initialization
Provides lazy initialization
En Conclusion
Avant de mettre en place un framework pour les tests d’automatisation, il faut réfléchir à la manière efficace de séparer la logique métier des tests du code pour son implémentation. Cette approche rend les tests plus lisibles, réutilisables et plus faciles à maintenir.
Pour atteindre ces objectifs, vous pouvez utiliser le Page Object Model (POM). Ce pattern peut être implémenté de manière standard ou via la classe Page Factory.
À mon avis, l’implémentation POM utilisant Page Factory présente plus d’avantages par rapport au POM classique. Parmi ces avantages, on peut citer la possibilité d’initialiser tous les éléments web en même temps, la prise en charge du concept de chargement différé et une déclaration d’éléments plus précise grâce à l’annotation @FindBy.
See how we helped Kazidomi speed up feature delivery with test automation and become #1 startup in Belgium