Для автоматизации тестирования под Windows Phone и Windows нет удобных и открытых инструментов, которые можно легко адаптировать под свои нужды. Те, что есть, закрыты, ограничены и предлагают свой подход, отличающийся от общепринятых стандартов вроде Selenium WebDriver.

Мой коллега skyline-gleb недавно писал на Хабре, как мы разработали свой selenium-like инструмент автоматизации функционального тестирования desktop-приложений под Windows. Параллельно мы разрабатывали аналогичный инструмент, только под мобильные платформы от Microsoft.

О том, почему взялись его писать, какую пользу получили от использования единой платформы для автоматизации тестирования под все мобильные платформы и как внедрить это на вашем проекте, читайте в этой статье.

Немного истории


  • Октябрь 2010. Вышел Windows Phone 7. Спустя год Expensify выкладывает в open source WindowsPhoneTestFramework — BDD инструмент тестирования для нативных приложений;
  • Октябрь 2012, выходит Windows Phone 8. Майкрософт всё ещё не выпустили инструмент для тестирования через UI;
  • Февраль — Март 2014, мы выкладываем первый прототип WinphoneDrvier, первой open-source реализации Selenium для нативных Silverlight-приложений под Windows Phone;
  • В апреле 2014 Майкрософт выпускает Windows Phone 8.1 и наконец-то, почти 4 года спустя, выпускают официальный инструментарий для тестирования приложений под Windows Phone через UI — CodedUI. Но, конечно же, это не Selenium-совместимый инструмент и доступен он только в самых дорогих подписках на Visual Studio;
  • В мае 2014 Salesforce.com выложили в open source windowsphonedriver, реализацию Selenium для тестирования веб-приложений под Windows Phone. Примерно в это же время мы обновили наш драйвер для поддержки Windows 8.1;
  • В феврале 2015 мы выложили в опенсорс Winium.StoreApps, обновлённую версию winphonedriver, реализующую значительную часть команд протокола и поддерживающую нативные StoreApps приложения под Windows Phone 8.1. Именно этот драйвер мы используем у себя в процессах.

Буквально следом за этим мы представили наш инструментарий на Codefest 2015, где из обсуждения в кулуарах с Sathish Gogineni из Badoo родилась идея Winium CodedUi — реализации Selenium-драйвера на основе CodedUI, поддерживающего нативные и гибридные приложения и, самое главное, тестирование на устройствах.

На момент начала проекта существовал один открытый инструмент — Expensify/WindowsPhoneTestFramework, который не подошёл нам из-за того, что был несовместим с Selenium и имел нестандартное API. Более того, он был заточен под BDD. За время разработки проекта Microsoft успели выпустить свою версию инструментария — CodedUI, который опять же имел свой нестандартный API, был заточен под написание тестов в Visual Studio на C#, был закрытым и платным (что плохо масштабируется).

Итак, мы вспомнили, как развивались события. Вернёмся к Winium. Так как перечисленные выше инструменты нам не подходили, мы решили написать свой. Так родился проект Winium, который начинался как инструмент автоматизации тестирования Windows Phone Silverlight-приложений и вскоре превратился в целый набор инструментов автоматизации тестирования для Windows-платформы:

  • Windows Phone Driver — драйвер для нативных Windows Phone Silverlight приложений;
  • Winium for Store Apps — драйвер для нативных Windows Phone Store Apps приложений;
  • Winium.StoreApps.CodedUi — драйвер на основе CodedUI для Windows Phone XAML-based приложений;
  • Winium for Desktop — драйвер для нативных Windows Desktop приложений;
  • Winium.Cruciatus — библиотека для автоматизации Windows Desktop приложений.

Про Winium.Desktop и Winium.Cruciatus мы уже рассказывали на хабре. А сегодня речь пойдет о Winium for Store Apps (наследнике Windows Phone Driver) и о Winium.StoreApps.CodedUi.

Winium.StoreApps


Основные фичи и ограничения драйвера

Winium.StoreApps — основная реализация драйвера для мобильных устройств. Мы используем её и развиваем. Исходный код открыт и доступен на Гитхабе.

