Irgendwelche Vorschläge zum Testen von extjs-Code in einem Browser, vorzugsweise mit Selen?


92

Wir haben Selen mit großem Erfolg verwendet, um Website-Tests auf hoher Ebene durchzuführen (zusätzlich zu umfangreichen Python-Doctests auf Modulebene). Jetzt verwenden wir jedoch extjs für viele Seiten, und es erweist sich als schwierig, Selentests für komplexe Komponenten wie Gitter zu integrieren.

Hat jemand erfolgreich automatisierte Tests für extjs-basierte Webseiten geschrieben? Viele googeln finden Menschen mit ähnlichen Problemen, aber nur wenige Antworten. Vielen Dank!


Die guten Leute bei Ext JS waren so freundlich, genau dieses Thema in ihrem Blog hier zu veröffentlichen . Ich hoffe das hilft.
NBRed5

Antworten:


173

Die größte Hürde beim Testen von ExtJS mit Selenium besteht darin, dass ExtJS keine Standard-HTML-Elemente rendert und die Selenium-IDE naiv (und zu Recht) Befehle generiert, die auf Elemente abzielen, die nur als Dekor dienen - überflüssige Elemente, die ExtJS auf dem gesamten Desktop unterstützen. Look-and-Feel. Hier sind einige Tipps und Tricks, die ich beim Schreiben eines automatisierten Selenium-Tests gegen eine ExtJS-App gesammelt habe.

Allgemeine Hinweise

Elemente lokalisieren

Beim Generieren von Selenium-Testfällen durch Aufzeichnen von Benutzeraktionen mit Selenium IDE unter Firefox basiert Selenium die aufgezeichneten Aktionen auf den IDs der HTML-Elemente. Für die meisten anklickbaren Elemente verwendet ExtJS jedoch generierte IDs wie "ext-gen-345", die sich bei einem späteren Besuch derselben Seite wahrscheinlich ändern, selbst wenn keine Codeänderungen vorgenommen wurden. Nach dem Aufzeichnen von Benutzeraktionen für einen Test muss manuell gearbeitet werden, um alle Aktionen, die von den generierten IDs abhängen, durchzuführen und zu ersetzen. Es gibt zwei Arten von Ersetzungen, die vorgenommen werden können:

Ersetzen eines ID-Locators durch einen CSS- oder XPath-Locator

CSS-Locators beginnen mit "css =" und XPath-Locators beginnen mit "//" (das Präfix "xpath =" ist optional). CSS-Locators sind weniger ausführlich und leichter zu lesen und sollten XPath-Locators vorgezogen werden. Es kann jedoch Fälle geben, in denen XPath-Locators verwendet werden müssen, da ein CSS-Locator sie einfach nicht schneiden kann.

JavaScript ausführen

Einige Elemente erfordern aufgrund des komplexen Renderings von ExtJS mehr als nur einfache Maus- / Tastaturinteraktionen. Beispielsweise ist eine Ext.form.CombBox nicht wirklich ein <select>Element, sondern eine Texteingabe mit einer getrennten Dropdown-Liste, die sich irgendwo am Ende des Dokumentbaums befindet. Um eine ComboBox-Auswahl richtig zu simulieren, können Sie zuerst einen Klick auf den Dropdown-Pfeil simulieren und dann auf die angezeigte Liste klicken. Das Auffinden dieser Elemente über CSS- oder XPath-Locators kann jedoch umständlich sein. Eine Alternative besteht darin, die ComoBox-Komponente selbst zu suchen und Methoden aufzurufen, um die Auswahl zu simulieren:

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

In Selenium kann der runScriptBefehl verwendet werden, um die obige Operation in einer präziseren Form auszuführen:

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

Umgang mit AJAX und Slow Rendering

