[Из песочницы] Автоматизация UI-тестирования на PhoneGap. Кейс платежного приложения
Не знаю, как вы, но я в воде чувствую себя уверенно. Однако недавно меня решили научить плавать снова, применив старый спартанский метод: кинули в воду и велели выживать.
Но довольно метафор.
Дано: PhoneGap-приложение с iframe, внутри которых загружается сторонний сайт; стаж тестировщика 1,5 года; стаж программиста 0 лет.
Задача: найти способ автоматизировать тестирование основного бизнес-кейса приложения, потому что тестировать вручную долго и дорого.
Решение: много костылей, регулярные обращения к программистам за помощью.
Впрочем, тесты работают и я многому научился. И мораль моей ретроспективы будет не в том, что не стоит совершать моих ошибок, а в том, что я разобрался со странной и нетипичной задачей — по-своему, но разобрался.
О проекте
Начну свой рассказ со знакомства с проектом. Это веб-сервис, с помощью которого люди оплачивают услуги ЖКХ, связи, автомобильные штрафы, делают платежи по кредитам и оплачивают покупки в некоторых интернет-магазинах. Сервис работает с 30 тысячами операторов услуг, это большой продукт со сложной историей, внушительным количеством интеграций и собственной командой.
Компания, где я работаю QA-специалистом, разработала для сервиса кроссплатформенное мобильное приложение. Клиенту нужно было сделать MVP, чтобы проверить ряд гипотез, и «кроссплатформа» была единственным способом быстро и недорого исполнить это желание.
Почему возникла задача автоматизировать UI-тесты?
Как я уже сказал выше, сервис живёт сложной и интересной внутренней жизнью. Команда разработки веб-сервиса регулярно вносит в его работу изменения и выкатывает их на сервер два раза в неделю. Наша команда никак не участвует в том, что происходит на бэке, зато участвует в разработке приложения. Мы не знаем, что увидим на экране приложения после очередного релиза и как внесённые изменения повлияют на работу приложения.
Когда речь идёт о работе MVP, то задача-максимум — поддерживать работу одной или нескольких ключевых возможностей продукта. В платежном сервис такими возможностями стали проведение оплаты поставщику услуг, работа корзины и отображение списка поставщиков услуг. Но оплата — важнее прочих.
Чтобы изменения, внесенные разработчиками сайта, не блокировали исполнение ключевых бизнес-кейсов приложения, нужно тестирование. Дымового вполне достаточно: запустилось? не сгорело? отлично. Но с такой частотой релизов тестировать приложение вручную было бы слишком затратно.
В голову пришла гипотеза:, а что, если этот процесс автоматизировать? И мы выделили время и бюджет, чтобы автоматизировать ряд ручных тестов и в будущем тратить два часа в неделю на тестирование вместо шести-восьми.
А всё ли можно автоматизировать?
Важно сказать, что изменения в работе сайта касаются не только UI, но и UX. Мы договорились, что аналитик со стороны клиента будет заранее рассказывать нам про планируемые обновления на сайте. Они могут быть разными, от перемещения кнопки до внедрения нового раздела. Тестирование последнего нельзя доверить автоматике — это сложный UX-сценарий, который приходится по старинке находить и проверять руками.
Как мы представляли себе реализацию
Тестировать основные функции мы решили через интерфейс приложения, вооружившись фреймворком Appium. Appium Inspector запоминает действия с интерфейсом и преобразует их в скрипт, тестировщик запускает этот скрипт и видит результаты теста. Так мы представляли себе работу в начале.
Тут мы ненадолго вернёмся к метафорическому вступлению к моей истории. Чтобы автоматизировать тесты, надо уметь программировать, а тут мои полномочия, как говорится, всё. Введение в этот мир заняло примерно четыре часа: мы развернули и настроили среду, техдир заверил, что всё просто, на примере одного теста показал, как писать остальные, и больше особо в мою работу на проекте не вмешивался. Кинул в воду со спущенным спасательным кругом и отсалютовал.
Я толком не представлял, что получится.
Начал с того, что составил тест-кейсы для проверки оплаты:
Для корзины и списка поставщиков тоже были составлены тест-кейсы. Дальше планировалось перейти к автоматизации других, более сложных сценариев. Но мы отказались от такого плана, потому что видов заполняемых форм у разных поставщиков услуг много. Для каждой из форм пришлось бы делать автоматизированный тест отдельно, а это долго и дорого. Клиент тестирует их сам.
Ещё одно ожидание было таким: раз это автотест, то тест-кейс может быть каким угодно сложным. Ведь программа всё будет тестировать сама, а тестировщику нужно будет только задать последовательность действий. Я придумывал огромные, монструозные кейсы, в которых в цикл оплаты вставлено ещё несколько дополнительных проверок, например таких: что будет, если в оплате перейти из корзины к списку поставщиков, добавить нового, потом вернуться, удалить его и продолжить оплату.
Когда я написал такой тест, я увидел, насколько он огромный и насколько нестабильно работает. Стало понятно, что тест-кейсы нужно упрощать. Короткий тест легче поддерживать и дописывать. И вместо тестов с несколькими проверками я начал делать тесты с одной-двумя проверками.
Что касается софта для тестирования, работу с Appium я представлял так: я совершаю какую-то последовательность действий в рекордере, он это записывает, фреймворк собирает из этого скрипт, я запускаю получившийся код и он повторяет мои действия в приложении.
Звучало неплохо, но только звучало.
Каким процесс оказался в реальности
И вот с какими проблемами я столкнулся:
- Как только я попытался запустить скрипт, записанный рекордером, я понял, что без программирования не обойтись. Код, который выдавал рекордер, в своём первозданном виде не стал бы работать — нужны были доработки. Например, если тестируется форма ввода, проще всего подставить данные (номер телефона, лицевой счёт, имя и фамилия и т.п.). Рекордер записывает каждый символ клавиатуры как отдельный элемент, поэтому скрипт вводит символы по одному, переключая клавиатуру, где нужно — в общем, слепо копирует записываемые действия. Здесь и впоследствии я решал свои проблемы либо силами программиста, который подсказывал, что делать, либо пятичасовым сёрфингом в Google.И это самый главный минус во всей истории.
- В какой то момент я понял, что нужны условия, при которых будет работать скрипт. Объясню на примере. Название поставщика услуг пишется кириллическими символами, и для того, чтобы ввести его, нужно включить на устройстве клавиатуру, переключить на ней язык с латиницы на кириллицу и ввести название.
В первый раз скрипт выполняет эти действия и у него получается то, что нужно. Но когда вы снова запускаете скрипт, он переключает клавиатуру с кириллицы на латиницу, не может найти на клавиатуре русскую букву и падает. Поэтому скрипту нужно было задавать условия с помощью кода, например, «попытайся найти русскую букву, если ты не можешь, переключи клавиатуру». Вроде бы простая вещь для того, кто давно программирует, но не для меня на тот момент. - Скрипт не понимает, прогрузился ли вызываемый элемент, открылся ли он, видно ли его; он просто нажимает на кнопки, которые записал рекордер. Поэтому к скрипту нужно где-то добавлять задержки, скролл или ещё какие-то дополнительные действия, а это снова требует опыта в программировании.
- Отсутствие ID у элементов. Для чего они нужны? Допустим, у вас в приложении есть кнопка login. По названию этой кнопки скрипт может не понять, что вы хотите нажать именно на неё. Наличие ID у элементов решает эту проблему.
Appium может найти элемент по названию, но бывает так, что у элемента даже его нет. Например, в приложении есть корзина, и кнопка корзины обозначена иконкой; она не подписана, у неё нет никакого названия — ничего. И чтобы скрипт её нажал, он должен как-то её найти. Без названия это никак нельзя сделать, даже перебором — скрипту не к чему обратиться и он не может нажать на кнопку корзины. А если бы у корзины был ID, его бы увидел рекордер в процессе записи действий, и скрипт смог бы найти кнопку. Решение нашлось неоднозначное.
В нативной разработке большинству элементов присваивается ID, к которому рекордер обращается без проблем.
Но не стоит забывать, что наш продукт — кроссплатформенное приложение. Их основная особенность в том, что среди нативных элементов присутствует веб-часть, к которой рекордер не может обращаться так же, как к нативной. Он считывает элементы непредсказуемо — где-то текст, где-то тип — и нет никаких специальных ID, т.к. в вебе ID используется для других целей. Проект изначально пишется средствами веб-разработки (то есть на JavaScript), после чего Cordova генерирует нативный код, разный для платформ iOS и Android, и присваивает ID в только ей самой ведомом порядке.
Итак, решение. Так как я мог обращаться к элементам по их названиям, я попросил разработчика задать название кнопкам прозрачным шрифтом. Название есть, пользователь его не видит, но видит рекордер Appium. Скрипт может обратиться к такой кнопке и нажать на неё.
- Отсутствие ID у элементов замедляет работу автотестов. Почему? Названия элементов на экране может повторяться (например, два поля ввода имеют одинаковое название, но разное предназначение). А так как тест в поисках нужного элемента перебирает все с определённым названием, он может обратиться не к тому элементу, который ему нужен. Если находить элемент по имени напрямую, проблема не решается, т.к. имя не уникально. Поэтому скрипт должен сравнивать не только названия элементов, но еще и их тип. Из-за всего это тесты проходят медленно.
- Этот проект нельзя было запускать на эмуляторе — только на физическом девайсе. Для поддержки — это класс для отображения веб-страниц в приложениях WKWebView, используемого в проекте, необходимо, чтобы внутри физического девайса разворачивался localhost. Тесты на эмуляторе работали бы в четыре раза быстрее, но это не наш случай.
- Рекордер создавал тесты неструктурированно и не в рамках одного проекта. Если бы я писал этот код с нуля, как это делают программисты, то все тесты были бы в одном файле, и каждый из тестов был бы описан отдельным циклом в рамках одного проекта. А рекордер генерирует каждый тест как отдельный файл, и в нем нет циклов. В итоге все тесты я получал как отдельные куски кода, которые я не мог объединить и сделать для них структуру.
- Отчёт о результатах теста, или логирование. Тут всё просто: его не было. Я знал о существующих средствах вроде py.test или TestNG (зависит от языка программирования, на котором написаны тесты), но они были рассчитаны опять же на структурированный проект, а это снова не про нас. Поэтому подключить что-то готовое было нельзя, а в том, чтобы писать своё, у меня не было опыта совсем. В итоге я пришёл к следующему: того сообщения, которое мне показывал терминал, когда тест падал, вполне достаточно, чтобы понять, какой элемент тест не нашёл и почему.
Я в любом случае проверял ошибку вручную. Логи были, в минимальном своём исполнении, но были. И то хорошо.
- Последний и, пожалуй, самый болезненный пункт. Когда я упирался лбом в какую-то проблему, я или шёл к нашему техническому директору и инициатору идеи сделать автотесты, или просто гуглил проблему по пять часов и не понимал, что происходит. Помощь техдира делала задачи быстрыми и простыми. Но в какой-то момент он меня оставил, а каждый раз дёргать разработчиков не круто. Пришлось справляться самому.
Итоги
Сейчас, когда работа над тестами закончена, я смотрю на то, что получилось и думаю: я бы все сделал иначе: там, где рекордер генерирует скрипты, написал бы тесты вручную; добавил бы вставку данных для входа там, где элементы ищутся на клавиатуре; избавился бы от перебора элементов посредством добавления ID; изучил бы фреймворк, запускающий тесты циклом; настроил и подключил бы CI, чтобы тесты сами запускались после каждого деплоя; настроил бы логирование и рассылал бы результаты по почте.
Но тогда я почти ничего не знал и проверял все свои решения, потому что не был в них уверен. Иногда я вообще не представлял, каким оно должно быть.
Тем не менее, я выполнил задачу — на проекте появились автотесты, и мы смогли проверять работу приложения на фоне постоянных изменений. К тому же, наличие тестов избавило от необходимости сидеть и протыкивать одни и те же сценарии два раза в неделю, что занимает очень много времени при ручном тестировании, ведь каждый сценарий повторяется 100 раз. А я получил мощный опыт и понимание того, как на самом деле нужно было всё это сделать. Если вам есть что посоветовать или добавить к сказанному выше, буду рад продолжить беседу в комментариях.