Основные фичи:

  • реализует протокол Selenium для тестирования нативных StoreApps приложений под Windows Phone платформу;
  • работает с Json Wire Protocol. Можно использовать selenium- или appium-биндинги и писать тесты так же, как для iOS или Android;
  • поддерживает установку и запуск тестируемого приложения, а также загрузку файлов в локальное хранилище приложения;
  • поддерживает single-touch жесты;
  • предоставлят базовый инспектор для просмотра UI дерева тестируемого приложения;
  • поддерживает Selenium Grid, что позволяет распараллелить исполнение тестов.

Ограничения:

  • поддерживаются только эмуляторы (хотя с небольшими изменениями драйвер может установить приложение на устройство и работать с ним, но пока мы не знаем, как на устройствах полноценно симулировать жесты и ввод);
  • необходимо встроить сервер автоматизации в ваше приложение (т.е. подключить nuget-пакет к приложению и добавить одну строку кода, которая запустить сервер на отдельном потоке. Да, это нарушает первое правило Аппиума, но пока варианта лучше, кроме CodedUI, найти не удалось);
  • поддерживается только одна сессия, но драйвер можно подключать к Grid для распределения тестов и их параллельного запуска.

Winium.StoreApps поддерживает все основные команды Selenium и его можно встроить в существующую инфраструктуру тестирования, построенную на базе Selenium/Appium. В целом, его уже можно активно использовать в непрерывном процессе, что мы и делаем.

Как это все работает

По сути Winium.StoreApps.Driver.exe представляет из себя HTTP-сервер, работающий по REST-протоколу JsonWire/WebDriver. Приходящие команды Driver при необходимости проксирует в тестовый сервер InnerServer, встраиваемый в тестируемое приложение.


Структура взаимодействия между тестами, драйвером и тестируемым приложением.

Как подготовить приложение и писать тесты

Для запуска тестов против нашего приложения необходимо выполнить три простых шага:

  • подготовить приложение;
  • написать тесты (Ок, это не так уж и легко.);
  • запустить тесты.

Подготавливаем приложение

Здесь всё просто: подключаем nuget-пакет Winium.StoreApps.InnerServer и инициализируем сервер автоматизации на основном потоке после того, как создан UI. Например, в методе MainPageOnLoaded. Сервер инициализируется на основном потоке только затем, чтобы получить правильный dispatcher. Работа сервера будет осуществляться на другом потоке, кроме непосредственно доступа к UI.

После этого хорошо бы обеспечить тестируемость приложения, прописав имена и идентификаторы для тех элементов, которые вы планируете использовать в тестах, но это не обязательно.

На этом все, осталось только собрать appx-пакет с вашим приложением.

Пишем тесты

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

Первое, что нам необходимо сделать, — создать новую сессию. При создании сессии мы можем указывать различные желаемые свойства. Вот несколько основных, поддерживаемых драйвером (полный список доступен в wiki).

dc = {
    'deviceName': 'Emulator 8.1 WVGA 4 inch 512MB',
    'app': r'C:\YorAppUnderTest.appx',
    'files': {
        {
            'C:\AppFiles\file1.png': 'download\file1.png',
            'C:\AppFiles\file2.png': 'download\file2.png'
        }
    },
    'debugConnectToRunningApp': False
}

  • deviceName — частичное имя устройства, на котором мы хотим запускать наши тесты. Если пусто, будет выбран первый эмулятор из списка.
  • app — полный путь до appx-пакета с тестируемым приложением, в которое вы встроили сервер автоматизации.
  • files — словарь файлов, которые будут загружены с локального диска в локальное хранилище приложения.
  • debugConnectToRunningApp — позволяет пропустить все шаги установки и загрузки файлов в приложение и подключиться к уже запущенному приложению. Это бывает очень удобно, когда вы запустили приложение из Visual Studio, расставили там точки breakpoint и хотите отладить ошибку, возникающую в приложении при прогоне одного из тестов.

Итак, сессию создали, приложение запустили. Теперь нам надо как-то найти элементы, если мы хотим с ними взаимодействовать. Драйвер поддерживает следующие локаторы:

  • id — AutomationProperties.AutomationId;
  • name — AutomationProperties.Name;
  • class name — полное имя класса (Windows.UI.Xaml.Controls.TextBlock);
  • tag name — то же, что и class name;
  • xname — x:Name, легаси локатор, обычно не поддерживается биндингами по умолчанию и требует доработки биндингов для использования, зато позволяет искать по имени, которое обычно прописывают элементу для доступа из кода.

