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


102

Ich habe eine Webanwendung zum Testen mit Selen. Beim Laden der Seite wird viel JavaScript ausgeführt.
Dieser JavaScript-Code ist nicht so gut geschrieben, aber ich kann nichts ändern. Es findElement()ist daher keine Option, darauf zu warten, dass ein Element mit der Methode im DOM angezeigt wird.
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 document.body.innerHTMLin einer Zeichenfolgenvariablen body.
  • Vergleichen Sie die bodyVariable mit der vorherigen Version von body. Wenn sie gleich sind, setzen Sie einen Zähler inkrementieren, notChangedCountandernfalls notChangedCountauf Null.
  • Warten Sie ein wenig (zum Beispiel 50 ms).
  • Wenn sich die Seite einige Zeit nicht geändert hat (z. B. 500 ms), notChangedCount >= 10verlassen Sie die Schleife, andernfalls fahren Sie mit dem ersten Schritt fort.

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


findElement () wartet nicht - was meinst du damit?
Rostislav Matl

1
findElementWartet darauf, dass ein Element verfügbar ist, aber manchmal ist das Element verfügbar, bevor der Javascript-Code vollständig initialisiert wird. Deshalb ist dies keine Option.
Psadac

Ich habe es vergessen - ich bin es gewohnt, WebDriverWait und ExpectedCondition zu verwenden, es ist viel flexibler.
Rostislav Matl

Antworten:


73

Wenn jemand tatsächlich eine allgemeine und immer zutreffende Antwort gewusst hätte, wäre sie vor langer Zeit überall umgesetzt worden und würde unser Leben so viel einfacher machen.

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

  1. Wie Ashwin Prabhu sagte, wenn Sie das Skript gut kennen, können Sie sein Verhalten beobachten und einige seiner Variablen verfolgen windowoder documentdiese Lösung etc., ist jedoch nicht jedermanns Sache und kann nur von Ihnen und nur auf eine begrenzte Anzahl von verwendet werden Seiten.

  2. Ihre Lösung, indem Sie den HTML-Code beobachten und feststellen, 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 abzurufen WebDriver), aber:

    • Es dauert lange, bis eine Seite tatsächlich aktiviert ist, und kann den Test erheblich verlängern.
    • Sie wissen nie, was das richtige Intervall ist. Das Skript lädt möglicherweise etwas Großes herunter, das mehr als 500 ms dauert. Auf der internen Seite unseres Unternehmens befinden sich mehrere Skripte, die im IE einige Sekunden dauern. Auf Ihrem Computer sind möglicherweise vorübergehend die Ressourcen knapp. Angenommen, ein Antivirenprogramm sorgt dafür, dass Ihre CPU voll funktionsfähig ist. 500 ms sind möglicherweise selbst für nicht komplexe Skripte zu kurz.
    • Einige Skripte werden nie erstellt. Sie rufen sich mit einiger Verzögerung ( setTimeout()) auf und arbeiten immer wieder und können möglicherweise den HTML-Code bei jeder Ausführung ändern. 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, die sie verwenden, als abgeschlossen betrachten, aber ... Sie können nicht sicher sein.
    • Was ist, wenn das Skript etwas anderes tut als den HTML-Code zu ändern? Es könnte Tausende von Dingen tun, nicht nur innerHTMLSpaß.
  3. Es gibt Tools, die Ihnen dabei helfen. Nämlich Progress Listener zusammen mit nsIWebProgressListener und einigen anderen. Die Browserunterstützung hierfür ist jedoch schrecklich. Firefox begann zu versuchen, es ab FF4 zu unterstützen (entwickelt sich noch weiter). IE hat grundlegende Unterstützung in IE9.

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


31

Danke Ashwin!

In meinem Fall sollte ich auf die Ausführung eines JQuery-Plugins in einem Element warten müssen. Speziell "qtip"

Basierend auf Ihrem Hinweis hat es perfekt für mich funktioniert:

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

Hinweis: Ich verwende Webdriver 2


2
Das funktioniert auch bei mir sehr gut. In meinem Fall wurde ein Element von Javascript unmittelbar nach dem Laden der Seite geändert, und Selenium wartete nicht implizit darauf.
Noxxys

2
Woher kommt Predicate? welcher import ??
Amado Saladino

@AmadoSaladino "com.google.common.base.Predicate importieren;" Von Google Lib Guava-15.0 Link: mvnrepository.com/artifact/com.google.guava/guava/15.0 gibt es neuere Version 27.0, Sie können es versuchen!
Eduardo Fabricio

26

Sie müssen warten, bis Javascript und jQuery vollständig geladen sind. Führen Sie Javascript aus, um zu überprüfen, ob es jQuery.activeist 0und document.readyStateist complete, was 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);
}

2
Ich
bin

1
Es funktioniert auch bei mir, aber es gibt einen schwierigen Teil: Nehmen wir an, Ihre Seite ist bereits geladen. Wenn Sie mit einem Element interagieren (z. B. einen .click () senden oder einige Schlüssel senden) und dann überprüfen möchten, wann die Verarbeitung Ihrer Seite abgeschlossen ist, müssen Sie eine Verzögerung hinzufügen: z. B. click (), sleep (0,5 Sek.) ), warten Sie bis (readyState = 'complete' & jQuery.active == 0). Wenn Sie den Schlaf nicht hinzufügen, ist die iQuery zur Testzeit nicht aktiv! (Ich brauchte einige Stunden, um es herauszufinden, also dachte ich, ich würde es teilen)
Fivos Vilanakis

