Любые предложения для тестирования кода extjs в браузере, предпочтительно с селеном? - PullRequest
91 голосов
/ 20 сентября 2008

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

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

Ответы [ 11 ]

173 голосов
/ 20 октября 2008

Самым большим препятствием в тестировании 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. Это происходит потому, что прослушиватель событий на самом деле находится в контейнере, наблюдая за событиями мыши на его дочерних элементах, которые в итоге всплывают до родительского. Элемент управления вкладки является одним из примеров. Чтобы нажать на вкладку, вы должны смоделировать событие mouseDown на ярлыке вкладки:

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

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

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

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

    ExtJS запускает таймер задержки проверки, когда поле получает события keyup. Чтобы запустить этот таймер, просто сгенерируйте фиктивное событие 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 = кнопка: содержит («Сохранить»)

    Выбирает кнопку по заголовку

  • Вариант 2

    Команда: нажмите Цель: css = # кнопка опций сохранения

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

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

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

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

5 голосов
/ 31 января 2014

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

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

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

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

4 голосов
/ 24 сентября 2009

Чтобы обнаружить этот элемент видимым, вы используете предложение: 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')]"
4 голосов
/ 09 октября 2008

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

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

/**
 * 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 генерировать свои собственные.

3 голосов
/ 06 ноября 2013

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

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

  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, что также может оказаться полезным.

3 голосов
/ 20 сентября 2008

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

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

2 голосов
/ 11 августа 2016

Мы разрабатываем среду тестирования, которая использует селен и столкнулась с проблемами с 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");
}
2 голосов
/ 25 октября 2013

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

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

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

Переопределив метод Ext.AbstractComponent 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 голосов
/ 14 января 2009

Полезные советы по извлечению сетки через Id сетки на странице: Я думаю, что вы можете расширить более полезную функцию из этого 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;
    }
1 голос
/ 10 июля 2014

Когда я тестировал приложение 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 в поисках братьев и сестер, спускать / подниматься элементов.

...