Run tests with QR code scan

Below you can find information on how to run tests, which include QR code scan

Prerequisites

  • Existing native application with a feature, related to the QR code scan.
  • The app is installed on a device with image injection enabled.
    More details can be found here and in our API reference collection.
  • The tests can operate with system alerts
    For example, using the Appium command.
  • Access key is generated.

Step 1: Add executing of the QR code upload API

We have a specific API action for uploading QR codes in our public API.
The action accepts a QR code as the file, which should be sent in the “multipart/form-data” parameter.
Below you can find an example for popular programming languages.

 // Rest-assured library is using here https://rest-assured.io/
 
 public void uploadQrCode(String deviceSerial, File qrCode) {
        RestAssured.given().auth().
                oauth2(<ACCESS_KEY>).
                baseUri("https://app.mobitru.com/billing/unit/"+
                      <TEAM_ACCOUNT_NAME>
                      +"/automation/api").
                header("X-Content-Type", "image/png").
                header("x-File-Name", qrCode.getName()).
                contentType("multipart/form-data").
                multiPart("file", qrCode).
                post("device/{serial}/injection/image", deviceSerial).then().
                statusCode(HTTP_NO_CONTENT);
    }
const axios = require('axios');
const fs = require('fs');
const path = require('path')
const FormData = require('form-data');
const KEY = 'mobitru_ak_....';
const PROJECT_NAME = '<TEAM_CODE>';
const APPIUM_HUB = 'app.mobitru.com';

async function uploadQrCode(qrCodePath) {
    // Create FormData
    const formData = new FormData();
    formData.append('file', fs.createReadStream(qrCodePath));

    // Config for headers
    const headers = {
        // Add multipart headers specific to the FormData instance
        ...formData.getHeaders(),
        "Authorization": `Bearer ${KEY}`,
        "X-Content-Type": "image/png",
        "x-File-Name": qrCodePath.split('/').pop() // Extract file name
    };


    // extract device serial from capabilities and build an API url
    const serial = browser.options.capabilities["appium:udid"];
    const apiUrl = `https://${APPIUM_HUB}/billing/unit/${PROJECT_NAME}/automation/api`;
    const url = `${apiUrl}/device/${serial}/injection/image`;

    // Send POST request
    const response = await axios.post(url, formData, {headers});

    // Check for status code
    if (response.status === 204) {
        console.log("File uploaded successfully!");
    } else {
        console.error(`Failed to upload file. Status code: ${response.status}, Message: ${response.statusText}`);
    }

}
import requests

PROJECT_NAME = "<TEAM_CODE>"
ACCESS_KEY = "mobitru_ak_...."
APPIUM_HUB = "app.mobitru.com"
DEVICE_UDID = "<DEVICE_SERIAL>"

def upload_qr_code(qr_code_path):
    api_url = f"https://{APPIUM_HUB}/billing/unit/{PROJECT_NAME}/automation/api"
    headers = {
        "Authorization": f"Bearer {ACCESS_KEY}",
        "X-Content-Type": "image/png",
        "x-File-Name": qr_code_path.split("/")[-1]  # extract the file name
    }

    files = {
        "file": open(qr_code_path, "rb")
    }

    url = f"{api_url}/device/{DEVICE_UDID}/injection/image"

    response = requests.post(
        url,
        headers=headers,
        files=files
    )

    if response.status_code == 204:  # HTTP_NO_CONTENT
        print("QR code uploaded successfully!")
    else:
        print(f"Failed to upload QR code. Status code: {response.status_code}, Response: {response.text}")
using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;

....

      const string ProjectName = "<TEAM_CODE>";
      const string AccessKey = "mobitru_ak_....";
      const string AppiumHost = "app.mobitru.com";
      const string DeviceUdid = "00008101-001608482602001E";


