Selenium WebDriver:JavaScriptを含む複雑なページが読み込まれるのを待つ


102

SeleniumでテストするWebアプリケーションがあります。ページの読み込み時に実行されるJavaScriptはたくさんあります。
このJavaScriptコードはあまりよく書かれていませんが、何も変更できません。したがって、DOM with findElement()methodに要素が表示されるのを待つことはオプションではありません。
Javaで汎用的な関数を作成して、ページの読み込みを待機したいのですが、可能な解決策は次のとおりです。

  • WebDriverからJavaScriptスクリプトを実行し、結果をdocument.body.innerHTML文字列変数に格納しますbody
  • body変数を以前のバージョンのと比較しますbody。それらが同じ場合は、カウンターをインクリメントし、notChangedCountそれ以外の場合notChangedCountはゼロに設定します。
  • 少し待ってください(たとえば50ミリ秒)。
  • ページが一定時間(たとえば500ミリ秒)変更されていない場合はnotChangedCount >= 10、ループを終了します。それ以外の場合は、最初のステップに戻ります。

それは有効な解決策だと思いますか?


findElement()は待機しません-それはどういう意味ですか?
Rostislav Matl

1
findElement要素が利用可能になるまで待機しますが、JavaScriptコードが完全に初期化される前に要素が利用可能になる場合があります。そのため、これはオプションではありません。
psadac

忘れました-WebDriverWaitとExpectedConditionを使用する方がずっと柔軟です。
Rostislav Matl

回答:


73

だれかが一般的で常に当てはまる答えを実際に知っていれば、それは何年も前にどこにでも実装されており、私たちの生活をとても簡単にします。

できることはたくさんありますが、それらすべてに問題があります。

  1. Ashwinプラブーが言ったようにあなたがよくスクリプトを知っていれば、あなたはその行動を観察し、上の変数のいくつかを追跡することができwindowたりdocumentなど、このソリューションを、しかし、皆のためではなく、あなたによってのみの限定されたセットでのみ使用することができますページ。

  2. HTMLコードを監視し、しばらくの間変更されていないかどうかを確認することによる解決策は悪くありません(また、によって元の編集されていないHTMLを直接取得する方法もありますWebDriver)。

    • 実際にページをアサートするのに長い時間がかかり、テストが大幅に長くなる可能性があります。
    • あなたは正しい間隔が何であるかを決して知りません。スクリプト 500ミリ秒以上かかる大きなデータをダウンロードしている可能性があります。弊社の内部ページには、IEで数秒かかるスクリプトがいくつかあります。コンピューターのリソースが一時的に不足している可能性があります-ウイルス対策によってCPUが完全に機能するとしたら、複雑でないスクリプトでも500ミリ秒では短すぎる可能性があります。
    • 一部のスクリプトは実行されません。彼らは少し遅れて自分自身を呼び出し(setTimeout())、何度も何度も動作し、実行するたびにHTMLを変更する可能性があります。真剣に、すべての「Web 2.0」ページがそれを行います。スタックオーバーフローも。使用される最も一般的な方法を上書きして、それらを使用するスクリプトを完了したと見なすことができますが、...確かではありません。
    • スクリプトがHTMLを変更する以外のことをしたらどうなるでしょうか。innerHTML楽しいだけでなく、何千ものことができます。
  3. これに役立つツールがあります。つまり、nsIWebProgressListenerおよびその他のいくつかと一緒にProgress Listenerを使用します。しかし、これに対するブラウザのサポートは恐ろしいものです。FirefoxはFF4以降(まだ進化中)からサポートを試み始め、IEはIE9で基本的なサポートを提供しています。

そして、すぐに別の欠陥のある解決策を思い付くと思います。事実は-作業を実行するスクリプトが永遠に続くため、「ページが完成した」と言うタイミングについて明確な答えはありません。最善のサービスを選択してください。ただし、その欠点に注意してください。


31

アシュウィンありがとう!