document.readyState == 'complete' && jQuery.active == 0funktioniert super, aber gibt es so etwas für React? Da diese Überprüfungen nicht fangen werden, werden noch Reaktionsdaten / Komponenten geladen?
Rain9333

10

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

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

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

um auf diese Bedingung zu testen (so etwas wie window.SomeClass && window.SomeClass.variable != null:) und einen booleschen Wert true/ zurückzugeben false.

Wickeln Sie dies in ein WebDriverWaitund warten Sie, bis das Skript zurückkehrt true.



7

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. Wenn die DOMs innerhalb der angegebenen Abfragezeit identisch sind, sind Sie es golden. So etwas, bei dem Sie die maximale Wartezeit und die Zeit zwischen den Seitenabfragen vor dem Vergleich eingeben. 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;
        }
    }
}

1
Ich möchte hier darauf hinweisen, dass ich die Zeit protokolliert habe, die zum zweimaligen Abrufen einer mittelgroßen Seite benötigt wurde, dann diese Zeichenfolgen in Java verglichen habe und etwas mehr als 20 ms gedauert habe.
TheFreddyKilo

2
Zuerst fand ich, dass dies eine schmutzige Lösung ist, aber es scheint großartig zu funktionieren und es geht nicht darum, Variablen clientseitig festzulegen. Ein Caveit könnte eine Seite sein, die ständig durch Javascript (dh eine Javascript-Uhr) geändert wird, aber ich habe das nicht, damit es funktioniert!
Peter

4

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

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 - Warten auf das Laden / Bereitstellen der Seite in Selenium WebDriver


2
Sie sollten Selen-Wartezeiten anstelle dieser Schleifen verwenden
Cocorossello

4

Zwei Bedingungen können verwendet werden, um zu überprüfen, ob die Seite geladen ist, bevor ein Element auf der Seite gefunden wird:

WebDriverWait wait = new WebDriverWait(driver, 50);

Bei Verwendung von readyState wird bis zum Laden der Seite gewartet

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

Unten 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 ,Out webElement zu finden.

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

1
Selenium tut dies auch standardmäßig.
Dadurch wird

2

Ich habe meine Entwickler gebeten, eine JavaScript-Variable "isProcessing" zu erstellen, auf die ich zugreifen kann (im "ae" -Objekt), die sie festgelegt haben, wenn Dinge ausgeführt werden, und die gelöscht werden, wenn Dinge erledigt sind. Ich führe es dann in einem Akkumulator aus, der es alle 100 ms überprüft, bis es fünf Änderungen hintereinander für insgesamt 500 ms ohne Änderungen erhält. Wenn 30 Sekunden vergehen, werde ich eine Ausnahme auslösen, 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

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

So warte ich auf einen iFrame. Dies erfordert, dass Ihre JUnit-Testklasse die Instanz von RemoteWebDriver an das Seitenobjekt übergibt:

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 mein gesamtes Arbeitsbeispiel hier sehen .


1

Für die nodejsSelenium-Bibliothek habe ich das folgende Snippet verwendet. In meinem Fall war ich für zwei Objekte suchen, der zum Fenster hinzugefügt werden, die in diesem Beispiel sind <SOME PROPERTY>, 10000ist die Timeout Millisekunden, <NEXT STEP HERE>ist das, was passiert nach den Eigenschaften auf dem Fenster zu finden sind.

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

0

Sie können eine Logik schreiben, um dies zu handhaben. Ich habe eine Methode geschrieben, die das zurückgibt, WebElementund diese Methode wird dreimal aufgerufen, oder Sie können die Zeit verlängern und eine Nullprüfung für hinzufügen. WebElementHier 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

Hier ist aus meinem eigenen Code:
Window.setTimeout wird nur ausgeführt, wenn der Browser inaktiv ist.
Das rekursive Aufrufen der Funktion (42 Mal) dauert also 100 ms, wenn im Browser keine Aktivität vorhanden ist, und viel 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 stelle fest, dass executeAsyncScript nicht gut funktioniert, wenn eine neue Seite geladen wird und der Test möglicherweise nicht mehr auf unbestimmte Zeit reagiert. Verwenden Sie diese Option stattdessen besser.

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

Ich weiß nicht, wie ich das machen soll, aber in meinem Fall stimmt das Laden und Rendern am Ende der Seite mit FAVICON überein, das auf der Registerkarte Firefox angezeigt wird.

Wenn wir also das Favicon-Bild im Webbrowser erhalten können, ist die Webseite vollständig geladen.

Aber wie geht das?



-1

So mache ich das:

new WebDriverWait(driver, 20).until(
       ExpectedConditions.jsReturnsValue(
                   "return document.readyState === 'complete' ? true : false"));

Selenium tut dies auch standardmäßig.
Dadurch wird

-1

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

Ich bin der Entwickler eines SuccessFactors (SaaS) -Testprojekts, 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 viermal im Jahr ändern, sodass ständig nach robusten, performanten Testmethoden für Selen gesucht wird (einschließlich NICHT-Tests mit Selen, wo dies möglich ist!).

Folgendes verwende ich für "Seite bereit". Es funktioniert derzeit in allen meinen eigenen Tests. Der gleiche Ansatz funktionierte vor ein paar Jahren auch für eine große interne Java-Web-App und war zu dem Zeitpunkt, als ich das Projekt verließ, über ein Jahr lang robust.

  • Driver ist die WebDriver-Instanz, die mit dem Browser kommuniziert
  • DefaultPageLoadTimeout ist ein Timeout-Wert in Ticks (100 ns 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 von irgendwo über Ajax 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 Robustheitsebene 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");
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.