web-dev-qa-db-fra.com

Selenium WebDriver: attendez le chargement d'une page complexe avec JavaScript

J'ai une application Web à tester avec Selenium. Il y a beaucoup de JavaScript en cours de chargement de la page.
Ce code JavaScript n’est pas si bien écrit, mais je ne peux rien changer . Donc, attendre qu’un élément apparaisse dans le DOM avec la méthode findElement() n’est pas une option.
Je veux créer une fonction générique en Java pour attendre le chargement d’une page, une solution possible serait: 

  • exécutez un script JavaScript sous la forme WebDriver et stockez le résultat de document.body.innerHTML dans une variable chaîne body.
  • comparez la variable body à la version précédente de body. si elles sont identiques, définir incrémenter un compteur notChangedCount sinon définir notChangedCount à zéro.
  • attendez un peu (50 ms par exemple).
  • si la page n'a pas changé depuis un certain temps (500 ms par exemple), alors notChangedCount >= 10 puis quittez la boucle sinon passez en boucle à la première étape.

Pensez-vous que c'est une solution valable?

84
psadac

Si quelqu'un connaissait réellement une réponse générale et toujours applicable, elle aurait été appliquée partout il y a très longtemps et faciliterait grandement nos vies SO.

Vous pouvez faire beaucoup de choses, mais chacune d’elles a un problème:

  1. Comme Ashwin Prabhu l'a dit, si vous connaissez bien le script, vous pouvez en observer le comportement et en suivre certaines variables sur window ou document etc. Cette solution ne convient toutefois pas à tout le monde et ne peut être utilisée que par vous et uniquement sur une base limitée. ensemble de pages.

  2. Votre solution en observant le code HTML et en indiquant si elle a été modifiée ou non pendant un certain temps n’est pas mauvaise (il existe également une une méthode pour obtenir le code HTML original et non édité directement par WebDriver), mais:

    • Il faut beaucoup de temps pour faire valoir une page et pourrait prolonger le test de manière significative.
    • Vous ne savez jamais quel est le bon intervalle. Le script pourrait télécharger quelque chose de grand qui prend plus de 500 ms. Il existe plusieurs scripts sur la page interne de notre société qui prennent plusieurs secondes dans IE. Il se peut que votre ordinateur manque temporairement de ressources - par exemple, un antivirus fera en sorte que votre processeur fonctionne pleinement. 500 ms est peut-être trop court, même pour des scripts non complexes.
    • Certains scripts ne sont jamais terminés. Ils s’appellent avec un certain retard (setTimeout()) et travaillent encore et encore et peuvent éventuellement changer le code HTML à chaque exécution. Sérieusement, chaque page "Web 2.0" le fait. Même débordement de pile. Vous pouvez écraser les méthodes les plus couramment utilisées et considérer que les scripts qui les utilisent sont terminés, mais ... vous ne pouvez pas en être sûr.
    • Et si le script faisait autre chose que de changer le HTML? Il pourrait faire des milliers de choses, pas seulement du plaisir innerHTML.
  3. Il existe des outils pour vous aider à ce sujet. À savoir Progress Listeners avec nsIWebProgressListener et quelques autres. Le support du navigateur pour cela, cependant, est horrible. Firefox a commencé à essayer de le prendre en charge à partir de FF4 (en évolution constante), IE dispose d’un support de base dans IE9.

Et je suppose que je pourrais bientôt proposer une autre solution défectueuse. Le fait est - il n'y a pas de réponse précise sur le moment de dire "maintenant la page est complète" à cause des scripts éternels faisant leur travail. Choisissez celui qui vous sert le mieux, mais méfiez-vous de ses inconvénients.

61
Petr Janeček

Merci Ashwin!

Dans mon cas, je devrais avoir besoin d'attendre l'exécution d'un plugin jquery dans un élément .. spécifiquement "qtip" 

basé sur votre indice, cela a parfaitement fonctionné pour moi:

wait.until( new Predicate<WebDriver>() {
            public boolean apply(WebDriver driver) {
                return ((JavascriptExecutor)driver).executeScript("return document.readyState").equals("complete");
            }
        }
    );

Note: J'utilise Webdriver 2

