- PVSM.RU - https://www.pvsm.ru -
С тех пор как Гугл выпустил в свет инструмент для автоматизации тестирования monkeyrunner [1] прошло немало времени, а улучшений в нем не видно. Тем не менее, для задачи регулярной проверки веб-страниц на корректность верстки лучшего инструмента не нашлось. Те, кому просто нужен готовый скрипт для сравнения скриншотов страниц на андроиде с поддержкой прокрутки, могут сразу скачать его по ссылке [2]. Под катом же будет рассказано, какие проблемы таит манкейраннер, и как их преодолевать.
Кратко, задача стояла так: пройтись по страницам по списку и убедиться, что они выглядят так же, как раньше (или почти так же). Само собой необходимо прокручивать длинные страницы до конца.
Сам по себе он выглядит достаточно просто:
Для снятия скриншотов monkeyrunner имеет готовую функцию takeSnapshot [3], а для их сравнения есть совершенно прекрасный ImageMagick [4], который позволяет оценить насколько картинки похожи, а не просто требовать 100% совпадения. У ИмейджМеджика множество метрик сравнения, я использую -metric RMSE.
Чтобы определить, когда пора закончить скролить станицу вниз, достаточно сравнить два последних скрина, если они совпали, значит конец достигнут.
Для экспорта результатов в CI или любимую IDE используется python-junit-xml [5]
Теперь самое интересное: если сделать все это «в лоб», то ничего не будет работать. Причин несколько.
1. Windows
monkeyrunner имеет интересную «фичу», monkeyrunner.bat под Windows в процессе исполнения подменяет текущий каталог на тот, где лежит сам манкейраннер, потому что ему так удобно. В итоге в нашем скрипте перестают работать все относительные пути и директивы import
Чтобы побороть это поведение, срипт должен сам определить свое местоположение и дальше действовать только по абсолютным путям. Делается это примерно так:
filepath = path.split(os.path.realpath(__file__))[0]
BASE_PATH = path.split(filepath)[0].encode(FILENAMES_ENCODING)
try:
import config
from junit_xmls import TestSuite, TestCase
except ImportError:
#dirty hack that loads 3rd party modules from script's dir not from working dir, which is always changed by windows monkeyrynner
import imp
config = imp.load_source('config', BASE_PATH+'/src/config.py')
junit_xml = imp.load_source('junit_xml', BASE_PATH+'/src/junit_xml/__init__.py')
TestSuite = junit_xml.TestSuite
TestCase = junit_xml.TestCase
2. Русские символы
Первая же попытка протестировать русскую википедию провалилась, так как манкейраннер основан на втором питоне и наследует все известные проблемы unicode и национальными символами. Пришлось в явной форме указывать кодировку везде, где возможно, а также немного модифицировать junit_xml, автор которой не подозревал о национальных символах.
Например, для корректного создания файлов нужно перевести имя в unicode явно
filePath.decode("utf-8")
Кроме того на русскоязычных версиях Windows скрипт просто будет падать, если задан неверный путь к ImageMagick, потому что Popen просто не ожидает получить русские символы в сообщении об ошибке, а Windows свои сообщения локализует.
3. Скрол
Если 10 раз проскролировать одну и ту же страницу в одном и том же браузере на одном и том же девайсе с помощью MonkeyDevice.drag() [6] мы получим 10 разных результатов. Drag просто не гарантирует, что он будет скролировать страницу одинаково. Чтобы решить эту проблему, пришлось применить следующий трюк: сравнивая новую страницу со старой, я отрезаю по несколько пикселей сверху и снизу страницы и ищу ее место ее вхождения в оригинал (к счастью ImageMagick умеет даже такое), то, насколько страница ниже или выше предполагаемой позиции, и есть погрешность скрола, нужно вычесть ее при следующем скроле. Такая обратная связь позволяет скрипту не отвалиться на третей итерации и благополучно дожить до конца страницы.
4. Расход памяти
Если открыть 10-20 страниц подряд любой браузер просто создаст 10-20 вкладок, со временем они сожрут всю память и браузер просто перестанет нормально реагировать на команды. В лучшем случае загрузка страниц замедлится, но обычно это приводит к тому, что monkeyrunner просто отваливается по таймауту. Чтобы избежать этого я пошел на небольшой хак: перед открытием новой страницы, текущий процесс браузера просто убивается через adb, примерно так
device.shell('am force-stop ' + BROWSER_PACKAGE_NAME)
Хак этот неплохо помогает стандартному AOSP браузеру в эмуляторе, но не очень действует на Хром, Оперу и ФФ, поэтому в итоге просто пришлось написать свою обертку на web-view, по сути — легковесный браузер без вкладок. Попутно решились и две других проблемы: самописный браузер не спрашивает подтверждения на доступ к местоположению пользователя и не портит скриншоты, а кроме того его можно включить в состав скрипта и он будет устанавливаться автоматически через MonkeyDevice.installPackage() [7]
5. Неудачные подключения
Если завершить работу скрипта и тут же перезапустить его, скорее всего MonkeyRunner.waitForConnection() [8] просто свалится с ошибкой, при этом повторый вызов обычно проходит уже без проблем. Поэтому финальный вариант скрипта всегда пробует подключиться к устройству дважды.
6. Блокировка экрана
Если девайс был заблокирован в момент запуска, скрипт просто будет работать некорректно. Этого можно избежать, сымитировав нажатие на аппаратную кнопку меню (по неизвестной причине это разблокирует Андроид аппараты)
MonkeyDevice.press("KEYCODE_MENU", MonkeyDevice.DOWN_AND_UP)
Но поскольку на разблокированном устройстве это приведет к появлению меню, лучше предварительно убедиться заблокирован ли девайс, сделать это можно через dumpsys
lockScreenRegexp = re.compile('mShowingLockscreen=(true|false)')
result = lockScreenRegexp.search(device.shell('dumpsys window policy'))
if result:
return (result.group(1) == 'true')
raise RuntimeError("Couldn't determine screen lock state")
Разблокировка сама по себе не включит экран на «спящем» устройстве, так что перед всеми этими операциями стоит сделать принудительный вызов MonkeyRunner.wake() [9]
После того как устройство успешно разблокировано, можно полагаться на то, что остальные функции начнут работать корректно.
Со всеми правками уже можно проверять корректность отображения страниц, не опасаясь падений через каждый 5 минут, но для лучшего результата стоит выбирать либо тестовый браузер, либо AOSP Browser из ванильного андроида.
П.С. Код лежит на битбакете и открыт для копирования и модификаций.
Автор: fo2rist
Источник [10]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/testirovanie/56785
Ссылки в тексте:
[1] monkeyrunner: http://developer.android.com/tools/help/monkeyrunner_concepts.html
[2] ссылке: https://bitbucket.org/fo2rist/monkeyrunner-sites-verificator/overview
[3] takeSnapshot: http://developer.android.com/tools/help/MonkeyDevice.html#takeSnapshot
[4] ImageMagick: http://www.imagemagick.org/
[5] python-junit-xml: https://github.com/kyrus/python-junit-xml
[6] MonkeyDevice.drag(): http://developer.android.com/tools/help/MonkeyDevice.html#drag
[7] MonkeyDevice.installPackage(): http://developer.android.com/tools/help/MonkeyDevice.html#installPackage
[8] MonkeyRunner.waitForConnection(): http://developer.android.com/tools/help/MonkeyRunner.html#waitForConnection
[9] MonkeyRunner.wake(): http://developer.android.com/tools/help/MonkeyDevice.html#wake
[10] Источник: http://habrahabr.ru/post/215211/
Нажмите здесь для печати.