web-dev-qa-db-fra.com

Tester AngularJS avec du sélénium

J'ai une application SPA sur la pile ASP MVC + AngularJS et j'aimerais tester l'interface utilisateur . Pour l'instant, je teste Selenium avec les pilotes PhantomJS et WebKit.

Ceci est un exemple de page de test - vue avec un seul élément. Les éléments de la liste <li> se chargent dynamiquement à partir du serveur et sont délimités par Angular.

<div id="items">
    <li>text</li>
    <li>text2</li>
</div>

J'essaie de passer un test et il y a une erreur dans cette ligne:

_driver.FindElements(By.TagName('li'))

À ce stade, il n'y a pas d'éléments chargés et _driver.PageSource ne contient pas d'éléments.

Comment puis-je attendre que les articles soient chargés? Merci de ne pas suggérer Thread.Sleep()

25
deeptowncitizen

Cela attendra les appels de pages/jquery.ajax (si présents) et $ http, ainsi que tout cycle de résumé/rendu, le jettera dans une fonction utilitaire et attendra.

/* C# Example
 var pageLoadWait = new WebDriverWait(WebDriver, TimeSpan.FromSeconds(timeout));
            pageLoadWait.Until<bool>(
                (driver) =>
                {
                    return (bool)JS.ExecuteScript(
@"*/
try {
  if (document.readyState !== 'complete') {
    return false; // Page not loaded yet
  }
  if (window.jQuery) {
    if (window.jQuery.active) {
      return false;
    } else if (window.jQuery.ajax && window.jQuery.ajax.active) {
      return false;
    }
  }
  if (window.angular) {
    if (!window.qa) {
      // Used to track the render cycle finish after loading is complete
      window.qa = {
        doneRendering: false
      };
    }
    // Get the angular injector for this app (change element if necessary)
    var injector = window.angular.element('body').injector();
    // Store providers to use for these checks
    var $rootScope = injector.get('$rootScope');
    var $http = injector.get('$http');
    var $timeout = injector.get('$timeout');
    // Check if digest
    if ($rootScope.$$phase === '$apply' || $rootScope.$$phase === '$digest' || $http.pendingRequests.length !== 0) {
      window.qa.doneRendering = false;
      return false; // Angular digesting or loading data
    }
    if (!window.qa.doneRendering) {
      // Set timeout to mark angular rendering as finished
      $timeout(function() {
        window.qa.doneRendering = true;
      }, 0);
      return false;
    }
  }
  return true;
} catch (ex) {
  return false;
}
/*");
});*/
38
npjohns

Créez une nouvelle classe vous permettant de déterminer si votre site Web utilisant AngularJS a fini de passer des appels AJAX, comme suit:

import org.openqa.Selenium.JavascriptExecutor;
import org.openqa.Selenium.WebDriver;
import org.openqa.Selenium.support.ui.ExpectedCondition;

public class AdditionalConditions {
    public static ExpectedCondition<Boolean> angularHasFinishedProcessing() {
        return new ExpectedCondition<Boolean>() {
            @Override
            public Boolean apply(WebDriver driver) {
                return Boolean.valueOf(((JavascriptExecutor) driver).executeScript("return (window.angular !== undefined) && (angular.element(document).injector() !== undefined) && (angular.element(document).injector().get('$http').pendingRequests.length === 0)").toString());
            }
        };
    }
}

Vous pouvez l'utiliser n'importe où dans le code en utilisant le code suivant:

WebDriverWait wait = new WebDriverWait(getDriver(), 15, 100);
wait.until(AdditionalConditions.angularHasFinishedProcessing()));
26
Shahzaib Salim

Nous avons eu un problème similaire où notre framework interne est utilisé pour tester plusieurs sites, certains utilisent JQuery et d’autres AngularJS (et 1 a même un mélange!). Notre framework est écrit en C #, il était donc important que tout JScript exécuté soit exécuté en un minimum de morceaux (à des fins de débogage). En fait, il a fallu beaucoup de réponses ci-dessus et les a écrasées ensemble (donc crédit pour lequel le crédit est dû à @npjohns). Ci-dessous une explication de ce que nous avons fait:

