Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions Framework/MainDriverApi.py
Original file line number Diff line number Diff line change
Expand Up @@ -2018,6 +2018,14 @@ async def main(device_dict, all_run_id_info):

final_dependency = run_id_info["dependency_list"]
shared.Set_Shared_Variables("dependency", final_dependency, protected=True)
shared.Set_Shared_Variables(
"mobile_execution",
run_id_info.get(
"mobile_execution",
{"target": "auto", "headless": False, "avd": "auto"},
),
protected=True,
)

final_run_params_from_server = run_id_info["run_time"]
final_run_params = {}
Expand Down
126 changes: 80 additions & 46 deletions Framework/Utilities/All_Device_Info.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,70 +3,100 @@
# -*- coding: cp1252 -*-


import sys, subprocess, time
from Framework.Utilities import CommonUtil
import json
import shutil
import subprocess
import sys

from Framework.install_handler.android.android_sdk import get_adb_path

# These create the device name we give to each device
device_name = "device "
device_cnt = 1


def _get_adb_executable():
"""Prefer the ZeuZ-managed adb and fall back to a native adb on PATH."""
managed_adb = get_adb_path()
if managed_adb.is_file():
return str(managed_adb)
return shutil.which("adb")


def _adb_output(adb_executable, *args, timeout=30):
return subprocess.check_output(
[adb_executable, *args],
text=True,
encoding="utf-8",
errors="replace",
timeout=timeout,
)


def get_all_connected_android_info():
""" For all connected Android devices, get specified information and return in a dictionary with the serial number as the top level key """
"""For all connected Android devices, get specified information and return in a dictionary with the serial number as the top level key"""

global device_cnt
try:
android_list = []
device_list = {}

# adb commands
android_cmd_os_version = "shell getprop ro.build.version.release"
android_cmd_model = "shell getprop ro.product.model"
android_cmd_name = "shell getprop ro.product.name"
android_cmd_mfg = "shell getprop ro.product.manufacturer"
android_cmd_imei = "shell dumpsys iphonesubinfo"
android_cmd_alt_imei = "shell service call iphonesubinfo 1" # Usually needed for CDMA phones
adb_executable = _get_adb_executable()
if not adb_executable:
return {}

# Get list of devices
# One report of this blocking the next line, so disabled: result = subprocess.check_output('adb kill-server', shell=True) # Stop adb server, to ensure this works properly
subprocess.check_output("adb start-server", shell=True, encoding="utf-8")
result = subprocess.check_output("adb devices", shell=True, encoding="utf-8")
result = result.replace("List of devices attached", "")
result = result.replace("\r", "")
result = result.replace("\t", " ")
result = result.split("\n")
for device in result:
if "device" in device:
android_list.append(str(device.split(" ")[0]).strip())
_adb_output(adb_executable, "start-server")
result = _adb_output(adb_executable, "devices")
for line in result.replace("\r", "").splitlines():
fields = line.split()
if len(fields) >= 2 and fields[1] == "device":
android_list.append(fields[0])