private void UploadQrCode(string qrCodePath)
        {
            // API URL
            string apiUrl = $"https://{AppiumHost}/billing/unit/{ProjectName}/automation/api/device/{DeviceUdid}/injection/image";

            using (var httpClient = new HttpClient())
            {
                // Headers
                var request = new HttpRequestMessage(HttpMethod.Post, apiUrl);
                request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", AccessKey);
                request.Headers.Add("X-Content-Type", "image/png");
                request.Headers.Add("x-File-Name", Path.GetFileName(qrCodePath)); // Extract file name

                // File to upload
                var formData = new MultipartFormDataContent();
                using (var fileStream = new FileStream(qrCodePath, FileMode.Open, FileAccess.Read))
                {
                    var fileContent = new StreamContent(fileStream);
                    fileContent.Headers.ContentType = new MediaTypeHeaderValue("image/png");
                    formData.Add(fileContent, "file", Path.GetFileName(qrCodePath));
                    request.Content = formData;

                    // Send POST request (synchronous)

                    var response = httpClient.Send(request);

                    // Handle response
                    if (response.IsSuccessStatusCode && response.StatusCode == System.Net.HttpStatusCode.NoContent)
                    {
                        Console.WriteLine("QR code uploaded successfully!");
                    }
                    else
                    {
                        Console.WriteLine($"Failed to upload QR code. Status code: {(int)response.StatusCode}, Response: {response.Content.ReadAsStringAsync().Result}");
                    }
                }
            }
        }

Step 2: Cover the QR code scan pop-up

It depends on the Platform, but usually, the two kinds of behavior should be covered:

  • For Android: “allow permission” action on the Camera dialog.
  • For iOS: “accept” action on a simple system Alert.

Both pop-ups typically activate when the QR code scanning process is initiated.
Also, here is an example of a single method for handling both items using Appium:

@AndroidFindBy(xpath = "//*[@resource-id='com.android.permissioncontroller:id/permission_allow_one_time_button']")
    private WebElement allowPermissionsOnceButton;
  
  public void openUploadQrCodeScreen() {
        //example of trigger the QR code scan process
        applyPromoButton.click();
        //wait for the process is started
        new WebDriverWait(driver, Duration.ofSeconds(10), Duration.ofSeconds(1))
                .ignoring(WebDriverException.class)
                .until(ExpectedConditions.invisibilityOf(applyPromoButton));
        //make different actions depending on the platform
        if (driver.getCapabilities().getPlatformName().equals(Platform.ANDROID)){
            // wait for allow permission button and click on it
            waitForElementDisplayed(allowPermissionsOnceButton);
            allowPermissionsOnceButton.click();
        }else{
            // wait for simple system alert and perform accept
            new WebDriverWait(driver, Duration.ofSeconds(10), Duration.ofSeconds(1))
                    .ignoring(WebDriverException.class)
                    .until(ExpectedConditions.alertIsPresent());
            driver.switchTo().alert().accept();
        }
    }
// The example is for WebdriverIO framework

async function openQRCodeScreen() {

    // Activate uploading a QR code
    const activateXPath = browser.isAndroid
        ? "//*[@resource-id='com.epam.mobitru:id/apply_promo_code']" // Android XPath
        : "//XCUIElementTypeButton[@name='Apply promo code']"; // iOS XPath

    const activateElement = await driver.$(activateXPath);
    await activateElement.click();

    // Handle camera permissions
    if (browser.isAndroid) {
        const allowButtonXPath = "//*[@resource-id='com.android.permissioncontroller:id/permission_allow_one_time_button']";
        const allowButton = await driver.$(allowButtonXPath);
        await allowButton.click();
    } else {
        // Wait for the iOS alert and accept it
        await driver.waitUntil(
            async () => (await driver.getAlertText()) !== null,
            {timeout: DEFAULT_WAIT_TIMEOUT_MS, timeoutMsg: "Alert not found"}
        );
        await browser.acceptAlert();
    }
}
from appium import webdriver
from appium.webdriver.common.appiumby import AppiumBy
from appium.webdriver.common.mobileby import MobileBy
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

def open_qr_code_screen(driver):
    is_android = "android" == driver.capabilities.get("platformName").lower()

    # activate uploading a qr code
    activate_xpath = "//*[@resource-id='com.epam.mobitru:id/apply_promo_code']" if is_android else "//XCUIElementTypeButton[@name='Apply promo code']"
    driver.find_element(AppiumBy.XPATH,activate_xpath).click()

    # allow camera permissions
    if is_android:
        driver.find_element(AppiumBy.XPATH,
                        "//*[@resource-id='com.android.permissioncontroller:id/permission_allow_one_time_button']").click()
    else:
        WebDriverWait(driver, DEFAULT_WAIT_TIMEOUT_SEC).until(
            EC.alert_is_present()
        )
        driver.switch_to.alert.accept()
