Creating the Base Scenarios

Introduction

In this section, we will create our base scenarios for simple things like checking the page source contains a given string of text, or checking the context or current activity of our app under test.

Validate a PageSource String on the Android App

Creating the Cucumber Scenario

  1. Open up the ‘BaseScenarios.feature’ file and add the following base test scenario.  We can also add a Cucumber tag either above the ‘Scenario’ (to apply to the specific scenario) or above the ‘Feature’ (to apply to all scenarios within the feature)…
    @Iphone8
    Feature: BaseScenarios
      These scenarios can be used in any project
    
      Scenario: 01. Validate the PageSource string on the app screen
        Given the Artistry app has been launched
        Then the user sees "Vincent Willem van Gogh" in the PageSource

    You should notice that the ‘Given’ and ‘Then’ lines are highlighted.  This is because they do not yet have any step definitions attached to them.

Creating the Undefined Step Definitions

  1. Open up Terminal (or Command Prompt) and ‘cd’ (change directory) in to the root of your Maven or Gradle project…
    cd path/To/ProjectRoot

    (change ‘path/To/ProjectRoot’ to local path of your project root)

  2. Enter in
    mvn install

    or

    ./gradlew build
    • The build might give an error but that’s fine for now
  3. Scroll up in the Terminal (or Command Prompt) and you will be able to see the undefined step definition
  4. Copy and paste the undefined ‘Given’ step definition in to the ‘BaseSteps’ class, it should end up looking like below…
    package steps;
    
    import cucumber.api.PendingException;
    import io.cucumber.java.en.Given;
    
    public class BaseSteps extends Page {
    
        @Given("^the Artistry app has been launched$")
        public void the_Artistry_app_has_been_launched() throws Throwable {
            // Write code here that turns the phrase above into concrete actions
            throw new PendingException();
        }
    }
  5. Copy and paste the undefined ‘Then’ step definition in to the ‘BaseSteps’ class as well, it should end up looking like below (note that I changed the name of the String argument to make it more easy to read and understand, this is good practice so you should do the same)…
    package steps;
    
    import cucumber.api.PendingException;
    import io.cucumber.java.en.Then;
    
    public class BaseSteps extends Page {
    
        @Given("^the Artistry app has been launched$") 
        public void the_Artistry_app_has_been_launched() throws Throwable { 
            // Write code here that turns the phrase above into concrete actions 
            throw new PendingException(); 
        }
    
        @Then("^the user sees \"([^\"]*)\" in the PageSource$")
        public void i_see_in_the_PageSource(String expectedPageSource) throws Throwable {
            // Write code here that turns the phrase above into concrete actions
            throw new PendingException();
        }
    }

Creating the Test Methods

We will now create a test method in Java for our step definition that we can then plug in to it ?

First of all, we need to find an element in the Artistry app that we can use to assert that the app has finished launching, by checking if this element is displayed or not.  Once it becomes displayed, then we can assume the app has finished launching and continue with the rest of our test.