私の場合、いくつかの要素でjqueryプラグインの実行を待つ必要があります。具体的には「qtip」

あなたのヒントに基づいて、それは私にとって完璧に機能しました:

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

注:私はWebdriver 2を使用しています


2
それも私にとってはとてもうまくいきます。私の場合、ページの読み込みが完了した直後にJavascriptによって要素が変更され、Seleniumは暗黙的にそれを待機していませんでした。
Noxxys 2016年

2
述語はどこから来たのですか?どのインポート??
アマドサラディーノ

@AmadoSaladino "import com.google.common.base.Predicate;" google lib guava -15.0リンクから:mvnrepository.com/artifact/com.google.guava/guava/15.0新しいバージョン27.0があり、試すことができます!
エドゥアルドファブリシオ

26

JavaScriptとjQueryの読み込みが完了するのを待つ必要があります。JavaScriptを実行して、jQuery.activeis 0およびdocument.readyStateis かどうかを確認completeします。これは、JSおよびjQueryのロードが完了したことを意味します。

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
これがすべての状況で機能するかどうかはわかりませんが、それは私のユースケースの治療として機能します
DaveH

1
それも私にとっては機能しますが、トリッキーな部分があります:あなたのページはすでにロードされているとしましょう。要素を操作して(たとえば、.click()または.sendにいくつかのキーを送信する)、ページの処理が完了したときに確認したい場合は、遅延を追加する必要があります。たとえば、click()、sleep(0.5秒) )、(readyState = 'complete'&jQuery.active == 0)になるまで待ちます。スリープを追加しないと、テスト時にiQueryがアクティブになりません。(調べるのに数時間かかったので、共有しようと思いました)
Fivos Vilanakis 2018年

document.readyState == 'complete' && jQuery.active == 0うまく動作しますが、Reactにそのようなものはありますか?これらのチェックはまだロード中の反応データ/コンポーネントをキャッチしないので?
Rain9333

10

JSライブラリは、ウィンドウ上の既知の変数を定義/初期化しますか?

もしそうなら、変数が現れるのを待つことができます。使用できます

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

この条件をテストし(次のようなものwindow.SomeClass && window.SomeClass.variable != null)、ブール値true/ を返しfalseます。

これをで囲みWebDriverWait、スクリプトが戻るまで待ちますtrue



7

要素を操作する前に、ページ上のhtmlが安定するのを待つだけでよい場合は、DOMを定期的にポーリングして結果を比較できます。指定されたポーリング時間内にDOMが同じ場合は、ゴールデン。比較する前に、最大待機時間とページポーリング間の時間を渡すこのようなもの。シンプルで効果的。

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
ここで、中サイズのページを2回ポーリングするのにかかった時間をログに記録してから、Javaでそれらの文字列を比較したところ、20ミリ秒強かかったことに注意したいと思います。
TheFreddyKilo

2
最初はこれは汚い解決策であることがわかりましたが、うまく機能しているようで、クライアント側で変数を設定する必要はありません。1つの洞窟は、JavaScript(つまり、JavaScriptクロック)によって絶えず変更されているページかもしれませんが、私はそれを持っていないので、動作します!
ピーター

4

以下のコードは私の場合完全に機能します-私のページには複雑なJavaスクリプトが含まれています

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

ソース-Selenium WebDriverでページが読み込まれる/準備されるのを待つ方法


2
これらのループの代わりにセレン待機を使用する必要があります
cocorossello 2017

4

ページ上の要素を見つける前に、ページが読み込まれているかどうかを確認するために2つの条件を使用できます。

WebDriverWait wait = new WebDriverWait(driver, 50);

以下のreadyStateを使用すると、ページがロードされるまで待機します

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

jQueryの下では、データがロードされなくなるまで待機します

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

これらのJavaScriptCodeの後、findOut webElementを試してください。

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

1
Seleniumもデフォルトでこれを実行します。これにより、スクリプトに追加のコード実行時間が追加されます(これは、追加の読み込みに十分な時間である可能性がありますが、そうでない場合もあります)
Ardesco

