Как мы тестировали drag&drop в HTML5

    Так или иначе, все сталкивались с ситуациями, когда в банальной обстановке вдруг происходило что-то необычное. Примерно такой случай произошел с нами при тестировании нового приложения на проверенном сто раз окружении. Сюрпризом для нас стало использование некоторых возможностей HTML5 в работе front-end’а, а точнее невозможность стандартными средствами Selenium WebDriver автоматизировать тестирование drag&drop операций. Об этом опыте мы хотим рассказать.



    Представьте проект, который технологически очень похож на предыдущий (на наш взгляд это дало небольшой негативный эффект с точки зрения правильного понимания и анализа появившейся проблемы), но версия Angular между проектами изменилась с 1.x на 5.x и добавилась библиотека Selenide для UI автотестов.

    Разрабатываемое веб-приложение имело страницу с неким набором сущностей, которые можно было перемещать между собой. Каково же было наше удивление, когда попытка выполнить автотест проверки drag&drop функции средствами Selenide не увенчалась успехом. Казалось бы, что могло пойти не так? На предыдущем проекте на аналогичном тестовом окружении все работало отлично.

    Первым делом проверили работу drag&drop функций Selenide и Selenium в текущем браузере на примере другого веб-приложения. Всё работает. Обновили версии, мало ли…
    Решили проверить то ли мы тащим и туда ли. А ошибиться в выборе элементов при использовании Angular довольно легко. Сели с front-end разработчиком и разобрались, что drag и drop элементы выбраны верно.

    В общем, тестовое окружение исправное, тестовые методы написаны верно, drag&drop «руками» работает, однако автотест не работает. И причин для этого на первый взгляд нет.

    Наконец мы смирились с фактом проблемы и пошли искать решение в интернете. Каково же было наше удивление, когда мы нашли открытую issue Chrome WebDriver #3604 от 04.03.2016. Только задумайтесь, с весны 2016 года официально существует проблема с неработающим drag&drop в Chrome WebDriver, не говоря уже о других браузерах. Нет, он конечно работает, но только не при использовании HTML5. А как выяснилось в процессе анализа проблемы, наше приложение использовало реализацию drag&drop средствами HTML5.

    Каковы же варианты реализации drag&drop для тестирования в условиях HTML5? На просторах интернета было найдено два решения:

    • Использовать Java библиотеку awt.Robot (или какой-то сторонний кликер);
    • Использовать JavaScript.

    Вероятно, мы слегка заработались или закопались в проблеме, но сразу оговорюсь, что первое выбранное решение нам не подошло :)

    Что можно сказать о реализации на Robot:

    • Перехватываем мышь, эмулируя полноценные действия пользователя;
    • Используем Selenium для определения координат элементов;
    • Так как используются элементы Selenium, то не потребуется изменять локаторы. Мы на проекте стараемся использовать xpath;
    • Написан на Java, синтаксис интуитивно понятен, хорошая документация.

    А вот про реализацию на JavaScript пришло на ум нечто такое:

    • Все происходит на JavaScript внутри браузера (действия скрыты от глаз тестировщика и эти действия вмешиваются в код);
    • Из js-библиотек для тестирования drag&drop в интернете была найдена одна, исходники которой найти было не так просто;
    • Найденную библиотеку придется допиливать напильником под свои нужды, так как она реализует только чистый drag&drop. А нам, например, было необходимо drag -> move -> hold -> drop;
    • Реализована библиотека в виде дополнения JQuery, а следовательно потребуется разбираться в структуре jQuery;
    • Придется приводить локаторы к css (jquery не работает с xpath);
    • Невозможно использовать поиск элементов Selenium, придется склеивать локаторы «ручками».

    На первый взгляд первое решение было куда удобнее и было опробовано.

    //Setup robot
            Robot robot = new Robot();
            robot.setAutoDelay(50);
    
            //Fullscreen page so selenium coordinates work
            robot.keyPress(KeyEvent.VK_F11);
            Thread.sleep(2000);
    
            //Get size of elements
            Dimension fromSize = dragFrom.getSize();
            Dimension toSize = dragTo.getSize();
    
            //Get centre distance
            int xCentreFrom = fromSize.width / 2;
            int yCentreFrom = fromSize.height / 2;
            int xCentreTo = toSize.width / 2;
            int yCentreTo = toSize.height / 2;
    
            //Get x and y of WebElement to drag to
            Point toLocation = dragTo.getLocation();
            Point fromLocation = dragFrom.getLocation();
    
            //Make Mouse coordinate centre of element
            toLocation.x += xOffset + xCentreTo;
            toLocation.y += yCentreTo;
            fromLocation.x += xCentreFrom;
            fromLocation.y += yCentreFrom;
    
            //Move mouse to drag from location
            robot.mouseMove(fromLocation.x, fromLocation.y);
    
            //Click and drag
            robot.mousePress(InputEvent.BUTTON1_MASK);
    
            //Move to final position
            robot.mouseMove(toLocation.x, toLocation.y);
    
            //Drop
            robot.mouseRelease(InputEvent.BUTTON1_MASK);
    

    В общем-то решение рабочее… Однако в процессе его проработки стали понятны его проблемные места.

    • Движение мыши или сворачивание браузера во время выполнения тестов приводит к вмешательству в ход тестов и их падению;
    • Невозможен параллельный запуск тестов средствами JUnit/TestNG. Разве что распараллелить через отдельные таски в CI.
    • Невозможно управлять мышью на удаленной машине через Selenium Grid/Selenoid;
    • В случае падения браузера Robot может запросто что-то нажать/перетащить на рабочем столе или в другом открытом приложении.

    В итоге все же JavaScript реализация...

    Сразу хочется сказать, что проблему использования xpath локаторов удалось решить использованием JQuery-плагина jquery.xpath.js.

    А основным инструментом для js управления операциями drag&drop стала библиотека drag_and_drop_helper.js (исходник тут). Разбирать ее работу смысла особого нет, а вот про то, как мы ее дорабатывали чуть позже.

    Теперь непосредственно о реализации в тестах. В Selenide все просто. Перед началом использования drag&drop требуется загрузить используемые JS библиотеки:

    StringBuilder sb = new StringBuilder();
    sb.append(readFile("jquery-3.3.1.min.js"));
    sb.append(readFile("jquery.xpath.min.js"));
    sb.append(readFile("drag_and_drop_helper.js"));
    executeJavaScript(sb.toString());
    

    Естественно jQuery необходимо загружать в том случае, если ее еще нет в приложении.

    В исходной версии библиотеки достаточно прописать следующее:

    executeJavaScript("$('" + source + "') .simulateDragDrop({ dropTarget: '" + target + "'});");
    

    source и target — это css-локаторы drag и drop элементов.

    Как оговаривалось выше, мы в проекте чаще используем xpath-локаторы, поэтому после небольшой доработки библиотека стала их принимать:

    executeJavaScript("$(document).xpath('" + source + "').simulateDragDrop({ dropTarget: '" + target + "'});");
    

    Теперь, собственно, о библиотеке drag_and_drop_helper.js. В блоке кода simulateEvent есть куски, отвечающие за определенные события мыши. Список возможных событий drag&drop операций в HTML5 приводить смысла нет, эту информацию легко найти.

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

    По аналогии добавили событие dragenter в библиотеку (между dragstart и drop).

    /*Simulating dragenter*/
    type = 'dragenter';
    var dragenterEvent = this.createEvent(type, {});
    dragenterEvent.dataTransfer = event.dataTransfer;
    this.dispatchEvent($(options.dropTarget)[0], type, dragenterEvent);
    

    Однако, этого не достаточно. Ведь событие удержания будет мгновенно закончено. Ставить фиксированную паузу между dragEnter и drop событиями показалось не самым удобным вариантом. Ведь изначально неизвестно, сколько требуется времени приложению на обработку того или иного события, неизвестно число и время проверок в тестах. Задержка между этими событиями должна быть как минимум управляема. Вместо этого мы решили разбить тестирование drag&drop на этапы и не делать эмуляцию полного набора событий мыши, то есть добавить возможность управлять перечнем задействованных событий через параметр.

    И вроде бы все хорошо, новых недостатков не проявилось, да и некоторые старые более не являются таковыми, а главное поставленные задачи выполняются. Казалось бы, все идеально. Однако современные средства разработки закладывают обработку далеко не двух событий и используют различные параметры перемещаемого элемента. Допустим, у нас данное решение при выполнении drag&drop вызывает ошибки dragStartListener. Но так как оно ничего не ломает, то мы мы ничего больше и не стали менять. Однако в каком-то другом приложении вероятно придется допиливать и этот момент.

    Хотим подвести итог вышесказанному. Удивительно, но факт! HTML5 вышел в далеком 2013 году, браузеры поддерживают его уже тоже не первый год, разрабатываются приложения заточенные под него, а вот webDriver, увы, до сих пор не умеет использовать его возможности. И тестирование операций drag&drop приходится реализовывать сторонними средствами, усложнять архитектуру и идти на всякие ухищрения. Да, такие средства есть и «танцы с бубном» делают нас только сильнее, но хочется все же иметь рабочее решение из коробки.

    По нашему опыту можем сказать, что подобные проблемы на сегодняшний день встречаются не так часто, хотя drag&drop применяется повсеместно. Вероятно, дело в выборе технологий разработки веб-приложений. Однако, процент приложений с использованием HTML5 неуклонно растет, развиваются фреймворки, и было бы замечательно, если разработчики браузеров и драйверов к ним тоже не отставали.

    P.S. Ну и напоследок немного лирики. Хочется посоветовать всем по возможности не принимать во внимание банальность ситуации или близость тестового окружения к какому-то шаблону при анализе проблем. Это может привести к неправильным выводам или потере времени.
    SimbirSoft
    74,00
    Лидер в разработке современных ИТ-решений на заказ
    Поделиться публикацией

    Комментарии 2

      +1
      Постараюсь не язвить, а по делу

      1. Robot UI у вас вообще вряд ли бы заработал на CI. И параллелизация не при чем. Насчет линукса с фреймбуфером не уверен, а вот на винде в дженкинсе работающем как сервис точно ничего бы не вышло.

      2. тянуть jquery — так себе затея. Решение без JQuery гуглится на минут 10 дольше
      gist.github.com/druska/624501b7209a74040175

      3. использовать еще какую-то библиотеку для xpath? ну хорошо бы залезть в документацию вебдрайвера иногда и узнать, что в executeScript можно передавать WebElement, он будет преобразован в dom-элемент. Обернуть его при помощи JQuery тоже нет проблем
      seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/JavascriptExecutor.html

      4. проблема не то, чтобы частая. Но мы даже своим студентам (точнее будет сказать интернам) даем такое задание.
        0
        Перед тем как ответить, сразу оговоримся, в этой статье мы лишь описали наш ход мыслей при поиске решения в условиях ограниченного времени. Нам хотелось привлечь внимание к данной проблеме и отсутствию легкодоступных однозначных и простых решений. Ресурсов, где описана эта проблема и варианты ее решения по пальцам перечесть. И статей как-то тоже не видно…
        1. Да, действительно robot на CI видимо не вариант. До реализации в CI этого решения у нас не дошло, по озвученным в статье причинам.
        2. Найти нативное решение все же не так просто. А уж написать самому тем более. Согласны, оно выглядит попроще и читабельнее. Однако если память нам не изменяет, запустить его с ходу у нас не вышло. Поэтому выбрали другое решение.
        3. Про данную возможность было известно, но в условиях ограниченного времени лезть в JQuery и изменять что-то в найденном нами решении не хотелось.
        Если применить предложенное выше нативное решение, то, пожалуй, благоразумно будет использовать и передачу WebElement.
        4. В том то и дело, что проблема, как ни странно, не особо частая. И давать частные случаи при подготовке специалиста возможно только тогда, когда всеми основными знаниями он уже владеет в совершенстве. Увы, такое, на наш взгляд, может себе позволить не каждый.
        Спасибо за комментарий. Хотя идеальным решением было бы исправление WebDriver.

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

      Самое читаемое