Skip to content
markokonsaFob edited this page May 29, 2019 · 2 revisions

Nordic Testing Days 2019

Mobile Automation with Cify Open Source platform

SwagLabs Test Automation Project

Cloning the repository

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:

  1. Products Empty Step Definitions
  2. Products Page Object
  3. Products Actions Implementation
  4. Products Step Definition implementation
  5. Cart Step Definitions
  6. Cart Page Object
  7. Cart Actions Implementation
  8. Adding to ActionsImpl class
  9. Cart Step Definition implementation
  10. Checkout Step Definitions
  11. Checkout Page Objects
  12. Checkout Actions
  13. ActionImpl implementation for Checkout
  14. Checkout Step Definition implementation

Let's get started!

Cucumber Feature implementation

  1. Create file named order.feature into the src/test/resources/features folder
  2. 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.

Products View Implementation

Products Empty Step Definitions

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.

Products Page Object Implementation

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...

Products Actions implementation

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)

    }

Products Step Definitions Implementation

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

Cart View Implementation

Let's move on with Cart view implementation. This is completely new so we need to create all the files.

Cart empty step definitions

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.

Cart Page Object Implementation

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)
        }
    }
}

Cart Actions Implementation

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())
    }

}

Cart ActionsImpl

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)
    }

Cart Step Definitions Implementation

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

Checkout View Implementation

Checkout empty step definitions

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

Checkout Page Object Implementation

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
    }
}

Checkout Actions Implementation

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())
    }

}

Checkout ActionsImpl

 /**
     * Gets Checkout actions for active device
     * @return
     */
    static CheckoutActions getCheckoutActions() {
        Device device = DeviceManager.getInstance().getActiveDevice()
        return new CheckoutActions(device)
    }

Checkout Step Definitions Implementation

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

Everything should be ready now! :)