C#: Автоматизация Android без посредников (adb)

от автора

Всем привет

Не давно понадобилось автоматизировать одно приложение.Мне не хотелось использовать какие то посредники по типу Appium, во-первых, ресурсы были ограничены, на одном компьютере нужно было заставить работать 3-4 эмулятора, во-вторых с adb работать не так уж и трудно, в-третьих я наткнулся на библиотеку SharpAdbClient, в которой были уже реализованы базовые функции, но самых важных мне не хватало, поэтому пришлось дописать их самому.Я подумал почему бы не сделать библиотеку с расширенным функционалом, поэтому в этой статье будет обзор на библиотеку AdvancedSharpAdbClient.

Установка

Вы можете установить библиотеку через nuget, либо через package manager:

PM> Install-Package AdvancedSharpAdbClient

Так же проект доступен на github

Инициализация

Для работы библиотеки понадобится adb.exe.Я использовал эмулятор Nox,где adb идёт вместе с приложением и находится по пути Nox\bin\adb.exe.

Первым делом запустим сервер

using System; using AdvancedSharpAdbClient;  namespace AdbTest {     class Program     {          static void Main(string[] args)         {             if (!AdbServer.Instance.GetStatus().IsRunning)             {                 AdbServer server = new AdbServer();                 StartServerResult result = server.StartServer(@"F:\Nox\bin\adb.exe", false);                 if (result != StartServerResult.Started)                 {                     Console.WriteLine("Can't start adb server");                     return;                 }             }         }     } } 

Далее нам необходимо создать новый клиент и подключиться к устройству