27
Eduardo Fabricio

Vous devez attendre que Javascript et jQuery aient fini de charger . Exécutez Javascript pour vérifier si jQuery.active est 0 et document.readyState est complete, ce qui signifie que la charge de JS et jQuery est terminée.

public boolean waitForJStoLoad() {

    WebDriverWait wait = new WebDriverWait(driver, 30);

    // wait for jQuery to load
    ExpectedCondition<Boolean> jQueryLoad = new ExpectedCondition<Boolean>() {
      @Override
      public Boolean apply(WebDriver driver) {
        try {
          return ((Long)executeJavaScript("return jQuery.active") == 0);
        }
        catch (Exception e) {
          return true;
        }
      }
    };

    // wait for Javascript to load
    ExpectedCondition<Boolean> jsLoad = new ExpectedCondition<Boolean>() {
      @Override
      public Boolean apply(WebDriver driver) {
        return executeJavaScript("return document.readyState")
            .toString().equals("complete");
      }
    };

  return wait.until(jQueryLoad) && wait.until(jsLoad);
}
22
LINGS

La bibliothèque JS définit-elle/initialise-t-elle une variable connue de la fenêtre?

Si tel est le cas, vous pouvez attendre que la variable apparaisse. Vous pouvez utiliser

((JavascriptExecutor)driver).executeScript(String script, Object... args)

pour tester cette condition (quelque chose comme: window.SomeClass && window.SomeClass.variable != null) et renvoyer un booléen true/false.

Emballez ceci dans un WebDriverWait , et attendez que le script retourne true.

8
Ashwin Prabhu

Si tout ce que vous avez à faire est d’attendre que le code HTML de la page devienne stable avant d’essayer d’interagir avec des éléments, vous pouvez interroger le DOM périodiquement et comparer les résultats. d'or. Quelque chose comme ceci, où vous passez le temps d'attente maximum et le temps entre les interrogations de page avant la comparaison. Simple et efficace.

public void waitForJavascript(int maxWaitMillis, int pollDelimiter) {
    double startTime = System.currentTimeMillis();
    while (System.currentTimeMillis() < startTime + maxWaitMillis) {
        String prevState = webDriver.getPageSource();
        Thread.sleep(pollDelimiter); // <-- would need to wrap in a try catch
        if (prevState.equals(webDriver.getPageSource())) {
            return;
        }
    }
}
4
TheFreddyKilo

Le code ci-dessous fonctionne parfaitement dans mon cas - ma page contient des scripts Java complexes

public void checkPageIsReady() {

  JavascriptExecutor js = (JavascriptExecutor)driver;


  //Initially bellow given if condition will check ready state of page.
  if (js.executeScript("return document.readyState").toString().equals("complete")){ 
   System.out.println("Page Is loaded.");
   return; 
  } 

  //This loop will rotate for 25 times to check If page Is ready after every 1 second.
  //You can replace your value with 25 If you wants to Increase or decrease wait time.
  for (int i=0; i<25; i++){ 
   try {
    Thread.sleep(1000);
    }catch (InterruptedException e) {} 
   //To check page ready state.
   if (js.executeScript("return document.readyState").toString().equals("complete")){ 
    break; 
   }   
  }
 }

Source - Comment attendre que la page soit chargée/prête dans Selenium WebDriver

3
user790049

J'ai eu le même problème. Cette solution fonctionne pour moi de WebDriverDoku:

WebDriverWait wait = new WebDriverWait(driver, 10);
WebElement element = wait.until(ExpectedConditions.elementToBeClickable(By.id("someid")));

http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp

2
Roma Kap

Pour la bibliothèque nodejs Selenium, j'ai utilisé l'extrait de code suivant. Dans mon cas, je recherchais deux objets ajoutés à la fenêtre, qui sont dans cet exemple <SOME PROPERTY>, 10000 est le délai d'expiration millisecondes, <NEXT STEP HERE> est ce qui se produit une fois que les propriétés sont trouvées dans la fenêtre.

