Drupal Testing: BDD Considerations

 

Feature Files

  • The Gherkin feature files should be treated like software & managed with great care
  • In doing so, not only will the framework be more concise & maintainable, but the BDD feature files essentially will write the framework themselves.
  • Shared Step Definition files can act as reusable macros, making scenarios shorter while reducing the size of the framework and time to maintain them.
  • Come up with a standard language and syntax for ALL feature files
  • Create templates in Excel that have available test steps to choose from
    • this will significantly reduce the amount of coding required for each scenario through REUSE
    • and thereby reduce the time it takes to automate them
    • and thereby avoid duplication of code & the cost of maintaining it

 

Step Definition Files

  • Since collisions occur with duplicate method names across Definition Files, managing Step Definitions is critical to a healthy test ecosystem.
    • simply creating arbitrary test steps in the feature file will result in utter chaos with BOTH feature file errors with duplicate step definitions AND with maintaining redundant Step Definitions that perform the same steps.
    •  
  • Create subfolders with Definition Macros
    • methods in the macros can reference other methods
  • Since Drupal pages are templates comprised of 'blocks,
    • it makes sense to model subfolders as drupal blocks
    • Step Definitions would then be prefixed with the 'block name' and a '-'
    • this prefix will also prevent step definition name collisions
    • Using Excel to manage Step Definition string creation can facilitate consistent Step Definitions in the feature file
      • and can also serve as a guide to know what dev work is required to support the feature
      • and will allow the BDD feature author to simply pick from a library of functions to construct the scenario
      • and will self-document the test architecture & supporting framework

 

Test Runner Files

  • Add ALL the attributes to the runners
  • this will avoid ambiguity when something isn't working
  • Also will self-document the available options

 

Example Mock Up

  • This feature file uses Test Steps from different StepDef files - in DIFFERENT subfolders under the *.StepDefs package
  • The following two Test Steps have definitions in the StepDef: [ com.cs.cmsauto.bdd.StepDefs.MenuAdminLoginDefs.java ]
    • Given user is already on Login Page
    • Then close the browser
  • The others use StepDefinitions from: [ com.cs.cmsauto.bdd.StepDefs.shared.menu_admin.DoNothingStepDefs.java ]

com.uc.test.cmsauto.bdd.features

Feature: Do Absolutely Nothing

Scenario: So When Nothing is Done, Nothing Happens

Given user is already on Login Page
Given user is doing nothing
When user still does nothing
Then nothing happens
Then close the browser

 

com.uc.test.cmsauto.bdd.runners

 

package com.cs.cmsauto.bdd.Runners;

import org.junit.runner.RunWith;

import cucumber.api.CucumberOptions;
import cucumber.api.junit.Cucumber;

@RunWith(Cucumber.class)
@CucumberOptions(
        
        features=  "src/test/java/com/cs/cmsauto/bdd/Features/donothing.feature"
        ,glue= {"com.cs.cmsauto.bdd.StepDefs"}
        
        ,format= {"pretty","html:test-output", "json:json-output/cucumber.json","junit:junit-xml-output/cucumber.xml"}
        ,monochrome = true
        ,strict = true //true = fail test suite if ANY test step not defined
        ,dryRun = true //true = check feature-definition-runner setup is valid... NO test logic executed
        
        )

public class DoNothingRunner {

}

 

com.uc.test.cmsauto.bdd.stepdefs

  • com.cs.cmsauto.bdd
    • StepDefs
      • CmsAutoDefinition.java - com.cs.cmsauto.bdd.StepDefs
      • MenuAdminLoginDefs.java - com.cs.cmsauto.bdd.StepDefs
      • shared
        • menu_admin
          • DoNothingStepDefs.java - com.cs.cmsauto.bdd.StepDefs.shared.menu_admin

 

CmsAutoDefinition.java - com.cs.cmsauto.bdd.StepDefs

package com.cs.cmsauto.bdd.StepDefs;

import org.junit.After;
import org.junit.Assert;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.interactions.Actions;

import cucumber.api.java.en.And;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;

public class CmsAutoDefinition {
    
    /*
     *
     * in order to use multiple StepDef files for Cucumber,
     *   the webdriver must be instantiated globally OR
     *   webdriver must be passed to called methods, etc
     * using POM will fix this & this issue is arising because this
     *   is just a mockup for bdd analysis
     *
     */
    private WebDriver driver;
    private WebElement el;
    String OS = "WINDOWS";
    //String OS = "LINUX";

    
    @Then("^title of the home page is Home Page$")
    public void title_of_the_home_page_is_Home_Page(){
        String title = driver.getTitle();
        Assert.assertEquals("HomePage", title);
    }

    
    @Then("^user moves to new contact page$")
    public void user_moves_to_new_contact_page() throws Throwable {    
    el = driver.findElement(By.xpath("//frame[@name='mainpanel']"));
    driver.switchTo().frame(el);
    
    //mouse over the menu item
    Actions action = new Actions(driver); //new action
    // findElement.... build action to MOVE to element....
    action.moveToElement(driver.findElement(By.xpath("//a[contains(.,'Contacts')]"))).build().perform();

    // THEN click
    action.moveToElement(driver.findElement(By.xpath("//a[contains(.,'New Contact')]"))).build().perform();

    el = driver.findElement(By.xpath("//a[contains(.,'New Contact')]"));
    el.click();
    
    
    }

    
    @Then("^user enters contact details for firstname \"([^\"]*)\" and lastname \"([^\"]*)\" and position \"([^\"]*)\"$")
    public void user_enters_contact_details_and_and(String firstname, String lastname, String position) throws Throwable {
        driver.findElement(By.id("first_name")).sendKeys(firstname);
        driver.findElement(By.id("surname")).sendKeys(lastname);
        driver.findElement(By.id("company_position")).sendKeys(position);
        driver.findElement(By.xpath("//input[@type='submit' and @value='Save']")).click();
        
        // page will redirect, check tabs_header for the new username
        el = driver.findElement(By.xpath("//td[contains(.,\'" + firstname + "\') and @class='tabs_header']"));
        Assert.assertEquals(firstname + " " + lastname, el.getText());
    }

