Unit testing responsive apps with Cucumber Tags

“Unit Testing”, “Responsive Apps” and “Cucumber”…. do these dots connect ?

Recently we developed a customer facing Java web-app for one of our clients. It was targeted for multiple platforms & devices and therefore, had to have a responsive UI.

After some study on device types and browser support (for JS / HTML 5/ CSS 3) we decided to serve 3 variations of our UI based on device screen size and JavaScript / CSS support.

  • We targeted devices with screen sizes ranging from 240px * 320px (small mobiles) up to 1024px * 1024px (laptops & more).
  • And of course, we had to support multiple browsers too…
  • Multiple devices & browsers meant that there would be multiple user flows corresponding to each device and therefore a good automation suite would be required to test these flows.

—————————————————————————————————————————

So we began with the BDD approach using Cucumber for our application, which made sure our automation tests were in place.
Based on our user flows, we built our features & step definitions using the Page Object Pattern.
We initially had one feature file covering a single feature / functionality for one type of UI. As a result, we ended up having 4 feature files for every single feature / functionality:

  • Sophisticated – For Tabs and Desktops
  • Medium – For high end smart-phones with good CSS support & large screen sizes
  • Minimal – For low end phones
  • NoJS – For phones with no / disabled JavaScript support

4 feature files per functionality was bad design !!! Also, as user flows increased, we had to re-factor redundant feature files…

—————————————————————————————————————————

So what helped us tackle this problem? Cucumber Tags

Using Cucumber Tags, we were able to get a single feature file working for all variations / categories of the web-app.
And what about multiple browsers? We used Gradle to execute these tests on different browsers…

Now we have a single feature file for 4 different layouts of application.
With our glue code using the configuration from the build script, we then initialized Selenium WebDriver with appropriate browser capabilities (…if you’d like to see how to initialize WebDriver, see the code snippet DriverSimulator.java at the end of this blog…).

—————————————————————————————————————————

Here’s an example of how Cucumber Tags work...
Say for a user login flow, we have below feature to be executed for 4 variations (Sophisticated, Medium, Minimal, NoJS) as well as different browsers:

Feature: As a valid user I should be able to access my home page only on successful login.


@all
Scenario: As an user I should be able to login with valid credentials
Given I am not logged
When I access login page
And I enter my username and password
And I click on login
Then I should land on my home page

@sophisticated  @medium  @minimal
Scenario: As an user I should see not be allowed to login without valid credentials
Given I am not logged
When I access login page
Then username password fields are empty and login button is disabled
And I enter my password
Then login button should remain disabled

@nojs
Scenario: As an user on no-js device I should see validations for empty username
Given I am not logged in
When I access login page
Then login button is enabled
And enter my password
And click on login button
Then I should see validation message for empty username field on login page

@skip
Scenario: As an user I should see validations for invalid username password
Given I am not logged in
When I access login page
And enter my username and incorrect password
And click on login button
Then I should see validation message for invalid username or password on login page

Here the “tags” take care of which scenario to execute for a particular simulated category.

  • @all – would run my tests on all profiles of WebDriver
  • @nojs – would run my tests only on driver with “JavaScript disabled” profile
  • @sophisticated – would run my tests on driver with “sophisticated” profile
  • @skip – would skip any test (see Gradle task)

and so on…
(Note: Categories were simulated using user agents as profiles in WebDriver)

We then used Gradle to execute these features across device categories as well as different browser versions by passing relevant browser name and initializing with corresponding driver profile using WebDriver.

build.gradle

task runWithSophisticatedDevice(dependsOn: [waitForTomcat]) {
   testClassesDir = sourceSets.functionalTest.output.classesDir
   doLast {
     javaexec {
       systemProperty 'BROWSER',"sophisticated"
       main = "cucumber.cli.Main"
       classpath = sourceSets.functionalTest.runtimeClasspath
       args = ['-f', 'html:build/test-results/sophisticated', '-f',
              'junit:build/test-results/sophisticated/test.xml',
              '--out','./build/test-results/sophisticated', '--glue', 'example.test.steps',
              'src/functional-test/resources/test', '--tags','~@skip',
              '--tags', 'sophisticated', '--tags', 'all']
     }
   }
 }

 task runWithChrome(dependsOn: waitForTomcat) {
   def systemProperties = System.properties
   def testClassesDir = sourceSets.functionalTest.output.classesDir
   doLast {
     javaexec {
        systemProperty 'BROWSER',"chrome"
        main = "cucumber.cli.Main"
        classpath = sourceSets.functionalTest.runtimeClasspath
        args = ['-f', 'html:build/test-results/chrome', '-f', 'junit:build/test-results/chrome/test.xml',
        '--out','./build/test-results/chrome', '--glue', 'example.test.steps',
        'src/functional-test/resources/test']
        jvmArgs '-Xms128m', '-Xmx512m', '-XX:MaxPermSize=128m', "-DBROWSER=chrome"

     }
   }
 }

  task runAll(dependsOn: [ tasks.matching {
     Task task -> task.name.startsWith("runWith")
   } ])

The task runAll will run all test groups.

DriverSimulator.java (…to initialize WebDriver…)

public static void initialize() {
        if (driver != null) {
            driver.quit();
        }
        getBrowserSpecificDriver();
        driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
        driver.manage().deleteAllCookies();
  }

    private static void getBrowserSpecificDriver() {
       String browser = System.getProperty("BROWSER");
       if (browser == null) {
         driver = new FirefoxDriver();
       } else if (browser.equalsIgnoreCase("nojs")) {
         FirefoxProfile profile = new FirefoxProfile();
         profile.setPreference("javascript.enabled", false);
         driver = new FirefoxDriver(profile);
       } else if (browser.equalsIgnoreCase("sophisticated")) {
         //Returns the driver initialised with corresponding useragent
         //as the app uses user agent to detect the request source and
         //renders the view accordingly.
         getDriverForSophisticatedDevice();
       } else if (browser.equalsIgnoreCase("chrome")) {

       try {
          File file = new File(PATH_TO_CHROME_DRIVER);
          System.setProperty("webdriver.chrome.driver", file.getAbsolutePath());
          driver = new ChromeDriver();
        } catch (Exception e) {
           e.printStackTrace();
        }
      } else {
          try {
            driver = new FirefoxDriver();
           } catch (Exception e) {
               e.printStackTrace();

The project structure below briefly gives an outline of what we used:
project_structure

Tejaswita Takawale

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s