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.Copyapplication.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.CopyTestNG 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.CopyDeviceUtils.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
tolog4j
version 2. The oldlog4j
version has security vulnerability warnings shared by Github bots. The changes to upgrade tolog4j 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.
CopyDeviceUtils.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));
}CopyPerfectoApplicationSteps.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:
CopyPerfectoApplicationSteps.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);
}CopyDeviceUtils.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.
-
Fixed a bug in the image injection step definition. The parameter name was incorrect.
CopyPerfectoApplicationsSteps.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
-
Make the following changes in the
pom.xml
file: -
Add the following dependencies in
pom.xml
file for thelog4j2
upgrade:Copypom.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> -
Remove the following dependencies after adding the above ones. There will be duplicates of
quantum-support
,slf4j-log4j12
, andlog4j
dependencies.Copypom.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> -
Add a new
log4j2.properties
file in the pathsrc/main/resources/
with the following content:Copylog4j2.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