driver.wait( driver => {
    return driver.executeScript( 'if(window.hasOwnProperty(<SOME PROPERTY>) && window.hasOwnProperty(<SOME PROPERTY>)) return true;' ); }, 10000).then( ()=>{
        <NEXT STEP HERE>
}).catch(err => { 
    console.log("looking for window properties", err);
});
1
Jason Lydon

J'ai demandé à mes développeurs de créer une variable JavaScript "isProcessing" à laquelle je peux accéder (dans l'objet "ae") qu'ils ont définie quand les choses commencent à s'exécuter et qui est claire quand les choses sont faites. Je le lance ensuite dans un accumulateur qui le vérifie toutes les 100 ms jusqu'à ce qu'il en obtienne cinq de suite pour un total de 500 ms sans aucune modification. Si 30 secondes s'écoulent, je jette une exception car quelque chose aurait dû arriver d'ici là. Ceci est en C #.

public static void WaitForDocumentReady(this IWebDriver driver)
{
    Console.WriteLine("Waiting for five instances of document.readyState returning 'complete' at 100ms intervals.");
    IJavaScriptExecutor jse = (IJavaScriptExecutor)driver;
    int i = 0; // Count of (document.readyState === complete) && (ae.isProcessing === false)
    int j = 0; // Count of iterations in the while() loop.
    int k = 0; // Count of times i was reset to 0.
    bool readyState = false;
    while (i < 5)
    {
        System.Threading.Thread.Sleep(100);
        readyState = (bool)jse.ExecuteScript("return ((document.readyState === 'complete') && (ae.isProcessing === false))");
        if (readyState) { i++; }
        else
        {
            i = 0;
            k++;
        }
        j++;
        if (j > 300) { throw new TimeoutException("Timeout waiting for document.readyState to be complete."); }
    }
    j *= 100;
    Console.WriteLine("Waited " + j.ToString() + " milliseconds. There were " + k + " resets.");
}
1
Roger Cook

Voici mon propre code:
Window.setTimeout ne s’exécute que lorsque le navigateur est inactif. 
Si vous appelez la fonction récursivement (42 fois), cela prendra 100 ms s'il n'y a pas d'activité dans le navigateur et beaucoup plus si le navigateur est occupé à faire autre chose.

    ExpectedCondition<Boolean> javascriptDone = new ExpectedCondition<Boolean>() {
        public Boolean apply(WebDriver d) {
            try{//window.setTimeout executes only when browser is idle,
                //introduces needed wait time when javascript is running in browser
                return  ((Boolean) ((JavascriptExecutor) d).executeAsyncScript( 

                        " var callback =arguments[arguments.length - 1]; " +
                        " var count=42; " +
                        " setTimeout( collect, 0);" +
                        " function collect() { " +
                            " if(count-->0) { "+
                                " setTimeout( collect, 0); " +
                            " } "+
                            " else {callback(" +
                            "    true" +                            
                            " );}"+                             
                        " } "
                    ));
            }catch (Exception e) {
                return Boolean.FALSE;
            }
        }
    };
    WebDriverWait w = new WebDriverWait(driver,timeOut);  
    w.until(javascriptDone);
    w=null;

En bonus, le compteur peut être réinitialisé sur document.readyState ou sur les appels jQuery Ajax ou si des animations jQuery sont en cours d'exécution (uniquement si votre application utilise jQuery pour les appels ajax ...) 
...

" function collect() { " +
                            " if(!((typeof jQuery === 'undefined') || ((jQuery.active === 0) && ($(\":animated\").length === 0))) && (document.readyState === 'complete')){" +
                            "    count=42;" +
                            "    setTimeout( collect, 0); " +
                            " }" +
                            " else if(count-->0) { "+
                                " setTimeout( collect, 0); " +
                            " } "+ 

...

EDIT: Je remarque que executeAsyncScript ne fonctionne pas bien si une nouvelle page est chargée et que le test peut cesser de répondre indéfiniment. Il est préférable de l’utiliser à la place.

