Dans le dernier article, j'ai parlé de quelques idées et modèles, tels que le modèle Objet de page, qui aident à écrire des tests d'interface utilisateur maintenables. Dans cet article, nous allons aborder quelques sujets avancés qui pourraient vous aider à écrire des tests plus robustes et à les résoudre en cas d'échec:
Je vais utiliser Selenium pour les sujets relatifs à l’automatisation du navigateur abordés dans cet article..
Tout comme dans l’article précédent, les concepts et solutions abordés dans cet article sont applicables quels que soient le langage et le cadre d’interface utilisateur que vous utilisez. Avant d’aller plus loin, veuillez lire l’article précédent car je vais me référer à celui-ci et à son exemple de code à quelques reprises. Ne t'inquiète pas Je vais attendre ici.
Ajouter Thread.Sleep
(ou généralement retarde) se sent comme un hack inévitable quand il s'agit de tester l'interface utilisateur. Vous avez un test qui échoue par intermittence et après une enquête, vous remontez à des retards occasionnels dans la réponse; Par exemple, vous accédez à une page et recherchez ou assignez quelque chose avant que la page ne soit complètement chargée et que votre infrastructure d'automatisation du navigateur lève une exception indiquant que l'élément n'existe pas. Beaucoup de choses pourraient contribuer à ce retard. Par exemple:
Ou un mélange de ces problèmes et d’autres.
Supposons qu'une page nécessite normalement moins d'une seconde à charger, mais que les tests qui y parviennent échouent de temps en temps en raison d'un délai de réponse occasionnel. Vous avez quelques options:
Vous voyez, il n'y a pas de gain avec des retards arbitraires: vous obtenez une suite de tests lente ou fragile. Ici, je vais vous montrer comment éviter d'insérer des délais fixes dans vos tests. Nous allons discuter de deux types de délais qui devraient couvrir à peu près tous les cas que vous devez traiter: ajouter un délai global et attendre que quelque chose se produise..
Si le chargement de toutes vos pages prend à peu près le même temps, ce qui est plus long que prévu, la plupart des tests échoueront à cause d'une réponse inopportune. Dans de tels cas, vous pouvez utiliser les demandes implicites:
Une attente implicite consiste à indiquer à WebDriver d'interroger le DOM pendant un certain temps lors de la recherche d'un ou de plusieurs éléments s'ils ne sont pas immédiatement disponibles. Le paramètre par défaut est 0. Une fois défini, l'attente implicite est définie pour la durée de vie de l'instance d'objet WebDriver..
Voici comment vous définissez une attente implicite:
Pilote WebDriver = new FirefoxDriver (); driver.Manage (). Timeouts (). ImplicitlyWait (TimeSpan.FromSeconds (5));
De cette façon, vous dites à Selenium d'attendre jusqu'à 5 secondes lorsqu'il essaie de trouver un élément ou d'interagir avec la page. Alors maintenant, vous pouvez écrire:
driver.Url = "http: // somedomain / url_that_delays_loading"; IWebElement myDynamicElement = driver.FindElement (By.Id ("someDynamicElement"));
au lieu de:
driver.Url = "http: // somedomain / url_that_delays_loading"; Thread.Sleep (5000); IWebElement myDynamicElement = driver.FindElement (By.Id ("someDynamicElement"));
L’avantage de cette approche est que FindElement
reviendra dès qu'il trouvera l'élément et n'attendra pas les 5 secondes lorsque l'élément sera disponible plus tôt.
Une fois que l'attente implicite est définie sur votre WebDriver
par exemple, cela s'applique à toutes les actions sur le pilote; afin que vous puissiez vous débarrasser de beaucoup Thread.Sleep
s dans votre code.
5 secondes, c’est une attente que j’ai faite pour cet article - vous devriez trouver l’attente implicite optimale pour votre application et vous devriez faire cette attente aussi courte que possible. À partir des documentations de l'API:
L'augmentation du délai d'attente implicite doit être utilisée à bon escient, car elle aura un effet défavorable sur la durée d'exécution du test, en particulier si elle est utilisée avec des stratégies de localisation plus lentes, telles que XPath..
Même si vous n'utilisez pas XPath, l'utilisation de longues attentes implicites ralentit vos tests, en particulier lorsque certains tests échouent réellement, car le pilote Web attend longtemps avant d'expiration et qu'il lève une exception..
L'utilisation de l'attente implicite est un excellent moyen de supprimer de nombreux retards de codage en dur dans votre code; mais vous allez toujours vous retrouver dans une situation où vous devez ajouter des délais fixes dans votre code car vous attendez que quelque chose se passe: une page est plus lente que toutes les autres pages et vous devez attendre plus longtemps. attendre qu'un appel AJAX se termine ou qu'un élément apparaisse ou disparaisse de la page, etc. C'est pour cette raison que vous devez attendre explicitement.
Donc, vous avez défini l'attente implicite à 5 secondes et cela fonctionne pour beaucoup de vos tests; mais il reste encore quelques pages dont le chargement prend parfois plus de 5 secondes et donne lieu à des tests infructueux.
En guise de remarque, vous devriez d'abord rechercher la raison pour laquelle une page prend si longtemps avant d'essayer de réparer le test interrompu en le faisant attendre plus longtemps. Il peut y avoir un problème de performances sur la page entraînant le test rouge, auquel cas vous devez corriger la page, pas le test..
Dans le cas d'une page lente, vous pouvez remplacer les délais fixes par Explicit Waits:
Une attente explicite est le code que vous définissez pour attendre qu'une certaine condition se produise avant d'aller plus loin dans le code..
Vous pouvez appliquer des attentes explicites en utilisant WebDriverWait
classe. WebDriverWait
vit à WebDriver.Support
assemblage et peut être installé à l'aide de Selenium.Support nuget:
////// Permet d'attendre une condition arbitraire pendant l'exécution du test. /// Classe publique WebDriverWait: DefaultWait/// /// Initialise une nouvelle instance du /// L'instance WebDriver utilisée pour attendre.La valeur de délai d'attente indiquant combien de temps attendre la condition. public WebDriverWait (pilote IWebDriver, délai d'attente TimeSpan); ///classe. /// /// Initialise une nouvelle instance du /// Un objet implémentant leclasse. /// interface utilisée pour déterminer quand le temps s'est écoulé.L'instance WebDriver utilisée pour attendre.La valeur de délai d'attente indiquant combien de temps attendre la condition.UNE valeur indiquant à quelle fréquence vérifier si la condition est vraie. public WebDriverWait (horloge IClock, pilote IWebDriver, délai d'attente TimeSpan, TimeSpan sleepInterval);
Voici un exemple de la façon dont vous pouvez utiliser WebDriverWait
dans vos tests:
driver.Url = "http: // somedomain / url_that_takes_a_long_time_to_load"; WebDriverWait wait = new WebDriverWait (pilote, TimeSpan.FromSeconds (10)); var myDynamicElement = wait.Until (d => d.FindElement (By.Id ("someElement")));
Nous disons à Selenium que nous voulons qu’il attende cette page / cet élément particulier pendant 10 secondes maximum..
Il est probable que quelques pages prennent plus de temps que votre attente implicite par défaut et ce n'est pas une bonne pratique de codage de répéter ce code partout. Après tout Le code de test est un code. Vous pouvez plutôt extraire cela dans une méthode et l'utiliser à partir de vos tests:
public IWebElement FindElementWithWait (Par, int secondesToWait = 10) var wait = nouveau WebDriverWait (WebDriver, TimeSpan.FromSeconds (secondsToWait)); return wait.Until (d => d.FindElement (par));
Ensuite, vous pouvez utiliser cette méthode comme:
var slowPage = new SlowPage ("http: // somedomain / url_that_takes_a_long_time_to_load"); var element = slowPage.FindElementWithWait (By.Id ("someElement"));
Ceci est un exemple artificiel pour montrer à quoi la méthode pourrait ressembler et comment elle pourrait être utilisée. Idéalement, vous déplaceriez toutes les interactions de page vers vos objets de page..
Voyons un autre exemple d'attente explicite. Parfois, la page est entièrement chargée, mais l'élément n'y est pas encore car il est chargé ultérieurement à la suite d'une demande AJAX. Peut-être n’est-ce pas un élément que vous attendez mais que vous voulez juste attendre qu’une interaction AJAX se termine avant de pouvoir faire une affirmation, par exemple dans la base de données. Encore une fois, c’est là que la plupart des développeurs utilisent Thread.Sleep
pour vous assurer, par exemple, que l'appel AJAX est terminé et que l'enregistrement est maintenant dans la base de données avant de passer à la ligne suivante du test. Ceci peut être facilement corrigé en utilisant l'exécution de JavaScript!
La plupart des infrastructures d'automatisation de navigateur vous permettent d'exécuter JavaScript sur la session active, et Selenium ne fait pas exception. Dans Selenium, il existe une interface appelée IJavaScriptExecutor
avec deux méthodes:
////// Définit l'interface via laquelle l'utilisateur peut exécuter JavaScript. /// interface publique IJavaScriptExecutor ////// Exécute JavaScript dans le contexte du cadre ou de la fenêtre actuellement sélectionné. /// /// Le code JavaScript à exécuter. ////// La valeur renvoyée par le script. /// object ExecuteScript (script de chaîne, params object [] args); ////// Exécute JavaScript de manière asynchrone dans le contexte du cadre ou de la fenêtre actuellement sélectionné. /// /// Le code JavaScript à exécuter. ////// La valeur renvoyée par le script. /// object ExecuteAsyncScript (script de chaîne, params object [] args);
Cette interface est implémentée par RemoteWebDriver
qui est la classe de base pour toutes les implémentations de pilotes Web. Donc, sur votre instance de pilote Web, vous pouvez appeler ExecuteScript
exécuter un script JavaScript. Voici une méthode que vous pouvez utiliser pour attendre la fin de tous les appels AJAX (en supposant que vous utilisez jQuery):
// Ceci est supposé appartenir à une classe ayant accès à l'instance active 'WebDriver' via le champ / propriété 'WebDriver'. public void WaitForAjax (int secondsToWait = 10) var wait = nouveau WebDriverWait (WebDriver, TimeSpan.FromSeconds (secondsToWait)); wait.Until (d => (bool) ((IJavaScriptExecutor) d) .ExecuteScript ("return jQuery.active == 0"));
Combiner les ExecuteScript
avec WebDriverWait
et vous pouvez vous en débarrasser Thread.Sleep
ajouté pour les appels AJAX.
jQuery.active
renvoie le nombre d'appels AJAX actifs initié par jQuery; donc, quand il est zéro, aucun appel AJAX n'est en cours. Cette méthode ne fonctionne évidemment que si toutes les requêtes AJAX sont lancées par jQuery. Si vous utilisez d'autres bibliothèques JavaScript pour les communications AJAX, vous devez consulter les documentations de l'API pour connaître une méthode équivalente ou suivre vous-même les appels AJAX..
Avec l'attente explicite, vous pouvez définir une condition et attendre qu'elle soit remplie ou que le délai expire. Nous avons vu comment vérifier que les appels AJAX sont terminés - un autre exemple est la vérification de la visibilité d'un élément. Tout comme la vérification AJAX, vous pouvez écrire une condition qui vérifie la visibilité d'un élément. mais il existe une solution plus facile pour cela appelé Condition attendue
.
De la documentation Selenium:
Certaines conditions courantes sont fréquemment rencontrées lors de l'automatisation des navigateurs Web..
Si vous utilisez Java, vous avez de la chance car Condition attendue
La classe en Java est assez étendue et a beaucoup de méthodes pratiques. Vous pouvez trouver la documentation ici.
.Les développeurs Internet ne sont pas aussi chanceux. Il y a encore un Conditions attendues
cours en WebDriver.Support
assemblage (documenté ici) mais c'est très minime:
classe scellée publique ExpectedConditions ////// Une attente pour vérifier le titre d'une page. /// /// Le titre attendu, qui doit être une correspondance exacte. ////// Func statique publicquand le titre correspond; autrement, . /// TitleIs (titre de la chaîne); /// /// Une attente pour vérifier que le titre d'une page contient une sous-chaîne sensible à la casse. /// /// Le fragment de titre attendu. ////// Func statique publicquand le titre correspond; autrement, . /// TitleContains (titre de la chaîne); /// /// Une attente pour vérifier qu'un élément est présent sur le DOM d'une page ///. Cela ne signifie pas nécessairement que l'élément est visible. /// /// Le localisateur utilisé pour trouver l'élément. ////// Le Func statique publicune fois qu'il est situé. /// ElementExists (par locator); /// /// Une attente pour vérifier qu'un élément est présent sur le DOM d'une page /// et visible. Visibilité signifie que l'élément n'est pas seulement affiché mais /// a également une hauteur et une largeur supérieures à 0. /// /// Le localisateur utilisé pour trouver l'élément. ////// Le Func statique publicune fois qu'il est situé et visible. /// ElementIsVisible (Par locator);
Vous pouvez utiliser cette classe en combinaison avec WebDriverWait
:
var wait = new WebDriverWait (pilote, TimeSpan.FromSeconds (3)) var element = wait.Until (ExpectedConditions.ElementExists (By.Id ("foo")));
Comme vous pouvez le constater à partir de la signature de classe ci-dessus, vous pouvez vérifier le titre ou des parties de celui-ci, ainsi que l'existence et la visibilité des éléments à l'aide de Condition attendue
. La prise en charge prête à l'emploi dans .Net pourrait être très minime; mais cette classe n’est rien qu’une enveloppe autour de certaines conditions simples. Vous pouvez tout aussi facilement implémenter d'autres conditions courantes dans une classe et les utiliser avec WebDriverWait
à partir de vos scripts de test.
Un autre joyau réservé aux développeurs Java est FluentWait
. De la page de documentation, FluentWait
est
Une implémentation de l'interface Wait dont le délai d'attente et l'intervalle d'interrogation peuvent être configurés à la volée. Chaque instance de FluentWait définit le délai maximal d’attente d’une condition, ainsi que la fréquence de vérification de celle-ci. De plus, l'utilisateur peut configurer l'attente pour ignorer certains types d'exceptions pendant l'attente, tels que NoSuchElementExceptions lors de la recherche d'un élément sur la page..
Dans l'exemple suivant, nous essayons de trouver un élément avec id foo
sur la page interrogation toutes les cinq secondes pendant 30 secondes maximum:
// Attendre 30 secondes la présence d'un élément sur la page, en vérifiant // sa présence toutes les cinq secondes. Attendrewait = new FluentWait (pilote) .withTimeout (30, SECONDS) .pollingEvery (5, SECONDS) .ignoring (NoSuchElementException.class); WebElement foo = wait.until (nouvelle fonction () public WebElement apply (pilote WebDriver) return driver.findElement (By.id ("foo")); );
Il y a deux choses remarquables à propos de FluentWait
: premièrement, cela vous permet de spécifier l'intervalle d'interrogation qui pourrait améliorer les performances de votre test et deuxièmement, cela vous permet d'ignorer les exceptions qui ne vous intéressent pas.
FluentWait
est assez génial et ce serait cool si un équivalent existait aussi en .Net. Cela dit, il n’est pas si difficile de le mettre en oeuvre en utilisant WebDriverWait
.
Vous avez vos objets de page en place, vous avez un code de test maintenable, et vous évitez également des délais fixes dans vos tests; mais vos tests échouent toujours!
L'interface utilisateur est généralement la partie la plus fréquemment modifiée d'une application type: parfois, vous déplacez des éléments sur une page pour modifier la conception de la page et parfois, la structure de la page change en fonction des besoins. Ces modifications de la mise en page et de la conception des pages peuvent conduire à de nombreux tests erronés si vous ne choisissez pas vos sélecteurs judicieusement.
N'utilisez pas de sélecteurs flous et ne vous fiez pas à la structure de votre page..
On m'a souvent demandé s'il était correct d'ajouter un identifiant aux éléments de la page uniquement à des fins de test, et la réponse est un oui retentissant. Pour que notre unité de code soit testable, nous apportons de nombreuses modifications, comme l'ajout d'interfaces et l'utilisation de Dependency Injection. Le code de test est un code. Faites ce qu'il faut pour supporter vos tests.
Disons que nous avons une page avec la liste suivante:
Dans l'un de mes tests, je souhaite cliquer sur l'album "Let There Be Rock". Je demanderais des ennuis si j'utilisais le sélecteur suivant:
By.XPath ("// ul [@ id = 'liste-albums'] / li [3] / a")
Dans la mesure du possible, vous devez ajouter un identifiant aux éléments et les cibler directement, sans vous fier aux éléments environnants. Je vais donc apporter un petit changement à la liste:
J'ai ajouté identifiant
Attributs aux ancres basés sur l'identifiant unique des albums afin que nous puissions cibler un lien directement sans passer par ul
et li
éléments. Alors maintenant, je peux remplacer le sélecteur fragile par By.Id ("album-35")
qui est garanti pour fonctionner tant que cet album est sur la page, ce qui est d'ailleurs une bonne affirmation aussi. Pour créer ce sélecteur, je devrais évidemment avoir accès à l'identifiant de l'album à partir du code de test..
Il n’est cependant pas toujours possible d’ajouter des identifiants uniques aux éléments, comme des lignes dans une grille ou des éléments dans une liste. Dans de tels cas, vous pouvez utiliser des classes CSS et des attributs de données HTML pour associer des propriétés traçables à vos éléments afin de faciliter leur sélection. Par exemple, si vous avez deux listes d’albums dans votre page, l’une résultant de la recherche d’utilisateur et l’autre contenant les albums suggérés en fonction des achats précédents de l’utilisateur, vous pouvez les différencier à l’aide d’une classe CSS. ul
élément, même si cette classe n'est pas utilisée pour styler la liste:
Si vous préférez ne pas avoir de classes CSS inutilisées, vous pouvez utiliser les attributs de données HTML et modifier les listes en:
et:
L'une des principales raisons de l'échec des tests d'interface utilisateur est qu'un élément ou du texte ne se trouve pas sur la page. Cela se produit parfois lorsque vous atterrissez sur une mauvaise page en raison d'erreurs de navigation, de modifications de la navigation dans les pages de votre site Web ou d'erreurs de validation. D'autres fois, cela peut être dû à une page manquante ou à une erreur de serveur..
Quelle que soit la cause de l’erreur et si vous l’obtenez dans le journal de votre serveur CI ou dans votre console de test de bureau, une NoSuchElementException
(ou similaire) n’est pas très utile pour déterminer ce qui ne va pas, n’est-ce pas? Ainsi, lorsque votre test échoue, le seul moyen de remédier à l'erreur consiste à l'exécuter à nouveau et à l'observer pendant son échec. Quelques astuces pourraient vous éviter de réexécuter vos tests d'interface utilisateur lents pour le dépannage. Une solution consiste à capturer une capture d'écran chaque fois qu'un test échoue afin que nous puissions y revenir plus tard..
Il y a une interface dans Selenium appelée ITakesScreen
:
////// Définit l'interface utilisée pour prendre des captures d'écran de l'écran. /// interface publique ITakesScreenshot ////// Obtient un /// ///objet représentant l'image de la page à l'écran. /// /// UNE Capture d'écran GetScreenshot ();objet contenant l'image. ///
Cette interface est implémentée par les classes de pilotes Web et peut être utilisée comme ceci:
var screenshot = driver.GetScreenshot (); screenshot.SaveAsFile ("", ImageFormat.Png);
Ainsi, lorsqu'un test échoue parce que vous êtes sur une mauvaise page, vous pouvez le comprendre rapidement en consultant la capture d'écran capturée..
Même capturer des screenshots ne suffit pas toujours. Par exemple, vous pouvez voir l'élément attendu sur la page, mais le test échoue en indiquant qu'il ne l'a pas trouvé, peut-être en raison du mauvais sélecteur qui entraîne l'échec de la recherche d'élément. Ainsi, au lieu de (ou pour compléter) la capture d'écran, vous pouvez également capturer le code source HTML. Il y a un PageSource
propriété sur IWebDriver
interface (qui est implémentée par tous les pilotes Web):
////// Obtient la source de la dernière page chargée par le navigateur. /// ////// Si la page a été modifiée après le chargement (par exemple, avec JavaScript) ///, rien ne garantit que le texte renvoyé est celui de la page modifiée. /// Veuillez consulter la documentation du pilote utilisé /// pour déterminer si le texte renvoyé reflète l'état actuel de la page /// ou le dernier texte envoyé par le serveur Web. La source de page renvoyée est une représentation /// du DOM sous-jacent: n'attendez pas qu'elle soit formatée /// ou échappée de la même manière que la réponse envoyée par le serveur Web. /// string PageSource get;
Comme nous l'avons fait avec ITakesScreen
vous pouvez implémenter une méthode qui récupère le code source de la page et le conserve dans un fichier pour inspection ultérieure:
File.WriteAllText ("", driver.PageSource);
Vous ne voulez pas vraiment capturer les captures d'écran et les sources de toutes les pages que vous visitez et pour les tests; sinon, vous devrez en parcourir des milliers lorsque quelque chose se passe mal. Au lieu de cela, vous ne devriez les capturer que lorsqu'un test échoue ou lorsque vous avez besoin de plus d'informations pour le dépannage. Pour éviter de polluer le code avec trop de blocs try-catch et pour éviter les duplications de code, vous devez regrouper toutes vos recherches d'éléments et assertions dans une classe, les encapsuler avec try-catch, puis capturer la capture d'écran et / ou la source de la page dans le bloc catch. . Voici un peu de code que vous pourriez utiliser pour exécuter des actions sur un élément:
public void Execute (By by, Actionaction) try var element = WebDriver.FindElement (par); action (élément); catch var capturer = new Capturer (WebDriver); capturer.CaptureScreenshot (); capturer.CapturePageSource (); jeter;
le Capturer
La classe peut être implémentée comme:
classe publique Capturer chaîne statique publique OutputFolder = Path.Combine (AppDomain.CurrentDomain.BaseDirectory, "FailedTests"); lecture seule privée RemoteWebDriver _webDriver; Capturer public (webDriver RemoteWebDriver) _webDriver = webDriver; public void CaptureScreenshot (string fileName = null) var camera = (ITakesScreenshot) _webDriver; var screenshot = camera.GetScreenshot (); var screenShotPath = GetOutputFilePath (nom_fichier, "png"); screenshot.SaveAsFile (screenShotPath, ImageFormat.Png); public void CapturePageSource (string fileName = null) var filePath = GetOutputFilePath (nomFichier, "html"); File.WriteAllText (filePath, _webDriver.PageSource); chaîne privée GetOutputFilePath (string fileName, string fileExtension) if (! Directory.Exists (OutputFolder)) Directory.CreateDirectory (OutputFolder); var windowTitle = _webDriver.Title; NomFichier = NomFichier ?? string.Format ("0 1. 2", windowTitle, DateTime.Now.ToFileTime (), extension de fichier) .Replace (':', '.'); var outputPath = Path.Combine (OutputFolder, NomFichier); var pathChars = Path.GetInvalidPathChars (); var stringBuilder = new StringBuilder (outputPath); foreach (élément var dans pathChars) stringBuilder.Replace (item, '.'); var screenShotPath = stringBuilder.ToString (); retourner screenShotPath;
Cette implémentation conserve la capture d'écran et le source HTML dans un dossier appelé FailedTests à côté des tests, mais vous pouvez le modifier si vous souhaitez un comportement différent..
Bien que je n’ai montré que des méthodes spécifiques à Selenium, des API similaires existent dans tous les frameworks d’automatisation que je connais et peuvent être facilement utilisés.
Dans cet article, nous avons parlé de quelques trucs et astuces de test de l'interface utilisateur. Nous avons expliqué comment éviter une suite de tests d’interface utilisateur fragile et lente en évitant les retards dans les tests. Nous avons ensuite discuté de la façon d'éviter les sélecteurs et les tests fragiles en choisissant judicieusement les sélecteurs, ainsi que de déboguer vos tests d'interface utilisateur lorsqu'ils échouent..
La plupart du code présenté dans cet article se trouve dans l'exemple de référentiel MvcMusicStore que nous avons vu dans le dernier article. Il est également intéressant de noter que beaucoup de code dans MvcMusicStore a été emprunté à la base de code de Seleno. Si vous voulez voir beaucoup de trucs sympas, vous pouvez essayer de vérifier Seleno. Disclaimer: Je suis cofondateur de l'organisation TestStack et contributeur sur Seleno.
J'espère que ce dont nous avons discuté dans cet article vous aidera dans vos tentatives de test d'interface utilisateur..