Есть предложения по тестированию кода extjs в браузере, желательно с селеном?

92

Мы с большим успехом использовали селен для высокоуровневого тестирования веб-сайтов (в дополнение к обширным тестам документации python на уровне модулей). Однако сейчас мы используем extjs для многих страниц, и оказалось, что сложно включить тесты Selenium для сложных компонентов, таких как сетки.

Кто-нибудь имел успех в написании автоматических тестов для веб-страниц на основе extjs? Многие поисковые запросы находят людей с похожими проблемами, но мало ответов. Благодарность!

2001 мм
источник
Хорошие люди из Ext JS были достаточно любезны, чтобы опубликовать эту тему в своем блоге здесь . Надеюсь, это поможет.
NBRed5

Ответы:

173

Самым большим препятствием при тестировании ExtJS с Selenium является то, что ExtJS не отображает стандартные элементы HTML, а Selenium IDE наивно (и справедливо) генерирует команды, нацеленные на элементы, которые просто действуют как декор - лишние элементы, которые помогают ExtJS со всем рабочим столом. Смотри и чувствуй. Вот несколько советов и приемов, которые я собрал при написании автоматического теста Selenium для приложения ExtJS.

Общие советы

Расположение элементов

При создании тестовых примеров Selenium путем записи действий пользователя с помощью Selenium IDE в Firefox, Selenium будет основывать записанные действия на идентификаторах элементов HTML. Однако для большинства интерактивных элементов ExtJS использует сгенерированные идентификаторы, такие как «ext-gen-345», которые могут измениться при последующем посещении той же страницы, даже если не было внесено никаких изменений в код. После записи действий пользователя для теста необходимо вручную выполнить все действия, которые зависят от сгенерированных идентификаторов, и заменить их. Возможны два типа замен:

Замена локатора идентификатора на локатор CSS или XPath

Локаторы CSS начинаются с «css =», а локаторы XPath начинаются с «//» (префикс «xpath =» необязателен). Локаторы CSS менее подробны, их легче читать, и им следует отдавать предпочтение перед локаторами XPath. Однако могут быть случаи, когда необходимо использовать локаторы XPath, потому что локатор CSS просто не может его вырезать.

Выполнение JavaScript

Некоторым элементам требуется больше, чем простое взаимодействие с мышью и клавиатурой из-за сложной визуализации, выполняемой ExtJS. Например, Ext.form.CombBox на самом деле не <select>элемент, а текстовый ввод с отдельным раскрывающимся списком, который находится где-то в нижней части дерева документа. Чтобы правильно имитировать выбор ComboBox, можно сначала смоделировать щелчок по стрелке раскрывающегося списка, а затем щелкнуть по появившемуся списку. Однако поиск этих элементов с помощью локаторов CSS или XPath может быть обременительным. Альтернативный вариант - найти сам компонент ComoBox и вызвать на нем методы для имитации выбора:

var combo = Ext.getCmp('genderComboBox'); // returns the ComboBox components
combo.setValue('female'); // set the value
combo.fireEvent('select'); // because setValue() doesn't trigger the event

В Selenium runScriptкоманду можно использовать для выполнения вышеуказанной операции в более краткой форме:

with (Ext.getCmp('genderComboBox')) { setValue('female'); fireEvent('select'); }

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

Selenium имеет разновидности «* AndWait» для всех команд ожидания загрузки страницы, когда действие пользователя приводит к переходу или перезагрузке страницы. Однако, поскольку выборка AJAX не включает фактическую загрузку страницы, эти команды нельзя использовать для синхронизации. Решение состоит в том, чтобы использовать визуальные подсказки, такие как наличие / отсутствие индикатора прогресса AJAX или появление строк в сетке, дополнительных компонентов, ссылок и т. Д. Например:

Command: waitForElementNotPresent
Target: css=div:contains('Loading...')

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

Command: waitForElementPresent
Target: css=span:contains('Do the funky thing')
Command: click
Target: css=span:contains('Do the funky thing')

