Revolusi atau evolusi Model Obyek Halaman?

Halo semuanya! Nama saya Artyom Sokovets . Saya ingin berbagi terjemahan artikel saya di Atlas: reinkarnasi dari kerangka HTML Elements, yang menyajikan pendekatan yang sama sekali berbeda untuk bekerja dengan Page Object.

Sebelum masuk ke detail, saya ingin bertanya: berapa banyak pembungkus untuk Page Object yang Anda tahu? Elemen Halaman, ScreenPlay, Komponen yang Dapat Dimuat, Rantai doa ...

Dan apa yang akan terjadi jika kita mengambil Page Object dengan implementasi pada antarmuka, kencangkan Proxy Pattern dan tambahkan sedikit fungsi Java 8?

Jika tertarik, saya sarankan beralih ke kucing.



Pendahuluan


Saat menggunakan pola desain PageObject standar, sejumlah masalah muncul:

Duplikasi elemen

public class MainPage { @FindBy(xpath = ".//div[@class = 'header']") private Header header; } public class AnyOtherPage { @FindBy(xpath = ".//div[@class = 'header']") private Header header; } 

Di sini, blok Header digunakan dalam berbagai kelas PageObject.

Kurangnya parameterisasi untuk elemen

 public class EditUserPage { @FindBy(xpath = "//div[text()='Text_1']") private TextBlock lastActivity; @FindBy(xpath = "//div[text()='Text_2']") private TextBlock blockReason; } 

Contoh ini menjelaskan elemen-elemen dari halaman pengeditan pengaturan pengguna. Dua elemen TextBlock berisi pelacak yang hampir identik dengan perbedaan hanya pada nilai teks ("Text_1" dan "Text_2").

Jenis kode yang sama

 public class UserPage { @FindBy(xpath = "//div[text()='']/input") private UfsTextInput innerPhone; @FindBy(xpath = "//div[text()='Email']/input") private UfsTextInput email; @FindBy(xpath = "//button[text()='']") private UfsButton save; @FindBy(xpath = "//button[text()='']") private UfsButton toUsersList; } 

Dalam pekerjaan sehari-hari, Anda dapat menemukan Obyek Halaman, yang terdiri dari banyak baris kode dengan elemen yang sama. Di masa depan, kelas-kelas semacam itu "tidak nyaman" untuk dipertahankan.