Selen verfügt über die Varianten "* AndWait" für alle Befehle zum Warten auf das Laden von Seiten, wenn eine Benutzeraktion zu Seitenübergängen oder Neuladungen führt. Da AJAX-Abrufe jedoch keine tatsächlichen Seitenladevorgänge beinhalten, können diese Befehle nicht für die Synchronisierung verwendet werden. Die Lösung besteht darin, visuelle Hinweise wie das Vorhandensein / Fehlen einer AJAX-Fortschrittsanzeige oder das Auftreten von Zeilen in einem Raster, zusätzliche Komponenten, Links usw. zu verwenden. Zum Beispiel:

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

Manchmal wird ein Element erst nach einer bestimmten Zeit angezeigt, je nachdem, wie schnell ExtJS Komponenten rendert, nachdem eine Benutzeraktion zu einer Ansichtsänderung geführt hat. Anstatt willkürliche Verzögerungen mit dem pauseBefehl zu verwenden, besteht die ideale Methode darin, zu warten, bis das interessierende Element in unsere Reichweite gelangt. So klicken Sie beispielsweise auf ein Element, nachdem Sie darauf gewartet haben, dass es angezeigt wird:

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

Sich auf beliebige Pausen zu verlassen, ist keine gute Idee, da Zeitunterschiede, die sich aus der Ausführung der Tests in verschiedenen Browsern oder auf verschiedenen Computern ergeben, die Testfälle schuppig machen.

Nicht anklickbare Elemente

Einige Elemente können vom clickBefehl nicht ausgelöst werden . Dies liegt daran, dass sich der Ereignis-Listener tatsächlich im Container befindet und nach Mausereignissen für seine untergeordneten Elemente sucht, die schließlich zum übergeordneten Element aufsteigen. Das Registersteuerelement ist ein Beispiel. Um auf die Registerkarte a zu klicken, müssen Sie ein mouseDownEreignis auf der Registerkarte beschriften:

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

Feldvalidierung

Formularfelder (Ext.form. * -Komponenten), denen reguläre Ausdrücke oder v-Typen zur Validierung zugeordnet sind, lösen die Validierung mit einer bestimmten Verzögerung aus (siehe die validationDelayEigenschaft, die standardmäßig auf 250 ms festgelegt ist), nachdem der Benutzer Text eingegeben hat oder sofort, wenn das Feld verliert Fokus - oder Unschärfen (siehe validateOnDelayEigenschaft). Um die Feldvalidierung nach der Ausgabe des Befehls Typ Selenium auszulösen, um Text in ein Feld einzugeben, müssen Sie einen der folgenden Schritte ausführen:

  • Auslösen einer verzögerten Validierung

    ExtJS löst den Validierungsverzögerungszeitgeber aus, wenn das Feld Keyup-Ereignisse empfängt. Um diesen Timer auszulösen, geben Sie einfach ein Dummy-Keyup-Ereignis aus (es spielt keine Rolle, welchen Schlüssel Sie verwenden, da ExtJS ihn ignoriert), gefolgt von einer kurzen Pause, die länger als die Validierungsverzögerung ist:

    Command: keyUp
    Target: someTextArea
    Value: x
    Command: pause
    Target: 500
  • Sofortige Validierung auslösen

    Sie können ein Unschärfeereignis in das Feld einfügen, um eine sofortige Validierung auszulösen:

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

Überprüfung auf Validierungsergebnisse

Nach der Validierung können Sie überprüfen, ob ein Fehlerfeld vorhanden ist oder nicht:

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

Beachten Sie, dass die Prüfung "Anzeige: Keine" erforderlich ist, da ExtJS das Fehlerfeld einfach ausblendet, sobald es angezeigt wird und dann ausgeblendet werden muss, anstatt es vollständig aus dem DOM-Baum zu entfernen.

Elementspezifische Tipps

Klicken Sie auf eine Ext.form.Button

  • Option 1

    Befehl: Klicken Sie auf Ziel: css = Schaltfläche: enthält ('Speichern')

    Wählt die Schaltfläche anhand ihrer Beschriftung aus

  • Option 2

    Befehl: Klicken Sie auf die Schaltfläche Ziel: css = # Speicheroptionen

    Wählt die Schaltfläche anhand ihrer ID aus

