Selenium WebDriver Abstract Class Kullanımı
Page Object Model yaklaşımı kullanılarak yapılan test otomasyon projelerinde sınıf hiyerarşisi oluşturulurken, bazen en tepede bulunan sınıfın kendisinden türetilecek olan alt sınıflar için ortak bir arayüz görevinde bulunması istenebilir. Nesne yönelimli programlama dillerinde bu tarz durumlar için soyut sınıf yani abstract class özelliği kullanılmaktadır. Soyut sınıflar büyük test otomasyon projelerinde kullanılırlar ve kalıtım özelliği sayesinde kod tekrarını azaltırlar. Bugün Java programlama dilini kullanarak Page Object Model yaklaşımında soyut sınıf kullanımına değineceğim.
Giriş
Page adında bir soyut sınıf oluşturarak işe başlayacağız sonrasında bu sınıfın içerisine ortak kullanılacak methodları tanımlayacağız. BasePage adında bir sınıf oluşturarak Page sınıfından kalıtım alacağız, Page sınıfı soyut bir sınıf olduğu için içerisinde yer alan methodları override ederek kullanacağız. Projemizi tamamladığımızda son hali bu şekilde görünecektir, projeyi GitHub’da bulabilirsiniz yazının sonunda linkini paylaşacağım.
Adım adım ilerleyeceğim konuyla ilgili sorularınız olursa lütfen benimle iletişime geçin, hazırsanız başlayalım.
Adım-1
Java’da Maven projesi oluşturalım ve pom.xml dosyasına aşağıda gösterildiği gibi bağımlılıkları ekleyelim.
Adım-2
src/java paketinin altına pages, tests ve collections adında üç adet paket oluşturalım. Projemizde kullanacağımız ortak methodları yazabilmek için pages paketinin altına Page adında bir sınıf oluşturalım.
Oluşturduğumuz sınıfı soyut sınıf olarak değiştirmek ve methodları soyut method yapmak için abstract anahtar kelimesini kullanalım.
getInstance(): Generic bir method oluşturdum, generic method türünden bir tane parametreye sahip ve geri dönüş değeri olarak bu method çağrıldığında ona verilen sınıfın bir örneğini yaratacak ve bu sınıfta tanımlanan WebDriver nesnesini kullanacaktır.
Page soyut sınıfına ait kod bloğu aşağıdaki gibi görünmektedir.
package pages; import lombok.SneakyThrows; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.WebDriverWait; import java.util.List; public abstract class Page { WebDriver driver; WebDriverWait wait; public Page(WebDriver driver) { this.driver = driver; this.wait = new WebDriverWait(this.driver, 10); } public abstract void click(By locator); public abstract WebElement waitElement(By locator); public abstract void writeText(By locator, String text); public abstract String readText(By locator); public abstract String getAlertboxText(); public abstract WebElement find(By locator); public abstract List<WebElement> multipleFind(By locator); public abstract void acceptAlertbox(); public abstract void selectItem(By locator, String value); @SneakyThrows public <TPage extends BasePage> TPage getInstance(Class<TPage> pageClass) { return pageClass.getDeclaredConstructor(WebDriver.class).newInstance(this.driver); } }
Adım-3
Page sınıfında tanımladığımız abstract methodları override ederek ilgili methodların içlerini dolduralım, bunun için pages paketinin içerisine BasePage adında bir sınıf oluşturuyoruz. Sınıfı oluşturduktan sonra Page soyut sınıfından kalıtım almayı unutmayın, kalıtım aldığınızda kullandığınız IDE, size override etmeniz gereken methodlar olduğunu bildirecektir.
waitElement(): Bu method sayfada aradığınız elementi bulunana kadar bekleyecektir, bulamadığı durumda selenium.TimeoutException fırlatacaktır. By türünden bir adet parametre almaktadır ve sonucunu WebElement türünden dönmektedir, diğer tüm methodlar sayfadaki elementlerle etkileşime (click, writeText, readText vs.) geçmeden önce bu methodu kullanacaktır.
BasePage sınıfına ait kod bloğu aşağıdaki gibi görünmektedir.
package pages; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.Select; import java.util.List; public class BasePage extends Page { public BasePage(WebDriver driver) { super(driver); } @Override public void click(By locator) { waitElement(locator).click(); } @Override public WebElement waitElement(By locator) { return wait.until(ExpectedConditions.visibilityOfElementLocated(locator)); } @Override public void writeText(By locator, String text) { waitElement(locator).sendKeys(text); } @Override public String readText(By locator) { return waitElement(locator).getText(); } @Override public String getAlertboxText() { return driver.switchTo().alert().getText(); } @Override public WebElement find(By locator) { return waitElement(locator).findElement(locator); } @Override public List<WebElement> multipleFind(By locator) { return waitElement(locator).findElements(locator); } @Override public void acceptAlertbox() { driver.switchTo().alert().accept(); } @Override public void selectItem(By locator, String value) { new Select(find(locator)).selectByVisibleText(value); } }
Adım-4
Tüm testler için ortak kullanılacak olan BaseTest sınıfını tests paketinin altına oluşturalım, oluşturduğumuz test senaryolarını çalıştırdığımızda sırasıyla BeforeClass, BeforeMethod, Test ve AfterClass anatasyonlarına sahip methodlar çalışacaktır.
methodSetup(): Bu methodun içerisine soyut sınıfımızın bir örneğini oluşturduk ve soyut methodları override ettiğimiz BasePage sınıfını parametre olarak gönderdik böylece burada oluşan WebDriver nesnesini BasePage sınıfına da taşımış olduk.
BaseTest sınıfına ait kod bloğu aşağıdaki gibi görünmektedir.
package tests; import io.github.bonigarcia.wdm.WebDriverManager; import org.openqa.selenium.WebDriver; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.chrome.ChromeOptions; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; import pages.BasePage; import pages.Page; public class BaseTest { //region Variables protected WebDriver driver; protected ChromeOptions chromeOptions; public Page page; //endregion @BeforeClass public void classSetUp() { WebDriverManager.chromedriver().setup(); chromeOptions = new ChromeOptions(); chromeOptions.setHeadless(false); driver = new ChromeDriver(chromeOptions); driver.manage().window().maximize(); } @BeforeMethod public void methodSetup() { page = new BasePage(driver); } @AfterClass public void tearDown() { driver.quit(); } }
Adım-5
pages paketinin altına oturum açma sayfamız için LoginPage adında sınıf oluşturalım. BasePage sınıfında kalıtım almayın unutmayın, kalıtım aldığınızda kullandığınız IDE constructor oluşturmanız için sizi bilgilendirecektir.
signIn(): HomePage geri dönüş türüne sahip bir method oluşturdum, HomePage sınıfını sonraki adımda oluşturacağım. Geri dönüş değeri olarak ise Page soyut sınıfında tanımladığım generic olan getInstance() methodunu çağırıyorum ve parametre olarak yine HomePage sınıfı veriyorum, böylece burada tanımlı olan WebDriver nesnesini HomePage sınıfında da kullanabileceğim.
LoginPage sınıfına ait kod bloğu aşağıdaki gibi görünmektedir.
package pages; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import static collections.BaseCollection.URL; public class LoginPage extends BasePage { public LoginPage(WebDriver driver) { super(driver); } By btnEnter = By.xpath("//button[contains(text(),'Bank Manager Login')]"); public HomePage signIn() { driver.get(URL); click(btnEnter); return getInstance(HomePage.class); } }
Kafanızda canlanabilmesi için oturum açma sayfasının ekran görüntüsü ve test ettiğim sayfanın linkini aşağıya bırakıyorum.
https://www.way2automation.com/angularjs-protractor/banking/#/login
Adım-6
HomePage sınıfının içerisine, oturum açtıktan sonra test edeceğimiz sayfalara yönlendirebilmek için birer navigasyon methodu oluşturdum. Navigasyon methodlarının dönüş türünü ilgili sayfalara ait sınıflar olarak belirttim ve aynı şekilde Page soyut sınıfında kullanılan getInstance() generic methodunu çağırdım.
HomePage sınıfına ait kod bloğu aşağıdaki gibi görünmektedir.
package pages; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; public class HomePage extends BasePage { public HomePage(WebDriver driver) { super(driver); } //region Page Elements By btnAddCustomer = By.xpath("//body/div[3]/div[1]/div[2]/div[1]/div[1]/button[1]"); By btnOpenAccount = By.xpath("//body/div[3]/div[1]/div[2]/div[1]/div[1]/button[2]"); //endregion //region Page Methods public CustomerAddPage navigateCustomerAddPage() { click(btnAddCustomer); return getInstance(CustomerAddPage.class); } public OpenAccountPage navigateOpenAccountPage() { click(btnOpenAccount); return getInstance(OpenAccountPage.class); } //endregion }
Home Page sınıfını temsil eden ana sayfanın ekranın görüntüsünü de aşağıya bırakıyorum.
Adım-7
Add Customer sayfasına ait sınıfı, pages paketinin altına oluşturarak devam edelim. Sayfada yer alan elementleri ve testler sırasında kullanılacak methodları tanımlandım, Page sınıfından türeyen ve BasePage sınıfında override edilen methodlarda hiçbir değişiklik yapmadan kullanabildim bu sayede kod tekrarını engellemiş olduk ayrıca bakımı kolay test kodları yazabildik.
CustomerAddPage sınıfına ait kod bloğu aşağıdaki gibi görünmektedir.
package pages; import lombok.SneakyThrows; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.testng.Assert; public class CustomerAddPage extends BasePage { public CustomerAddPage(WebDriver driver) { super(driver); } //region Page Elements By txtFirstName = By.xpath("//body/div[3]/div[1]/div[2]/div[1]/div[2]/div[1]/div[1]/form[1]/div[1]/input[1]"); By txtLastName = By.xpath("//body/div[3]/div[1]/div[2]/div[1]/div[2]/div[1]/div[1]/form[1]/div[2]/input[1]"); By txtPostCode = By.xpath("//body/div[3]/div[1]/div[2]/div[1]/div[2]/div[1]/div[1]/form[1]/div[3]/input[1]"); By btnSave = By.xpath("//body/div[3]/div[1]/div[2]/div[1]/div[2]/div[1]/div[1]/form[1]/button[1]"); //endregion //region Page Methods public CustomerAddPage defineCustomerAdd(String name, String lastname, String postCode) { writeText(txtFirstName, name); writeText(txtLastName, lastname); writeText(txtPostCode, postCode); click(btnSave); return this; } @SneakyThrows public CustomerAddPage verifyTestResult(String expect) { Thread.sleep(500); Assert.assertTrue(getAlertboxText().contains(expect)); driver.switchTo().alert().accept(); return this; } //endregion }
Adım-8
Customer Add sayfasına ait test senaryolarımızı oluşturmak için tests paketinin içerisine CustomerAddTest adında bir sınıf oluşturdum ve BaseTest sınıfından kalıtım aldım.
CustomerAddTest sınıfına ait kod bloğu aşağıdaki gibi görünmektedir.
package tests; import org.testng.annotations.Test; import pages.HomePage; import pages.LoginPage; public class CustomerAddTest extends BaseTest { @Test public void customerAddFeature() { page.getInstance(LoginPage.class) .signIn() .navigateCustomerAddPage() .defineCustomerAdd("Yasin", "Albakır", "06690") .verifyTestResult("Customer added successfully with customer"); } @Test public void withAllRequiredFieldsBlank() { page.getInstance(HomePage.class) .navigateCustomerAddPage() .defineCustomerAdd(" ", " ", " ") .verifyTestResult("Please check the details. Customer may be duplicate."); } }
Sonuç
TestNG kütüphanesini kullandığım için testng.xml dosyası oluşturarak yazdığım test senaryolarını çalıştırdım.
Sizde test otomasyon projelerinizde soyut sınıfları kullanarak kendini tekrar etmeyen bakımı kolay test kodları oluşturabilirsiniz.
Sağlıklı ve mutlu günler dilerim.
GitHub Projesi
Oluşturduğum örnek projeye ait kaynak kodlarına aşağıdaki linkten ulaşabilirsiniz.