AltTester-Driver 2.3.3__tar.gz → 2.4.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/AltTester_Driver.egg-info/PKG-INFO +3 -1
  2. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/AltTester_Driver.egg-info/SOURCES.txt +3 -0
  3. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/PKG-INFO +3 -1
  4. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/__version__.py +1 -1
  5. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/_websocket.py +2 -2
  6. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/altdriver.py +17 -4
  7. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/setup.py +0 -1
  8. alttester_driver-2.4.1/tests/appium/conftest.py +113 -0
  9. alttester_driver-2.4.1/tests/appium/test_native_popup.py +106 -0
  10. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/tests/integration/conftest.py +7 -8
  11. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/tests/integration/test_driver.py +2 -2
  12. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/tests/integration/test_scene01_part2.py +1 -0
  13. alttester_driver-2.4.1/tests/unit/__init__.py +3 -0
  14. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/tests/unit/test_websocket.py +48 -0
  15. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/AltTester_Driver.egg-info/dependency_links.txt +0 -0
  16. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/AltTester_Driver.egg-info/not-zip-safe +0 -0
  17. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/AltTester_Driver.egg-info/requires.txt +0 -0
  18. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/AltTester_Driver.egg-info/top_level.txt +0 -0
  19. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/LICENSE +0 -0
  20. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/MANIFEST.in +0 -0
  21. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/README.md +0 -0
  22. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/__init__.py +0 -0
  23. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/altobject.py +0 -0
  24. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/by.py +0 -0
  25. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/AltTesterCommands/__init__.py +0 -0
  26. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/AltTesterCommands/add_notification_listener.py +0 -0
  27. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/AltTesterCommands/remove_notification_listener.py +0 -0
  28. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/AltTesterCommands/reset_input.py +0 -0
  29. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/AltTesterCommands/set_server_logging.py +0 -0
  30. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/FindObjects/__init__.py +0 -0
  31. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/FindObjects/find_object.py +0 -0
  32. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/FindObjects/find_object_at_coordinates.py +0 -0
  33. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/FindObjects/find_object_which_contains.py +0 -0
  34. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/FindObjects/find_objects.py +0 -0
  35. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/FindObjects/find_objects_which_contains.py +0 -0
  36. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/FindObjects/wait_for_component_property.py +0 -0
  37. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/FindObjects/wait_for_object.py +0 -0
  38. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/FindObjects/wait_for_object_to_not_be_present.py +0 -0
  39. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/FindObjects/wait_for_object_which_contains.py +0 -0
  40. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/FindObjects/wait_for_visual_element_property.py +0 -0
  41. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/InputActions/__init__.py +0 -0
  42. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/InputActions/begin_touch.py +0 -0
  43. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/InputActions/click_coordinates.py +0 -0
  44. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/InputActions/end_touch.py +0 -0
  45. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/InputActions/keys_down.py +0 -0
  46. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/InputActions/keys_up.py +0 -0
  47. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/InputActions/move_mouse.py +0 -0
  48. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/InputActions/move_touch.py +0 -0
  49. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/InputActions/multi_point_swipe.py +0 -0
  50. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/InputActions/press_keys.py +0 -0
  51. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/InputActions/scroll_mouse.py +0 -0
  52. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/InputActions/swipe.py +0 -0
  53. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/InputActions/tap_coordinates.py +0 -0
  54. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/InputActions/tilt.py +0 -0
  55. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/Notifications/__init__.py +0 -0
  56. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/Notifications/base_notification_callbacks.py +0 -0
  57. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/Notifications/load_scene_mode.py +0 -0
  58. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/Notifications/load_scene_notification_result.py +0 -0
  59. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/Notifications/log_notification_result.py +0 -0
  60. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/Notifications/notification_type.py +0 -0
  61. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/ObjectCommands/__init__.py +0 -0
  62. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/ObjectCommands/call_method.py +0 -0
  63. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/ObjectCommands/click_element.py +0 -0
  64. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/ObjectCommands/fetch_text.py +0 -0
  65. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/ObjectCommands/find_object_from_object.py +0 -0
  66. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/ObjectCommands/get_all_components.py +0 -0
  67. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/ObjectCommands/get_component_property.py +0 -0
  68. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/ObjectCommands/pointer_down.py +0 -0
  69. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/ObjectCommands/pointer_enter.py +0 -0
  70. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/ObjectCommands/pointer_exit.py +0 -0
  71. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/ObjectCommands/pointer_up.py +0 -0
  72. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/ObjectCommands/set_component_property.py +0 -0
  73. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/ObjectCommands/tap_element.py +0 -0
  74. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/ObjectCommands/update_text.py +0 -0
  75. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/UnityCommands/__init__.py +0 -0
  76. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/UnityCommands/delete_player_pref.py +0 -0
  77. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/UnityCommands/delete_player_pref_key.py +0 -0
  78. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/UnityCommands/get_all_loaded_scenes.py +0 -0
  79. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/UnityCommands/get_current_scene.py +0 -0
  80. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/UnityCommands/get_player_pref_key.py +0 -0
  81. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/UnityCommands/get_time_scale.py +0 -0
  82. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/UnityCommands/get_visual_element_property.py +0 -0
  83. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/UnityCommands/load_scene.py +0 -0
  84. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/UnityCommands/set_player_pref_key.py +0 -0
  85. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/UnityCommands/set_time_scale.py +0 -0
  86. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/UnityCommands/unload_scene.py +0 -0
  87. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/UnityCommands/wait_for_current_scene_to_be.py +0 -0
  88. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/__init__.py +0 -0
  89. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/base_command.py +0 -0
  90. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/get_png_screenshot.py +0 -0
  91. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/get_server_version.py +0 -0
  92. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/get_static_property.py +0 -0
  93. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/commands/set_static_property.py +0 -0
  94. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/exceptions.py +0 -0
  95. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/keycode.py +0 -0
  96. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/logging.py +0 -0
  97. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/playerpref.py +0 -0
  98. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/alttester/reverse_port_forwarding.py +0 -0
  99. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/requirements-dev.txt +0 -0
  100. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/requirements.txt +0 -0
  101. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/setup.cfg +0 -0
  102. {alttester_driver-2.3.3/tests/integration → alttester_driver-2.4.1/tests/appium}/__init__.py +0 -0
  103. {alttester_driver-2.3.3/tests/unit → alttester_driver-2.4.1/tests/integration}/__init__.py +0 -0
  104. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/tests/integration/test_notifications.py +0 -0
  105. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/tests/integration/test_reverse_port_forwarding.py +0 -0
  106. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/tests/integration/test_scene01_part1.py +0 -0
  107. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/tests/integration/test_scene02.py +0 -0
  108. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/tests/integration/test_scene03.py +0 -0
  109. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/tests/integration/test_scene05.py +0 -0
  110. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/tests/integration/test_scene07.py +0 -0
  111. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/tests/integration/test_scene09.py +0 -0
  112. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/tests/integration/test_scene10.py +0 -0
  113. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/tests/integration/utils.py +0 -0
  114. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/tests/unit/test_altobject.py +0 -0
  115. {alttester_driver-2.3.3 → alttester_driver-2.4.1}/tests/unit/test_commands.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: AltTester-Driver
