Des suggestions pour tester le code extjs dans un navigateur, de préférence avec du sélénium?


92

Nous avons utilisé le sélénium avec beaucoup de succès pour gérer les tests de sites Web de haut niveau (en plus de nombreux doctests python au niveau du module). Cependant, nous utilisons maintenant extjs pour de nombreuses pages et il s'avère difficile d'incorporer des tests Selenium pour les composants complexes comme les grilles.

Quelqu'un a-t-il réussi à écrire des tests automatisés pour des pages Web basées sur extjs? Beaucoup de recherches sur Google trouvent des personnes ayant des problèmes similaires, mais peu de réponses. Merci!


Les bonnes personnes d'Ext JS ont eu la gentillesse de publier sur ce sujet sur leur blog ici . J'espère que ça aide.
NBRed5

Réponses:


173

Le plus gros obstacle dans le test d'ExtJS avec Selenium est qu'ExtJS ne rend pas les éléments HTML standard et que Selenium IDE générera naïvement (et à juste titre) des commandes ciblées sur des éléments qui agissent simplement comme décor - des éléments superflus qui aident ExtJS avec l'ensemble du bureau- regarde et ressent. Voici quelques trucs et astuces que j'ai rassemblés lors de l'écriture de test Selenium automatisé sur une application ExtJS.

Conseils généraux

Localisation des éléments

Lors de la génération de cas de test Selenium en enregistrant les actions des utilisateurs avec Selenium IDE sur Firefox, Selenium basera les actions enregistrées sur les identifiants des éléments HTML. Cependant, pour la plupart des éléments cliquables, ExtJS utilise des identifiants générés comme "ext-gen-345" qui sont susceptibles de changer lors d'une prochaine visite sur la même page, même si aucune modification de code n'a été effectuée. Après avoir enregistré les actions de l'utilisateur pour un test, un effort manuel est nécessaire pour exécuter toutes ces actions qui dépendent des identifiants générés et pour les remplacer. Deux types de remplacements peuvent être effectués:

Remplacement d'un localisateur d'identifiant par un localisateur CSS ou XPath

Les localisateurs CSS commencent par "css =" et les localisateurs XPath commencent par "//" (le préfixe "xpath =" est facultatif). Les localisateurs CSS sont moins détaillés et plus faciles à lire et devraient être préférés aux localisateurs XPath. Cependant, il peut y avoir des cas où des localisateurs XPath doivent être utilisés car un localisateur CSS ne peut tout simplement pas le couper.

Exécution de JavaScript

Certains éléments nécessitent plus que de simples interactions souris / clavier en raison du rendu complexe effectué par ExtJS. Par exemple, un Ext.form.CombBox n'est pas vraiment un <select>élément mais une entrée de texte avec une liste déroulante détachée qui se trouve quelque part au bas de l'arborescence du document. Afin de simuler correctement une sélection ComboBox, il est possible de simuler d'abord un clic sur la flèche déroulante puis de cliquer sur la liste qui apparaît. Cependant, localiser ces éléments via des localisateurs CSS ou XPath peut être fastidieux. Une alternative consiste à localiser le composant ComoBox lui-même et à appeler des méthodes dessus pour simuler la sélection:

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

Dans Selenium, la runScriptcommande peut être utilisée pour effectuer l'opération ci-dessus sous une forme plus concise:

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

Faire face à AJAX et au rendu lent

Selenium a des saveurs "* AndWait" pour toutes les commandes d'attente de chargement de page lorsqu'une action de l'utilisateur entraîne des transitions ou des recharges de page. Cependant, comme les récupérations AJAX n'impliquent pas de chargements de page réels, ces commandes ne peuvent pas être utilisées pour la synchronisation. La solution est d'utiliser des indices visuels comme la présence / absence d'un indicateur de progression AJAX ou l'apparition de lignes dans une grille, des composants supplémentaires, des liens etc. Par exemple:

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

Parfois, un élément n'apparaît qu'après un certain temps, selon la vitesse à laquelle ExtJS restitue les composants après qu'une action de l'utilisateur entraîne un changement de vue. Au lieu d'utiliser des délais arbitraires avec la pausecommande, la méthode idéale est d'attendre que l'élément d'intérêt soit à notre portée. Par exemple, pour cliquer sur un élément après avoir attendu qu'il apparaisse:

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

S'appuyer sur des pauses arbitraires n'est pas une bonne idée car les différences de timing résultant de l'exécution des tests dans différents navigateurs ou sur différentes machines rendront les cas de test floconneux.

