top of page
Foto van schrijverBas Dam

Cucumber, Selenium en het gebruik van het Page Object Pattern


Als je Selenium gebruikt om je testen aan te sturen, kan je gebruik maken van het Page Object Pattern om een laag van abstractie aan te maken: Het maakt een model van je pagina zodat het makkelijker is om acties op die pagina uit te voeren. Dit heeft grote voordelen: Zodra er een wijziging op de pagina plaatsvindt, hoef je alleen het model aan te passen en niet je test acties. Een tester kan daarnaast redelijk eenvoudig teststappen opbouwen zonder de achterliggende Selenium code te hoeven beheersen.


Het spreekt voor zich dat je het Page Object Pattern graag zou willen gebruiken als je wilt testen met het Cucumber framework: Je glue code (of steps) kunnen dan op een hoog abstractielaag gemaakt worden waardoor je het lostrekt van het onderliggende uitvoerende mechanisme.


Voor een enkele pagina kan je dat als volgt doen:

public class CustomerStep {
    // maak een page object aan voor de customer page
    static CustomerPage customerPage = new CustomerPage();
 
    @Given("^I navigate to the customers page$")
    public void I_navigate_to_the_customers_page() throws 
                                               Throwable {
        // roep deze methode aan op het page object
        customerPage.makeActivePage(); 
    }
}
 
public class CustomerPage {
 
    // laten we hier een webDriver aanmaken zodat we kunnen 
    // interacteren met de pagina.
    public WebDriver webDriver;
 
    public CustomerPage() {
        WebDriver webDriver = new FirefoxDriver();
    }
 
    public void makeActivePage() {
        webDriver.navigate().to("http://foo.com/customer.html");
    }
}

Nu hebben we een werkend geheel waarbij we het Page Object Pattern kunnen gebruiken binnen Cucumber. We krijgen alleen een probleem als we meerdere pagina’s gaan gebruiken. Voor elke pagina wordt er namelijk een nieuwe webdriver aangemaakt. Niet handig als je meerdere scenario’s op dezelfde context (dus in dezelfde browsersessie) uit wilt voeren.


Dat lossen we als volgt op: We maken een class aan met een static webdriver die overerfbaar is.

public class CommonPage {
 
    // De webDriver verplaatsen we hiernaartoe. We maken hem 
    // static zodat er maar 1 webdriver aangemaakt wordt, 
    // onafhankelijk van het aantal pagina instanties die er 
    // gebruikt worden.
    public static WebDriver webDriver;
 
    protected CommonPage() {
        if (webDriver == null)
            webDriver = new FirefoxDriver();
    }
}

Wanneer we een nieuw Page Object maken, dan overerven we van deze class. Op die manier maken we altijd gebruik van dezelfde webdriver. Hieronder zien we hoe de het CustomerPage object eruit ziet met een CommonPage als super class:

Public class CustomerPage extends CommonPage {
 
    // We gebruiken de webDriver van de Super class
    public void makeActivePage() {
        webDriver.navigate().to("http://foo.com/customer.html");
    }
}

De class ziet er dus nog simpeler uit, terwijl we toch altijd gebruik kunnen maken van de webdriver.


Een ander voordeel is, is dat we generieke acties, die dus niet specifiek tot een pagina behoren, kwijt kunnen in deze super class: Alle pagina objecten hebben vanaf dan toegang tot deze aspecifieke methodes.

public class CommonPage {
 
    // [ ... ]
 
    // Op alle pagina’s komen secties voor. Laten we hier een
    // herbruikbare methode van maken. Alle pagina objecten kunnen
    // nu gebruik maken van “getSections”
    public List<WebElement> getSections(String sectionName) {
        return webDriver.findElements(
            By.cssSelector("section." + sectionName));
    }
}

Nu is de method “getSections” te gebruiken door iedere PageStep library. Op deze manier kunnen we dus ook een Common PageStep library maken waar we alle Common Steps in plaatsen:

public class CommonSteps {
 
    // We gebruiken geen specifieke page om onze acties op te 
    // doen, we kunnen natuurlijk wel een common page object 
    // gebruiken.
    CommonPage commonPage = new CommonPage();
 
    @Before
    public void beforeScenario() {
        // Plaats hier acties die altijd moeten gebeuren voordat 
        // een scenario uitgevoerd moet worden.
        // Bedenk eens wat je hier allemaal zou kunnen doen!
    }
 
    // Een pagina aspecifieke actie. Die kunnen we het beste 
    // hier kwijt.
    @Then("^it contains a \"([^\"]*)\" section$")
    public void it_contains_a_section(String sectionName) throws 
                                                     Throwable {
        List<WebElement> sections = 
            commonPage.getSections(sectionName);
        assertTrue("a section "+sectionName+" could be found.", 
            !sections.isEmpty());
    }
}

Daarnaast hebben we meteen een mooie placeholder voor de acties die voor, met de @Before annotatie, en na, met behulp van de @After annotatie, een scenario moeten gebeuren.


Door slim gebruik te maken van pagina objecten, overerving en een static webdriver kan een net en overzichtelijke codebasis gemaakt worden voor Cucumber testen die gebruik maken van Selenium. We splitsen de stap op in een beoogde actie op de pagina door een call naar het pagina object en de specifiek invulling van de actie door Selenium. Ook splitsen we generieke acties van specifieke pagina acties door gebruik te maken van een common library.

Recente blogposts

Alles weergeven

Comments


bottom of page