Jakieś sugestie dotyczące testowania kodu extjs w przeglądarce, najlepiej z selenem?

92

Z dużym powodzeniem używamy selenu do przeprowadzania testów stron internetowych na wysokim poziomie (oprócz rozbudowanych testów doctonów w języku Python na poziomie modułu). Jednak teraz używamy extjów do wielu stron i okazuje się, że trudno jest włączyć testy Selenium do złożonych komponentów, takich jak siatki.

Czy ktoś odniósł sukces w pisaniu automatycznych testów dla stron internetowych opartych na extjs? Wiele wyszukiwań w Google znajduje ludzi z podobnymi problemami, ale niewiele odpowiedzi. Dzięki!

mm2001
źródło
Dobrzy ludzie z Ext JS byli na tyle uprzejmi, aby napisać na ten temat na swoim blogu tutaj . Mam nadzieję, że to pomoże.
NBRed5,

Odpowiedzi:

173

Największą przeszkodą w testowaniu ExtJS z Selenium jest to, że ExtJS nie renderuje standardowych elementów HTML, a Selenium IDE naiwnie (i słusznie) generuje polecenia skierowane do elementów, które działają tylko jako dekoracje - zbędne elementy, które pomagają ExtJS z całym pulpitem- widzieć i czuć. Oto kilka wskazówek i sztuczek, które zebrałem podczas pisania automatycznego testu Selenium dla aplikacji ExtJS.

Ogólne wskazówki

Lokalizowanie elementów

Podczas generowania przypadków testowych Selenium poprzez rejestrowanie działań użytkownika za pomocą Selenium IDE w przeglądarce Firefox, Selenium oprze zarejestrowane działania na identyfikatorach elementów HTML. Jednak w przypadku większości klikalnych elementów ExtJS używa wygenerowanych identyfikatorów, takich jak „ext-gen-345”, które prawdopodobnie zmienią się podczas kolejnej wizyty na tej samej stronie, nawet jeśli nie wprowadzono żadnych zmian w kodzie. Po zarejestrowaniu działań użytkownika na potrzeby testu należy wykonać ręcznie wszystkie czynności, które zależą od wygenerowanych identyfikatorów, i je zastąpić. Istnieją dwa rodzaje zamienników, które można wykonać:

Zastąpienie lokalizatora identyfikatora lokalizatorem CSS lub XPath

Lokalizatory CSS zaczynają się od „css =”, a lokalizatory XPath zaczynają się od „//” (przedrostek „xpath =” jest opcjonalny). Lokalizatory CSS są mniej szczegółowe i łatwiejsze do odczytania i powinny być preferowane względem lokalizatorów XPath. Jednak mogą wystąpić przypadki, w których trzeba użyć lokalizatorów XPath, ponieważ lokalizator CSS po prostu nie może ich wyciąć.

Wykonywanie JavaScript

Niektóre elementy wymagają czegoś więcej niż tylko prostych interakcji myszy / klawiatury ze względu na złożone renderowanie wykonywane przez ExtJS. Na przykład Ext.form.CombBox nie jest w rzeczywistości <select>elementem, ale tekstem z odłączoną listą rozwijaną, która znajduje się gdzieś na dole drzewa dokumentu. Aby poprawnie zasymulować wybór ComboBox, można najpierw zasymulować kliknięcie strzałki rozwijanej, a następnie kliknięcie wyświetlonej listy. Jednak lokalizowanie tych elementów za pomocą lokalizatorów CSS lub XPath może być kłopotliwe. Alternatywą jest zlokalizowanie samego komponentu ComoBox i wywołanie na nim metod w celu zasymulowania wyboru:

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

W Selenium runScriptpolecenie może być użyte do wykonania powyższej operacji w bardziej zwięzłej formie:

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

Radzenie sobie z AJAX i Slow Rendering

Selenium ma smaki "* AndWait" dla wszystkich poleceń czekających na załadowanie strony, gdy akcja użytkownika skutkuje przejściem strony lub ponownym załadowaniem. Ponieważ jednak pobieranie AJAX nie obejmuje faktycznego ładowania strony, tych poleceń nie można używać do synchronizacji. Rozwiązaniem jest wykorzystanie wizualnych wskazówek, takich jak obecność / brak wskaźnika postępu AJAX lub wygląd wierszy w siatce, dodatkowe komponenty, linki itp. Na przykład:

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

Czasami element pojawia się dopiero po pewnym czasie, w zależności od tego, jak szybko ExtJS renderuje komponenty po tym, jak akcja użytkownika powoduje zmianę widoku. Zamiast używać arbitralnych opóźnień przy pausepoleceniu, idealną metodą jest zaczekanie, aż interesujący nas element znajdzie się w naszym zasięgu. Na przykład, aby kliknąć element po odczekaniu, aż się pojawi:

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

Poleganie na arbitralnych przerwach nie jest dobrym pomysłem, ponieważ różnice czasowe wynikające z uruchamiania testów w różnych przeglądarkach lub na różnych komputerach spowodują, że przypadki testowe będą niestabilne.