Éléments non cliquables

Certains éléments ne peuvent pas être déclenchés par la clickcommande. C'est parce que l'écouteur d'événements est en fait sur le conteneur, surveillant les événements de souris sur ses éléments enfants, qui finissent par remonter jusqu'au parent. Le contrôle onglet est un exemple. Pour cliquer sur l'onglet a, vous devez simuler un mouseDownévénement au niveau de l'étiquette de l'onglet:

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

Validation sur le terrain

Les champs de formulaire (composants Ext.form. *) Qui ont des expressions régulières associées ou des vtypes pour la validation déclencheront la validation avec un certain délai (voir la validationDelaypropriété qui est définie sur 250 ms par défaut), après que l'utilisateur a saisi du texte ou immédiatement lorsque le champ perd focus - ou floue (voir la validateOnDelaypropriété). Afin de déclencher la validation du champ après avoir émis la commande de type Selenium pour entrer du texte dans un champ, vous devez effectuer l'une des opérations suivantes:

  • Déclenchement de la validation retardée

    ExtJS déclenche le temporisateur de délai de validation lorsque le champ reçoit des événements de keyup. Pour déclencher ce minuteur, émettez simplement un événement de keyup factice (peu importe la clé que vous utilisez car ExtJS l'ignore), suivi d'une courte pause qui est plus longue que le validationDelay:

    Command: keyUp
    Target: someTextArea
    Value: x
    Command: pause
    Target: 500
  • Déclenchement de la validation immédiate

    Vous pouvez injecter un événement de flou dans le champ pour déclencher une validation immédiate:

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

Vérification des résultats de validation

Après validation, vous pouvez vérifier la présence ou l'absence d'un champ d'erreur:

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

Notez que la vérification "display: none" est nécessaire car une fois qu'un champ d'erreur est affiché et qu'il doit ensuite être caché, ExtJS masquera simplement le champ d'erreur au lieu de le supprimer entièrement de l'arborescence DOM.

Conseils spécifiques aux éléments

Cliquer sur un bouton Ext.form.Button

  • Option 1

    Commande: cliquez sur Cible: css = bouton: contient ('Enregistrer')

    Sélectionne le bouton par sa légende

  • Option 2

    Commande: cliquez sur Target: css = # save-options button

    Sélectionne le bouton par son identifiant

Sélection d'une valeur à partir d'un ext.form.ComboBox

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

Définit d'abord la valeur, puis déclenche explicitement l'événement select au cas où il y aurait des observateurs.



5

Ce blog m'a beaucoup aidé. Il a beaucoup écrit sur le sujet et il semble que ce soit toujours actif. Le gars semble également apprécier le bon design.

Il parle essentiellement d'utiliser l'envoi de javascript pour faire des requêtes et d'utiliser la méthode Ext.ComponentQuery.query pour récupérer des éléments de la même manière que vous le faites dans votre application ext en interne. De cette façon, vous pouvez utiliser des xtypes et des itemsIds sans avoir à vous soucier d'essayer d'analyser les trucs fous générés automatiquement.

J'ai trouvé cet article particulièrement utile.

Pourrait publier quelque chose d'un peu plus détaillé ici bientôt - j'essaie toujours de comprendre comment faire cela correctement


4

J'ai testé mon application Web ExtJs avec du sélénium. L'un des plus gros problèmes était de sélectionner un élément dans la grille afin d'en faire quelque chose.

Pour cela, j'ai écrit une méthode d'assistance (dans la classe SeleniumExtJsUtils qui est une collection de méthodes utiles pour une interaction plus facile avec 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)";
}

et quand j'avais besoin de sélectionner une ligne, j'appelais simplement:

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

Pour que cela fonctionne, je dois définir mon identifiant sur la grille et ne pas laisser ExtJs générer le sien.


vous devriez être en mesure de trouver la grille en utilisant son xtype dans Ext.ComponentQuery.query (en supposant que vous avez étendu la grille et défini le xtype pour votre propre grille), j'ai mis quelques trucs à ce sujet plus bas. J'ai également trouvé que vous pouviez cliquer sur des éléments en utilisant xpath pour trouver le td qui contient une valeur d'identification pour une ligne
JonnyRaa

4