To view details of elements in an IOS app, there are not many options to be honest.  With Android, we can use the UIAutomatorViewer tool, but iOS does not have an equivalent tool like this, so instead we have to use Appium Desktop, where we can create our own session in the program (using the same Desired Capabilities as in our Java project) and in that we can inspect elements in an iOS application.

  1. Launch ‘Appium Desktop’ on your Mac machine
  2. Keep the ‘Host’ and ‘Port’ values as the default ones and click ‘Start Server’…
  3. Once the Appium server is running, click the magnifying glass icon to start an Inspector Session…
  4. Add in all the Desired Capabilities from our test framework into the Appium Desktop session, using ‘text’ as the type for all of them except “app”, where we can use ‘filePath’ and then click the icon to browse for the .app file 🙂 It should look similar to below…
  5. Also add in a ‘platformName’ desired capability as unlike our test framework, Appium Desktop cannot tell if the platform is Android or iOS…
  6. Once all the necessary Desired Capabilities are added, click ‘Start Session’ to start the Appium session with capabilities we have fed in (Optionally you can even save the Capability Set so it’s easier to reuse in the future. Also, you will have to wait a little while)
  7. Eventually, the ‘Artistry’ app should open up in the iPhone simulator we have specified 🙂
  8. If we look in Appium Desktop, we can see it is similar to UIAutomatorViewer, where a screenshot of the current screen we are on has been taken and it lists the elements of that screen in a XML hierarchy…
  9. If we click on the “Pablo Picasso” button in Appium Desktop, we can inspect its element and see details of it like its Xpath value and Accessibility ID (XCUITest uses Accessibility features to interact with elements)…

    XCUITest recommends using Accessibility IDs over XPath’s for finding/interacting with elements, so let’s do that.
  10. Open up the ‘BasePage’ class in the ‘pages’ package and add the following IOSElement inside the top of the class…
    @iOSFindBy(accessibility = "Pablo Picasso")
    private IOSElement btnPabloPicasso;

    We will use this element in our test method for ensuring that the app has finished loading and fully launched.

  11. In the ‘BasePage’ class again, add the following method to ensure that the ‘Artistry’ app has finished loading by waiting until the btnPabloPicasso iOS element is visible…
    public void appFullyLaunched() {
        WebDriverWait wait = new WebDriverWait(driver, 20);
        wait.until(ExpectedConditions.visibilityOf(btnPabloPicasso));
    }

    Make sure the correct imports exist above the class…

    import io.appium.java_client.ios.IOSDriver;
    import io.appium.java_client.ios.IOSElement;
    import io.appium.java_client.pagefactory.iOSFindBy;
    import org.apache.logging.log4j.LogManager;
    import org.apache.logging.log4j.Logger;
    import org.openqa.selenium.support.ui.ExpectedConditions;
    import org.openqa.selenium.support.ui.WebDriverWait;
    
    import static utils.appium.Driver.appDriver;

    In the ‘BasePage’ class again, add the following method to assert that the source of the current app screen contains the given string argument that we passed into the method…

    public void validatePageSource(String expectedPageSource) {
        Assert.assertTrue(getPageSource().contains(expectedPageSource));
        log.info(":: The text " + expectedPageSource + " is present in the app screen's source.");
    }

    For the above to work, you will also need to add a getPageSource() method near the top of the class, which returns driver.getPageSource() …

    private String getPageSource() { return driver.getPageSource(); }

    When we use .getPageSource() method on a native mobile app, it instead gets the XML hierarchy of all the elements on that screen instead of the normal HTML page source given by websites with Selenium etc.

    Additionally, make sure the class contains the following import for the TestNG Assert…

    import org.testng.Assert;

    The whole thing should look similar to below…

    package pages;
    
    import io.appium.java_client.ios.IOSDriver;
    import io.appium.java_client.ios.IOSElement;
    import io.appium.java_client.pagefactory.iOSFindBy;
    import org.apache.logging.log4j.LogManager;
    import org.apache.logging.log4j.Logger;
    import org.openqa.selenium.support.ui.ExpectedConditions;
    import org.openqa.selenium.support.ui.WebDriverWait;
    import org.testng.Assert;
    
    import static utils.appium.Driver.appDriver;
    
    public class BasePage extends Page {
    
        private IOSDriver driver = appDriver();
    
        private static Logger log = LogManager.getLogger(BasePage.class.getName());
        
        private String getPageSource() { return driver.getPageSource(); }
    
        @iOSFindBy(accessibility = "Pablo Picasso")
        private IOSElement btnPabloPicasso;
    
        public void appFullyLaunched() {
            WebDriverWait wait = new WebDriverWait(driver, 20);
            wait.until(ExpectedConditions.visibilityOf(btnPabloPicasso));
        }
    
        public void validatePageSource(String expectedPageSource) {
            Assert.assertTrue(getPageSource().contains(expectedPageSource));
            log.info(":: The text " + expectedPageSource + " is present in the app screen's source.");
        }
    }

Connecting the Step Definitions to the Test Methods

We will now call the test method from the appropriate step definition. This whole Page Objects Model (POM) approach is nice as we have suitable layers of abstraction, which helps the code be easier to read.  It also allows the step definitions to simply contain one line of code calling the test methods.