Auswählen eines Werts aus einer Ext.form.ComboBox

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

Legt zuerst den Wert fest und löst dann das Auswahlereignis explizit aus, falls Beobachter vorhanden sind.



5

Dieser Blog hat mir sehr geholfen. Er hat ziemlich viel zu diesem Thema geschrieben und es scheint immer noch aktiv zu sein. Der Typ scheint auch gutes Design zu schätzen.

Er spricht im Wesentlichen davon, Javascript für Abfragen zu senden und die Ext.ComponentQuery.query-Methode zu verwenden, um Inhalte auf dieselbe Weise abzurufen, wie Sie es in Ihrer ext-App intern tun. Auf diese Weise können Sie xtypes und itemIds verwenden und müssen sich keine Sorgen mehr machen, wenn Sie versuchen, die verrückten automatisch generierten Inhalte zu analysieren.

Ich fand diesen Artikel besonders hilfreich.

Könnte bald etwas detaillierteres hier posten - ich versuche immer noch, mir klar zu machen, wie man das richtig macht


4

Ich habe meine ExtJs-Webanwendung mit Selen getestet. Eines der größten Probleme war die Auswahl eines Elements im Raster, um etwas damit zu tun.

Zu diesem Zweck habe ich eine Hilfsmethode geschrieben (in der SeleniumExtJsUtils-Klasse, die eine Sammlung nützlicher Methoden zur einfacheren Interaktion mit ExtJs darstellt):