3
- Version: 2.3.3
3
+ Version: 2.4.1
4
4
  Summary: Python bindings for the AltTester® framework. AltTester® is an UI driven test automation tool that helps you find objects in your game and interacts with them.
5
5
  Home-page: https://alttester.com/docs/sdk/latest/
6
6
  Author: Altom Consulting
@@ -35,6 +35,7 @@ Classifier: Topic :: Software Development :: Testing :: Acceptance
35
35
  Classifier: Topic :: Software Development :: Testing :: Unit
36
36
  Requires-Python: >=3.4.0
37
37
  Description-Content-Type: text/markdown
38
+ License-File: LICENSE
38
39
  Requires-Dist: loguru
39
40
  Requires-Dist: deprecated
40
41
  Requires-Dist: websocket-client
@@ -47,6 +48,7 @@ Dynamic: description-content-type
47
48
  Dynamic: home-page
48
49
  Dynamic: keywords
49
50
  Dynamic: license
51
+ Dynamic: license-file
50
52
  Dynamic: project-url
51
53
  Dynamic: requires-dist
52
54
  Dynamic: requires-python
@@ -90,6 +90,9 @@ alttester/commands/UnityCommands/set_player_pref_key.py
90
90
  alttester/commands/UnityCommands/set_time_scale.py