Ce qui suit retourne un vrai/faux si le DOM HTML a été chargé:

        public bool DomHasLoaded(IJavaScriptExecutor jsExecutor, int timeout = 5)
    {

        var hasThePageLoaded = jsExecutor.ExecuteScript("return document.readyState");
        while (hasThePageLoaded == null || ((string)hasThePageLoaded != "complete" && timeout > 0))
        {
            Thread.Sleep(100);
            timeout--;
            hasThePageLoaded = jsExecutor.ExecuteScript("return document.readyState");
            if (timeout != 0) continue;
            Console.WriteLine("The page has not loaded successfully in the time provided.");
            return false;
        }
        return true;
    }

Ensuite, nous vérifions si JQuery est utilisé:

public bool IsJqueryBeingUsed(IJavaScriptExecutor jsExecutor)
    {
        var isTheSiteUsingJQuery = jsExecutor.ExecuteScript("return window.jQuery != undefined");
        return (bool)isTheSiteUsingJQuery;
    }

Si JQuery est utilisé, nous vérifions qu'il est chargé:

public bool JqueryHasLoaded(IJavaScriptExecutor jsExecutor, int timeout = 5)
        {
                var hasTheJQueryLoaded = jsExecutor.ExecuteScript("jQuery.active === 0");
                while (hasTheJQueryLoaded == null || (!(bool) hasTheJQueryLoaded && timeout > 0))
                {
                    Thread.Sleep(100);
                timeout--;
                    hasTheJQueryLoaded = jsExecutor.ExecuteScript("jQuery.active === 0");
                    if (timeout != 0) continue;
                    Console.WriteLine(
                        "JQuery is being used by the site but has failed to successfully load.");
                    return false;
                }
                return (bool) hasTheJQueryLoaded;
        }

Nous faisons ensuite la même chose pour AngularJS:

    public bool AngularIsBeingUsed(IJavaScriptExecutor jsExecutor)
    {
        string UsingAngular = @"if (window.angular){
        return true;
        }";            
        var isTheSiteUsingAngular = jsExecutor.ExecuteScript(UsingAngular);
        return (bool) isTheSiteUsingAngular;
    }

S'il est utilisé, nous vérifions qu'il a été chargé:

public bool AngularHasLoaded(IJavaScriptExecutor jsExecutor, int timeout = 5)
        {
    string HasAngularLoaded =
        @"return (window.angular !== undefined) && (angular.element(document.body).injector() !== undefined) && (angular.element(document.body).injector().get('$http').pendingRequests.length === 0)";            
    var hasTheAngularLoaded = jsExecutor.ExecuteScript(HasAngularLoaded);
                while (hasTheAngularLoaded == null || (!(bool)hasTheAngularLoaded && timeout > 0))
                {
                    Thread.Sleep(100);
                    timeout--;
                    hasTheAngularLoaded = jsExecutor.ExecuteScript(HasAngularLoaded);
                    if (timeout != 0) continue;
                    Console.WriteLine(
                        "Angular is being used by the site but has failed to successfully load.");
                    return false;

                }
                return (bool)hasTheAngularLoaded;
        }

Après avoir vérifié que le DOM a été chargé avec succès, vous pouvez utiliser ces valeurs bool pour effectuer des attentes personnalisées:

    var jquery = !IsJqueryBeingUsed(javascript) || wait.Until(x => JQueryHasLoaded(javascript));
    var angular = !AngularIsBeingUsed(javascript) || wait.Until(x => AngularHasLoaded(javascript));
6
D Sayer

Si vous utilisez AngularJS, utiliser Protractor est une bonne idée.

Si vous utilisez rapporteur, vous pouvez utiliser la méthode waitForAngular () qui attendra que les requêtes http soient terminées. Il est toujours bon d'attendre l'affichage des éléments avant de les utiliser. En fonction de votre langue et de votre implémentation, cela peut apparaître dans un langage synchrone.

WebDriverWait wait = new WebDriverWait(webDriver, timeoutInSeconds);
wait.until(ExpectedConditions.visibilityOfElementLocated(By.id<locator>));

Ou, dans JS, vous pouvez utiliser la méthode wait qui exécute une fonction jusqu'à ce qu'elle retourne à true

browser.wait(function () {
    return browser.driver.isElementPresent(elementToFind);
});
3
eddiec

Vous pouvez vous contenter de mon rapporteur pour obtenir des extraits de code utiles. Cette fonction est bloquée jusqu'à ce que Angular ait fini de rendre la page. C'est une variante de la réponse de Shahzaib Salim, sauf qu'il l'interroge et que je passe un rappel.

