Sunday, September 10, 2023

How to Scroll Down to an Element in Selenium WebDriver with Appium

Let's say that you're automating a test scenario for a mobile application, and you need to interact with an element that is not initially visible on the screen. You need a way to scroll down until the element becomes visible, allowing you to perform actions on it.

To tackle this problem, you can create a reusable method that scrolls down to a specified element. This method will use the TouchAction class in Appium to simulate a touch-based scroll gesture until the element becomes visible. 

Here's a sample method in Java:

public void scrollDownToElement(WebElement element) {
    Dimension size = driver.manage().window().getSize();
    int startX = size.width / 2;
    int startY = (int) (size.height * 0.8);
    int endY = (int) (size.height * 0.2);

    while (!element.isDisplayed()) {
        TouchAction<?> action = new TouchAction<>((PerformsTouchActions) driver);
        action.press(PointOption.point(startX, startY))
                .waitAction(WaitOptions.waitOptions(Duration.ofMillis(500)))
                .moveTo(PointOption.point(startX, endY))
                .release()
                .perform();
    }
}

Let's break down how this method works:
  1. We obtain the screen dimensions to calculate the starting and ending points for the scroll gesture. So we set startX to the middle of the screen, startY to 80% down from the top, and endY to 20% down from the top.
  2. We enter a while loop that continues until the specified element becomes visible.
  3. Inside the loop, we create a TouchAction instance and perform the following steps:
    • press: We press at the starting point.
    • waitAction: We introduce a small delay (500 milliseconds) to allow the element to become visible.
    • moveTo: We move the touch to the ending point.
    • release: We release the touch action.
  4. The scroll action is performed repeatedly until the element is displayed, indicating that it is now in view.

The scrollDownToElement method presented here provides a reusable solution for ensuring that your target element is within view, allowing you to automate interactions seamlessly. Happy testing!

Saturday, September 9, 2023

Android Architectural Pattern - Databinding or 2 way Databinding.

' We don't use resource Id's for these. They are designed using a different architectural pattern called data binding and in some cases 2 way data binding.'

This is what my friendly app lead told me when I told him I'd like to have 'resource IDs' for some of the elements in the UI. He further said to look 'automated testing using Android dataBinding' and for any hook onto the databindingImpl classes. Very useful hints :).. thanks G

Android DataBinding

Android DataBinding is a library that allows you to bind UI components in your app's layout files directly to data sources (e.g., ViewModel).

It simplifies the process of updating UI elements when data changes and can improve code readability

Steps to identify and interact with elements

Assuming you are using appium/java. Also you have setup necesssary env setup such as mobile capabilties and driver initialisation , server startup, here is how the element capturing happens.

Since DataBinding allows you to bind UI elements directly to data sources, it is a bit different from the traditional method of locating elements using resource IDs or XPath
  • In Android DataBinding layouts, UI elements are bound to data sources using expressions like @{viewModel.text}.
  • To locate and interact with these elements in your automated tests, you can use the driver.findElementByAndroidUIAutomator method.
  • You construct a UiSelector expression that matches the text or other attributes bound to the element. 
  • In this case, I am looking for a TextView with the text matching viewModel.text.
    import io.appium.java_client.android.AndroidElement;
    
    // Locate TextView using DataBinding expression
    AndroidElement textView = driver.findElementByAndroidUIAutomator(
        "new UiSelector().textMatches(\".*" + viewModel.text + ".*\")"
    );
    

  • Once I have identified the element using DataBinding expression, I can perform various interactions such as clicking, sending text, or verifying properties. 
    // Click a button
    button.click();
    


Friday, September 8, 2023

How to scroll down in ScollView uiElement using Selenium WebDriver with Appium

A ScrollView is a common UI element in mobile app development, particularly in Android and iOS development. It's used to create a scrollable view that can contain content that doesn't fit entirely on the screen, allowing users to scroll vertically or horizontally to see more content.


Now let's see appium code for scroll ingto an element within a ScrollView in Android.
 public void scrollDownInScrollView(WebElement scrollview) {
        // Calculate screen dimensions
        int screenHeight = driver.manage().window().getSize().getHeight();

        // Define scroll points
        int startX = scrollview.getLocation().getX() + scrollview.getSize().getWidth() / 2;
        int startY = scrollview.getLocation().getY() + scrollview.getSize().getHeight() * 3 / 4;
        int endY = scrollview.getLocation().getY() + scrollview.getSize().getHeight() / 4;

        // Perform the scroll action
        TouchAction<?> action = new TouchAction<>((PerformsTouchActions) driver);
        action.press(PointOption.point(startX, startY))
                .waitAction(WaitOptions.waitOptions(Duration.ofMillis(500)))
                .moveTo(PointOption.point(startX, endY))
                .release()
                .perform();
    }