91
91
  alttester/commands/UnityCommands/unload_scene.py
92
92
  alttester/commands/UnityCommands/wait_for_current_scene_to_be.py
93
+ tests/appium/__init__.py
94
+ tests/appium/conftest.py
95
+ tests/appium/test_native_popup.py
93
96
  tests/integration/__init__.py
94
97
  tests/integration/conftest.py
95
98
  tests/integration/test_driver.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: AltTester-Driver
3
- Version: 2.3.3
3
+ Version: 2.4.1
4
4
  Summary: Python bindings for the AltTester® framework. AltTester® is an UI driven test automation tool that helps you find objects in your game and interacts with them.
5
5
  Home-page: https://alttester.com/docs/sdk/latest/
6
6
  Author: Altom Consulting
@@ -35,6 +35,7 @@ Classifier: Topic :: Software Development :: Testing :: Acceptance
35
35
  Classifier: Topic :: Software Development :: Testing :: Unit
36
36
  Requires-Python: >=3.4.0
37
37
  Description-Content-Type: text/markdown
38
+ License-File: LICENSE
38
39
  Requires-Dist: loguru
39
40
  Requires-Dist: deprecated
40
41
  Requires-Dist: websocket-client
@@ -47,6 +48,7 @@ Dynamic: description-content-type
47
48
  Dynamic: home-page
48
49
  Dynamic: keywords
49
50
  Dynamic: license
51
+ Dynamic: license-file
50
52
  Dynamic: project-url
51
53
  Dynamic: requires-dist
52
54
  Dynamic: requires-python
@@ -2,4 +2,4 @@
2
2
  Copyright(C) 2026 Altom Consulting
3
3
  """
4
4
 
5
- VERSION = "2.3.3"
5
+ VERSION = "2.4.1"
@@ -84,8 +84,8 @@ class NotificationHandler:
84
84
  def add_notification_listener(self, notification_type, callback, overwrite=False):
85
85
  if overwrite:
86
86
  self._notification_callbacks[notification_type] = [callback]
87
-
88
- self._notification_callbacks[notification_type].append(callback)
87
+ else:
88
+ self._notification_callbacks[notification_type].append(callback)
89
89
 
90
90
  def remove_notification_listener(self, notification_type):
91
91
  self._notification_callbacks[notification_type].clear()
@@ -33,6 +33,7 @@ class AltDriver:
33
33
  enable_logging (:obj:`bool`, optional): If set to ``True`` will turn on logging, by default logging is disabled.
34
34
  timeout (:obj:`int`, :obj:`float`, optional): The timeout duration for establishing a connection, in seconds.
35
35
  If set to ``None``, the connection attempt will wait indefinitely. The default value is ``60`` seconds.
36
+ driver_type (:obj:`str`, optional): The driver type reported to the AltTester® Server. Defaults to ``python``.
36
37
 
37
38
  """
38
39
 
@@ -47,7 +48,8 @@ class AltDriver:
47
48
  platform_version="unknown",
48
49
  device_instance_id="unknown",
49
50
  app_id="unknown",
50
- secure_mode=False
51
+ secure_mode=False,
52
+ driver_type="python"
51
53
  ):
52
54
  self.host = host
53
55
  self.port = port
@@ -59,6 +61,7 @@ class AltDriver:
59
61
  self.device_instance_id = device_instance_id
60
62
  self.app_id = app_id
61
63
  self.secure_mode = secure_mode
64
+ self.driver_type = driver_type
62
65
 
63
66
  self._config_logging(self.enable_logging)
64
67
 
@@ -83,13 +86,23 @@ class AltDriver:
83
86
  "platformVersion": self.platform_version,
84
87
  "deviceInstanceId": self.device_instance_id,
85
88
  "appId": self.app_id,
86
- "driverType": "python_" + VERSION
89
+ "driverType": self.driver_type + "_" + VERSION
87
90
  },
88
91
  command_handler=self._command_handler,
89
92
  notification_handler=self._notification_handler
90
93
  )