/**
 * 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)";
}

und wenn ich eine Zeile auswählen musste, rief ich einfach an:

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

Damit dies funktioniert, muss ich meine ID im Raster festlegen und darf ExtJs nicht ihre eigene generieren lassen.


Sie sollten in der Lage sein, das Raster mithilfe seines xtype in Ext.ComponentQuery.query zu finden (vorausgesetzt, Sie haben das Raster erweitert und den xtype für Ihr eigenes Raster definiert). Ich habe einige Informationen dazu weiter unten aufgeführt. Ich fand auch, dass Sie auf Dinge klicken können, indem Sie xpath verwenden, um das td zu finden, das einen identifizierenden Wert für eine Zeile enthält
JonnyRaa

4

Um festzustellen, dass dieses Element sichtbar ist, verwenden Sie die folgende Klausel: not(contains(@style, "display: none")

Es ist besser, dies zu verwenden:

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

Können Sie mehr Einblick in die Arten von Problemen geben, die Sie beim Testen von extjs haben?

Eine Selenium-Erweiterung, die ich nützlich finde, ist waitForCondition . Wenn Ihr Problem Probleme mit den Ajax-Ereignissen zu sein scheint, können Sie waitForCondition verwenden, um auf Ereignisse zu warten.


3

Ext JS-Webseiten können aufgrund des komplizierten HTML-Codes, den sie wie bei Ext JS-Rastern generieren, schwierig zu testen sein.

HTML5 Robot behandelt dies, indem es eine Reihe von Best Practices verwendet, um Komponenten basierend auf Attributen und Bedingungen, die nicht dynamisch sind, zuverlässig zu suchen und mit ihnen zu interagieren. Anschließend werden Verknüpfungen mit allen HTML-, Ext JS- und Sencha Touch-Komponenten bereitgestellt, mit denen Sie interagieren müssten. Es gibt zwei Geschmacksrichtungen:

  1. Java - Vertraute Selenium- und JUnit-basierte API, die die Unterstützung von Webtreibern für alle modernen Browser integriert hat.
  2. Gwen - Eine Sprache im menschlichen Stil zum schnellen und einfachen Erstellen und Verwalten von Browsertests, die über eine eigene integrierte Entwicklungsumgebung verfügt. All dies basiert auf der Java-API.

Wenn Sie beispielsweise die Ext JS-Rasterzeile mit dem Text "Foo" suchen möchten, können Sie in Java Folgendes tun:

findExtJsGridRow("Foo");

... und Sie könnten in Gwen Folgendes tun:

extjsgridrow by text "Foo"

Sowohl für Java als auch für Gwen gibt es zahlreiche Dokumentationen zur Arbeit mit Ext JS-spezifischen Komponenten. In der Dokumentation wird auch der resultierende HTML-Code für alle diese Ext JS-Komponenten beschrieben, den Sie möglicherweise auch nützlich finden.


2

Nützliche Tipps zum Abrufen des Rasters über die ID des Rasters auf der Seite: Ich denke, Sie können die nützlichere Funktion dieser API erweitern.

   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

Einfacheres Testen durch benutzerdefinierte HTML-Datenattribute

Aus der Sencha-Dokumentation :

Eine itemId kann als alternative Methode verwendet werden, um eine Referenz auf eine Komponente abzurufen, wenn keine Objektreferenz verfügbar ist. Anstatt eine ID mit Ext.getCmp zu verwenden, verwenden Sie itemId mit Ext.container.Container.getComponent, um itemIds oder IDs abzurufen. Da itemIds ein Index für die interne MixedCollection des Containers sind, wird die itemId lokal für den Container festgelegt, um mögliche Konflikte mit Ext.ComponentManager zu vermeiden, für die eine eindeutige ID erforderlich ist.

Beim Überschreiben der Methode Ext.AbstractComponent's onBoxReadysetze ich ein benutzerdefiniertes Datenattribut (dessen Name von meiner benutzerdefinierten testIdAttrEigenschaft jeder Komponente stammt) auf den itemIdWert der Komponente , falls vorhanden. Fügen Sie die Testing.overrides.AbstractComponentKlasse application.jsdem requiresArray Ihrer Datei hinzu .

/**
 * 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);
  }
});

Diese Methode bietet Entwicklern die Möglichkeit, einen beschreibenden Bezeichner in ihrem Code wiederzuverwenden und diese Bezeichner bei jedem Rendern der Seite verfügbar zu machen. Kein Durchsuchen von nicht beschreibenden, dynamisch generierten IDs mehr.


2

Wir entwickeln ein Testframework, das Selen verwendet und auf Probleme mit extjs stößt (da es clientseitig gerendert wird). Ich finde es nützlich, nach einem Element zu suchen, sobald das DOM fertig ist.

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");
}

1

Für komplexe Benutzeroberflächen, die kein formales HTML sind, können Sie sich immer auf xPath verlassen, aber ein wenig komplex, wenn es um die Implementierung verschiedener Benutzeroberflächen mit ExtJs geht.

Sie können Firebug und Firexpath als Firefox-Erweiterungen verwenden, um den xpath eines bestimmten Elements zu testen, und einfach den vollständigen xpath als Parameter an Selen übergeben.

Zum Beispiel in Java-Code:

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

selenium.click(fullXpath);

1

Als ich die ExtJS-Anwendung mit WebDriver getestet habe, habe ich den nächsten Ansatz verwendet: Ich habe nach Feld anhand des Textes des Labels gesucht und das @forAttribut vom Label erhalten. Zum Beispiel haben wir ein Etikett

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

Und wir müssen WebDriver einige Eingaben zeigen : //input[@id=(//label[contains(text(),'Name Of Needed Label')]/@for)].

Es wird also die ID aus dem @forAttribut auswählen und weiter verwenden. Dies ist wahrscheinlich der einfachste Fall, bietet Ihnen jedoch die Möglichkeit, Elemente zu lokalisieren. Es ist viel schwieriger, wenn Sie keine Beschriftung haben, aber dann müssen Sie ein Element finden und Ihren xpath schreiben, um nach Geschwistern zu suchen, Elemente abzusteigen / aufzusteigen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.