Чтобы облегчить поиск локаторов и общее понимание структуры UI тестируемого приложения, мы сделали инспектор, который может подключиться к приложению и отобразить текущее состояние UI, как его видит драйвер.


Главное окно инспектора

Инспектор пока умеет немного, но уже можно посмотреть снимок экрана, дерево UI, как его видит драйвер, узнать локаторы элементов и их основные свойства: позицию, видимость, текст.

Итак, элемент мы нашли, теперь можем делать с ним что угодно.

# можно текст элемента запросить
element.text

# а можно и кликнуть в элемент
element.click()

# можно ещё ввести что-то в элемент
element.send_keys('Hello!'+Keys.ENTER)

# можно запросить публичное свойство по имени
element.get_attribute('Width')

# можно запросить вложенное свойство
element.get_attribute('DesiredSize.Width')

# а можно запросить сложные свойста, драйвер попробует сериализовать их в JSON
element.get_attribute('DesiredSize')
# '{"Width":300.0,"Height":114.0,"IsEmpty":false}'

В мире мобилок обычным кликом не обойтись, тут нужны жесты. Мы поддерживаем старый и проверенный API из JSWP (но скоро добавим поддержку нового Mobile WebDriver API). Уже сейчас можно делать флики и скроллы.

TouchActions(driver).flick_element(element, 0, 500, 100).perform()
TouchActions(driver).scroll(200,200).perform()

# можно даже создавать ваши собственные жесты
ActionChains(driver) .click_and_hold() .move_by_offset(100, 100) .release().perform()

А так как мы внедряем сервер автоматизации в приложение, то можно и более интересные штуки делать. Например, вызывать команды MS Acessability API:

# direct use of Windows Phone automation APIs
app_bar_button = driver.find_element_by_id('GoAppBarButton')
driver.execute_script('automation: invoke', app_bar_button)

list_box = driver.find_element_by_id('MyListBox')
si = {'v': 'smallIncrement', 'count': 10}
driver.execute_script('automation: scroll', list_box, si)

Это позволяет при необходимости использовать точные средства прокрутки элементов вместо симуляции жестов.
А ещё можно задать значение публичного свойства, хотя мы и не рекомендуем так делать в тестах:

text_box = driver.find_element_by_id('MyTextBox')
driver.execute_script('attribute: set', text_box, 'Width', 10)
driver.execute_script('attribute: set', text_box, 'Background.Opacity', 0.3)

Но бывают ситуации, когда это оправдано. Например, в наших тестах — чтобы быстро и точно переместить карту в нужную позицию мы как раз и используем этот API вместо перемещения карты жестами.

Это далеко не полный список команд поддерживаемых драйвером. Более подробный список и заметки по командам читайте в wiki.

Итак, соберем простенький тест на основе всех этих команд:

Код простенького теста
# coding: utf-8
import os

from selenium.webdriver import Remote
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC

class TestAutoSuggestBox(object):
    def setup_method(self, _):
        executor = "http://localhost:{}".format(os.environ.get('WINIUM_PORT', 9999))
        self.driver = Remote(command_executor=executor,
                             desired_capabilities={'app': 'aut.appx'})

    def test_select_suggest(self, waiter):
        self.driver.execute_script("mobile: OnScreenKeyboard.Disable")

        pivots = self.driver.find_elements_by_class_name("Windows.UI.Xaml.Controls.Primitives.PivotHeaderItem")
        pivots[1].click()

        autosuggestion_box = waiter.until(EC.presence_of_element_located((By.ID, 'MySuggestBox')))
        autosuggestion_input = autosuggestion_box.find_element_by_class_name('Windows.UI.Xaml.Controls.TextBox')
        autosuggestion_input.send_keys('A')

        suggestions_list = waiter.until(EC.presence_of_element_located((By.XNAME, 'SuggestionsList')))
        suggestions = suggestions_list.find_elements_by_class_name('Windows.UI.Xaml.Controls.TextBlock')

        expected_text = 'A2'

        for suggest in suggestions:
            if suggest.text == expected_text:
                suggest.click()
                break

        assert expected_text == autosuggestion_input.text

    def teardown_method(self, _):
        self.driver.quit()