Использование произвольных пауз - не лучшая идея, поскольку различия во времени, возникающие в результате выполнения тестов в разных браузерах или на разных машинах, сделают тестовые примеры нестабильными.

Не кликабельные элементы

Некоторые элементы не могут быть активированы clickкомандой. Это потому, что прослушиватель событий фактически находится в контейнере, наблюдая за событиями мыши на его дочерних элементах, которые в конечном итоге всплывают до родительского. Вкладка - один из примеров. Чтобы щелкнуть вкладку a, вы должны смоделировать mouseDownсобытие на метке вкладки:

Command: mouseDownAt
Target: css=.x-tab-strip-text:contains('Options')
Value: 0,0

Проверка поля

Поля формы (компоненты Ext.form. *), Которые имеют связанные регулярные выражения или vtypes для проверки, будут запускать проверку с определенной задержкой (см. validationDelayСвойство, которое по умолчанию установлено на 250 мс), после того, как пользователь вводит текст или сразу после того, как поле теряет фокус - или размывает (см. validateOnDelayсвойство). Чтобы запустить проверку поля после выдачи команды типа Selenium для ввода текста внутри поля, вам необходимо выполнить одно из следующих действий:

  • Запуск отложенной проверки

    ExtJS запускает таймер задержки проверки, когда поле получает события нажатия клавиш. Чтобы запустить этот таймер, просто вызовите фиктивное событие keyup (не имеет значения, какой ключ вы используете, поскольку ExtJS игнорирует его), за которым следует короткая пауза, которая длиннее, чем validationDelay:

    Command: keyUp
    Target: someTextArea
    Value: x
    Command: pause
    Target: 500
    
  • Запуск немедленной проверки

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

    Command: runScript
    Target: someComponent.nameTextField.fireEvent("blur")
    

Проверка результатов проверки

После проверки вы можете проверить наличие или отсутствие поля ошибки:

Command: verifyElementNotPresent   
Target: //*[@id="nameTextField"]/../*[@class="x-form-invalid-msg" and not(contains(@style, "display: none"))]

Command: verifyElementPresent   
Target: //*[@id="nameTextField"]/../*[@class="x-form-invalid-msg" and not(contains(@style, "display: none"))]

Обратите внимание, что проверка «display: none» необходима, потому что после отображения поля ошибки, а затем его необходимо скрыть, ExtJS просто скроет поле ошибки, а не полностью удалит его из дерева DOM.

Советы по отдельным элементам

Щелчок по кнопке Ext.form.Button

  • Опция 1

    Команда: нажмите Цель: css = button: contains ('Сохранить')

    Выбирает кнопку по ее подписи

  • Вариант 2

    Команда: нажмите кнопку Target: css = # save-options

    Выбирает кнопку по ее id

Выбор значения из Ext.form.ComboBox

Command: runScript
Target: with (Ext.getCmp('genderComboBox')) { setValue('female'); fireEvent('select'); }

Сначала устанавливает значение, а затем явно запускает событие выбора, если есть наблюдатели.

Атес Горал
источник
3
Вот несколько советов по руководству: rkapse.blogspot.nl/2009/06/selenium-ide-issues-with-extjs.html
dan-klasson
5

Этот блог мне очень помог. Он довольно много писал по этой теме, и, похоже, она все еще активна. Кажется, парень тоже ценит хороший дизайн.

Он в основном говорит об использовании отправки javascript для выполнения запросов и использовании метода Ext.ComponentQuery.query для извлечения данных так же, как вы это делаете в своем приложении ext внутри. Таким образом, вы можете использовать xtypes и itemIds и не беспокоиться о попытках разобрать любой из безумных автоматически сгенерированных материалов.

Я нашел эту статью особенно очень полезной.

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

JonnyRaa
источник
4

Я тестировал свое веб-приложение ExtJs на селене. Одна из самых больших проблем заключалась в выборе элемента в сетке, чтобы что-то с ним сделать.

Для этого я написал вспомогательный метод (в классе SeleniumExtJsUtils, который представляет собой набор полезных методов для более простого взаимодействия с ExtJ):

