Running tests together with Appium
When it comes to mobile automation testing, there are many different choices for a test tool. One popular option is Appium, an open source project which enables running automated tests on both Android and iOS devices.
No registration is needed and you can either download the latest version of the standalone app here, or you can install the cli version by running:
> brew install node # get node.js
> npm install -g appium # get appium
> npm install wd # get appium client
> appium & # start appium
We’ve created an example python project for AltTester® Unity SDK together with Appium, which can be found here. It can help you get started on your own projects and will automatically install the requirements needed for running the tests. More details about it below.
Why use Appium together with AltTester® Unreal SDK
There’s a couple of reasons/scenarios for which you would want to use both of these frameworks:
By itself, AltTester® Unreal SDK cannot launch an app on a device. If you want to run tests in a pipeline, or by using cloud services, you can either create a script which will start your app, or you can use Appium before the tests execution;
AltTester® Unreal SDK cannot perform some types of actions, such as interacting with any native popups your app might have, or putting the app in the background and resuming it. In any of these cases you can use Appium to do the things that AltTester® Unreal SDK can’t.
AltTester® Unreal SDK with Appium example
We currently provide an example project that demonstrates how to use AltTester® Unity SDK together with Appium. You can find it here: AltTester Unity SDK with Appium example.
Even though the project uses a Unity game, the Appium setup (capabilities, driver initialization and reverse port forwarding for AltTester®) is the same when testing Unreal builds that integrate AltTester® Unreal SDK. You can follow the instructions from the Unity example and apply the same configuration values (such as app package/bundle id, device UDID, etc.) for your Unreal application.
Connection settings popup for cloud/Appium flows
When running AltTester® Unreal SDK builds in the cloud and driving them with Appium, you might not know in advance which app name to use from your test scripts to connect to the correct instrumented instance. To address this, AltTester® Unreal SDK provides an optional native connection settings popup that can be shown in the app.
The popup allows you to:
Enter the AltTester® Server host;
Enter the AltTester® Server port;
Enter the app name that will be used by the tests when connecting;
Check “Don’t show this again” to prevent the popup from appearing on subsequent launches.
Because the popup is built using native UI elements, you can fully interact with it from Appium: locate the fields and buttons using your preferred locator strategy (for example accessibility id, xpath, or text), type the desired host, port and app name values, and confirm the dialog before starting your AltTester® tests.
Whether this popup is shown or not is controlled by a dedicated setting in AltTester® Unreal SDK. Enable this setting when you want the popup to appear (for example in cloud/Appium runs where connection details are provided dynamically), and disable it when you prefer to configure connection parameters directly in your game or test code without any additional UI.
Examples: identifying and interacting with the popup in Appium
Below is a simple example that shows how you can identify and fill in the connection settings popup with Appium before starting your AltTester® tests.
public static void SetConnectionData(AppiumDriver appiumDriver, string? host = null, string? port = null, string? appName = null, bool dontShowThisAgain = false, int timeout = 60)
{
if (appiumDriver == null)
{
throw new ArgumentNullException(nameof(appiumDriver), "Appium driver cannot be null");
}
// Set a longer implicit wait to ensure elements are found during connection setup
appiumDriver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(timeout);
try
{
// Update host if provided
if (!string.IsNullOrEmpty(host))
{
var hostField = appiumDriver.FindElement(MobileBy.AccessibilityId("AltTesterHostInputField"));
hostField.Clear();
hostField.SendKeys(host);
}
// Update port if provided
if (!string.IsNullOrEmpty(port))
{
var portField = appiumDriver.FindElement(MobileBy.AccessibilityId("AltTesterPortInputField"));
portField.Clear();
portField.SendKeys(port);
}
// Update app name if provided
if (!string.IsNullOrEmpty(appName))
{
var appNameField = appiumDriver.FindElement(MobileBy.AccessibilityId("AltTesterAppNameInputField"));
appNameField.Clear();
appNameField.SendKeys(appName);
}
// Set "Don't show this again" if specified
if (dontShowThisAgain)
{
var dontShowAgainCheckbox = appiumDriver.FindElement(MobileBy.AccessibilityId("AltTesterDontShowAgainCheckbox"));
if (!dontShowAgainCheckbox.Selected)
{
dontShowAgainCheckbox.Click();
}
}
// Press OK button
var okButton = appiumDriver.FindElement(MobileBy.AccessibilityId("AltTesterOkButton"));
okButton.Click();
}
catch (ArgumentException)
{
throw;
}
catch (Exception ex)
{
throw new AppiumHelperException($"Error while setting connection data: {ex.Message}", ex);
}
}
public static void setConnectionData(AppiumDriver appiumDriver,
String host,
String port,
String appName,
boolean dontShowThisAgain,
int implicitWaitTimeoutSeconds) {
if (appiumDriver == null) {
throw new IllegalArgumentException("Appium driver cannot be null");
}
// Set a longer implicit wait to ensure elements are found during connection setup
appiumDriver.manage().timeouts()
.implicitlyWait(Duration.ofSeconds(implicitWaitTimeoutSeconds));
try {
// Update host if provided
if (host != null && !host.isEmpty()) {
WebElement hostField = appiumDriver.findElement(AppiumBy.accessibilityId("AltTesterHostInputField"));
hostField.clear();
hostField.sendKeys(host);
}
// Update port if provided
if (port != null && !port.isEmpty()) {
WebElement portField = appiumDriver.findElement(AppiumBy.accessibilityId("AltTesterPortInputField"));
portField.clear();
portField.sendKeys(port);
}
// Update app name if provided
if (appName != null && !appName.isEmpty()) {
WebElement appNameField = appiumDriver.findElement(AppiumBy.accessibilityId("AltTesterAppNameInputField"));
appNameField.clear();
appNameField.sendKeys(appName);
}
// Set "Don't show this again" if specified
if (dontShowThisAgain) {
WebElement dontShowAgainCheckbox = appiumDriver.findElement(AppiumBy.accessibilityId("AltTesterDontShowAgainCheckbox"));
if (!dontShowAgainCheckbox.isSelected()) {
dontShowAgainCheckbox.click();
}
}
// Press OK button
WebElement okButton = appiumDriver.findElement(AppiumBy.accessibilityId("AltTesterOkButton"));
okButton.click();
} catch (Exception ex) {
throw new RuntimeException("Error while setting connection data: " + ex.getMessage(), ex);
}
}
def set_connection_data(cls, host=None, port=None, app_name=None, dont_show_this_again=False, implicit_wait_timeout=60):
if cls.appium_driver is None:
raise ValueError("Appium driver cannot be None")
# Set a longer implicit wait to ensure elements are found during connection setup
cls.appium_driver.implicitly_wait(implicit_wait_timeout)
try:
# Update host if provided
if host is not None:
host_field = cls.appium_driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value="AltTesterHostInputField")
host_field.clear()
host_field.send_keys(host)
# Update port if provided
if port is not None:
port_field = cls.appium_driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value="AltTesterPortInputField")
port_field.clear()
port_field.send_keys(port)
# Update app_name if provided
if app_name is not None:
app_name_field = cls.appium_driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value="AltTesterAppNameInputField")
app_name_field.clear()
app_name_field.send_keys(app_name)
# Set "Don't show this again" if specified
if dont_show_this_again:
dont_show_again_checkbox = cls.appium_driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value="AltTesterDontShowAgainCheckbox")
if not dont_show_again_checkbox.is_selected():
dont_show_again_checkbox.click()
# Press OK button
ok_button = cls.appium_driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value="AltTesterOkButton")
ok_button.click()
except Exception as ex:
raise Exception(f"Error while setting connection data: {str(ex)}") from ex
*** Settings ***
Library AppiumLibrary
*** Keywords ***
Set Connection Data
[Arguments] ${host}= ${port}= ${app_name}= ${dont_show_this_again}=False ${implicit_wait_timeout}=60
# Wait to ensure elements are found during connection setup
Wait Until Page Contains Element accessibility_id=AltTesterHostInputField timeout=${implicit_wait_timeout}
# Update host if provided
Run Keyword If '${host}' != '' Clear Text accessibility_id=AltTesterHostInputField
Run Keyword If '${host}' != '' Input Text accessibility_id=AltTesterHostInputField ${host}
# Update port if provided
Run Keyword If '${port}' != '' Clear Text accessibility_id=AltTesterPortInputField
Run Keyword If '${port}' != '' Input Text accessibility_id=AltTesterPortInputField ${port}
# Update app name if provided
Run Keyword If '${app_name}' != '' Clear Text accessibility_id=AltTesterAppNameInputField
Run Keyword If '${app_name}' != '' Input Text accessibility_id=AltTesterAppNameInputField ${app_name}
# Set "Don't show this again" if specified
${checkbox_selected}= Run Keyword If '${dont_show_this_again}' == 'True' Get Element Attribute accessibility_id=AltTesterDontShowAgainCheckbox checked
Run Keyword If '${dont_show_this_again}' == 'True' and '${checkbox_selected}' != 'true' Click Element accessibility_id=AltTesterDontShowAgainCheckbox
# Press OK button
Click Element accessibility_id=AltTesterOkButton