Selenium WebDriver: JavaScript içeren karmaşık sayfanın yüklenmesini bekleyin


103

Selenium ile test etmem gereken bir web uygulamam var. Sayfa yüklemesinde çalışan çok sayıda JavaScript var.
Bu JavaScript kodu çok iyi yazılmış değil ama hiçbir şeyi değiştiremiyorum. Dolayısıyla, findElement()yöntemle DOM'da bir öğenin görünmesini beklemek bir seçenek değildir.
Java'da bir sayfanın yüklenmesini beklemek için genel bir işlev oluşturmak istiyorum, olası bir çözüm şöyle olacaktır:

  • WebDriver'dan bir JavaScript komut dosyası çalıştırın ve sonucunu document.body.innerHTMLbir dize değişkeninde saklayın body.
  • bodydeğişkeni önceki sürümüyle karşılaştırın body. eğer aynıysa, artım notChangedCountayarlayın notChangedCount, aksi takdirde sıfıra ayarlayın .
  • kısa bir süre bekleyin (örneğin 50 ms).
  • sayfa bir süre değişmediyse (örneğin 500 ms), bu nedenle notChangedCount >= 10döngüden çıkın, aksi takdirde ilk adıma geçin.

Bunun geçerli bir çözüm olduğunu düşünüyor musunuz?


findElement () beklemiyor - bununla ne demek istiyorsun?
Rostislav Matl

1
findElementbir öğenin kullanılabilir olmasını bekler, ancak bazen öğe javascript kodu tamamen başlatılmadan önce kullanılabilir, bu yüzden bu bir seçenek değildir.
psadac

Unuttum - WebDriverWait ve ExpectedCondition kullanmaya alışkınım bu çok daha esnek.
Rostislav Matl

Yanıtlar:


73

Eğer biri gerçekten genel ve her zaman geçerli bir cevabı bilseydi, çağlar önce her yerde uygulanacaktı ve hayatımızı ÇOK kolaylaştıracaktı.