Pour détecter que cet élément est visible, utilisez la clause: not(contains(@style, "display: none")

Il vaut mieux utiliser ceci:

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

Pouvez-vous fournir plus d'informations sur les types de problèmes que vous rencontrez avec les tests extjs?

Une extension Selenium que je trouve utile est waitForCondition . Si votre problème semble être un problème avec les événements Ajax, vous pouvez utiliser waitForCondition pour attendre que les événements se produisent.


3

Les pages Web Ext JS peuvent être difficiles à tester, en raison du HTML complexe qu'elles finissent par générer comme avec les grilles Ext JS.

HTML5 Robot gère cela en utilisant une série de bonnes pratiques pour rechercher et interagir de manière fiable avec des composants en fonction d'attributs et de conditions qui ne sont pas dynamiques. Il fournit ensuite des raccourcis pour ce faire avec tous les composants HTML, Ext JS et Sencha Touch avec lesquels vous auriez besoin d'interagir. Il se décline en 2 saveurs:

  1. Java - API familière basée sur Selenium et JUnit qui a intégré la prise en charge des pilotes Web pour tous les navigateurs modernes.
  2. Gwen - Un langage de style humain pour créer et gérer rapidement et facilement des tests de navigateur, qui est livré avec son propre environnement de développement intégré. Tout cela est basé sur l'API Java.

Par exemple, si vous souhaitez trouver la ligne de grille Ext JS contenant le texte "Foo", vous pouvez effectuer les opérations suivantes en Java:

findExtJsGridRow("Foo");

... et vous pouvez faire ce qui suit dans Gwen:

extjsgridrow by text "Foo"

Il y a beaucoup de documentation pour Java et Gwen sur la façon de travailler avec des composants spécifiques à Ext JS. La documentation détaille également le code HTML résultant pour tous ces composants Ext JS, que vous pouvez également trouver utiles.


2

Conseils utiles pour récupérer la grille via l'identifiant de la grille sur la page: Je pense que vous pouvez étendre une fonction plus utile à partir de cette 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

Tests plus faciles grâce aux attributs de données HTML personnalisés

À partir de la documentation Sencha :

Un itemId peut être utilisé comme moyen alternatif pour obtenir une référence à un composant lorsqu'aucune référence d'objet n'est disponible. Au lieu d'utiliser un id avec Ext.getCmp, utilisez itemId avec Ext.container.Container.getComponent qui récupérera les itemId ou id. Les itemId étant un index de la MixedCollection interne du conteneur, l'itemId est étendu localement au conteneur - évitant ainsi les conflits potentiels avec Ext.ComponentManager qui nécessite un identifiant unique.

En remplaçant la méthode Ext.AbstractComponents onBoxReady, j'ai défini un attribut de données personnalisé (dont le nom provient de ma testIdAttrpropriété personnalisée de chaque composant) sur la itemIdvaleur du composant , s'il existe. Ajoutez la Testing.overrides.AbstractComponentclasse au tableau de votre application.jsfichier 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);
  }
});

Cette méthode permet aux développeurs de réutiliser un identifiant descriptif dans leur code et de disposer de ces identifiants chaque fois que la page est rendue. Plus besoin de rechercher des identifiants non descriptifs et générés dynamiquement.


2

Nous développons un framework de test qui utilise le sélénium et avons rencontré des problèmes avec extjs (car il s'agit d'un rendu côté client). Je trouve utile de rechercher un élément une fois que le DOM est prêt.

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

Pour une interface utilisateur complexe qui n'est pas du HTML formel, xPath est toujours quelque chose sur lequel vous pouvez compter, mais un peu complexe lorsqu'il s'agit de différentes implémentations d'interface utilisateur utilisant ExtJs.

Vous pouvez utiliser Firebug et Firexpath comme extensions de Firefox pour tester le xpath d'un certain élément, et passer simplement le xpath complet comme paramètre au sélénium.

Par exemple en code java:

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

selenium.click(fullXpath);

1

Lorsque je testais l'application ExtJS à l'aide de WebDriver, j'ai utilisé l'approche suivante: j'ai recherché le texte champ par étiquette et obtenu l' @forattribut de l'étiquette. Par exemple, nous avons une étiquette

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

Et nous devons pointer WebDriver une entrée: //input[@id=(//label[contains(text(),'Name Of Needed Label')]/@for)].

Ainsi, il choisira l'identifiant de l' @forattribut et l'utilisera davantage. C'est probablement le cas le plus simple, mais il vous donne le moyen de localiser l'élément. C'est beaucoup plus difficile quand vous n'avez pas d'étiquette, mais vous devez ensuite trouver un élément et écrire votre xpath à la recherche de frères et sœurs, d'éléments descendants / ascendants.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.