Quantum 1.22.0 release notes

This article introduces the changes made in version 1.22.0 (after version 1.21) and also includes upgrade instructions. 

Changes in version 1.22.0

Version 1.22.0 introduces the following changes after version 1.21: 

  • Added the option for a device availability check before creating a driver. This solution addresses the problem that QAF attempts to create a driver many times when a device is unavailable. This results in too many blocked reports in Perfecto Smart Reporting. By enabling this listener in the project, the total count of the executed scenarios in the Quantum project and in Perfecto Smart Reporting will match even in the case of multiple blocked reports due to device unavailability. 

    • Known Limitations:

      • This solution is not applicable for desktop web, both Windows and Mac instances.

      • For teams using distributed parallel execution with dynamic capabilities like Regex in the device model name, this solution should not be enabled because it is expected to return inconsistent results. 

      • Proxy-based environments are currently not supported. This will be implemented in the next release.

    • Required changes: To enable this solution, you need to perform the following changes.

      The key should already be included in the application.properties file. Here, we only add the package.

      Copy

      application.properties File Driver Listener Registration

      wd.command.listeners=com.quantum.listeners.PerfectoDriverListener;com.quantum.listeners.DriverInitListener

      Alternatively, if you do not want to configure this in the application.properties file, you can include the following line in the TestNG configuration file.

      Copy

      TestNG File Driver Listener Registration

      <parameter name="wd.command.listeners" value="com.quantum.listeners.PerfectoDriverListener;com.quantum.listeners.DriverInitListener"></parameter>
  • Upgraded the Appium Java Client to version 7.3.0 and made the necessary changes.
  • Fixed an issue with a new pattern in the step parameter. (Related to Github Issue #64)

  • Added service functions of the Perfecto features Call, SMS, and Email. (Github Issue #72)
    For your reference, following is the source code of these functions.

    Copy

    DeviceUtils.java - Cloud Call, SMS & Email Service Functions

    /**
         * Generates an external voice call recording to the selected destination
         * It is possible to select multiple destinations that may include devices, users, and phone numbers.
         * There is no default. To use, at least one destination must be selected.
         *
         *  @param toHandset - The destination device. It is possible to select multiple devices.
         *  @param toUser - The user for this command. It is possible to select multiple users.
         *  @param toLogical - user | none    The user currently running the script.
         *  @param toNumber     -     The destination phone number. It is possible to select multiple phone numbers.
         *                         Format -  +[country code][area code][phone number]
         *
         */
        public static void cloudCall( String toHandset, String toUser, String toLogical , String toNumber )  throws Exception {
            if (toHandset.isEmpty() && toUser.isEmpty() && toLogical.isEmpty()  && toNumber.isEmpty())
                throw new Exception("Please select at least one destination");

            Map<String, Object> pars = new HashMap<>();
            if (!toHandset.isEmpty()) pars.put("to.handset", toHandset);
            if (!toUser.isEmpty()) pars.put("to.user", toUser);
            if (!toLogical.isEmpty()) pars.put("to.logical", toLogical);
            if (!toNumber.isEmpty()) pars.put("to.number", toNumber);
            getQAFDriver().executeScript("mobile:gateway:call", pars);
        }

        /**
         * Sends an email message to the selected destination
         *     It is possible to select multiple destinations that may include email addresses, devices, and users.
         *    There is no default.
         *    At least one destination must be selected. If not specified, the message subject and body are be defaulted to none and “test email”.
         *
         * Confirm that the destination device is configured to receive email messages.
         *
         *  @param subject - The message subject for this command. <default is "none">.
         *  @param body - The message text for this command. <default is "test email">.
         *  @param toHandset - The destination device. It is possible to select multiple devices.
         *  @param toAddress - The email address for this command.
         *  @param toUser - The user for this command. It is possible to select multiple users.
         *  @param toLogical - user | none    The user currently running the script.
         *
         */
        public static void cloudEmail(String subject, String body, String toHandset, String toAddress, String toUser, String toLogical )  throws Exception {
            if (toHandset.isEmpty() && toAddress.isEmpty() && toUser.isEmpty() && toLogical.isEmpty())
                throw new Exception("Please select at least one destination");

            Map<String, Object> pars = new HashMap<>();
            if (!subject.isEmpty()) pars.put("subject", subject);
            if (!body.isEmpty()) pars.put("body", body);
            if (!toHandset.isEmpty()) pars.put("to.handset", toHandset);
            if (!toAddress.isEmpty()) pars.put("to.address", toAddress);
            if (!toUser.isEmpty()) pars.put("to.user", toUser);
            if (!toLogical.isEmpty()) pars.put("to.logical", toLogical);
            getQAFDriver().executeScript("mobile:gateway:email", pars);
        }

        /**
         * Sends an SMS message to the selected destination.
         * It is possible to select multiple destinations that may include devices, users, and phones.
         * There is no default. To use, at least one destination must be selected.
         *
         *  @param body - The message text for this command. <default is "test email">.
         *  @param toHandset - The destination device. It is possible to select multiple devices.
         *  @param toUser - The user for this command. It is possible to select multiple users.
         *  @param toLogical - user | none    The user currently running the script.
         *  @param toNumber     -     The destination phone number. It is possible to select multiple phone numbers.
         *                         Format -  +[country code][area code][phone number]
        *
         */
        public static void cloudSMS( String body, String toHandset, String toUser, String toLogical , String toNumber )  throws Exception {
            if (toHandset.isEmpty() && toUser.isEmpty() && toLogical.isEmpty()  && toNumber.isEmpty())
                throw new Exception("Please select at least one destination");

            Map<String, Object> pars = new HashMap<>();
            if (!body.isEmpty()) pars.put("body", body);
            if (!toHandset.isEmpty()) pars.put("to.handset", toHandset);
            if (!toUser.isEmpty()) pars.put("to.user", toUser);
            if (!toLogical.isEmpty()) pars.put("to.logical", toLogical);
            if (!toNumber.isEmpty()) pars.put("to.number", toNumber);
            getQAFDriver().executeScript("mobile:gateway:sms", pars);
        }
  • Upgraded log4j to log4j version 2. The old log4j version has security vulnerability warnings shared by Github bots. The changes to upgrade to log4j 2 are mentioned in the following version upgrade notes.

  • Added application installation utility methods to perform sensor instrumentation to the project. Following are the newly added steps/methods.

    Copy

    DeviceUtils.java - Application Installation Utility methods

    /**
    *
    * @param repoKey - The full repository path, including directory and file name, where to locate the application.
    * @param instrument - Perform instrumentation.. Possible values :noinstrument (default) | instrument.
    * @param sensorInstrument - Enable device sensor. Possible values: nosensor (default)| sensor.
    * @param resignEnable - Re-sign the app with a Perfecto code-signing certificate that has the cloud device provisioned. Possible values false (default) | true.
    */
    public static void installApp(String repoKey, String instrument, String sensorInstrument, String resignEnable) {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("file", getBundle().getString("repoKey", repoKey));
        if (!instrument.isEmpty()) params.put("instrument", instrument);
        if (!sensorInstrument.isEmpty())params.put("sensorInstrument", sensorInstrument);
        if (!resignEnable.isEmpty())params.put("resign", resignEnable);

        String resultStr = (String) getQAFDriver().executeScript("mobile:application:install", params);
        System.out.println(resultStr);
    }



    /**
    *
    * For Android devices:
    * - The appplication manifest.xml file must include internet access permission: <uses-permission android:name="android.permission.INTERNET"/>* - The application will automatically be signed with an Android debug key to enable native object automation.
    * @param repoKey - The full repository path, including directory and file name, where to locate the application.
    * @param instrument - Perform instrumentation.. Possible values :noinstrument (default) | instrument.
    * @param sensorInstrument - Enable device sensor. Possible values: nosensor (default)| sensor.
    * @param certificateFile - The repository path, including directory and file name, of the certificate for certifying the application after instrumentation. This is the Keystore file in Android devices.
    * @param certificateUser - The user for certifying the application after instrumentation.This is the Key Alias in Android devices.
    * @param certificatePassword - The password for certifying the application after instrumentation. This is the Keystore Password in Android devices.
    * @param certificateParams - The key password parameter for certifying the application after instrumentation. This is the Key Password in Android devices.
    The value must be preceded with "keypass".
    *
    */


    public static void installInstrumantedAppOnAndroid(String repoKey, String instrument, String sensorInstrument, String certificateFile, String certificateUser, String certificatePassword, String certificateParams) {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("file", getBundle().getString("repoKey", repoKey));
        if (!getBundle().getString(instrument, instrument).isEmpty()) params.put("instrument", instrument);
        if (!sensorInstrument.isEmpty())params.put("sensorInstrument", sensorInstrument);

        if (getBundle().getString(instrument, instrument).equals("instrument")) {

        params.put("certificate.file", getBundle().getString("certificateFile", certificateFile));
        params.put("certificate.user", getBundle().getString("certificateUser", certificateUser));
        params.put("certificate.password", getBundle().getString("certificateFile", certificatePassword));
        params.put("certificate.params", getBundle().getString("certificateFile", certificateParams));

    }
    Copy

    PerfectoApplicationSteps.java - New Install Application Steps

    /**
    * Installs a single application on the device, and re-sign the application.
    * <p>* To use, specify the local path to the application or the application repository key.
    * If the application repository key is specified, the application must first be uploaded to the Perfecto Lab repository.
    * <p>* To do this, log in to the Perfecto Lab interface and use the Repository manager.
    * Supported file formats include APK files for Android and IPA for iOS.
    *
    * @param application the local or repository path, including directory and file name, where to locate the application
    */
    @Then("^I install application \"(.*?)\" and re-sign it")
    public static void installAppWithWebViewInstrumentation(String application){
        DeviceUtils.installApp(application, "noinstrument", "nosensor", "true");
    }



    /**
    * Installs a single application on the device, with sensor instrumentation and re-sign the application.
    * <p>* To use, specify the local path to the application or the application repository key.
    * If the application repository key is specified, the application must first be uploaded to the Perfecto Lab repository.
    * <p>* To do this, log in to the Perfecto Lab interface and use the Repository manager.
    * Supported file formats include APK files for Android and IPA for iOS.
    *
    * @param application the local or repository path, including directory and file name, where to locate the application
    */
    @Then("^I install application \"(.*?)\" with sensor instrumentation and re-sign it$")
    public static void installAppWithWebViewInstrumentationAndSensorInstrumentation( String application){
        DeviceUtils.installApp(application, "noinstrument", "sensor", "true");
    }


    /**
    * Installs a single application on the device, with sensor instrumentation.
    * <p>* To use, specify the local path to the application or the application repository key.
    * If the application repository key is specified, the application must first be uploaded to the Perfecto Lab repository.
    * <p>* To do this, log in to the Perfecto Lab interface and use the Repository manager.
    * Supported file formats include APK files for Android and IPA for iOS.
    *
    * @param application the local or repository path, including directory and file name, where to locate the application
    */
    @Then("^I install application \"(.*?)\" with sensor instrumentation")
    public static void installAppWithSensorInstrumentation(String application){
        DeviceUtils.installApp(application, "noinstrument", "sensor", "false");
    }
  • Added Accessibility Audit Command method, steps, and download feature. Following are the code blocks of the steps and method:

    Copy

    PerfectoApplicationSteps.java

    /**
        * This step will perform an audit of the accessibility features in the application. To check the entire application, this command needs to be repeated for each application screen.
        *
        * @param tagName - The tag that is appended to the name of the audit report to help match it to the application screen.
        */
       @Then("^I perform an audit of the accessibility on tag application screen \"(.*?)\"$")
       public static void checkAccessibility(String tagName) {
           DeviceUtils.checkAccessibility(tagName);
       }
    Copy

    DeviceUtils.java - This method was added in the file

    /**
         * Performs an audit of the accessibility features in the application. To check the entire application, this command needs to be repeated for each application screen.
         *
         * @param tagName - The tag that is appended to the name of the audit report to help match it to the application screen.
         */
        public static void checkAccessibility(String tagName) {
             //declare the Map for script parameters
             Map<String, Object> params = new HashMap<>();
             params.put("tag", tagName);
             getQAFDriver().executeScript("mobile:checkAccessibility:audit", params);
        }

    To download the accessibility report, configure the below key in the application.properties file.

    Copy

    application.properties - Set the below key value to true

    perfecto.download.attachments=true


  • Fixed a bug in the image injection step definition. The parameter name was incorrect.

    Copy

    PerfectoApplicationsSteps.java - Code fix in the below code

    /**
    * Start image injection to the device camera to application using application name.
    *
    * @param repositoryFile the image repository file location
    * @param id the identifier of the application
    * @see <a href="https://community.perfectomobile.com/series/21760/posts/995065">Application Identifier</a>*/
    @Then("^I start inject \"(.*?)\" image to application id \"(.*?)\"$")
    public static void startImageInejection(String repositoryFile, String id){
        DeviceUtils.startImageInjection(repositoryFile, id, "identifier");
    }
  • Added a condition to not quit the driver if there is a shared deviceSessionId capability in the driver. This ensures that the applications and browsers are not closed and the debugging can be continued in the manual session.

Upgrade steps

  1. Make the following changes in the pom.xml file:

    Copy

    pom.xml - Change the Quantum version

            <quantum.version>1.22.0</quantum.version>
  2. Add the following dependencies in pom.xml file for the log4j2 upgrade: 

    Copy

    pom.xml - Change the Quantum version

    <dependency>
        <groupId>com.quantum</groupId>
        <artifactId>quantum-support</artifactId>
        <version>${quantum.version}</version>
        <exclusions>
            <exclusion>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.7</version>
        <exclusions>
            <exclusion>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-api</artifactId>
        <version>2.13.3</version>
    </dependency>
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.13.3</version>
    </dependency>
  3. Remove the following dependencies after adding the above ones. There will be duplicates of quantum-supportslf4j-log4j12, and log4j dependencies.

    Copy

    pom.xml - Change the Quantum version

    <dependency>
        <groupId>com.quantum</groupId>
        <artifactId>quantum-support</artifactId>
        <version>${quantum.version}</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.7</version>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
  4. Add a new log4j2.properties file in the path src/main/resources/ with the following content:

    Copy

    log4j2.properties - Create new file with the following content

    #Specifying the logs level:- info or debug or error...ect
    rootLogger.level = info
    #Specifying different appender reference. In log4j2 configuring appenders is different. Refer Line numbers 4,5,6,7. 
    rootLogger.appenderRef.Console.ref = Console
    rootLogger.appenderRef.File.ref = File
    rootLogger.appenderRef.rolling.ref = RollingFile
    rootLogger.appenderRef.SCENARIOLOGFILE.ref = SCENARIOLOGFILE
     # Line number 4 to enable console logs. 
     #Line 5 to enable logs in "isfw.log" file.
     #Line 6 to enable logs in "ws.log" file.
     #Line 7 to enable logs in "scenario.log" file.
     
    #filter.threshold.type = ThresholdFilter
    #filter.threshold.level = debug
     
    appender.console.type = Console
    appender.console.name = Console
    appender.console.layout.type = PatternLayout
    appender.console.layout.pattern = %d{HH:mm:ss:SSS}: %.250m %n
    #appender.console.layout.pattern = %t: %.250m %n
    appender.console.filter.threshold.type = ThresholdFilter
    appender.console.filter.threshold.level = INFO

    property.outputDir=logs
    appender.file.type = File
    appender.file.name = File
    appender.file.filename = ${outputDir}/isfw.log
    appender.file.layout.type = PatternLayout
    appender.file.layout.pattern = [%t] %d{HH:mm:ss,SSS} %-5p [%c] %m%n
    appender.file.filter.threshold.type = ThresholdFilter
    appender.file.filter.threshold.level = INFO

    appender.SCENARIOLOGFILE.type = File
    appender.SCENARIOLOGFILE.name = SCENARIOLOGFILE
    appender.SCENARIOLOGFILE.filename = ${outputDir}/scenario.log
    appender.SCENARIOLOGFILE.layout.type = PatternLayout
    appender.SCENARIOLOGFILE.layout.pattern = %d{HH:mm:ss} %m%n
    appender.SCENARIOLOGFILE.filter.threshold.type = ThresholdFilter
    appender.SCENARIOLOGFILE.filter.threshold.level = DEBUG

    logger.rolling.appenderRef.rolling.ref = RollingFile
    appender.rolling.filePattern = ${outputDir}/test1-%d{MM-dd-yy}.log
    logger.rolling.name = test123
    appender.rolling.type = RollingFile
    appender.rolling.name = RollingFile
    logger.rolling.level = debug
    logger.rolling.additivity = true
    appender.rolling.fileName = ${outputDir}/ws.log
    appender.rolling.layout.type = PatternLayout
    appender.rolling.layout.pattern = %d{[dd.MM.yyyy] [HH:mm:ss]} %p [%t] %c (%F:%L) - %m%n
    appender.rolling.policies.type = Policies
    appender.rolling.policies.time.type = TimeBasedTriggeringPolicy
    appender.rolling.policies.time.interval = 2
    appender.rolling.policies.time.modulate = true
    appender.rolling.policies.size.type = SizeBasedTriggeringPolicy
    appender.rolling.policies.size.size=1GB
    appender.rolling.strategy.type = DefaultRolloverStrategy
    appender.rolling.strategy.max = 1