Elementy nieklikalne

Niektórych elementów nie można wywołać clickpoleceniem. Dzieje się tak, ponieważ detektor zdarzeń znajduje się w kontenerze, obserwując zdarzenia myszy w elementach podrzędnych, które ostatecznie pojawiają się w nadrzędnym. Kontrolka karty jest jednym z przykładów. Aby kliknąć zakładkę, musisz zasymulować mouseDownzdarzenie na etykiecie zakładki:

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

Walidacja pola

Pola formularza (komponenty Ext.form. *), Które mają skojarzone wyrażenia regularne lub vtypes do walidacji, wyzwalają walidację z pewnym opóźnieniem (patrz validationDelaywłaściwość, która jest domyślnie ustawiona na 250 ms), po wprowadzeniu tekstu przez użytkownika lub natychmiast po utracie pola focus - lub rozmywa (zobacz validateOnDelaywłaściwość). Aby wyzwolić walidację pola po wydaniu polecenia typu Selenium w celu wprowadzenia tekstu w polu, należy wykonać jedną z następujących czynności:

  • Wyzwalanie opóźnionej walidacji

    ExtJS odpala licznik czasu opóźnienia walidacji, gdy pole otrzymuje zdarzenia keyup. Aby uruchomić ten licznik czasu, po prostu wydaj fałszywe zdarzenie klucza (nie ma znaczenia, którego klucza używasz, ponieważ ExtJS go ignoruje), a następnie krótką pauzę dłuższą niż validationDelay:

    Command: keyUp
    Target: someTextArea
    Value: x
    Command: pause
    Target: 500
    
  • Wyzwalanie natychmiastowej weryfikacji

    Możesz wstrzyknąć zdarzenie rozmycia w pole, aby uruchomić natychmiastową weryfikację:

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

Sprawdzanie wyników walidacji

Po sprawdzeniu poprawności możesz sprawdzić obecność lub brak pola błędu:

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"))]

Zwróć uwagę, że sprawdzenie "display: none" jest konieczne, ponieważ po wyświetleniu pola błędu, a następnie trzeba je ukryć, ExtJS po prostu ukryje pole błędu zamiast całkowicie usunąć je z drzewa DOM.

Wskazówki dotyczące poszczególnych elementów

Kliknięcie Ext.form.Button

  • opcja 1

    Polecenie: kliknij Cel: przycisk css =: zawiera ('Zapisz')

    Zaznacza przycisk według jego podpisu

  • Opcja 2

    Polecenie: kliknij przycisk Cel: css = # opcje zapisywania

    Wybiera przycisk według jego identyfikatora

Wybieranie wartości z Ext.form.ComboBox

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

Najpierw ustawia wartość, a następnie jawnie uruchamia wybrane zdarzenie w przypadku obecności obserwatorów.

Ates Goral
źródło
3
Oto kilka porad dotyczących instrukcji: rkapse.blogspot.nl/2009/06/selenium-ide-issues-with-extjs.html
dan-klasson
5

Ten blog bardzo mi pomógł. Napisał sporo na ten temat i wygląda na to, że nadal jest aktywny. Facet wydaje się też doceniać dobry projekt.

Mówi po prostu o używaniu wysyłania javascript do wykonywania zapytań i używaniu metody Ext.ComponentQuery.query do pobierania rzeczy w taki sam sposób, jak robisz to w wewnętrznej aplikacji ext. W ten sposób możesz używać xtypes i itemIds i nie musisz się martwić o próbę parsowania jakichkolwiek szalonych automatycznie generowanych rzeczy.

Znalazłem ten artykuł w szczególności bardzo pomocne.

Może wkrótce opublikuję tutaj coś bardziej szczegółowego - wciąż próbuję dowiedzieć się, jak to zrobić poprawnie

JonnyRaa
źródło
4

Testowałem moją aplikację internetową ExtJs z selenem. Jednym z największych problemów było wybranie elementu w siatce, aby coś z nim zrobić.

W tym celu napisałem metodę pomocniczą (w klasie SeleniumExtJsUtils będącej zbiorem przydatnych metod ułatwiających interakcję z 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)";
}

a kiedy chciałem wybrać wiersz, dzwoniłem po prostu:

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

Aby to zadziałało, muszę ustawić mój identyfikator w sieci i nie pozwolić ExtJ generować własnego.

Marko Dumic
źródło
powinieneś być w stanie znaleźć siatkę, używając jej xtype w Ext.ComponentQuery.query (zakładając, że rozszerzyłeś z gridu i zdefiniowałeś xtype dla swojej własnej siatki). Odkryłem również, że możesz klikać rzeczy, używając xpath, aby znaleźć td, który zawiera wartość identyfikującą wiersz
JonnyRaa,
4

