UI-тестирование: проверка системы на разных разрешениях

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

    В этой статье на Хабр расскажем о нашем опыте тестирования больших экранов инструментами Protractor, Zalenium и Selenium-grid. Как мы поэтапно внедряли эти инструменты автоматического UI-тестирования и через какие сложности нам пришлось пройти.



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



    И следующие разрешения экранов:



    Каждый раз после выпуска новой версии нужно проверить её на работоспособность в этих браузерах и разрешениях.

    Для лучшего понимания, как обычно работают UI-тесты в нашем случае, рассмотрим простой кейс: тестирование функционала заявки на восстановление пароля. Соответственно, есть две страницы. На одной нужно указать логин или почту, куда придёт письмо со ссылкой на смену пароля. После отправки заявки пользователь переходит на страницу с подтверждением, что всё прошло хорошо.


    Форма восстановления пароля


    Страница, на которую происходит редирект после успешной отправки заявки

    Тестирование этого механизма в упрощённом виде выглядит так:



    Этот же сценарий в виде кода:

    it('Отправить заявку на смену пароля', async function(): Promise<boolean | void> {
         await browser.get(browser.baseUrl);
         await element(by.name('EmailOrLogin')).sendKeys(userLogin);
         await element(by.buttonText('Отправить')).click();
         const screenshot = await browser.takeScreenshot();
         const canonicalScreenshotPath  = <путь до эталонного скриншота>;
         await compareScreenshot(screenshot, canonicalScreenshotPath);
    });

    Как видно, код теста выглядит достаточно просто, в нём буквально повторяется то, что было изображено на блок-схеме.

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

    Выбранный инструментарий


    Начали с Chrome


    Для автоматизации тестирования мы выбрали Protractor — это e2e фреймворк для тестирования Angular приложений. У нас приложение как раз на Angular. С Protractor мы сделали два типа тестов:

    1. На общий функционал: Открывается форма подачи заявок, заполняется данными и отправляется заявка, после чего мы проверяем, что произошел редирект на реестр всех заявок.
    2. Скриншот-тесты: Protractor позволяет в любой момент сделать скриншот экрана. Воспользовавшись отдельной библиотекой blue-harvest, мы сравнивали два скриншота: эталонный и реальный. Если они не совпадают, тут же создаётся скриншот, на котором видны все различия, прямо как в Git.

    Например, если продолжать разбирать пример, описанный выше.


    Скриншот с неверным сообщением о ссылке


    Различия (текст и вёрстка кнопки) на эталонном и актуальном скриншотах выделены сиреневым

    Если на странице есть динамически изменяющиеся данные (например, дата или номер заказа), нужно накладывать на них маску — такой функционал есть в библиотеке blue-harvest. В противном случае сравнение скриншотов выдаст отрицательный результат.

    Добавляем FF и разные разрешения


    Со временем наши требования к существующей системе UI-тестирования выросли:

    • запускать тесты не только в Сhrome, но и в FF,
    • проводить скриншот-тестирование не на одном разрешении, а на самых популярных,
    • запускать тесты параллельно.

    Итак, давайте сделаем так, чтобы наши тесты выполнялись параллельно и в Chrome, и в FF.

    Для этого в конфиге Protractor’a capabilities заменяем на такое:

    multiCapabilities: [
            {
                shardTestFiles: true,
                maxInstances: 2,
                browserName: 'chrome'
            },
            {
                shardTestFiles: true,
                maxInstances: 2,
                browserName: 'firefox'
            }
        ]

    Тут всё очевидно, за что отвечает каждая настройка. Внимания заслуживает только
    shardTestFiles: true
    /** * If this is set to be true, specs will be shared by file (i.e. all files to be run by this set of capabilities will run in parallel). * Default is false. */

    Этим флагом мы обеспечиваем параллельный запуск всех spec’ов во всех браузерах, которые указаны в multiCapabilities.

    Под словом specs скрывается параметр конфига, определяющий, по какому шаблону искать файлы с тестами.

    exports.config = {
     ...
        specs: ['./e2e/**/*.spec.ts'],
     ...
    }

    Вполне логичной выглядит теория разделения всех тестов на несколько spec-файлов, т.к. параллелизм работает по ним (например, завести для каждой фичи приложения свой spec файл).

    Помимо этого, в Protractor’e присутствует возможность выделить сьюты (suites).

    exports.config = {
    ...
       suites: {
            suite01: './e2e/**/suite01.*.spec.ts',
            suite02: './e2e/**/suite02.spec.ts',
            suite03: './e2e/**/suite03.spec.ts'
        },
    ...
    }

    Как можно понять из этой части конфига, при запуске одного сьюта выполняются тесты только из одного spec файла. Это наверняка вам пригодится, если хотите прогнать тесты только для одной части приложения.

    Что касается тестирования приложения на разных разрешениях, то мы пошли следующим путём. В качестве подопытных разрешений были выбраны одни из самых популярных: 1920х1080, 1366х768, 1440х900, 768х1024. При запуске каждого теста изначально выполнялись все необходимые действия, а затем выполняется серия проверок скриншотов.



    Подключаем IE и полноразмерные скриншоты


    Скриншот-тесты, которые у нас получились, к сожалению, работали только на основании изначально видимой части страницы. И если на странице присутствовала прокрутка, то всё, что ниже, оставалось неисследованным на предмет поехавшей вёрстки.

    Кроме этого, нам нужно также поддерживать IE, на который наши тесты на текущий момент не были рассчитаны. Мы начали искать адекватную возможность запускать тесты в IE и делать полноразмерные скриншоты.



    Мы остановились на двух решениях: Zalenium и Selenium-Grid. Опустим описание того, кто из них чем хвастается, и расскажем о том, что нашли/не нашли в том и ином решении.

    Zalenium: это решение запускается в Docker-контейнере и вслед за основным контейнером поднимается N других контейнеров — для запуска браузеров. Позволяет при выполнении docker run указать screenWidth, screenHeight и получить размер экрана, например, 1920х6000. Отсутствует поддержка IE.


    Два браузера с размерами экрана 1920х6000

    Selenium-grid: очень легко запустились тесты в IE.
    Из минусов: нет возможности установить размер окна браузера любого размера.

    Итого: Zalenium + Selenium-grid
    Мы остановились на связке Zalenium + Selenium-grid: через Zalenium было решено гонять тесты в Chrome и FF, а через Selenium-grid проверять минимальную работоспособность приложения в IE.

    Что интересного встретили в процессе


    1. Использование formControlName в качестве локатора элементов

    Используя поиск по formControlName, можно легко искать нужные поля на форме и заполнять их тестовыми данными. Локатор по formControlName не является встроенным, но несложно дописывается:

    exports.config = {
    ...
    onPrepare() {
            require('ts-node').register({
                project: 'e2e/tsconfig.e2e.json'
            });
            jasmine.getEnv().addReporter(new specReporter({ spec: { displayStacktrace: true } }));
            addFormControlNameLocator(protractor);
        }
    ...
    }
    
    function addFormControlNameLocator(protractor) {
        protractor.by.addLocator('formControlName', function(value, optParentElement) {
            var using = optParentElement || document;
            return using.querySelectorAll('[formControlName="' + value + '"]');
        });
    }

    2. Различие в работе глобально и локально установленного webdriver-manager в Protractor

    При установке Protractor’a в систему (официальная документация советует устанавливать Protractor глобально), помимо самого фреймворка для тестирования, мы получаем и установленный webdriver-manager, который предоставляет драйвера для браузеров для запуска тестов, с которым и словили одну из проблем.

    После перехода в папку проекта и выполнения webdriver-manager update скачиваются драйвера для Chrome, FF, IE. После этого при запуске тестов схватывали ошибку:

    No update-config.json found. Run 'webdriver-manager update' to download binaries.

    Мы поняли, что ошибка исчезает, если упаковать webdriver-manager update в npm script:

    {
    ...
       scripts:{
    	“webdriver-update”: “webdriver-manager update”
       }
    ...
    }

    и запустить через npm run webdriver-update, то и исходники драйверов, и упомянутый выше update-config.json попадают в папку проекта.

    3. Запуск Zalenium через docker-compose

    Документация Zalenium сопровождается примерами docker run команды и docker-compose файла. Взлетает всё почти как по мановению волшебной палочки. Единственная проблема, с которой мы столкнулись, возникла при запуске через docker-compose файл
    /var/run/docker.sock is not a valid windows path. Решение тут.

    4. Видимость элементов Protractor’ом

    В ходе наших экспериментов в один момент перед нами встала задача протыкать некоторое меню на страничке, т.е. перейти на каждую страницу меню и сделать скриншот.



    После обработки первого элемента списка тесты начали падать, кидаясь ошибкой о том, что по некоторым координатам не на что кликать. Как выяснилось, независимо от того, что изначально были выбраны все элементы списка, после того, как они стали действительно невидимы для наших глаз, они пропали и для глаз Protractor’a.

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

    5. Изменение разрешения в IE (Selenium-grid)

    Как вы помните, в Chrome и FF мы отлично настроили изменение разрешения экрана. Когда дело дошло до этого в IE, то мы просто словили ошибку: Failed: java.io.IOException: Server returned HTTP response code: 405 for URL: http://localhost:21800/session/8d14426d-6483-4bbf-90b3-2a3ed46ebe9b/window/size.

    Путём долгого дебага удалось выяснить, что ошибка летит ровно в тот момент, когда пытается выполниться код: browser.driver.manage().window().setSize(x, y). Если вы попробуете выполнить browser.driver.manage().window().setPosition(x, y), то словите аналогичную же ошибку, только size поменяется на position. Это приводит нас к тому, что управлять разрешением экрана, когда тесты запущены в IE, невозможно. Решение проблемы — костыли, которыми вам придется обложить ваш код, чтобы не менять разрешение\позицию, когда запущен IE.

    6. Zalenium и WebSocket

    Собрав локально все шишки, было решено выносить Zalenium в корпоративную инфраструктуру. Контейнер заведен, DNS прописан, теперь все могут гонять тесты в Zalenium’e, просто указав путь до него в своём конфиге Protractor’a. Красота, да? Но не тут-то было.

    В этом случае всё было развернуто уже на Linux машине, в качестве сервера использовался Nginx. Контейнер с Zalenium’ом без проблем поднимался, поднимал следом ещё N контейнеров для запуска браузеров, но… связи с ними установить не мог. Несложно было обнаружить, что родительский контейнер пытается общаться с контейнерами браузеров через протокол WebSocket, а Nginx такое по умолчанию не мог. Лечение очень простое.

    location / {
       proxy_pass some_url;
       proxy_set_header Upgrade $http_upgrade;
       proxy_set_header Connection “upgrade”;
    }

    9. Запуск Selenium-Grid

    Для запуска Selenium-Grid нам понадобятся:

    • selenium-server,
    • драйвер IE, который легко получить через webdriver-manager update --ie32.

    запуск сервера:

    java -jar selenium-server-standalone.jar -role hub -hubConfig selenium-server-config.json

    selenium-server-config.json
    {
      "host": "127.0.0.1",
      "maxSessions": 5,
      "port": 4445,
      "cleanupCycle": 5000,
      "timeout": 300000,
      "newSessionWaitTimeout": -1,
      "servlets": [],
      "prioritizer": null,
      "capabilityMatcher": "org.openqa.grid.internal.utils.DefaultCapabilityMatcher",
      "throwOnCapabilityNotPresent": true,
      "nodePolling": 180000,
      "platform": "WINDOWS"
    }

    запуск драйвера IE:

    java -Dwebdriver.ie.driver=<путь до драйвера> -jar selenium-server-standalone.jar -role node -nodeConfig .\\ie-driver-config.json

    ie-driver-config.json
    {
        "capabilities": [
            {
                "browserName": "internet explorer",
                "maxInstances": 1,
                "platform": "WINDOWS",
                "webdriver.ie.driver": "C:/Program Files (x86)/Internet Explorer/iexplore.exe"
            }
        ],
        "cleanUpCycle": 2000,
        "timeout": 30000,
        "port": -1,
        "role": "node",
        "hub": "http://127.0.0.1:4445/grid/register/",
        "register": true,
        "maxSessions": 5
    }

    Обратите внимание на использование одинаковых портов при запуске сервера и в hub URL’e драйвера. В качестве seleniumAddress в конфиге protractor’a используем http://127.0.0.1:4445/wd/hub.

    Выводы


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

    Как встроили тесты в рабочий процесс


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

    Мы не стали внедрять UI-тесты в CI, т.к. выполняются они достаточно долго, чтобы позволить себе ждать их выполнения на каждый билд.

    Для тестирования локальной версии приложения или же задеплоенного на одну из тестовых сред (внутреннюю или заказчика) достаточно поменять baseUrl в конфиге Protractor’a. Поэтому тесты может запустить и разработчик, и тестировщик.
    Разработчик — когда есть необходимость выполнить тесты, например, для локальной версии приложения в процессе разработки новой фичи. QA-специалист — например, в качестве smoke-тестирования после деплоя на прод, либо как проверка по верхам после кучи рефакторинга.

    В завершении хотим акцентировать:

    • Помните, что UI-тесты не избавят вас от необходимости проводить детальное ручное тестирование. Автоматика только помогает покрыть по площадям.
    • Используйте автоматику разумно, в первую очередь для Smoke-тестирования. Покрывать все и вся нет смысла — дорого и сложно поддерживать в актуальном состоянии
    • Пишите просто. если ваши UI-тесты выглядят сложно, значит вы что-то делаете не так. Бейте на простые и используйте по назначению (см. пункт 2)

    Мы верим, что автоматизация UI-тестирования хороша, когда делается легко. Так что делитесь с нами своими наблюдениями и удачными инструментами в комментах.

    Полезные ссылки от нас:


    True Engineering
    110,00
    Специалисты по цифровой трансформации бизнеса
    Поделиться публикацией

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

      0
      Спасибо за интересную статью.
      А кто у вас занимается написанием таких тестов? Разработчик, qa-автоматизатор или любой qa может?
      Сколько у вас таких тестов на этом проекте, за сколько они выполняются? Какие «железные» ресурсы нужны для этого?
        0

        На проекте, описанном в статье, у нас 49 подобных тестов. На полный прогон уходит около 20 минут, запускаем по 2 Хрома и FF. Контейнер Zalenium'a развёрнут на виртуалке с 11Гб RAM, ей доступно 16 ядер xeon e5, но, очевидно, что один этот контейнер расходует очень малую часть всего этого. Вопросом минимальных требований железа не задавались.
        Изначальная база для написания тестов была заложена разработчиками, сейчас новые тесты пишет QA-специалист.

        0

        Если команда выполняется через npm run то будут использованы локальный протрактор и драйвер менеджер сложит драйвера локально. Глобальный протрактор будет работать только если указать явно в конфиге пути к соотв. драйверам, например, chromeDriver: '...'. По непонятной причине простого способа заставить протрактор использовать глобальный конфиг нельзя, но если хочется, то можно находить и парсить update-config.json.

          0

          Спасибо за ценный комментарий! Полностью с ним согласны.

          0
          Я бы хотел узнать как запустить IE11 в докерах ибо мы бежим на Jenkins Dockers и как вы понимаете, на них Эксплорер не бежит. Есть решение этой проблемы?

          Спасибо огромоное за статью. Я на данном этапе воюю с Protractor Timeout's & Sleep не хочу слип использовать. Есть на это какие на это взгляды?
            0

            Насчёт IE в докере — загляните вот сюда https://github.com/MicrosoftDocs/Virtualization-Documentation/issues/214. (P.S. ничего хорошего)


            Насчёт задержек. Честно признаться, в тех редких в наших тестах случаях, когда нам чего-то нужно было дождаться (например, серверную валидацию), мы использовали те самые слипы: await browser.sleep(300). Понимание, что это не круто, есть, но пока что оставили так. Если вдруг вы когда-то найдёте более элегантное решение, будем рады услышать :)

              0

              альтернатива это browser.wait, фактически тот же самый слип, только можно выйти из него раньше.

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

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