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.

POM Soyut Sınıf Kullanımı - Proje Yapısı
Proje Yapısı

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.

POM Soyut Sınıf Kullanımı - Pom.xml
Pom.xml

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.

POM Soyut Sınıf Kullanımı - Page abstract class
Page abstract class

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

POM Soyut Sınıf Kullanımı - Login Page
Login Page

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.

Home Page
Home Page

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.

Test Result
Test Result

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.

https://github.com/yasinealbakir/conceptAbstractionPom