Selenium WebDriver: Tunggu halaman kompleks dengan JavaScript dimuat


103

Saya memiliki aplikasi web untuk diuji dengan Selenium. Ada banyak JavaScript yang berjalan saat halaman dimuat.
Kode JavaScript ini tidak ditulis dengan baik tetapi saya tidak dapat mengubah apa pun. Jadi menunggu elemen muncul di DOM dengan findElement()metode bukanlah suatu pilihan.
Saya ingin membuat fungsi generik di Java untuk menunggu halaman dimuat, solusi yang mungkin adalah:

  • menjalankan skrip JavaScript dari WebDriver dan menyimpan hasil document.body.innerHTMLdalam variabel string body.
  • bandingkan bodyvariabel dengan versi sebelumnya dari body. jika mereka sama maka setel kenaikan penghitung notChangedCountjika tidak diatur notChangedCountke nol.
  • tunggu sebentar (misalnya 50 ms).
  • jika halaman tidak berubah selama beberapa waktu (misalnya 500 ms), notChangedCount >= 10maka keluar dari loop jika tidak, lakukan loop ke langkah pertama.

Apakah menurut Anda ini solusi yang valid?


findElement () tidak menunggu - apa maksud Anda dengan itu?
Rostislav Matl

1
findElementmenunggu elemen tersedia, tetapi terkadang elemen tersedia sebelum kode javascript diinisialisasi sepenuhnya, itulah mengapa ini bukan pilihan.
psadac

Saya lupa - saya terbiasa menggunakan WebDriverWait dan ExpectedCondition, ini jauh lebih fleksibel.
Rostislav Matl

Jawaban:


73

Jika ada yang benar-benar mengetahui jawaban yang umum dan selalu dapat diterapkan, itu akan diterapkan di mana - mana berabad-abad yang lalu dan akan membuat hidup kita jauh lebih mudah.

Ada banyak hal yang dapat Anda lakukan, tetapi semuanya memiliki masalah:

  1. Seperti yang dikatakan Ashwin Prabhu, jika Anda mengetahui skrip dengan baik, Anda dapat mengamati perilakunya dan melacak beberapa variabelnya di windowatau documentdll. Solusi ini, bagaimanapun, bukan untuk semua orang dan hanya dapat digunakan oleh Anda dan hanya pada kumpulan terbatas. halaman.

  2. Solusi Anda dengan mengamati kode HTML dan apakah itu telah atau belum diubah untuk beberapa waktu tidak buruk (juga, ada metode untuk mendapatkan HTML asli dan tidak diedit secara langsung WebDriver), tetapi:

    • Perlu waktu lama untuk benar-benar menegaskan halaman dan dapat memperpanjang pengujian secara signifikan.
    • Anda tidak pernah tahu apa itu interval yang tepat. Skrip mungkin mengunduh sesuatu yang besar yang membutuhkan lebih dari 500 md. Ada beberapa skrip di halaman internal perusahaan kami yang memerlukan beberapa detik di IE. Komputer Anda mungkin sementara kekurangan sumber daya - katakanlah bahwa antivirus akan membuat CPU Anda bekerja sepenuhnya, maka 500 md mungkin terlalu pendek bahkan untuk skrip non-kompleks.
    • Beberapa skrip tidak pernah selesai. Mereka menyebut diri mereka sendiri dengan beberapa penundaan ( setTimeout()) dan bekerja lagi dan lagi dan mungkin dapat mengubah HTML setiap kali dijalankan. Serius, setiap halaman "Web 2.0" melakukannya. Bahkan Stack Overflow. Anda dapat menimpa metode yang paling umum digunakan dan menganggap skrip yang menggunakannya telah selesai, tetapi ... Anda tidak dapat memastikannya.
    • Bagaimana jika skrip melakukan sesuatu selain mengubah HTML? Itu bisa melakukan ribuan hal, bukan hanya innerHTMLkesenangan.
  3. Ada alat untuk membantu Anda dalam hal ini. Yaitu Progress Listeners bersama dengan nsIWebProgressListener dan beberapa lainnya. Namun, dukungan browser untuk ini sangat buruk. Firefox mulai mencoba mendukungnya dari FF4 dan seterusnya (masih berkembang), IE memiliki dukungan dasar di IE9.

Dan saya rasa saya bisa segera menemukan solusi cacat lainnya. Faktanya adalah - tidak ada jawaban pasti tentang kapan harus mengatakan "sekarang halaman telah selesai" karena skrip abadi melakukan tugasnya. Pilih salah satu yang paling sesuai untuk Anda, tetapi waspadalah terhadap kekurangannya.


