Repository: alttester/AltTester-Unity-SDK (language: python)

Issue:
Add job to run tests on Android in Browserstack



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml
index e94613ae2..a8299339e 100644
--- a/.github/workflows/push.yml
+++ b/.github/workflows/push.yml
@@ -34,6 +34,7 @@ jobs:
             -'Docs/**'
           scenes:
             -'Assets/AltTester/Examples/Scenes/**'
+
   build-docs:
     runs-on: ubuntu-latest
     needs: paths-filter
@@ -68,18 +69,33 @@ jobs:
           export ALTSERVER_PORT=13020
           export ALTSERVER_HOST="127.0.0.1"
           $UNITY_2021_3_HOME -batchmode -stackTraceLogType None -projectPath $CI_PROJECT_DIR -executeMethod AltTesterTools.BuildAltTester.MacBuildFromCommandLine -logFile buildMac.log -quit
-      - name: Build SampleGame
+
+      - uses: actions/upload-artifact@v3
+        if: always() # run this step even if one of the previous step failed
+        with:
+          name: BuildMacArtifact
+          path: |
+            sampleGame.app
+            **/*.log
+
+  build-android:
+    runs-on: [self-hosted, macOS]
+    needs: paths-filter
+    steps:
+      - uses: actions/checkout@v3
+
+      - name: Build SampleGame apk
         run: |
           export ALTSERVER_PORT=13020
           export ALTSERVER_HOST="127.0.0.1"
-          $UNITY_2021_3_HOME -batchmode -stackTraceLogType None -projectPath $CI_PROJECT_DIR -executeMethod AltTesterTools.BuildAltTester.MacBuildFromCommandLine -logFile buildMac.log -quit
+          $UNITY_2021_3_HOME -batchmode -stackTraceLogType None -projectPath $CI_PROJECT_DIR -executeMethod AltTesterTools.BuildAltTester.AndroidBuildFromCommandLine -logFile buildAndroid.log -quit
 
       - uses: actions/upload-artifact@v3
         if: always() # run this step even if one of the previous step failed
         with:
-          name: BuildMacArtifact
+          name: BuildAndroidArtifact
           path: |
-            sampleGame.app
+            sampleGame.apk
             **/*.log
 
   test-CSharp-Editor:
@@ -112,7 +128,6 @@ jobs:
             **/*.log
             reportTest.xml
 
-
   test-DotNet:
     runs-on: [self-hosted, macOS]
     needs: build-macOS
@@ -148,19 +163,18 @@ jobs:
     steps:
       - uses: actions/checkout@v3
 
-      - uses: actions/download-artifact@v3
-        with:
-          name: BuildMacArtifact
       - uses: actions/download-artifact@v3
         with:
           name: BuildMacArtifact
 
       - name: Open application
         run: |
+          export RUN_ANDROID_IN_BROWSERSTACK="false"
           export ALTSERVER_PORT=13020
           export ALTSERVER_HOST="127.0.0.1"
           chmod -R 755 sampleGame.app
           open sampleGame.app
+
       - uses: actions/setup-python@v4
         with:
           python-version: "3.10"
@@ -227,6 +241,53 @@ jobs:
           name: TestJavaArtifact
           path: "**/*.log"
 
+  test-Python-Android-Browserstack:
+    runs-on: [self-hosted, macOS]
+    needs: build-android
+    if: needs.paths-filter.outputs.python == 'true' || needs.paths-filter.outputs.scenes == 'true'
+    steps:
+      - uses: actions/checkout@v3
+
+      - uses: actions/download-artifact@v3
+        with:
+          name: BuildAndroidArtifact
+
+      - name: Copy application to Python test folder
+        run: |
+          cp sampleGame.apk  Bindings~/python
+
+      - uses: actions/setup-python@v4
+        with:
+          python-version: "3.10"
+
+      - name: Install dependencies
+        run: |
+          pip install --upgrade pip setuptools wheel
+          pip uninstall AltTester-Driver
+          pip install -r "Bindings~/python/requirements.txt"
+          pip install -r "Bindings~/python/requirements-dev.txt"
+          pip install -e "Bindings~/python" --root "Bindings~/python"
+
+      - name: Run integration tests
+        run: |
+          export RUN_ANDROID_IN_BROWSERSTACK="true"
+          export BROWSERSTACK_USERNAME=${{ secrets.BROWSERSTACK_USERNAME }}
+          export BROWSERSTACK_KEY=${{ secrets.BROWSERSTACK_KEY }}
+          export ALTSERVER_PORT=13020
+          export ALTSERVER_HOST="127.0.0.1"
+          pytest -s Bindings~/python/tests/integration
+
+      - uses: actions/upload-artifact@v3
+        if: always() # run this step even if one of the previous step failed
+        with:
+          name: TestPythonAndroidInBrowserstackArtifact
+          path: "**/*.log"
+      
+      - name: Unset Browserstack Flag
+        if: always()
+        run: |
+          export RUN_ANDROID_IN_BROWSERSTACK="false"
+        
   deploy-dev-docs:
     runs-on: ubuntu-latest
     needs: build-docs