if len(android_list) == 0:
return False
# Get device information
for serial in android_list:
# Execute commands
os_version = subprocess.check_output(
"adb -s %s %s" % (serial, android_cmd_os_version),
shell=True,
encoding="utf-8",
os_version = _adb_output(
adb_executable,
"-s",
serial,
"shell",
"getprop",
"ro.build.version.release",
)
model = subprocess.check_output(
"adb -s %s %s" % (serial, android_cmd_model),
shell=True,
encoding="utf-8",
model = _adb_output(
adb_executable,
"-s",
serial,
"shell",
"getprop",
"ro.product.model",
)
name = subprocess.check_output(
"adb -s %s %s" % (serial, android_cmd_name),
shell=True,
encoding="utf-8",
name = _adb_output(
adb_executable,
"-s",
serial,
"shell",
"getprop",
"ro.product.name",
)
mfg = subprocess.check_output(
"adb -s %s %s" % (serial, android_cmd_mfg), shell=True, encoding="utf-8"
mfg = _adb_output(
adb_executable,
"-s",
serial,
"shell",
"getprop",
"ro.product.manufacturer",
)
imei = subprocess.check_output(
"adb -s %s %s" % (serial, android_cmd_imei),
shell=True,
encoding="utf-8",
imei = _adb_output(
adb_executable,
"-s",
serial,
"shell",
"dumpsys",
"iphonesubinfo",
)

# Cleanup of results
Expand All @@ -81,10 +111,15 @@ def get_all_connected_android_info():
imei.split(" ")[-1]
).strip() # Last word on this line is IMEI
else: # Try alternative method to get imei
imei = subprocess.check_output(
"adb -s %s %s" % (serial, android_cmd_alt_imei),
shell=True,
encoding="utf-8",
imei = _adb_output(
adb_executable,
"-s",
serial,
"shell",
"service",
"call",
"iphonesubinfo",
"1",
)
imei = imei.split(" ")
final = ""
Expand Down Expand Up @@ -120,7 +155,7 @@ def get_all_connected_android_info():


def get_all_connected_ios_info():
""" For all connected IOS devices, get specified information and return in a dictionary with the UUID as the top level key """
"""For all connected IOS devices, get specified information and return in a dictionary with the UUID as the top level key"""

global device_cnt
try:
Expand Down Expand Up @@ -184,10 +219,9 @@ def get_all_connected_ios_info():


def get_all_booted_ios_simulator_info():
""" For all booted simulator IOS devices, get specified information and return in a dictionary with the UUID as the top level key """
"""For all booted simulator IOS devices, get specified information and return in a dictionary with the UUID as the top level key"""
global device_cnt
try:

device_list = {}
all_ios_simulators = subprocess.check_output(
"xcrun simctl list --json", shell=True, encoding="utf-8"
Expand Down Expand Up @@ -230,7 +264,7 @@ def get_all_booted_ios_simulator_info():


def get_all_connected_device_info():
""" For all connected IOS and Android devices, get specified information and return in a dictionary with the serial number/UUID as the top level key """
"""For all connected IOS and Android devices, get specified information and return in a dictionary with the serial number/UUID as the top level key"""

try:
device_list = {}
Expand Down
18 changes: 12 additions & 6 deletions Framework/Utilities/CommonUtil.py
Original file line number Diff line number Diff line change
Expand Up @@ -885,12 +885,18 @@ async def TakeScreenShot(function_name, local_run=False, pre_action=False):
Method = screen_capture_type
Driver = screen_capture_driver

# The driver for web/mobile may not exist yet when the BEFORE frame is requested
# (e.g. Go_To_Link launches the browser, so no selenium_driver exists until the
# action runs). A missing driver for a pre-action capture is expected, not an
# error, so skip it silently instead of announcing the capture and then logging a
# level-3 error from Thread_ScreenShot.
if pre_action and Method in ("mobile", "web") and Driver is None:
# The driver for web/mobile may not exist around a launch action. Before the
# action this is expected; after a failed launch, the action already logged the
# useful root cause. Do not add a second level-3 screenshot error for a driver
# that was never created.
if Method in ("mobile", "web") and Driver is None:
if not pre_action:
ExecLog(
sModuleInfo,
"Skipping screenshot because the %s driver is not available"
% Method,
0,
)
return

# Decide if screenshot should be captured
Expand Down
36 changes: 35 additions & 1 deletion Framework/deploy_handler/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,35 @@
# from pb.v1.deploy_response_message_pb2 import DeployResponse


def normalize_mobile_execution(value: str) -> tuple[str, Dict[str, Any]]:
"""Separate the mobile platform from its local execution target."""
raw_value = (value or "").strip()
normalized = raw_value.lower().replace("_", " ").replace("-", " ")
normalized = " ".join(normalized.split())

if normalized in {"android headless", "android emulator headless"}:
return "Android", {
"target": "emulator",
"headless": True,
"avd": "auto",
}
if normalized == "android":
return "Android", {
"target": "auto",
"headless": False,
"avd": "auto",
}
if normalized == "ios simulator":
return "iOS", {
"target": "simulator",
"headless": False,
}
return raw_value, {
"target": "device",
"headless": False,
}


def read_actions(actions_pb) -> List[Dict]:
actions = []
for action in actions_pb:
Expand Down Expand Up @@ -96,6 +125,9 @@ def adapt(message: str, node_id: str) -> List[Dict]:
"""

r = json.loads(message)
mobile_platform, mobile_execution = normalize_mobile_execution(
r["deployInfo"]["dependency"]["mobile"]
)

# TODO: Check r.server_version and create different adapeter classes for new
# schemaa changes.
Expand All @@ -113,9 +145,11 @@ def adapt(message: str, node_id: str) -> List[Dict]:

"dependency_list": {
"Browser": r["deployInfo"]["dependency"]["browser"],
"Mobile": r["deployInfo"]["dependency"]["mobile"],
"Mobile": mobile_platform,
},

"mobile_execution": mobile_execution,

"device_info": None,

"run_time": {},
Expand Down
22 changes: 19 additions & 3 deletions Framework/install_handler/android/appium.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ async def check_status() -> bool:
# Use the check function from nodejs_appium_installer
node_installed, appium_installed, missing_drivers = check_installations()

if appium_installed:
if appium_installed and not missing_drivers:
# Get the installation location
appium_path = get_appium_path()
node_dir = get_node_dir()
Expand Down Expand Up @@ -51,6 +51,22 @@ async def check_status() -> bool:
}
})
return True
elif appium_installed:
missing_text = ", ".join(missing_drivers)
logger.warning(
"[installer][android-appium] Missing required drivers: %s",
missing_text,
)
await send_response({
"action": "status",
"data": {
"category": "Android",
"name": "Appium",
"status": "not installed",
"comment": f"Appium is installed, but required drivers are missing: {missing_text}",
}
})
return False
else:
logger.info("[installer][android-appium] Not installed")
await send_response({
Expand All @@ -71,7 +87,7 @@ async def check_status() -> bool:
"category": "Android",
"name": "Appium",
"status": "not installed",
"comment": "Run the ZeuZ Node, it will automatically install Appium",
"comment": f"Unable to verify Appium: {str(e)[:160]}",
}
})
return False
Expand Down Expand Up @@ -230,4 +246,4 @@ async def install() -> bool:
"comment": f"Installation error: {str(e)[:100]}",
}
})
return False
return False
Loading
Loading