Running tests together with Appium / Selenium
AltTester® Unity SDK can be used together with different automation frameworks depending on your target platform:
Appium for mobile automation testing on Android and iOS devices
Selenium for WebGL-based builds running in web browsers
Both are open source projects that enable automated testing and can be used alongside AltTester® Unity SDK to handle platform-specific interactions and operations that AltTester® Unity SDK cannot perform directly.
Appium - Mobile Testing (Android & iOS)
When it comes to mobile automation testing, Appium is a popular choice for automating 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 which can be found here This project demonstrates a working setup and includes the necessary dependencies. More details about it below.
Why use Appium together with AltTester® Unity SDK
There’s a couple of reasons/scenarios for which you would want to use both of these frameworks:
By itself, AltTester® Unity 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® Unity 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® Unity SDK can’t.
AltTester® Unity SDK with Appium example
After you cloned our example project, there are a couple of things you need to check before running the tests:
For Android you need to have Android SDK version 16 or higher installed on your machine;
For iOS you need XCode with Command Line Tools installed (will only work on Mac OSX);
Your mobile device needs to have developer mode enabled and be connected via USB to the machine running the tests.
Running the tests
For Android you can just run the script
run-tests_android.shFor iOS, you first need to
export IOS_UDID=<your-device-udid>then run the scriptrun-tests_ios.sh
Note
To find out an iOS device UDID you can go to Finder, click the device in the sidebar and click the info under the device name to reveal the UDID
The script will install any requirements that are missing from your machine (except Android SDK and XCode CLT), then run a basic test scenario:
The app will be started by Appium;
AltTester® Unity SDK will ensure it’s initially loaded;
Appium will put the app in the background for a couple of seconds, then resume it;
AltTester® Unity SDK will check if the app was resumed successfully.
Note
Please observe the following about the setup method in base_test.py:
A minimum amount of capabilities have to be set in order for Appium to work. More details about capabilities can be found in the official Appium documentation
Starting with Selenium 4 the DesiredCapabilities are deprecated and the Webdriver now uses Options to pass capabilities, so if you’re using Selenium 4, in order for the example project to work, you may have to update the code with the new setup - see the Selenium documentation
The Appium driver needs to be created before the reverse port forwarding needed by AltTester® Unity SDK is done. This is because Appium clears any other reverse port forwarding when it starts.
Selenium - WebGL Testing
Selenium is a powerful automation framework for web browsers. When running AltTester® Unity SDK on WebGL builds, Selenium allows you to automate browser interactions and handle web-specific operations that complement AltTester® Unity SDK’s game automation capabilities.
Why use Selenium together with AltTester® Unity SDK
There are several scenarios where you would want to use Selenium with AltTester® Unity SDK for WebGL builds:
By itself, AltTester® Unity SDK cannot handle browser-specific operations such as opening new tabs, managing cookies, or interacting with browser dialogs;
Selenium provides robust browser management and can handle native browser popups and dialogs that AltTester® Unity SDK cannot interact with;
You can use Selenium to perform cross-browser testing of your WebGL builds on different browsers (Chrome, Firefox, Safari, Edge, etc.);
Selenium allows you to automate tasks before launching your WebGL application, such as logging in on a web page or navigating to a specific URL.
Connection settings popup for Appium / Selenium flows
When running AltTester® Unity SDK builds in the cloud and driving them with Appium or Selenium, 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® Unity 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 or Selenium: 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® Unity SDK. Enable this setting when you want the popup to appear (for example in cloud/Appium/Selenium 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 / Selenium
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
using AltTester.AltTesterSDK.Driver;
using OpenQA.Selenium.Chrome;
public class MyFirstTest
{
private static ChromeDriver driver;
private static AltDriver altDriver;
[OneTimeSetUp]
public void SetUp()
{
driver = new ChromeDriver();
driver.Navigate().GoToUrl("http://localhost:8000");
// Set connection data in the app
string appName = "__default__";
string altServerHost = "127.0.0.1";
string altServerPort = "13000";
SetConnectionData(altServerHost, altServerPort, appName);
// Initialize AltDriver
altDriver = new AltDriver(host: altServerHost, port: int.Parse(altServerPort), appName: appName);
}
private void SetConnectionData(string? host = null, string? port = null, string? appName = null, bool dontShowThisAgain = false, int implicitWaitTimeout = 60)
{
if (driver == null)
{
throw new ArgumentNullException(nameof(driver), "Selenium driver cannot be null");
}
// Set a longer implicit wait to ensure elements are found during connection setup
driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(implicitWaitTimeout);
try
{
// Update host if provided
if (host != null)
{
var hostField = driver.FindElement(OpenQA.Selenium.By.Id("AltTesterHostInputField"));
hostField.Clear();
hostField.SendKeys(host);
}
// Update port if provided
if (port != null)
{
var portField = driver.FindElement(OpenQA.Selenium.By.Id("AltTesterPortInputField"));
portField.Clear();
portField.SendKeys(port);
}
// Update app_name if provided
if (appName != null)
{
var appNameField = driver.FindElement(OpenQA.Selenium.By.Id("AltTesterAppNameInputField"));
appNameField.Clear();
appNameField.SendKeys(appName);
}
// Set "Don't show this again" if specified
if (dontShowThisAgain)
{
var dontShowAgainCheckbox = driver.FindElement(OpenQA.Selenium.By.Id("AltTesterDontShowAgainCheckbox"));
if (!dontShowAgainCheckbox.Selected)
{
dontShowAgainCheckbox.Click();
}
}
// Press OK button
var okButton = driver.FindElement(OpenQA.Selenium.By.Id("AltTesterOkButton"));
okButton.Click();
}
catch (Exception ex)
{
throw new Exception($"Error while setting connection data: {ex.Message}", ex);
}
}
[OneTimeTearDown]
public void TearDown()
{
altDriver?.Stop();
driver?.Dispose();
}
}
import com.alttester.AltDriver;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Assumptions;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.openqa.selenium.support.ui.ExpectedConditions;
import java.time.Duration;
public class MyFirstTest {
private static WebDriver driver;
private static AltDriver altDriver;
@BeforeAll
public static void setUp() {
try {
driver = new ChromeDriver();
driver.navigate().to("http://localhost:8000");
// Set connection data in the app
String appName = "__default__";
String altServerHost = "127.0.0.1";
String altServerPort = "13000";
setConnectionData(altServerHost, altServerPort, appName);
// Initialize AltDriver
altDriver = new AltDriver(altServerHost, Integer.parseInt(altServerPort));
} catch (Exception ex) {
Assumptions.assumeTrue(false, "Test environment not ready (ChromeDriver/AltTester): " + ex.getMessage());
}
}
public static void setConnectionData(String host, String port, String appName) {
setConnectionData(host, port, appName, false, 60);
}
public static void setConnectionData(String host, String port, String appName, boolean dontShowThisAgain, int implicitWaitTimeout) {
if (driver == null) {
throw new IllegalArgumentException("Selenium driver cannot be null");
}
// Set implicit wait to ensure elements are found during connection setup
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(implicitWaitTimeout));
try {
// Update host if provided
if (host != null) {
WebElement hostField = driver.findElement(By.id("AltTesterHostInputField"));
hostField.clear();
hostField.sendKeys(host);
}
// Update port if provided
if (port != null) {
WebElement portField = driver.findElement(By.id("AltTesterPortInputField"));
portField.clear();
portField.sendKeys(port);
}
// Update app_name if provided
if (appName != null) {
WebElement appNameField = driver.findElement(By.id("AltTesterAppNameInputField"));
appNameField.clear();
appNameField.sendKeys(appName);
}
// Set "Don't show this again" if specified
if (dontShowThisAgain) {
WebElement dontShowAgainCheckbox = driver.findElement(By.id("AltTesterDontShowAgainCheckbox"));
if (!dontShowAgainCheckbox.isSelected()) {
dontShowAgainCheckbox.click();
}
}
// Press OK button
WebElement okButton = driver.findElement(By.id("AltTesterOkButton"));
okButton.click();
} catch (Exception ex) {
throw new RuntimeException("Error while setting connection data: " + ex.getMessage(), ex);
}
}
@AfterAll
public static void tearDown() throws Exception {
if (altDriver != null) {
altDriver.stop();
}
if (driver != null) {
driver.quit();
}
}
}
import unittest
from alttester import AltDriver
from selenium import webdriver
from selenium.webdriver.common.by import By
class TestBase(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.driver = webdriver.Chrome()
cls.driver.get("http://localhost:8000")
# Set connection data in the app
app_name = "__default__"
altserver_host = "127.0.0.1"
altserver_port = "13000"
cls.set_connection_data(host=altserver_host,
port=altserver_port, app_name=app_name)
# Initialize AltDriver
cls.altdriver = AltDriver(host=altserver_host, port=int(
altserver_port), app_name=app_name)
@classmethod
def set_connection_data(cls, host=None, port=None, app_name=None, dont_show_this_again=False, implicit_wait_timeout=60):
if cls.driver is None:
raise ValueError("Selenium driver cannot be None")
# Set a longer implicit wait to ensure elements are found during connection setup
cls.driver.implicitly_wait(implicit_wait_timeout)
try:
# Update host if provided
if host is not None:
host_field = cls.driver.find_element(
by=By.ID, value="AltTesterHostInputField")
host_field.clear()
host_field.send_keys(host)
# Update port if provided
if port is not None:
port_field = cls.driver.find_element(
by=By.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.driver.find_element(
by=By.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.driver.find_element(
by=By.ID, value="AltTesterDontShowAgainCheckbox")
if not dont_show_again_checkbox.is_selected():
dont_show_again_checkbox.click()
# Press OK button
ok_button = cls.driver.find_element(
by=By.ID, value="AltTesterOkButton")
ok_button.click()
except Exception as ex:
raise Exception(
f"Error while setting connection data: {str(ex)}") from ex
@classmethod
def tearDownClass(cls):
if cls.altdriver:
cls.altdriver.stop()
if cls.driver:
cls.driver.quit()
*** Settings ***
Library AltTesterLibrary
Library SeleniumLibrary
Suite Setup Suite Setup Tests
Suite Teardown Stop Altdriver
*** Keywords ***
Suite Setup Tests
Open Browser http://localhost:8000/index.html chrome
Set Connection Data 127.0.0.1 13000 __default__ 60
Initialize Altdriver host=127.0.0.1 port=13000 app_name=__default__
Set Connection Data
[Arguments] ${host}=None ${port}=None ${app_name}=None ${dont_show_this_again}=False ${implicit_wait_timeout}=60
# Set implicit wait
Set Selenium Implicit Wait ${implicit_wait_timeout}s
# Update host if provided
Run Keyword If '${host}' != 'None'
... Input Text id:AltTesterHostInputField ${host}
# Update port if provided
Run Keyword If '${port}' != 'None'
... Input Text id:AltTesterPortInputField ${port}
# Update app_name if provided
Run Keyword If '${app_name}' != 'None'
... Input Text 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 id:AltTesterDontShowAgainCheckbox checked
Run Keyword If '${dont_show_this_again}' == 'True' and '${checkbox_selected}' != 'true' Click Element id:AltTesterDontShowAgainCheckbox
# Press OK button
Click Button id:AltTesterOkButton