Tuesday, August 15, 2023

Test tours

To explain test tours, I will start with exploratory testing.

Explotatory testing is a more creative way of testing, but doesn't mean to say it is a random testing as well. Experts say exploratory can be more effective when we make use of test chrters.

Test tours is one of the methods that we can adopt to write test charters (test purpose) for exploratory testing.

Below are some of the tours that I found in the web. All kudos to this page [1]. I am copying and pasting it here for my notes.

  • Obsessive-Compulsive tour: This tour aims to repeat the same action as often as possible. This tour can reveal bugs like applying the discount twice.
  • Bad-Neighborhood tour: Explore the area or feature that has the most errors. In this tour, you spend time on areas known to have issues.
  • Supermodel tour: This tour focuses only on surface looks and the impressions it makes. Test the UI interface and design elements like layouts, icons, flows, and colors. 
  • Museum tour: Test code that developers didn't change in a while. You can experiment with how the code functions in new environments (Android, iOS, etc.).


Sunday, July 30, 2023

UiAutomatorViewer isn't working with higher java versions.

 

I was getting this error when tried with my default Java 11;

./uiautomatorviewer -Djava.ext.dirs=/Users/yumani/Library/Android/sdk/tools/lib/x86_64:/Users/yumani/Library/Android/sdk/tools/lib is not supported. Use -classpath instead. Error: Could not create the Java Virtual Machine. Error: A fatal exception has occurred. Program will exit

The problem seems to be caused by the usage of the -Djava.ext.dirs option, which is not supported in newer versions of Java. 

Then, I used use -classpath option to set the classpath and got this error

yumani@Yumanis-MacBook-Pro bin % ./uiautomatorviewer -classpath /Users/yumani/Library/Android/sdk/tools/lib/x86_64:/Users/yumani/Library/Android/sdk/tools/lib -Djava.ext.dirs=/Users/yumani/Library/Android/sdk/tools/lib/x86_64:/Users/yumani/Library/Android/sdk/tools/lib is not supported. Use -classpath instead. Error: Could not create the Java Virtual Machine. Error: A fatal exception has occurred. Program will exit.

I am on Java11,  tried different options in the net such as upgratig sdk-tools, downloading swt.jar.

Non worked for me in macOS Ventura.

The web advise that worked was downgrading to jdk 1.8.

