Cake Server end-to-end testing using Selenium and Cucumber.
Cake Server is one of my open source projects which helps programmers to split their tasks. It is a web app and could be validated by end-to-end testing. This repository uses Selenium, Cucumber and Page Object pattern to perform the end-to-end testing.
This repository use chrome driver to manipulate Chrome browser. Because the version of the driver upgrades quickly, the driver is set to be git ignored. Please download the driver with the version number as the same with that of your Chrome browser, and put it in the folder under this project folder:
./webdriver/chromedriver
The download link is here.
./gradlew cucumber
Page Object is an object which contains all the web elements as its member variables and encapsulate the operation details on a particular page:
public class CakeServerPage {
private WebDriver driver;
private String url;
private Button newCommitBtn;
private CommitList commitList;
private TextField messageField;
private TextField noteField;
private Button saveBtn;
private Button redoBtn;
private Button copyCommitMessageBtn;
private Button deleteBtn;
// ...
}
Initialize the web elements:
public class CakeServerPage {
// ...
public CakeServerPage(WebDriver driver, String url) {
this.driver = driver;
this.url = url;
this.newCommitBtn = new Button(driver, By.className("e2e-new-commit"));
this.commitList = new CommitList(driver, By.className("e2e-commit-message"), By.className("e2e-commit-note"));
this.messageField = new TextField(driver, By.className("e2e-message"));
this.noteField = new TextField(driver, By.className("e2e-note"));
this.saveBtn = new Button(driver, By.className("e2e-save"));
this.redoBtn = new Button(driver, By.className("e2e-redo"));
this.copyCommitMessageBtn = new Button(driver, By.className("e2e-copy-commit-message"));
this.deleteBtn = new Button(driver, By.className("e2e-delete"));
}
// ...
}
Delegate the operations to elements:
public class CakeServerPage {
// ...
public void open() {
driver.get(url);
}
public boolean commitExists(String message, String note) {
return commitList.commitExists(message, note);
}
public void selectCommitWithMessageAndNote(String message, String note) {
commitList.selectCommitWithMessageAndNote(message, note);
}
public void enterCommitMessage(String message) {
messageField.fill(message);
}
public void enterCommitNote(String note) {
noteField.fill(note);
}
public void saveCommit() {
saveBtn.click();
}
public void selectNewCommit() {
newCommitBtn.click();
}
}
Reusable web elements designed with DI principle, and which encapsulate the operation details:
public class Button {
private WebDriver driver;
private By selector;
public Button(WebDriver driver, By selector) {
this.driver = driver;
this.selector = selector;
}
public void click() {
WebElement element = driver.findElement(selector);
element.click();
}
}
public class TextField {
private WebDriver driver;
private By selector;
public TextField(WebDriver driver, By selector) {
this.driver = driver;
this.selector = selector;
}
public void fill(String text) {
WebElement element = driver.findElement(selector);
element.clear();
element.sendKeys(text);
}
public String grab() {
WebElement element = driver.findElement(selector);
return element.getAttribute("value");
}
}
By default, cucumber creates new step definition instances for every step. In order to share states (variables, page objects ...etc.) between steps, we need to introduce DI container guice
and annotate the step definition classes with @ScenarioScoped
annotation.
dependencies {
testImplementation("io.cucumber:cucumber-guice:6.0.0")
testImplementation("com.google.inject:guice:4.0")
}
@ScenarioScoped
public class CakeServerStepdefs {
WebDriver driver;
CakeServerPage cakeServerPage;
@Before
public void setUp() {
System.setProperty("webdriver.chrome.driver", "./webdriver/chromedriver");
driver = new ChromeDriver();
cakeServerPage = new CakeServerPage(driver, "https://chinhung.github.io/cakeserver/");
cakeServerPage.open();
}
@After
public void tearDown() {
driver.close();
}
@Given("^commit with message: (.*) and note: (.*) exists$")
public void commitWithMessageAndNoteExists(String message, String note) {
assertTrue(cakeServerPage.commitExists(message, note));
}
@When("^create commit with message: (.*) and note: (.*)$")
public void createCommitWithMessageAndNote(String message, String note) {
cakeServerPage.selectNewCommit();
cakeServerPage.enterCommitMessage(message);
cakeServerPage.enterCommitNote(note);
cakeServerPage.saveCommit();
}
@When("^select commit with message: (.*) and note: (.*)$")
public void selectCommitWithMessageAndNote(String message, String note) {
cakeServerPage.selectCommitWithMessageAndNote(message, note);
}
@When("^enter commit message: (.*)")
public void enterCommitMessage(String message) {
cakeServerPage.enterCommitMessage(message);
}
@When("^enter commit note: (.*)")
public void enterCommitNote(String note) {
cakeServerPage.enterCommitNote(note);
}
@When("save commit")
public void saveCommit() {
cakeServerPage.saveCommit();
}
}
Feature: Create Commit
Scenario: create commit successfully
Given commit with message: New Message and note: New Note does not exist
When create commit with message: New Message and note: New Note
Then commit with message: New Message and note: New Note exists
Feature: Update Commit
Background:
Given create commit with message: Given Message and note: Given Note
Scenario: update commit message
When select commit with message: Given Message and note: Given Note
* enter commit message: Updated
* save commit
Then commit with message: Updated and note: Given Note exists
Scenario: update commit note
When select commit with message: Given Message and note: Given Note
* enter commit note: Updated
* save commit
Then commit with message: Given Message and note: Updated exists
See all test cases at ./src/test/resources/features