Due to the method we created earlier in the ‘Page’ class, where we made a generic method called instanceOf<T>() that takes the same generic class and initialises a new object from PageFactory with the correct elements for the page (as well as decorating our elements with the required AppiumFieldDecorator), we can now use that when calling our test methods to always make sure the elements of that page class are initialised first ?

  1. Open up the ‘BaseSteps’ class again, and refactor the ‘Given’ step definition so it initialises the pages via the instanceOf() method (in this case ‘BasePage’) with the correct elements and then calls the appFullyLaunched() method, like below (also tidy up the method and get rid of the exception that will never be thrown)…
    @Given("^the Artistry app has been launched$")
    public void the_Artistry_app_has_been_launched() {
        instanceOf(BasePage.class).appFullyLaunched();
    }
  2. Open up the ‘BaseSteps’ class again, and refactor the ‘Then’ step definition so it initialises the page (in this case ‘BasePage’) with the correct elements and then calls the validatePageSource() method, like below (also tidy up the method again and get rid of the exception that will never be thrown)…
    @Then("^the user sees \"([^\"]*)\" in the PageSource$")
    public void i_see_in_the_PageSource(String expectedPageSource) {
        instanceOf(BasePage.class).validatePageSource(expectedPageSource);
    }

Validate Existence of Multiple Text in PageSource

Creating the Cucumber Scenario

  1. Open up the ‘BaseScenarios.feature’ file and add the following test scenario…
    Scenario: 02. Validate existence of multiple texts in PageSource
        Given the Artistry app has been launched
        Then the user sees
         | Pablo Picasso    |
         | Spanish painter  |
         | sculptor         |
         | printmaker       |

    You should notice that only the ‘Then’ line is highlighted.  This is because it does not yet have any step definition attached to it, but the ‘Given’ line does, as we have already added code for that scenario line and are simply reusing it in this scenario.

Creating the Undefined Step Definition

  1. Highlight some of the undefined ‘Then’ line in IntelliJ until a yellow light bulb icon appears next to it
  2. Click the yellow light bulb icon and select ‘Create step definition’ –> ‘BaseSteps (steps)’
    • An undefined step definition should be automatically added to ‘BaseSteps’ like below…
      @Then("^the user sees$")
      public void iSee() throws Throwable {
          // Write code here that turns the phrase above into concrete actions
          throw new PendingException();
      }

      Edit the undefined step definition so it has a List<String> argument that is passed in to it called ‘existsInPageSource’, it should look similar to below (make sure to add an import java.util.List; at the top)…

      @Then("^the user sees$")
      public void iSee(List<String> existsInPageSource) throws Throwable {
          // Write code here that turns the phrase above into concrete actions
          throw new PendingException();
      }

Creating the Test Method

We will now create the test method in Java for our undefined ‘Then’ step

  1. Open up the ‘BasePage’ class in the ‘pages’ package and add the following method to assert the PageSource contains the multiple expected strings…
    public void validateMultipleInPageSource(List<String> table) {
        for (String row : table) {
            Assert.assertTrue(getPageSource().contains(row));
            log.info("The text " + row + " is in the app screen's source.");
        }
    }

    Ensure the

    import java.util.List;

    has been added. The whole thing should look like below…

    package pages;
    
    import io.appium.java_client.ios.IOSDriver;
    import io.appium.java_client.ios.IOSElement;
    import io.appium.java_client.pagefactory.iOSFindBy;
    import org.apache.logging.log4j.LogManager;
    import org.apache.logging.log4j.Logger;
    import org.openqa.selenium.support.ui.ExpectedConditions;
    import org.openqa.selenium.support.ui.WebDriverWait;
    import org.testng.Assert;
    import java.util.List;
    
    import static utils.appium.Driver.appDriver;
    
    public class BasePage extends Page {
    
        private IOSDriver driver = appDriver();
    
        private static Logger log = LogManager.getLogger(BasePage.class.getName());
    
        private String getPageSource() { return driver.getPageSource(); }
    
        @iOSFindBy(accessibility = "Pablo Picasso")
        private IOSElement btnPabloPicasso;
    
        public void appFullyLaunched() {
            WebDriverWait wait = new WebDriverWait(driver, 20);
            wait.until(ExpectedConditions.visibilityOf(btnPabloPicasso));
        }
    
        public void validatePageSource(String expectedPageSource) {
            Assert.assertTrue(getPageSource().contains(expectedPageSource));
            log.info(":: The text " + expectedPageSource + " is present in the app screen's source.");
        }
    
        public void validateMultipleInPageSource(List<String> table) {
            for (String row : table) {
                Assert.assertTrue(getPageSource().contains(row));
                log.info("The text " + row + " is in the app screen's source.");
            }
        }
    }

