Class-ify the device under test methods
Issue: INFRA-230
Change-Id: I61c92b422de649e737900d4df313e22ea48ed282
diff --git a/deploy.py b/deploy.py
index a0a0b98..0ac8ecd 100755
--- a/deploy.py
+++ b/deploy.py
@@ -1,13 +1,12 @@
#!/usr/bin/env python3
-import os
import pathlib
import re
import subprocess
import sys
import time
-from uiautomator import Device
+import uiautomator
VWS_CREDENTIALS = {"user": "fairphonetesting@gmail.com", "password": "aish3echi:uwaiSh"}
@@ -136,111 +135,7 @@
return ret
-def force_awake(serial, always=True):
- """Force the device to stay awake.
-
- Raises:
- DeviceCommandError: If the underlying adb command failed.
- """
- adb(
- "shell",
- "svc power stayon {}".format("true" if always else "false"),
- serial=serial,
- )
-
-
-def unlock(dut):
- """Wake-up the device and unlock it.
-
- Raises:
- DeviceCommandError: If the underlying adb commands failed.
- """
- if not dut.info["screenOn"]:
- adb("shell", "input keyevent KEYCODE_POWER", serial=dut.serial)
- time.sleep(1)
- # The KEYCODE_MENU input is enough to unlock a "swipe up to unlock"
- # lockscreen on Android 6, but unfortunately not Android 7. So we use a
- # swipe up (that depends on the screen resolution) instead.
- adb("shell", "input touchscreen swipe 930 880 930 380", serial=dut.serial)
- time.sleep(1)
- adb("shell", "input keyevent KEYCODE_HOME", serial=dut.serial)
-
-
-def getprop(serial, key):
- """Get system property of device.
-
- Example:
- >>> getprop('167eb6e8', 'ro.build.id')
- 'FP2-gms-18.02.0'
-
- :param str serial:
- Identifier for ADB connection to device.
- :param str key:
- Key of property to get.
- :returns str:
- Value of system property.
- :raise DeviceCommandError: If the underlying adb command failed.
- """
- process = adb("shell", "getprop", key, serial=serial)
- return process.stdout.strip()
-
-
-def is_gms_device(serial):
- """Test if device runs GMS or sibon.
-
- Example:
- >>> is_gms_device('167eb6e8')
- True
-
- :param str serial:
- Identifier for ADB connection to device.
- :returns bool:
- True if device runs GMS, false otherwise.
- :raise DeviceCommandError: If the underlying adb command failed.
- """
- return getprop(serial, "ro.build.id").startswith("FP2-gms-") or getprop(
- serial, "ro.build.version.incremental"
- ).startswith("gms-")
-
-
-def uninstall_apk(serial, filename, prebuilts_dir):
- """Uninstall apk from prebuilts_dir on device.
-
- Raises:
- ValueError: If the package name could not be read from the apk.
- DeviceCommandError: If the uninstall command failed.
- """
- ret = aapt("dump", "badging", "{}/{}".format(prebuilts_dir, filename))
- package = None
- for line in ret.stdout.splitlines():
- if line.startswith("package"):
- for token in line.split(" "):
- if token.startswith("name="):
- # Extract the package name out of the token
- # (name='some.package.name')
- package = token[6:-1]
- break
- if not package:
- raise ValueError("Could not find package of app `{}`".format(filename))
-
- adb("uninstall", package, serial=serial)
-
-
-def install_apk(dut, filename, prebuilts_dir):
- """Install apk from prebuilts_dir on device.
-
- Raises:
- DeviceCommandError: If the install command failed.
- """
- path = os.path.join(prebuilts_dir, filename)
- command = ["install", "-r"]
- if dut.sdk >= 23:
- # From Marshmallow onwards, adb has a flag to grant default permissions
- command.append("-g")
- adb(*command, path, serial=dut.serial)
-
-
-def configure_wifi_networks(dut, networks):
+def configure_wifi_networks(device, networks):
"""Configure Wi-Fi networks.
The `networks` parameters is a list of networks to configure hashed by
@@ -251,34 +146,34 @@
'WPA/WPA2 PSK'.
Parameters:
- dut (Device): The device object.
+ device: The device object.
networks (dict(dict(str))): The list of networks to configure.
Raises:
DeviceCommandError: If the UI automation fails.
"""
# Open the Wi-Fi settings
- adb(
- "shell",
- "am start -a android.settings.WIFI_SETTINGS --activity-clear-task",
- serial=dut.serial,
+ device.adb(
+ "shell", "am start -a android.settings.WIFI_SETTINGS --activity-clear-task"
)
# Make sure Wi-Fi is enabled
- wifi_enabler = dut(text="OFF", resourceId="com.android.settings:id/switch_widget")
+ wifi_enabler = device.ui(
+ text="OFF", resourceId="com.android.settings:id/switch_widget"
+ )
if wifi_enabler.exists:
wifi_enabler.click()
# Check for registered networks
registered_networks = set()
- dut(description="More options").click.wait()
+ device.ui(description="More options").click.wait()
time.sleep(1)
- saved_networks = dut(text="Saved networks")
+ saved_networks = device.ui(text="Saved networks")
if saved_networks.exists:
saved_networks.click.wait()
for ssid in networks.keys():
- if dut(text=ssid).exists:
+ if device.ui(text=ssid).exists:
registered_networks.add(ssid)
- dut.press.back()
+ device.ui.press.back()
missing_networks = networks.keys() - registered_networks
@@ -288,50 +183,55 @@
for ssid in missing_networks:
print("Configuring `{}` Wi-Fi network…".format(ssid))
- dut(description="More options").click.wait()
- dut(text="Add network").click.wait()
- dut(resourceId="com.android.settings:id/ssid").set_text(ssid)
- dut(resourceId="com.android.settings:id/security").click()
- dut(text=networks[ssid]["security"]).click()
- password_field = dut(resourceId="com.android.settings:id/password")
+ device.ui(description="More options").click.wait()
+ device.ui(text="Add network").click.wait()
+ device.ui(resourceId="com.android.settings:id/ssid").set_text(ssid)
+ device.ui(resourceId="com.android.settings:id/security").click()
+ device.ui(text=networks[ssid]["security"]).click()
+ password_field = device.ui(resourceId="com.android.settings:id/password")
time.sleep(1)
if "password" in networks[ssid] and networks[ssid]["password"]:
if not password_field.exists:
- dut(text="Cancel").click()
- raise DeviceCommandError(dut, "UI: add Wi-Fi", "missing password field")
+ device.ui(text="Cancel").click()
+ raise DeviceCommandError(
+ device.ui, "UI: add Wi-Fi", "missing password field"
+ )
password_field.set_text(networks[ssid]["password"])
elif password_field.exists:
- dut(text="Cancel").click()
- raise DeviceCommandError(dut, "UI: add Wi-Fi", "missing password data")
- save_button = dut(text="Save")
+ device.ui(text="Cancel").click()
+ raise DeviceCommandError(
+ device.ui, "UI: add Wi-Fi", "missing password data"
+ )
+ save_button = device.ui(text="Save")
if not save_button.click():
- dut(text="Cancel").click()
- raise DeviceCommandError(dut, "UI: add Wi-Fi", "could not save network")
+ device.ui(text="Cancel").click()
+ raise DeviceCommandError(
+ device.ui, "UI: add Wi-Fi", "could not save network"
+ )
# Force the Wi-Fi on and off to pick the best network available
- dut(text="ON", resourceId="com.android.settings:id/switch_widget").click()
- dut(text="OFF", resourceId="com.android.settings:id/switch_widget").click()
+ device.ui(text="ON", resourceId="com.android.settings:id/switch_widget").click()
+ device.ui(text="OFF", resourceId="com.android.settings:id/switch_widget").click()
# Leave the settings
- dut.press.back()
+ device.ui.press.back()
-def disable_privacy_impact_popup(dut):
+def disable_privacy_impact_popup(device):
"""Disable Privacy Impact popup on Android 5.
This simplifies UI automation. Disabling the feature globally is more robust
than clicking through the the Privacy Impact screen per app.
"""
print("Disabling the Privacy Impact screen…")
- adb(
+ device.adb(
"shell",
(
"am start -a android.intent.action.MAIN "
"com.fairphone.privacyimpact/.PrivacyImpactPreferenceActivity"
),
- serial=dut.serial,
)
- disable_privacy_impact_checkbox = dut(className="android.widget.CheckBox")
+ disable_privacy_impact_checkbox = device.ui(className="android.widget.CheckBox")
if not disable_privacy_impact_checkbox.checked:
disable_privacy_impact_checkbox.click()
@@ -344,88 +244,179 @@
# Prepare the DUT
-def prepare_dut(dut, scenarios_dir, data_dir, prebuilts_dir):
- flavour = "gms" if is_gms_device(dut.serial) else "sibon"
- proxy_apk = get_proxy_apk(dut.sdk, flavour)
+def prepare_dut(device, scenarios_dir, data_dir, prebuilts_dir):
+ flavour = "gms" if device.is_gms_device() else "sibon"
+ proxy_apk = get_proxy_apk(device.sdk, flavour)
+ prebuilts_dir = pathlib.Path(prebuilts_dir)
# Uninstall the smartviser apps
for app in PREBUILT_APKS + [proxy_apk]:
print("Uninstalling `{}`…".format(app))
- uninstall_apk(dut.serial, app, prebuilts_dir)
+ device.uninstall(prebuilts_dir / app)
# Copy the scenarios
print("Pushing scenarios from `{}`…".format(scenarios_dir))
- adb("push", scenarios_dir, "/sdcard/viser", serial=dut.serial)
+ device.adb("push", scenarios_dir, "/sdcard/viser")
# Copy the scenarios data
print("Pushing scenarios data from `{}`…".format(data_dir))
- adb("push", data_dir, "/sdcard/viser/data", serial=dut.serial)
+ device.adb("push", data_dir, "/sdcard/viser/data")
# Install the smartviser apps (starting with the proxy app)
for app in [proxy_apk] + PREBUILT_APKS:
print("Installing `{}`…".format(app))
- install_apk(dut, app, prebuilts_dir)
+ device.install(prebuilts_dir / app)
# Grant the permissions through the UI
-def configure_perms(dut):
+def configure_perms(device):
# Input the credentials
- dut(resourceId="android:id/content").child(text="Username").child(
+ device.ui(resourceId="android:id/content").child(text="Username").child(
className="android.widget.EditText"
).set_text(VWS_CREDENTIALS["user"])
- dut(resourceId="android:id/content").child(text="Password").child(
+ device.ui(resourceId="android:id/content").child(text="Password").child(
className="android.widget.EditText"
).set_text(VWS_CREDENTIALS["password"])
# Sign in
- signin_label = "SIGN IN" if dut.sdk >= 24 else "Sign in"
- dut(resourceId="android:id/content").child(
+ signin_label = "SIGN IN" if device.sdk >= 24 else "Sign in"
+ device.ui(resourceId="android:id/content").child(
text=signin_label, className="android.widget.Button"
).click()
-def configure_sms(dut):
- # TODO wait for the connection to be established and time-out
- prompt = dut(resourceId="android:id/content").child(
- text="Viser must be your SMS app to send messages"
- )
- while not prompt.exists:
- time.sleep(1)
-
- # Make viser the default SMS app
- dut(resourceId="android:id/content").child_by_text(
- "Viser must be your SMS app to send messages",
- className="android.widget.LinearLayout",
- ).child(text="OK", className="android.widget.Button").click()
-
- dut(resourceId="android:id/content").child_by_text(
- "Change SMS app?", className="android.widget.LinearLayout"
- ).child(text="Yes", className="android.widget.Button").click()
-
-
-def configure_settings(dut):
+def configure_settings(device):
# Set the e-mail account
- dut(text="Settings", className="android.widget.TextView").click()
- dut(resourceId="android:id/list").child_by_text(
+ device.ui(text="Settings", className="android.widget.TextView").click()
+ device.ui(resourceId="android:id/list").child_by_text(
"User settings", className="android.widget.LinearLayout"
).click()
- dut(resourceId="android:id/list").child_by_text(
+ device.ui(resourceId="android:id/list").child_by_text(
"Email account", className="android.widget.LinearLayout"
).click()
- prompt = dut(resourceId="android:id/content").child_by_text(
+ prompt = device.ui(resourceId="android:id/content").child_by_text(
"Email account", className="android.widget.LinearLayout"
)
prompt.child(resourceId="android:id/edit").set_text("fairphone.viser@gmail.com")
prompt.child(text="OK", className="android.widget.Button").click()
- dut(resourceId="android:id/list").child_by_text(
+ device.ui(resourceId="android:id/list").child_by_text(
"Email password", className="android.widget.LinearLayout"
).click()
- dut(text="Password :").child(className="android.widget.EditText").set_text(
+ device.ui(text="Password :").child(className="android.widget.EditText").set_text(
"fairphoneviser2017"
)
- dut(description="OK", className="android.widget.TextView").click()
- dut.press.back()
- dut.press.back()
+ device.ui(description="OK", className="android.widget.TextView").click()
+ device.ui.press.back()
+ device.ui.press.back()
+
+
+class DeviceUnderTest:
+ """An Android device under test."""
+
+ serial: str
+ """The device serial number (adb/fastboot)."""
+ sdk: int
+ """The Android SDK version number."""
+ ui: uiautomator.Device
+ """The UI Automator handle piloting the device."""
+
+ def __init__(self, serial: str):
+ self.serial = serial
+ self.ui = uiautomator.Device(serial)
+
+ # Cache the Android SDK version
+ self.sdk = self.ui.info["sdkInt"]
+
+ def adb(self, *args) -> subprocess.CompletedProcess:
+ """Execute an adb command on this device.
+
+ :returns: The completed process.
+ :raise DeviceCommandError: If the underlying adb command failed.
+ """
+ return adb(*args, serial=self.serial)
+
+ def force_awake(self, always=True) -> None:
+ """Force the device to stay awake.
+
+ :raise DeviceCommandError: If the underlying adb command failed.
+ """
+ self.adb("shell", "svc power stayon {}".format("true" if always else "false"))
+
+ def unlock(self) -> None:
+ """Wake-up the device and unlock it.
+
+ :raise DeviceCommandError: If the underlying adb commands failed.
+ """
+ if not self.ui.info["screenOn"]:
+ self.adb("shell", "input keyevent KEYCODE_POWER")
+ time.sleep(1)
+ # The KEYCODE_MENU input is enough to unlock a "swipe up to unlock"
+ # lockscreen on Android 6, but unfortunately not Android 7. So we use a
+ # swipe up (that depends on the screen resolution) instead.
+ self.adb("shell", "input touchscreen swipe 930 880 930 380")
+ time.sleep(1)
+ self.adb("shell", "input keyevent KEYCODE_HOME")
+
+ def getprop(self, key: str) -> str:
+ """Get a system property.
+
+ Example:
+ >>> self.getprop('ro.build.id')
+ 'FP2-gms-18.02.0'
+
+ :param key: Key of property to get.
+ :returns: Value of system property.
+ :raise DeviceCommandError: If the underlying adb command failed.
+ """
+ process = self.adb("shell", "getprop", key)
+ return process.stdout.strip()
+
+ def is_gms_device(self) -> bool:
+ """Whether the device runs GMS or sibon.
+
+ Example:
+ >>> self.is_gms_device()
+ True
+
+ :returns: True if device runs GMS, false otherwise.
+ :raise DeviceCommandError: If the underlying adb command failed.
+ """
+ return self.getprop("ro.build.id").startswith("FP2-gms-") or self.getprop(
+ "ro.build.version.incremental"
+ ).startswith("gms-")
+
+ def uninstall(self, apk: pathlib.Path) -> None:
+ """Uninstall an app from an APK file.
+
+ :raise ValueError: If the package name could not be read from the apk.
+ :raise DeviceCommandError: If the uninstall command failed.
+ """
+ ret = aapt("dump", "badging", str(apk))
+ package = None
+ for line in ret.stdout.splitlines():
+ if line.startswith("package"):
+ for token in line.split(" "):
+ if token.startswith("name="):
+ # Extract the package name out of the token
+ # (name='some.package.name')
+ package = token[6:-1]
+ break
+ if not package:
+ raise ValueError("Could not find package of app `{}`".format(apk.name))
+
+ self.adb("uninstall", package)
+
+ def install(self, apk: pathlib.Path) -> None:
+ """Install an app from an APK file.
+
+ :raise DeviceCommandError: If the install command failed.
+ """
+ command = ["install", "-r"]
+ if self.sdk >= 23:
+ # From Marshmallow onwards, adb has a flag to grant default permissions
+ command.append("-g")
+
+ self.adb(*command, str(apk))
def deploy():
@@ -439,35 +430,30 @@
for serial in serials:
print("Configuring device {}…".format(serial))
- dut = Device(serial)
- # Work around the not-so-easy Device class
- dut.serial = serial
- # Cache the Android SDK version (dut.info fetches system properties)
- dut.sdk = dut.info["sdkInt"]
-
+ device = DeviceUnderTest(serial)
try:
# Make sure the screen stays on - we're going to use UI automation
- force_awake(serial)
- unlock(dut)
+ device.force_awake()
+ device.unlock()
# Configure common Fairphone Wi-Fi networks
- if dut.sdk < 24:
- configure_wifi_networks(dut, FAIRPHONE_WIFI_NETWORKS)
+ if device.sdk < 24:
+ configure_wifi_networks(device, FAIRPHONE_WIFI_NETWORKS)
else:
print(
"Uh oh, the device is running Android SDK {} on which we "
- "do not deploy Wi-Fi networks yet.".format(dut.sdk)
+ "do not deploy Wi-Fi networks yet.".format(device.sdk)
)
# Disable Privacy Impact popup on Android 5.
- if dut.sdk <= 22:
- disable_privacy_impact_popup(dut)
+ if device.sdk <= 22:
+ disable_privacy_impact_popup(device)
# Push the scenarios, their data, and install the apps
- prepare_dut(dut, "../scenarios", "../scenarios-data", PREBUILTS_PATH)
+ prepare_dut(device, "../scenarios", "../scenarios-data", PREBUILTS_PATH)
# Start the viser app
- adb(
+ device.adb(
"shell",
"monkey",
"-p",
@@ -475,22 +461,17 @@
"-c",
"android.intent.category.LAUNCHER",
"1",
- serial=serial,
)
- configure_perms(dut)
+ configure_perms(device)
- # TODO DO NOT DO THE FOLLOWING IF NO SIM CARD IS IN THE DUT
- # time.sleep(10)
- # configure_sms(dut)
-
- configure_settings(dut)
+ configure_settings(device)
except (HostCommandError, DeviceCommandError) as e:
print("ERROR {}".format(e), file=sys.stderr)
finally:
try:
# Leave the device alone now
- force_awake(serial, always=False)
+ device.force_awake(always=False)
except DeviceCommandError as e:
print("WARNING {}".format(e), file=sys.stderr)