31

Terima kasih Ashwin!

Dalam kasus saya, saya harus menunggu eksekusi plugin jquery di beberapa elemen .. khususnya "qtip"

berdasarkan petunjuk Anda, ini bekerja dengan sempurna untuk saya:

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

Catatan: Saya menggunakan Webdriver 2


2
Ini bekerja dengan sangat baik untuk saya juga. Dalam kasus saya, sebuah elemen dimodifikasi oleh Javascript tepat setelah halaman selesai dimuat, dan Selenium tidak menunggu itu secara implisit.
Noxxys

2
Dari mana asalnya Predikat? impor yang mana ??
Amado Saladino

@AmadoSaladino "import com.google.common.base.Predicate;" dari link google lib guava-15.0: mvnrepository.com/artifact/com.google.guava/guava/15.0 ada versi yang lebih baru 27.0 Anda dapat mencobanya!
Eduardo Fabricio

26

Anda harus menunggu Javascript dan jQuery selesai memuat. Jalankan Javascript untuk memeriksa apakah jQuery.activeis 0dan document.readyStateis complete, yang berarti pemuatan JS dan jQuery sudah selesai.

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
Tidak yakin apakah ini akan berhasil dalam setiap keadaan, tetapi berfungsi untuk kasus penggunaan saya
DaveH

1
Ini berfungsi untuk saya juga, tetapi ada bagian yang sulit: Katakanlah halaman Anda sudah dimuat. Jika Anda berinteraksi dengan sebuah elemen [mis. Send a .click () atau .send some keys] dan kemudian Anda ingin memeriksa ketika halaman Anda selesai diproses, Anda perlu menambahkan penundaan: misalnya klik (), tidur (0,5 detik ), tunggu hingga (readyState = 'complete' & jQuery.active == 0). Jika Anda tidak menambahkan tidur, iQuery tidak akan aktif pada waktu pengujian! (butuh beberapa jam untuk mengetahuinya, jadi saya berpikir untuk membagikannya)
Fivos Vilanakis

document.readyState == 'complete' && jQuery.active == 0berfungsi dengan baik tetapi apakah ada yang seperti itu untuk React? Karena pemeriksaan tersebut tidak akan menangkap masih memuat data / komponen reaksi?
Rain9333



7

Jika yang perlu Anda lakukan hanyalah menunggu html pada halaman menjadi stabil sebelum mencoba berinteraksi dengan elemen, Anda dapat melakukan polling DOM secara berkala dan membandingkan hasilnya, jika DOM sama dalam waktu polling yang diberikan, Anda keemasan. Sesuatu seperti ini di mana Anda melewati waktu tunggu maksimum dan waktu antara jajak pendapat halaman sebelum membandingkan. Sederhana dan efektif.

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
Saya ingin mencatat di sini bahwa saya mencatat waktu yang dibutuhkan untuk melakukan polling pada halaman berukuran sedang dua kali, kemudian membandingkan string tersebut di java dan butuh waktu lebih dari 20ms.
TheFreddyKilo

2
Pada awalnya saya menemukan ini sebagai solusi kotor, tetapi tampaknya berfungsi dengan baik dan tidak melibatkan pengaturan variabel sisi klien. Satu peringatan mungkin merupakan halaman yang terus-menerus diubah oleh javascript (yaitu jam javascript) tetapi saya tidak memilikinya sehingga berfungsi!
Peter

4

Kode di bawah ini berfungsi dengan sempurna dalam kasus saya - halaman saya berisi skrip java yang kompleks

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

Sumber - Cara Menunggu Halaman Dimuat / Siap Di Selenium WebDriver


2
Anda harus menggunakan selenium sebagai pengganti loop tersebut
cocorossello

4

Dua kondisi dapat digunakan untuk memeriksa apakah halaman dimuat sebelum menemukan elemen apa pun di halaman:

WebDriverWait wait = new WebDriverWait(driver, 50);

Menggunakan readyState di bawah ini akan menunggu sampai halaman dimuat

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

Di bawah JQuery akan menunggu hingga data belum dimuat

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

Setelah JavaScriptCode ini coba findOut webElement.

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

1
Selenium juga melakukan ini secara default, ini hanya akan menambahkan beberapa waktu eksekusi kode tambahan ke skrip Anda (yang mungkin cukup waktu untuk memuat hal-hal tambahan, tetapi sekali lagi mungkin tidak)
Ardesco

2

