Writing visual tests that use the Native Mobile Grid
In a previous article, we described how to write an Eyes visual test using the
ClassicRunner
.
When using the
ClassicRunner
,
the Eyes SDK uses real mobile device, Android emulator or iOS simulator to capture screen shots for the checkpoints.
The current article explains how to adapt this code so that it uses the Native Mobile Grid.
When using the Native Mobile Grid, the Eyes SDK captures a view hierarchy snapshot and other resources and sends it to the Grid server, where the checkpoint image is rendered concurrently for all relevant configurations, along with other checkpoints in your test suite.
Using the Native Mobile Grid considerably reduces test time and also enables you to test multiple mobile devices in a single test run. For a full description of the Native Mobile Grid and what it can do for you, see the article Introduction to the Native Mobile Grid.
Overview
The first step is typically done as part of the instrumentation process.
The instrumentation process is available for both iOS and Android platforms, to ease the integration of the test application with Applitools.
Applitools collects the application resources by instrumenting a dedicated library (“Grid library”) into the test application, in order to render the image across the other configured devices.
Specifying the use of the Native Mobile Grid
The Java snippet below illustrates how you can easily switch to using the Native Mobile Grid by assigning the runner an instance of a
VisualGridRunner
class instead of a
ClassicRunner
class.
public void beforeTestSuite() {
/* REMOVE
runner = new ClassicRunner();
*/
runner = new VisualGridRunner(new RunnerOptions().testConcurrency(10));
// continued below....
}
The type of runner you pass to the Eyes constructor when you create it for each test determines whether its checkpoints are rendered using the Native Mobile Grid or not.
The RunnerOptions
passed to the VisualGridRunner
constructor is used to configure the runner. The example shows the method $runnerOptions.testConcurrency being passed. This option is used to limit the maximum number of Eyes tests that the runner will run simultaneously.
Increasing the concurrency value can allow your test suite to run faster, but the maximum number of tests that can run simultaneously depends on your Eyes plan, if concurrency has been allocated at a team level, and what other tests are being run on your account at the same time.
Baselines
By default, when running tests using the Native Mobile Grid, every mobile device configuration uses the baseline implied by the execution environment of that configuration, i.e. the Device’s viewport size and its operating system:
- When adding a mobile device (e.g. using the Configuration.addMobileDevice method), the Device and Viewport size baseline attributes are defined by the parameters of the method. The Operating system attributes will depend on the device type.
Example
The code below shows a complete example of a test that runs using the Native Mobile Grid.
The examples are for Android and iOS platforms, based on Java Appium and Espresso with Kotlin for Android and XCUI with Swift for iOS.
The code examples below are for demonstration purposes only. In order to be able to execute tests with the Native Mobile Grid, additional prerequisites are required.
Android
Framework: Appium
Language: Java
Add following dependency to the pom.xml file
<dependency>
<groupId>com.applitools</groupId>
<artifactId>eyes-appium-java5</artifactId>
<version>5.1.0</version>
</dependency>
Write your test code:
import com.applitools.eyes.appium.Eyes;
import com.applitools.eyes.config.Configuration;
import com.applitools.eyes.visualgrid.model.AndroidDeviceInfo;
import com.applitools.eyes.visualgrid.model.AndroidDeviceName;
import com.applitools.eyes.visualgrid.model.DeviceAndroidVersion;
import com.applitools.eyes.visualgrid.model.ScreenOrientation;
import com.applitools.eyes.visualgrid.services.RunnerOptions;
import com.applitools.eyes.visualgrid.services.VisualGridRunner;
import io.appium.java_client.android.AndroidDriver;
import org.openqa.selenium.remote.DesiredCapabilities;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
public class BasicDemo {
public static void main(String[] args) throws
MalformedURLException {
VisualGridRunner visualGridRunner = new
VisualGridRunner(new RunnerOptions().testConcurrency(5));
Eyes eyes = new Eyes(visualGridRunner);
eyes.setApiKey("API_KEY_HERE");
// Configure grid devices to render on
Configuration configuration = eyes.getConfiguration();
configuration.addMobileDevice(new
AndroidDeviceInfo(AndroidDeviceName.Pixel_4));
configuration.addMobileDevice(new
AndroidDeviceInfo(AndroidDeviceName.Pixel_4_XL,
ScreenOrientation.LANDSCAPE));
configuration.addMobileDevice(new
AndroidDeviceInfo(AndroidDeviceName.Pixel_3_XL,
ScreenOrientation.LANDSCAPE, DeviceAndroidVersion.LATEST));
eyes.setConfiguration(configuration);
// Create an AndroidDriver instance to automate your app
AndroidDriver driver = startApp();
try {
// Visually validate the stickers dialog
eyes.open(driver, "My Android App", "Java Appium
Android UFG Demo");
eyes.check("Check", Target.window());
eyes.closeAsync();
} finally {
driver.quit();
eyes.abortAsync();
visualGridRunner.getAllTestResults();
}
}
private static AndroidDriver startApp() throws
MalformedURLException {
DesiredCapabilities capabilities = new
DesiredCapabilities();
capabilities.setCapability("platformName", "Android");
capabilities.setCapability("deviceName", "Pixel 2");
String appPath = new File("").getAbsolutePath() +
"/src/main/resources/app-debug.apk";
capabilities.setCapability("app", appPath);
capabilities.setCapability("platformVersion", "9");
capabilities.setCapability("automationName",
"UiAutomator2");
capabilities.setCapability("newCommandTimeout", 300);
capabilities.setCapability("fullReset", false);
capabilities.setCapability("noReset", true);
return new AndroidDriver<>(new
URL("http://localhost:4723/wd/hub"), capabilities);
}
}
Framework: Espresso
Language: Kotlin
package com.applitools.android.ufg.espresso
import androidx.test.core.app.ActivityScenario
Import com.applitools.android.ufg.espresso.ui.login.LoginActivity
import com.applitools.eyes.android.common.AndroidDeviceName
import com.applitools.eyes.android.common.logger.Logger
import
com.applitools.eyes.android.common.logger.StdoutLogHandler
import
com.applitools.eyes.android.components.androidx.AndroidXComponen
tsProvider
import com.applitools.eyes.android.espresso.Eyes
import com.applitools.eyes.android.espresso.fluent.Target
import
com.applitools.eyes.android.espresso.visualgrid.RunnerOptions
import
com.applitools.eyes.android.espresso.visualgrid.VisualGridRunner
import org.junit.Before
import org.junit.Test
class KotlinTest {
@Before
fun beforeTest() {
ActivityScenario.launch(LoginActivity::class.java)
}
@Test
fun testLogin() {
val logger = Logger()
logger.logHandler = StdoutLogHandler(true)
val options = RunnerOptions()
options.apiKey("<API_KEY_HERE>")
val runner = VisualGridRunner(options)
val eyes = Eyes(runner)
eyes.componentsProvider = AndroidXComponentsProvider()
eyes.configuration = eyes.configuration
.addMobileDevice(AndroidDeviceInfo(AndroidDeviceName.Pixel_4))
.addMobileDevice(
AndroidDeviceInfo(
AndroidDeviceName.Pixel_4_XL
)
)
.addMobileDevice(
AndroidDeviceInfo(
AndroidDeviceName.Pixel_3_XL
)
)
yes.logHandler = logger.logHandler
try {
eyes.open("AndroidX UFG test app", "Test Login page
UFG Kotlin")
eyes.check(Target.window().withName("Check"))
eyes.closeAsync()
} finally {
runner.allTestResults
eyes.abortIfNotClosed()
}
}
}
iOS
Framework: XCUI
Language: Swift
import XCTest
import EyesXCUI
class UFGDemoTests: XCTestCase {
static var visualGridRunner = UFGDemoTests.configureRunner()
static var eyes = UFGDemoTests.configureEyes(runner: visualGridRunner)
private static func configureRunner() -> VisualGridRunner {
let options =
RunnerOptions().testConcurrency(5).apiKey("API_KEY_HERE")
return VisualGridRunner(options: options)
}
private static func configureEyes(runner: VisualGridRunner) ->
Eyes {
let eyes = Eyes(runner: runner)
let config = Configuration()
let iPhone8 = IosDeviceInfo(deviceName: .deviceiPhone8)
let iPhoneProMax = IosDeviceInfo(deviceName:
.deviceiPhone12ProMax)
let iPad = IosDeviceInfo(deviceName:
.deviceiPadPro3rdgeneration)
config.addMobileDevices([iPhone8, iPhoneProMax, iPad])
eyes.configuration = config
return eyes
}
override class func setUp() {
let app =
XCUIApplication(bundleIdentifier:"com.mycompany.myapp")
app.launchEnvironment["UFG_LIB_FRAMEWORK"] = ""
app.launch()
visualGridRunner = UFGDemoTests.configureRunner()
eyes = UFGDemoTests.configureEyes(runner:
visualGridRunner)
eyes.testedApplication = app
eyes.open(withApplicationName: "My iOS App", testName:
"eyesXCUI UFG Demo")
}
func testMyCheck() throws {
Self.eyes.check(withTag: "Check", andSettings:
Target.window())
}
static override func tearDown() {
eyes.closeAsync()
let allTestResults =
visualGridRunner.getAllTestResultsShouldThrowException(false)
print(allTestResults)
}
}