The following test implements a simple order placement flow:
@Test
public void canPlaceOrderTest() {
HomePage homePage = new HomePage(driver);
homePage.open();
ResultsPage resultsPage = homePage.search("iphone");
DetailsPage detailsPage = resultsPage.selectProduct(1);
detailsPage.addToCart();
BasketPage basketPage = detailsPage.goToBasketPage();
CheckoutPage checkOutPage = basketPage.checkout();
checkOutPage.addPersonalInfo(
firstName, lastName, address, city, country, postalCode);
checkOutPage.addCardInfo(cardInfo, cardExpiry, cardCode);
checkOutPage.addDeliveryInfo(
deliveryDay, deliveryTime, description, deliveryMethod);
ConfirmationPage confirmationPage = checkOutPage.placeOrder();
Assert.assertTrue(confirmationPage.isOrderPlaced());
}
The class that we are going to focus on is CheckoutPage.
It allows the user to add personal info, card info and delivery info to the Checkout page of the site. All fields for personal info, card info and delivery info are included in a frame with the name edit.
Inside of the edit frame, there are 3 other frames
frame for the personal information fields; the frame name is personal-info
frame for the credit card fields; the frame name is card-info
frame for the delivery fields; the frame name is delivery-info
In addition, Checkout page has other elements that are outside of the frames, for example, the button that allows the order to be placed.
The CheckoutPage class is below:
public class CheckoutPage {
private By firstNameBy = By.id("");
private By lastNameBy = By.id("");
private By addressBy = By.id("");
private By cityBy = By.id("");
private By countryBy = By.id("");
private By postalCodeBy = By.id("");
private By cardInfoBy = By.id("");
private By cardExpiryBy = By.id("");
private By cardCodeBy = By.id("");
private By deliveryDayBy = By.id("");
private By deliveryTimeBy = By.id("");
private By descriptionBy = By.id("");
private By deliveryMethodBy = By.id("");
private By placeOrderBy = By.id("");
private WebDriver driver;
public CheckoutPage(WebDriver driver) {
this.driver = driver;
}
public void addPersonalInfo(String firstName,
String lastName,
String address,
String city,
String country,
String postalCode) {
driver.switchTo().frame("edit");
driver.switchTo().frame("personal-info");
WebElement firstNameBox = driver.findElement(firstNameBy);
firstNameBox.sendKeys(firstName);
WebElement lastNameBox = driver.findElement(lastNameBy);
lastNameBox.sendKeys(lastName);
WebElement addressBox = driver.findElement(addressBy);
addressBox.sendKeys(address);
WebElement cityBox = driver.findElement(cityBy);
cityBox.sendKeys(city);
WebElement countryBox = driver.findElement(countryBy);
countryBox.sendKeys(country);
WebElement postalCodeBox = driver.findElement(postalCodeBy);
postalCodeBox.sendKeys(postalCode);
}
public void addCardInfo(String cardInfo,
String cardExpiry,
String cardCode) {
driver.switchTo().parentFrame();
driver.switchTo().frame("card-info");
WebElement cardInfoBox = driver.findElement(cardInfoBy);
cardInfoBox.sendKeys(cardInfo);
WebElement cardExpiryBox = driver.findElement(cardExpiryBy);
cardExpiryBox.sendKeys(cardExpiry);
WebElement cardCodeBox = driver.findElement(cardCodeBy);
cardCodeBox.sendKeys(cardCode);
}
public void addDeliveryInfo(String deliveryDay,
String deliveryTime,
String description,
String deliveryMethod) {
driver.switchTo().parentFrame();
driver.switchTo().frame("delivery-info");
WebElement deliveryDayBox = driver.findElement(deliveryDayBy);
deliveryDayBox.sendKeys(deliveryDay);
WebElement deliveryTimeBox = driver.findElement(deliveryTimeBy);
deliveryTimeBox.sendKeys(deliveryTime);
WebElement descriptionBox = driver.findElement(descriptionBy);
descriptionBox.sendKeys(description);
WebElement deliveryMethodBox = driver.findElement(deliveryMethodBy);
deliveryMethodBox.sendKeys(deliveryMethod);
driver.switchTo().parentFrame();
driver.switchTo().defaultContent();
}
public ConfirmationPage placeOrder() {
WebElement placeOrderButton = driver.findElement(placeOrderBy);
placeOrderButton.click();
}
}
The methods that type information in the page are pretty easy to understand:
addPersonalInfo() switches the focus to edit frame, then change it to the personal-information frame; finally, it types all information in the personal info fields
addCardInfo() goes to parent frame (since the addPersonalInfo() worked in the personal-information frame); the parent frame is edit frame; it then changes to the card-info frame and types all information in the card info fields
addDeliveryInfo() goes to parent frame (since the addCardInfo() worked in the card-info frame); the parent frame is edit frame; it then changes to the delivery-info frame, types all information in the delivery info fields, switches back to the parent frame and then to default content (the main page)
placeOrder() submits the order
The test works well.
But other related tests will run into issues.
The previous test executes these methods in sequence:
addPersonalInfo()
addCardInfo()
addDeliveryInfo()
placeOrder()
The switching to frames seems to be going well:
addPersonalInfo()
switch to edit frame
switch to personal-info frame
add personal info to page
addCardInfo()
switch to edit frame
switch to card-info frame
add card info to page
addDeliveryInfo()
switch to edit frame
switch to delivery-info frame
add delivery info to page
switch to edit frame
switch to default content
placeOrder()
But let’s say that we want to create another test that types the information in a different order, first the delivery info, then the card info and finally the personal information.
In this case, the flow is different
addDeliveryInfo()
switch to edit frame
switch to delivery-info frame
add delivery info to page
switch to edit frame
switch to default content
addCardInfo()
switch to edit frame
switch to card-info frame
add card info to page
addPersonalInfo()
switch to edit frame
switch to personal-info frame
add personal info to page
placeOrder()
This is obviously not going to work.
placeOrder() will fail since the focus is still in the personal-info frame instead of being on default content.
Or lets say that we have a test that wants to validate error messages so it only types personal info and then tries placing the order:
addPersonalInfo()
switch to edit frame
switch to personal-info frame
add personal info to page
placeOrder()
Again, placeOrder() will fail since the focus is not on default content but in the personal-info frame.
All issues are related to the way the page methods are being implemented.
addCardInfo() assumes that the focus is in a child frame of edit frame.
addDeliveryInfo() makes the same assumption.
To fix the issues, we have to make each method assumption-free.
We can achieve this by making sure that each method switches to a frame, does the work and then switches out:
public void addPersonalInfo(String firstName,
String lastName,
String address,
String city,
String country,
String postalCode) {
driver.switchTo().frame("edit");
driver.switchTo().frame("personal-info");
WebElement firstNameBox = driver.findElement(firstNameBy);
firstNameBox.sendKeys(firstName);
WebElement lastNameBox = driver.findElement(lastNameBy);
lastNameBox.sendKeys(lastName);
WebElement addressBox = driver.findElement(addressBy);
addressBox.sendKeys(address);
WebElement cityBox = driver.findElement(cityBy);
cityBox.sendKeys(city);
WebElement countryBox = driver.findElement(countryBy);
countryBox.sendKeys(country);
WebElement postalCodeBox = driver.findElement(postalCodeBy);
postalCodeBox.sendKeys(postalCode);
driver.switchTo().parentFrame();
driver.switchTo().defaultContent();
}
public void addCardInfo(String cardInfo,
String cardExpiry,
String cardCode) {
driver.switchTo().frame("edit");
driver.switchTo().frame("card-info");
WebElement cardInfoBox = driver.findElement(cardInfoBy);
cardInfoBox.sendKeys(cardInfo);
WebElement cardExpiryBox = driver.findElement(cardExpiryBy);
cardExpiryBox.sendKeys(cardExpiry);
WebElement cardCodeBox = driver.findElement(cardCodeBy);
cardCodeBox.sendKeys(cardCode);
driver.switchTo().parentFrame();
driver.switchTo().defaultContent();
}
public void addDeliveryInfo(String deliveryDay,
String deliveryTime,
String description,
String deliveryMethod) {
driver.switchTo().frame("edit");
driver.switchTo().frame("delivery-info");
WebElement deliveryDayBox = driver.findElement(deliveryDayBy);
deliveryDayBox.sendKeys(deliveryDay);
WebElement deliveryTimeBox = driver.findElement(deliveryTimeBy);
deliveryTimeBox.sendKeys(deliveryTime);
WebElement descriptionBox = driver.findElement(descriptionBy);
descriptionBox.sendKeys(description);
WebElement deliveryMethodBox = driver.findElement(deliveryMethodBy);
deliveryMethodBox.sendKeys(deliveryMethod);
driver.switchTo().parentFrame();
driver.switchTo().defaultContent();
}
Each method starts from the same state with focus in the default content.
Each method ends in the same same state with focus also in the default content.
Each method switches to the edit frame, then to a more specific frame, does its work, then switches out of the specific frame and then back to default content.
Because of this, the methods can be executed in any sequence.