Tentang kesesuaian Selenium WebDriverWait

Semakin saya mengenal Selenium WebDriver, semakin saya memiliki pertanyaan mengapa fungsi ini atau itu diterapkan dengan cara ini dan bukan sebaliknya. Dalam pidatonya, "Troubles in Selenium WebDriver," Alexey Barantsev menjelaskan seluk-beluk penerapan alat otomasi ini dan membedakan antara "bug" dan "fitur". Anda akan menemukan banyak hal menarik dalam video, tetapi masih ada beberapa poin (setidaknya bagi saya) di tempat teduh.

Pada artikel ini saya ingin membahas alat yang sering digunakan untuk menunggu acara pada halaman, diimplementasikan menggunakan kelas WebDriverWait dan metode utama Hingga . Saya ingin tahu apakah WebDriverWait diperlukan sama sekali, dan apakah mungkin untuk menolaknya?

Pikiran akan disajikan dalam konteks C #, meskipun saya tidak berpikir bahwa logika implementasi kelas ini akan berbeda untuk bahasa pemrograman lain.

Saat membuat instance WebDriverWait, instance driver dilewatkan ke konstruktor, yang disimpan di bidang input input . Metode Hingga mengasumsikan delegasi yang parameter inputnya harus IWebDriver, yang inputnya adalah instance.

Mari kita lihat kode sumber metode Hingga . Tulang punggung logikanya adalah siklus tanpa akhir dengan dua kondisi untuk keluar: permulaan peristiwa atau batas waktu yang diinginkan. "Barang tambahan" lainnya mengabaikan pengecualian yang telah ditentukan dan mengembalikan objek jika bes tidak bertindak sebagai TResult (lebih lanjut tentang itu nanti).

Keterbatasan pertama yang saya lihat adalah bahwa kita selalu membutuhkan turunan IWebDriver, walaupun di dalam metode Hingga (tepatnya, sebagai parameter input untuk kondisi), kita dapat sepenuhnya mengelola ISearchContext. Memang, dalam kebanyakan kasus, kami mengharapkan beberapa elemen atau perubahan di propertinya dan menggunakan FindElement untuk mencarinya.

Saya mengambil risiko menyatakan bahwa menggunakan ISearchContext akan menjadi lebih logis, karena kode klien (kelas) tidak hanya objek halaman, yang, dalam pencarian untuk anak-anak, ditolak dari akar halaman. Kadang-kadang ini adalah kelas yang menjelaskan elemen komposit yang akarnya adalah elemen lain dari halaman, dan bukan halaman itu sendiri. Contohnya adalah SelectElement , yang menerima referensi ke induk IWebElement di konstruktor.

Mari kita kembali ke masalah inisialisasi WebDriverWait. Tindakan ini membutuhkan turunan driver. Yaitu kami selalu, dengan satu atau lain cara, perlu membuang instance IWebDriver dari luar kode klien, bahkan jika itu adalah kelas dari beberapa elemen komposit (contoh tentang SelectElement), yang sudah menerima "induk". Dari sudut pandang saya, ini tidak perlu.

Tentu saja, kita dapat mendeklarasikan kelas dengan analogi
SearchContextWait : DefaultWait<ISearchContext> 
Tapi jangan terburu-buru. Kami tidak membutuhkannya.

Mari kita lihat bagaimana instance driver dilewati ke kondisi digunakan. Biasanya terlihat seperti ini:

 var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(10)); wait.Until( d => d.FindElements(By.XPath("locator")).Count > 0 ); 

Muncul pertanyaan, mengapa versi "lokal" dari driver diperlukan jika kondisi selalu tersedia dari kode klien? Selain itu, ini adalah contoh yang sama yang dilewatkan sebelumnya melalui konstruktor. Yaitu kode mungkin terlihat seperti ini:

 var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(10)); wait.Until( d => Driver.FindElements(By.XPath("locator")).Count > 0 ); 

Bahkan Simon Stewart menggunakan pendekatan ini dalam pidatonya .

gambar

Dia tidak menulis "d -> d.", Tetapi menulis "d -> driver.", I.e. contoh driver yang diteruskan ke metode diabaikan. Tetapi perlu untuk mengirimkannya, karena ini diperlukan oleh metode tanda tangan!

Mengapa melewati pengemudi di dalam kondisi metode ini? Dimungkinkan untuk mengisolasi pencarian di dalam metode ini, seperti yang diterapkan dalam ExpectedConditions ? Lihatlah implementasi metode TextToBePresentInElement . Atau VisibilitasOfAllElementsLocatedBy . Atau TextToBePresentInElementValue . Pengemudi yang ditransfer bahkan tidak digunakan di dalamnya!

Jadi, pemikiran pertama adalah bahwa kita tidak memerlukan metode Hingga dengan parameter delegasi yang diterima oleh pengemudi.

