web-dev-qa-db-de.com

Selenium WebDriver: Warten Sie, bis eine komplexe Seite mit JavaScript geladen ist

Ich habe eine Webanwendung zum Testen mit Selenium. Beim Laden der Seite wird viel JavaScript ausgeführt.
Dieser JavaScript-Code ist nicht so gut geschrieben, aber ich kann nichts ändern. Das Warten auf ein Element im DOM mit der findElement()-Methode ist also keine Option.
Ich möchte eine generische Funktion in Java erstellen, um auf das Laden einer Seite zu warten. Eine mögliche Lösung wäre: 

  • führen Sie ein JavaScript-Skript von WebDriver aus und speichern Sie das Ergebnis von document.body.innerHTML in einer Zeichenfolgenvariablen body.
  • vergleichen Sie die Variable body mit der vorherigen Version von body. Wenn sie gleich sind, wird der Zähler notChangedCount erhöht, andernfalls notChangedCount auf Null gesetzt.
  • warten Sie eine Weile (z. B. 50 ms).
  • wenn sich die Seite für einige Zeit nicht geändert hat (z. B. 500 ms), beenden Sie die Schleife ansonsten mit notChangedCount >= 10 zum ersten Schritt.

Denken Sie, dass es eine gültige Lösung ist?

84
psadac

Wenn irgendjemand eine allgemeine und immer zutreffende Antwort gewusst hätte, wäre sie schon vor Jahrhunderten überallimplementiert worden und würde unser Leben SO viel einfacher machen.

Es gibt viele Dinge, die Sie tun können, aber jeder einzelne von ihnen hat ein Problem:

  1. Wie Ashwin Prabhu sagte, wenn Sie das Skript gut kennen, können Sie sein Verhalten beobachten und einige seiner Variablen auf window oder document usw. verfolgen. Diese Lösung ist jedoch nicht für jeden geeignet und kann verwendet werden Nur von Ihnen und nur auf einer begrenzten Anzahl von Seiten.

  2. Ihre Lösung, indem Sie den HTML-Code beobachten und prüfen, ob er seit einiger Zeit geändert wurde oder nicht, ist nicht schlecht (es gibt auch eine Methode , um den ursprünglichen und nicht bearbeiteten HTML-Code direkt von WebDriver), aber:

    • Es dauert sehr lange, eine Seite zu behaupten, und dies kann den Test erheblich verlängern.
    • Sie wissen nie, was das richtige Intervall ist. Das Skript lädt möglicherweiseetwas Großes herunter, das mehr als 500 ms dauert. Es gibt mehrere Skripte auf unserer firmeninternen Seite, die im IE einige Sekunden dauern. Möglicherweise sind auf Ihrem Computer vorübergehend nur wenige Ressourcen verfügbar. Angenommen, Ihre CPU wird durch ein Antivirenprogramm voll funktionsfähig. Dann sind 500 ms möglicherweise zu kurz, selbst für nicht komplexe Skripts.
    • Einige Skripte werden nie ausgeführt. Sie rufen sich mit einiger Verzögerung auf (setTimeout()) und arbeiten immer wieder und können möglicherweise den HTML-Code jedes Mal ändern, wenn sie ausgeführt werden. Im Ernst, jede "Web 2.0" Seite macht es. Gleichmäßiger Stapelüberlauf. Sie könnten die am häufigsten verwendeten Methoden überschreiben und die Skripte, in denen sie verwendet werden, als abgeschlossen betrachten, aber ... Sie können nicht sicher sein.
    • Was ist, wenn das Skript etwas anderes tut als das HTML zu ändern? Es könnte Tausende von Dingen tun, nicht nur etwas innerHTML Spaß.
  3. Es gibt Tools, die Ihnen dabei helfen. Nämlich Progress Listener zusammen mit nsIWebProgressListener und einigen anderen. Die Browserunterstützung dafür ist jedoch schrecklich. Firefox versuchte, es ab FF4 zu unterstützen (noch in der Entwicklung). IE verfügt über grundlegende Unterstützung in IE9.

Und ich denke, ich könnte bald eine andere fehlerhafte Lösung finden. Tatsache ist - es gibt keine definitive Antwort auf die Frage, wann "Jetzt ist die Seite fertig" zu sagen ist, da die ewigen Skripte ihre Arbeit erledigen. Wählen Sie diejenige, die Ihnen am besten dient, aber achten Sie auf ihre Mängel.

63
Petr Janeček

Danke Ashwin!

In meinem Fall sollte ich auf ein Jquery-Plugin in einem Element warten. 

ihrem Tipp entsprechend funktionierte es perfekt für mich:

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

Hinweis: Ich verwende Webdriver 2

27

Sie müssen warten, bis Javascript und jQuery vollständig geladen sind . Führen Sie Javascript aus, um zu prüfen, ob jQuery.active0 und document.readyStatecomplete sind. Dies bedeutet, dass das Laden von JS und jQuery abgeschlossen ist.

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

Definiert/initialisiert die JS-Bibliothek eine allgemein bekannte Variable im Fenster?

In diesem Fall könnten Sie warten, bis die Variable angezeigt wird. Sie können verwenden

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

um diese Bedingung zu testen (etwa: window.SomeClass && window.SomeClass.variable != null) und eine boolesche true/false zurückzugeben.

Wickeln Sie dies in eine WebDriverWait und warten Sie, bis das Skript true zurückgibt.

8
Ashwin Prabhu

