web-dev-qa-db-ja.com

Selenium WebDriver:JavaScriptを使用した複雑なページの読み込みを待つ

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

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

有効なソリューションだと思いますか?

92
psadac

誰もが一般的で常に適用可能な答えを実際に知っていた場合、それは何年も前にeverywhere実装され、私たちの生活はSOになりますはるかに簡単。

できることはたくさんありますが、どれにも問題があります。

  1. Ashwin Prabhuが言ったように、スクリプトをよく知っていれば、windowまたはdocumentなどでその動作を観察し、その変数の一部を追跡できます。ただし、この解決策はすべてのユーザー向けではなく、限られたユーザーのみで使用できますページのセット。

  2. HTMLコードと、しばらくの間変更されていないかどうかを観察することによる解決策は悪くありません(また、WebDriverによって直接編集されていない元のHTMLを取得する a method があります) 、しかし:

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

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

63
Petr Janeček

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

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

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

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

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

28

JavascriptとjQueryの読み込みが完了するまで待つ必要があります。 Javascriptを実行して、jQuery.active0であり、document.readyStatecompleteであるかどうかを確認します。これは、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);
}
25
LINGS

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

その場合、変数が表示されるのを待つことができます。使用できます

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

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

これを WebDriverWait でラップし、スクリプトがtrueを返すまで待ち​​ます。

10
Ashwin Prabhu

ページ上の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;
        }
    }
}
6
TheFreddyKilo

私の場合、以下のコードは完全に機能します-私のページには複雑な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でのページの読み込み/準備が完了するまで待つ方法

4
user790049

同じ問題がありました。このソリューションは、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

4
Roma Kap

開発者に、(「ae」オブジェクト内で)アクセスできるJavaScript変数「isProcessing」を作成するように依頼しました。次に、100ミリ秒ごとにチェックするアキュムレーターで実行し、変更なしで合計500ミリ秒で5連続で取得します。 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.");
}
2
Roger Cook

ページ上の要素を見つける前に、ページがロードされているかどうかを確認するには、2つの条件を使用できます。

WebDriverWait wait = new WebDriverWait(driver, 50);

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

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の後、findElement webElementを試してください。

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

nodejs Seleniumライブラリには、次のスニペットを使用しました。私の場合、ウィンドウに追加される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);
});
1
Jason Lydon

適切に行うには、例外を処理する必要があります。

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
djangofan

これは私自身のコードからのものです:
Window.setTimeoutは、ブラウザーがアイドル状態のときにのみ実行されます。

    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
Mariusz

これを処理するロジックを作成できます。 WebElementを返すメソッドを作成しました。このメソッドは3回呼び出されるか、時間を増やしてWebElementのnullチェックを追加できます。例を次に示します。

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

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

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

これが「ページ準備完了」に使用するものです。現在、私自身のすべてのテストで機能します。同じアプローチは、数年前に大きな社内Javaウェブアプリでも機能し、私がプロジェクトを辞めた時点で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ドキュメント対応、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");
}
0
Nick