public static ExpectedCondition<Boolean> documentNotActive(final int counter){ 
    return new ExpectedCondition<Boolean>() {
        boolean resetCount=true;
        @Override
        public Boolean apply(WebDriver d) {

            if(resetCount){
                ((JavascriptExecutor) d).executeScript(
                        "   window.mssCount="+counter+";\r\n" + 
                        "   window.mssJSDelay=function mssJSDelay(){\r\n" + 
                        "       if((typeof jQuery != 'undefined') && (jQuery.active !== 0 || $(\":animated\").length !== 0))\r\n" + 
                        "           window.mssCount="+counter+";\r\n" + 
                        "       window.mssCount-->0 &&\r\n" + 
                        "       setTimeout(window.mssJSDelay,window.mssCount+1);\r\n" + 
                        "   }\r\n" + 
                        "   window.mssJSDelay();");
                resetCount=false;
            }

            boolean ready=false;
            try{
                ready=-1==((Long) ((JavascriptExecutor) d).executeScript(
                        "if(typeof window.mssJSDelay!=\"function\"){\r\n" + 
                        "   window.mssCount="+counter+";\r\n" + 
                        "   window.mssJSDelay=function mssJSDelay(){\r\n" + 
                        "       if((typeof jQuery != 'undefined') && (jQuery.active !== 0 || $(\":animated\").length !== 0))\r\n" + 
                        "           window.mssCount="+counter+";\r\n" + 
                        "       window.mssCount-->0 &&\r\n" + 
                        "       setTimeout(window.mssJSDelay,window.mssCount+1);\r\n" + 
                        "   }\r\n" + 
                        "   window.mssJSDelay();\r\n" + 
                        "}\r\n" + 
                        "return window.mssCount;"));
            }
            catch (NoSuchWindowException a){
                a.printStackTrace();
                return true;
            }
            catch (Exception e) {
                e.printStackTrace();
                return false;
            }
            return ready;
        }
        @Override
        public String toString() {
            return String.format("Timeout waiting for documentNotActive script");
        }
    };
}
0
Mariusz

Deux conditions peuvent être utilisées pour vérifier si la page est chargée avant de rechercher un élément sur la page:

WebDriverWait wait = new WebDriverWait(driver, 50);

Utiliser ci-dessous redayState attendra le chargement de la page

wait.until((ExpectedCondition<Boolean>) wd ->
   ((JavascriptExecutor) wd).executeScript("return document.readyState").equals("complete"));

En dessous, JQuery attendra que les données n'aient pas été chargées. 

  int count =0;
            if((Boolean) executor.executeScript("return window.jQuery != undefined")){
                while(!(Boolean) executor.executeScript("return jQuery.active == 0")){
                    Thread.sleep(4000);
                    if(count>4)
                        break;
                    count++;
                }
            }

Après ces JavaScriptCode, essayez de trouverOut webElement.

WebElement we = wait.until(ExpectedConditions.presenceOfElementLocated(by));
0
Ankit Gupta

Vous pouvez écrire une logique pour gérer cela. J'ai écrit une méthode qui retournera le WebElement et cette méthode sera appelée trois fois ou vous pouvez augmenter le temps et ajouter un contrôle nul pour WebElement

public static void main(String[] args) {
        WebDriver driver = new FirefoxDriver();
        driver.get("https://www.crowdanalytix.com/#home");
        WebElement webElement = getWebElement(driver, "homekkkkkkkkkkkk");
        int i = 1;
        while (webElement == null && i < 4) {
            webElement = getWebElement(driver, "homessssssssssss");
            System.out.println("calling");
            i++;
        }
        System.out.println(webElement.getTagName());
        System.out.println("End");
        driver.close();
    }

    public static WebElement getWebElement(WebDriver driver, String id) {
        WebElement myDynamicElement = null;
        try {
            myDynamicElement = (new WebDriverWait(driver, 10))
                    .until(ExpectedConditions.presenceOfElementLocated(By
                            .id(id)));
            return myDynamicElement;
        } catch (TimeoutException ex) {
            return null;
        }
    }
0
Vaibhav Pandey

Pour le faire correctement, vous devez gérer les exceptions.

Voici comment je fais une attente pour un iFrame. Cela nécessite que votre classe de test JUnit transmette l'instance de RemoteWebDriver à l'objet page:

public class IFrame1 extends LoadableComponent<IFrame1> {

    private RemoteWebDriver driver;

