Repository navigation
Home
Mobile Automation with Cify Open Source platform
For participating in the session please clone the repository and let's start working with the master branch.
If for some reason we did not made it to the end feel free to checkout nordic-final branch where you can see everything implemented and from this page you can see step by step how we managed to do that.
We will start implementing from Cucumber, implement full Cucumber feature file and then will move on with topics in that order:
- Products Empty Step Definitions
- Products Page Object
- Products Actions Implementation
- Products Step Definition implementation
- Cart Step Definitions
- Cart Page Object
- Cart Actions Implementation
- Adding to ActionsImpl class
- Cart Step Definition implementation
- Checkout Step Definitions
- Checkout Page Objects
- Checkout Actions
- ActionImpl implementation for Checkout
- Checkout Step Definition implementation
Let's get started!
- Create file named order.feature into the src/test/resources/features folder
- Implement it with following content:
Feature: Ordering item from SwagLabs shop
Scenario: Complete ordering positive flow
Given user opens application
Then login page should be visible
When user enters VALID credentials into login form
And user clicks login button
Then products page should be visible
When user adds first element to cart
And user navigates to cart view
Then cart view should be visible
And cart should be filled with entered data
When user clicks on checkout button on cart view
Then checkout information page should be visible
When user enters "Nordic" into the first name field on checkout page
And user enters "Testing" into the last name field on checkout page
And user enters "12345" into the postal code field on checkout page
And user clicks continue button on checkout page
Then user should be on checkout overview page
When user clicks finish on checkout overview page
Then checkout complete page should be visible
This is our test case. Some of those steps are already implemented, some we need to implement ourselves. Don't worry about that, we will get there.
Now lets move on to the steps implementation. At the moment we have LoginSteps.groovy and ProductSteps.groovy and CoreSteps.groovy implemented.
Lets start with easiest ones.
We have 2 steps from feature file which needs to be implemented in ProductsSteps.groovy file.
When user adds first element to cart
And user navigates to cart view
If you click alt + Enter on that step it should give you a possibility to generate step that you have focus on. (Alt + Enter -> Create Step Definition -> Choose right file / ProductsSteps.groovy)
Let's leave them as they are at the moment and get back to them in a while.
In products page we need to add following elements to PageObject
@FindBy(className = "shopping_cart_link")
@AndroidFindBy(accessibility = "test-Cart")
@iOSXCUITFindBy(accessibility = "test-Cart")
WebElement cartButton
// Test item inner elements
By addToCartLocator = MobileBy.AccessibilityId("test-ADD TO CART")
By priceLocator = MobileBy.AccessibilityId("test-Price")
By titleLocator = MobileBy.AccessibilityId("test-Item title")
The reason why some of them are "By" locators is that we need to find inner elements from inventoryItem element. / Add to Cart etc...
Add following methods to Actions class, don't just copy paste them, try to understand why something is done like that.
/**
* Get all product elements
* @return
*/
List<WebElement> getProducts() {
productsPage.getInventoryItems()
}
/**
* Add given product to cart
* @param product
*/
void addProductToCart(WebElement product) {
click(product.findElement(productsPage.getAddToCartLocator()))
saveProductInfoToTestData(product)
}
/**
* Clicks on cart icon on navigation bar
*/
void clickOnCartIcon() {
click(productsPage.getCartButton())
}
/**
* Saves product information to test data
* @param product
* @return
*/
private saveProductInfoToTestData(WebElement product) {
// Get information about the given product
String itemTitle = product.findElement(productsPage.getTitleLocator()).getText()
String price = product.findElement(productsPage.getPriceLocator()).getText()
// Save to TestDataManager
LazyMap productData = [:]
productData.put("title", itemTitle)
productData.put("price", price)
TestDataManager.setTestData(Constants.CART_DATA, productData)
}
When(~/^user adds first element to cart$/) { ->
WebElement product = ActionsImpl.getProductActions().getProducts().first()
ActionsImpl.getProductActions().addProductToCart(product)
}
And(~/^user navigates to cart view$/) { ->
ActionsImpl.getProductActions().clickOnCartIcon()
}
RIGHT NOW WE ARE ON TAG products Jump right in with command:
git checkout products
Let's move on with Cart view implementation. This is completely new so we need to create all the files.
Let's create CartSteps.groovy class to src/test/groovy/steps folder.
Then cart view should be visible
And cart should be filled with entered data
When user clicks on checkout button on cart view
Those steps needs to be generated into the newly created groovy class. PS! Just duplicate one of Steps classes and remove methods - easiest way.
Full cart page object will look like this. There are two methods getCartItemTitleElement and getCartItemPriceElement which are little bit different from the previous page object. Those are needed cause the application don't have good options to get Title and Price elements. This way we can still have a cross-platform implementation and any difference is on the Page Object side - good in a structure.
class CartPage extends PageObjects {
@AndroidFindBy(accessibility = "test-Item")
@iOSXCUITFindBy(accessibility = "test-Item")
List<WebElement> cartItems
@AndroidFindBy(accessibility = "test-CONTINUE SHOPPING")
@iOSXCUITFindBy(accessibility = "test-CONTINUE SHOPPING")
WebElement continueShoppingButton
@AndroidFindBy(accessibility = "test-CHECKOUT")
@iOSXCUITFindBy(accessibility = "test-CHECKOUT")
WebElement checkoutButton
By androidTextViewLocator = MobileBy.className("android.widget.TextView")
By iOSTextViewLocator = MobileBy.className("XCUIElementTypeStaticText")
// Test item inner elements
By testDescriptionLocator = MobileBy.AccessibilityId("test-Description")
By testPriceLocator = MobileBy.AccessibilityId("test-Price")
Device device
CartPage(Device device) {
super(device)
this.device = device
}
/**
* Gets Cart Item Title element for given item
*
* Android and IOS text view classes are different, thats why we need to have if else clause
*
* @param item
* @return
*/
WebElement getCartItemTitleElement(WebElement item) {
WebElement description = item.findElement(testDescriptionLocator)
if (device.getDriver() instanceof AndroidDriver) {
description.findElement(androidTextViewLocator)
} else {
description.findElement(iOSTextViewLocator)
}
}
/**
* Gets Cart item price element for given item
*
* Android and IOS text view classes are different, thats why we need to have if else clause
*
* @param item
* @return
*/
WebElement getCartItemPriceElement(WebElement item) {
WebElement description = item.findElement(testPriceLocator)
if (device.getDriver() instanceof AndroidDriver) {
description.findElement(androidTextViewLocator)
} else {
description.findElement(iOSTextViewLocator)
}
}
}
Full Implementation of the Cart actions class. Try to understand whats happening here. Two things to focus. TestDataManager and how to use it and waitforcondition method, which is really useful on the automation on mobiles.
class CartActions implements ActionsDesktopWeb, ActionsMobileIOSApp, ActionsMobileAndroidApp {
Device device
CartPage cartPage
CartActions(Device device) {
this.device = device
this.cartPage = new CartPage(device)
}
/**
* Verify that Cart page is visible
* @return
*/
boolean isCartPageVisible() {
isDisplayed(cartPage.getContinueShoppingButton())
}
/**
* Verifies cart content from TestDataManager
* @return
*/
boolean isCartFilledWithCorrectData() {
// Gets Cart Data from TestDataManager
LazyMap cartData = TestDataManager.getValue(Constants.CART_DATA) as LazyMap
String title = cartData.get("title")
String price = cartData.get("price")
List<WebElement> cartItems = cartPage.getCartItems()
WebElement cartItem = cartItems.first()
boolean isSizeCorrect = cartItems.size() == 1
boolean isTitleDisplayed = cartPage.getCartItemTitleElement(cartItem).getText() == title
boolean isPriceDisplayed = cartPage.getCartItemPriceElement(cartItem).getText() == price
return isSizeCorrect && isTitleDisplayed && isPriceDisplayed
}
/**
* Clicks checkout button
*/
void clickCheckout() {
ActionsWrapper.waitForCondition(device, {
if (!isDisplayed(cartPage.getCheckoutButton())) {
ActionsWrapper.slowScrollDown(device)
return false
}
return true
}, 30)
click(cartPage.getCheckoutButton())
}
}
Add one method to ActionsImpl class, so it would be easier to get those actions from step definitions.
/**
* Gets Cart actions for active device
* @return
*/
static CartActions getCartActions() {
Device device = DeviceManager.getInstance().getActiveDevice()
return new CartActions(device)
}
Then(~/^cart view should be visible$/) { ->
if (!ActionsImpl.getCartActions().isCartPageVisible()) {
throw new CifyTestException("Cart page should be visible!")
}
}
And(~/^cart should be filled with entered data$/) { ->
if (!ActionsImpl.getCartActions().isCartFilledWithCorrectData()) {
throw new CifyTestException("Cart should be filled with correct Data! Saved data is: " + TestDataManager.getValue(Constants.CART_DATA).toString())
}
}
When(~/^user clicks on checkout button on cart view$/) { ->
ActionsImpl.getCartActions().clickCheckout()
}
RIGHT NOW WE ARE ON TAG cart Jump right in with command:
git checkout cart
Repeat the creation of the step definitions for Cart and add following empty implementations to the file:
Then checkout information page should be visible
When user enters "Nordic" into the first name field on checkout page
And user enters "Testing" into the last name field on checkout page
And user enters "12345" into the postal code field on checkout page
And user clicks continue button on checkout page
Then user should be on checkout overview page
When user clicks finish on checkout overview page
Then checkout complete page should be visible
class CheckoutPage extends PageObjects {
@AndroidFindBy(accessibility = "test-First Name")
@iOSXCUITFindBy(accessibility = "test-First Name")
WebElement firstNameField
@AndroidFindBy(accessibility = "test-Last Name")
@iOSXCUITFindBy(accessibility = "test-Last Name")
WebElement lastnameField
@AndroidFindBy(accessibility = "test-Zip/Postal Code")
@iOSXCUITFindBy(accessibility = "test-Zip/Postal Code")
WebElement postalCodeField
@AndroidFindBy(accessibility = "test-CONTINUE")
@iOSXCUITFindBy(accessibility = "test-CONTINUE")
WebElement continueButton
@AndroidFindBy(accessibility = "test-FINISH")
@iOSXCUITFindBy(accessibility = "test-FINISH")
WebElement finishButton
@AndroidFindBy(accessibility = "test-CHECKOUT: COMPLETE!")
@iOSXCUITFindBy(accessibility = "test-CHECKOUT: COMPLETE!")
WebElement checkoutCompleteContainer
Device device
CheckoutPage(Device device) {
super(device)
this.device = device
}
}
class CheckoutActions implements ActionsDesktopWeb, ActionsMobileIOSApp, ActionsMobileAndroidApp {
Device device
CheckoutPage checkoutPage
CheckoutActions(Device device) {
this.device = device
this.checkoutPage = new CheckoutPage(device)
}
/**
* Verify that Checkout information page is visible
* @return
*/
boolean isCheckoutInfoPageVisible() {
isDisplayed(checkoutPage.getFirstNameField()) &&
isDisplayed(checkoutPage.getLastnameField()) &&
isDisplayed(checkoutPage.getPostalCodeField())
}
/**
* Verify that Checkout overview page is visible
* @return
*/
boolean isCheckoutOverViewPageVisible() {
try {
ActionsWrapper.waitForCondition(device, {
if (!isDisplayed(checkoutPage.getFinishButton())) {
ActionsWrapper.slowScrollDown(device)
return false
}
return true
}, 30)
} catch (ignored) {
false
}
}
/**
* Enter firstname
* @param firstname
*/
void enterFirstName(String firstname) {
checkoutPage.getFirstNameField().clear()
sendKeys(checkoutPage.getFirstNameField(), firstname)
}
/**
* Enter lastname
* @param lastName
*/
void enterLastName(String lastName) {
checkoutPage.getLastnameField().clear()
sendKeys(checkoutPage.getLastnameField(), lastName)
}
/**
* Enter postal Code
* @param postalCode
*/
void enterPostalCode(String postalCode) {
checkoutPage.getPostalCodeField().clear()
sendKeys(checkoutPage.getPostalCodeField(), postalCode)
}
/**
* Clicks continue button
*/
void clickContinue() {
if (!isDisplayed(checkoutPage.getContinueButton())) {
ActionsWrapper.hideKeyboard(device)
}
click(checkoutPage.getContinueButton())
}
/**
* Clicks finish button
*/
void clickFinish() {
if (!isDisplayed(checkoutPage.getFinishButton())) {
ActionsWrapper.slowScrollDown(device)
}
click(checkoutPage.getFinishButton())
}
/**
* Verifies that checkout complete page is visible
* @return
*/
boolean isCheckoutCompletePageVisible() {
isDisplayed(checkoutPage.getCheckoutCompleteContainer())
}
}
/**
* Gets Checkout actions for active device
* @return
*/
static CheckoutActions getCheckoutActions() {
Device device = DeviceManager.getInstance().getActiveDevice()
return new CheckoutActions(device)
}
Then(~/^checkout information page should be visible$/) { ->
if (!ActionsImpl.getCheckoutActions().isCheckoutInfoPageVisible()) {
throw new CifyTestException("Checkout information page should be visible!")
}
}
When(~/^user enters "([^"]*)" into the first name field on checkout page$/) { String firstname ->
ActionsImpl.getCheckoutActions().enterFirstName(firstname)
}
And(~/^user enters "([^"]*)" into the last name field on checkout page$/) { String lastname ->
ActionsImpl.getCheckoutActions().enterLastName(lastname)
}
And(~/^user enters "([^"]*)" into the postal code field on checkout page$/) { String postalcode ->
ActionsImpl.getCheckoutActions().enterPostalCode(postalcode)
}
And(~/^user clicks continue button on checkout page$/) { ->
ActionsImpl.getCheckoutActions().clickContinue()
}
Then(~/^user should be on checkout overview page$/) { ->
if (!ActionsImpl.getCheckoutActions().isCheckoutOverViewPageVisible()) {
throw new CifyTestException("Checkout Overview page should be visible!")
}
}
When(~/^user clicks finish on checkout overview page$/) { ->
ActionsImpl.getCheckoutActions().clickFinish()
}
Then(~/^checkout complete page should be visible$/) { ->
if (!ActionsImpl.getCheckoutActions().isCheckoutCompletePageVisible()) {
throw new CifyTestException("Checkout Complete page should be visible!")
}
}
RIGHT NOW WE ARE ON TAG checkout Jump right in with command:
git checkout checkout
Marko Konsa marko.konsa@fob-solutions.com