def wait_for_angular(self, Selenium):
    self.Selenium.set_script_timeout(10)
    self.Selenium.execute_async_script("""
        callback = arguments[arguments.length - 1];
        angular.element('html').injector().get('$browser').notifyWhenNoOutstandingRequests(callback);""")

Remplacez 'html' par n'importe quel élément est votre ng-app.

Il provient de https://github.com/angular/protractor/blob/71532f055c720b533fbf9dab2b3100b657966da6/lib/clientsidescripts.js#L51

3
user7610

J'ai fait le code suivant et cela m'a aidé pour les échecs asynchrones.

$window._docReady = function () {
        var phase = $scope.$root.$$phase;
        return $http.pendingRequests.length === 0 && phase !== '$apply' && phase !== '$digest';
    }

Maintenant, dans le modèle Selenium PageObject, vous pouvez attendre 

Object result = ((RemoteWebDriver) driver).executeScript("return _docReady();");
                    return result == null ? false : (Boolean) result;
3
Sanjay Bharwani

Si votre application Web est bien créée avec Angular, comme vous le dites, le meilleur moyen de faire des tests de bout en bout est avec Protractor .

En interne, Protractor utilise sa propre méthode waitForAngular pour s'assurer que Protractor attend automatiquement jusqu'à ce que Angular ait fini de modifier le DOM.

Ainsi, dans le cas normal, vous auriez jamais écrit une variable explicite wait dans vos cas de test: Protractor le fait pour vous.

Vous pouvez consulter le didacticiel Phonecat Angular pour savoir comment configurer Protractor.

Si vous voulez utiliser sérieusement Protractor, vous voudrez adopter pageobjects . Si vous voulez un exemple, jetez un œil à ma suite de tests d’objets page pour le phonographe angulaire.

Avec Protractor, vous écrivez vos tests en Javascript (Protractor est bien basé sur Node), et non en C # - mais en retour, Protractor gère tout ce qui vous attend.

2
avandeursen

Pour mon problème particulier avec la page HTML contenant les iframes et développée avec AnglularJS, l'astuce suivante m'a permis de gagner beaucoup de temps: Dans le DOM, j'ai clairement vu qu'il existe un iframe qui englobe tout le contenu . Donc le code suivant censé fonctionner:

driver.switchTo().frame(0);
waitUntilVisibleByXPath("//h2[contains(text(), 'Creative chooser')]");

Mais cela ne fonctionnait pas et m'a dit quelque chose du genre "Je ne peux pas basculer sur le cadre. La fenêtre a été fermée" . Ensuite, j'ai modifié le code pour:

driver.switchTo().defaultContent();
driver.switchTo().frame(0);
waitUntilVisibleByXPath("//h2[contains(text(), 'Creative chooser')]");

Après cela, tout s'est bien déroulé… .. Il était donc évident qu'Angular manipulait quelque chose avec des iframes et juste après le chargement de la page, alors que vous vous attendiez à ce que le pilote se concentre sur le contenu par défaut, il était ciblé par certains déjà supprimés par Angular frame aidez certains d'entre vous.

0

Si vous ne souhaitez pas basculer complètement vers Protractor mais que vous souhaitez attendre Angular, je vous recommande d'utiliser Paul Hammants ngWebDriver (Java). C'est basé sur le rapporteur mais vous n'avez pas à changer.

J'ai résolu le problème en écrivant une classe d'actions dans laquelle j'attendais Angular (à l'aide de waitForAngularRequestsToFinish ()) de ngWebDriver avant d'effectuer les actions (cliquer, remplir, vérifier, etc.).

Pour un extrait de code, voir ma réponse à cette question

0
Marlies

J'ai implémenté une utilisation basée sur répondre .__ de D Sayar et qui pourrait être utile à quelqu'un. Il vous suffit de copier toutes les fonctions booléennes mentionnées dans une seule classe, puis d'ajouter la méthode PageCallingUtility () ci-dessous. Cette méthode appelle une dépendance interne. 

Dans votre utilisation normale, vous devez appeler directement la méthode PageCallingUtility (). 

public void PageCallingUtility()
{
    if (DomHasLoaded() == true)
    {
        if (IsJqueryBeingUsed() == true)
        {
            JqueryHasLoaded();
        }

        if (AngularIsBeingUsed() == true)
        {
            AngularHasLoaded();
        }
    }
}
0
Ishita Shah