/**
 * Javascript needed to execute in order to select row in the grid
 * 
 * @param gridId Grid id
 * @param rowIndex Index of the row to select
 * @return Javascript to select row
 */
public static String selectGridRow(String gridId, int rowIndex) {
    return "Ext.getCmp('" + gridId + "').getSelectionModel().selectRow(" + rowIndex + ", true)";
}

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

selenium.runScript( SeleniumExtJsUtils.selectGridRow("<myGridId>", 5) );

Чтобы это сработало, мне нужно установить свой идентификатор в сетке и не позволять ExtJ генерировать его собственный.

Марко Думич
источник
вы сможете найти сетку, используя ее xtype в Ext.ComponentQuery.query (при условии, что вы расширили сетку и определили xtype для своей собственной сетки). Я рассказал кое-что об этом ниже. Я также обнаружил, что вы можете щелкнуть что-нибудь, используя xpath, чтобы найти td, который содержит идентифицирующее значение для строки
JonnyRaa
4

Чтобы обнаружить, что этот элемент видим, вы используете предложение: not(contains(@style, "display: none")

Лучше использовать это:

visible_clause = "not(ancestor::*[contains(@style,'display: none')" +
    " or contains(@style, 'visibility: hidden') " + 
    " or contains(@class,'x-hide-display')])"

hidden_clause = "parent::*[contains(@style,'display: none')" + 
    " or contains(@style, 'visibility: hidden')" + 
    " or contains(@class,'x-hide-display')]"
Мастер-тест
источник
3

Можете ли вы подробнее рассказать о типах проблем, с которыми вы сталкиваетесь при тестировании extjs?

Одно расширение Selenium, которое я считаю полезным, - это waitForCondition . Если ваша проблема связана с событиями Ajax, вы можете использовать waitForCondition, чтобы дождаться возникновения событий.

Райан Гест
источник
3

Веб-страницы Ext JS может быть сложно протестировать из-за сложного HTML, который они генерируют, как в случае с сетками Ext JS.

HTML5 Robot решает эту проблему, используя ряд передовых методов для надежного поиска и взаимодействия с компонентами на основе атрибутов и условий, которые не являются динамическими. Затем он предоставляет ярлыки для этого со всеми компонентами HTML, Ext JS и Sencha Touch, с которыми вам необходимо взаимодействовать. Он бывает двух видов:

  1. Java - знакомый API на основе Selenium и JUnit, который имеет встроенную поддержку веб-драйверов для всех современных браузеров.
  2. Gwen - язык человеческого стиля для быстрого и простого создания и сопровождения тестов браузера, который поставляется с собственной интегрированной средой разработки. Все это основано на Java API.

Например, если вы хотите найти строку сетки Ext JS, содержащую текст «Foo», вы можете сделать в Java следующее:

findExtJsGridRow("Foo");

... и в Гвен вы могли бы делать следующее:

extjsgridrow by text "Foo"

Как для Java, так и для Gwen существует множество документации по работе со специфическими компонентами Ext JS. В документации также подробно описывается итоговый HTML-код для всех этих компонентов Ext JS, который также может оказаться полезным.

Джон V
источник
2

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

   sub get_grid_row {
        my ($browser, $grid, $row)  = @_;


        my $script = "var doc = this.browserbot.getCurrentWindow().document;\n" .
            "var grid = doc.getElementById('$grid');\n" .
            "var table = grid.getElementsByTagName('table');\n" .
            "var result = '';\n" .
            "var row = 0;\n" . 
            "for (var i = 0; i < table.length; i++) {\n" .
            "   if (table[i].className == 'x-grid3-row-table') {\n".
            "       row++;\n" . 
            "       if (row == $row) {\n" .
            "           var cols_len = table[i].rows[0].cells.length;\n" .
            "           for (var j = 0; j < cols_len; j++) {\n" .
            "               var cell = table[i].rows[0].cells[j];\n" .
            "               if (result.length == 0) {\n" .
            "                   result = getText(cell);\n" .
            "               } else { \n" .
            "                   result += '|' + getText(cell);\n" .
            "               }\n" .
            "           }\n" .
            "       }\n" .
            "   }\n" .
            "}\n" .
            "result;\n";

        my $result = $browser->get_eval($script);
        my @res = split('\|', $result);
        return @res;
    }

источник
2

Упрощенное тестирование с помощью пользовательских атрибутов данных HTML

Из документации Sencha :

ItemId можно использовать как альтернативный способ получить ссылку на компонент, когда ссылка на объект недоступна. Вместо использования id с Ext.getCmp используйте itemId с Ext.container.Container.getComponent, который будет извлекать itemId или id. Так как itemId являются индексом для внутренней коллекции MixedCollection контейнера, itemId привязан к контейнеру локально, что позволяет избежать потенциальных конфликтов с Ext.ComponentManager, для которого требуется уникальный идентификатор.

Переопределив метод Ext.AbstractComponent's onBoxReady, я устанавливаю пользовательский атрибут данных (имя которого происходит от моего настраиваемого testIdAttrсвойства каждого компонента) на значение компонента itemId, если оно существует. Добавьте Testing.overrides.AbstractComponentкласс в массив вашего application.jsфайла requires.

/**
 * Overrides the Ext.AbstracComponent's onBoxReady
 * method to add custom data attributes to the
 * component's dom structure.
 *
 * @author Brian Wendt
 */
Ext.define('Testing.overrides.AbstractComponent', {
  override: 'Ext.AbstractComponent',


  onBoxReady: function () {
    var me = this,
      el = me.getEl();


    if (el && el.dom && me.itemId) {
      el.dom.setAttribute(me.testIdAttr || 'data-selenium-id', me.itemId);
    }


    me.callOverridden(arguments);
  }
});

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

Брайан Вендт
источник
2

Мы разрабатываем среду тестирования, которая использует селен и столкнулась с проблемами с extjs (поскольку это рендеринг на стороне клиента). Я считаю полезным поискать элемент, когда DOM будет готов.

public static boolean waitUntilDOMIsReady(WebDriver driver) {
    def maxSeconds = DEFAULT_WAIT_SECONDS * 10
    for (count in 1..maxSeconds) {
        Thread.sleep(100)
        def ready = isDOMReady(driver);
        if (ready) {
            break;
        }
    }
}

public static boolean isDOMReady(WebDriver driver){
    return driver.executeScript("return document.readyState");
}
Bertanasco
источник
1

Для сложного пользовательского интерфейса, который не является формальным HTML, вы всегда можете рассчитывать на xPath, но это немного сложно, когда дело доходит до другой реализации пользовательского интерфейса с использованием ExtJ.

Вы можете использовать Firebug и Firexpath в качестве расширений firefox для проверки xpath определенного элемента и просто передать полный xpath в качестве параметра для selenium.

Например, в java-коде:

String fullXpath = "xpath=//div[@id='mainDiv']//div[contains(@class,'x-grid-row')]//table/tbody/tr[1]/td[1]//button"

selenium.click(fullXpath);
Чун
источник
1

Когда я тестировал приложение ExtJS с помощью WebDriver, я использовал следующий подход: я искал поле по тексту метки и получал @forатрибут из метки. Например, у нас есть этикетка

<label id="dynamic_id_label" class="TextboxLabel" for="textField_which_I_am_lloking_for">
Name Of Needed Label
<label/>

И нам нужно указать WebDriver некоторых входного сигнала: //input[@id=(//label[contains(text(),'Name Of Needed Label')]/@for)].

Итак, он выберет идентификатор из @forатрибута и будет использовать его в дальнейшем. Это, вероятно, самый простой случай, но он дает вам возможность найти элемент. Это намного сложнее, когда у вас нет метки, но тогда вам нужно найти какой-то элемент и написать свой xpath для поиска братьев и сестер, элементов по убыванию / возрастанию.

Olyv
источник