SOM, Sikuli, Jython и тестирование нативных UI-приложений

В этом посте я бы хотел рассказать о разработанной микробиблиотеке SOM для jython, которую планирую применить для тестирования одного нативного легаси-проекта. SOM является враппером над Sikuli.

C нативными приложениями такая беда, что там нет selenium’a, который более менее частично решает проблемы автоматизации. Хорошо, если разработчики нативного приложения озаботились качеством и сделали инфраструктуру для функциональных тестов н-р на gtest. Плохо, когда это не так. Тогда и приходится выкручиться, используя такую библиотеку как Sikuli, которая базируется на распознавании изображений на экране и выполнении действий над распознанным участком экрана (посыл кликов или текста).

Поигравшись с sikuli и jython, стало понятно, что это довольно низкоуровневая библиотека и писать спагетти-код совсем не хочется. А хочется высокоуровневый API и интуитивно понятные методы. Так появился SOM.

Для контроля над элементом SOM использует контрольные точки - небольшие скриншоты элемента, которые его однозначно характеризуют. Используя sikuli, SOM определяет область экрана внутри которого лежат эти контрольные точки, и считает его областью элемента. При этом можно вкладывать один элемент в другой, и тогда SOM будет искать один элемент только внутри области другого, соблюдая иерархию.

Подробную информацию можно посмотреть в readme проекта.

Вот пример, как выглядит декларация приложения, шагов и тестов с использованием SOM и jython:

# application and elements registration
def create_ff():
    som.Element.set_elements_dir(
        os.path.join(os.getcwd(), "example", "elements"))
    
    ff = som.Application("firefox")
    ff.set_children(
        address_bar=som.Element("address-bar"),
        yandex=som.Element("yandex"),
        github=som.Element("github"))

    return ff

# steps to manage application
class Steps(object):

    def __init__(self):
        som.load_sikuli(tuneup_sikuli)
        self._ff = create_ff()

    def launch_firefox(self):
        self._ff.launch()

    def open_web_page(self, url):
        self._ff.address_bar.set_text(url, enter=True)

        if url.startswith("https://yandex"):
            self._ff.yandex.wait_for_visible()
            self._ff.yandex.screenshot()

        if url.startswith("https://github"):
            self._ff.github.wait_for_visible()
            self._ff.github.screenshot()

    def close_firefox(self):
        self._ff.close()

# tests
class MyTest(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        global S
        S = steps.Steps()

    def setUp(self):
        S.launch_firefox()
    
    def tearDown(self):
        S.close_firefox()

    def test_open_yandex(self):
        S.open_web_page("https://yandex.ru")
    
    def test_open_github(self):
        S.open_web_page("https://github.com")

Видео с запуском тестов:

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

1 лайк

@Sergei_Chipiga в коде использованы google style докстринги? Если да, то можно ли линк на спеку по оным попросить. Не встречал ранее типов “num” в докстрингах, а официальная гугло-дока (да и дока сфинкса) о таких вещах умалчивает (как впрочем и о многих других)

Я использовал стиль из sphinx-doc, насколько помнил, num это я так условно обозначил number.

А расскажите, почему именно Sikuli? Почему не opencv + pyautogui например?

Про Sikuli я слышал давно, поэтому решил взять за основу его. А за ваш вариант спасибо, глянул вчера, проекты показались весьма любопытными, чтобы в них покопаться. Кстати, одно время я работал над автоматизацией десктопных приложений, тогда мы использовали проект uisoup на python.