Тестирование проекта Ext.Js на Selenium

в 10:50, , рубрики: .net, extjs, Oreodor, selenium, Библиотека ExtJS/Sencha, тестирование, метки: , , ,

Есть три вещи, которые можно делать бесконечно:
1. Наблюдать, как горит огонь
2. Наблюдать, как бежит вода
3. И наблюдать, как кто-то работает

В нашем случае, наблюдать за тем, как крутятся наши over 9000 тестов. Особенно красиво смотрятся Selenium тесты. Выглядит, как будто бешеный тушканчик с вечным двигателем внутри сел тестировать систему.

Не знаю как вас, но меня это затягивает:

Остаток статьи я расскажу маленькую success-story о том, как мы организовали наше тестирование на Selenium

Это был наш второй подход к снаряду. В первый раз, еще в 2009 году, сыпалось все:
• Web драйвер работал только с IE.
• Иногда подвисал при открытии браузера
• Иногда не мог закрыть браузер
• И самое главное: встроенная запись тестов через расширение браузера (т.н. «накликивание») делала плохие тесты для Ext.Js оболочки.

Наш проект написан с использованием Ext.Net – оболочки Ext.Js для .NET. А Ext.Js был явно слишком сложен для Selenium. Он генерировал для клиента случайные идентификаторы элементов, и дополнительно случайные идентификаторы объектов так-же генерировали мы сами (что необходимо для параллелизма и высоких нагрузок нашей платформы).

Вроде бы тест можно легко накликать мышкой, но время жизни этого теста составляло – считанные дни. Хуже того, когда падал тест, то понять причину падения часто было сложней, чем просто перенакликать его заново (что и делали пару раз).

А еще тесты были медленные! Представьте сценарий:
1. Мы вбиваем строку поиска в таблицу с данными
2. Сервер возвращает ответ – пустой грид (что иногда может быть правильным)
3. И вдогонку шлет сообщение об ошибке, если она произошла.

Ext.Js с его асинхронными запросами на любой клик устроен так, что для многих вызовов нам нужно ждать сообщение об ошибке секунд 10 и если мы его не получали, то считали, что поиск отработал нормально.
10 секунд здесь, 10 там и в итоге сам тест на 90% времени состоит из ожидания.

И это еще не все. А вы пробовали разобраться в причинах падения накликанного теста из Selenium лога? Вот так, чтобы повторно не запускать тест, а окинуть взглядом лог и понять суть проблемы. У меня – не получалось.

Через пару месяцев стало понятно, что такое тестирования отнимает у нас больше времени чем приносит пользы и Selenium тесты мы убили.

Проект тем временем все рос, количество функций измерялось тысячами и вот, год назад героический selmaril вернулся к этой проблеме с обновленным Selenium и обновленным пониманием, как тестировать Ext.Js

Его решение было: писать API.

Мы решили отказаться от записи тестов мышкой, но прийти к возможности записывать NUnit(!) тесты с короткой нотацией в виде:
1. Отфильтровать пользователей по дате создания = сегодня.
2. Отсортировать по имени
3. Открыть первую запись
4. Изменить пароль.
5. Сохранить.
6. Нажать ОК, если выскочит окно «Вы уверены».

Для этого было нужно API над Selenium и NUnit, которое само состояло бы из очень простых элементов, но позволяющих оперировать не мышкой и DOM моделью, а с объектами интерфейса Ext.Js.

Месяц напряженной работы, и первая версия API появилась.