Yapabileceğiniz birçok şey var ama her birinin bir sorunu var:

  1. Ashwin Prabhu dediği gibi iyi senaryoyu biliyorsanız, onun davranışlarını gözlemlemek ve onun değişkenlerin bazılarını izleyebilirsiniz windowveya documentvs. Bu çözüm, ancak, herkes için değildir ve sizin tarafınızdan ve sadece sınırlı sayıda sadece kullanılabilir sayfalar.

  2. HTML kodunu ve bir süredir değiştirilip değiştirilmediğini gözlemleyerek çözümünüz fena değildir (ayrıca, orijinal ve düzenlenmemiş HTML'yi doğrudan almak için bir yöntem vardır WebDriver), ancak:

    • Bir sayfayı gerçekten ileri sürmek uzun zaman alır ve testi önemli ölçüde uzatabilir.
    • Doğru aralığın ne olduğunu asla bilemezsiniz. Komut dosyası , 500 ms'den uzun süren büyük bir şey indiriyor olabilir . Şirketimizin iç sayfasında, IE'de birkaç saniye süren birkaç komut dosyası vardır. Bilgisayarınızın kaynakları geçici olarak yetersiz olabilir - bir antivirüsün CPU'nuzun tam olarak çalışmasını sağlayacağını söyleyin, bu durumda karmaşık olmayan komut dosyaları için bile 500 ms çok kısa olabilir.
    • Bazı komut dosyaları asla bitmez. Kendilerini biraz gecikme ( setTimeout()) ile çağırırlar ve tekrar tekrar çalışırlar ve muhtemelen her çalıştırdıklarında HTML'yi değiştirebilirler. Cidden, her "Web 2.0" sayfası bunu yapar. Hatta Yığın Taşması. Kullanılan en yaygın yöntemlerin üzerine yazabilir ve bunları kullanan betiklerin tamamlandığını düşünebilirsiniz, ancak ... emin olamazsınız.
    • Ya komut dosyası HTML'yi değiştirmekten başka bir şey yaparsa? Sadece biraz innerHTMLeğlence değil, binlerce şey yapabilir .
  3. Bu konuda size yardımcı olacak araçlar var. Yani nsIWebProgressListener ve diğerleri ile birlikte İlerleme Dinleyicileri . Ancak bunun için tarayıcı desteği korkunç. Firefox, FF4'ten itibaren onu desteklemeye başladı (hala gelişiyor), IE'nin IE9'da temel desteği var.

Ve sanırım yakında başka bir kusurlu çözüm bulabilirim. Gerçek şu ki - işlerini yapan sonsuz komut dosyaları nedeniyle "şimdi sayfa tamamlandı" demenin kesin bir cevabı yok. Size en uygun olanı seçin, ancak eksikliklerine dikkat edin.


31

Teşekkürler Ashwin!

Benim durumumda, bazı öğelerde jquery eklentisinin çalıştırılması için beklemem gerekir .. özellikle "qtip"

senin ipucuna göre, benim için mükemmel çalıştı:

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

Not: Webdriver 2 kullanıyorum


2
Benim için de çok iyi çalışıyor. Benim durumumda, sayfanın yüklenmesi bittikten hemen sonra Javascript tarafından bir eleman değiştirildi ve Selenium bunu dolaylı olarak beklemiyordu.
Noxxys

2
Predicate nereden geliyor? hangi ithalat ??
Amado Saladino

@AmadoSaladino "import com.google.common.base.Predicate;" google lib guava-15.0 bağlantısından: mvnrepository.com/artifact/com.google.guava/guava/15.0 deneyebileceğiniz daha yeni 27.0 sürümü var!
Eduardo Fabricio

26

Javascript ve jQuery'nin yüklemeyi bitirmesini beklemeniz gerekir. Olmadığını kontrol etmek Javascript yürütme jQuery.activeolduğunu 0ve document.readyStateolan completeJS ve jQuery yükleme tamamlandıktan anlamına gelir.

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
Bunun her durumda işe
yarayıp

1
Benim için de işe yarıyor ama zor bir kısmı var: Diyelim ki sayfanız zaten yüklendi. Bir öğeyle etkileşimde bulunursanız [ör. Bir .click () gönderin veya. Ona bazı anahtarlar gönderin] ve ardından sayfanızın işlenmesinin ne zaman bittiğini kontrol etmek istiyorsanız, bir gecikme eklemeniz gerekir: ör. Tıklama (), uyku (0,5 saniye ), (readyState = 'complete' & jQuery.active == 0) olana kadar bekleyin. Uykuyu eklemezseniz, iQuery test zamanında aktif olmayacaktır! (öğrenmem birkaç saatimi aldı, bu yüzden paylaşmayı düşündüm)
Fivos Vilanakis

document.readyState == 'complete' && jQuery.active == 0harika çalışıyor ama React için buna benzer bir şey var mı? Bu kontroller, tepki verilerini / bileşenlerini yüklemeye devam etmeyeceğinden mi?
Rain9333



7

Yapmanız gereken tek şey, öğelerle etkileşim kurmaya çalışmadan önce sayfadaki html'nin kararlı hale gelmesini beklemekse, DOM'u periyodik olarak yoklayabilir ve sonuçları karşılaştırabilirsiniz; DOM'lar verilen anket süresi içinde aynıysa, altın. Karşılaştırmadan önce maksimum bekleme süresini ve sayfa anketleri arasındaki süreyi geçtiğiniz gibi bir şey. Basit ve etkili.

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
Burada, orta büyüklükteki bir sayfayı iki kez sorgulamak için geçen süreyi kaydettiğimi, ardından bu dizeleri java'da karşılaştırdığımı ve 20 ms'nin biraz üzerinde sürdüğünü belirtmek isterim.
TheFreddyKilo

2
İlk başta bunu kirli bir çözüm olarak buldum, ancak harika çalışıyor gibi görünüyor ve değişkenleri istemci tarafında ayarlamayı içermiyor. Bir caveit, javascript (yani bir javascript saati) tarafından sürekli değiştirilen bir sayfa olabilir, ancak bende yok, bu yüzden çalışıyor!
Peter

4

Aşağıdaki kod benim durumumda mükemmel çalışıyor - sayfam karmaşık java komut dosyaları içeriyor

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

Kaynak - Selenium WebDriver'da Sayfanın Yüklenmesi / Hazırlanması İçin Nasıl Beklenir


2
Bu döngüler yerine selenyum beklemeleri kullanmalısınız
cocorossello

4

Sayfada herhangi bir öğe bulmadan önce sayfanın yüklenip yüklenmediğini kontrol etmek için iki koşul kullanılabilir:

WebDriverWait wait = new WebDriverWait(driver, 50);

Aşağıdaki readyState kullanımı sayfanın yüklenmesini bekleyecektir

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

JQuery'nin altında veri yüklenene kadar bekleyecek

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

Bu JavaScriptCode'dan sonra webElement'i bulmaya çalışın.

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

1
Selenium da bunu varsayılan olarak yapar, bu sadece betiğinize bazı ek kod yürütme süresi ekler (bu, ek şeylerin yüklenmesi için yeterli zaman olabilir, ancak daha sonra olmayabilir)
Ardesco

2

Geliştiricilerimden, işler çalışmaya başladığında belirledikleri ve işler bittiğinde netleşen erişebileceğim ("ae" nesnesinde) bir JavaScript değişkeni "isProcessing" oluşturmalarını istedim. Daha sonra, her 100 ms'de bir kontrol eden bir akümülatörde çalıştırıyorum ve herhangi bir değişiklik yapmadan toplam 500 ms boyunca arka arkaya beş tane alana kadar. 30 saniye geçerse, bir istisna atarım çünkü o zamana kadar bir şey olması gerekirdi. Bu C # içindedir.

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

Doğru şekilde yapmak için istisnaları halletmeniz gerekir.

İşte bir iFrame için nasıl bekleyeceğim. Bu, JUnit test sınıfınızın RemoteWebDriver örneğini sayfa nesnesine geçirmesini gerektirir:

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

NOT: Çalışma örneğimin tamamını burada görebilirsiniz .


1

İçin nodejsSelenyum kütüphane, aşağıdaki pasajı kullandı. Benim durumumda, ben bu örnekte olduğu penceresine eklenir iki nesne, arıyordu <SOME PROPERTY>, 10000, zaman aşımı milisaniye <NEXT STEP HERE>özellikleri penceresinde bulunan sonra neler olduğunu.

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

Bunu halletmek için biraz mantık yazabilirsiniz. 'I döndürecek bir yöntem yazdım WebElementve bu yöntem üç kez çağrılacak veya zamanı artırıp boş bir kontrol ekleyebilirsiniz WebElementİşte bir örnek

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

İşte kendi
kodumdan : Window.setTimeout yalnızca tarayıcı boştayken çalıştırılır.
Bu nedenle, işlevi yinelemeli olarak (42 kez) çağırmak, tarayıcıda etkinlik yoksa 100 ms ve tarayıcı başka bir şey yapmakla meşgulse çok daha fazlasını alır.

    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;

Bir bonus olarak, sayaç document.readyState veya jQuery Ajax çağrılarında veya herhangi bir jQuery animasyonu çalışıyorsa (yalnızca uygulamanız ajax çağrıları için jQuery kullanıyorsa ...) sıfırlanabilir
...

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

...

DÜZENLEME: executeAsyncScript yeni bir sayfa yüklenirse ve test sonsuza kadar yanıt vermeyi durdurabilirse, bunun yerine bunu kullanmak daha iyi olur.

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

Bunu nasıl yapacağımı bilmiyorum ama benim durumumda, Firefox sekmesinde görüntülenen FAVICON ile sayfa yükleme sonu ve oluşturma eşleşiyor.

Dolayısıyla, web tarayıcısında favicon görüntüsünü alabilirsek, web sayfası tamamen yüklenir.

Ama bunu nasıl yapın ...



-1

İşte bunu nasıl yapıyorum:

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

Selenium da bunu varsayılan olarak yapar, bu sadece betiğinize bazı ek kod yürütme süresi ekler (bu, ek şeylerin yüklenmesi için yeterli olabilir, ancak daha sonra olmayabilir)
Ardesco

-1

Kararlı hale gelene kadar HTML sorgulama fikrinizi beğendim. Bunu kendi çözümüme ekleyebilirim. Aşağıdaki yaklaşım C # içindedir ve jQuery gerektirir.

Web sayfasının arkasındaki DOM'un özellikleri veya geliştiriciler üzerinde hiçbir etkimizin olmadığı bir SuccessFactors (SaaS) test projesinin geliştiricisiyim. SaaS ürünü, temeldeki DOM tasarımını yılda 4 kez potansiyel olarak değiştirebilir, bu nedenle Selenium ile test etmek için sağlam, performanslı yollar arayışı kalıcı olarak devam eder (mümkünse Selenium ile test ETMEMEK dahil!)

İşte "sayfaya hazır" için kullandığım şey. Şu anda tüm kendi testlerimde çalışıyor. Aynı yaklaşım, birkaç yıl önce büyük bir şirket içi Java web uygulaması için de işe yaradı ve projeden ayrıldığımda bir yıldan fazla bir süredir sağlamdı.

  • Driver tarayıcıyla iletişim kuran WebDriver örneğidir
  • DefaultPageLoadTimeout tik cinsinden bir zaman aşımı değeridir (onay başına 100 ns)

public IWebDriver Driver { get; private set; }

// ...

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

Aşağıda, PageReadydüşünürseniz mantıklı olan yöntemdeki bekleme sırasına (Selenium belgesi hazır, Ajax, animasyonlar) dikkat edin:

  1. kodu içeren sayfayı yükle
  2. Ajax aracılığıyla bir yerden veri yüklemek için kodu kullanın
  3. verileri muhtemelen animasyonlarla sunun

DOM karşılaştırma yaklaşımınız gibi bir şey, başka bir sağlamlık katmanı eklemek için 1 ile 2 arasında kullanılabilir.


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");
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.