Aby wykryć, że element jest widoczny, użyj klauzuli: not(contains(@style, "display: none")

Lepiej użyć tego:

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')]"
Master-Test
źródło
3

Czy możesz podać więcej informacji na temat typów problemów, które masz podczas testowania extjs?

Jednym z przydatnych rozszerzeń Selenium jest waitForCondition . Jeśli wydaje się, że problem dotyczy zdarzeń Ajax, możesz użyć waitForCondition, aby czekać na wystąpienie zdarzeń.

Ryan Guest
źródło
3

Strony internetowe Ext JS mogą być trudne do przetestowania ze względu na skomplikowany HTML, który generują, podobnie jak w przypadku siatek Ext JS.

HTML5 Robot radzi sobie z tym, stosując serię najlepszych praktyk dotyczących niezawodnego wyszukiwania i interakcji z komponentami w oparciu o atrybuty i warunki, które nie są dynamiczne. Następnie udostępnia skróty do zrobienia tego ze wszystkimi komponentami HTML, Ext JS i Sencha Touch, z którymi musiałbyś korzystać. Występuje w 2 smakach:

  1. Java - znane API oparte na Selenium i JUnit, które ma wbudowaną obsługę sterownika sieciowego dla wszystkich nowoczesnych przeglądarek.
  2. Gwen - ludzki język do szybkiego i łatwego tworzenia i utrzymywania testów przeglądarki, który jest dostarczany z własnym zintegrowanym środowiskiem programistycznym. Wszystko to jest oparte na Java API.

Na przykład, jeśli chcesz znaleźć wiersz siatki Ext JS zawierający tekst „Foo”, możesz wykonać następujące czynności w Javie:

findExtJsGridRow("Foo");

... i możesz wykonać następujące czynności w Gwen:

extjsgridrow by text "Foo"

Istnieje wiele dokumentacji zarówno dla Javy, jak i Gwen, aby dowiedzieć się, jak pracować z określonymi komponentami Ext JS. Dokumentacja zawiera również szczegóły dotyczące wynikowego kodu HTML dla wszystkich tych komponentów Ext JS, co również może okazać się przydatne.

John V
źródło
2

Przydatne wskazówki dotyczące pobierania siatki za pomocą identyfikatora siatki na stronie: Myślę, że możesz rozszerzyć bardziej użyteczną funkcję z tego interfejsu 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;
    }

źródło
2

Łatwiejsze testowanie dzięki niestandardowym atrybutom danych HTML

Z dokumentacji Sencha :

Element itemId może służyć jako alternatywny sposób uzyskiwania odniesienia do komponentu, gdy żadne odwołanie do obiektu nie jest dostępne. Zamiast używać identyfikatora z Ext.getCmp, użyj itemId z Ext.container.Container.getComponent, który pobierze itemId lub id. Ponieważ itemId są indeksami do wewnętrznej kolekcji MixedCollection kontenera, itemId jest określana lokalnie w kontenerze - co pozwala uniknąć potencjalnych konfliktów z Ext.ComponentManager, który wymaga unikalnego identyfikatora.

Zastępując metodę Ext.AbstractComponent's onBoxReady, ustawiłem niestandardowy atrybut danych (którego nazwa pochodzi z mojej niestandardowej testIdAttrwłaściwości każdego komponentu) na wartość komponentu itemId, jeśli istnieje. Dodaj Testing.overrides.AbstractComponentklasę do tablicy application.jspliku 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);
  }
});

Ta metoda umożliwia programistom ponowne użycie identyfikatora opisowego w kodzie i udostępnienie tych identyfikatorów za każdym razem, gdy strona jest renderowana. Koniec z przeszukiwaniem nieopisowych, dynamicznie generowanych identyfikatorów.

Brian Wendt
źródło
2

Opracowujemy framework testowy, który używa selenu i napotkał problemy z extjs (ponieważ jest to renderowanie po stronie klienta). Uważam, że warto poszukać elementu, gdy DOM jest gotowy.

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
źródło
1

W przypadku złożonego interfejsu użytkownika, który nie jest formalnym kodem HTML, xPath jest zawsze czymś, na co można liczyć, ale jest nieco skomplikowany, jeśli chodzi o różne implementacje interfejsu użytkownika za pomocą ExtJ.

Możesz użyć Firebug i Firexpath jako rozszerzeń firefoxa do testowania xpath określonego elementu i po prostu przekazać pełną xpath jako parametr selenu.

Na przykład w kodzie java:

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

selenium.click(fullXpath);
Chun
źródło
1

Kiedy testowałem aplikację ExtJS przy użyciu WebDriver, zastosowałem następne podejście: szukałem pola po tekście etykiety i otrzymałem @foratrybut z etykiety. Na przykład mamy etykietę

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

I musimy wskazać WebDriver jakiś wkład: //input[@id=(//label[contains(text(),'Name Of Needed Label')]/@for)].

Więc wybierze identyfikator z @foratrybutu i użyje go dalej. Jest to prawdopodobnie najprostszy przypadek, ale pozwala zlokalizować element. Jest to znacznie trudniejsze, gdy nie masz etykiety, ale wtedy musisz znaleźć jakiś element i napisać swoją ścieżkę XP w poszukiwaniu rodzeństwa, elementów descend / ascend.

olyv
źródło