So I installed jdk 1.8, updated in .bash_profile, commented out my older version (because I want to switch back to it when I am not using the UiAutomatorViewer.

source ~/.bash_profile 

yumani@Yumanis-MacBook-Pro sdk % tools/bin/uiautomatorviewer -classpath /Users/yumani/Library/Android/sdk/tools/lib/x86_64/swt.jar






How to identify the main actvity from the AndroidManifest.xml

 When you setup applium capacbilities giving the app package and app activity, you have to give the main activity as the app activity.

Here is how you identify the main activity from AndroidManifest.xml when it has multiple activities.

  • Locate the AndroidManifest.xml:

The AndroidManifest.xml file is located in the app folder of your Android project. The typical path is app/src/main/AndroidManifest.xml.

  • Open the AndroidManifest.xml:

Use a text editor or an XML editor to open the AndroidManifest.xml file.

  • Search for <activity> tags:

Inside the AndroidManifest.xml, look for <activity> tags. Each <activity> tag represents an activity (screen) of the Android application.

  • Find the Main Activity:

The AppActivity is usually the main activity of the app. It is the entry point of the application, and its intent filter is often set to respond to the android.intent.action.MAIN and android.intent.category.LAUNCHER actions.

  • Look for Intent Filters:

Inside the <activity> tags, check for <intent-filter> tags. The AppActivity should have an <intent-filter> that includes the android.intent.action.MAIN and android.intent.category.LAUNCHER actions.

  • Extract the AppActivity Name:

Extract the value of the android:name attribute from the <activity> tag that has the <intent-filter> with the android.intent.action.MAIN and android.intent.category.LAUNCHER actions. The value of android:name attribute will be the AppActivity.

<activity

    android:name=".ui.MainActivity"  <!-- This is the AppActivity -->

    android:label="@string/app_name">

    <intent-filter>

        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />

    </intent-filter>

</activity>


In this example, the AppActivity is .ui.MainActivity.


Friday, March 31, 2023

Hybrid approach to Automation

What does hybrid approach to test automation mean?

It's a strategic approach that aims to support better maintainability and reduce flakiness in end-to-end (E2E) test automation. This approach involves using a combination of API tests and UI tests.

In scenarios where certain prerequisites must be met before attempting the actual test, we can use endpoint calls/API tests to achieve these prerequisites. Then, we can use webdriver tests for UI-level functional validations/assertions.

By using this approach, we can redice the flakiness that may come from  webdriver tests due to delays, object changes, and other issues. This approach is particularly useful for sections of the test case that are outside the main test case.

Let me explain with an example:

In here we are doing a mandatry field validation check; Our featue file is as below;

Scenario: Mandatory fields validation
Given I am a registered user
And I go to the accounts page
And I leave the basic salary field blank
When I try to move to the next page
Then a validation error message is displayed for the basic salary field

My step definition will be like this; 

public class MandatoryFieldsValidationStepDefinitions {

    @Given("I am a registered user")
    public void iAmARegisteredUser() {
        // Code to implement the Creat user step using RestAssured API
        
    }

    @And("I go to the accounts page")
    public void iGoToTheAccountsPage() {
        // Code to implement the "And" step using Appium
        // Navigate to the accounts page using Appium
    }

    @And("I leave the basic salary field blank")
    public void iLeaveTheBasicSalaryFieldBlank() {
        // Code to implement the "And" step using Appium
        // Find the basic salary field element and clear its value
    }

    @When("I try to move to the next page")
    public void iTryToMoveToTheNextPage() {
        // Code to implement the "When" step using Appium
        // Find the "Next" button element and click it
    }

    @Then("a validation error message is displayed for the basic salary field")
    public void aValidationErrorMessageIsDisplayedForTheBasicSalaryField() {
        // Code to implement the "Then" step using Appium
        // Find the error message element for the basic salary field and verify it is displayed
    }
}

So how do we support this in our test framework? Since the CucumberRunner takes care of executing the features by mapping them with the correct step definition, all we have to do is ensure that our design is correct and that relevant API testing and webdriver libraries are included in the project.

A sample automation framework structure for a Maven project is shown below;








Thursday, March 30, 2023

CT Server?

What is CT Server?

Recently, I have been reading a lot, or rather more than usual, about Continuous Testing. In fact, I have been trying to design such a process here at BlackArrow. However, today I came across some real details about the CT Server.

So, what is a CT Server? It refers to Continuous Testing server. The blog post that I read advised against using Jenkins for CT. Also, E2E testing is not something that Jenkins deals with in CI builds.

Instead, the author recommends using CT servers, such as BuildWise, which is an open-source software. I am yet to explore how it does things, but I'll be back with another post soon.


Tuesday, March 21, 2023

CucumberRunner sample


In this example file, I've added the following options while extending the AbstractTestNGCucumberTests class

import cucumber.api.CucumberOptions;
import cucumber.api.testng.AbstractTestNGCucumberTests;

@CucumberOptions(
        features = "src/test/resources/features",
        glue = {"step_definitions"},
        tags = "@JREQ-BA001-2088 or @smoke or @SE",
        plugin = {
                "pretty",
                "html:target/cucumber-html-report",
                "json:target/cucumber.json",
                "rerun:target/rerun.txt"
        },
        strict = true,
        monochrome = true,
        dryRun = false,
        snippets = cucumber.api.SnippetType.CAMELCASE
)
public class MyTestRunner extends AbstractTestNGCucumberTests {
}

  • tags: Allows you to run only scenarios with specific tags. In this case, we're running scenarios with any of the three tags specified in the feature file. I have the story ticket, the test inclusion category and the feature tagged.
  • plugin: This specifies the output formats for the Cucumber report. In addition to the "pretty" format, we're also generating an HTML report, a JSON report, and a rerun file (which lists any scenarios that failed on the first run and need to be rerun).
  • strict: If set to true, Cucumber will fail if there are any undefined or pending steps. I prefer to have this set to false and user the power of the dryRun option.
  • monochrome: This makes the output in the console easier to read by removing colour and special characters.
  • dryRun: If set to true, Cucumber will check that all steps have corresponding step definitions, but won't actually run the scenarios. This can be useful for quickly checking that your step definitions match your feature file.
  • snippets: This specifies the naming convention for generated step definitions. In this case, we're using CamelCase naming.

Friday, March 17, 2023

Chosen path for using feature files to call my test daa

 

This is a summary of the findings from my research past week.  


Out of these, I am choosing the method 3 considering the plus points that I could think of..



Tuesday, March 14, 2023

Using feature files for API test automation - 3

In here the entire user payload is read from the examples table and stored as a Map in the step definition. 

This approach is useful when you have a small number of fields in the request body, and you can easily read them from the feature file's data table.

Scenario Outline: Create User Successfully

    Given the user payload is:
      | first_name    | <first_name> |
      | last_name     | <last_name>  |
      | email         | <email>      |
      | mobile_phone  | <mobile_phone> |
      | date_of_birth | <date_of_birth> |
      | password      | <password>   |
      | username      | <username>   |

    When I send a <method> request to <path>

    Then the response status code should be <status_code>

    Examples:
    | first_name | last_name | email                 | mobile_phone | date_of_birth | password  | username | method | path           | status_code |
    | John       | Doe       | john.doe@example.com  | 1234567890  | 1990-01-01    | password | johndoe  | POST   | /users/create | 201         |

My stpe definition is as below;

public class CreateUserStepDefinitions {

    private RequestSpecification requestSpec;
    private Response response;
    private Map<String, String> userPayload = new HashMap<>();

    @Given("the user payload is:")
    public void the_user_payload_is(Map<String, String> userData) {
        this.userPayload = userData;
    }

    @When("I send a {string} request to {string}")
    public void i_send_a_request_to(String method, String path) {
        requestSpec = RestAssured.given()
                .contentType(ContentType.JSON)
                .body(userPayload);

        response = requestSpec.when()
                .request(method, path);
    }

    @Then("the response status code should be {int}")
    public void the_response_status_code_should_be(int expectedStatusCode) {
        assertEquals(expectedStatusCode, response.getStatusCode());
    }

}

Let me explain the java bits in here - 

In the method the_user_payload_is, the Map<String, String> userData parameter contains the data that was provided in the Examples table of the feature file. The data is in key-value pairs where the key represents the column header and the value represents the corresponding cell value.

this.userPayload variable is an instance variable of the class (which means it can be accessed from any method within the class). In the_user_payload_is method, we are assigning the userData map to the userPayload instance variable. This is done so that we can access the userPayload map in other methods within the class, specifically the i_send_a_request_to method.

In the i_send_a_request_to method, we are using the userPayload map as the body of the request. 
The RestAssured.given() method is used to start building the request. It is followed by the .contentType(ContentType.JSON) method, which specifies that the request body will be in JSON format. The .body(userPayload) method sets the request body as the userPayload object, which contains the data for creating a new user.

The requestSpec.when().request(method, path) method is then called to send the request to the server with the given method and path. This method returns a response object, which is stored in the response variable for later use in the_response_status_code_should_be method.

Monday, March 13, 2023

Two different flavours of feature files

Format 1 - Scenario outline with example table

Given I have a valid auth token
When I submit a POST request to create a user with the following details:
| first_name | last_name | email | mobile_phone | date_of_birth | password | username |
| <first_name>| <last_name> | <email> | <mobile_phone>| <date_of_birth> | <password> | <username> |
Then the response status code should be <status_code>

Examples:
| first_name | last_name| email | mobile_phone | date_of_birth | password | username | status_code |
| John | Doe | johndoe@example.com | 1234567890 | 1990-01-01 | password123 | johndoe123 | 201 |
| Jane | Smith | janesmith@example.com | 0987654321 | 1985-02-14 | password456 | janesmith12 | 201 |

This format gives a higher level of abstraction where the same scenario is run multiple times with different sets of data. Yes data including the expected result as well. 

This format is useful when you don't want to repeat the same scenario. Also when you have a simpler payload.


Format 2 - Single Scenario with Given\When\Then steps

Feature: Create User API

Scenario: Create User Successfully
Given the user payload is:
| first_name | John             |
| last_name | Doe             |
| email | john.doe@example.com |
| mobile_phone | 1234567890         |
| date_of_birth | 1990-01-01           |
| password | password             |
| username | johndoe              |
When I send a {method} request to {path}
Then the response status code should be 201
|method  | path | status_code |
| POST |/users/create"|201 |

This gives more detailed description of the scenario. Each @Given, @When, @Then step is elaborated with relevant data.

In this case you may be having more specific scenarios and conditions.

Featured

Selenium - Page Object Model and Action Methods

  How we change this code to PageObjectModel and action classes. 1 2 3 driver . findElement ( By . id ( "userEmail" )). sendKeys (...

Popular Posts