using System; using System.Linq; using AdvancedSharpAdbClient;  namespace AdbTest {     class Program     {         static AdvancedAdbClient client;          static DeviceData device;          static void Main(string[] args)         {             if (!AdbServer.Instance.GetStatus().IsRunning)             {                 AdbServer server = new AdbServer();                 StartServerResult result = server.StartServer(@"F:\Nox\bin\adb.exe", false);                 if (result != StartServerResult.Started)                 {                     Console.WriteLine("Can't start adb server");                     return;                 }             }             client = new AdvancedAdbClient();             client.Connect("127.0.0.1:62001"); // Ip Nox'а             device = client.GetDevices().FirstOrDefault(); // Выбираем девайс из подключенных             if (device == null)             {                 Console.WriteLine("Can't connect to device");                 return;             }         }     } } 

Тут я покажу вам лайфхак, как можно автоматически получать Ip эмуляторов.Это пригодится при работе с несколькими эмуляторами одновременно(многопоточность).

Рассмотрим на примере Nox’a, у которого каждый порт начинается с 620, например:

  • 127.0.0.1:62001

  • 127.0.0.1:62025

  • 127.0.0.1:62040

Для поиска можно воспользоваться утилитой netstat, которая слушает активные порты и подключения

Получение IP для нескольких эмуляторов
static List<string> deviceports = new List<string>();  static string GetIP() {     while (true)     {         Process[] processes = Process.GetProcessesByName("NoxVMHandle");         foreach (Process process in processes)         {             ProcessStartInfo startInfo = new ProcessStartInfo("cmd", "/c netstat -a -n -o | find \"" + process.Id + "\" | find \"127.0.0.1\" | find \"620\"");             startInfo.UseShellExecute = false;             startInfo.CreateNoWindow = true;             startInfo.RedirectStandardOutput = true;              var proc = new Process();             proc.StartInfo = startInfo;             proc.Start();             proc.WaitForExit();              MatchCollection matches = Regex.Matches(proc.StandardOutput.ReadToEnd(), "(?<=127.0.0.1:)62.*?(?= )");             foreach (Match match in matches)             {                 if (match.Value != "" && !deviceports.Contains(match.Value))                 {                     deviceports.Add(match.Value);                     return "127.0.0.1:" + match.Value;                 }             }         }     } }

Так же, если вы хотите работать в многопотоке, в каждом потоке необходимо создавать новый AdvancedAdbClient для стабильной работы.

Пример многопоточной работы
static ConcurrentQueue<string> deviceports = new ConcurrentQueue<string>(); // Используем потокобезопасный список  static object locker = new object();  static string GetIP() {     while (true)     {         Process[] processes = Process.GetProcessesByName("NoxVMHandle");         foreach (Process process in processes)         {             ProcessStartInfo startInfo = new ProcessStartInfo("cmd", "/c netstat -a -n -o | find \"" + process.Id + "\" | find \"127.0.0.1\" | find \"620\"");             startInfo.UseShellExecute = false;             startInfo.CreateNoWindow = true;             startInfo.RedirectStandardOutput = true;              var proc = new Process();             proc.StartInfo = startInfo;             proc.Start();             proc.WaitForExit();              MatchCollection matches = Regex.Matches(proc.StandardOutput.ReadToEnd(), "(?<=127.0.0.1:)62.*?(?= )");             foreach (Match match in matches)             {                 if (match.Value != "" && !deviceports.Contains(match.Value))                 {                     deviceports.Enqueue(match.Value);                     return "127.0.0.1:" + match.Value;                 }             }         }     } }  static void Main(string[] args) {     GetIP();     if (!AdbServer.Instance.GetStatus().IsRunning)     {         AdbServer server = new AdbServer();         StartServerResult result = server.StartServer(@"F:\Nox\bin\adb.exe", false);         if (result != StartServerResult.Started)         {             Console.WriteLine("Can't start adb server");             return;         }     }     // Перед запуском необходимо добавить в Multi Drive 3 эмулятора     for (int i = 0; i < 3; i++) // Запускаем три эмулятора     {         new Thread(() =>         {             Process process = new Process();             process.StartInfo.FileName = @"F:\Nox\bin\Nox.exe";             process.StartInfo.Arguments = $"-clone:Nox_{i}"; // Запускаем i-тый эмулятор из Multi-Drive             process.Start();             AdvancedAdbClient client = new AdvancedAdbClient();             lock (locker) // Во избежании ошибок             {                 client.Connect(GetIP()); // Ip Nox'а             }             DeviceData device = client.GetDevices().FirstOrDefault();             if (device == null)             {                 Console.WriteLine("Can't connect to device");                 return;             }         }).Start();     } }

Автоматизация

Поиск элемента

Для поиска элемента используется метод FindElement и FindElements

static AdvancedAdbClient client;  static DeviceData device;  static void Main(string[] args) {     client = new AdvancedAdbClient();     client.Connect("127.0.0.1:62001");     device = client.GetDevices().FirstOrDefault();     Element el = client.FindElement(device, "//node[@text='Login']"); }

Можно указать время ожидания элемента (по умолчанию его нету)

Element el = client.FindElement(device, "//node[@text='Login']", TimeSpan.FromSeconds(5));
Element[] els = client.FindElements(device, "//node[@resource-id='Login']", TimeSpan.FromSeconds(5));

Получение атрибута элемента

Каждый элемент содержит свои аттрибуты, для получения необходимо обратиться к полю attributes

static void Main(string[] args) {     ...     Element el = client.FindElement(device, "//node[@resource-id='Login']", TimeSpan.FromSeconds(3));     string eltext = el.attributes["text"];     string bounds = el.attributes["bounds"];     ... }

Клик по элементу

Вы можете кликнуть по координатам на экране

static void Main(string[] args) {     ...     client.Click(device, 600, 600); // Click on the coordinates (600;600)     ... }

Либо же по найденному элементу

static void Main(string[] args) {     ...     Element el = client.FindElement(device, "//node[@text='Login']", TimeSpan.FromSeconds(3));     el.Click();// Click on element by xpath //node[@text='Login']     ... }

Свайп

AdvancedSharpAdbClient позволяет свайпнуть от одного элемента к другому

static void Main(string[] args) {     ...     Element first = client.FindElement(device, "//node[@text='Login']");     Element second = client.FindElement(device, "//node[@text='Password']");     client.Swipe(device, first, second, 100); // Swipe 100 ms     ... }

И так же по координатам

static void Main(string[] args) {     ...     device = client.GetDevices().FirstOrDefault();     client.Swipe(device, 600, 1000, 600, 500, 100); // Swipe from (600;1000) to (600;500) on 100 ms     ... }

Ввод текста

Текст может быть абсолютно любым, но учтите, что adb не поддерживает кириллицу.

static void Main(string[] args) {     ...     client.SendText(device, "text"); // Элемент должен быть в фокусе     ... }

Так же можно автоматически фокусироваться на элементе (кликать, а потом вводить текст)

static void Main(string[] args) {     ...     client.FindElement(device, "//node[@resource-id='Login']").SendText("text"); // Автоматически фокусируется     ... } 

Очистка поля ввода

Для очистки поля ввода необходимо указать максимальное количество символов в поле

static void Main(string[] args) {     ...     client.ClearInput(device, 25); // The second argument is to specify the maximum number of characters to be erased     ... }

Автоматически определять длинну текста (может работать неправильно)

static void Main(string[] args) {     ...     client.FindElement(device, "//node[@resource-id='Login']").ClearInput(); // Get element text attribute and remove text length symbols     ... }

Отправка нажатия клавиши (keyevent)

Список эвентов вы можете посмотреть тут

static void Main(string[] args) {     ...     client.SendKeyEvent(device, "KEYCODE_TAB");     ... }

Команды устройства

Все команды устройства вы можете посмотреть на страничке Github, здесь я покажу лишь 2 главных метода.

Установка apk

static void Main(string[] args) {     ...     PackageManager manager = new PackageManager(client, device);     manager.InstallPackage(@"C:\Users\me\Documents\mypackage.apk", reinstall: false);     manager.UninstallPackage("com.android.app");     ... }

Запуск приложений

static void Main(string[] args) {     ...     client.StartApp(device, "com.android.app");     client.StopApp(device, "com.android.app"); // force-stop     ... }

Важные ссылки

AdvancedSharpAdbClient — основная библиотека

SharpAdbClient — была взята за основу, является форком madb

madb — портированная версия ddmlib с Java

ddmlib — оригинальная библиотека для работы с adb

P.S

Я не являюсь противником посредников, типа Appium, просто эти фреймворки больше больше подходят для тестирования приложений, а не автоматизации.

Я являюсь только начинающим разработчиком, поэтому прошу строго не судить правильность. кода

Если у вас появились какие либо вопросы, пишите мне на почту.

Это моя первая статья на Habr, поэтому заранее извиняюсь за косяки.


ссылка на оригинал статьи https://habr.com/ru/articles/581252/