Connecting the Step Definition to the Test Method

  1. Open up the ‘ValidationSteps’ class again, and refactor the ‘Then’ step definition so it initialises the page (in this case ‘BasePage’ again) with the correct elements and then calls the validateMultipleInPageSource() method, like below…
    @Then("^the user sees$")
    public void iSee(List<String> existsInPageSource) {
        instanceOf(BasePage.class).validateMultipleInPageSource(existsInPageSource);
    }

Validate The Current Context of the App

The current context of an app screen can either be the native app or it can be a webView. This test will validate that the current context is equal to the string value we pass to it (e.g. ‘NATIVE_APP’ or ‘WEBVIEW_<package name>’)

  1. Open up the ‘BaseScenarios.feature’ file and add the following test scenario…
    Scenario: 03. Validate the Current Context of the App
        Given the Artistry app has been launched
        Then the user sees the current context is "NATIVE_APP"

    You should again notice that only the ‘Then’ line is highlighted.  This is again because it does not yet have any step definition attached to it, but the ‘Given’ line does, as we have already added code for that scenario line and are simply reusing it in this scenario.

Creating the Undefined Step Definition

  1. Highlight some of the undefined ‘Then’ line in IntelliJ until a yellow light bulb icon appears next to it
  2. Click the yellow light bulb icon and select ‘Create step definition’ –> ‘BaseSteps (steps)’
    • An undefined step definition should be automatically added to ‘BaseSteps’ like below (change the name of the String argument to something more readable too, and get rid of the Throwable that will never be thrown)…
      @Then("^the user sees the current context is \"([^\"]*)\"$")
      public void iSeeTheCurrentContextIs(String currentContext) throws Throwable {
          // Write code here that turns the phrase above into concrete actions
          throw new PendingException();
      }

Creating the Test Method

We will now create the test method in Java for our undefined ‘Then’ step

  1. Open up the ‘BasePage’ class in the ‘pages’ package and add the following method to assert the current context is the same as the string value we pass in to the method…
    public void validateCurrentContext(String currentContext) {
        Assert.assertTrue(getCurrentContext().contains(currentContext));
        log.info(":: The current context is: " + currentContext);
    }

    For the above to work, we also need to add a getCurrentContext() method which returns a String at the top of the class, like below…

    private String getCurrentContext() { return driver.getContext(); }

    The whole thing should look similar to below…

    package pages;
    
    import io.appium.java_client.ios.IOSDriver;
    import io.appium.java_client.ios.IOSElement;
    import io.appium.java_client.pagefactory.iOSFindBy;
    import org.apache.logging.log4j.LogManager;
    import org.apache.logging.log4j.Logger;
    import org.openqa.selenium.support.ui.ExpectedConditions;
    import org.openqa.selenium.support.ui.WebDriverWait;
    import org.testng.Assert;
    import java.util.List;
    
    import static utils.appium.Driver.appDriver;
    
    public class BasePage extends Page {
    
        private IOSDriver driver = appDriver();
    
        private static Logger log = LogManager.getLogger(BasePage.class.getName());
    
        private String getPageSource() { return driver.getPageSource(); }
        private String getCurrentContext() { return driver.getContext(); }
    
        @iOSFindBy(accessibility = "Pablo Picasso")
        private IOSElement btnPabloPicasso;
    
        public void appFullyLaunched() {
            WebDriverWait wait = new WebDriverWait(driver, 20);
            wait.until(ExpectedConditions.visibilityOf(btnPabloPicasso));
        }
    
        public void validatePageSource(String expectedPageSource) {
            Assert.assertTrue(getPageSource().contains(expectedPageSource));
            log.info(":: The text " + expectedPageSource + " is present in the app screen's source.");
        }
    
        public void validateMultipleInPageSource(List<String> table) {
            for (String row : table) {
                Assert.assertTrue(getPageSource().contains(row));
                log.info("The text " + row + " is in the app screen's source.");
            }
        }
    
        public void validateCurrentContext(String currentContext) {
            Assert.assertTrue(getCurrentContext().contains(currentContext));
            log.info(":: The current context is: " + currentContext);
        }
    }