Saya meminta pengembang saya untuk membuat variabel JavaScript "isProcessing" yang dapat saya akses (dalam objek "ae") yang mereka setel saat segala sesuatunya mulai berjalan dan jelas ketika sesuatu sudah selesai. Saya kemudian menjalankannya di akumulator yang memeriksanya setiap 100 ms hingga mendapat lima berturut-turut dengan total 500 ms tanpa perubahan apa pun. Jika 30 detik berlalu, saya memberikan pengecualian karena sesuatu seharusnya telah terjadi saat itu. Ini ada di 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

Untuk melakukannya dengan benar, Anda perlu menangani pengecualian.

Inilah cara saya menunggu iFrame. Ini mengharuskan kelas pengujian JUnit Anda meneruskan instance RemoteWebDriver ke objek halaman:

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

CATATAN: Anda dapat melihat seluruh contoh kerja saya di sini .


1

Untuk nodejsperpustakaan Selenium, saya menggunakan potongan berikut. Dalam kasus saya, saya mencari dua objek yang ditambahkan ke jendela, yang dalam contoh ini adalah <SOME PROPERTY>, 10000milidetik batas waktu, <NEXT STEP HERE>adalah apa yang terjadi setelah properti ditemukan di jendela.

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

Anda dapat menulis beberapa logika untuk menangani ini. Saya telah menulis metode yang akan mengembalikan WebElementdan metode ini akan dipanggil tiga kali atau Anda dapat menambah waktu dan menambahkan centang nol untuk WebElementBerikut adalah contoh

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

Ini dari kode saya sendiri:
Window.setTimeout hanya dijalankan saat browser menganggur.
Jadi memanggil fungsi secara rekursif (42 kali) akan memakan waktu 100 md jika tidak ada aktivitas di browser dan lebih banyak lagi jika browser sibuk melakukan hal lain.

    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;

Sebagai bonus, penghitung dapat disetel ulang di document.readyState atau pada panggilan jQuery Ajax atau jika ada animasi jQuery yang berjalan (hanya jika aplikasi Anda menggunakan jQuery untuk panggilan ajax ...)
...

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

...

EDIT: Saya melihat executeAsyncScript tidak berfungsi dengan baik jika halaman baru dimuat dan pengujian mungkin berhenti merespons tanpa batas waktu, lebih baik gunakan ini sebagai gantinya.

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

Tidak tahu bagaimana melakukannya tetapi dalam kasus saya, pemuatan halaman akhir & rendering cocok dengan FAVICON yang ditampilkan di tab Firefox.

Jadi jika kita bisa mendapatkan gambar favicon di webbrowser, maka halaman web sudah terisi penuh.

Tapi bagaimana melakukan ini ....



-1

Begini cara saya melakukannya:

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

Selenium juga melakukan ini secara default, ini hanya akan menambahkan beberapa waktu eksekusi kode tambahan ke skrip Anda (yang mungkin cukup waktu untuk memuat hal-hal tambahan, tetapi sekali lagi mungkin tidak)
Ardesco

-1

Saya suka ide Anda untuk melakukan polling terhadap HTML sampai stabil. Saya dapat menambahkannya ke solusi saya sendiri. Pendekatan berikut ada di C # dan membutuhkan jQuery.

Saya adalah pengembang untuk proyek uji SuccessFactors (SaaS) di mana kami tidak memiliki pengaruh sama sekali terhadap pengembang atau karakteristik DOM di balik laman web. Produk SaaS berpotensi mengubah desain DOM dasarnya 4 kali setahun, jadi perburuan secara permanen mencari cara yang kuat dan berkinerja untuk menguji dengan Selenium (termasuk BUKAN pengujian dengan Selenium jika memungkinkan!)

Inilah yang saya gunakan untuk "siap halaman". Ini bekerja di semua tes saya sendiri saat ini. Pendekatan yang sama juga berhasil untuk aplikasi web Java internal yang besar beberapa tahun yang lalu, dan telah kuat selama lebih dari setahun pada saat saya meninggalkan proyek.

  • Driver adalah instance WebDriver yang berkomunikasi dengan browser
  • DefaultPageLoadTimeout adalah nilai batas waktu dalam ticks (100ns per 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();

Berikut ini, perhatikan urutan metode menunggu PageReady(siap dokumen Selenium, Ajax, animasi), yang masuk akal jika Anda memikirkannya:

  1. memuat halaman yang berisi kode
  2. gunakan kode untuk memuat data dari suatu tempat melalui Ajax
  3. menyajikan data, mungkin dengan animasi

Sesuatu seperti pendekatan perbandingan DOM Anda dapat digunakan antara 1 dan 2 untuk menambahkan lapisan ketahanan lainnya.


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");
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.