91
- self._connection.connect()
92
- self._check_server_version()
94
+ try:
95
+ self._connection.connect()
96
+ self._check_server_version()
97
+ except BaseException:
98
+ self._close_after_failed_connect()
99
+ raise
100
+
101
+ def _close_after_failed_connect(self):
102
+ try:
103
+ self._connection.close()
104
+ except Exception as ex:
105
+ logger.debug("Failed to close the connection after a failed connect: {}", ex)
93
106
 
94
107
  def __repr__(self):
95
108
  return "{}({!r}, {!r}, {!r}, {!r}, {!r})".format(
@@ -53,7 +53,6 @@ setup(
53
53
  install_requires=REQUIRED,
54
54
  packages=find_packages(exclude=['tests']),
55
55
  include_package_data=True,
56
- license_files=[],
57
56
 
58
57
  classifiers=[
59
58
  "Development Status :: 5 - Production/Stable",
@@ -0,0 +1,113 @@
1
+ """
2
+ Copyright(C) 2026 Altom Consulting
3
+ """
4
+
5
+ import datetime
6
+ import os
7
+ import time
8
+ import pytest
9
+ from appium import webdriver
10
+ from appium.options.android import UiAutomator2Options
11
+ from appium.options.ios import XCUITestOptions
12
+
13
+
14
+ saucelabs_devices = [
15
+ {"name": "Samsung Galaxy S23.*", "os": "android", "os_version": "13"},
16
+ {"name": "Samsung Galaxy S22.*", "os": "android", "os_version": "12"},
17
+ {"name": "iPhone 16.*", "os": "ios", "os_version": "18"},
18
+ {"name": "iPhone 15.*", "os": "ios", "os_version": "17"},
19
+ {"name": "iPhone 14.*", "os": "ios", "os_version": "16"},
20
+ {"name": "OnePlus 9.*", "os": "android", "os_version": "11"},
21
+ ]
22
+
23
+ saucelabs_android_devices = [d for d in saucelabs_devices if d["os"] == "android"]
24
+ saucelabs_ios_devices = [d for d in saucelabs_devices if d["os"] == "ios"]
25
+
26
+
27
+ def get_saucelabs_username():
28
+ return os.environ.get("SAUCE_USERNAME", "")
29
+
30
+
31
+ def get_saucelabs_access_key():
32
+ return os.environ.get("SAUCE_ACCESS_KEY", "")
33
+
34
+
35
+ def get_saucelabs_region():
36
+ return os.environ.get("SAUCE_REGION", "us-west-1")
37
+
38
+
39
+ def get_saucelabs_tunnel_name():
40
+ return os.environ.get("SAUCE_TUNNEL_NAME", "alttester-tunnel")
41
+
42
+
43
+ def get_saucelabs_app_url(device):
44
+ if device["os"] == "android":
45
+ return os.environ.get("SAUCE_APP_URL_ANDROID", "")
46
+ return os.environ.get("SAUCE_APP_URL_IOS", "")
47
+
48
+
49
+ def get_saucelabs_capabilities(device):
50
+ return {
51
+ "platformName": "Android" if device["os"] == "android" else "iOS",
52
+ "appium:deviceName": device["name"],
53
+ "appium:platformVersion": device["os_version"],
54
+ "appium:app": get_saucelabs_app_url(device),
55
+ "appium:automationName": "UiAutomator2" if device["os"] == "android" else "XCUITest",
56
+ "appium:newCommandTimeout": 2000,
57
+ "sauce:options": {
58
+ "username": get_saucelabs_username(),
59
+ "accessKey": get_saucelabs_access_key(),
60
+ "build": "AltTester SDK - Native Popup",
61
+ "name": "native-popup-{date:%Y-%m-%d_%H:%M:%S}".format(date=datetime.datetime.now()),
62
+ "tunnelName": get_saucelabs_tunnel_name(),
63
+ "tunnelOwner": get_saucelabs_username(),
64
+ "appiumVersion": "latest",
65
+ "idleTimeout": 300,
66
+ }
67
+ }
68
+
69
+
70
+ def create_appium_driver(device):
71
+ caps = get_saucelabs_capabilities(device)
72
+ region = get_saucelabs_region()
73
+ hub_url = "https://ondemand.{}.saucelabs.com:443/wd/hub".format(region)
74
+ if device["os"] == "android":
75
+ options = UiAutomator2Options().load_capabilities(caps)
76
+ else:
77
+ options = XCUITestOptions().load_capabilities(caps)
78
+ driver = webdriver.Remote(hub_url, options=options)
79
+ time.sleep(10)
80
+ return driver
81
+
82
+
83
+ @pytest.fixture(autouse=True, scope="session")
84
+ def worker_id(request):
85
+ request.config.getoption("--dist", default="no", skip=True)
86
+ if hasattr(request.config, "workerinput"):
87
+ return request.config.workerinput["workerid"]
88
+ return "master"
89
+
90
+
91
+ @pytest.fixture(autouse=True, scope="session")
92
+ def current_device(worker_id):
93
+ if os.environ.get("RUN_IN_SAUCELABS_ANDROID_ONLY", "") == "true":
94
+ selected_devices = saucelabs_android_devices
95
+ elif os.environ.get("RUN_IN_SAUCELABS_IOS_ONLY", "") == "true":
96
+ selected_devices = saucelabs_ios_devices
97
+ else:
98
+ selected_devices = saucelabs_devices
99
+
100
+ if worker_id == "master":
101
+ return selected_devices[0]
102
+ index = int(worker_id.split("gw")[1])
103
+ return selected_devices[index % len(selected_devices)]
104
+
105
+
106
+ @pytest.fixture(scope="class")
107
+ def appium_driver(current_device):
108
+ driver = create_appium_driver(current_device)
109
+ yield driver
110
+ try:
111
+ driver.quit()
112
+ except Exception:
113
+ pass
@@ -0,0 +1,106 @@
1
+ """
2
+ Copyright(C) 2026 Altom Consulting
3
+
4
+ Tests that verify the AltTester native connection settings popup appears
5
+ and can be interacted with via Appium on iOS and Android.
6
+ """
7
+
8
+ import pytest
9
+ from appium.webdriver.common.appiumby import AppiumBy
10
+
11
+
12
+ POPUP_ELEMENTS = [
13
+ "AltTesterHostInputField",
14
+ "AltTesterPortInputField",
15
+ "AltTesterAppNameInputField",
16
+ "AltTesterDontShowAgainCheckbox",
17
+ "AltTesterOkButton",
18
+ ]
19
+
20
+ HOST_PLACEHOLDER = "Host"
21
+ PORT_PLACEHOLDER = "Port"
22
+ APP_NAME_PLACEHOLDER = "App Name"
23
+ IMPLICIT_WAIT_TIMEOUT = 60
24
+
25
+
26
+ class TestNativePopup:
27
+
28
+ @pytest.fixture(autouse=True)
29
+ def setup(self, appium_driver, current_device):
30
+ self.driver = appium_driver
31
+ self.device = current_device
32
+ self.driver.implicitly_wait(IMPLICIT_WAIT_TIMEOUT)
33
+
34
+ def _find(self, accessibility_id):
35
+ return self.driver.find_element(
36
+ by=AppiumBy.ACCESSIBILITY_ID, value=accessibility_id
37
+ )
38
+
39
+ def test_popup_appears_on_launch(self):
40
+ """Verify the connection settings popup is displayed when the app launches."""
41
+ for element_id in POPUP_ELEMENTS:
42
+ element = self._find(element_id)
43
+ assert element.is_displayed(), \
44
+ "Expected '{}' to be visible but it was not".format(element_id)
45
+
46
+ def test_popup_host_field_starts_empty(self):
47
+ """Verify the host input field starts empty, showing its placeholder text."""
48
+ host_field = self._find("AltTesterHostInputField")
49
+ assert host_field.text == HOST_PLACEHOLDER, \
50
+ "Expected placeholder '{}', got '{}'".format(HOST_PLACEHOLDER, host_field.text)
51
+
52
+ def test_popup_port_field_starts_empty(self):
53
+ """Verify the port input field starts empty, showing its placeholder text."""
54
+ port_field = self._find("AltTesterPortInputField")
55
+ assert port_field.text == PORT_PLACEHOLDER, \
56
+ "Expected placeholder '{}', got '{}'".format(PORT_PLACEHOLDER, port_field.text)
57
+
58
+ def test_popup_app_name_field_starts_empty(self):
59
+ """Verify the app name input field starts empty, showing its placeholder text."""
60
+ app_name_field = self._find("AltTesterAppNameInputField")
61
+ assert app_name_field.text == APP_NAME_PLACEHOLDER, \
62
+ "Expected placeholder '{}', got '{}'".format(APP_NAME_PLACEHOLDER, app_name_field.text)
63
+
64
+ def test_popup_fields_are_editable(self):
65
+ """Verify host, port, and app name fields accept user input."""
66
+ host_field = self._find("AltTesterHostInputField")
67
+ host_field.clear()
68
+ host_field.send_keys("192.168.1.1")
69
+ assert host_field.text == "192.168.1.1"
70
+
71
+ port_field = self._find("AltTesterPortInputField")
72
+ port_field.clear()
73
+ port_field.send_keys("13001")
74
+ assert port_field.text == "13001"
75
+
76
+ app_name_field = self._find("AltTesterAppNameInputField")
77
+ app_name_field.clear()
78
+ app_name_field.send_keys("MyApp")
79
+ assert app_name_field.text == "MyApp"
80
+
81
+ def _is_checked(self):
82
+ # Android exposes "selected" and "checked" as distinct accessibility
83
+ # properties; a CheckBox only ever updates "checked" on toggle, so
84
+ # is_selected() (which reads "selected") never reflects its state.
85
+ return self._find("AltTesterDontShowAgainCheckbox").get_attribute("checked") == "true"
86
+
87
+ def test_dont_show_again_checkbox_is_toggleable(self):
88
+ """Verify the 'Don't show this again' checkbox can be toggled."""
89
+ initial_state = self._is_checked()
90
+ self._find("AltTesterDontShowAgainCheckbox").click()
91
+ assert self._is_checked() != initial_state, \
92
+ "Checkbox state did not change after click"
93
+ # restore
94
+ self._find("AltTesterDontShowAgainCheckbox").click()
95
+ assert self._is_checked() == initial_state
96
+
97
+ def test_popup_dismisses_on_ok(self):
98
+ """Verify the popup disappears after tapping OK."""
99
+ ok_button = self._find("AltTesterOkButton")
100
+ ok_button.click()
101
+
102
+ self.driver.implicitly_wait(5)
103
+ elements = self.driver.find_elements(
104
+ by=AppiumBy.ACCESSIBILITY_ID, value="AltTesterOkButton"
105
+ )
106
+ assert len(elements) == 0, "Popup was still visible after tapping OK"
@@ -76,6 +76,10 @@ def get_app_name():
76
76
  return os.environ.get("ALTSERVER_APP_NAME", "__default__")
77
77
 
78
78
 
79
+ def get_platform_version():
80
+ return os.environ.get("ALTSERVER_PLATFORM_VERSION", "unknown")
81
+
82
+
79
83
  def get_browserstack_username():
80
84
  return os.environ.get("BROWSERSTACK_USERNAME", "")
81
85
 
@@ -143,7 +147,9 @@ def alt_driver(request, appium_driver, worker_id, current_device):
143
147
  port=get_port(),
144
148
  app_name=get_app_name(),
145
149
  platform=platform,
146
- platform_version=current_device["os_version"].split(".")[0],
150
+ platform_version=get_platform_version()
151
+ if current_device["os_version"] == "unknown"
152
+ else current_device["os_version"].split(".")[0],
147
153
  timeout=180
148
154
  )
149
155
  if alt_driver is None:
@@ -189,13 +195,6 @@ def log_to_report(message):
189
195
 
190
196
  @pytest.fixture(autouse=True, scope="session")
191
197
  def current_device(request, worker_id):
192
- global devices
193
- global local_run_device
194
- global android_devices
195
- global ios_devices
196
- global saucelabs_devices
197
- global saucelabs_android_devices
198
- global saucelabs_ios_devices
199
198
  current_device = None
200
199
  selected_devices = []
201
200
  if os.environ.get("RUN_IN_SAUCELABS", "") == "true":
@@ -91,7 +91,7 @@ class TestDriver:
91
91
  @pytest.mark.WebGLUnsupported
92
92
  def test_set_server_logging(self):
93
93
  rule = self.alt_driver.call_static_method(
94
- "AltTester.AltTesterUnitySDK.Logging.ServerLogManager",
94
+ "AltTester.AltTesterUnitySDK.Logging.AltTesterLogManager",
95
95
  "Instance.Configuration.FindRuleByName",
96
96
  "Assembly-CSharp",
97
97
  parameters=["AltServerFileRule"],
@@ -102,7 +102,7 @@ class TestDriver:
102
102
 
103
103
  self.alt_driver.set_server_logging(AltLogger.File, AltLogLevel.Off)
104
104
  rule = self.alt_driver.call_static_method(
105
- "AltTester.AltTesterUnitySDK.Logging.ServerLogManager",
105
+ "AltTester.AltTesterUnitySDK.Logging.AltTesterLogManager",
106
106
  "Instance.Configuration.FindRuleByName",
107
107
  "Assembly-CSharp",
108
108
  parameters=["AltServerFileRule"],
@@ -401,6 +401,7 @@ class TestScene01Part2:
401
401
  assert type(plane.worldY) is float
402
402
  assert type(plane.worldZ) is float
403
403
 
404
+ @pytest.mark.skip(reason="Flaky: tilt's server-side duration isn't reliably honored in CI")
404
405
  def test_set_command_response_timeout(self):
405
406
  self.alt_driver.set_command_response_timeout(1)
406
407
  with pytest.raises(exceptions.CommandResponseTimeoutException) as execinfo:
@@ -0,0 +1,3 @@
1
+ """
2
+ Copyright(C) 2026 Altom Consulting
3
+ """
@@ -9,6 +9,7 @@ import pytest
9
9
 
10
10
  from alttester._websocket import Store, WebsocketConnection, CommandHandler, NotificationHandler
11
11
  from alttester.exceptions import ConnectionError
12
+ from alttester.commands.Notifications.notification_type import NotificationType
12
13
 
13
14
 
14
15
  class TestStore:
@@ -182,3 +183,50 @@ class TestWebsocketConnection:
182
183
 
183
184
  assert self.connection._command_handler.get_current_command() != (
184
185
  command["messageId"], command["commandName"])
186
+
187
+
188
+ class TestNotificationHandler:
189
+
190
+ @pytest.fixture(autouse=True)
191
+ def setup(self):
192
+ self.notification_handler = NotificationHandler()
193
+ self.paused_message = {
194
+ "commandName": "applicationPausedNotification",
195
+ "data": json.dumps(True)
196
+ }
197
+
198
+ def test_add_notification_listener_with_overwrite(self):
199
+ first_callback = mock.Mock()
200
+ second_callback = mock.Mock()
201
+
202
+ self.notification_handler.add_notification_listener(
203
+ NotificationType.APPLICATION_PAUSED, first_callback, overwrite=True)
204
+ self.notification_handler.add_notification_listener(
205
+ NotificationType.APPLICATION_PAUSED, second_callback, overwrite=True)
206
+ self.notification_handler.handle_notification(self.paused_message)
207
+
208
+ first_callback.assert_not_called()
209
+ second_callback.assert_called_once_with(True)
210
+
211
+ def test_add_notification_listener_without_overwrite(self):
212
+ first_callback = mock.Mock()
213
+ second_callback = mock.Mock()
214
+
215
+ self.notification_handler.add_notification_listener(
216
+ NotificationType.APPLICATION_PAUSED, first_callback, overwrite=False)
217
+ self.notification_handler.add_notification_listener(
218
+ NotificationType.APPLICATION_PAUSED, second_callback, overwrite=False)
219
+ self.notification_handler.handle_notification(self.paused_message)
220
+
221
+ first_callback.assert_called_once_with(True)
222
+ second_callback.assert_called_once_with(True)
223
+
224
+ def test_remove_notification_listener(self):
225
+ callback = mock.Mock()
226
+
227
+ self.notification_handler.add_notification_listener(
228
+ NotificationType.APPLICATION_PAUSED, callback, overwrite=True)
229
+ self.notification_handler.remove_notification_listener(NotificationType.APPLICATION_PAUSED)
230
+ self.notification_handler.handle_notification(self.paused_message)
231
+
232
+ callback.assert_not_called()