Connecting the Step Definition to the Test Method

  1. Open up the ‘ValidationSteps’ class again, and refactor the ‘Then’ step definition so it initialises the page (in this case ‘BasePage’ again) with the correct elements and then calls the validateCurrentContext() method, like below…
    @Then("^the user sees the current context is \"([^\"]*)\"$")
    public void iSeeTheCurrentContextIs(String currentContext) {
        instanceOf(BasePage.class).validateCurrentContext(currentContext);
    }

Running the Base Scenarios

Running the Tests

  1. Open up ‘BaseScenarios.feature’ file and make sure you have the ‘@Iphone8’ tag at the top of the feature file or above each test scenario you want to run
  2. Right-click on a test scenario and run it

They should all run in your iPhone simulator and pass 😀

Launching the app before each test scenario

  1. If you look at the three base scenarios in your ‘BaseScenarios.feature’ file, you will see that every scenario shares the step of ‘Given the Artistry app has launched’.  Because of this, we can clean up the Feature file a bit by calling the ‘appFullyLaunched()’ method in our ‘Before(“@Iphone8”)’ hook (see https://github.com/cucumber/cucumber/wiki/Background for more information)…
    package utils.hooks;
    
    import io.cucumber.java.After;
    import io.cucumber.java.Before;
    import pages.BasePage;
    import utils.appium.DriverController;
    import java.net.MalformedURLException;
    
    import static pages.Page.instanceOf;
    
    public class CucumberHooks {
    
        @Before("@Iphone8")
        public void beforeIphone8() throws MalformedURLException {
            DriverController.instance.startIphone8(); //start our iOS driver and device when we run a test with "Iphone8" as the tag
            instanceOf(BasePage.class).appFullyLaunched();
        }
    
        @After
        public void stopAppDriver() {
            DriverController.instance.stopAppDriver(); //stop the Driver after the scenario or feature has run
        }
    }

We can then remove the ‘Given’ step from our base scenarios…

@Iphone8
Feature: BaseScenarios
  These scenarios can be used in any project

  Scenario: 01. Validate the PageSource string on the app screen
    Then the user sees "Vincent Willem van Gogh" in the PageSource

  Scenario: 02. Validate existence of multiple texts in PageSource
    Then the user sees
      | Pablo Picasso    |
      | Spanish painter  |
      | sculptor         |
      | printmaker       |

  Scenario: 03. Validate the Current Context of the App
    Then the user sees the current context is "NATIVE_APP"

We can then also remove the ‘Given’ step in the ‘BaseScenariosSteps’ class…

package steps;

import io.cucumber.java.en.Then;
import pages.BasePage;
import java.util.List;

public class BaseScenariosSteps extends BaseSteps {

    @Then("^the user sees \"([^\"]*)\" in the PageSource$")
    public void i_see_in_the_PageSource(String expectedPageSource) {
        instanceOf(BasePage.class).validatePageSource(expectedPageSource);
    }

    @Then("^the user sees$")
    public void iSee(List<String> existsInPageSource) {
        instanceOf(BasePage.class).validateMultipleInPageSource(existsInPageSource);
    }

    @Then("^the user sees the current context is \"([^\"]*)\"$")
    public void iSeeTheCurrentContextIs(String currentContext) {
        instanceOf(BasePage.class).validateCurrentContext(currentContext);
    }
}

In the next section, we will create our first proper test and navigate between Page Objects.

Liked it? Take a second to support Thomas on Patreon!

Previous Article

Next Article

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.