using OpenQA.Selenium;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Android;
using OpenQA.Selenium.Appium.iOS;
using OpenQA.Selenium.Support.UI;


        private void OpenQrCodeScreen()
        {
            bool isAndroid = driver.PlatformName.ToLower().Equals("android");
            // XPath for element activation
            string activateXPath = isAndroid
                ? "//*[@resource-id='com.epam.mobitru:id/apply_promo_code']"
                : "//XCUIElementTypeButton[@name='Apply promo code']";

            // Find and click the element
            AppiumElement activateElement = driver.FindElement(By.XPath(activateXPath));
            activateElement.Click();
            if (isAndroid)
            {
                // Click the allow button on Android
                string allowButtonXPath = "//*[@resource-id='com.android.permissioncontroller:id/permission_allow_one_time_button']";
                IWebElement allowButton = driver.FindElement(By.XPath(allowButtonXPath));
                allowButton.Click();
            }
            else
            {
                // Wait for the iOS alert to appear and then accept it
                WebDriverWait wait = new(driver, TimeSpan.FromSeconds(DefaultWaitTimeoutSec));
                wait.Until(driver => !string.IsNullOrEmpty(driver.SwitchTo().Alert().Text));

                var alert = driver.SwitchTo().Alert();
                alert.Accept();
            }

        }

Step 3: Use both actions in a test

In the end, you can cover the full scenario by using both actions one after another.
Below you can find an example for popular programming languages:

  @Test(description = "Check apply promo from qr code")
    public void checkPromoApply() {
        //read a QR code image to file object
        File qrCode = new File(IOUtils.resourceToURL("qr.png", this.getClass().getClassLoader()).getFile());
        //trigger the QR code scan process
        openUploadQrCodeScreen();
        //upload the QR code image, which will be applied automatically
        uploadQrCode(udid, qrCode);
        //actions after apply the QR code...
    }
// The example is for WebdriverIO framework

describe('Appium QR Code Test', () => {

    it('should upload a QR code successfully', async () => {

        // trigger the QR code scan process
        await openQRCodeScreen(driver);

        //perform the qr code uploading
        await uploadQrCode(path.resolve(__dirname, "qr.png"))

        //wait for the element, which appears after success uploading
        const resultXpath = browser.isAndroid
            ? "//*[@resource-id='com.epam.mobitru:id/applied']" // Android XPath
            : "//*[contains(@name,'applied')]"; // iOS XPath
        const resultElement = await $(resultXpath);
        await resultElement.waitForDisplayed({timeout: DEFAULT_WAIT_TIMEOUT_MS});
    });

});
def test_qr_code_apply(driver):
    is_android = "android" == driver.capabilities.get("platformName").lower()

    # trigger the QR code scan process
    open_qr_code_screen(driver)

    # perform the qr code uploading
    upload_qr_code("qr.png")

    # wait for the element, which appears after success uploading
    result_xpath = "//*[@resource-id='com.epam.mobitru:id/applied']" if is_android else "//*[contains(@name,'applied')]"
    WebDriverWait(driver, DEFAULT_WAIT_TIMEOUT_SEC).until(
        EC.visibility_of_element_located((MobileBy.XPATH, result_xpath))
  )
using NUnit.Framework;


        [Test]
        public void TestQrCode()
        {
            // trigger the QR code scan process
            OpenQrCodeScreen();

            //perform the qr code uploading
            UploadQrCode(Path.Combine(Directory.GetParent(Environment.CurrentDirectory).Parent.Parent.FullName, "qr.png"));

            // wait for element, which appears after the QR code upload

            // Select the correct XPath based on the platform
            string resultXPath = driver.PlatformName.ToLower().Equals("android")
                ? "//*[@resource-id='com.epam.mobitru:id/applied']"
                : "//*[contains(@name,'applied')]";

            // Wait for the element using WebDriverWait
            WebDriverWait wait = new(driver, TimeSpan.FromSeconds(DefaultWaitTimeoutSec));
            wait.Until(driver => driver.FindElement(By.XPath(resultXPath)).Displayed);
        }

Note:

If you don’t have an Application with the QR code scan but would like to try the described steps, please use our open-source demo apps for Android and iOS.

Scroll to Top