Sekarang mari kita mencari tahu apakah metode Hingga membutuhkan nilai kembali? Jika bool bertindak sebagai TResult, maka tidak, itu tidak perlu. Memang, jika sukses, Anda akan menjadi kenyataan, dan jika gagal, Anda akan mendapatkan TimeoutException. Apa isi informasi dari perilaku tersebut?

Tetapi bagaimana jika objeknya adalah TResult? Asumsikan konstruksi berikut:

 var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(10)); wait.IgnoreExceptionTypes(typeof(NoSuchElementException)); var element = wait.Until(d => d.FindElement(By.XPath("locator"))); 

Yaitu kami tidak hanya menunggu penampilan elemen, tetapi juga menggunakannya (jika kami telah menunggu), sehingga menghapus satu panggilan yang tidak perlu ke DOM. Bagus

Mari kita lihat lebih dekat ketiga baris kode ini. Di dalam implementasi metode Hingga, itu bermuara pada semacam kesamaan (kode kondisional)

 try { FindElement } catch (NoSuchElementException) {} 

Yaitu pengecualian akan dilemparkan setiap kali sampai elemen muncul di DOM. Karena generasi pengecualian adalah peristiwa yang agak mahal, saya lebih suka menghindarinya, terutama di tempat-tempat yang tidak sulit. Kami dapat menulis ulang kode sebagai berikut:

 var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(10)); var elements = wait.Until(d => d.FindElements(By.XPath("locator"))); 

Yaitu kami menggunakan FindElements, yang tidak membuang pengecualian. Tunggu, akankah desain ini menunggu penampilan elemen? TIDAK! Karena, jika Anda melihat kode sumber , loop tak terbatas selesai dengan segera, segera setelah kondisi mengembalikan non-nol. Dan FindElements jika gagal mengembalikan koleksi kosong, tetapi tidak nol dengan cara apa pun. Yaitu Untuk daftar item, menggunakan Hingga tidak masuk akal.

Ok, daftarnya jelas. Tapi tetap saja, bagaimana mengembalikan elemen yang ditemukan dan tidak membuang pengecualian? Kode mungkin terlihat seperti ini:

 var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(10)); var element = wait.Until(d => d.FindElements(By.XPath("locator")).FirstOrDefault()); 

Dalam hal ini, pada setiap iterasi dari loop, kita tidak hanya akan mendapatkan daftar IWebElement (yang mungkin kosong), tetapi juga mencoba mengekstraksi elemen pertama darinya. Jika elemen masih tidak ditampilkan pada halaman, kita akan mendapatkan null (nilai default untuk objek) dan beralih ke iterasi loop berikutnya. Jika elemen ditemukan, kita akan keluar dari metode dan variabel elemen akan diinisialisasi dengan nilai kembali.

Namun, pemikiran kedua adalah bahwa nilai pengembalian metode Hingga tidak digunakan dalam banyak kasus.

Nilai yang diteruskan tidak perlu, nilai balik tidak digunakan. Apa kegunaan Sampai? Hanya dalam siklus dan frekuensi memanggil metode kondisi? Pendekatan ini telah diimplementasikan dalam C # dalam metode SpinWait.SpinUntil . Satu-satunya perbedaan adalah bahwa ia tidak membuang pengecualian timeout. Ini dapat diperbaiki sebagai berikut:

 public void Wait(Func<bool> condition, TimeSpan timeout) { var waited = SpinWait.SpinUntil(condition, timeout); if (!waited) { throw new TimeoutException(); } } 

Yaitu beberapa baris kode ini dalam banyak kasus menggantikan logika seluruh kelas WebDriverWait. Apakah upaya itu sepadan dengan hasilnya?

Perbarui

Dalam komentar pada artikel, pengguna KSA membuat komentar yang masuk akal tentang perbedaan antara SpinUntil dan Hingga dalam hal frekuensi pelaksanaan kondisi. Untuk WebDriverWait, nilai ini dapat disesuaikan dan standarnya adalah 500 milidetik. Yaitu metode Hingga memiliki penundaan antara pengulangan loop. Sedangkan untuk SpinUntil, logikanya sedikit rumit dan seringkali menunggu tidak melebihi 1 milidetik.

Dalam praktiknya, ini menghasilkan situasi di mana, sambil menunggu elemen yang muncul dalam 2 detik, metode Unitl melakukan 4 iterasi, dan metode SpinUntil membutuhkan sekitar 200 atau lebih.

Mari kita buang SpinUntil dan tulis ulang metode Tunggu sebagai berikut.

 public void Wait(Func<bool> condition, TimeSpan timeout, int evaluatedInterval = 500) { Stopwatch sw = Stopwatch.StartNew(); while (sw.Elapsed < timeout) { if (condition()) { return; } Thread.Sleep(evaluatedInterval); } throw new TimeoutException(); } 


Kami menambahkan beberapa baris kode, dan pada saat yang sama kami semakin dekat dengan logika metode Hingga.

Source: https://habr.com/ru/post/id443754/


All Articles