Nous avons utilisé Selenium avec beaucoup de succès pour gérer les tests de site Web de haut niveau (en plus de doctes étendus python au niveau du module). Cependant, nous utilisons maintenant extjs pour beaucoup de pages et il s'avère difficile d'intégrer des tests de sélénium pour les composants complexes comme les grilles.
Quelqu'un at-il réussi à écrire des tests automatisés pour les 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!
Le plus grand obstacle dans le test d'ExtJS avec Selenium est que ExtJS ne rend pas les éléments HTML standard et le Selenium IDE va naïvement (et à juste titre) générer des commandes ciblées sur les éléments qui agissent simplement comme décor - des éléments superflus qui aident ExtJS à l'ensemble de l'apparence et de la convivialité du bureau. Voici quelques trucs et astuces que j'ai rassemblés lors de l'écriture d'un test Selenium automatisé contre une application ExtJS.
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 aucun changement de code n'a été effectué. Après avoir enregistré les actions de l'utilisateur pour un test, il doit y avoir un effort manuel pour parcourir toutes ces actions qui dépendent des identifiants générés et pour les remplacer. Il existe deux types de remplacements qui peuvent être effectués:
Les localisateurs CSS commencent par "css =" et les localisateurs XPath commencent par "//" (le préfixe "xpath =" est facultatif). Les localisateurs CSS sont moins verbeux et plus faciles à lire et devraient être préférés aux localisateurs XPath. Cependant, il peut y avoir des cas où les localisateurs XPath doivent être utilisés car un localisateur CSS ne peut tout simplement pas le couper.
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 élément <select>
Mais une entrée de texte avec une liste déroulante détachée 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, la localisation de ces éléments via des localisateurs CSS ou XPath peut être fastidieuse. Une alternative consiste à localiser le composant ComoBox lui-même et à appeler des méthodes sur celui-ci 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 commande runScript
peut être utilisée pour effectuer l'opération ci-dessus sous une forme plus concise:
with (Ext.getCmp('genderComboBox')) { setValue('female'); fireEvent('select'); }
Selenium a des saveurs "* AndWait" pour toutes les commandes pour attendre le chargement de la page lorsqu'une action de l'utilisateur entraîne des transitions ou des rechargements de page. Cependant, comme les récupérations de AJAX n'impliquent pas de chargement de page réel, ces commandes ne peuvent pas être utilisées pour la synchronisation. La solution consiste à utiliser des indices visuels tels que la présence/absence d'un indicateur de progression AJAX ou l'apparition de lignes dans une grille, de composants supplémentaires, de liens, etc. Par exemple:
Command: waitForElementNotPresent
Target: css=div:contains('Loading...')
Parfois, un élément n'apparaît qu'après un certain laps de temps, selon la vitesse à laquelle ExtJS rend les composants après qu'une action de l'utilisateur entraîne un changement de vue. Au lieu d'utiliser des retards arbitraires avec la commande pause
, 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 synchronisation résultant de l'exécution des tests dans différents navigateurs ou sur différentes machines rendront les cas de test floconneux.
Certains éléments ne peuvent pas être déclenchés par la commande click
. C'est parce que l'écouteur d'événements est en fait sur le conteneur, à la recherche d'événements de souris sur ses éléments enfants, qui finissent par remonter jusqu'au parent. Le contrôle onglet en est un exemple. Pour cliquer sur l'onglet a, vous devez simuler un événement mouseDown
sur l'étiquette de l'onglet:
Command: mouseDownAt
Target: css=.x-tab-strip-text:contains('Options')
Value: 0,0
Les champs de formulaire (composants Ext.form. *) Qui ont associé des expressions régulières ou des vtypes pour la validation déclencheront la validation avec un certain délai (voir la propriété validationDelay
qui est définie sur 250 ms par défaut), après que l'utilisateur ait saisi du texte ou immédiatement lorsque le champ perd le focus - ou devient flou (voir la propriété validateOnDelay
). Afin de déclencher la validation du champ après avoir émis la commande de type Selenium pour saisir du texte à l'intérieur d'un champ, vous devez effectuer l'une des opérations suivantes:
Déclenchement de la validation différé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 temporisateur, émettez simplement un événement de clé factice (peu importe la clé que vous utilisez car ExtJS l'ignore), suivie d'une courte pause plus longue que validationDelay:
Command: keyUp
Target: someTextArea
Value: x
Command: pause
Target: 500
Déclenchement de la validation immédiate
Vous pouvez injecter un événement flou dans le champ pour déclencher une validation immédiate:
Command: runScript
Target: someComponent.nameTextField.fireEvent("blur")
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 masqué, ExtJS masquera simplement le champ d'erreur au lieu de le supprimer entièrement de l'arborescence DOM.
Option 1
Commande: cliquez sur Cible: bouton css =: contient ('Enregistrer')
Sélectionne le bouton par sa légende
Option 2
Commande: cliquez sur Cible: bouton css = # save-options
Sélectionne le bouton par son identifiant
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.
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 effectuer 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 xtypes et itemIds et ne pas avoir à vous soucier d'essayer d'analyser les trucs fous générés automatiquement.
J'ai trouvé cet article en particulier très utile.
Je pourrais bientôt publier quelque chose de plus détaillé ici - toujours en train de comprendre comment le faire correctement
J'ai testé mon application Web ExtJs avec Selenium. 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 la 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.
Pour détecter que cet élément est visible, vous 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')]"
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.
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 résout ce problème en utilisant une série de meilleures pratiques pour rechercher et interagir de manière fiable avec des composants basés sur des attributs et des 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:
Par exemple, si vous vouliez trouver la ligne de grille Ext JS contenant le texte "Foo", vous pouvez effectuer les opérations suivantes en Java:
findExtJsGridRow("Foo");
... et vous pourriez 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 les composants spécifiques d'Ext JS. La documentation détaille également le code HTML résultant pour tous ces composants Ext JS, qui peuvent également vous être utiles.
Nous développons un framework de test qui utilise Selenium et a rencontré des problèmes avec extjs (puisque c'est le 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");
}
Conseils utiles pour récupérer la grille via Id de grille sur la page: Je pense que vous pouvez étendre la fonction plus utile 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;
}
De la documentation Sencha :
Un itemId peut être utilisé comme un autre moyen d'obtenir une référence à un composant lorsqu'aucune référence d'objet n'est disponible. Au lieu d'utiliser un identifiant avec Ext.getCmp, utilisez itemId avec Ext.container.Container.getComponent qui récupérera les identifiants ou identifiants itemId. Étant donné que les itemId sont un index de la MixedCollection interne du conteneur, l'itemId est délimité localement vers le conteneur - évitant ainsi les conflits potentiels avec Ext.ComponentManager qui nécessite un identifiant unique.
En remplaçant la méthode onBoxReady
du Ext.AbstractComponent
, J'ai défini un attribut de données personnalisé (dont le nom provient de ma propriété testIdAttr
personnalisée de chaque composant) sur le itemId
valeur, si elle existe. Ajoutez la classe Testing.overrides.AbstractComponent
Au tableau requires
de votre fichier application.js
.
/**
* 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 fournit aux développeurs un moyen de réutiliser un identifiant descriptif dans leur code et d'avoir ces identifiants disponibles à chaque fois que la page est rendue. Plus besoin de rechercher des identifiants générés dynamiquement et non descriptifs.
Lorsque je testais l'application ExtJS à l'aide de WebDriver, j'ai utilisé l'approche suivante: j'ai cherché le champ par le texte de l'étiquette et obtenu l'attribut @for
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'id de l'attribut @for
Et l'utilisera plus loin. C'est probablement le cas le plus simple mais il vous permet 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, descendre/monter des éléments.
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 quand il s'agit de différentes implémentations d'interface utilisateur utilisant ExtJs.
Vous pouvez utiliser Firebug et Firexpath comme extensions firefox pour tester le xpath d'un certain élément, et passer simplement xpath complet comme paramètre à Selenium.
Par exemple dans Java code:
String fullXpath = "xpath=//div[@id='mainDiv']//div[contains(@class,'x-grid-row')]//table/tbody/tr[1]/td[1]//button"
Selenium.click(fullXpath);