В этом примере мы создаем новую сессию. Прячем экранную клавиатуру (для демонстрации и просто чтобы не мешалась).

Переключаемся на вторую вкладку в pivot-элементе. Находим там поле ввода с подсказками и начинаем вводить текст. После этого выбираем одну из подсказок в списке и проверяем, что значение в поле ввода совпадает со значением подсказки. И, наконец, закрываем сессию.

Запуск тестов

Осталось самое простое. Запускаем Winium.StoreApps.Driver.exe (который можно скачать с Гитхаба), запускаем тесты любимым тестранером и наслаждаемся магией.

Demo.

Winium CodedUI


Основные фичи и ограничения драйвера. После Codefest 2015 у нас появилась идея создать прототип selenium-драйвера, оборачивающего CodedUI. Идея была воплощена в жизнь и теперь доступна на Гитхабе.

Основные фичи:

  • не требует модификации тестируемого приложения. Вы даже можете тестировать предустановленные или скачанные из магазина приложения;
  • работает и на эмуляторах, и на устройствах;
  • совместим с Json Wire Protocol;
  • поддерживает нативные приложения;
  • уже имеет ограниченную поддержку гибридных приложений.

Ограничения:

  • ранний прототип, поэтому возможны некоторые проблемы со стабильностью;
  • требуется лицензия на Visual Studio Premium или выше (для 2013, для 2015 — Business);
  • одна сессия (но мы портируем многосессионность из StoreApps по мере возможности).

Как это все работает

Работает всё на тех же принципах, что и в StoreApps, но теперь вместо внедрения сервера в приложение мы запускаем сервер как отдельный фоновый процесс через vs.test.console и CodedUI. Этот тест-сервер имеет доступ к телефону и UI запущенных приложений напрямую через Accessibility API (например, для поиска элементов) и через CodedUI API (например, для жестов).


Структура взаимодействия между тестами, драйвером и тестируемым приложением.

Как подготовить приложение и писать тесты

Так как в таком подходе не надо изменять тестируемое приложение, то можно тестировать как релизные версии приложений, так и предустановленные приложения. Т.е. эта версия драйвера максимально приближена к философии Аппиума. Это и плюс, и минус — потому что накладывает ограничения на доступ к некоторым внутренностям приложения.
Никакой особой подготовки приложения не требуется. Тесты пишутся и запускаются также, как и для Winium.StoreApps.

Посмотрите демо-ролик, в котором мы автоматизируем создание события в стандартном предустановленном приложении «Календарь».

Код примера
from time import sleep
from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait

def find_element(driver, by, value):
    """
    :rtype: selenium.webdriver.remote.webelement.WebElement
    """
    return WebDriverWait(driver, 5).until(expected_conditions.presence_of_element_located((by, value)))

winium_driver = webdriver.Remote(
    command_executor='http://localhost:9999',
    desired_capabilities={
        'deviceName': 'Emulator',
        'locale': 'en-US',
    })

# AutomationId for tiles can not be used to find tile directly,
# but can be used to launch apps by switching to window
# Actula tile_id is very very very long
# {36F9FA1C-FDAD-4CF0-99EC-C03771ED741A}:x36f9fa1cyfdady4cf0y99ecyc03771ed741ax:Microsoft.MSCalendar_8wekyb3d8bbwe!x36f9fa1cyfdady4cf0y99ecyc03771ed741ax
# but all we care about is part after last colon
winium_driver.switch_to.window('_:_:Microsoft.MSCalendar_8wekyb3d8bbwe!x36f9fa1cyfdady4cf0y99ecyc03771ed741ax')

# accept permisson alert if any
try:
    accept_btn = winium_driver.find_element_by_name("allow")
    accept_btn.click()
except NoSuchElementException:
    pass

# now we are in calendar app
new_btn = find_element(winium_driver, By.NAME, "new")
new_btn.click()
sleep(1)  # it all happens fast, lets add sleeps