    @FindBy(id = "iFrame1TextFieldTestInputControlID" )
    public WebElement iFrame1TextFieldInput;

    @FindBy(id = "iFrame1TextFieldTestProcessButtonID" )
    public WebElement copyButton;

    public IFrame1( RemoteWebDriver drv ) {
        super();
        this.driver = drv;
        this.driver.switchTo().defaultContent();
        waitTimer(1, 1000);
        this.driver.switchTo().frame("BodyFrame1");
        LOGGER.info("IFrame1 constructor...");
    }

    @Override
    protected void isLoaded() throws Error {        
        LOGGER.info("IFrame1.isLoaded()...");
        PageFactory.initElements( driver, this );
        try {
            assertTrue( "Page visible title is not yet available.", driver
     .findElementByCssSelector("body form#webDriverUnitiFrame1TestFormID h1")
                    .getText().equals("iFrame1 Test") );
        } catch ( NoSuchElementException e) {
            LOGGER.info("No such element." );
            assertTrue("No such element.", false);
        }
    }

    @Override
    protected void load() {
        LOGGER.info("IFrame1.load()...");
        Wait<WebDriver> wait = new FluentWait<WebDriver>( driver )
                .withTimeout(30, TimeUnit.SECONDS)
                .pollingEvery(5, TimeUnit.SECONDS)
                .ignoring( NoSuchElementException.class ) 
                .ignoring( StaleElementReferenceException.class ) ;
            wait.until( ExpectedConditions.presenceOfElementLocated( 
            By.cssSelector("body form#webDriverUnitiFrame1TestFormID h1") ) );
    }
....

NOTE: Vous pouvez voir tout mon exemple de travail ici .

0
djangofan

J'aime votre idée d'interroger le code HTML jusqu'à ce qu'il soit stable. Je peux ajouter cela à ma propre solution. L'approche suivante est en C # et nécessite jQuery.

Je suis le développeur d'un projet de test SuccessFactors (SaaS) dans lequel nous n'avons aucune influence sur les développeurs ou les caractéristiques du DOM derrière la page Web. Le produit SaaS peut potentiellement changer sa conception sous-jacente du DOM 4 fois par an. La recherche de méthodes de test robustes et performantes avec Selenium est donc toujours active (y compris les tests NOT avec Selenium lorsque cela est possible!)

Voici ce que j'utilise pour "page prête". Cela fonctionne dans tous mes propres tests actuellement. La même approche a également fonctionné pour une grande application web Java interne il y a quelques années et était robuste depuis plus d'un an au moment où j'ai quitté le projet.

  • Driver est l'instance WebDriver qui communique avec le navigateur.
  • DefaultPageLoadTimeout est une valeur de délai d'attente en ticks (100ns par tick)

public IWebDriver Driver { get; private set; }

// ...

const int GlobalPageLoadTimeOutSecs = 10;
static readonly TimeSpan DefaultPageLoadTimeout =
    new TimeSpan((long) (10_000_000 * GlobalPageLoadTimeOutSecs));
Driver = new FirefoxDriver();

Dans ce qui suit, notez l'ordre des attentes dans la méthode PageReady (document Selenium ready, Ajax, animations), ce qui est logique si vous y réfléchissez:

  1. charger la page contenant le code
  2. utiliser le code pour charger les données quelque part via Ajax
  3. présenter les données, éventuellement avec des animations

Quelque chose comme votre approche de comparaison DOM pourrait être utilisé entre 1 et 2 pour ajouter une autre couche de robustesse.


public void PageReady()
{
    DocumentReady();
    AjaxReady();
    AnimationsReady();
}


private void DocumentReady()
{
    WaitForJavascript(script: "return document.readyState", result: "complete");
}

private void WaitForJavascript(string script, string result)
{
    new WebDriverWait(Driver, DefaultPageLoadTimeout).Until(
        d => ((IJavaScriptExecutor) d).ExecuteScript(script).Equals(result));
}

private void AjaxReady()
{
    WaitForJavascript(script: "return jQuery.active.toString()", result: "0");
}

private void AnimationsReady()
{
    WaitForJavascript(script: "return $(\"animated\").length.toString()", result: "0");
}
0
Nick