Вот образец теста:

        /// <summary>
        /// DocumentOperationGridOperationTest
        /// </summary>
        [Test]
        public void DocumentOperationGridOperationTest()
        {
            var baseDocName = typeof(BaseInDocument).ModelName();
            var implDocName = typeof(BaseInDocumentImpl).ModelName();

            using (var grid = Env.Navigation.OpenList(implDocName))
            {
                var operationCaption = "Документик_Создайся";
                grid.Toolbar.Click(operationCaption);

                var docForm = Env.TabPanel.GetForm(implDocName);

                docForm.Toolbar.Click("Создание", "Завершить операцию {0}".FormatWith(operationCaption));

                docForm.Close();
            }

            using (var grid = Env.Navigation.OpenList(baseDocName))
            {
                var operationCaption = "Редактируем_Наследник";
                grid.Data.First().Select();                
                grid.Toolbar.Click("Операции", operationCaption);
                grid.Toolbar.Click("Открыть");                
                var docForm = Env.TabPanel.GetForm(implDocName);
                var fieldValue = docForm.GetField<TextField>("SomeNewField");
                Assert.False(string.IsNullOrEmpty(fieldValue.GetValue()), "Не заполнилось поле которое должно быть заполнено в классе операции, то есть не вызвался класс операций для документа");
                grid.DeleteFirstRow();
                docForm.Close();
            }
        }

А вот как выглядит реализация API

        /// <summary>
        /// Получить элемент из контекстного меню
        /// </summary>
        /// <param name="fieldName">Системное имя поля сущности, в ячейке которой у данной записи надо вызвать контекстное меню</param>
        /// <param name="buttonCaption">Путь к элементу контекстного меню, по которому надо получить элемент (кнопку действия например)</param>
        /// <returns>Найденный элемент в контекстном меню</returns>
        public IToolbarElement GetContextMenuItem(string fieldName, string[] buttonCaption)
        {
            var buttonsFullPath = Oreodor.Utils.EnumerableExtensions.ToString(buttonCaption, "->");
            Env.AddHistory("Получить элемент в контекстном меню ячейки для записи с Id - SysName = {0} - {1}, для колонки {2}, по пути '{3}'".FormatWith(
                                                                                                                                                         Id,
                                                                                                                                                         this.Data.ContainsKey("SysName") ? this.Data["SysName"] : string.Empty,
                                                                                                                                                         fieldName,
                                                                                                                                                         buttonsFullPath));

            IToolbarElement menuItem = null;

            if (buttonCaption.Length > 0)
            {
                var menuItems = new List<IToolbarElement>();
                if (IsContextMenuVisible(fieldName))
                {
                    menuItems.AddRange(GetCurrentContextMenu());
                }
                else
                {
                    menuItems.AddRange(ShowContextMenu(fieldName));
                }

                menuItem = Toolbar.CheckItem(menuItems, buttonCaption[0]);

                if (buttonCaption.Length > 1)
                {
                    for (var i = 1; i < buttonCaption.Length; i++)
                    {
                        var menuCaption = buttonCaption[i - 1];
                        var itemCaption = buttonCaption[i];

                        var menu = menuItem.Menu.ToList();

                        Assert.That(menu.Count() != 0, "Меню '{0}' не должно быть пустым, т.к. в нем ещё надо найти '{1}'.".FormatWith(menuCaption, itemCaption));

                        var candidateToolbarItem = Toolbar.CheckItem(menu, itemCaption);

                        Assert.That(candidateToolbarItem != null, "Проверка наличия элемента '{0}' в меню '{1}'. Найдены следующие элементы: {2}".FormatWith(itemCaption, menuCaption, menu.Aggregate(", ", (aggregated, item) => aggregated + "'" + item.Text + "'")));

                        menuItem = candidateToolbarItem;
                    }
                }
            }

            return menuItem;
        }

Также была решена важная проблема – быстрое понимание ошибки из лога TeamCity. Api генерирует вот такой отчет по каждому тесту.

Итог: сейчас мы покрыли около 80% поведения интерфейса системы. Делюсь выжимкой нашего опыта, для тестирования Ext.Js на Selenium Вам нужно писать свою обертку для тестирования для решения следующих проблем:
1. Лаконичность и понятность теста
2. Устойчивость теста к изменением системы
3. Скорость работы тестов
4. Легко разобраться в причинах поломки теста
Тесты, созданные накликиванием из интерфейса браузера скорее всего Вам не подойдут.

P.S.
Если у вас Ext.Js проект и вы решите покрыть его Selenium тестами — обращайтесь за советами, постараемся помочь.

Автор: Joshua

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js