diff --git a/Assets/Editor/BuildAltTester.cs b/Assets/Editor/BuildAltTester.cs
index 64deb3940..e51b649fb 100644
--- a/Assets/Editor/BuildAltTester.cs
+++ b/Assets/Editor/BuildAltTester.cs
@@ -127,7 +127,7 @@ protected static void AndroidBuildFromCommandLine()
 
                     locationPathName = "sampleGame.apk",
                     target = BuildTarget.Android,
-                    options = BuildOptions.Development | BuildOptions.IncludeTestAssemblies | BuildOptions.AutoRunPlayer
+                    options = BuildOptions.Development | BuildOptions.IncludeTestAssemblies
                 };
 
 
diff --git a/Bindings~/python/requirements-dev.txt b/Bindings~/python/requirements-dev.txt
index a8dbff040..61fc36d09 100644
--- a/Bindings~/python/requirements-dev.txt
+++ b/Bindings~/python/requirements-dev.txt
@@ -10,3 +10,7 @@ flake8
 pytest
 pytest-cov
 pytest-repeat
+
+#browserstack
+browserstack-local
+Appium-Python-Client
\ No newline at end of file


Test patch:
diff --git a/Bindings~/python/tests/integration/conftest.py b/Bindings~/python/tests/integration/conftest.py
index 4ca0c28ae..c074deea5 100644
--- a/Bindings~/python/tests/integration/conftest.py
+++ b/Bindings~/python/tests/integration/conftest.py
@@ -1,6 +1,13 @@
 ﻿import os
 import pytest
+import requests
+import time
+import datetime
 from alttester import AltDriver
+from appium.options.android import UiAutomator2Options
+from browserstack.local import Local
+from appium import webdriver
+
 """ Copyright(C) 2023  Altom Consulting
 
     This program is free software: you can redistribute it and/or modify
@@ -31,16 +38,90 @@ def get_app_name():
     return os.environ.get("ALTSERVER_APP_NAME", "__default__")
 
 
+def get_browserstack_username():
+    return os.environ.get("BROWSERSTACK_USERNAME", "")
+
+
+def get_browserstack_key():
+    return os.environ.get("BROWSERSTACK_KEY", "")
+
+
 @pytest.fixture(scope="session")
-def altdriver():
+def altdriver(appium_driver):
     altdriver = AltDriver(
         host=get_host(),
         port=get_port(),
         app_name=get_app_name(),
         enable_logging=True,
-        timeout=60 * 5
+        timeout=60
     )
+    print("altdriver started")
 
     yield altdriver
 
     altdriver.stop()
+
+
+@pytest.fixture(scope="session")
+def appium_driver(request):
+    appium_driver = None
+
+    if os.environ.get("RUN_ANDROID_IN_BROWSERSTACK", "") == "true":
+        files = {
+            'file': ('sampleGame.apk', open('sampleGame.apk', 'rb')),
+        }
+
+        response = requests.post(
+            'https://api-cloud.browserstack.com/app-automate/upload',
+            files=files,
+            auth=(get_browserstack_username(), get_browserstack_key()))
+        try:
+            app_url = response.json()['app_url']
+        except Exception():
+            pytest.fail("Error uploading app to BrowserStack, response: "
+                        + str(response.text))
+
+        options = UiAutomator2Options().load_capabilities({
+            "platformName": "android",
+            "platformVersion": "12.0",
+            "deviceName": "Google Pixel 6",
+            "app": app_url,
+
+            # Set other BrowserStack capabilities
+            'bstack:options': {
+                "projectName": "AltTester",
+                "buildName": "alttester-pipeline-python-android",
+                "sessionName": 'tests-{date:%Y-%m-%d_%H:%M:%S}'
+                .format(date=datetime.datetime.now()),
+                "local": "true",
+                "wsLocalSupport": "true",
+                "deviceOrientation": "landscape",
+                "networkLogs": "true",
+                "userName": get_browserstack_username(),
+                "accessKey": get_browserstack_key()
+            }
+        })
+
+        bs_local = Local()
+        bs_local_args = {"key": get_browserstack_key(),
+                         "forcelocal": "true",
+                         "force": "true"}
+        bs_local.start(**bs_local_args)
+        appium_driver = webdriver.Remote("http://hub.browserstack.com/wd/hub",
+                                         options=options)
+        time.sleep(10)
+    yield appium_driver
+
+    if os.environ.get("RUN_ANDROID_IN_BROWSERSTACK", "") == "true":
+        appium_driver.quit()
+        bs_local.stop()
+
+
+@pytest.fixture(autouse=True)
+def do_something_with_appium(appium_driver):
+    if os.environ.get("RUN_ANDROID_IN_BROWSERSTACK", "") != "true":
+        return
+    # browserstack has an idle timeout of max 300 seconds
+    # so we need to do something with the appium driver
+    # to keep it alive
+    appium_driver.get_window_size()