2

開発者に、 "ae"オブジェクトで)アクセス可能なJavaScript変数 "isProcessing"を作成するよう依頼しました。次に、アキュムレータで実行し、100ミリ秒ごとにチェックして、5つ続けて500ミリ秒になるまで変更を加えません。30秒が経過すると、それまでに何かが起こっているはずだったので、例外をスローします。これは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

これを正しく行うには、例外を処理する必要があります。

ここでは、iFrameを待機する方法を示します。これには、JUnitテストクラスがRemoteWebDriverのインスタンスをページオブジェクトに渡す必要があります。

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

注:ここで私の実際の例全体見ることができます


1

以下のためにnodejsセレンライブラリ、私は次のスニペットを使用しました。私の場合、ウィンドウに追加された2つのオブジェクトを探していました。この例<SOME PROPERTY>10000は、タイムアウトミリ秒<NEXT STEP HERE>です。これは、ウィンドウでプロパティが見つかった後に何が起こるかです。

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

これを処理するロジックを記述できます。を返すメソッドを作成しました。WebElementこのメソッドは3回呼び出されるか、時間を増やしてnullチェックを追加できます。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

これが私の独自のコードです
。Window.setTimeoutは、ブラウザがアイドル状態のときにのみ実行されます。
そのため、関数を再帰的に呼び出す(42回)には、ブラウザーにアクティビティがない場合は100ミリ秒かかり、ブラウザーが他の作業でビジー状態の場合はさらに多くかかります。

    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;

おまけとして、document.readyStateまたはjQuery Ajax呼び出し、またはjQueryアニメーションが実行されている場合にカウンターをリセットできます(アプリがajax呼び出しにjQueryを使用している場合のみ...)
...

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

...

編集:新しいページが読み込まれ、テストが無期限に応答を停止する場合、executeAsyncScriptがうまく機能しないことに気づきました。代わりにこれを使用することをお勧めします。

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

それを行う方法がわからないが、私の場合、ページの読み込みとレンダリングの終わりは、Firefoxタブに表示されるFAVICONと一致します。

したがって、ウェブブラウザでファビコン画像を取得できる場合、ウェブページは完全に読み込まれています。

しかし、これをどのように実行するか....



-1

ここに私がそれをする方法があります:

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

Seleniumもデフォルトでこれを実行します。これにより、スクリプトに追加のコード実行時間が追加されます(追加の読み込みには十分な時間ですが、そうでない場合もあります)
Ardesco

-1

HTMLが安定するまでポーリングするというアイデアが気に入っています。それを自分のソリューションに追加することができます。次のアプローチはC#であり、jQueryが必要です。

私は、SuccessFactors(SaaS)テストプロジェクトの開発者であり、開発者やWebページの背後にあるDOMの特性にまったく影響を与えていません。SaaS製品は、基礎となるDOM設計を1年に4回変更する可能性があるため、Seleniumを使用してテストする堅牢で高性能な方法(可能な場合はSeleniumを使用しないテストを含む)を探し続けています。

これが「ページ準備」に使用するものです。現在、私のすべてのテストで機能します。同じアプローチは、数年前に社内の大規模なJava Webアプリでも機能し、私がプロジェクトを去ったとき、1年以上も堅牢でした。

  • Driver ブラウザと通信するWebDriverインスタンスです
  • DefaultPageLoadTimeout ティック単位のタイムアウト値(ティックあたり100ns)

public IWebDriver Driver { get; private set; }

// ...

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

以下では、メソッドの待機順序PageReady(Selenium document ready、Ajax、アニメーション)に注意してください。これは、考えれば意味があります。

  1. コードを含むページをロードする
  2. コードを使用して、Ajax経由でどこかからデータをロードします
  3. おそらくアニメーションでデータを提示する

DOM比較アプローチのようなものを1と2の間で使用して、堅牢性の別のレイヤーを追加できます。


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");
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.