subject = find_element(winium_driver, By.ID, "EditCardSubjectFieldSimplified")
subject.send_keys(u'Winium Coded UI Demo')
sleep(1)

# we should have searched for LocationFiled using name or something, but Calendar app uses slightly different
# classes for location filed in 8.1 and 8.1 Update, searching by class works on both
location = winium_driver.find_elements_by_class_name('TextBox')[1]
location.send_keys(u'Your computer')
sleep(1)

save_btn = find_element(winium_driver, By.NAME, "save")

save_btn.click()
sleep(2)

winium_driver.close()
winium_driver.quit()


Что нам дал Winium


Что нам дало создание этого инструмента вместо использования специфичных для платформы?

Теперь все наши мобильные команды используют единообразные инструменты, что позволяет нам легко делиться опытом и строить единый инструментарий и инфраструктуру для запуска наших тестов и не распылять внимание по платформам. В частности, один опытный инженер смог без проблем перейти из автоматизации iOS в Windows Phone, поделиться опытом и обучить инженеров по тестированию, что значительно подняло уровень проекта.

С точки зрения инфраструктуры это позволило нам сфокусироваться на создании одного инструмента (vmmaster) для предоставления повторяемой среды для тестирования по требованию, но это уже тема для отдельной статьи.

Но самое главное, что это позволило нам начать объединение наших тестов для разных платформ в один проект (демо).

Короче, теперь у нас…

  • меньше велосипедов;
  • меньше дублирования кода;
  • поддержка проще;
  • лучше качество кода;
  • «расшаривание» знаний;
  • системный подход;
  • более быстрая разработка.

И конечно же, всё это opensource, поэтому вы тоже можете использовать этот инструмент для автоматизации тестирования ваших Windows Phone приложений. Причем Winium.StoreApps можно использовать полностью бесплатно, скачав последний релиз и установив эмуляторы или Visual Studio Community с мобильным SDK. А вот для Winium.CodedUi понадобится платная версия Visual Studio Premium или выше.

Еще раз ссылка на репозиторий и другие opensource продукты 2ГИС.

Комментарии (9)


  1. bbidox
    19.11.2015 11:39

    Интересная штука, надо попробовать. Подскажите, а управление webview доступно?


  1. NickAb
    19.11.2015 12:06

    В Winium.StoreApps пока нельзя, но это есть у нас в планах. И об этом нас уже спрашивали, поэтому вполне вероятно, что мы вскоре займемся этим вопросом.
    В Winium.StoreAppsCodedUI (который лучше было бы просто назвать Winium.Mobile.CodedUI) с WebView можно работать, но очень ограничено, т.к. работа идет через инструменты для обеспечения доступности приложения. Т.е. вы не сможете получить полный исходный html код страницы или искать по css селектору, но вы сможете найти элемент по id и кликнуть в него.


  1. Shersh
    19.11.2015 12:53

    Windows 10 планируется поддержка?


    1. NickAb
      19.11.2015 12:57

      Поддержку Windows 10 Mobile добавили буквально на днях. Точнее, сам сервер автоматизации и так работал с 10-кой, проблема оказалась только в инструменте деплоя, т.к в 10-ке изменился формат манифеста appx файлов. Скоро релиз выпустим.


  1. bayandin
    19.11.2015 13:28

    Огонь же! Молодцы!


  1. gaploid
    20.11.2015 12:05

    А нет ли инструментов, которые готовили бы тесты/сессии записывая действия пользователя?


    1. NickAb
      20.11.2015 14:36

      К сожалению таких инструментов у нас нет, поскольку мы тесты всегда пишем как код. Так проще поддерживать большие наборы тест-кейсов при частых изменениях трестируемого приложения. Но, вообще, это можно сделать, доработав Inspector. Над ним хорошо бы поработать, т.к. он выполняет свои минимальные функции, но внутри он написан на очень скорую руку.


      1. gaploid
        20.11.2015 14:48

        Спасибо за ответ, а вообще может быть есть сторонние инструменты, которые могут делать такие smoke тесты из записанных действий? Для команды 1-2 разработчика не просто писать тесты:) А сделать, к примеру тест из записи действий пользователя, где протыкиваются все экраны и кнопки было бы очень полезно и быстро.


        1. NickAb
          20.11.2015 15:18

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