Kelas besar dengan langkah-langkah

 public class MainSteps { public void hasText(HtmlElement e, Matcher m) public void hasValue(HtmlElement e, Matcher m) public void linkContains(HtmlElement e, String s) public void hasSize(List<HtmlElement> e, Matcher m) public void hasItem(List<HtmlElement> e, Matcher m) //... } 

Seiring waktu, kelas langkah untuk bekerja dengan elemen tumbuh. Diperlukan lebih banyak perhatian agar tidak ada metode rangkap.

Panduan Anda ke dunia Obyek Halaman


Reinkarnasi kerangka HTML Elements ditujukan untuk menyelesaikan masalah di atas, mengurangi jumlah baris kode untuk proyek uji, pekerjaan yang lebih bijaksana dengan daftar dan harapan, serta menyempurnakan alat untuk diri sendiri berkat sistem ekstensi.

Atlas adalah kerangka kerja Java generasi baru untuk mengembangkan uji otomatis UI dengan penerapan pola Obyek Halaman melalui antarmuka. Pendekatan ini memberikan kemungkinan pewarisan berganda dalam konstruksi pohon elemen, yang akhirnya memberikan kode ringkas untuk autotest Anda.

Inovasi utama kerangka kerja ini adalah penggunaan antarmuka alih-alih kelas standar.

Seperti inilah deskripsi beranda github.com:

 public interface MainPage extends WebPage, WithHeader { @FindBy("//a[contains(text(), 'Or start a free)]") AtlasWebElement trial(); } 

Kode di atas menjelaskan halaman utama situs GitHub dengan satu elemen dan banyak pewarisan dari lapisan WebPage dan WithHeader (contoh diberikan hanya untuk tujuan pendidikan, sehingga sebagian besar elemen web dihilangkan).

Kerangka arsitektur


Atlas saat ini terdiri dari tiga modul:

  • atlas-core
  • atlas-webdriver
  • atlas-appium

Atlas-core menjelaskan fungsionalitas dasar untuk memproses Objek Halaman menggunakan antarmuka. Ide menggunakan antarmuka diambil dari alat Retrofit yang terkenal.



Dua modul atlas-webdriver dan atlas-appium lainnya digunakan untuk mengembangkan web UI otomatis dan skrip seluler UI. Titik masuk utama untuk mendeskripsikan halaman web adalah antarmuka WebPage, dan untuk layar seluler - Layar. Secara konseptual, atlas-webdriver dan atlas-appium dibangun di atas ekstensi (paket * .ekstensi).

Item




Alat ini dilengkapi dengan dua kelas khusus untuk bekerja dengan elemen UI (analog dari kelas WebElement).



AtlasWebElement dan AtlasMobileElement dilengkapi dengan metode should dan waitUntil . (pertimbangan metode ini akan lebih jauh dalam artikel).

Alat ini menyediakan kemampuan untuk membuat komponen Anda sendiri dengan memperluas kelas-kelas di atas, yang memungkinkan Anda membuat elemen lintas-platform.

Fitur utama


Mari kita pertimbangkan secara lebih rinci fungsi alat ini:



Antarmuka bukan kelas

Saat menjelaskan PageObjects standar, antarmuka digunakan sebagai ganti kelas.

 public interface MainPage extends WebPage, WithHeader { @FindBy("//a[contains(text(), 'Or start a free trial of Enterprise Server')]") AtlasWebElement trial(); } 

Contoh ini menjelaskan tautan di halaman awal GitHub.

Parameterisasi elemen

Bayangkan bahwa kita memiliki formulir dengan bidang:



Untuk menggambarkannya, Anda perlu membuat 11 variabel dengan penjelasan @FindBy dan, jika perlu, deklarasikan pengambil.

Menggunakan Atlas, Anda hanya perlu satu elemen AtlasWebElement yang diparameterisasi.

 public interface MainPage extends WebPage { @FindBy("//div[text()='{{ text }}']/input") AtlasWebElement input(@Param("text") String text); } 

Kode pengujian otomatis adalah sebagai berikut:

 @Test public void simpleTest() { onMainPage().input("First Name").sendKeys("*"); onMainPage().input("Postcode").sendKeys("*"); onMainPage().input("Email").sendKeys("*"); } 

Kami membuka halaman yang diinginkan, memanggil metode dengan parameter dan melakukan tindakan yang diperlukan dengan elemen tersebut. Metode dengan parameter menjelaskan bidang tertentu.

Warisan berganda

Telah disebutkan sebelumnya bahwa sebuah blok (misalnya, Header), yang digunakan dalam Objek Halaman yang berbeda, adalah duplikasi kode.

Ada header github.



Kami menggambarkan blok ini (sebagian besar elemen web dihilangkan):

 public interface Header extends AtlasWebElement { @FindBy(".//input[contains(@class,'header-search-input')]") AtlasWebElement searchInput(); } 

Selanjutnya, buat layer yang dapat dihubungkan ke halaman mana saja:

 public interface WithHeader { @FindBy("//header[contains(@class,'Header')]") Header header(); } 

Kami memperluas halaman utama dengan blok header.

 public interface MainPage extends WebPage, WithHeader { @FindBy("//a[contains(text(), 'Or start a)]") AtlasWebElement trial(); } 

Secara umum, Anda dapat membuat lebih banyak layer dan menghubungkannya ke halaman yang diinginkan. Pada contoh di bawah ini, sambungkan header, footer, lapisan sidebar dari halaman utama.

 public interface MainPage extends WithHeader, WithFooter, WithSidebar {} 

Mari kita lanjutkan. Header berisi 4 tombol, 3 menu drop-down dan satu bidang pencarian:



Mari kita buat elemen Tombol kita sendiri dan jelaskan empat tombol dengan satu elemen.

 public interface Button extends AtlasWebElement { @FindBy(".//a[contains(., '{{ value }}')]") AtlasWebElement selectButton(@Param("value") String value); } 

Hubungkan tombol tombol ke lapisan header. Jadi, kami memperluas fungsionalitas header GitHub.

 public interface Header extends WithButton { … } 

Elemen Tombol terpisah dapat dihubungkan ke berbagai lapisan situs web dan dengan cepat mendapatkan elemen yang diinginkan pada halaman yang diinginkan.

Contoh:

 @Test public void simpleTest() { onMainPage().open("https://github.com"); onMainPage().header().button("Priing").click(); } 

Pada baris kedua pengujian, tajuk situs diakses, lalu kita memanggil tombol parameter dengan nilai "Harga" dan klik.

Mungkin ada beberapa elemen di situs yang diuji yang mengulang dari halaman ke halaman. Agar tidak menggambarkan semuanya dengan pendekatan Objek Halaman standar, Anda bisa menggambarkannya sekali dan menghubungkannya jika perlu. Menghemat waktu dan jumlah baris kode jelas.



Metode standar

Java 8 memperkenalkan metode standar, yang digunakan untuk menentukan fungsi yang diinginkan.

Misalkan kita memiliki elemen "berbahaya": misalnya, kotak centang yang aktif atau tidak aktif. Banyak skenario melewatinya. Diperlukan untuk mengaktifkan kotak centang jika dinonaktifkan:

 if(onMainPage().rentFilter().checkbox("").getAttribute("class").contains("disabled")) { onMainPage().rentFilter().checkbox("").click(); } 

Agar tidak menyimpan semua kode ini di kelas langkah, dimungkinkan untuk menempatkannya di sebelah elemen sebagai metode default.

 public interface Checkbox extends AtlasWebElement { @FindBy("//...") AtlasWebElement checkBox((@Param("value") String value); default void selectCheckbox(String value) { if (checkBox(value).getAttribute("class").contains("disabled")) { checkBox(value).click(); } } } 

Sekarang langkah dalam tes akan terlihat seperti ini:

 onMainPage().rentFilter().selectCheckbox(""); 

Contoh lain di mana Anda ingin menggabungkan pembersihan dan mengetik karakter dalam bidang.

 onMainPage().header().input("GitHub").clear(); onMainPage().header().input("GitHub").sendKeys("*"); 

Tentukan metode yang membersihkan bidang dan mengembalikan elemen itu sendiri:

 public interface Input extends AtlasWebElement { @FindBy("//xpath") AtlasWebElement input(@Param("value") String value); default AtlasWebElement withClearInput(String value) { input(value).clear(); return input(value); } } 

Dalam metode pengujian, langkahnya adalah sebagai berikut:

 onMainPage().header().withClearInput("GitHub").sendKeys("Atlas"); 

Dengan cara ini, perilaku yang diinginkan dalam elemen dapat diprogram.

Coba lagi

Atlas memiliki percobaan ulang bawaan. Anda tidak perlu khawatir tentang pengecualian seperti NotFoundException , StaleElementReferenceException dan WebDriverException , dan Anda juga bisa lupa menggunakan ekspektasi eksplisit dan implisit dari Selenium API.

 onSite().onSearchPage("Junit 5").repositories().waitUntil(hasSize(10)); 

Jika pada titik tertentu dalam rantai Anda menangkap pengecualian, fase berulang dari awal.

Dimungkinkan untuk secara mandiri menyesuaikan interval waktu di mana Anda dapat mengulangi, atau frekuensi pengulangan.

 Atlas atlas = new Atlas(new WebDriverConfiguration(driver)) .context(new RetryerContext(new DefaultRetryer(3000L, 1000L, Collections.singletonList(Throwable.class)))); 

Kami berharap selama tiga detik dengan tingkat pemungutan suara sekali per detik.

Kami juga dapat menyesuaikan tunggu untuk item tertentu menggunakan coba lagi anotasi. Untuk semua elemen, pencarian akan terjadi dalam 3 detik, dan dalam kasus satu akan 20.

 @Retry(timeout = 20_000L, polling = 2000L) @IOSFindBy(xpath = "//XCUIElementTypeSearchField[@name='Search Wikipedia']") @AndroidFindBy(xpath = "//*[contains(@text, 'Search Wikipedia')]") AtlasMobileElement searchWikipedia(); 

Bekerja dengan daftar

Di luar kotak, alat ini menyediakan pekerjaan dengan daftar. Apa artinya ini? Ada bidang dengan tag input tempat kita memasukkan teks, lalu daftar drop-down muncul, elemen tidak segera muncul.



Untuk kasus seperti itu ada entitas ElementsCollection. Dengan bantuannya ada pekerjaan dengan daftar.

 public interface ContributorsPage extends WebPage, WithHeader { @FindBy(".//ol[contains(@class, 'contrib-data')]//li[contains(@class, 'contrib-person')]") ElementsCollection<RepositoryCard> hovercards(); } 

Penggunaan:

 onSite().onContributorsPage().hovercards().waitUntil(hasSize(4)); 

Dimungkinkan juga untuk memfilter elemen dan mengonversinya ke daftar jenis lain.

Penegasan yang cerdas

Seperti yang disebutkan sebelumnya, entitas AtlasWebElement dan AtlasMobileElement menggunakan metode should, waitil, hingga bekerja dengan cek (pernyataan).

MetodeDeskripsi
harus (...)Melakukan berbagai pernyataan (pemeriksaan) dengan suatu elemen. Melontar Pernyataan Keteguhan
tungguSampai (...)Melakukan berbagai pernyataan (pemeriksaan) dengan suatu elemen. Melempar RuntimeException

Mengapa ini dilakukan? Untuk menghemat waktu saat menjalankan laporan yang berjalan untuk skrip otomatis. Sebagian besar pemeriksaan fungsional dilakukan pada akhir skrip: mereka menarik bagi spesialis pengujian fungsional, dan pemeriksaan menengah ke spesialis pengujian otomatis. Oleh karena itu, jika fungsionalitas produk tidak berfungsi, masuk akal untuk melempar pengecualian AssumptionError, jika tidak RuntimeException.





Allure akan segera menunjukkan apa yang sedang kita hadapi: apakah kita memiliki cacat produk (spesialis FT mengambil pekerjaan), atau autotest rusak (spesialis AT mengerti).

Model Ekstensi



Pengguna memiliki kesempatan untuk mendefinisikan kembali fungsionalitas dasar alat atau mengimplementasikan fungsinya sendiri. Model ekstensi Atlas mirip dengan model ekstensi JUnit 5. Modul atlas-webdriver dan atlas-appium dibuat berdasarkan ekstensi. Jika Anda tertarik, lihat kode sumbernya.

Mari kita periksa contoh abstrak: diperlukan untuk mengembangkan tes UI untuk Internet Explorer 11 (di beberapa tempat masih digunakan). Ada kalanya klik standar pada elemen tidak berfungsi, maka Anda dapat menggunakan klik-JS. Anda memutuskan untuk sementara mengesampingkan klik pada seluruh proyek pengujian.

 onMainPage().header().button("en").click(); 

Bagaimana cara melakukannya?

Buat ekstensi yang mengimplementasikan antarmuka MethodExtension.

 public class JSClickExt implements MethodExtension { @Override public Object invoke(Object proxy, MethodInfo methodInfo, Configuration config) { final WebDriver driver = config.getContext(WebDriverContext.class) .orElseThrow(() -> new AtlasException("Context doesn't exist")).getValue(); final JavascriptExecutor js = (JavascriptExecutor) driver; js.executeScript("arguments[0].click();", proxy); return proxy; } @Override public boolean test(Method method) { return method.getName().equals("click"); } } 

Kami mendefinisikan kembali dua metode. Dalam metode uji (), kami menetapkan bahwa kami mengganti metode klik. Metode memanggil mengimplementasikan logika yang diperlukan. Sekarang klik pada elemen akan terjadi melalui JavaScript.

Kami menghubungkan ekstensi sebagai berikut:

 atlas = new Atlas(new WebDriverConfiguration(driver, "https://github.com")) .extension(new JSClickExt()); 

Dengan bantuan ekstensi, Anda dapat membuat pencarian untuk elemen-elemen dalam database dan mencari fitur menarik lainnya - semuanya tergantung pada imajinasi dan kebutuhan Anda.

Titik masuk tunggal ke PageObjects (WebSite)

Alat ini memungkinkan Anda untuk menyimpan semua Halaman Anda di satu tempat dan hanya bekerja melalui entitas Situs di masa depan.

 public interface GitHubSite extends WebSite { @Page MainPage onMainPage(); @Page(url = "search") SearchPage onSearchPage(@Query("q") String value); @Page(url = "{profile}/{project}/tree/master/") ProjectPage onProjectPage(@Path("profile") String profile, @Path("project") String project); @Page ContributorsPage onContributorsPage(); } 

Selain itu, mungkin bagi Page untuk mengatur url cepat, parameter kueri, dan segmen jalur.

 onSite().onProjectPage("qameta", "atlas").contributors().click(); 

Baris di atas berisi dua segmen jalur (qameta dan atlas), yang diterjemahkan menjadi github.com/qameta/atlas/tree/master . Keuntungan utama dari pendekatan ini adalah memungkinkan untuk segera membuka halaman yang diinginkan tanpa mengkliknya.

 @Test public void usePathWebSiteTest() { onSite().onProjectPage("qameta", "atlas").contributors().click(); onSite().onContributorsPage().hovercards().waitUntil(hasSize(4)); } 

Bekerja dengan elemen seluler

Bekerja dengan elemen seluler (AtlasMobileElement) mirip dengan bekerja dengan elemen web AtlasWebElement. Selain itu, tiga metode telah ditambahkan ke AtlasMobileElement: menggulir layar ke atas / bawah (scrollUp / scrollDown) dan mengklik item dengan penahan (longPress).

Biarkan saya memberi Anda contoh layar utama aplikasi Wikipedia. Satu elemen dijelaskan untuk platform iOS dan Android. Mereka juga menggambarkan tombol parameter.

 public interface MainScreen extends Screen { @Retry(timeout = 20_000L, polling = 2000L) @IOSFindBy(xpath = "//XCUIElementTypeSearchField[@name='Search Wikipedia']") @AndroidFindBy(xpath = "//*[contains(@text, 'Search Wikipedia')]") AtlasMobileElement searchWikipedia(); @IOSFindBy(id = "{{ value }}") AtlasMobileElement button(@Param("value") String value); } 

Tes terlihat dengan cara yang sama:

 public void simpleExample() { onMainScreen().searchWikipedia().click(); onSearchScreen().search().sendKeys("Atlas"); onSearchScreen().item("Atlas LV-3B").swipeDownOn().click(); onArticleScreen().articleTitle().should(allOf(displayed(), text("Atlas LV-3B"))); } 

Pada contoh di atas, kita membuka halaman utama Wikipedia, klik pada bilah pencarian, masukkan teks Atlas, lalu gulir ke item daftar dengan nilai Atlas LV-3B dan pergi ke tampilan. Baris terakhir memeriksa apakah judul ditampilkan dan berisi nilai yang diinginkan.

Pendengar

Pencatatan aktivitas dapat diimplementasikan menggunakan pendengar khusus (antarmuka pendengar). Setiap metode memiliki empat peristiwa ketika dipanggil: Sebelum , Lulus , Gagal . Setelah .



Menggunakan antarmuka ini, Anda dapat mengatur pelaporan. Di bawah ini adalah contoh dari Allure Listener, yang dapat ditemukan di tautan .



Selanjutnya, hubungkan pendengar ketika menginisialisasi kelas Atlas.

 atlas = new Atlas(new WebDriverConfiguration(driver)).listener(new AllureListener()); 


Dengan cara di atas, dimungkinkan untuk membuat pendengar untuk berbagai sistem pelaporan (misalnya, untuk ReportPortal).

Koneksi



Untuk mengotomatiskan web UI, cukup mendaftarkan dependensi atlas-webdriver dan menunjukkan versi terkini (versi 1.6.0 relevan pada saat penulisan teks ini).

Maven:
  <dependency> <groupId>io.qameta.atlas</groupId> <artifactId>atlas-webdriver</artifactId> <version>${atlas.version}</version> </dependency> 


Gradle:
 dependencies { ompile 'io.qameta.atlas:atlas-webdriver:1.+' } 


Kami melakukan hal yang sama jika Anda ingin mengotomatiskan UI Mobile.

Maven:
 <dependency> <groupId>io.qameta.atlas</groupId> <artifactId>atlas-appium</artifactId> <version>${atlas.version}</version> </dependency> 


Gradle:
 dependencies { ompile 'io.qameta.atlas:atlas-appium:1.+' } 


Gunakan



Setelah Anda menambahkan ketergantungan pada proyek Anda, Anda harus menginisialisasi instance kelas Atlas.

 @Before public void startDriver() { driver = new ChromeDriver(); atlas = new Atlas(new WebDriverConfiguration(driver)); } 

Kami meneruskan ke konstruktor Atlas instance dari konfigurasi, serta driver.

Saat ini ada dua konfigurasi: Konfigurasi WebDriverConfiguration dan AppiumDriverConfiguration. Setiap konfigurasi berisi ekstensi default spesifik.

Selanjutnya, kita mendefinisikan metode yang akan membuat semua PageObject.

 private <T extends WebPage> T onPage(Class<T> page) { return atlas.create(driver, page); } 


Contoh kasus uji sederhana:

 @Test public void simpleTest() { onPage(MainPage.class).open("https://github.com"); onPage(MainPage.class).header().searchInput().sendKeys("Atlas"); onPage(MainPage.class).header().searchInput().submit(); } 

Kami membuka situs, beralih ke lapisan header, mencari bidang teks (input pencarian) di dalamnya, masukkan teks dan tekan enter.

Ringkasan


Sebagai kesimpulan, saya ingin mencatat bahwa Atlas adalah alat yang fleksibel dengan fitur-fitur hebat. Ini dapat disesuaikan untuk proyek uji tertentu dengan cara yang nyaman bagi tim Anda dan Anda. Kembangkan tes lintas platform, dll.

Klip video laporan dari Heisenbug , Selenium Camp, dan Nexign QA Meetup tersedia . Ada obrolan Telegram @atlashelp.

Dengan menggunakan alat ini, Anda dapat mengurangi sejumlah besar baris kode (diuji pada proyek oleh perusahaan-perusahaan seperti Yandex, SberTech dan Tinkoff).

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


All Articles