Всем привет! В связи с успехом прошлой статьи я решил и дальше пойти по линии автоматизации того что стандартными средствами ZP не предусмотрено. Сегодня мы с вами займемся автоматизацией приложения Instagram'a
прямо на вашем смартфоне через USB !
Начинаем
Для того чтобы это все заработало нам понадобится:
Скачиваем и устанавливаем все это добро. Рекомендую ставить Android SDK по стандартному пути который предложит установочник, если вы по привычке его изменили как это сделал я, то
эта инструкция для вас
После того как все установлено, вам нужно открыть Android SDK и установить внутри него вот эти пакеты:
Теперь, когда все готово можем приступить к созданию проекта, сначала берем USB, телефон и все это подключаем к ПК, в телефоне в настройках нужно включить опции разработчика и отладку по USB
Первое что нам понадобится это запустить appium с нужными параметрами чтобы на пк поднялся сервер для перенаправления команд на телефон, делаем:
Создадим два bat файла
1)
"C:/Program Files (x86)/Appium/node.exe" "C:/Program Files (x86)/Appium/node_modules/appium/bin/Appium.js" --address 127.0.0.1 --chromedriver-port 9516 --bootstrap-port 4725 --selendroid-port 8082 --no-reset --local-timezone
и назовем его AppiumStart.bat
2)
это пусть будет AppiumStop.bat
Теперь первым экшеном в нашем проекте будет запуск программы с указанием пути до файла AppiumStart:
Следующим шагом плавно переходим к кодингу, для работы сего приложения нам понадобятся библиотеки от Appium и Selenium а также Newtonsoft.Json ибо они без него не работают, добавляем их в ссылки GAC:
Все нужные dll во вложении
Теперь добавляем необходимые using:
using System.Linq;
using OpenQA.Selenium.Appium.Android;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium;
using OpenQA.Selenium.Remote;
Для того чтобы мы управляли конкретным приложением и при старте на телефоне открылось именно оно, нам нужно указать параметры инициализации экземпляра класса драйвера управления, не думаю что в это нужно вникать, можно просто все сделать по инструкции =)
Создаем экшен Код С# и начинаем кодить:
DesiredCapabilities cap = new DesiredCapabilities(); // экземпляр класса настроек
cap.SetCapability("deviceName", "Insta"); // имя девайся (абсолютно любое)
cap.SetCapability("platformVersion", "4.0.3");// версия платформы (тоже любая)
cap.SetCapability("platformName", "Android");//имя платформы
cap.SetCapability("appPackage", "com.instagram.android"); // пространство имен используемое приложением
cap.SetCapability("appActivity", ".activity.MainTabActivity"); // активное окно
AndroidDriver<IWebElement> driver = new AndroidDriver<IWebElement>(new Uri("http://127.0.0.1:4723/wd/hub"), cap); // инициализируем экземпляр класса драйвера с настроечками
Как вы заметили в настройках фигурируют два непонятных параметра это appPackage и аppActivity
Для любого приложения их можно получить следующим образом:
- Открываем на телефоне приложение для которого хотим получить данные (исходим из условия что телефон уже подключен к ПК и на нем включен режим отладки)
- Запускаем cmd от имени администратора
- пишем adb devices
- Дальше adb shell
- И теперь dumpsys window windows | grep -E ‘mCurrentFocus|mFocusedApp’
Если все верно вы должны получить данные о запущенном приложении:
Именно они и указывались в наших настройках.
Теперь если запустить проект, то на экшене c кодом на телефоне уже должно само открыться приложение Instagram.
Дальше построим наши действия следующим образом, будем автоматизировать рассылку сообщения в директе инсты, для всего этого нам нужно научиться исследовать элементы приложения, т.к. кликать мы будем именно по ним, а не по координатам, а так же нужно ожидать появления элемента на экране, чтобы все работало четко, а не на неоднозначных паузах, которые могут привести к сбою последовательности действий.
Начнем с 1 пункта
Исследование элементов на андроид приложениях
На самом деле тут не нужно изобретать велосипед, Google сам уже все сделал для этого, вам достаточно открыть папку с android- sdk, перейти в папку tools и открыть файл uiautomatorviewer.bat
Мой полный путь до него: "C:\android-sdk\tools\uiautomatorviewer.bat"
Он немного потупит и откроет нам вот такое окно:
Далее открываем на телефоне необходимый экран нужного приложения, в нашем случае это инстаграм, жмем на кнопку что на скрине и видим что он нам загрузил скрин экрана со всеми его элементами:
Наша задача нажать на самолетик, чтобы отправить сообщение, выделяем его и смотрим по каким параметрам его можно найти. Больше всего мне понравилось искать по XPath
Давайте сразу в коде добавим нажатие на этот элемент, выглядеть это будет вот так:
driver.FindElementByXPath("//android.widget.FrameLayout[contains(@resource-id, 'action_bar_inbox_icon_with_badge')]").Click();
Если разбирать по букофкам то мы обратились к нашему драйверу driver, вызвали в нем функцию поиска элемента на экране по XPath- FindElementByXPath, соотеветственно в него передали наше имя класса, взятое из uiautomatorviewer, внутри полученной коллекции элементов с этим классом вычленили элемент у которого resource-id равен тому id что показал нам инспектор (id начинает после :id/) и кликнули по нему Click()
По идее если сейчас запустить наш проект, то уже должно раскрыться приложение инсты и там должен нажаться самолетик, НО такое исполнение может вызывать ошибку "Элемент не найден" ибо телефон может затупить и долго открывать окно приложения, поэтому сразу зацепим следующий пункт
Ожидание появления элементов на экране телефона
В этом тоже нет ничего сложного, для того чтобы дождаться когда на нашем телефоне появится этот самолетик мы будем использовать следующий способ:
Сначала подключим необходимый using для этого:
using OpenQA.Selenium.Support.UI;
Далее объявим экземпляр класса который будет отвечать за ожидание:
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(60));
При его объявлении мы указали для какого объекта драйвера он будет использоваться и в течении какого времени производить ожидание, в нашем случае это 60 секунд
Непосредственно само ожидание:
wait.Until(ExpectedConditions.PresenceOfAllElementsLocatedBy(By.XPath("//android.widget.FrameLayout[contains(@resource-id, 'action_bar_inbox_icon_with_badge')]")));
Разбираем, мы сказали объекту что должны выполнить ожидание появления элемента на экране по заданному XPath, следовательно программа будет стоять на строке этого кода пока не появится этот элемент либо не пройдет наши заданные 60с, если они пройдут то строка выдаст исключение с текстом об истечении таймаута, как только он появится мы сразу нажимаем на него
В экшене все это выглядит уже так:
DesiredCapabilities cap = new DesiredCapabilities(); // экземпляр класса настроек
cap.SetCapability("deviceName", "Insta"); // имя девайся (абсолютно любое)
cap.SetCapability("platformVersion", "4.0.3");// версия платформы (тоже любая)
cap.SetCapability("platformName", "Android");//имя платформы
cap.SetCapability("appPackage", "com.instagram.android"); // пространство имен используемое приложением
cap.SetCapability("appActivity", ".activity.MainTabActivity"); // активное окно
AndroidDriver<IWebElement> driver = new AndroidDriver<IWebElement>(new Uri("http://127.0.0.1:4723/wd/hub"), cap); // инициализируем экземпляр класса драйвера с настроечками
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(60)); //экземпляр класса для ожидания
wait.Until(ExpectedConditions.PresenceOfAllElementsLocatedBy(By.XPath("//android.widget.FrameLayout[contains(@resource-id, 'action_bar_inbox_icon_with_badge')]"))); //ожидаем
driver.FindElementByXPath("//android.widget.FrameLayout[contains(@resource-id, 'action_bar_inbox_icon_with_badge')]").Click(); //жмем
Итого, теперь мы умеем смотреть все элементы на экране тлф, программно их находить и ожидать, осталось дело за малым - допилить нашего спамера по директу инсты, погнали:
Алгоритм примерно такой:
Нажать новое сообщение
Ввести никнейм получателя
Дождаться результата, выбрать первого, ввести текст и нажать отправить
Жмем на "Новое сообщение"
driver.FindElementsByClassName("android.widget.TextView").Where(f=> f.Text == "Новое сообщение").First().Click();
Я исследовал элемент, выяснил что значение его класса не уникально и у него отсутствует resource-id, поэтому сделал поиск по классу и свой- ву Text, к первому элементу применил метод Click();
Вводим никнейм
Любые нажатия в приложения отправляются при помощи метода SendKeys("тут текст");
driver.FindElementsByClassName("android.widget.EditText").First(f=> f.Text == "Поиск").SendKeys("test");
Дальше ожидаем появления результатов поиска и делаем Click() на первый результат выдачи:
wait.Until(ExpectedConditions.PresenceOfAllElementsLocatedBy(By.XPath("//android.widget.LinearLayout[contains(@resource-id, 'user_row_background')]")));
driver.FindElementsByXPath("//android.widget.LinearLayout[contains(@resource-id, 'user_row_background')]").First().Click();
Вводим текст сообщения
driver.FindElementByXPath("//android.widget.EditText[contains(@resource-id, 'row_thread_composer_edittext')]").SendKeys("Привееееет");
исходил из данных от инспектора
Нажимаем на кнопку отправить и не забываем предварительно ожидать ее появления:
wait.Until(ExpectedConditions.PresenceOfAllElementsLocatedBy(By.XPath("//android.widget.ImageView[contains(@resource-id, 'row_thread_composer_button_send')]")));
driver.FindElementByXPath("//android.widget.ImageView[contains(@resource-id, 'row_thread_composer_button_send')]").Click();
Но тут у меня всплыл такой момент, когда я пытаюсь отправить нажатия русских символов, то ничего не вводится, а английские работают, проблема решилась добавлением в начальные настройки инициализации драйвера строки
cap.SetCapability("unicodeKeyboard", "true");
ВУАЛЯ, все круто, все работает, вот демонстрация того как это происходит у меня:
Для трансляции видео на экран ПК через инет использовал Screen Stream Mirroring
P.S: По секрету говоря абсолютно не важно будете ли вы использовать свой телефон подключенный через USB или любой эмулятор андроида, например bluestacks или NOX, данная инструкция подойдет для применения к любому из них, только заместо подключенного по USB телефона у вас на пк должен быть открыт эмулятор андроида и все =)
Получившийся проект прикрепил!
Спасибо за внимание! Всех с наступающим! =)