Wenn Sie nur warten müssen, bis der HTML-Code auf der Seite stabil ist, bevor Sie versuchen, mit Elementen zu interagieren, können Sie das DOM regelmäßig abfragen und die Ergebnisse vergleichen. Sind die DOMs innerhalb der angegebenen Abfragedauer gleich? golden. So etwas, bei dem Sie die maximale Wartezeit und die Zeit zwischen den Seitenabfragen vor dem Vergleich durchlaufen. Einfach und effektiv.

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

Der folgende Code funktioniert in meinem Fall perfekt - meine Seite enthält komplexe Java-Skripts

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; 
   }   
  }
 }

Quelle - Wie zu warten, bis die Seite geladen wird/Bereit in Selenium WebDriver

3
user790049

Ich hatte das gleiche Problem. Diese Lösung funktioniert für mich von 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

Für die nodejs Selenium-Bibliothek habe ich den folgenden Ausschnitt verwendet. In meinem Fall habe ich nach zwei Objekten gesucht, die dem Fenster hinzugefügt werden. In diesem Beispiel sind dies <SOME PROPERTY>, 10000 ist das Timeout in Millisekunden. <NEXT STEP HERE> passiert, nachdem die Eigenschaften im Fenster gefunden wurden.

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

Ich habe meine Entwickler gebeten, eine JavaScript-Variable "isProcessing" zu erstellen, auf die ich zugreifen kann (im "ae" -Objekt), auf die sie gesetzt werden, wenn die Dinge laufen, und klar sind, wenn die Dinge erledigt sind. Ich lasse es dann in einem Akkumulator laufen, der es alle 100 ms prüft, bis es insgesamt fünf aufeinander folgende 500 ms ohne Änderungen erhält. Wenn 30 Sekunden vergehen, stoße ich eine Ausnahme aus, da bis dahin etwas hätte passieren sollen. Dies ist in 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

Hier ist aus meinem eigenen Code:
Window.setTimeout wird nur ausgeführt, wenn der Browser inaktiv ist. 
Der Aufruf der Funktion rekursiv (42-mal) dauert also 100 ms, wenn der Browser nicht aktiv ist, und vieles mehr, wenn der Browser mit etwas anderem beschäftigt ist.

    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;

Als Bonus kann der Zähler bei document.readyState oder bei jQuery Ajax-Aufrufen zurückgesetzt werden, oder wenn JQuery-Animationen ausgeführt werden (nur wenn Ihre App jQuery für Ajax-Aufrufe verwendet ...) 
...

" 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); " +
                            " } "+ 

...

BEARBEITEN: Ich bemerke, dass executeAsyncScript nicht gut funktioniert, wenn eine neue Seite geladen wird und der Test möglicherweise auf unbestimmte Zeit nicht mehr reagiert.

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

Mit zwei Bedingungen kann geprüft werden, ob die Seite geladen ist, bevor ein Element auf der Seite gefunden wird:

WebDriverWait wait = new WebDriverWait(driver, 50);

Wenn Sie unter redayState verwenden, warten Sie bis zum Laden der Seite

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

Darunter wartet JQuery, bis keine Daten geladen wurden 

  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++;
                }
            }

Versuchen Sie nach diesen JavaScriptCodes, das webElement zu finden.

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

Sie können eine Logik schreiben, um dies zu handhaben. Ich habe eine Methode geschrieben, die WebElement zurückgibt. Diese Methode wird dreimal aufgerufen, oder Sie können die Zeit erhöhen und eine Nullprüfung für WebElement hinzufügen. Hier ist ein Beispiel

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

Um dies richtig zu machen, müssen Sie die Ausnahmen behandeln.

So warte ich auf einen iFrame. Dazu muss Ihre JUnit-Testklasse die Instanz von RemoteWebDriver in das Seitenobjekt übergeben:

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") ) );
    }
....

HINWEIS: Sie können siehe mein gesamtes Arbeitsbeispiel hier .

0
djangofan

Ich mag Ihre Idee, das HTML abzufragen, bis es stabil ist. Ich kann das zu meiner eigenen Lösung hinzufügen. Der folgende Ansatz ist in C # und erfordert jQuery.

Ich bin Entwickler für ein SuccessFactors (SaaS) -Testprojekt, bei dem wir keinerlei Einfluss auf die Entwickler oder die Eigenschaften des DOM hinter der Webseite haben. Das SaaS -Produkt kann möglicherweise das zugrunde liegende DOM-Design 4-mal pro Jahr ändern. Daher ist die Jagd nach robusten, performanten Möglichkeiten zum Testen mit Selen auf Dauer eröffnet (einschließlich des NICHT-Testens mit Selen, wo dies möglich ist!).

Folgendes verwende ich für "page ready". Es funktioniert derzeit in allen meinen eigenen Tests. Der gleiche Ansatz hat vor ein paar Jahren auch für eine große firmeninterne Java Web-App funktioniert und war über ein Jahr lang robust, als ich das Projekt verließ.

  • Driver ist die WebDriver-Instanz, die mit dem Browser kommuniziert
  • DefaultPageLoadTimeout ist ein Timeout-Wert in Ticks (100ns pro 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();

Beachten Sie im Folgenden die Reihenfolge der Wartezeiten in der Methode PageReady (Selenium document ready, Ajax, animations), die sinnvoll ist, wenn Sie darüber nachdenken:

  1. laden Sie die Seite mit dem Code
  2. verwenden Sie den Code, um die Daten über Ajax von irgendwoher zu laden
  3. präsentieren Sie die Daten, möglicherweise mit Animationen

So etwas wie Ihr DOM-Vergleichsansatz könnte zwischen 1 und 2 verwendet werden, um eine weitere Ebene der Robustheit hinzuzufügen.


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