    @Then("^user verify contact created$")
    public void user_verify_contact_created(){
        el = driver.findElement(By.xpath("//td[contains(.,firstname)]"));
        String title = driver.getTitle();
        Assert.assertEquals("ContactCreated", title);
    }
}

 

 

MenuAdminLoginDefs.java - com.cs.cmsauto.bdd.StepDefs

package com.cs.cmsauto.bdd.StepDefs;

import org.junit.Assert;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;

import cucumber.api.java.en.And;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;

public class MenuAdminLoginDefs {
    private WebDriver driver;
    private WebElement el;
    String OS = "WINDOWS";
    //String OS = "LINUX";
    

    @Given("^user is already on Login Page$")
    public void user_is_already_on_Login_Page() throws Throwable {
        if (OS.equals("WINDOWS")) {
            System.setProperty("webdriver.chrome.driver", "src/test/resources/executables/chromedriver-2.39-win32/chromedriver.exe");
        } else if (OS.equals("LINUX")) {
            System.setProperty("webdriver.chrome.driver", "res/chromedriver/chromedriver-v2.39_linux64");    
        }
        driver = new ChromeDriver();
        driver.get("https://unityconstruct.org/uc/");
    }

    @When("^title of login page is PageTitle$")
    public void title_of_login_page_is_Free_CRM() throws Throwable {
        String title = driver.getTitle();
        System.out.println(title);
        Assert.assertEquals("Home | UnityConstruct", title);
    }

    
    @Then("^user clicks on login link$")
    public void user_clicks_on_login_link() {
        driver.findElement(By.xpath("//a[contains(.,'Log in')]")).click();
    }
    
    @Then("^user enters username \"([^\"]*)\" and password \"([^\"]*)\"$")
    public void user_enters_username_and_password(String username, String password) throws Throwable {
        driver.findElement(By.xpath("//input[@id='edit-name']")).sendKeys(username);
        driver.findElement(By.xpath("//input[@id='edit-pass']")).sendKeys(password);

    }

    @Then("^user clicks on login button$")
    public void user_clicks_on_login_button() throws Throwable {
        //Xpath.click technique
        driver.findElement(By.xpath("//input[@id='edit-submit']")).click();
    }    
    
    
    @Then("^user is on home page$")
    public void user_is_on_home_page() throws Throwable {
        String title = driver.getTitle();
        System.out.println(title);
        Assert.assertEquals("TestAccount | UnityConstruct", title);
    }

    @And("^close the browser$")
    public void close_the_browser() {
        driver.quit();
    }
}

 

 

 

DoNothingStepDefs.java - com.cs.cmsauto.bdd.StepDefs.shared.menu_admin

package com.cs.cmsauto.bdd.StepDefs.shared.menu_admin;

import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;

public class DoNothingStepDefs {
    
    @Given("^user is doing nothing$")
    public void user_is_doing_nothing() {
        System.out.println("user is doing nothing");
    }

    @When("^user still does nothing$")
    public void user_still_does_nothing() {
        System.out.println("user still does nothing");
    }

    @Then("^nothing happens$")
    public void nothing_happens(){
        System.out.println("nothing heppens");
    }
}

 

 

 

 

StepDefs Tree Example

  • shared
    •  
  • macros
    • logins
    • content_filters
    • content_creation
    • content_deletion
    • searches
  • navigation
    • menu_main
    • menu_admin
    • menu_tools
    • search_bar
    • search_advanced
  • pages
    • admin
      • performance
        • clear_cache()
        • aggregate_cssjss()
      • extend
        • addmodule()
      • content_all
        • publishFirstItem()
        • unpublisthFirstItem()
        • filterByTitle()
      • content_types
        • book
          • books_disable()
        • article
          •  
        • taxonomy
          •  
      • people
    • home
      • home-navigation
      • home-menu-footer
    • ideas
      • ideas-navigation
      • ideas-menu-footer
      • ideas-content-filter
  • drush
    • calls
      •  
    • site
      • siteInstall
      • siteUpdate
      • siteRevert
      • siteRename
      • siteBackup
    • db
      • dbSwap
      • tableSwap
      • dbInsert
      • dbBackup
    • themes
    • core
    • modules
      • disbableModule()
      • enableModule()
  • shell
    • drupal
      • sites
        • wipeSites()
        • resetSettingsPhpToDefault()
      • themes
        • wipeThemes()
        • addTheme()
    • logs
      • pushLogs()
      • resetLogs()
      • parseLogs()
      • archiveLogs()
    •  
      •  
Tags