ZK Testing with Selenium IDE fr
Robert Wenzel, Engineer, Potix Corporation
July 30, 2013
ZK 6.5 (or later)
Introduction
Vous avez donc votre chouette application ZK et vous aimeriez maintenant être de certain qu'elle va fonctionner correctement sur différents navigateurs (et continuera de fonctionner après de changements, ce qui survient souvent de nos jours...). Vous avez bien entendu testé tout ce qui était possible avec votre test unitaire et des tests d'intégration ;-). Vous avez essayé toutes les possibilités offertes par ZATS pour avoir la certitude que vos composants ZK fonctionnent correctement ensemble dans un environnement simulé.
Vous en arrivez donc finalement au point de vouloir garantir le bon fonctionnement de votre application (ou au moins les éléments essentiels de celle-ci) dans un environnement réel (avec d'autres navigateurs, serveur réel d'applications, trafic et latence réseaux réels etc.) - alors, Selenium entre en jeux.
À propos de cet article
Différentes approches existent pour créer des tests avec Selenium:
Par programmation - méthode préférée des développeurs - est déjà développée dans ce Small Talk. Cette méthode vous offre un contrôle maximum sur l'exécution du test et vous permet d'écrire des tests réutilisables, flexibles, facile à mettre à jour et même des cas "intelligents". Bien sure, pour écrire de tels tests, il faut du temps et des ressources.
Il arrive parfois qu'une personne ayant un profil moins technique doive écrire ces tests. On entend alors souvent parler d'outils "record and replay". Selenium IDE possède cette caractéristique "record and replay" mais aussi beaucoup d'autres options adapter et étendre les tests après enregistrement. Dans de raraes cas, les tests enregistrés peuvent être rejoués de façon instantanée. Cependant, particulièrement avec AJAX, certains éléments vont permettre d'éviter l'exécution de vos tests hors carde.
Voici deux exemples (et vous pouvez facilement en trouver d'autres):
- problèmes avec Missing clicks
- quel'qu'un a enregistré ceci video qui explique quelques problèmes
Mais pas de panique, cet article va vous expliquer comment gérer ces cas!
La conclusion majeure est: Voyez plutôt Selenium IDE comme un outil d'enregistrement/adaptation/maintenance et de ré-exécution/débogage lorsque vous construisez vos tests.
Cet article va montrer (via une application web exemple) comment:
- autoriser un enregistrement de test initial dans ZK
- adapter les tests pour pouvoir les rejouer
- garder vos tests maintenables et robustes face aux changements dans vos pages
- ajouter les actions manquantes
Nombre d'idées dans cet article sont basées sur des solutions à des problèmes généraux depuis certains forums et aussi l'expérience des projets passés. Certaines sont simplement le reflet de mes préférences personnelles sur la manière de gérer les choses. Cet article ne montre pas une manière unique d'écrire des tests pour des applications ZK mais met juste en évidence des problèmes et des solutions, discute de certains considérations prises, et explique pourquoi cette approche a été choisie.
Bonne lecture et n'hésitez pas à commenter !!
Environment
- Firefox 22
- Selenium IDE 2.1.0 (Selenium IDE 2.0.0 ne fonctionne pas avec FF >=22)
- JDK 6/7
- ZK 6.5.3
- Maven 3.0
Étapes
Initialisez votre environnement pour cet exemple
- Installez le plugin Selenium IDE dans Firefox via Addons ou download documentation page
- Maven 3.0 download & install
- download le projet exemple, il contient les dossiers suivants:
- selenium_example/ (l'application exemple, le projet maven)
- extensions/ (les extension selenium utilisées dans cet article)
- test_suite/ (une suite de tests Selenium illustrant les concepts discutés ici)
- lancez depuis une ligne de commande
cd selenium_example_app mvn jetty:run
- ou exécutez selenium_testing_app/launch_for_PROD.bat / .sh
- ouvrez l'application test http://localhost:8080/selenium_testing
Situation initiale
- Lancez Firefox
- Lancez Selenium IDE CTRL+ALT+S
- Mettez l'URL de base à "http://localhost:8080/"
- Ouvrez l'application exemple http://localhost:8080/selenium_testing
- Enregistrez le login-test et essayez de le ré-exécuter
New Test | ||
open | /selenium_testing/login.zul | |
type | id=jXDW5 | test |
type | id=jXDW8 | test |
clickAndWait | id=jXDWb |
Cela parait difficile à lire / maintenir et ne fonctionne même pas :'(
En jetant un coup d’œil aux commandes enregistrées et en comparant les IDs à la page source, nous remarquons que ces derniers sont générés et changés à chaque rafraîchissement de la page. Aucune chance donc d'enregistrer / rejouer de cette manière.
Une manière de rendre les IDs prévisibles est montrée dans la section suivante.
Générateur d'ID sur mesure
Comme mentionnée dans l'autre Small Talks sur les tests, une stratégie est d'utiliser une implémentation d'un générateur d'ID sur mesure pour créer des ID's de composants qui soient prévisibles, lisibles (avec un nom business) et facilement sélectionnable par selenium.
public class TestingIdGenerator implements IdGenerator {
public String nextComponentUuid(Desktop desktop, Component comp,
ComponentInfo compInfo) {
int i = Integer.parseInt(desktop.getAttribute("Id_Num").toString());
i++;// Start from 1
StringBuilder uuid = new StringBuilder("");
desktop.setAttribute("Id_Num", String.valueOf(i));
if(compInfo != null) {
String id = getId(compInfo);
if(id != null) {
uuid.append(id).append("_");
}
String tag = compInfo.getTag();
if(tag != null) {
uuid.append(tag).append("_");
}
}
return uuid.length() == 0 ? "zkcomp_" + i : uuid.append(i).toString();
}
Les IDs ressembleront à ceci:
- {id}_{tag}_{##} (pour les composants avec un id donné)
- {tag}_{##} (pour les composants sans id donnée - p.e. listitems dans une listbox)
- {zkcomp}_{##} (pour les autres cas, histoire simplement de les rendre uniques)
p.e. un bouton dans un fichier zul
<button id="login" label="login" />
deviendra quelque chose comme ceci en HTML dans un navigateur (le nombre peut varier):
<button id="login_button_12" class="z-button-os" type="button">login</button>
Pour séparer la production de la configuration test, on peut inclure un ficher de config supplémentaire au démarrage pour autoriser le TestingIdGenerator uniquement dans les tests. Avec Zk, ceci est possible en configurant la propriété de librairies org.zkoss.zk.config.path (voir aussi notre Testing Tips)
- p.e. via l'arguement VM -Dorg.zkoss.zk.config.path=/WEB-INF/zk-test.xml
Windows:
set MAVEN_OPTS=-Dorg.zkoss.zk.config.path=/WEB-INF/zk-test.xml mvn jetty:run
Linux:
export MAVEN_OPTS=-Dorg.zkoss.zk.config.path=/WEB-INF/zk-test.xml mvn jetty:run
ou exécutez selenium_testing_app/launch_for_TEST.bat / .sh
On a maintenant des ID's déterminés lors de l'enregistrement d'un test.
Après l'enregistrement, on relance le test et on obtient ceci:
login test | ||
---|---|---|
open | /selenium_testing/login.zul | |
type | id=username_textbox_6 | test |
type | id=password_textbox_9 | test |
clickAndWait | id=login_button_12 |
Ça parait déjà mieux et ça s'explique de sois-même, mais malheureusement cela no fonctionne toujours pas si on le relance.
Pourquoi cela se passe-t-il et comment le régler ? Voyons la section suivante ...
Détails spécifiques à ZK
Vu la nature des applications web riches, JavaScript est souvent très utilisé mais Selenium IDE n'enregistre pas par défaut tous les événements. Dans notre cas, les événements "blur" des champs d'entrée n'ont pas été enregistrés alors que ZK se base sur eux pour déterminer les champs mis à jour. On doit donc ajouter manuellement la commande selenium "fireEvent target blur". Selenium n'offre malheureusement pas d'équivalente à "focus".
login test | ||
---|---|---|
open | /selenium_testing/login.zul | |
type | id=username_textbox_6 | test |
fireEvent | id=username_textbox_6 | blur |
type | id=password_textbox_9 | test |
fireEvent | id=password_textbox_9 | blur |
clickAndWait | id=login_button_12 | |
assertLocation | */selenium_testing/index.zul | |
verifyTextPresent | Welcome test | |
verifyTextPresent | Feedback Overview |
Si on relance le script, maintenant cela fonctionne :D et on voit la page overview (on a aussi ajouté quelques vérifications pour cela).
Extensions Selenium
Si vous avez le sentiment qu'il est difficile de se souvenir de la syntaxe de "fireEvent" ou si vous pensez qu'il est trop compliqué d'ajouter une ligne additionnelle juste pour mettre à jour un champ d'entrée, utiliser une Selenium Core extension peut vous aider. Le fichier d'extension, généralement appelé user-extensions.js peut être utilisé par Selenium IDE et par Selenium RC lorsque les tests se font en dehors de Selenium IDE (merci de vous référer à selenium documentation).
L'extrait suivant montrent deux actions simples:
- blur
- pratique pour éviter "fireEvent locator blur"
- typeAndBlur
- combine le type avec un événement blur, pour rendre ZK attentif aux changement de la valeur entrée
Selenium.prototype.doBlur = function(locator) {
// All locator-strategies are automatically handled by "findElement"
var element = this.page().findElement(locator);
// Fire the "blur" event
this.doFireEvent(locator, "blur")
};
Selenium.prototype.doTypeAndBlur = function(locator, text) {
// All locator-strategies are automatically handled by "findElement"
var element = this.page().findElement(locator);
// Replace the element text with the new text
this.page().replaceText(element, text);
// Fire the "blur" event
this.doFireEvent(locator, "blur")
};
Pour l'activer, ajoutez le fichier (extensions/user-extension.js dans le zip de l'exemple) à la configuration de Selenium IDE. (le fichier contient une autre extension ... voir custom locators)
- (Selenium IDE Options > Options... > [General -Tab] > Selenium Core extensions)
Redémarrez ensuite Selenium IDE - fermez la fenêtre et rouvrez-la - p.e. avec [CTRL+ALT+S]
Test adapté qui utilise l'action blur:
login test | ||
---|---|---|
open | /selenium_testing/login.zul | |
type | id=username_textbox_6 | test |
blur | id=username_textbox_6 | |
type | id=password_textbox_9 | test |
blur | id=password_textbox_9 | |
clickAndWait | id=login_button_12 |
Même test en utilisant typeAndBlur:
login test | ||
---|---|---|
open | /selenium_testing/login.zul | |
typeAndBlur | id=username_textbox_6 | test |
typeAndBlur | id=password_textbox_9 | test |
clickAndWait | id=login_button_12 |
Il n'est pas nécessaire d'utiliser l'une de ces extensions mais ma préférence personnelle va à l'économie d'une ligne pour chaque entrée.
Une autre chose à garder à l'esprit est le manque de robustesse et l'effort de maintenance à fournir pour les tests quand des changements apparaissent dans l'UI ... les numéros générés à la fin de chaque ID restent un obstacle parce qu'ils sont sujets à changer chaque fois qu'un composant est ajouté ou retiré au dessus de ce composant.
Ne serait-il pas sympa de n'avoir à adapter aussi souvent les tests ? Voyons la suite...
Améliorer la robustesse et la maintenance
Locators dans Selenium
Pour enlever les nombres hard codés des ID composants de notre test, Selenium offre p.e. XPath ou CSS locators.
En utilisant XPath, est-il possible de sélectionner un nœud à l'aide de son préfixe ID ?:
//input[starts-with(@id, 'username_')]
Cela va fonctionner tant que le préfixe "username_" est unique sur la page, sans quoi l'action sera exécutée sur le premier élément trouvé. C'est donc une bonne idée, dans ce scénario, de donner aux widgets des ID appropriés (plus: cela va aussi augmenter la lisibilité du code source).
Dans des cas plus complexes, on peut sélectionner des composants imbriqués pour distinguer les composants avec le même ID (p.e. dans des espaces ID différents). p.e. si le composant "street" apparaît plusieurs fois sur la page, utilisez ceci:
//div[starts-with(@id, 'deliveryAddress_')]//input[starts-with(@id, 'street_')] //div[starts-with(@id, 'billingAddress_')]//input[starts-with(@id, 'street_')]
Un autre exemple pour localiser le bouton "delete" dans la ligne sélectionnée d'une listbox qui utilise des préfixes ID, CSS-class et comparaison de texte.
//div[starts-with(@id, 'overviewList_')]//tr[contains(@class, 'z-listitem-seld')]//button[text() = 'delete']
NOTE: Veillez à ne pas utiliser l'opérateur "//" de façon trop extensive dans XPath, cela pourrait ne pas fonctionner (cela n'a jamais été un problème pour moi jusqu’à présent vu qu'il y a des goulots d’étranglement bien pire en terme de performance qui affectent la vitesse d'exécution de votre test - voir plus tard).
Ce lien sheet provided by Michael Sorens est un excellente référence avec plein d'exemples utiles sur comment sélectionner des éléments de page dans diverses situations.
On peut donc maintenant changer le test pour utiliser cette sorte de XPath locator, en cherchant uniquement avec le préfixe ID:
login test | ||
---|---|---|
open | /selenium_testing/login.zul | |
typeAndBlur | //input[starts-with(@id, 'username_')] | test |
typeAndBlur | //input[starts-with(@id, 'password_')] | test |
clickAndWait | //button[starts-with(@id, 'login_')] |
Ceci fait, l'ordre des composants peut changer sans casser le test, tant que les IDs ne sont pas changés. Ceci requirt bien entendu un peu de travail manuel mais l'effort investi une fois payera rapidement au cours de l'évolution des votre projet.
Custom Locator et LocatorBuilder
Si vous ne voulez pas changer le locator vers XPath de façon manuelle à chaque fois que vous enregistre un test, Selenium IDE vous offre une solution qui va encore augmenter la lisibilité.
Custom Locator
- Dans le user-extensions.js d'ici plus haut, vous allez trouver un custom locator qui va cacher une partie de la complexité de XPath pour des scénarios simples. C'est basé sur le format des IDs générés par TestingIdGenerator (voir plus haut).
// The "inDocument" is the document you are searching.
PageBot.prototype.locateElementByZkTest = function(text, inDocument) {
// Create the text to search for
var text2 = text.trim();
var separatorIndex = text2.indexOf(" ");
var elementNameAndIdPrefix = (separatorIndex != -1 ? text2.substring(0, separatorIndex) : text2).split("#");
var xpathSuffix = separatorIndex != -1 ? text2.substring(separatorIndex + 1) : "";
var elementName = elementNameAndIdPrefix[0] || "*";
var idPrefix = elementNameAndIdPrefix[1];
var xpath = "//" + elementName + "[starts-with(@id, '" + idPrefix + "')]" + xpathSuffix;
return this.xpathEvaluator.selectSingleNode(inDocument, xpath, null, this._namespaceResolver);
};
Ce locator va travailler selon 2 formes
1. zktest=#login_ 2. zktest=input#username_
XPaths généré/questionné en interne sera
1. //*[starts-with(@id, 'login_')] 2. //input[starts-with(@id, 'username_')]
- cherchera les éléments dont l'ID commence par "login_"
- testera aussi les éléments avec tag html, et trouvera l'élément <input> avec le préfixe ID "username_..."
De plus, il est possible de joindre n'importe quel XPath supplémentaire pour affiner les résultats.
p.e. (pour localiser la deuxième option d'un élement <select>)
zktest=select#priority_ /option[2]
deviendra dans XPath
//select[starts-with(@id, 'priority_')]/option[2]
Même si ce locator est très basique (peut être étendu si besoin), il augmente déjà la lisibilité de nombreux use-cases.
Custom LocatorBuilder
Pour rendre ceci très maniable, Selenium IDE offre aussi des extensions (ne pas mélanger avec Selenium Core Extension)
Ajoutez le ficher extension IDE extensions/zktest-Selenium-IDE-extension.js depuis example.zip à la config selenium
- (Selenium IDE Options > Options... > [General -Tab] > Selenium IDE extensions)
et déplacez le vers le haut dans la liste de priorités du Locator Builder.
- (Selenium IDE Options > Options... > [Locator Builders - Tab])
Il contient le LocatorBuilder suivant qui va générer le locator dans la forme "zktest=elementTag#IdPrefix" mentionnée ci-dessus en utilisant uniquement la première partie de l'ID (le préfixe ID - son nom business). Si un ID ne contient pas 3 tokens séparés par "_", il utilisera l'ID complet à la plac en incluant le numéro séquentiel. Ceci indiquera que l'élément n'a pas d'ID spécifié et une autre approche de localisation peut être meilleure, ou donnez lui juste un ID dans le fichier zul.
LocatorBuilders.add('zktest', function(e) {
var idTokens = e.id.split("_")
if (idTokens.length > 2) {
idTokens.pop();
idTokens.pop();
return "zktest=" + e.nodeName.toLowerCase() + "#" + idTokens.join("_") + "_";
} else {
return "zktest=" + "#" + e.id;
}
return null;
});
Redémarrez Selenium IDE et enregistrez le test à nouveau... vous aurez ceci (après avoir changé les événements de type à typeAndBlur).
New Test | ||
---|---|---|
open | /selenium_testing/login.zul | |
typeAndBlur | zktest=input#username_ | test |
typeAndBlur | zktest=input#password_ | test |
clickAndWait | zktest=button#login_ |
Plus de Tests
Faisons un test pour l'édition et la sauvegarde d'un "feedback item" existant dans la liste, vérifions les résultats et faisons un test de lougout. Ces tests introduisent des nouveaux challenges pour tester une application ajax avec Selenium - et leurs solutions ou contournements.
Maintenant que nous changeons les données, j'ai adapté le test de login pour utiliser un utilisateur aléatoire à chaque fois. Dès lors, l'implémentation Mock fournira des données fraîches à chaque essai, sans avoir à redémarrer le serveur ni à faire de nettoyage (il existe d'autres stratégies pour faire la même chose, c'est simplement celle que j'utilise ici).
login test | ||
---|---|---|
store | javascript{'testUser'+Math.floor(Math.random()*10000000)} | username |
open | /selenium_testing/login.zul | |
typeAndBlur | zktest=input#username_ | ${username} |
typeAndBlur | zktest=input#password_ | ${username} |
clickAndWait | zktest=button#login_ | |
assertLocation | */selenium_testing/index.zul | |
verifyTextPresent | logout ${username} |
Test Edit et Save
Après avoir enregistré et appliqué les actions typeAndBlur, on obtient ceci:
edit save test | ||
---|---|---|
open | /selenium_testing/index.zul | |
click | zktest=#button_27 | |
typeAndBlur | zktest=input#subject_ | Subject 1 (updated) |
typeAndBlur | zktest=input#topic_ | consulting |
select | zktest=select#priority_ | label=low |
click | zktest=#listitem_62 | |
typeAndBlur | zktest=textarea#comment_ | test comment (content also updated) |
click | zktest=button#submit_ | |
assertTextPresent | Feedback successfully updated | |
verifyTextPresent | subject=Subject 1 (updated) | |
verifyTextPresent | [text=test comment (content also updated)] | |
verifyTextPresent | priority=low | |
click | zktest=button#backToOverview_ | |
assertTextPresent | Feedback Overview |
Nous voyons toujours quelques valeurs hard-codées. Je vais juste supprimer "click zktest=#listitem_62" vu que la commande "select" fera l'affaire (Selenium IDE va parfois enregistrer plus que nécessaire, et parfois pas assez - par défaut).
Ce qui est intéressant maintenant est le locator "zktest=#button_27" - un des boutons "edit". Si nous essayons de lui donner un ID dans le code zul, cela ne donnera aucun résultat (ID dupliqué) pcq le bouton est déclaré plusieurs fois - à chaque <listitem>. (un contournement possible serait de l'encadrer avec un composant <idspace> ou d'inclure les boutons depuis un fichier différent). Mais cela n'est pas nécessaire - en utilisant le bon locator XPath.
Localiser un élement dans une Listbox
Si vous voulez localiser le premier bouton edit dans une liste, vous pouvez utiliser(il trouvera le bouton par son texte et non son ID):
pure XPath:
//*[starts-with(@id, 'overviewList_')]//button[text() = 'edit']
ou combiner avec le selecteur "zktest" de l'extension selenium vue précédemment
zktest=#overviewList_ //button[text() = 'edit']
Il est plus intéressant de localiser par l'index, p.e. exactement le deuxième bouton edit
xpath=(//*[starts-with(@id, 'overviewList_')]//button[text() = 'edit'])[2]
ou plus marrant de sélectionner le bouton "edit" dans un <listitem> contenant un texte spécifique dans une cellule
xpath=//*[starts-with(@id, 'overviewList_')]//*[starts-with(@id, 'listitem_')][.//*[starts-with(@id, 'listcell_')][text() = 'Subject 2']]//button[text() = 'edit']
Cela peut devenir très complexe et diminuer la lisibilité de votre test. C'est donc toujours une bonne idée d'écrire des comments (voir screenshot en dessous) dans vos test (oui, ils peuvent aussi avoir des commentaires).
Bien que compliqué, ce dernier locator reste stable et fait face aux changements de l'UI. Si le contenu de <listbox> changes, vous pouvez toujours l'utiliser pour trouver le <listitem> (et le bouton qu'il contient) plusieurs fois au cours d'un test.
Utiliser des variables
Maintenir ces locators dans vos scripts de tests va vous donner la migraine, p.e. si le label d'un bouton change de "edit" à "update". Il est plus simple de "stocker" les chaines de caractères, ou une partie de celles-ci, dans des variables qui peuvent être réutilisées tout au long du test.
Ce ne sont que quelques idées générales sur comment réduire le travail de maintenance des tests, en augmentant leur stabilité ou en les rendant plus lisibles - moyennant un investissement raisonnable lors de la mise sur pied initiale.
Attendre AJAX
Bien que la ré-exécution du test à pleine vitesse (ce qui est toujours l'objectif) va, ou pourrait parfois, échouer (pcq on attend pas assez longtemps les réponses d'ajax)... il n'y a pas de rechargement complet de page, donc Selenium IDE n'attend pas de manière automatique.
Une idée serait de réduire la vitesse du test... ce qui affecterait chaque étape dans tous les tests, nous essayons donc d'éviter cela. Aussi, dans notre exemple de feedback, l'opération du click sur le bouton "submit" est longue (2 secondes). Retarder chaque étape de 2 secondes provoquerait des dégâts considérables sur la durée de notre replay global.
Un autre possibilité est d'utiliser la commande pause et spécifier le nombre de millisecondes à attendre. Ici aussi la vitesse d'exécution peut varier et nous serons donc trop court, ce qui va engendrer des erreurs, ou trop longs ("juste pour être certain") et gaspiller du temps, ces deux effets étant indésirables.
Heureusement, Selenium fournit une variété d'actions du type waitFor... qui interrompent l'exécution tant qu'une condition n'est pas rencontrée. Une section très utile:
- waitForTextPresent - attend q'un texte soit présent sur le page
- waitForText - attend qu'un élément reçoive un texte
- waitForElementPresent - attend qu'une élément soit présenté
- waitForVisible - attend qu'un élément devienne visible
Le test "edit et save" précédent devrait échouer à la ligne 3 juste après le click sur le bouton "edit". Entre l'étape 2 et l'étape 3, ZK met à jour la page avec des éléments d'AJAX, ce qui provoque un délais court (mais long assez que pour faire échouer le test - lorsqu'il est à pleine vitesse).
edit save test | ||
---|---|---|
open | /selenium_testing/index.zul | |
click | zktest=#overviewList_ //button[text() = 'edit'] | |
typeAndBlur | zktest=input#subject_ | Subject 1 (updated) |
typeAndBlur | zktest=input#topic_ | consulting |
select | zktest=select#priority_ | label=low |
typeAndBlur | zktest=textarea#comment_ | test comment (content also updated) |
click | zktest=button#submit_ | |
assertTextPresent | Feedback successfully updated | |
... | ... | ... |
On doit donc attendre p.e. que le nouveau Headline "New/Edit Feedback" soit présent, ce qui indique que la mise à jour AJAX est terminée. Et attendre en conséquence après l'envoi de l'article de feedback - remplacer "assertTextPresent" par "waitForTextPresent".
edit save test | ||
---|---|---|
open | /selenium_testing/index.zul | |
click | zktest=#overviewList_ //button[text() = 'edit'] | |
waitForTextPresent | New/Edit Feedback | |
typeAndBlur | zktest=input#subject_ | Subject 1 (updated) |
typeAndBlur | zktest=input#topic_ | consulting |
select | zktest=select#priority_ | label=low |
typeAndBlur | zktest=textarea#comment_ | test comment (content also updated) |
click | zktest=button#submit_ | |
waitForTextPresent | Feedback successfully updated | |
verifyTextPresent | subject=Subject 1 (updated) | |
verifyTextPresent | [text=test comment (content also updated)] | |
verifyTextPresent | priority=low | |
click | zktest=button#backToOverview_ | |
waitForTextPresent | Feedback Overview |
Ce test peut maintenant fonctionner aussi vite que possible, s'adapter automatiquement à une quelconque augmentation ou diminution de vitesse du serveur de test.
Il est aussi possible d'attendre de façon implicite qu'AJAX ait fini à un niveau technique [traité ici]. Je pense personnellement que c'est tentant au premier abord. Mais après réflexion, je préfère l'idée d'une attente explicite (via waitForXXX) - seulement quand il faut attendre.
Vous pourez aussi trouver par vous-même quelque-chose sur votre page qui affecte la performance (= expérience utilisateur) - si un test échoue soudainement après un changement qui affecte la capacité de réponse de votre application.
De plus, il pourrait y avoir un autre délais entre le moment ou AJAX a terminé et le moment où le composant affecté par cette réponse est réellement visible sur la page(p.e. à cause d'une animation). Cette approche pourrait donc toujours échouer - à garder en mémoire.
Logout Test
This test should be very simple, but still there is a catch. When trying to record this test, we notice that Seleniume-IDE won't record clicking on the logout button. Selenium IDE is not very predictable about which events are recorded and which are ignored. In this case the it is a <toolbarbutton> which is rendered as a <div> so maybe that's why.
(There is a [stackoverflow question about this] and also an [IDE extension] available to enable recording of ALL clicks in a test. I tried this one - and didn't like the big number of clicks recorded. In the end I leave it up to you to uncomment this in the IDE extension for Locator Builder provided above and judge yourself - maybe you want to test exactly this.)
As we know its name (or we can easily find out with tools like firebug) we just add another click event manually and change the locator to "zktest=div#logout_"
logout test | ||
---|---|---|
click | zktest=div#logout_ | |
waitForTextPresent | Login (with same username as password) | |
assertLocation | */selenium_testing/login.zul |
Other Components Examples
Other components require some extra attention too when recording events on them (sometimes you might wonder why the results are varying). So here are just a few examples.
Button (mold="trendy")
The "new/edit page" of the example application contains 2 submit buttons - rendered using different molds: "default" and "trendy". Both have the same effect (save the feedback item). When you try to record the click event of the second one - labelled "submitTrendy" - Selenium IDE just ignores the click.
Funny thing is, when you first focus another window on your desktop and then click the button directly - without prior focusing the browser window - it gets recorded. But our custom locator builder does not match here. So we get some other generic locator strings. (The "trendy" button is rendered using a layout table and images for the round corners.)
The second choice contains the component ID, we can use to build our own locator string.
zktest=#submitTrendy_
This would usually be sufficient but not in this example. When we test it, clicking on the "Find" button in Selenium IDE we see it is selecting the whole cell around it. ZK has generated some html elements of the layout grid around our button sharing the same ID prefix. Inspecting the element in the browser we can see the element we really want to click and that it is an element with a style class "z-button"
<span id="submitTrendy_button_129" class="z-button">
So a more specific locator would look like this
zktest=span#submitTrendy_
or that
zktest=#submitTrendy_ [@class = "z-button"]
equivalent XPaths are:
//span[starts-with(@id, 'submitTrendy_')] //*[starts-with(@id, 'submitTrendy_')][@class="z-button"]
Menu
Recording events on a <menubar> (even with submenus) is generally very simple. Unless you click the "wrong" areas while recording highlighted in red see image below. If you just click the menu(item)-texts (green) Selenium IDE will create nice locators, otherwise it will fallback to some other strategy, using CSS or generated XPath which might surprise you (this is no bug in Selenium IDE, it is just a different html element receiving the click event).
menu test (not working for us) | ||
---|---|---|
click | css=td.z-menu-inner-m > div | |
click | css=span.z-menuitem-img |
What you get avoiding the red areas.
menu test (what we want) | ||
---|---|---|
click | zktest=button#feedbackMenu_ | |
click | zktest=a#newFeedbackMenuItem_ |
Tabbox
When all your <tab>s and <tabpanel>s (and nested components) have unique IDs everything is simple, but when it comes to dynamically added tabs without nice Ids things get a bit tricky.
So in our example some manual work is required to record interactions with the "comments" tabbox.
The "new comment"-button is again a toolbarbutton like the "logout"-button, so its events are not recorded automatically to create a new comment-tab just add this "click zktest=div#newComment_" manually will create a new tab. Now how to activate the new tab (Comment 1).
Locating a Tab
Automatic recording will give us this, which will work for now, and fail again after a change to the page.
click zktest=#tab_261-cnt
To avoid the hard-coded index, and truly being sure the second tab (inside our comments tabbox) is selected I would prefer this
//div[starts-with(@id, 'comments_')]//li[starts-with(@id, 'tab_')][2]
or (locating by label)
//div[starts-with(@id, 'comments_')]//li//span[text() = 'Comment 1']
It could be simplified increasing the chance of possible future failure (if there is another li inside the "comments" tabbox) using the custom zktest locator
zktest=div#comments_ //li//span[text() = 'Comment 1']
Locating a Tabpanel
Locating e.g. the <textarea> to edit the comment of the currently selected tab is somewhat more difficult, as the <tabpanels> component is not in the same branch of the Browser-DOM tree as the <tabs> component.
Easiest is to locate by index (if you know it):
//div[starts-with(@id, 'comments_')]//div[starts-with(@id, 'tabpanel_')][2]//textarea
or
zktest=div#comments_ //div[starts-with(@id, 'tabpanel_')][2]//textarea
Selecting by label of selected Tab is somewhat complex (I am sure there is a simpler solution to it - if you know feel free to share):
xpath=(//div[starts-with(@id, 'comments_')]//textarea[starts-with(@id, 'comment_')])[count(//div[starts-with(@id, 'comments_')]//li[.//span[text() = 'Comment 2']]/preceding-sibling::*)+1]
Once again we see locators are very flexible - just be creative.
Combobox
Comboboxes are also very dynamic components. So if you just want to test your page, it is easiest to just "typeAndBlur" a value into them.
typeAndBlur zktest=input#topic_ theValue
If you really need to test the <comboitems> are populated correctly, my first suggestion is to test your ListSubModel implementation in a unit test in pure java code. But if you really really need to test this in a page test you can select the combobox-dropdown button with:
zktest=i#topic_ /i or zktest=i#topic_ [contains(@id, '-btn')]
XPath equivalents
//i[starts-with(@id, 'topic_')]/i or //i[starts-with(@id, 'topic_')][contains(@id, '-btn')]
To select the "consulting" <comboitem> - via mouse - you could use the following:
New Test | ||
---|---|---|
click | zktest=i#topic_ /i | |
waitForVisible | zktest=div#topic_ /table | |
click | zktest=div#topic_ //tr[starts-with(@id, 'comboitem_')]//td[text() = 'consulting'] |
In many cases there is no general recipe, about what is right. In most cases it helps to inspect the page source and do what you need.
Putting it all together
The example package contains a running test suite featuring all the discussed topics from above.
It contains 4 test cases. 3 from above and a longer test case "new re-edit test" showing some more complex component interactions.
- login test
- edit save test
- new re-edit test
- logout test
To see it running you need to:
- Unpack the downloaded zip file
- Launch the testing application in test mode (/selenium_testing_app/launch_for_TEST.bat or selenium_testing_app/launch_for_TEST.sh)
- Open the Selenium IDE in Firefox ([CTRL+ALT+S])
- Make sure the extensions are configured (/extensions/user-extension.js) - if not, configure them, close and restart Selenium IDE
- In Selenium IDE open the sample test suite (/test_suite/suite.html)
- Check the Base URL - should be http://localhost:8080/
- Click the button "Play entire Test Suite"
- Lean back and watch...
Run against different Browsers using Selenium Server
There are many ways of running your tests against different browsers using Selenium Server. As this is not the focus of this document, here are just a few command line examples to see if your suite will actually run outside of Selenium IDE using Selenium Server 2.33 (while writing this document the latest version 2.33 only supports Firefox up to version 21.0)
Place the selenium-server-standalone-2.33.0.jar in the folder where you unzipped this example and run a command prompt at the same path: (of course you might have to adapt the paths of your browser executables)
execute with Firefox 21.0:
java -jar selenium-server-standalone-2.33.0.jar -userExtensions extensions/user-extensions.js -htmlSuite "*firefox C:\Program Files (x86)\Mozilla Firefox 21\firefox.exe" "http://localhost:8080" test_suite/suite.html test_suite/results.html
execute with google chrome:
java -jar selenium-server-standalone-2.33.0.jar -userExtensions extensions/user-extensions.js -htmlSuite "*googlechrome C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" "http://localhost:8080" test_suite/suite.html test_suite/results.html
Appendix
Download
Comments
Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License. |